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