本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑 : b% ^& X; u2 j
% t6 a. j8 n+ u1 g) i7 E没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。
; o1 L5 J) {+ a
1 l$ ^' o3 I3 Y
: W m& d' x7 d) f3 U9 k5 C7 @发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。
: O0 d/ N& H* @3 Y1 Q
, Q. x" `$ w+ U. K8 d- K5 G% z买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。
9 o/ F2 V( Y' E0 f2 e为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?
2 ]0 j: Z6 _, ?7 d然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。
' v1 x( X% s, U2 [& U' b8 ~$ t; j5 Y) S( x; z
+ [1 S* M' H0 ^0 u% |) E) Z, U# J
2 ~: d* v" g+ V1 B( H最后把按键、导电橡胶和电路板装上去。! G' E- C& @0 j( f/ ~( Z( {0 Y
精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。. |+ i2 H- [" X( t+ r4 R
. ~$ M h: A& K
! [# z b0 O9 p: m6 [. Q5 o) @& `
完工!
4 W% ^- D. N2 T" I+ O+ @. M" Q, f
# R; d) Y4 X! S! k0 X* [/ {" D9 F w: \) Z2 |
这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。, s# q7 `% z, T; T# @- {
7 ^0 f/ Z+ J- q
7 ~1 G( F# q2 F9 Z接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。
0 x3 J# H: W+ j' |- [' a
: [2 R s$ W! v1 R9 l, m' S4 _8 t: t' Y7 l% i2 l! g! M& Z4 T
3 i- u0 b: {. D9 C! B+ ?电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。6 t x8 ?- n/ H
1 a% k' d; V ?6 H& k7 ^实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。8 v# x E1 k/ y
) I$ p" c7 f8 E2 x4 {& q$ y3 [: a( G这是电路图,有兴趣的朋友可以参考。
: \. ?9 C: R5 s+ i: B6 W$ k/ U) z* `. W. l( V. m* c
- M$ M$ K8 a' `1 m* y- Y2 y2 J% c
我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!
& y/ h( ~4 {+ w! w |8 X" h q编译器:Keil 51。
& A6 G1 m( ~+ x+ o" G9 x+ f9 N. o8 T8 S, g. P( _2 x; ~( {4 G+ }3 {
发射端:
" r1 S' O% A" ]) {$ U- #include <INTRINS.H>% t: h) Y, r% t+ \$ y& n- u; T* V% W" k
- #include "STC15W.h"
0 K7 b4 T: Q- H
: A- S7 N) K1 M8 Y- , v/ h1 J% n f# F8 n1 H! ?
- /*% V# q% D, t% l" V
- --------------------------
0 A6 A) z; B- Z0 U* _ - |1 (OUT) \__/ (INT1) 8|
- f0 X2 ~0 ?1 m - | |
0 Q# w3 ^- `. ]5 L3 s) _- p+ s3 i' S - |2 (VCC) (INT0) 7|
; H# n) T* i" t, Y1 W& } - | STC15W204S |
, R2 s5 L6 g G! s - |3 (LED) (TXD) 6|2 q3 P" z4 ]9 H% r+ m2 h& g
- | |+ Y7 Y2 n! u7 y/ q- g
- |4 (GND) (RXD) 5|3 | z- e) u9 ^ q- m7 v! w
- --------------------------
4 q+ G3 q6 P) x - + M, I+ \% t" `- @9 ]
- LED ---|<|---[===]--- VCC$ `! Z9 y/ B4 b
- Red 330
! Q6 g: m$ b, L$ a ]* T - 5 q' p6 i; H) T9 A) F1 k P' f" m
- Fosc = 6MHz
* d: R8 Z& X j6 h/ n6 m) y+ q - */& N8 m" R2 `6 J) |5 y% ?
$ }2 X, P: W7 y6 u4 M' U
. |) J% F1 G6 b2 Q2 A+ S- // 矩形波输出脚
/ m0 C7 Y# {4 ` - sbit OUT = P5^4;
# R5 u9 l1 u$ s+ O: c. E - // 低压指示灯引脚
5 J0 n0 J4 c9 T: q' y - sbit LED = P5^5;
& G3 H0 x$ m6 K - " Q* a3 x5 g+ |2 Z! \2 c
- // 停机标志位
) t2 t" v: l8 }# x) k$ C1 L) b4 C - bit isReadyToHalt = 0;, c. {, }. u1 ~
- ) ~2 f* _# n' O; w8 t
- // 矩形波次数累计
& q6 u1 ` p' a - static volatile unsigned char count = 0;
+ X/ T1 Y+ [' k+ ?. U - 2 k' `3 l* q F* X% g+ @& H
" F% _0 `% l% w: b) W0 b- static void GPIO_Init(){! A1 h" g5 k( z2 j9 y: z7 [
- // P3口设置为准双向,默认靠内部弱上拉输出高电平5 o2 `/ p/ ~, z' V7 x' U
- P3M0 = 0;( ~6 v2 b1 s- z4 u( k0 U P$ C( f2 Z# K
- P3M1 = 0;
3 F$ y3 |% U5 P8 S/ t( f - P3 = 0xFF;! P0 o: H% ?, D6 j2 m
- // P5口设置为准双向,默认靠内部弱上拉输出高电平% ]5 Y1 e0 x U9 r
- P5M0 = 0;
: L. K, Q1 l# t/ E2 X/ Q0 O - P5M1 = 0;
" q: |3 e5 n# X m - P5 = 0xFF;" u. |0 U A4 ^
- }
# _2 r4 o0 x+ u7 V( W- ^3 {- c8 H - 6 a. [* v; r+ W# u
- static void Timer0_Init(){: x1 a& S7 W" i9 i" j
- // 16毫秒@6.000MHz- A" }3 h+ \+ e( A* S: w) Y+ P
- AUXR &= 0x7F; // 定时器时钟12T模式* Y: l$ ^# T" `! Z) g
- TMOD &= 0xF0; // 设置定时器模式
9 F+ c! f- z3 O/ J4 Q" g2 q - TL0 = 0xC0; // 设置定时初值) P& D6 w" `& Y& q
- TH0 = 0xE0; // 设置定时初值
) p h+ u5 x, `) \ ~ - TF0 = 0; // 清除TF0标志
3 J6 x' b( e+ D& l( ^& t9 C - TR0 = 1; // 定时器0开始计时7 f- N6 W/ \* Z/ g! j
- }# ^1 u0 a8 W- q3 Z; ~: m
- 1 D9 n6 J; l# w- j: m$ \1 k
- static void Interrupt_Init(){% K) G4 C' _2 d( I
- // INT0中断触发类型为下降沿触发+ Z H6 Z& h+ l: j& |8 f
- IT0 = 1;. N" k7 A: `! p2 t* I: c2 u7 V
- // 允许INT0中断! \( V+ P" ~( i
- EX0 = 1;' T1 G- @1 ?: [3 U6 f5 {6 v
- // INT1中断触发类型为下降沿触发
% S" i/ |4 s: _% @3 p - IT1 = 1;/ O/ f3 n+ H( K# S/ n" j1 m4 {
- // 允许INT1中断+ n/ E9 C5 m! C& K1 w) Z' F$ \
- EX1 = 1;
1 F' a+ ~5 X7 p" b8 P: a - // 允许定时器0中断! i4 p1 ]8 n! i9 Z [2 e- X
- ET0 = 1;
7 f! [# q ^2 @" F/ S+ D - // 允许全局中断% l$ ?% x, V7 w" U S- p
- EA = 1;
/ w( p0 L# [7 \ - }
5 r. r* H! G5 s7 x
3 j4 n! y* u o. j. s H6 C: Q8 g- static void INT0_Isr() interrupt 0{/ h' ?+ b, V$ g- I& y
- // 矩形波次数累计归零
^) C3 e- x/ l$ }7 p- M - count = 0;
$ @2 X# }3 V1 J. A8 t - // 重开定时器0中断
& L' ~+ w2 S* Y' \9 U% z) C7 P - ET0 = 1;
; K' D( s9 Q3 T. Y, m8 h7 b: |$ G - }% ~3 [6 z8 I7 T' @
- + c' B. A& [, o: _% C
- static void Timer0_Isr() interrupt 1{& m# N$ T6 J' |- S- c
- // 输出矩形波
2 K. c% ~* Y4 K9 ?- a; M# C - OUT = ~OUT;: l6 z+ X/ W" r* v3 W
- // 累计矩形波次数
% v2 v( i9 S; Z2 P - count++;
: ?5 X7 M" f/ h* W$ [ - // 超过250个矩形波,准备停机! o; _7 E4 X7 x- X( V* q
- if(count > 250){5 ` `9 [; N' {( q8 s( j. Z
- count = 0;
8 y% K. k7 c1 y. l - isReadyToHalt = 1;6 m! s7 T9 K- x% w
- }
2 B3 z, I, E! h# O; U - }! O* |$ d7 b2 E" O& ~$ I9 X
) G, K" r4 w. v4 z7 g* a( K# @- static void INT1_Isr() interrupt 2{7 N, U H" B; d! L& J2 L- ?# K5 V' a
- // 矩形波次数累计归零
* Z( d0 F9 ?' Y4 t - count = 0;. t9 c$ [, v+ C2 Q" v
- // 重开定时器0中断5 O! d5 I5 |1 Q
- ET0 = 1;6 S+ n: y) J7 A+ `, H
- }
: j+ X+ V5 y0 ?; |0 t+ `( I" Q
4 N, @+ R9 |9 ]; C- , X: k, E9 s' L
- void main(){$ ]1 f+ _. R' H! H
- // 初始化P3和P5口
' M1 Y; C% V( V( @. Y0 c! k - GPIO_Init();
5 a& N# L9 f& r8 Y* m7 o& w8 x5 P - // 初始化定时器02 i% ], L: N3 ~( l& w& M2 l, q
- Timer0_Init();4 K- i, g1 @- X+ h+ M' S! B
- // 初始化中断
: [( J# ^$ I+ J5 |, I7 R' H - Interrupt_Init();
* c4 y! M6 [4 @0 h. K8 a/ g, O+ E6 @ - $ i+ i8 K! { ?* l' F( u
- while(1){
) L6 h) q) p1 _* {* s- i4 z+ ] - // 检测电量
9 B2 n5 {8 G6 d6 U4 t5 { - if(PCON & (1 << 5)){; T- p x1 Y6 ?* p0 o% V* w; r5 R
- // 如果电量低,低压指示灯亮
# _1 R4 @, N3 y2 g$ u3 M - LED = 0;
$ E% q4 S% ?3 M3 h+ H( W - // 清电量检测硬件标志位
( c* p/ i$ h0 i - PCON &= ~(1 << 5);! m* c* p4 D% t' ]. K6 j m
- }else{3 U! R5 O1 r6 v8 p4 e& h
- // 电量正常,低压指示灯灭4 ]- t+ r. z$ M; ^
- LED = 1;) V2 o# @3 T4 X4 N
- }# u3 x7 B& \* B1 j5 d+ T- l! y
b$ Z) v) }% c$ q- // 检测到停机标志7 R' @6 Q( X% y4 z9 f& T
- if(isReadyToHalt == 1){$ F/ G4 s, [6 p/ p$ H$ N
- // 暂时关闭定时器0中断
" f( G$ r; ^" i- z* s3 P - ET0 = 0;2 ~9 ^1 I3 `9 E0 u$ B, u% J) Q4 a
- // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU
% B0 E8 ?, n1 [ - OUT = 0;
1 J, w; a _1 N- m% I7 Q - // 停机之前先把低压指示灯灭掉,以省电6 W2 w1 q* V5 m, ]6 u; k
- LED = 1;
1 Y" i$ b( I8 e& |0 U1 N3 t8 i - // 进入停机模式
) r1 U* l/ O; w: i. O7 H' u! ]! K0 `: N - PCON |= 0x02;
4 D! d3 h3 X b& @ - _nop_();
9 E6 j% z% y9 ^+ c( Z0 O - _nop_();; ?* F5 C/ g, ]. i6 s6 a. H
- _nop_();
! |( ]- f% R% E1 [' E. s( n - _nop_();3 D8 j( c3 u! G+ i: r
- // 唤醒,清标志位
) Q1 G* g% B6 O4 e - isReadyToHalt = 0;" b" w; o$ T( W! W) _+ N$ o4 Z4 W% ?
- // 重开定时器0中断
5 z3 M. S/ [3 I/ q& W - ET0 = 1;
; |1 |$ ~$ M5 ]+ o# |0 }* z - }+ T0 j0 U$ e9 T
- }
3 s' ?% g9 q/ g$ T( e4 R - }
- J+ T. ^) D6 x- z
复制代码 " v0 u# a. }$ z) z+ J9 g
硬件参数配置:* X2 K8 e0 }: M# P/ E
7 J; }9 \0 f( J. V0 G1 ~' _1 g
7 |( R( d4 g6 U0 H* J7 o接收端:
" ~* X" p9 s2 n" Q1 M% ?1 L" k- #include <INTRINS.H>
; G* M! V# r4 r# u% V1 A - #include "STC15W.h"7 T0 i* N3 a; u& |, q
' u) l8 [6 l- P9 A
2 Q( H6 o3 Y" d9 U0 _$ ^- /*
7 |$ m5 r3 i0 z6 s, n' X+ X
& A, `) p% A4 v; h! `/ d3 ]+ S- *---------------------------*9 E. w9 T# R) r7 I- y2 J, P( p
- |1 (GPIO2) \__/ (GPIO1)16|
) p4 g3 I+ y$ t; L) } - | |
$ G7 s" N! ]7 } - |2 (GPIO3) (GPIO0)15|& Z, q% h, o! \; X ?; e. Z3 G" ^
- | |3 `7 X7 L% V& d9 H
- |3 (GPIO4) 14|
I. b3 @8 O7 o - | |& T1 h6 D: s) Z9 k6 [
- |4 (GPIO5) (DATA)13|+ B z/ K( F- F& P9 `
- | STC15W204S |
5 r7 G1 f4 @; ?0 B - |5 (GPIO6) (LATCH)12|
) v/ k! w% i/ W* x% I" G - | |
`3 V2 W& f+ n" S2 J `: T% r - |6 (VCC) (CLK)11|8 W- N7 J0 c4 {0 ^- A# G
- | |9 [" H+ O( s% Z3 Y
- |7 (GPIO7) (TXD)10|
& _ `" f! b8 T - | |& v9 s5 x/ a) Q. u9 a
- |8 (GND) (RXD) 9|+ A) G/ {5 s" C) A7 l& c/ n; I
- *---------------------------*0 M! B6 T/ g! }$ Z0 n. [/ N
- Fosc = 12MHz/ r' R5 d6 f, d( F
- 3 e! J% g: g2 x5 }' ], H
- P1.0 -> 上
6 Z8 h$ \3 W7 A0 e2 _9 e$ L* r% Y - P1.1 -> 左; \& k- q0 H9 Z: T
- P1.2 -> 下( X4 v% c5 }9 {1 y) N% d% z
- P1.3 -> 右
5 J1 v# g; I, q, h l, S - P1.4 -> SEL+ z9 H. h9 M- Z9 B4 W3 c
- P1.5 -> STA& U6 ?' k. _2 p- S, y+ B
- P5.4 -> B! t$ V$ a! P( [ \' w& `
- P5.5 -> A
/ l3 ?( R" o M$ u# F, A# P - 8 l7 i( V/ k X$ I, v |% B
- */: w$ B% L* i. q/ {: Y H7 K1 t
- 1 ?- P9 C5 S: z" e' L1 q
- / T8 M& O) U7 n/ e: M
- sbit CLK = P3^2;1 p6 |( ?3 |0 X6 l- M
- sbit LATCH = P3^3;
- w$ K/ v2 i3 O+ C1 L J - sbit DATA = P3^6;+ O- }% u3 _* m/ [
. a: Y I$ E( ], r4 n- bit isReady = 0;
# C# Y5 C" Z/ [7 ^2 M - static unsigned char key = 0;
5 } b# A( D- O3 a - static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值
6 X( o% ^( N& g' K. t8 s - static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份
& r) t, N. J: P: E - static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中
" D! p. [/ K% o& b6 p0 t6 }. y0 i! Y - static unsigned char idx = 0;) [8 B7 h1 L* X" K- R% I
- 2 v# ~( t7 B* h% {1 n
- . I4 _9 |* _9 ?/ |% r
- static void GPIO_Init(){
8 X; r4 k# ]4 ~3 B - // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平
3 U( T3 i v% y! ^ - P1M0 = 0;
. b0 Q# ?% X' G - P1M1 = 0;# k e2 h' Q$ p8 a( b" m5 Y
- P1= 0xFF;4 G4 i0 y; o9 U2 x
- P5M0 = 0;8 O' M5 \6 x& K/ e
- P5M1 = 0;
- B$ ^$ o9 ^/ c( L' D+ _6 L0 c6 W - P5= 0xFF;$ }+ b- P) U( g7 ?2 {2 x
- // P3口初始化为准双向
: H: N# l( t6 f0 A - P3M0 = 0;
9 L* \+ v1 G1 c; g# ] - P3M1 = 0;
% k4 Q1 E0 w. s( u7 p - P3 = 0xFF;4 O8 j- g" A7 o% Z
- }
8 H1 V/ O8 o0 x3 r: Z# G
. h2 J6 \& s% F& B- static void Interrupt_Init(){) h7 s7 E: p/ l/ X; X
- // INT0中断(CLK)触发类型为上升沿+下降沿. s4 C) G1 ~+ x* w8 p( Q
- IT0 = 0;9 y! J: g+ j b, ^* m8 h
- // 允许INT0中断
# z% ?4 A! N ~6 y. Z& c" L9 E - EX0 = 1;
) p& s: i1 T5 d, G, i% Z - // INT1中断(LATCH)触发类型为上升沿+下降沿$ Y E) E' c8 i6 P; t
- IT1 = 0;
/ p! U9 C1 y. `4 u2 G% q' _ - // 允许INT1中断
+ K0 s. t# n6 t( A - EX1 = 1;
+ F, J/ ]3 _+ K) [0 m9 X - // 开启全局中断: v4 t3 e9 x/ ^" {, J- a
- EA = 1;- ?$ R Z$ l7 }! t" c/ w4 B
- }
; e F# x2 S4 _! K' ]8 t - 6 h' \- X( P) r, I
- static void INT0_Isr() interrupt 0{7 _: l2 `& x- \
- // 只有已经成功锁存才允许CLK移位+ O# F! ]# p( K
- if(isReady == 0)
5 K- r- v2 r6 E, u ]! m- ^' {! u - return;
1 Y, f6 K' P q [4 H) {2 E - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
0 p) n- D- N$ {8 q; t! ? Z - if(CLK == 0)# \, B9 i/ d9 p- {8 ?8 s. W
- return;
! e# q" [% w, z# I/ M u' d - // CLK上升沿到来的时候,取锁存值的下一位输出' u/ I9 `: ?/ c0 ]
- idx++;% j$ B$ E! i7 X; n) I# i* h: V
- DATA = key & mask[idx];
' m/ j, h% a) k% Y9 e7 p - // 如果已经完成7次移位,则一轮读取完成8 j5 ?; D" r& e# b @7 X1 h+ G$ D" ^' [
- if(idx >= 7)
% D; g' K! R& o5 S$ N; a - isReady = 0;' I7 S% ^# w% r$ }, U0 m
- }8 a. B9 D0 r% _
- ; b, Q5 u1 t0 }1 [1 {0 j
- static void INT1_Isr() interrupt 2{
Q, b9 f/ ~) E1 ]5 v- H" y5 m J4 ` - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断4 m, ~0 Y4 b' N5 q F* S0 j
- if(LATCH == 0)0 h/ m$ M: q& B( M+ O5 F
- return;
I4 t- J* Y; c$ F. `% ?, _ - // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA
/ o5 _; v5 G1 x( l* H- N - key = bufReady;
9 N5 c2 |. o3 g0 v8 ^! r3 c - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表% q; c, Q& g5 _$ N7 \7 x
- idx = 0;
+ y4 Q; ?1 t8 i - // 允许CLK进行移位
" U. a% s' {) r' I# d* \ - isReady = 1;) D! L3 f$ p* j8 z# |
- }
$ _( s* e. D' _; E* ~8 W; j$ w - 8 \: H# E" h; D& D
- void main(){
4 r3 M$ ~( r/ d( Q1 B9 f/ a& @& L - GPIO_Init();4 p" w4 V1 B$ m8 L* o' j
- Interrupt_Init();* T* ~, r( q W3 u7 _
- % c2 L3 L, P; f. V
- while(1){
8 t/ O I$ L' ?+ N - //PCON |= 0x01; // 进入省电模式
% i7 Q% f% b0 T5 l+ g - //_nop_();$ [& e$ j0 o" r
- //_nop_();
0 K5 D p( N3 o0 N - //_nop_();; ?) p) b+ P: N; h" r
- //_nop_();6 Y# h4 C7 g4 m! ^# U* r" g& `' d/ n
- buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));
! U8 p, d; \) ^ o4 S6 } - bufReady = buf;
- _+ [: k: W4 Z/ M! n; ^ - }1 i5 K4 @' N" ]5 e! x5 u5 D" P" Q
- }3 |$ r* G9 b3 n! e6 g0 I! B: _
复制代码
7 ]$ y- `; e$ c+ h+ P, [硬件参数配置无特殊要求,晶振频率选择12M即可。# i+ {( Q {& a q4 `6 J, \/ y
9 ^+ ^) ^: U( r/ L8 D3 K; T, k
这是编译好的固件。 |