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