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