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