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