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