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