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