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