本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑 k7 m7 P+ Y6 F" J. y5 i
$ o4 Q& }! \, I% E/ Q+ v, M/ ]
没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。
3 @6 W9 I8 P e- }% m6 L; I
! w. X) T$ ~# ^( b- R2 \( N
N0 E j @7 ~1 ]4 s发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。
* ~) t$ S! e, b* h [6 R; C6 V. ]) @. h# V, q$ i
买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。
) N2 V) O9 D) L1 x% S) w' @为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?
/ M+ e1 B% ?# U& P) B* H' g然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。
- F6 W8 a$ J' V+ R0 c6 h. o( M. N; T& M$ }6 }" ^, w. u
" I+ R" [8 i0 O+ w4 L9 }* o- {2 L: Z
" S% \$ [/ D# N最后把按键、导电橡胶和电路板装上去。3 k& ?; D, b( d8 Y, h5 Y
精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。
" v g6 B8 o$ C7 \) j- t& W5 c* g, w) q5 U( s
3 Y5 e0 m: Q0 U: K
完工!4 D2 Z6 N; ?! e' S1 [6 T+ H
. J/ q; }2 w$ ^7 d6 N' W
$ F+ x9 l: R$ P q* U这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。2 R! M+ v5 v# V5 m' }/ `9 V. M
6 I( \2 f$ l+ E. _5 C
8 i7 x2 h% w1 B3 ~5 Z
接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。
9 v) y6 g: I' X0 w4 F, c9 ?: ?# L5 _3 O N/ t3 U# H. I
# n2 j3 s2 R7 w, _" c+ K9 i! i
" O( h% w4 d) D电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。
. `" d" g. [9 r( D# D2 ?4 @9 g; s0 V) y" y4 O! ]
实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。& |7 V" T3 z2 _+ p5 }% k
* }+ a, L0 p6 I4 E& j这是电路图,有兴趣的朋友可以参考。( z0 N4 ]1 M$ R- h, b
+ g7 I, K" e& w8 y1 h
- Y5 G" F: S8 ^( U$ s
我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!4 `" ^# X3 c& L" `/ q' T
编译器:Keil 51。
4 ]6 ^* b) x2 D& P* d/ t
6 k8 { H+ j8 f' k! q发射端:
$ r+ v8 w; A$ k: X0 M( Y1 O9 a m$ i4 q- #include <INTRINS.H>2 s' l, C3 p4 o( s% \, }0 N
- #include "STC15W.h"7 Q0 k$ g1 w+ \* a; s/ F7 M
4 j& s9 P# @& T2 E- * i e1 |2 J! Y# S
- /*, z% g4 s+ \1 N+ o/ ~
- --------------------------
5 F- L* H [: e* ^# p+ y% l- ] - |1 (OUT) \__/ (INT1) 8|
+ x3 r; K8 q) @1 K! I - | |. q7 H. [0 n0 ^! h9 H' O/ d8 I
- |2 (VCC) (INT0) 7|
* o e6 o; g$ ?+ ~! ] - | STC15W204S |* A/ u2 i7 b& u3 f# a
- |3 (LED) (TXD) 6|
. Q8 k2 p% j+ b; E - | |
5 v" w) Q- l# i" a# E! y$ ]. ` - |4 (GND) (RXD) 5|1 V; T. t! d9 g6 K- e
- --------------------------
3 a. e: u, ~2 f. @3 R& d- w - . ?# J) R( c9 q: [
- LED ---|<|---[===]--- VCC
' C+ K- m. b7 O( a - Red 330- F/ x& J5 z: q% a' {% ]* X
5 n" r2 `( Z2 u, U4 w$ Z V8 Y- Fosc = 6MHz) w) S# Z! a( `6 j+ M/ e. w3 b6 S2 i
- */; A; N* K' G! G) a
- 6 N6 ?* L3 e7 h* z
7 u. q1 u% x+ ^- // 矩形波输出脚. N6 O* F, N( g+ P
- sbit OUT = P5^4;" H0 u$ }. C" g5 M" ^
- // 低压指示灯引脚3 { }: n+ w: W2 Z2 K
- sbit LED = P5^5;
0 E' j; I4 Q/ a' W6 ]
- }" T: n6 m% o; c9 y- // 停机标志位
7 b5 Q3 I% o; g% `- a/ F - bit isReadyToHalt = 0;& ^% r1 z r4 ] M+ i {1 }
- ; l! o6 y2 E# B' z3 D+ J
- // 矩形波次数累计* ~+ Z& ^4 T: o
- static volatile unsigned char count = 0;
; e9 {4 a1 H) l
/ i+ l8 ~' N# f. p0 Y) D+ ^- ' L0 L# ~* l/ h$ |0 r7 K+ [& i
- static void GPIO_Init(){
0 x+ |3 q/ R% V8 l$ }) H - // P3口设置为准双向,默认靠内部弱上拉输出高电平
, u& Y( E/ Q' X& q - P3M0 = 0;
! v3 _: _1 t+ t, U! I D$ Z; } - P3M1 = 0;! w# D+ k4 v/ l
- P3 = 0xFF;4 W8 T% b6 e ]9 J
- // P5口设置为准双向,默认靠内部弱上拉输出高电平
" h( D: x3 ?. X" ]$ K6 y - P5M0 = 0;
3 B9 i' k7 t, k* ^; b7 [" s( v6 q - P5M1 = 0;0 q$ h4 v+ m- [9 ^$ k
- P5 = 0xFF;
! S2 N" y9 S7 Y# X$ y - }- A: I% z; j+ A+ ?% s
- $ O9 ~ Q% N2 Y) D9 J9 E
- static void Timer0_Init(){
1 ?! o; }* P# S" \ - // 16毫秒@6.000MHz
k2 H$ C# ^( u4 o& u* Q5 i+ m - AUXR &= 0x7F; // 定时器时钟12T模式) f T4 L5 {# w# j+ q6 ^
- TMOD &= 0xF0; // 设置定时器模式 r8 d1 D/ x6 _" v$ D5 a
- TL0 = 0xC0; // 设置定时初值2 w( L) g" l4 l3 Q8 w1 @5 @' U
- TH0 = 0xE0; // 设置定时初值
; I6 j- j" i) n1 E- r - TF0 = 0; // 清除TF0标志
" R H) n/ h/ V7 Y! `7 y% r" u - TR0 = 1; // 定时器0开始计时
9 h4 O. x( v+ W0 J& m. O - }
4 H6 |* m a) b* o3 \
6 G/ C" U6 b, O3 K9 }" \: _ ]- static void Interrupt_Init(){ I% z+ K5 I2 ?7 r
- // INT0中断触发类型为下降沿触发
8 Z+ }) V( E, H - IT0 = 1;! E" I1 v# _* x, \/ t
- // 允许INT0中断
- ~% Y: P+ k0 ], T - EX0 = 1;
5 y& d& {' z' P$ G/ r0 J - // INT1中断触发类型为下降沿触发
7 L. N u8 A9 I, C9 |3 T; X0 g/ m6 V - IT1 = 1;5 h* q9 d0 ]1 P: u9 Z( h
- // 允许INT1中断; I8 w9 K- H+ `3 J0 R! N
- EX1 = 1;# c7 a5 A; X. ~ [; r( @( V7 o
- // 允许定时器0中断0 l, Y5 {# d M; Q
- ET0 = 1;# W0 ^/ n- T& G, P
- // 允许全局中断
( q; t4 N2 N/ O h+ m5 x; p - EA = 1;
# b. v& x- ~; {5 {( R+ c2 w - }
1 Z$ A" |7 U: L" f - 6 J/ F2 @, C: \ {5 d" v
- static void INT0_Isr() interrupt 0{5 g" y, f2 y# u6 }. {0 f
- // 矩形波次数累计归零
' [; ]4 C8 _- K# \; s - count = 0;$ [/ M: U+ W3 ~- s* p2 V! K) j
- // 重开定时器0中断" w5 {& y% a& x8 k; y
- ET0 = 1;
6 L2 U4 H/ v7 ?8 O - } T* j$ s) |2 e2 ~& c |/ ~8 M* a1 C
( s, N$ b4 e4 Y- V+ s: w- static void Timer0_Isr() interrupt 1{6 r1 v) p6 S6 N$ ?4 h9 Q( h
- // 输出矩形波
5 g8 W+ U* s3 U. a- I" \ P; d - OUT = ~OUT;- s6 J4 r: {- Q" }4 ^
- // 累计矩形波次数
7 ?3 h0 `5 q" ~( W" r - count++;6 x: e5 j4 _. ~/ U
- // 超过250个矩形波,准备停机, ]: |, z; ~( |. K( u4 q
- if(count > 250){
, t; \3 ]7 t7 f3 m5 X- B& c - count = 0;4 S1 h2 m6 d0 T( p2 D2 O+ G, R
- isReadyToHalt = 1;1 ^, ]; \$ ^4 f1 ?' o& o( T
- }
5 x9 ?' w4 W. i/ s4 E: W. k- m - }( ^' [! X N5 h
# I8 L5 O* _; B- static void INT1_Isr() interrupt 2{
/ K: { g% K: f% B/ | - // 矩形波次数累计归零2 w, q2 C, }4 w) c5 Y! z* c& @% B
- count = 0;
$ t) E8 k0 X) @" C- m7 a - // 重开定时器0中断
* t$ w) Q+ n, i9 R5 i4 o - ET0 = 1;
. U* k. S! S8 C# @) F% j8 x- z$ { - }
7 J( I$ g9 Q( w8 U: ]0 Y
' d% j. k8 s: Z( x: V
* L# ?; ?1 q1 c# v' G- void main(){
7 x4 q9 n F, z* f0 U+ { - // 初始化P3和P5口% T. K( j/ W7 f
- GPIO_Init();) D% Q6 \7 Z% o; g7 h# d
- // 初始化定时器0. Z9 I& |- O( g; ]" T
- Timer0_Init();
9 o, s& X+ f& l/ ~" Y" ? - // 初始化中断
8 `1 A( q( y, ?6 `. c9 ]: B0 m - Interrupt_Init();
" g: F! l$ b% ^$ b8 n5 | -
4 w* p1 k& c j2 ?' O - while(1){
3 H# | `9 r) \9 Y: [ - // 检测电量. o0 W) w, s- m; p1 i& M4 l$ d
- if(PCON & (1 << 5)){
9 ~9 W1 ]# g+ O' m* N0 O: _ - // 如果电量低,低压指示灯亮. s- c3 k% L1 Q* x6 [" Q& P7 m
- LED = 0;
% D; R7 C; b0 P2 x; F! j - // 清电量检测硬件标志位9 ^6 t( Z9 Z# c* ?& k% @0 u
- PCON &= ~(1 << 5);3 D4 h9 p6 s6 `$ v
- }else{2 S0 J5 S" N4 K9 |0 P
- // 电量正常,低压指示灯灭+ k' Y" ^0 ^* x- I. I
- LED = 1;
, M8 N! W4 e0 ^/ J0 O' D7 \ - }
* d9 T2 a( o( E y% p* y7 a1 ^
8 M a v" U0 ?; c& F- // 检测到停机标志
2 m; t$ n/ g1 e4 }2 x$ K - if(isReadyToHalt == 1){
6 R. P( F6 n! p# j! W: W2 ^ - // 暂时关闭定时器0中断6 I `+ b7 W6 R1 k
- ET0 = 0;
8 j. D1 R' T; x0 Y4 s3 n - // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU
7 r$ o3 t4 y# l% L& y5 m - OUT = 0;
# K: F8 n+ ]( E, b; N - // 停机之前先把低压指示灯灭掉,以省电( f) k4 y) ], y8 r: q- E$ l' {( s; b" C
- LED = 1;3 R9 p/ V. m2 w. p
- // 进入停机模式+ r; f; `- @; h N
- PCON |= 0x02;( n2 @$ w, [% s" ?; T: F# p/ v
- _nop_();
. V0 I2 T' H( y" j! S2 ~: ^' ] - _nop_();
/ _. C0 X. B8 D4 E% w# M$ \3 V: D - _nop_();, Z c" e d! n- F* R$ o/ a
- _nop_();
( V; s( Y8 Q1 {) y! V% C) A - // 唤醒,清标志位
" e2 A6 D- ~2 P) q" x - isReadyToHalt = 0;
5 ]' t8 E! F1 G/ T) u& _0 k' o - // 重开定时器0中断
5 R' I* H. I$ X3 C) k( ^2 a- R - ET0 = 1;$ v0 o. d: a+ D2 E/ G# }8 O. g
- }
: Z7 I# `' ]6 v* Y+ l - }& b+ m8 ~8 ?) ~+ R: H
- }
2 c0 ^/ l( `: J7 T
复制代码
' I+ ?! x2 f: y: w. N+ O硬件参数配置:% L5 {6 n1 P5 S3 O7 I, X5 l
) R# t5 _* p. a" E, ]
6 z4 s% E H: C I+ h
接收端:
5 o5 ?- F6 w! Y0 n' h9 L: e* d- #include <INTRINS.H>( j5 i7 \- F- u5 j0 z% Y5 k
- #include "STC15W.h"& {% Q9 D4 g- z! ~
& `' K. f: @9 y4 D6 z( l* Y% @- 5 Y5 t1 u' F5 o2 Y: O
- /*5 y5 q# E$ }, q
- 7 C' e7 q/ M, ~" x/ u3 L
- *---------------------------*$ R* a0 J* X& q4 D
- |1 (GPIO2) \__/ (GPIO1)16|0 ^) N0 s, t9 q* t/ \0 ]
- | |1 P% t9 o' N( t. H7 H
- |2 (GPIO3) (GPIO0)15|
7 y# j9 Y* c3 t; u3 E# A - | |
8 m) {' K) W0 N; ?$ s n: i - |3 (GPIO4) 14|
( U2 p' ^8 ?4 \0 S G - | |
& \! g8 D7 v" ?& \5 } - |4 (GPIO5) (DATA)13|
# h4 p* K, ?- ] e5 E7 f5 ? - | STC15W204S |
6 V/ o; l4 Z& {0 v1 x - |5 (GPIO6) (LATCH)12|
9 M+ } {6 V g0 h9 p4 n5 D/ z - | |/ ?8 k/ q8 s1 n0 S' P
- |6 (VCC) (CLK)11|
* z X0 m6 e' G+ ]0 |3 x% z - | |
& N! L) N. x$ Z9 b - |7 (GPIO7) (TXD)10|
7 c8 R" L" l" i) \7 ^: E3 P/ B - | |( F( R z, D' m* F1 M) c8 q' _
- |8 (GND) (RXD) 9|
: z( K X* }0 j; T* X6 r - *---------------------------*
9 y- \9 }, K) G& h - Fosc = 12MHz
! Z) `* W; ~$ L% |
+ L* H! q2 F, i8 w) u9 t- P1.0 -> 上* |' j! A& ^. R/ e3 B
- P1.1 -> 左; T, p9 P+ @/ x; q. |- E6 G# f
- P1.2 -> 下( ?# V3 I5 ~4 t5 m4 r$ q6 |2 ^1 C
- P1.3 -> 右
) k6 T7 K" J5 E8 b; D2 C. e - P1.4 -> SEL, l3 l: m' ~0 o4 }
- P1.5 -> STA
) |" a! G2 R# M% b# M - P5.4 -> B
. M+ V7 t+ v) _" g; }! r* T - P5.5 -> A" k! @1 M& e9 P8 v- X9 H
- # @) l( h: D8 ]! n1 _" T
- */
$ v ~: x# a( }
; h2 o# H1 E, M+ T! k
' \& o1 e/ K( _+ S- sbit CLK = P3^2;
( O) K6 s8 ^& x# |' s% ~$ H. B6 y: Y - sbit LATCH = P3^3;- p( Z: ^$ ]& K7 n o6 }& K
- sbit DATA = P3^6;
8 o8 i( c5 U) g D* u5 H
. W2 @% e: Y$ [ o: r a- bit isReady = 0;5 j) j$ K% f2 G6 Y9 P
- static unsigned char key = 0;/ u4 z: b/ t) @, K
- static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值' E+ r* @6 u2 [9 h' X: j$ m4 o6 P
- static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份+ P: Y8 M3 f/ y+ V5 r r
- static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中7 B3 U: j# L9 o0 A0 g0 \# d4 S
- static unsigned char idx = 0;6 R! W7 s# N7 S9 F6 X
- / ~/ q- z7 [. T. k3 V& ~
- 3 z/ W" K( U6 F! Q
- static void GPIO_Init(){3 y6 [ E4 c1 w v2 G" l+ r4 ]
- // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平
' U9 s% F6 H" T, Q x$ V# T; t - P1M0 = 0;1 m& l5 i" [) H/ n" b
- P1M1 = 0;8 J4 ?' ?% C8 q
- P1= 0xFF;5 q- }+ h1 E `2 y, I
- P5M0 = 0;% T6 f9 C+ D3 x- v/ u3 N
- P5M1 = 0;
0 Y3 T9 {, \6 l( U- M% l7 t q6 r - P5= 0xFF;$ E" @" ]% w6 b2 }9 t
- // P3口初始化为准双向2 D, e Z8 Q. I6 [8 C
- P3M0 = 0;' J7 _, N5 S5 i$ D7 j3 T5 I' [9 m1 _
- P3M1 = 0;6 p' C' }* B& N
- P3 = 0xFF;
/ U5 H. b( K) Q$ f4 n# @ - }
$ H8 z# x v. @& h: Q - 3 p" j2 G' R. t
- static void Interrupt_Init(){
) z! P6 F; H, f5 \ - // INT0中断(CLK)触发类型为上升沿+下降沿2 ]- [4 E5 G0 S3 y$ P3 h3 N' l7 \
- IT0 = 0;
6 w+ o4 a4 M$ {$ J( b - // 允许INT0中断
( c1 \5 a7 F% [ u) K* r - EX0 = 1;
5 H6 B4 S" Y+ L - // INT1中断(LATCH)触发类型为上升沿+下降沿$ c1 Y) O& Q" l" Y# l! d( Y' @
- IT1 = 0;4 {% I2 F R( ?7 P- c; F7 L
- // 允许INT1中断
* ]8 Y& k# d _# D! o+ J* V - EX1 = 1;
& O d4 l) ] {/ g, d - // 开启全局中断
6 }" e! s& t( }. g4 J; q - EA = 1;; x6 q2 h# ]6 G, m7 g6 y9 T) r7 ?
- }
; [5 r% l0 h, a( } - - S+ Y" p }5 h2 k3 _8 a
- static void INT0_Isr() interrupt 0{
, p/ t3 H5 F. u" F% k" H7 ~ - // 只有已经成功锁存才允许CLK移位! N$ U/ l$ F" U
- if(isReady == 0)& q3 z! e$ X( U$ S1 o
- return;
% b( Y6 v: U6 ?6 t3 v3 O1 V - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断1 c) y2 X: o' a
- if(CLK == 0)8 d, _8 C& |- n! C% Y* Q
- return;
$ G2 h) J7 Z1 Z1 } - // CLK上升沿到来的时候,取锁存值的下一位输出
' b/ b$ T0 t& `3 K; j - idx++;+ ?" l! ?# Y- R
- DATA = key & mask[idx];" T5 r$ i% A0 F+ C; S7 m/ f/ d6 h" h
- // 如果已经完成7次移位,则一轮读取完成" t5 n( ?: F; H
- if(idx >= 7)
/ u% T+ |& Q4 D+ E - isReady = 0;
, t% L7 W: M7 Q8 _( n - }
$ v- m& r) t! e3 H7 f
: f& J9 y$ W3 C( I- static void INT1_Isr() interrupt 2{
! t$ M1 q& ]% L: U0 p) X - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断8 U4 E- Z) q- l, x! A
- if(LATCH == 0)/ E1 D1 j3 X9 P' c1 e% F, p. E c
- return;
5 W/ s& S5 n, e- g3 v9 s - // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA
) d. ?4 z9 D2 _ - key = bufReady;7 g% g/ X1 r" P! c( Z2 Y
- DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表
6 \- J4 J+ i7 \& g+ @# Y' i0 I6 T/ S& [ - idx = 0;6 c( j$ _0 }/ |8 v$ ^7 I
- // 允许CLK进行移位
D7 d5 l& ]( Y. g - isReady = 1;
9 I p+ ?! T3 r) {3 Q2 f3 d. A p P - }7 H3 f" ]4 `6 e% O& t) |
- 5 ^) n J- y7 e7 k7 X* A
- void main(){
) e$ \, c& l) A, S5 s/ ? - GPIO_Init();/ |- K& E$ L7 |( n; W9 \
- Interrupt_Init();5 G; g2 y, j3 ~4 j4 U6 f3 {2 _
- % b. Z7 R1 D0 o3 g4 q; R
- while(1){
! y9 N/ g% @! u F5 l - //PCON |= 0x01; // 进入省电模式2 U1 Q7 J9 o* D: w
- //_nop_();
% [2 h e2 b# E5 R! } - //_nop_();! Z& }1 H6 J' m9 E* v+ z& D! Z
- //_nop_();
" K8 c$ H/ J9 Q9 U - //_nop_();8 I+ W+ Y+ z: f# T/ ^# f
- buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));
3 v$ x0 t ~. Y+ I+ Z- J. ] - bufReady = buf;: w- A6 C+ B) P8 r9 O. z+ F- `
- }8 C8 e; y, g, y
- }
/ f% D- I8 a4 }2 h4 ?
复制代码
9 `; m$ j! b, T# @. A9 |% S! p硬件参数配置无特殊要求,晶振频率选择12M即可。* |9 Z( [3 M' ]+ s) x* m
: [/ `6 u2 m3 E" z; ~9 H
这是编译好的固件。 |