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