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