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