本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑
, D7 P; _( l/ z* m3 F( [/ y# }
+ V5 ]2 I1 {# F1 c$ x# T3 i没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。
* w4 M0 E4 J, z& T( t0 r. H
# o g, I/ {6 S0 o9 K2 `3 T6 J: I, ?2 g, q
发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。
4 P I: e" i y; ?
: s( K9 X) l( i9 g+ C" v买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。
8 z- @9 D' a! C0 k7 t为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?
" O6 V3 ~* d( p然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。
& l F6 {! e2 B
) n8 j5 ?* s A% O! W2 W8 z4 w5 R- b- F& R
$ W! d/ J& J6 ^# t最后把按键、导电橡胶和电路板装上去。, q4 x; C. `+ ]4 s- R5 P
精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。
! Q* x) p; B8 m# w3 F
. F/ F/ E# i" n# a. }# V5 Y3 a: D0 N$ j9 I! B
完工!
! }2 p! V. D* m. y* \; p u& M
' u3 {2 |+ f% o. P2 ~4 ~4 }* Z: Q* }; t |" i7 V! z! C' O& s
这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。2 u+ s6 D& }( a
! E7 R. ~- Q* X7 K4 [+ u6 Z$ g
; E( y5 t; C' w! g接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。* k. L5 G3 m/ `- d
4 q0 ~) S" } S/ B' i' b2 G v" r; o$ l l; u' `
& w; v \3 z6 j2 `电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。& w0 t/ C, a7 s" n
1 l% l: `* W, R$ @: |. f
实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。
. c6 U5 n* K: x5 q: U1 R3 f7 s; K" `( O" k8 l8 W: m
这是电路图,有兴趣的朋友可以参考。' {' z9 @/ K9 {- Z& S* x
6 j8 |+ q; ?2 W4 j0 y+ t" N1 g3 \# h) d1 T$ t# ]1 q- R( p
我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!3 T! H9 d! t" \
编译器:Keil 51。, V( L: s2 ?1 E" n: I
% _, B/ r3 z0 c发射端:" z. h! r; i0 ^
- #include <INTRINS.H>3 E: a; n- E; P" _8 Q# S
- #include "STC15W.h"0 I8 S* W4 g- B: d4 E* I
9 Z1 p9 X8 Y) `" O+ _2 K* o- " r$ E0 z- o+ D( W) O
- /** n& }* M' _6 c5 X
- --------------------------- b8 {: x9 D0 g1 F6 o
- |1 (OUT) \__/ (INT1) 8|9 l0 U5 f8 l. ^6 v: f
- | |( O- H( [- n7 m
- |2 (VCC) (INT0) 7|; @# B: T. V: T2 d" }5 O2 }
- | STC15W204S | d& p* w6 [2 J, ?! i
- |3 (LED) (TXD) 6|5 l9 i' L- D+ o- A+ X
- | |# f L1 ]! }8 Y
- |4 (GND) (RXD) 5|
$ v3 v& r; b) D- ~/ C* Z - --------------------------
+ Q3 ^: |, M+ C - . j) C4 e! l9 d& T% M( d
- LED ---|<|---[===]--- VCC- q- Y2 n) K# W/ p( H
- Red 330
8 w9 v# _* A8 b6 z! A - 2 j4 _, P8 ^ o; C4 A+ [. ?$ O
- Fosc = 6MHz
8 }1 o% w/ K" f4 O" H8 d8 q - */# f1 }4 A3 I% ]7 p* J1 e+ A3 _5 [
8 R- C' ^+ k, w. v T
. h1 a" Z& @, h9 Q4 q( e- // 矩形波输出脚
9 X. |1 V3 G0 e9 p, N# s8 i @) ^7 n - sbit OUT = P5^4;
2 N4 V6 U) G% ] - // 低压指示灯引脚
6 ^* l8 S- C( [& V- O - sbit LED = P5^5;! U0 p3 @0 Y# X t$ S2 W9 ~
- ) z' \) N6 V h2 r6 _ W% y
- // 停机标志位/ c7 N! ^5 @8 K$ z% J% v* Z
- bit isReadyToHalt = 0;9 l/ Q3 D9 G( X1 s7 h
2 H, J: \$ D. v- // 矩形波次数累计& e. H# z6 g5 H- [ q- A8 n
- static volatile unsigned char count = 0;" \" ^& p6 h3 N0 V8 T, f& X
- 7 T- J% S; |$ q
2 O6 b( B: N! |8 X7 x" `7 B1 V- static void GPIO_Init(){
3 y1 G/ Q \- X7 ~5 c - // P3口设置为准双向,默认靠内部弱上拉输出高电平
+ z2 t9 W+ U4 k3 |7 E - P3M0 = 0;
4 N6 u! k ^* }9 p3 F - P3M1 = 0;# v3 \8 ^2 M p. M2 P3 M2 k6 h
- P3 = 0xFF;" S. F5 c; m. [
- // P5口设置为准双向,默认靠内部弱上拉输出高电平* o% V# W: N# v$ C" a
- P5M0 = 0;
) N' p+ R% n( m6 E9 C& @$ E - P5M1 = 0;) B+ P9 {& r/ [; w8 h' d
- P5 = 0xFF;
2 g5 F. ^3 e- v8 O Q - }
4 I3 v+ F9 y, c% @9 g
1 L, F+ I8 s/ p9 J5 ?# i* R- static void Timer0_Init(){
8 l3 }: S3 b" D& I! k" f# |% ~ - // 16毫秒@6.000MHz6 W! R! ?# x$ c% Y, q! M
- AUXR &= 0x7F; // 定时器时钟12T模式! f) g1 ^3 p! t; v! L2 T: x& l
- TMOD &= 0xF0; // 设置定时器模式
; M. }% t y. F* D: n s - TL0 = 0xC0; // 设置定时初值3 \1 l# H! A2 b5 F9 {2 J
- TH0 = 0xE0; // 设置定时初值9 }2 z/ m& h' J5 e% @1 c
- TF0 = 0; // 清除TF0标志& C, w9 G, t! E
- TR0 = 1; // 定时器0开始计时
0 b; T; s1 Z9 x6 v$ g1 i - }
' d9 _' ?5 u& R - & _; m3 i9 J9 Y' g3 S6 y+ l9 c
- static void Interrupt_Init(){
v' i) T0 F2 q5 z - // INT0中断触发类型为下降沿触发1 V$ M8 A3 y7 x% ]
- IT0 = 1;/ }& B# ~. z e& T
- // 允许INT0中断
8 b* m# o" A& ]# {; L0 B' Z8 G6 h - EX0 = 1;; S- N8 g0 ?: j5 S
- // INT1中断触发类型为下降沿触发1 Y( O7 u1 M1 p7 O2 [4 U9 Z
- IT1 = 1;
v, |2 N. R% s% a4 r% j. o - // 允许INT1中断
! @+ [0 n% c/ Y3 V' M q - EX1 = 1;
4 k/ B2 P; ]- b0 ^3 b" ? - // 允许定时器0中断
4 ~3 E% e& ?+ y2 Y) n- u - ET0 = 1;9 P2 R% P8 j2 `! Y. [- |
- // 允许全局中断0 x q, W2 A% S( ~- k) h5 v
- EA = 1;
6 K; [1 T8 d1 L; \. R( H - } O7 D7 C& k) I
8 _0 V2 r% V6 J) v% r- static void INT0_Isr() interrupt 0{
/ r+ \+ I" X7 X* w5 z/ n - // 矩形波次数累计归零
+ t4 }# D' q0 \' |6 |7 ] - count = 0;
7 |2 W, X" e% V - // 重开定时器0中断4 c) A7 D3 G* R. f, R: x" R$ n4 j
- ET0 = 1;1 J: u! d1 [ n/ H' k
- }
, a$ g, y! t# u6 M5 k. Y0 L
+ U6 x' [4 ]! ?1 R- static void Timer0_Isr() interrupt 1{) u/ q3 N" T0 H
- // 输出矩形波
2 R) |: ]: C& |. H0 ] - OUT = ~OUT;7 L! D" F$ z$ J
- // 累计矩形波次数
' V0 {$ l2 o. i- I% } - count++;
( m: w. R% @8 v# e4 M& d/ j - // 超过250个矩形波,准备停机
' m; o% k: g* X: R/ m( y - if(count > 250){: h* _& O" _& F3 ~9 d! a" z
- count = 0;
; [4 C4 ^: w3 R. T - isReadyToHalt = 1;4 F& X2 ~8 B3 ^ }! W: A0 t0 j/ n
- }
) U8 w- a, L9 U1 H - }6 \& [+ \* N* b; Z; y! G. e
- 2 z9 v3 d$ q" Y( f" e8 E
- static void INT1_Isr() interrupt 2{* a) Z8 B3 U4 @9 \1 t- ]& e
- // 矩形波次数累计归零6 f( T; R9 |2 B& x3 @
- count = 0;
e8 Q# E- K$ R0 r3 f6 a0 t }* G& ? - // 重开定时器0中断$ Y0 l' \5 x. j* Y+ u
- ET0 = 1;
; }- U7 o. X% L6 @ - }
5 _8 j+ W* q5 p" ]( m/ p9 W
; T" H: Q# A8 l: w& l
, `7 j& y; o( i% {2 u- void main(){2 i8 k/ @. v" S$ |7 o
- // 初始化P3和P5口
8 t# K. M) a' b+ |3 L! r6 S - GPIO_Init();
& K8 `1 }4 m0 {2 _ - // 初始化定时器0' Q$ P8 b) M3 F$ B% _; J7 }! Y
- Timer0_Init();
" ?+ A) s+ T' j1 v8 N% L, L* f4 y - // 初始化中断
9 {# e+ O. \5 S; l% y, h4 a - Interrupt_Init();' Q0 n+ ?; l# y" Z
- ( C3 L8 B; X- B
- while(1){- k0 p' | n. d* I
- // 检测电量; o& R' v* q. @7 I; V6 \5 D8 L
- if(PCON & (1 << 5)){6 N# V+ w3 V6 B- F! p- F% ~
- // 如果电量低,低压指示灯亮% ~# y2 W5 _ f, K% b
- LED = 0;4 n' H' `# \( w. s( u8 Q/ G
- // 清电量检测硬件标志位
7 E/ n7 [7 `& L9 `$ r - PCON &= ~(1 << 5);
# M J. J" _9 [" K' a - }else{% G Y' f, c- F9 E+ q& U5 n
- // 电量正常,低压指示灯灭
6 u# \' |& J4 D$ q4 r% F) y - LED = 1;! F* N* B+ c$ K' X1 F
- }
1 r3 D! k l$ x0 a: }5 z9 ^* M - 4 w. g5 x2 G6 s7 D8 B1 T+ v* y
- // 检测到停机标志" F- A# [' C6 ?) ~, m
- if(isReadyToHalt == 1){; Q4 Q: l) U9 \& h& _' l7 J
- // 暂时关闭定时器0中断2 W- n0 j; L' T% }3 a0 s# e
- ET0 = 0;
* F. T# ]3 R% `, e7 E# K* k - // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU+ u" q- a7 H! @+ z
- OUT = 0;3 ~% d& E' |7 i
- // 停机之前先把低压指示灯灭掉,以省电
( I, Q) l$ c) t' w - LED = 1;
) {0 T8 C) r- z) \; f - // 进入停机模式
6 M1 K, P( f0 o* y" H - PCON |= 0x02;7 D; w, K! \7 P7 s3 Z. A
- _nop_();
4 D5 f; H; {0 C! ` - _nop_();
! {$ ?) L9 @) j$ O - _nop_();
! l! p1 ], I6 ~8 Z, @5 ^3 m) N! k - _nop_();. G9 s" n. |# _+ e* e& r
- // 唤醒,清标志位
% q2 v; {5 R6 H" C; p6 e - isReadyToHalt = 0;
% {: k/ Z8 z2 k. t - // 重开定时器0中断2 M6 X+ q. r6 C! z3 D5 n1 j
- ET0 = 1;
" S: q# P+ f' }* ^ - }( P( d$ ~- [5 }# T+ M
- }
: U$ j& ~! M5 z! E! ]% t4 n0 c - }
- R/ k) H% s i7 q( W7 w4 C
复制代码
4 p7 q& x5 b% j5 ~$ _硬件参数配置:
$ W$ c1 D+ n0 V* k7 r. ?- t% X& W+ a- w( o* u& _' H @: M* d
7 j8 U" B8 h1 L- d% e
接收端:: \ E7 }8 o7 v) n# H
- #include <INTRINS.H>
. m: s) J. A [% r% G W - #include "STC15W.h"5 T# o9 G" X0 a/ w
- ; J0 J9 z9 J# `7 K
( T" }) b1 s7 s4 _ a5 x- /*4 { A3 G) V6 h; f2 u, v+ g
0 T8 Z; J7 o- |& O4 H$ @- *---------------------------*
5 l. u, U' ^& Q6 l: r8 d$ Z( R" ^) z - |1 (GPIO2) \__/ (GPIO1)16|4 Y i1 h: W" ?* c
- | |
+ k, _% U( W- Z - |2 (GPIO3) (GPIO0)15|3 M/ f' q6 B& `. v
- | |
# d6 W( \2 T$ D8 P8 }* L) x, _2 M - |3 (GPIO4) 14|+ z' K7 i$ A$ A- k( I, B# A
- | |! t, Q2 a K5 l4 q; w3 h- u
- |4 (GPIO5) (DATA)13|6 l- R( ~5 [0 J" e J* w! B
- | STC15W204S |2 i7 ~ s: l! s( R
- |5 (GPIO6) (LATCH)12|
/ p" }4 U, w, M% ?) ?; x - | |
& e& E; Z9 I6 V& p) e - |6 (VCC) (CLK)11|
2 Q- r' J* }, F- n7 l7 C$ c - | |8 F- V1 X9 H# W9 M& [4 T+ S, y
- |7 (GPIO7) (TXD)10|: k! {0 q9 t, n4 Y2 w! D
- | |1 V5 K R. L! U( Z
- |8 (GND) (RXD) 9|
% Y. i9 @5 l: F! D- H+ i: d; W - *---------------------------*; N5 H) f/ m2 I. s4 M
- Fosc = 12MHz
' s; n, l1 z1 X2 _. N) }6 {
5 k1 Q; X* g8 J5 V- P1.0 -> 上5 l( P N8 o9 F; M8 L9 Q
- P1.1 -> 左
" U8 ?1 p1 V8 M, P& ] - P1.2 -> 下
# f- P l! k! l9 E( t) P2 T - P1.3 -> 右9 m7 s# ~7 I- S, v0 f( F
- P1.4 -> SEL' e& ~0 @. L2 H- ?( u a6 Z
- P1.5 -> STA. ?2 M8 G7 d$ Y+ Y; X; @, E
- P5.4 -> B
4 U3 `$ h' l+ I2 k( A - P5.5 -> A' h8 D. Y" y4 X5 l/ R8 _1 c
7 L2 J3 [5 e% A6 T) z) ^7 P- */
8 K: {7 b R0 `) ]% e) } - * B: e) }- w8 [+ x" i' k( h
- & a$ x7 Y$ I# b x5 B: z
- sbit CLK = P3^2;
6 H' q. |% _0 z4 ~( p; v/ j+ A - sbit LATCH = P3^3;
9 @9 W1 O/ b. Y6 H7 y$ g( I4 s - sbit DATA = P3^6;
- Q' X8 x3 r3 x1 B' _ - " E1 E5 [! ~1 ~- i4 \1 U/ V+ V
- bit isReady = 0;* a- p W, O2 X* @
- static unsigned char key = 0;! k5 q+ L, J3 ~. `
- static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值6 u0 w) n5 Z. B- h
- static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份
9 o' d' W# Z8 e& ] - static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中
5 ~' f. J- T4 R, D - static unsigned char idx = 0;
+ u. |0 p5 H0 D7 n8 l" s
& i+ ^( y* O* w
5 w% @/ ?" k+ O- T, E. P6 k: _- static void GPIO_Init(){
0 J+ v2 d& ]; J+ M/ J& q - // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平
8 ?0 K3 f" I: q8 f2 ~ - P1M0 = 0;) I/ d. l/ [( R
- P1M1 = 0;; L" j0 c8 A" i/ |" x& H
- P1= 0xFF;
& P/ T& Q) K4 o2 h$ y - P5M0 = 0;
! _+ N2 d3 s* @/ n% p5 [ - P5M1 = 0;
/ f2 ]& X) ~" Q) B) O6 u; L - P5= 0xFF;
8 t/ D7 r/ a% e L2 ]" S) t - // P3口初始化为准双向
% H8 v8 p9 S, Z. W+ _5 Y* f) s - P3M0 = 0;
# @) Q' y" B& ? - P3M1 = 0;/ P! r' {8 S1 y! _
- P3 = 0xFF;' k5 y' g0 ]/ M- k/ B4 d
- }+ `" ?1 J4 S& `" V6 E
G* @& n6 ~: |" E6 [- static void Interrupt_Init(){
0 @0 w1 `+ a: G - // INT0中断(CLK)触发类型为上升沿+下降沿
3 K: I8 f2 \; Q' c1 _3 M) | - IT0 = 0;: S+ j/ C& O* @# q6 q% R* _
- // 允许INT0中断
0 N* y" e/ X; T. G2 L - EX0 = 1;, g2 Q' b- j( e! K; l5 j2 Q
- // INT1中断(LATCH)触发类型为上升沿+下降沿
/ D, J3 h- T' H9 E% G- v p9 ^ - IT1 = 0;0 r P/ x) A2 g; ~/ q! x8 @
- // 允许INT1中断
% n! h& u/ s8 H* x - EX1 = 1;
) k- l" j7 r! b" q - // 开启全局中断
# o# c6 R: P$ _( b8 s0 q# y - EA = 1;
4 m( e7 p" [) B9 h2 C - }
7 l; S' M( M/ _+ T, B0 S
9 y( r- o5 F; @. { S: P- static void INT0_Isr() interrupt 0{; A5 u2 l: x) c9 k6 K
- // 只有已经成功锁存才允许CLK移位+ R9 m7 M, c" N; u8 b" g
- if(isReady == 0)
' P- w& E1 y+ s W# g! T - return;
) \) }6 D9 ^6 v3 `# ^4 _ - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断' ]1 ]( L e" _
- if(CLK == 0)
9 _& S7 w1 K, k& w - return;
6 e! Z+ z" i, g: { - // CLK上升沿到来的时候,取锁存值的下一位输出6 l% s1 p; G" `6 T7 R$ T% z0 i4 e
- idx++;
' L2 T) V& q; R- u2 v - DATA = key & mask[idx];! m: w. e' q8 C5 a0 r
- // 如果已经完成7次移位,则一轮读取完成
e' R# x) X* ^. B2 E4 o' W - if(idx >= 7)
& J1 Q# ]# t C - isReady = 0;2 N0 I5 ]8 q" K
- }" F0 R3 w P- ~4 ]# L
! z# f0 h7 v( z, M+ {! z- static void INT1_Isr() interrupt 2{
- j8 X" c7 c# v- A: l: W! X - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
' [9 o( ]5 M- M, g - if(LATCH == 0)
5 |" @% I/ {+ ]5 A - return;
% T. \8 n" Y9 c' i( | - // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA9 U4 Q/ U" T: j8 G9 c
- key = bufReady;
7 W+ J5 v! V. c/ X2 G8 w5 g - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表+ P I/ o- _/ T
- idx = 0;; L: Y5 q2 e" b# e
- // 允许CLK进行移位
! s8 d$ W% a3 K0 E- z. V1 U) {- P - isReady = 1;+ R% y' J0 E1 _( V
- }
5 u8 P0 \% H, Y
0 E2 ^1 M* F1 m4 W5 C$ r- void main(){
8 k! }5 I0 A6 N' ^* T - GPIO_Init();
6 e: T* \. W$ l2 A6 ~# o# o, C - Interrupt_Init();" J4 L$ D0 x9 R2 N D
- $ w! K4 u3 h( g4 K. v: R. v" r8 x3 p
- while(1){
4 i2 L3 t, H. n5 `( f) g - //PCON |= 0x01; // 进入省电模式2 {* E F: u% o, D- F
- //_nop_();
* r0 `8 k* B( {8 [& ] - //_nop_(); N8 C) T5 \/ D- U9 a4 m
- //_nop_();5 d8 u9 ]/ f$ L s K# V/ R
- //_nop_();
' i: \1 m8 D% e1 V. [+ N - buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));: P0 N, J% M k, W z2 {% |' W1 S H% \ g0 h
- bufReady = buf;: t/ G) q- @0 _# R, _0 `* T
- }3 n0 s4 `" _+ k1 ?
- }$ L5 C z- O+ K0 e( O9 v9 ^
复制代码
1 v) ~' N7 }% R硬件参数配置无特殊要求,晶振频率选择12M即可。3 A6 w0 m' H$ R" J0 W$ t5 n
7 m/ v, ?' ?: k/ P9 o这是编译好的固件。 |