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