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