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