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