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