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