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