本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑
) \3 z+ U3 f( x: `+ B. g- i
6 z% Q+ p- `: t没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。( q' Q7 Z2 Q7 h% _5 e
# j4 a& t# K. @; Q, @: L
; ~2 o$ D, a- D: d5 ~$ O9 {发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。* F+ W9 Z7 z" H% u, F. d
! N6 u8 X( f& I1 w; G6 O3 q" y( b
买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。
; v2 H8 T8 e3 `& T3 Z2 ^9 [7 g为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?
& k7 s1 n9 n& K9 v/ y然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。1 ?1 Y7 [8 Q: u1 F2 R$ h
/ w# j" t9 ~6 m. E ~
$ b l* Q5 e" l8 r$ m% d. C
3 U- [- L( c" x2 u# b最后把按键、导电橡胶和电路板装上去。
- d0 D0 L1 |6 K B精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。/ Q% V* b/ m# A) m
2 x1 X8 d: S1 W6 X
( w4 q/ v5 L4 o7 l% n' Z
完工!
, ^4 j T9 {; ^2 g& ~( i5 E4 k6 k% O" C4 W- S( L0 A4 i
6 `/ B' T* _9 y! g, w这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。; S! F9 @1 e) G7 w2 o# M
: B6 d5 w. t$ e4 q$ u8 k
6 ?5 D: z& f$ r F/ I; A接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。) Q& M* K7 Y$ j ?; i& s+ u$ {
0 `1 W7 {8 g( Y- n$ z- |* O
8 E" ^5 c! C. U1 d5 J
0 L4 ]# {2 _% \' i, K; w' L& [# Q
电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。4 I6 [1 _" b, o2 i/ \
* s k9 K% S; k' J% z" N5 y$ _; b
实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。8 F1 i( c2 P' o9 a
5 r/ I; |0 X8 ~) l
这是电路图,有兴趣的朋友可以参考。
Q) o' y9 H( G7 I) M+ }7 G2 _! q& ]& I$ ^" A2 e
% f/ O( a7 U5 p7 _! Z
我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!
4 \' S+ G- {) n% q" G6 y& I) D编译器:Keil 51。+ l4 }0 r; G' z% p5 L: r% i, ?
) C* g7 M1 S4 c发射端:
. y x8 H5 q' s& |( Y- #include <INTRINS.H>, x3 Y: K; H. l- E
- #include "STC15W.h"
+ }1 V6 A) t, A, n+ | - % d8 Y" r$ V! j' M: g) s j
5 o+ y d) A) r; d7 e) m# A/ g- /*7 J0 L2 x' L3 ` ?- c3 v
- --------------------------) g9 G2 X9 o( U( D
- |1 (OUT) \__/ (INT1) 8|
0 x6 c0 k6 b$ t; z- a - | |
* O, e5 t# m* S+ D; m4 [: H T* t - |2 (VCC) (INT0) 7|
9 \3 F/ u: N, e, ~+ k - | STC15W204S |( w* P% A& D O! l* l1 @, R6 z
- |3 (LED) (TXD) 6|
) w* C" ]7 E8 }1 q( U; a8 W& f - | |: q# y0 O/ n% f" S1 o
- |4 (GND) (RXD) 5|. k ~+ @" h* W' f2 c* c) ]0 G4 @
- --------------------------1 w2 V: y6 L' x" m1 H9 j
7 ^% n4 L) }% b- LED ---|<|---[===]--- VCC
# b, v3 Y! ^: V6 k l" `* c# O) k! _ - Red 330) G% ~0 @% P: }4 I
- 0 r& f$ X( c" r3 s3 w: C Z; p" s, z
- Fosc = 6MHz
. B' [# T5 J# m' s. ]* ] - */1 _; F' m% l& T k. @7 m
! c. p9 [! P y) w t) U9 i1 a8 X- $ }* F& l3 X* m$ A: T
- // 矩形波输出脚8 q9 N6 q, o6 e* m* L; r$ |
- sbit OUT = P5^4;8 D5 `& h/ V& Z& P' I
- // 低压指示灯引脚- A7 I' h/ m! g: M8 k# z
- sbit LED = P5^5;
) {6 O) u: g3 i( p; y8 l3 w: O8 f
1 n3 W7 ?8 ~% P- // 停机标志位: x/ G" Q: O/ s
- bit isReadyToHalt = 0;4 L: q% l. n" n0 C) A- ^4 d! U3 b$ j
- + E) E, S/ [# y$ F# W: O! `
- // 矩形波次数累计
& P- c% }9 f- i! L - static volatile unsigned char count = 0;; Z4 X* _- r4 k# F, S9 u
- 2 }* g. W4 h0 h! i
- & J6 H% W% ?% i3 L" h4 Z3 l
- static void GPIO_Init(){
% V8 [! z @5 _6 X5 B - // P3口设置为准双向,默认靠内部弱上拉输出高电平
5 y! z" \0 _! U7 v - P3M0 = 0;5 S- `0 ^. {- ? j. ?
- P3M1 = 0;. ~$ C8 O: j* L. ~# y
- P3 = 0xFF;
9 H) P* T& B, l/ n, ~ - // P5口设置为准双向,默认靠内部弱上拉输出高电平
; Z2 e1 \) O# e/ \/ t* q - P5M0 = 0;8 w, R# R$ n! |% x
- P5M1 = 0;$ J; C" U8 A t! z. K4 G! e
- P5 = 0xFF;
\3 `; _' o* H$ E - }
# P s& a" I( F' Q3 ] - / `+ c6 C( w4 a6 ^! A r
- static void Timer0_Init(){" ]8 m3 I/ \/ W0 c( j3 y
- // 16毫秒@6.000MHz
6 t$ L; A9 F/ d& Y2 i - AUXR &= 0x7F; // 定时器时钟12T模式
5 b& A9 R. A7 H; n( U$ P - TMOD &= 0xF0; // 设置定时器模式
2 v+ c& b: G; c4 _3 ^3 d2 o0 D - TL0 = 0xC0; // 设置定时初值% c$ Y- J6 X" e' Q9 g/ h" d
- TH0 = 0xE0; // 设置定时初值0 S- B) f5 C. ^
- TF0 = 0; // 清除TF0标志: ~6 O; O7 u4 k; O/ G8 g
- TR0 = 1; // 定时器0开始计时
9 ^1 i+ n/ ~: I* E: n - }( z7 e# |+ g# z- {# Q3 H
- 7 T1 k; w1 W* H% c" }; ^' }
- static void Interrupt_Init(){
% q u+ O* b* D/ i! h- D% m/ p - // INT0中断触发类型为下降沿触发5 W k i0 b- | t
- IT0 = 1;
5 U0 `! R. R W; h, c - // 允许INT0中断1 g& L: |4 K) @5 d6 d' d f
- EX0 = 1;) X0 w, B% Z2 u/ n( P b: ]
- // INT1中断触发类型为下降沿触发
: O% {& d+ I, P+ n - IT1 = 1;: d' j8 F- ]# P5 r: \7 q# ~; L
- // 允许INT1中断
% h4 O) z% |1 T- D/ N - EX1 = 1;
0 i5 ~9 _3 ]6 G, a7 B. B6 a6 i - // 允许定时器0中断- B g6 o6 I$ f/ J3 ^1 O% @
- ET0 = 1;
$ s6 n1 @9 _7 N+ F& Q4 h- ` - // 允许全局中断
( V; W4 d9 ?% n5 r- d6 d - EA = 1;
" R; I$ Z4 T) l- W+ @ - }
, ^& F- B. W9 R - " u, G- ?" f& m* j4 }
- static void INT0_Isr() interrupt 0{
3 D/ b2 @1 x7 x - // 矩形波次数累计归零$ k. l G( k2 e* U
- count = 0;9 U) j% ]% c4 v9 B: ^. @
- // 重开定时器0中断
8 k2 r$ S. ~; ?" W - ET0 = 1;
* V5 V+ F- b6 u, c' s, `# y4 X - }
( W' o9 l8 z8 G" z/ n& W0 A( U
c" j/ a$ H$ s6 Y- i6 l- static void Timer0_Isr() interrupt 1{
/ s0 P7 g3 c) t) G* O3 x- ~ - // 输出矩形波
- y& U& ?& r$ E- _5 {3 }$ @ - OUT = ~OUT;( ?: s' S1 n% y
- // 累计矩形波次数
- q- {' M7 v( t ]5 g - count++;
' K8 `1 c7 `2 b - // 超过250个矩形波,准备停机4 y6 t8 U& U0 f
- if(count > 250){, D- |# f: j8 U5 b/ y: _
- count = 0;
# y$ v- S9 w2 ^# }1 {- r - isReadyToHalt = 1;
3 L( m1 q; P/ s5 E) ~2 E0 G F - }
+ U7 w3 O, E+ L. o1 t+ r# D - }2 |$ V/ z2 G, w
* f& l; v; z. G- v- static void INT1_Isr() interrupt 2{
- E. e% ]5 p- A i0 c, n$ O - // 矩形波次数累计归零
6 q, @' e! a, Z3 |- N6 a - count = 0;
$ U$ `8 S" f/ S5 Y; J& \ - // 重开定时器0中断
8 f! R; e: ?! P2 t* F. v" g - ET0 = 1;
5 A; I# I1 i% @4 S - } x9 W) D" g1 q# z+ O7 q
- 5 k' k6 w0 H- u& E
- 4 }4 y; h7 R7 ]) }2 B
- void main(){1 }7 ?" ?0 [- {
- // 初始化P3和P5口
% k. G6 f0 g7 J - GPIO_Init();7 ]# ?/ [6 g. A- i
- // 初始化定时器0
) |6 `1 m2 a$ T2 i: N c1 o - Timer0_Init();
0 x* L3 Y2 P" y - // 初始化中断
; D- l6 p" O# d9 ~3 ? - Interrupt_Init();
2 V' T7 y4 \& [9 C8 ^/ } - " Z& g/ p* O5 l1 e5 f
- while(1){: V( L8 A; W/ e
- // 检测电量- c- k$ x/ J; e' R0 o
- if(PCON & (1 << 5)){9 \) C( m b1 K6 r$ c6 D; H
- // 如果电量低,低压指示灯亮
4 X. ^' E/ t( u4 X' f0 n5 ^! M - LED = 0;
f5 m" _0 ^: f$ q9 Q$ { - // 清电量检测硬件标志位
& Q4 t% h+ a: v6 N, u, p6 q& w - PCON &= ~(1 << 5);
* l$ ?" G. T. s1 {8 ~3 ~ - }else{
" d7 q, }9 ]$ p. b - // 电量正常,低压指示灯灭
7 J8 l1 }: t7 z) [! V - LED = 1;, L. Q4 c7 U1 S( }# H5 ]/ }( Z
- }/ L+ O8 S, q4 K2 N8 ? h) W
: \- A6 M6 v& h7 k0 R) o" K( g- // 检测到停机标志
/ p) F" @2 F2 I7 r3 r0 a% l9 K - if(isReadyToHalt == 1){
$ x6 }, P1 ?. r4 b( L2 h0 x - // 暂时关闭定时器0中断
6 N5 b) `$ f& k3 B" @ - ET0 = 0;
) A7 N3 k0 r0 X) [8 T' F7 Q5 o% n& f - // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU
) Q; M1 m( a3 @2 t. Z& }2 u4 W - OUT = 0;
2 {6 e" \! X0 q; X3 f+ ^ - // 停机之前先把低压指示灯灭掉,以省电# y; X1 P* S$ I; [" V5 J) [$ ]/ C
- LED = 1;
3 U7 B5 G& r1 k- [ h1 t# ~1 D - // 进入停机模式1 N. N1 T' z2 F; Z* U
- PCON |= 0x02;
' J- t# C2 `7 ^9 ^; g. Z8 ? - _nop_();
A0 r* {2 p- i& G - _nop_();3 I+ v3 r* ~6 f% T" i4 Z# b
- _nop_();9 f/ p, \5 i4 [; g
- _nop_();* ]6 L; y6 I7 z
- // 唤醒,清标志位# c6 H2 C; z( @/ h8 f
- isReadyToHalt = 0;- b4 A% J# D7 x
- // 重开定时器0中断) T0 N6 [! B0 [: B: ?9 V4 y! S5 ~& ^
- ET0 = 1;, y' b; O& B" ^
- }
- T1 m+ M& ~2 F1 J9 I* J# X - }
+ A; j7 v$ O7 w0 _* g& |8 `' k - }
: r+ |' i9 A \0 P& d% M$ Q2 `, |: {
复制代码 ( T6 P& ?! P* W" v% J
硬件参数配置: @$ f1 ^4 T: E, ]0 l3 x! ^
) l C- v* Y' E$ R; Q: {* b6 x; N& |) h+ E
接收端:2 t/ ?" F( F# g: M! I
- #include <INTRINS.H>2 M7 r; a( R$ V3 q
- #include "STC15W.h"
* ]3 R: g4 m6 g9 y4 u6 ?7 k
/ c) T$ s. V2 g, g! L8 A
1 O# {1 ?, {6 \. ?5 t- /*" Z1 ^# ^% h0 A" P; h9 @
( Q6 Q5 r- r: M5 h- {- *---------------------------*
7 c% Q5 ?7 H# ?6 C* M- ` - |1 (GPIO2) \__/ (GPIO1)16|& P# D* T5 Q9 R k+ p
- | |
- ]2 F7 o" ? @0 q5 X, v8 l+ m - |2 (GPIO3) (GPIO0)15|
7 t& H: l6 g3 k$ s - | |# Y( [# k# y5 j& j
- |3 (GPIO4) 14|0 s2 ?6 S0 F9 p9 ]5 P) _; W
- | |
$ W9 ~- p5 E, x- s6 R+ K7 H - |4 (GPIO5) (DATA)13|* o: X+ X7 k; E5 r
- | STC15W204S |& D" }' U% e ~: ?9 g9 }
- |5 (GPIO6) (LATCH)12|
& b! c/ z# ?% X - | |
C" a% \. r* D# t- g6 G- [ - |6 (VCC) (CLK)11|' t( ^& I: j1 m5 u& N, j# ^3 s
- | |5 c' V0 }' d5 K+ E Y! \
- |7 (GPIO7) (TXD)10|5 G [4 \5 z% X( P r
- | |2 l6 J- R. m5 `" E2 R
- |8 (GND) (RXD) 9|
6 `, E3 S: b5 f; {; x4 R' d - *---------------------------*, X- {% c8 y2 q. L' I
- Fosc = 12MHz
& L# i4 |1 Q& M$ b6 R, l4 `- y
) t6 d' b- l2 U( F6 {6 O- P1.0 -> 上
1 ~) T4 j/ J* N; Z - P1.1 -> 左( |" R+ d4 |5 ?- P' e
- P1.2 -> 下
+ `7 g9 g8 i3 G# M! V - P1.3 -> 右% D6 j T$ g. k2 L
- P1.4 -> SEL
, ~5 h" D& g2 e - P1.5 -> STA# f5 h: u% T. Z& G1 O
- P5.4 -> B
2 A7 H0 F- z: e6 Q$ v6 W, j - P5.5 -> A
9 O/ G" z" E# A) Z# h - - @; d5 y" O3 V4 o. x e3 U
- */- s4 @( c' Q: U0 |) d+ w
8 ^# y/ x9 \) e" i8 B% f6 k- ) K; @5 a8 G( Z2 j9 P8 j7 A( U2 H
- sbit CLK = P3^2;( B8 p* u$ l. n. X6 a
- sbit LATCH = P3^3;
9 [8 L# P: n/ m* C - sbit DATA = P3^6;$ T& b1 f( s7 |, E
- 3 e0 U& {; M# X
- bit isReady = 0;5 l/ L/ `( w0 G7 ` `* I
- static unsigned char key = 0;
' G! O1 G- o2 A- I8 ^ - static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值 \8 x2 d' Y$ H% Z7 w; y+ j7 c
- static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份
+ i+ w& m5 Y$ t( e2 X7 X - static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中; F) J! R9 |3 a# h! H+ W, q. h
- static unsigned char idx = 0;
1 y8 n3 B; ?, M2 i. g( C5 K - ; N0 k2 u- a. W+ O& N' {! D" V
$ f. _* P g0 p, L- static void GPIO_Init(){5 ?0 n& y! f* l3 [3 g
- // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平
) e# g4 Y! E/ V+ b! \) {( u+ p# ~ - P1M0 = 0;
2 x3 l ^: X% H - P1M1 = 0;% z7 P; w- _4 D( V. q* q# Y
- P1= 0xFF;$ q l- b1 o, u( S! x' N% ^
- P5M0 = 0;2 C! H$ H8 G0 E# _
- P5M1 = 0;
' O+ r3 O4 }4 T) K - P5= 0xFF;
1 i( c- m' `" t& P - // P3口初始化为准双向
1 J! |' H/ n9 v& {. ? - P3M0 = 0;% a X5 Z) G4 r$ ~8 b
- P3M1 = 0;
+ Y* H0 h5 L" ]; n3 ~3 {6 t: |. V3 } - P3 = 0xFF;* g" v3 p+ ^2 s$ u# Y
- }
( n) D6 p1 j' v9 f1 X# G$ ~ - & ]* D. H) O( x3 H w% W
- static void Interrupt_Init(){
5 w: V$ }! B) E/ Q P' o0 s) u/ r8 \ - // INT0中断(CLK)触发类型为上升沿+下降沿: x; Y) J5 M& J. B# z! O
- IT0 = 0;& c$ w* h, |; {; x: ]/ S+ C
- // 允许INT0中断
1 P2 I0 o4 X% U( h0 F - EX0 = 1;
: u7 b; ]0 y) a2 Q8 S: c - // INT1中断(LATCH)触发类型为上升沿+下降沿
8 @3 s! ?/ x. |& O0 _0 s - IT1 = 0;
f$ Z* n9 v) c/ _! c - // 允许INT1中断9 I2 A$ C% G$ X J# G
- EX1 = 1;
, n* H; M0 C0 \4 m( c - // 开启全局中断
- F7 B1 v; {1 `8 @, ?; e' ? - EA = 1; M7 o/ \: ]% X K
- }# u4 ]+ i" x3 M4 }+ B: O: I: p& L
( w" ^ f' q( m" k- static void INT0_Isr() interrupt 0{. m% [3 ]7 J9 ^& S3 b
- // 只有已经成功锁存才允许CLK移位. x" k3 r* c( j' c; h
- if(isReady == 0)
/ U6 S5 O( `! `& V2 ? - return;
7 q' m, ~* O" M. K, M; E5 X" p% W1 f - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
4 i* K( ~/ p3 h* I - if(CLK == 0)
: I$ ?7 q! r. h* [1 b - return;
9 |& V0 i" o# w. l' P4 k - // CLK上升沿到来的时候,取锁存值的下一位输出
* e4 n6 z5 |$ U4 g' B - idx++;2 E! `- I4 l3 z2 D
- DATA = key & mask[idx];
+ P9 T# N- e# G+ } - // 如果已经完成7次移位,则一轮读取完成! }6 E& j. j3 ~; O# X, r
- if(idx >= 7)# m: Y5 l ^" T( [
- isReady = 0;
, r" F5 O8 q. d3 w5 W2 S - }: o M8 K5 \; A1 Y
5 n3 u+ ? k. J6 v" @0 v9 |- static void INT1_Isr() interrupt 2{
0 a3 Y2 [; m) }5 I* k$ X: K - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
3 F* v5 f2 o8 J2 L ~3 Y' Q - if(LATCH == 0)) b' g! @3 \1 F: Y* k& w
- return;# K0 e1 d1 ~2 P/ i* ^- h
- // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA! ~0 e1 m: U3 | k& c
- key = bufReady;
) p- d5 F+ Y# T7 O7 u. L - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表
4 W* r) }+ R. A: j7 Q - idx = 0;
8 n3 I4 B5 M. }4 ]% A4 n- ` R8 h6 X2 p - // 允许CLK进行移位1 \% |* H6 t0 Y
- isReady = 1;
% V6 B* J, u& z+ s - }+ c K, u! T% [: F/ S
- 5 m a: _9 X$ g1 u( U
- void main(){
6 {4 G; ^- m T) z& K h - GPIO_Init(); c" E0 A! i; n' v* v
- Interrupt_Init();2 R+ m0 X! ^6 E! f8 g3 R
- + d. W* @( P/ g/ p4 K# W
- while(1){
3 w1 S6 O# S' W A0 I - //PCON |= 0x01; // 进入省电模式: Z& @" E" m; f' y! l" _5 t4 Z
- //_nop_();, P8 O$ z$ k) S& c
- //_nop_();
" w5 z3 o r9 u# ?5 K - //_nop_();( B; a2 Z) m# h8 [
- //_nop_();4 \0 K9 S( c! S9 n
- buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));
0 z9 j1 P8 y1 a - bufReady = buf;
" q$ g* b" P% B( b. M- Q+ A& Y - }( N+ V! ~- l. q8 l5 T; Q6 V
- }
5 i# ?1 j- {% h
复制代码 + q. i# G+ V6 n# t
硬件参数配置无特殊要求,晶振频率选择12M即可。
+ Z' a) C5 u# r: O8 M7 `
. R- M; ]8 d) M/ Y A# [这是编译好的固件。 |