本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑 & D# u' v: ?3 C t9 W
$ `3 C) H0 X: F6 G没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。
: [, t& U( F9 F, e3 e
3 E% ]9 u3 U- i: r( h+ h5 Z% l/ z, V8 X
发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。/ v& M7 b; z9 m
& F" |! Q- t. y
买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。! j1 M. s8 u3 s
为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?# Q& d# X* C/ X1 J, } F
然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。2 z' \7 ?$ O h+ B# I) g" z& L
: N) t- \% f/ w; Z- h
7 @* W% a. }) w3 q3 j* G' I+ D. o* _1 _5 @
最后把按键、导电橡胶和电路板装上去。$ F' J# Y, E1 [5 k0 b/ w0 }
精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。+ {7 ?" @ m5 D3 r8 R
' g1 n2 x7 [ c" {
' r6 B( E0 \! C, m完工!$ o- I% _0 W1 @6 V. e) J
9 b, B6 {# n$ M& y9 r6 U
& Z/ I3 v( m' B# }$ o* b l2 b/ m/ ~这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。! J3 T. o \# H. E( e# Z
4 i8 H" P4 l5 N9 A: e( }4 l
# a: p& r5 Z- V5 B. `9 C
接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。! o- Y# z5 ]% `8 T S
8 {5 K+ t4 d$ k8 x2 ]
! D; P$ U0 g* C2 s9 _" E
8 ?+ s& f7 }2 X6 q7 e: K
电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。/ L* n5 K: e3 j1 e( T
% J6 \1 w7 u" \ e
实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。! _1 d: e5 D$ M" `# E: r1 l7 A
6 t% n, r/ H7 Z. s' S5 Y8 r" y0 `0 k这是电路图,有兴趣的朋友可以参考。2 b& ?1 T; p6 r( @/ E& W9 h3 k v
$ B' A. I8 c' U8 G+ h0 W4 g- m/ k+ |" u1 }# A, V$ d! o2 G* S
我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!
- \% t9 y; }; w& g, ~3 k6 c编译器:Keil 51。+ ~' ?8 D7 z% d& X
$ w% u; ? ^2 y3 V
发射端:: r- U" H5 o; i, X, o% c- O
- #include <INTRINS.H>
- p Z# X2 L8 a - #include "STC15W.h"& U! Z+ _3 C \/ Z/ b0 D3 H
- - t( }& J2 w! y L
( M, p' I8 Z5 Z8 y h- /*
7 M/ q2 Y# q) X+ e - --------------------------
# U0 I+ y* p/ e/ V( I - |1 (OUT) \__/ (INT1) 8| @1 z, W& ^* m
- | |. S; O% P! P6 P- H( I3 i/ p3 i
- |2 (VCC) (INT0) 7|: N0 y( b- H! w* Z' ^8 H3 E1 b
- | STC15W204S |
9 T2 _! e' ]' C5 O9 h7 A/ d - |3 (LED) (TXD) 6|
, B7 z y8 h3 {/ d4 i - | |# ?0 m% i4 V* g* w, L9 [
- |4 (GND) (RXD) 5|; o( B0 }' A+ A5 O' ~ j4 V8 c
- --------------------------+ ~# Z6 ?+ E& X) \: ~: O. K+ |
- % l q/ U3 \. r* a$ z
- LED ---|<|---[===]--- VCC' x, j' e* _3 t* z
- Red 330
" n6 {# H- A0 S7 X
$ m% I: V* w7 g( \- W2 _6 q- Fosc = 6MHz% @* {) {3 }: A4 ?3 E6 ?/ O O
- */
7 O8 w* z* o, j: R8 M - 1 Y5 J" p \& e7 I5 I4 S7 V
" z% S7 Z, I) {( l- I: Z* D# q9 [- // 矩形波输出脚$ H- J2 U- I2 w, S& U
- sbit OUT = P5^4;
( D+ _# [$ L) L( Z" S' |& a9 P - // 低压指示灯引脚& {: m. X3 T0 \4 Y) C/ N
- sbit LED = P5^5;4 z( w5 V3 |* O7 F$ H
- 3 [+ W, ]) i0 P6 E
- // 停机标志位
$ \* [. u* l- Z3 e: ` k( G - bit isReadyToHalt = 0;% P( O; |/ k- c9 f+ k6 }* q
- O& k6 J( T# _' o- // 矩形波次数累计# Y O, _6 n: p
- static volatile unsigned char count = 0;: m; x" t: A& l1 n# i* e
* s+ D$ B# l0 F; x: c
: t( y, L* b2 m; u; }" w2 H- static void GPIO_Init(){( P% i" [" |% ^, e7 `# j# Y
- // P3口设置为准双向,默认靠内部弱上拉输出高电平8 i0 N# g- Y( c4 @
- P3M0 = 0;
. y4 N5 B+ Y7 }# u t& H4 ^5 b1 ? - P3M1 = 0;
* D6 H# Z Y& i% p - P3 = 0xFF;0 x4 D# y5 Z9 T5 @/ _7 E# y
- // P5口设置为准双向,默认靠内部弱上拉输出高电平
1 G1 L6 O& P- q/ y! i - P5M0 = 0;' d, K6 F3 M) I
- P5M1 = 0;
5 Q) P" l3 U( h6 q5 y7 r: z* [ - P5 = 0xFF;
_/ p: T, u1 T - }
. c% s9 |- ^2 K: Y R) Z3 S - 9 L7 }* O1 \! a1 X
- static void Timer0_Init(){1 N: J' |& K' i: j* G' D s6 V
- // 16毫秒@6.000MHz" [# e q- m& f3 E# C4 e) v
- AUXR &= 0x7F; // 定时器时钟12T模式* |& W \/ n4 M5 D8 e1 }- {
- TMOD &= 0xF0; // 设置定时器模式6 v& T0 ^: E- n e; b/ T' u8 K
- TL0 = 0xC0; // 设置定时初值
- d0 A0 G; U6 r( I0 I& i0 E3 E - TH0 = 0xE0; // 设置定时初值# s+ C% a2 G2 f! G3 Z* [+ _
- TF0 = 0; // 清除TF0标志" O k$ W- w: f) `
- TR0 = 1; // 定时器0开始计时5 u* `2 @8 r% U& v& C
- }& G6 e% B5 x! r& W9 S* ]
- ( r/ Z7 p8 ?) P1 t2 M, u
- static void Interrupt_Init(){
, ~2 @" f$ t2 v. ]5 S - // INT0中断触发类型为下降沿触发
/ \! g/ \0 E. j0 R - IT0 = 1;
+ M1 A, M! R! I# E - // 允许INT0中断
$ R2 E, l2 X/ y9 [5 Z% S - EX0 = 1;. T# Y) h4 w% g0 d
- // INT1中断触发类型为下降沿触发
/ v, w: A# c+ E5 y - IT1 = 1;- @1 X9 ^* i, C! _7 ]4 [" o
- // 允许INT1中断. s3 }$ m; M- P6 n
- EX1 = 1;
" J5 L, F" S+ Q, H5 l% @; Q: b, X- b( b - // 允许定时器0中断
7 ]9 S, o0 X& l: e - ET0 = 1;6 p! w$ |9 I O) Q6 Z! b
- // 允许全局中断
7 W9 ~+ d8 h6 r1 M6 o - EA = 1;% z6 A, {2 }, G
- }
; s- h3 `) g1 q6 a
& j, b3 z) Z( U1 y9 ~% ]- static void INT0_Isr() interrupt 0{
! {2 D2 v- S' F# }4 V2 v' D - // 矩形波次数累计归零
. {& @ a$ n% k; u - count = 0;
, J+ A& N0 q8 W: J - // 重开定时器0中断
- {6 g6 F# @, m+ [8 Z5 p3 E - ET0 = 1;1 W" I# n0 k r. l' K
- }
* i- `( s# M$ F4 c. W% B; ^ - 0 `! V+ g9 k# M/ v" W; N% ` j+ J
- static void Timer0_Isr() interrupt 1{ B. j5 W4 C+ o, E$ z
- // 输出矩形波
9 A8 ~- T# X% {) f4 W: b - OUT = ~OUT;
) r1 D! z" i, W- b( @1 K+ O - // 累计矩形波次数& W. D2 j% Q2 J) W
- count++;; F1 P+ M3 E% {
- // 超过250个矩形波,准备停机
* p/ o+ L8 o% v |% D - if(count > 250){# A7 E# ~. {8 i" e4 N( o* @1 b3 G1 |
- count = 0;
, a9 L4 M5 e% F& J1 B - isReadyToHalt = 1;
2 k5 |# B4 j& [( L: Y4 k - }
0 T* ?* j% ?2 ] - }& k& ^1 u% o: }* ?- A
- 0 k: I, `' n+ A9 O1 {
- static void INT1_Isr() interrupt 2{
! G+ L# q5 A- s' z1 K4 U - // 矩形波次数累计归零8 K4 G0 H: n: p3 i- |5 n4 G+ }( d
- count = 0;3 y4 a8 K: Z- H) R7 ]" j1 V
- // 重开定时器0中断8 n6 r0 ]+ {9 j- {9 Y
- ET0 = 1;
% ~' O4 n4 S" |9 X) n - }
: ]: i3 v( _ b3 J- J- N8 I+ a& e) C
' e5 D' S& H5 A8 _# {
1 }1 e! a8 r# J7 Y# T% L. d- void main(){7 U: x2 f5 X: K8 b/ _! R }
- // 初始化P3和P5口
' e/ M% I |, ` - GPIO_Init();
! \5 S, ?2 Y6 N; x% y) ] - // 初始化定时器00 c7 v# L o- K
- Timer0_Init();! ?* a8 V% J& ^) U6 ]
- // 初始化中断
8 f- S+ b8 s1 u; D9 {9 R - Interrupt_Init();
- N2 `# @ q& ? - * J; ^7 ?, T2 t
- while(1){2 `7 ?0 g; \ e' m; U8 d8 V
- // 检测电量
: ?' H$ E( w- j - if(PCON & (1 << 5)){
' c8 Z+ E: k1 J6 L - // 如果电量低,低压指示灯亮
/ h3 F! [8 r/ `, d. D' G - LED = 0;" l; c! W8 ] r0 ~, h/ @
- // 清电量检测硬件标志位
N0 R) z" H4 Z6 `, u- x - PCON &= ~(1 << 5);
( W5 ]7 D6 q+ o |4 q - }else{
4 @. v) I! u* h8 m$ |% ^7 x - // 电量正常,低压指示灯灭
`6 e& m$ J1 I - LED = 1;9 n, F7 k/ G C
- }
7 G- V3 |) a4 Q# ^# i
4 k4 M2 G; M: Y% L* A* h- // 检测到停机标志- z7 k5 z( J3 C1 Z; ^, B; T; H% T# X# @
- if(isReadyToHalt == 1){! }( ]! V9 b" \9 s
- // 暂时关闭定时器0中断, ~# X5 m& P3 ]$ ?
- ET0 = 0;
% i4 J7 e, e9 X4 ]4 I U* ~1 }8 L - // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU, a" d: r4 r- z; F3 F& `# I9 S
- OUT = 0;
6 r: t, S7 C4 O) T0 n! _ - // 停机之前先把低压指示灯灭掉,以省电$ z' x" l" i! I/ N% R9 |4 x. F2 r
- LED = 1;
- F6 D5 m# r" E7 ] - // 进入停机模式0 A0 }3 u# {+ _7 q; V* t; v/ [& W
- PCON |= 0x02;+ G1 _3 W1 o5 H' _' c# S* ?
- _nop_();
% u' `6 n, o/ R$ Q0 Y - _nop_();
4 e1 b& k0 { S) a, K" P - _nop_();
- I7 l6 _+ h* w) g$ G( i - _nop_();
* d- L) G, N5 S3 U9 ?& ], P - // 唤醒,清标志位7 @/ O" D1 v9 s
- isReadyToHalt = 0;( [4 {* O M/ U' ] G2 e$ w- |5 p
- // 重开定时器0中断: o! p+ M2 Y$ q8 ?$ F8 w
- ET0 = 1;$ f; i0 {5 [3 ~8 @9 O% R4 `
- }4 h+ y0 J# S" x, c6 c
- }6 k9 }' `9 O1 y _2 h- ~* a& ]
- }
+ L3 m% I2 p B4 r3 ?% M' J" @# R
复制代码 9 K3 X [' N6 x5 K7 \% U
硬件参数配置:
+ m; y. ?! i" Z8 M% o9 c" K: ]9 N5 L1 J6 R! m( C( T+ @
2 n) L5 `( s: c' |: F
接收端:
. c5 A6 L4 B8 n- u ]! ?- #include <INTRINS.H>
+ e8 C4 ]/ W$ S+ ~# g* Q. s8 [4 ]) Z - #include "STC15W.h"
& J% z* T2 w* Z# ] - " y8 v( `1 s, M: K a
, w- I: G1 v) S0 q8 ]- /*
6 |1 e: z6 T8 e
- n" a) L, X' I( o% L9 h- *---------------------------*
+ _9 z8 f2 W0 [; b4 @' w+ \ - |1 (GPIO2) \__/ (GPIO1)16|
4 {# K( \; U6 u* a6 E @ M - | |3 c- ~" W0 @: F. Q6 P3 c
- |2 (GPIO3) (GPIO0)15|
, o6 X5 h8 O( }6 c4 E j - | |* W7 l7 N% n: F4 s1 R
- |3 (GPIO4) 14|
) t) c2 A/ `" I* q W& c8 U: J2 K - | |
" B" O+ s; T& [0 Y5 c2 y - |4 (GPIO5) (DATA)13|" g" L3 T/ i9 v5 r8 z1 t; G' ]
- | STC15W204S |
7 i' N/ h/ t T" R3 H( @* c - |5 (GPIO6) (LATCH)12|
# C5 r: K( @& |* q/ \3 t$ h6 l - | |
% \ M2 J% L& ]! K& F - |6 (VCC) (CLK)11|* N" q; x7 {8 q0 z5 P3 `
- | |$ }& `! ]9 \+ i- h5 Y: q
- |7 (GPIO7) (TXD)10|
5 j8 k" \# Y! f o( q% { - | |( M9 q2 d" [, I8 Z l `
- |8 (GND) (RXD) 9|$ t" e W* {+ Q% v/ b
- *---------------------------*. e9 S" R4 |' Y; H
- Fosc = 12MHz0 F( M; U3 N9 a2 \3 d
. l% N; s/ w% C3 N- W& g+ ~- P1.0 -> 上
3 J& Q% j9 U& H% a* O1 o' p) ?/ e - P1.1 -> 左
& M4 v* g8 p: w V - P1.2 -> 下
/ t; ^ `$ f1 m2 C - P1.3 -> 右
$ z/ ]. E) t' w, | C3 f9 i - P1.4 -> SEL
3 ^8 \7 W! y# @/ { - P1.5 -> STA) w7 _1 z" P% N' P. ~! ], W
- P5.4 -> B
& h# {' D1 N4 _1 H& ~+ i - P5.5 -> A) l3 i7 Y9 }2 U) `; t( M, K8 M2 D& O- c
3 M& r+ U; O2 _, h8 k- */
& Q) g; B: `$ n: K1 N3 H
. C+ P6 G$ m, H9 U0 S( b% S- 7 S* t( m; M( m" ^2 U( _. A _3 E6 g
- sbit CLK = P3^2;1 a4 T# d8 y) p' }4 \& L
- sbit LATCH = P3^3;
4 S/ W+ L+ X7 L3 W3 _ - sbit DATA = P3^6;
L2 {* ? b3 E! O" M( o, E5 R
4 _* a0 t4 D) } u' @7 Q: v; ^- bit isReady = 0;
6 W* \( _9 Q- W S/ G* \; H - static unsigned char key = 0;) S4 ^2 K3 N4 Q* J! Z5 O1 l; i
- static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值
5 u3 R6 s2 d( d% E$ Z: Y; v( E - static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份+ h2 t0 j7 H/ ]
- static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中7 J6 b% O8 n" m% Z' g% ~; J
- static unsigned char idx = 0;8 c- `( ~# |! P8 q( E
- : Q) y" h/ R2 U2 M
- 6 h3 P @# ?% v! a q) V
- static void GPIO_Init(){* P% m" D; z; o$ s& w( x
- // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平, S# i/ E+ b- g$ T2 A9 Y7 n
- P1M0 = 0;9 M4 C* R3 {: e# N2 h9 J
- P1M1 = 0;. j* m* L/ {" G. @1 a% z
- P1= 0xFF;0 j, R; l& }3 F9 O2 O
- P5M0 = 0;4 K% R) m; @' F* k2 U
- P5M1 = 0;
9 t* T% Z- D; s - P5= 0xFF;. Q' \$ X; G' U4 a' [
- // P3口初始化为准双向
9 d. ?4 F6 H. A" r - P3M0 = 0;# e0 i* z5 t; ?& O
- P3M1 = 0; y7 {" H* A; G+ x1 d. N
- P3 = 0xFF;+ {8 z) P# T% M ]9 ]
- }
+ v& E4 i' C6 C
. r1 o7 S6 c+ A/ _4 C- static void Interrupt_Init(){. o G; k4 e5 ?1 W4 @1 b
- // INT0中断(CLK)触发类型为上升沿+下降沿
: w3 E! K/ `! X1 [2 j. r9 J - IT0 = 0;
, [8 B. U$ N$ ^ y - // 允许INT0中断
5 `" V5 f. z" J6 Q/ E. y - EX0 = 1;; j* D x* j& {0 a
- // INT1中断(LATCH)触发类型为上升沿+下降沿
) l8 S0 G: f# E7 o r+ e' s - IT1 = 0;
0 R! M3 P) D' x' y2 o! _# k9 } - // 允许INT1中断
9 h" S0 P; [ j R - EX1 = 1;# j( p+ i9 X7 t+ M
- // 开启全局中断3 M. ?3 Y) M. ]8 {
- EA = 1;7 l6 {4 b8 a. d. m6 {4 m3 X
- }
" X2 C# J# W+ v8 d( F9 V - & p w: \9 y' Z! K( H
- static void INT0_Isr() interrupt 0{1 r I1 Q. k4 [8 q6 v9 V
- // 只有已经成功锁存才允许CLK移位# U2 L/ C2 y+ B: J8 S7 B+ K J
- if(isReady == 0)$ w) B6 l6 `1 Z' a- ?
- return;
2 H3 J6 ?: l: Q - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断# E' Q& G2 F: P. z
- if(CLK == 0) y! |) z5 l7 Q6 A0 {& [7 g
- return;9 ^/ R' Z: N$ L7 [3 o7 R9 `( O$ z* H
- // CLK上升沿到来的时候,取锁存值的下一位输出$ `* t2 Q4 ]" m; D
- idx++;
' G/ [' ^+ Z8 P. d0 f+ O - DATA = key & mask[idx];
! G6 o9 h: E% ~3 n - // 如果已经完成7次移位,则一轮读取完成& S' R' F8 e5 B: ]' f
- if(idx >= 7)
9 j& z* [ l4 v; M; O! } G: T2 z - isReady = 0;
8 ^0 \4 s c6 C - }
# f8 @7 m( E0 {# d8 d& i - ' H- ]0 O, |9 ~7 y* i
- static void INT1_Isr() interrupt 2{
3 k" U8 U6 o0 @" k* F+ }5 \ - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断' b1 N* ?/ G# O4 q# P
- if(LATCH == 0) W7 c! I" N* p! T
- return;
4 E) N& g8 V& n" O - // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA
% i4 i* H+ v2 M - key = bufReady;
2 J7 n7 s+ c" m - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表, r! R" c4 V" t- O5 s
- idx = 0;
. u- L; t1 b" V% O8 c" z - // 允许CLK进行移位+ G: q9 R0 Y4 ?, l I; Z! X% d
- isReady = 1;
5 q& p& m5 Q% D, t% D - }
8 A2 D) U' X ^/ q) w% |( n - 2 E1 C8 ?/ j0 D1 s! d2 @
- void main(){3 c( Y) ~1 M8 C1 V" H
- GPIO_Init(); q6 p/ r" B2 Y
- Interrupt_Init();: h4 _1 W2 ^& e; q5 ~* L" _( p
- ; k) x8 n: q# V* ^& r) H
- while(1){
0 @; J) M+ T9 w3 e - //PCON |= 0x01; // 进入省电模式, c+ [' N6 u/ ]3 N2 G; `( a1 K! y4 Z
- //_nop_();
( F1 \4 c: F- v* K% F - //_nop_();
. u3 {; e! z' s - //_nop_();
0 L# G9 Q, E5 T M, _; a( N: u - //_nop_();9 a7 n0 v0 [$ B, d/ ]
- buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));, z" y) ?9 ?" Q& H4 s L2 I& H
- bufReady = buf;( R: z$ i, d4 e% k/ d
- }5 h+ Q, s$ A- r
- }( p' T A1 p1 ]( o+ p
复制代码
4 `' a* ]9 B* F1 a/ {* n硬件参数配置无特殊要求,晶振频率选择12M即可。
n) E7 w2 _0 R1 L3 j$ F! n9 f! l& r- T- \0 _
这是编译好的固件。 |