本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑
# F' D3 @1 D5 r2 p% e5 [/ _. R' I5 o0 s. C4 A
没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。
q7 [) {4 W: ~# z0 N7 j- f5 y) o8 N* R. y
, \& O1 [9 p) Q( r
发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。
- k' ^, j i0 d7 N* w1 }; e- W, J. u/ o0 M1 i! \
买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。
7 _5 ^7 Z. k1 _为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?
6 o7 H/ c0 u* ~+ Y) V4 v然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。; ~' Q6 J8 r2 {1 N
( A) E) x, y: L. d% d3 L
) P4 m( X9 _" N ~. I0 o) z9 R; N0 g0 a
最后把按键、导电橡胶和电路板装上去。. L8 w6 @$ b3 L- v; L* V ^9 K
精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。- T, o4 x. }" E
. o5 _! o `+ b! w9 W
+ x2 z& o% o# ?; t! ~0 W9 k完工!
" d3 P0 [7 O' A3 n7 B% m( q7 V6 x# n" k; r' v
1 M4 `; A5 e- R$ x这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。
5 p# ~' A+ }* _/ G, m' u/ Y$ A& M6 a! C
' y. i( k! Q s5 c/ O7 y$ D接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。
6 o! [, ~) F, O5 [$ m3 d6 h/ r4 d
1 M9 l* t" |1 P1 G( V% ]2 F7 P
2 |9 n! d* P# d, S" ~1 B# x/ }电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。8 l" Q W8 b6 v' z+ D# e
! a' P! n+ d2 [: K* O, T; |
实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。: |' V2 | B3 X6 N T4 e
; E( z# ?- x; m) Y
这是电路图,有兴趣的朋友可以参考。
7 i! J1 K1 ^& V1 F& o0 ?; D0 |! [9 M1 @0 e; D: u' R
; H9 [5 H* t1 x% x0 E, e) j/ T2 j我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!' I8 u: ]7 \2 T9 i/ ^$ H5 d- k/ F4 |. E' B
编译器:Keil 51。, u5 X8 U5 e' ?4 X8 o# v1 X
* I6 p; E% ^6 C% b
发射端:; k- R- W3 e+ j+ Q3 m! a
- #include <INTRINS.H>0 ]: G. k6 p( x8 Y' r% D
- #include "STC15W.h"% U# P8 p6 ?$ X1 n! o& P. y
! l% {4 g1 H- Q( p, h
, T$ {+ ]5 x! i- /*+ G- Q2 v+ j2 f, ~8 X* J V1 L: A
- --------------------------3 @; y9 i( h3 U5 W1 L" C9 D6 b
- |1 (OUT) \__/ (INT1) 8|
. Z9 J, W; |, M0 s0 Y9 { - | |
# Q- W8 |; E6 m* e' d* S - |2 (VCC) (INT0) 7|
- E3 w6 T2 R. b: d3 N C" v8 I - | STC15W204S |
$ X5 h' }3 p4 _0 ^ - |3 (LED) (TXD) 6|
5 E% U; @) c, q3 A7 V7 }& W* ^ - | |
- K5 K7 m1 I: g6 P& U) ~& Q - |4 (GND) (RXD) 5|- j1 X a, Y+ K0 O
- --------------------------
6 ?2 T1 Z B2 a% Y" ~" O
7 |6 N2 o: A+ Y5 n- LED ---|<|---[===]--- VCC$ l$ V# h8 k/ `
- Red 330
6 U2 l x' D# m3 q$ b! Z - 9 G! x( D3 d# i. @; v7 o2 D
- Fosc = 6MHz
t$ r v! S: T1 Y4 [7 P - */% Z' H$ }" q& k* s" e
5 @$ h/ p9 r/ h1 L: b- 6 d6 E4 _, l: A* `) y
- // 矩形波输出脚" ?+ M& g6 U+ d) V, ?
- sbit OUT = P5^4;
& w, w* ^! z5 v9 G% N& j* [ - // 低压指示灯引脚6 }* N2 P: c$ }4 f% ?8 E& O
- sbit LED = P5^5;. g+ `. p3 ], `) [9 e- i8 m
- , `) u" ^9 i$ D4 P
- // 停机标志位
+ y4 D. f- d! F, L( i0 t2 s+ m - bit isReadyToHalt = 0; g& Z9 d7 U% m w( j, b' r
- - G/ Y1 F9 d) [! l# a( ^
- // 矩形波次数累计* l" H/ ?+ g: m0 w5 k# J b; ]. B
- static volatile unsigned char count = 0;
, D ]- M1 [' [7 A; J
. _" }- X! ?* _7 z" r4 N3 q. ~
" e# L) P$ l( \ t9 ^/ j- Y- static void GPIO_Init(){' @; D6 q9 o* X
- // P3口设置为准双向,默认靠内部弱上拉输出高电平
' a9 f& d, m9 F - P3M0 = 0;; p! x- c5 V) s/ p& u0 C$ t9 r
- P3M1 = 0;5 a% N+ p# P' ^4 H3 ~
- P3 = 0xFF;* g/ k* L. N9 D& o
- // P5口设置为准双向,默认靠内部弱上拉输出高电平5 L# `9 T8 e4 A, M
- P5M0 = 0;7 W4 H- H4 D* y. y$ ~: K9 I
- P5M1 = 0;
8 `/ ^( B, t' T( x; V- [ \ - P5 = 0xFF;
v% @0 f8 L5 I' A - }. q* F& e) d: [4 k( q" K
6 j. M+ S# R A9 E- static void Timer0_Init(){1 I: q2 A5 g+ f+ S" D
- // 16毫秒@6.000MHz
; E# F2 i4 S% R4 @3 c - AUXR &= 0x7F; // 定时器时钟12T模式* a! K" X+ r a. k T8 U
- TMOD &= 0xF0; // 设置定时器模式9 ]9 r9 @% W; E% n
- TL0 = 0xC0; // 设置定时初值
* n4 M) z. r7 K1 x) Y" h2 O4 E - TH0 = 0xE0; // 设置定时初值# J: j) h# l; t: ^: Q
- TF0 = 0; // 清除TF0标志& `5 A g/ s% q
- TR0 = 1; // 定时器0开始计时: ~3 ^$ \% E0 j( x! g
- }
( p! R3 b# G% \' q1 p# r
# o% O4 n9 y% Q# n3 P+ a+ u$ [- static void Interrupt_Init(){1 r+ S& o& X* g$ e, V! F& C
- // INT0中断触发类型为下降沿触发9 \1 ?& w( w+ K2 p5 \* V& U- P
- IT0 = 1;
: j! W1 V7 ^ p' I: ]& e. w" V) q - // 允许INT0中断
! } p* y! k, x$ D& h - EX0 = 1;" V/ _. I* F% h
- // INT1中断触发类型为下降沿触发: Q; l* I$ N5 q: O4 ~ [9 M) Z$ c8 E
- IT1 = 1;
" \* j0 }" X, e' E - // 允许INT1中断2 T9 u. [- Q5 m
- EX1 = 1;( [6 e4 U3 r+ H9 j% |3 z# n( J1 e# J$ V
- // 允许定时器0中断
. E1 `* f) y. T" V; D, `& @, a - ET0 = 1;/ D) ?/ m1 j: b7 S" t( F% p0 @2 \
- // 允许全局中断0 x) B3 _- A# I9 b* `2 u" j& ^
- EA = 1;
* R! d# w( ?: K3 X- E - }
0 i2 D# u6 k3 u% R' u
$ j- k( @4 a O' O/ n- static void INT0_Isr() interrupt 0{& x. d( \1 Z: Q O: Y- U
- // 矩形波次数累计归零1 ?% i2 W( Z4 H6 i; v2 b
- count = 0;! h1 ^. N) D* E( V7 D
- // 重开定时器0中断2 h/ s/ A$ h* |3 X& r2 W
- ET0 = 1;: s4 o# I, ^% n4 F1 @5 D ^' A; N( F8 S. ?
- }1 b6 P' J8 O9 A$ P
3 d5 k) T% P7 E8 }) L! C- static void Timer0_Isr() interrupt 1{
6 z4 E: ^* t3 l* t' Y& `3 p) n - // 输出矩形波5 b; l/ K( V. ` p: `! i/ i
- OUT = ~OUT;/ ^* T0 p5 u# k; K3 `
- // 累计矩形波次数
$ q* w0 c" x, i& } - count++;
/ k' w4 }# F( t; m& l - // 超过250个矩形波,准备停机
9 G+ G& h5 I. e! p, ~ - if(count > 250){6 O, l! W) @0 P# W
- count = 0;
7 S+ K6 o5 ?3 b$ R' c: b - isReadyToHalt = 1;9 J% |6 s& y% `+ M/ x: k1 l) Y Q/ A9 H4 U
- }, O: j" g& X% D/ L* [
- }
: d8 y2 H9 @* l7 Z2 h
) {# L; e F# H0 { U- static void INT1_Isr() interrupt 2{
8 w% G1 N. @ [; J - // 矩形波次数累计归零
$ s8 I4 J) @9 l8 T A - count = 0;
9 ~, H+ _# m* m4 T& W$ ~ - // 重开定时器0中断/ k. x( M+ ^, g5 n* k& |+ k: c, Y
- ET0 = 1;* v3 y8 m; ]0 ]; C- g$ ^, X
- }* j; ~4 O! |, x% b2 f6 e2 {' ?; z
/ k6 k- A. E5 M& `# \- . U9 Z" E# X# h: ~! Y& D6 M- H
- void main(){
- Y7 @- t7 v7 E3 J - // 初始化P3和P5口' n5 c2 X- ^8 w- l$ O8 h0 w1 C
- GPIO_Init();4 e2 `* j$ I/ l" Y& f( @9 G
- // 初始化定时器0
0 o" w4 o- d5 f2 L9 _ - Timer0_Init();
8 @. V) x. T P! g9 G; u - // 初始化中断$ G) [/ ?" S" k
- Interrupt_Init();. d7 |6 n f9 A( R
-
! J) a1 {. S# A, N - while(1){
; G) G" x( {8 M- v4 X - // 检测电量
5 a4 ]- R: `& i& d - if(PCON & (1 << 5)){
_; g" f' r) j; g% S - // 如果电量低,低压指示灯亮
1 z! H; G' v: e" C - LED = 0;. j. x+ `9 R @- f; V0 V
- // 清电量检测硬件标志位. R7 N& f( R! n
- PCON &= ~(1 << 5);
: f8 G q; e+ Z s7 J, G0 ~( ^ - }else{
, S! N3 o" b1 \8 Z - // 电量正常,低压指示灯灭: y* t/ f4 ?( N B6 J
- LED = 1;
/ O$ v5 S b0 z9 }4 N. h) r9 i: R; o - }
8 O$ R/ d' X5 q - c, |1 k, m. _$ Z. n! ~( X# f
- // 检测到停机标志
+ f% z9 o1 i1 P; A! N: S - if(isReadyToHalt == 1){
8 I. |5 R4 G& F: s* k - // 暂时关闭定时器0中断
' R/ Y) `3 _% |7 N - ET0 = 0;
% o ]# Z( L) u6 Y* m - // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU; V5 W# M- @5 ^: z" \# T- x
- OUT = 0;
& S5 x; L& n, p - // 停机之前先把低压指示灯灭掉,以省电* I1 l! l1 N' n- o0 h( X
- LED = 1;
5 S: [: n: g$ e5 z& d - // 进入停机模式1 Z3 n, E* m" V6 O1 t
- PCON |= 0x02;+ w% V4 _! j1 J4 ^
- _nop_();0 ~1 ]' ^2 W( c& ~% @* b
- _nop_();+ {8 r/ e1 w- C9 H
- _nop_();5 }9 M. V9 }; m3 k/ W6 u8 H
- _nop_();
& ? ^) W' E/ z6 q, }' V - // 唤醒,清标志位
4 B' u& ^) v4 J9 P+ z - isReadyToHalt = 0;
$ b) X. {* d( \6 W2 p - // 重开定时器0中断1 Z# f( u8 O% A1 }
- ET0 = 1;
% g% E/ N: ]6 P$ l" q - }
8 N/ p2 G' x5 r* H - }3 `" N2 H% J3 B. T- { _
- }$ Z0 L [6 j1 q
复制代码 2 b2 J+ }$ h" h
硬件参数配置: D3 b/ a4 c' f
% @4 ?* F* A$ z( b# s2 A
7 Z; x5 u* r2 y# X, P- j1 U
接收端:
/ b. ~: n' Z" _, F) S& h- #include <INTRINS.H>
h6 x: b5 H" t6 U9 y. Y- u, j - #include "STC15W.h"
0 v2 q# i9 n6 |+ `( e0 q: @ m - 5 s: Q& w; j Y* D% R
- % e4 q( `! K* |1 V% O6 n! _
- /*
* Q' Q6 s' V* ]4 e - 4 S M' Y5 x& C, E
- *---------------------------*
4 i# g( {4 v9 a G3 a; [ - |1 (GPIO2) \__/ (GPIO1)16|' Q" D: Y* \ U
- | |
- y5 y; y0 a4 B: ?# _+ } - |2 (GPIO3) (GPIO0)15|
8 I8 h+ T8 k; T6 @! p7 X/ B9 H - | |
5 w9 }0 O! J6 g/ T* R) e( X$ W$ i. T" i - |3 (GPIO4) 14|" ?! g% I6 t9 t2 P
- | |; u" s& H* X0 j& ~: R" }
- |4 (GPIO5) (DATA)13|- Y* t& h; }6 g6 D5 d1 N3 M
- | STC15W204S |) n' H6 F; K9 @0 w+ K
- |5 (GPIO6) (LATCH)12|
4 M% M3 l8 L+ ]+ v1 ` - | |0 \( _5 p9 I7 u+ f2 L5 S
- |6 (VCC) (CLK)11|+ k8 Z5 X$ K' B. R& X
- | |
8 M6 {0 o' [% Z* h - |7 (GPIO7) (TXD)10|8 u# R2 M4 E- P# b M& s" G
- | |
) p$ A' X8 J" ~& B/ E - |8 (GND) (RXD) 9|
. [" m" V( V: l% b. @ - *---------------------------*
* M+ ]: ?- w1 J) R* P2 H - Fosc = 12MHz
6 h/ i+ Y, R4 a) q - 7 Z( H' f4 R: d g5 v5 d- i
- P1.0 -> 上( x8 q& I; R1 R M8 B
- P1.1 -> 左
* A: e# G, G7 u/ v - P1.2 -> 下. e1 E0 W2 g6 F% J1 z0 N% ?
- P1.3 -> 右
! b+ G/ S7 E; [. E - P1.4 -> SEL
4 V3 e3 b: Y4 y, H7 x4 B - P1.5 -> STA
' n7 Y3 v3 a9 |5 o7 Y A - P5.4 -> B3 p" M+ Z# a% N3 L9 w9 y
- P5.5 -> A! U7 i; `% W0 x/ R
, I8 S" j g' p0 H) R& f' h C- */# K6 ?( c1 f# d( f+ `
- ' u3 v# O m" q, D
- 0 X8 q7 t& O. i( w' K5 e* w
- sbit CLK = P3^2;7 ^$ n& p5 d; t: T+ a. c* P
- sbit LATCH = P3^3;
, I! ?. D& F ]8 F7 ? - sbit DATA = P3^6; S5 L, `0 |& [* O' T
5 z7 t, s+ r8 \5 ?, Z! c- bit isReady = 0;
+ x7 p8 W. ^ H8 `3 P2 N - static unsigned char key = 0;
4 O$ R; I; `1 W/ a - static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值
1 m5 S6 e" k( H9 ?( W8 G - static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份
3 F: G }" h8 d: \. ^ - static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中6 [# G R. Z8 \- \( b# k
- static unsigned char idx = 0;2 t0 a$ m. {- B/ n |: w4 \ @/ a
- . r% T) q9 X5 m3 I
- & x) h0 z: s, ]( J
- static void GPIO_Init(){; [& j% P4 U) U2 [! J
- // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平, L4 a. J, o: {! M
- P1M0 = 0;) {/ V$ V) g* {3 E4 q; A1 M& a; H
- P1M1 = 0;" l2 p4 O" j6 H! b* H |
- P1= 0xFF;) }0 b' O; z6 E; g
- P5M0 = 0;
/ |' R' t/ E0 \4 c8 W$ D& V# i - P5M1 = 0;7 G) `; z) m/ F- y, Y+ L
- P5= 0xFF;/ N9 D2 g; G. o6 S
- // P3口初始化为准双向
/ N/ P/ Y: u' \7 ]$ x( p - P3M0 = 0;
5 ^8 W) T2 x8 P+ a. F: X' b - P3M1 = 0;7 K0 L( w7 [1 H i
- P3 = 0xFF;
& z! J+ h5 L- A: L5 s2 t - }7 \, n+ ^( P- n* k" [
- $ A( v' x5 r q& \- H/ c! k' ]! g
- static void Interrupt_Init(){* {7 ^8 B- s& ^ B4 H! e5 y
- // INT0中断(CLK)触发类型为上升沿+下降沿
. _, j& x9 `" M3 d( K$ P4 O# n - IT0 = 0;
- c5 {+ p4 f6 T+ l9 m8 } - // 允许INT0中断
: ~) O# g Z* \3 B* h) t7 }9 t9 ~ - EX0 = 1;
5 i! @( b+ ?" q- }3 ^ - // INT1中断(LATCH)触发类型为上升沿+下降沿
9 e0 r2 ~$ ^, g; J3 W9 v- A( [ - IT1 = 0;% e3 k4 M) L$ S6 L$ Y, u5 l
- // 允许INT1中断6 `) \. T1 ^# W8 ]5 F& ~+ k; Y' ?9 P
- EX1 = 1;
2 }7 E0 N$ g" [0 X - // 开启全局中断
! ~3 f+ O/ z# z3 A* T - EA = 1;2 Y$ H, @% A5 r- {4 ^
- }
( h& A5 Q4 N( u6 z R$ S* s7 A - s; f* H( x% g* l; R
- static void INT0_Isr() interrupt 0{0 E4 E- ^$ Q4 g0 ^, y9 ]# F6 \
- // 只有已经成功锁存才允许CLK移位% f( e( E6 Q ^
- if(isReady == 0)
7 j% d6 Y* E- S L# X! {* P5 a - return;
) P5 [; {; E3 ]! L! F% K9 R - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
" s+ b; X% p' D9 y4 C - if(CLK == 0)
1 z2 l8 x) N6 C' ] - return;
9 Z2 {5 _6 G, J3 u2 B - // CLK上升沿到来的时候,取锁存值的下一位输出
1 {" j6 |; L/ b, C - idx++;* N8 z3 y u2 q/ x6 e3 d
- DATA = key & mask[idx];: ?7 D8 V- M( r9 l/ u6 K& V
- // 如果已经完成7次移位,则一轮读取完成: T1 z& F+ R. b0 v+ r
- if(idx >= 7)9 s! {+ G: y" r, X) S
- isReady = 0;" I7 D# t& {( F; i
- }
' s: q8 `- W( L$ i - $ I! _" s! K G+ D3 X9 d! B$ Y
- static void INT1_Isr() interrupt 2{4 E+ v! Q1 G0 D# } G
- // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
, H/ n# N! T W- | Y2 V - if(LATCH == 0)
0 Z0 l" {, V- \+ i& g# j" I4 } - return;" |7 n& c0 ]( E5 g1 g- [: ?/ c; ]' t
- // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA; B+ {' u! O2 S
- key = bufReady; B# q. u! t" z2 ]+ N, M6 w1 L( q7 O
- DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表
1 B' e- y6 R Y! B - idx = 0;" o/ J2 s9 \: F+ R$ D3 @
- // 允许CLK进行移位+ ^' p0 h' {& K2 k) w" G, m; I
- isReady = 1;
: q2 Z" u) Z J$ i' q ^* h - }
: N: ~+ o1 S% T! k3 K
$ R1 ?# u+ e+ ]; l. r& J; w- void main(){
9 \/ N3 {3 y( \* D+ N& K* E - GPIO_Init();
- g9 i5 |% g# S( z4 K0 ? - Interrupt_Init();
, \8 z! e+ H$ l5 D8 A -
& Q# ~3 s3 n* n* r- ~: f( [ - while(1){
) K$ I* |* x; Q - //PCON |= 0x01; // 进入省电模式
9 E& H8 ?1 }2 F3 ]3 g1 u0 H' c - //_nop_();
/ k6 _( f! I' A# |+ C H - //_nop_();
- ~" q" Z Q; J5 s2 B) U - //_nop_();
; `! U4 I3 O! M% f" s2 E2 e& V0 O7 X( n - //_nop_();
9 \& O: u( N* s - buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));' x6 l8 V0 ~' s* K
- bufReady = buf;
" }* d6 `6 F" W/ b% V# a: { - }$ ]5 K9 I) A0 E4 A
- }- w2 E$ c! X$ `, M
复制代码 4 A5 F7 q3 `/ O; l+ ^9 P& Q
硬件参数配置无特殊要求,晶振频率选择12M即可。; _! Q! s1 ~# |
$ R3 H* y* d$ P0 s* @- r- N这是编译好的固件。 |