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