本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑 6 b6 x6 @: j* G2 }9 T) ^1 S* T+ j
6 t0 H2 |1 U, l2 g1 E; R( ^没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。
( s Q$ f, {, l% L$ U4 \
8 W! @+ _2 W' K( d! \6 ~- n( ^# {9 C, P1 d1 h, p5 I! D
发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。
G3 [, L q' _! e( N& y+ b
9 k9 P8 F9 L( H% _买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。5 |: C3 }) A' I7 N/ o) H
为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?0 Y7 C& E2 ~+ P( f% _- S
然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。
9 y) ?, W2 M5 k6 W- t3 V/ V1 n+ F+ x8 ^
/ R, E- _- N, y$ q; N! ~0 |" Z. G
3 A, i' T+ m! h8 g0 e! ^6 z最后把按键、导电橡胶和电路板装上去。5 V# e' k& m4 L& L
精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。
7 k& b$ f; J! A( {: h) b9 H/ S6 O6 D5 _6 M
3 y4 [# h6 d. p完工!
# p2 D% i S8 p2 D6 G- _; x' X: Z5 p& o' g
* w# p+ v# M; `这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。
4 g8 h4 K+ j' L4 u9 P1 I5 r* C2 G9 p
6 Z) S% B X, V
接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。# d: v3 d& O, _- c/ Y7 j
- S' m+ R7 Y* g+ d9 J) H$ \1 D
( q# S1 i0 o8 \7 H% Q) D; g1 K5 g. f/ E+ f
电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。
9 o/ r& S6 E+ O( L
: `+ j& y$ v3 z. W5 F, s- N实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。2 F/ o! N$ T- O1 U% Q. R3 L
, H2 U r$ W6 I! B; ~
这是电路图,有兴趣的朋友可以参考。9 @3 j( l& \* |
, j7 B: j+ u$ [( z6 T0 ]2 a
3 r( u9 c+ v9 e& n; K
我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!0 E* h, y$ r; x
编译器:Keil 51。
& x9 n8 o, v% J. v- t/ R7 T {$ K
; A5 w& l' o. C9 T发射端:# C+ h+ i0 _7 M- U
- #include <INTRINS.H>
7 e' f5 S8 E0 K; Q% p$ U1 T7 q - #include "STC15W.h"
% h/ ?! N) A/ |* O
1 @* }% z" ~; K
2 B! C) o, f& ~5 w9 N- /*# A$ F: x# u2 {# H7 r
- --------------------------
) ]0 D. \/ y0 }9 C0 i: I5 H$ Q - |1 (OUT) \__/ (INT1) 8|
! U v* K# i2 S3 M* s( r- J; \ - | |0 W8 `% U; R! h) c
- |2 (VCC) (INT0) 7| W7 J( v+ i; M8 M3 h' O" |8 t
- | STC15W204S |( q0 S! {8 L) R5 o$ P
- |3 (LED) (TXD) 6|; G: G1 B& i' M" @3 O# k
- | |
s" J" n& p6 k3 g; o - |4 (GND) (RXD) 5|
) H4 M& u0 g6 d5 w' m - --------------------------
- ~" t3 R# ]7 S5 m! q" K7 B7 s# ]
5 K; E0 D" l* Y1 [! I4 O. l _- LED ---|<|---[===]--- VCC
+ x4 f$ X) I" m6 I& v - Red 330& o2 I* x% L, N$ f
- : h7 o% C) d0 y7 A
- Fosc = 6MHz9 ?& a- t2 J' a+ R% n$ ]3 g+ K
- */: A8 x, |. R Z" S4 | n
- 2 }/ }- W. D8 S
. @+ y1 N) X( C- // 矩形波输出脚5 h; B+ |& z7 p, `4 D" A* z" @8 T
- sbit OUT = P5^4;5 m% \. b+ n! Y. H' ^8 O. o
- // 低压指示灯引脚
& ` l- D6 j e; ^( H5 ~ - sbit LED = P5^5;
: B" a: j# E6 r! E' G% `6 |
( N8 L( H1 X3 H9 W# O- // 停机标志位
" S7 g; j7 h" G0 ^# h& @9 _ - bit isReadyToHalt = 0;
* b* Z0 I, G% N, V- h0 n _ - ! G5 I% R! b7 H' ?% U! [/ s; ]& s: E
- // 矩形波次数累计
1 q6 o6 N) a- H$ r/ E; c8 B V - static volatile unsigned char count = 0;+ M, B) D8 ^4 g- ?
- ( U. l2 c; k, n) s& {
+ u; X o( X7 [4 t' M5 V) M- static void GPIO_Init(){' I- d' H w7 y
- // P3口设置为准双向,默认靠内部弱上拉输出高电平1 z9 Z: C5 U/ w5 {( ^8 X8 D: ^
- P3M0 = 0;* k$ W$ V5 D/ |/ w0 |# W
- P3M1 = 0;
3 t& ~; A& f. ?8 V! e - P3 = 0xFF;
3 b' A, L% S! s/ G6 c W8 _ - // P5口设置为准双向,默认靠内部弱上拉输出高电平
7 u% k( M$ x, F" u - P5M0 = 0;
0 I# [4 u9 ?6 Q) t4 |* B, I+ j - P5M1 = 0;& N) L4 g' n8 O3 l
- P5 = 0xFF;
) B# D7 r2 T/ i+ ]$ H- H - }) W3 n6 }7 M k7 `, U
0 Q5 f* K6 g I* d6 i- static void Timer0_Init(){; B0 V/ G+ [: N T
- // 16毫秒@6.000MHz
+ B6 [# N8 P4 N - AUXR &= 0x7F; // 定时器时钟12T模式
8 h: `3 Y) O) a! M - TMOD &= 0xF0; // 设置定时器模式
2 g5 e' Y* T, o1 L- ?8 B) { - TL0 = 0xC0; // 设置定时初值/ y& I, A8 @: G1 z+ Z, m2 r2 x* L
- TH0 = 0xE0; // 设置定时初值 J7 l) z5 D- u( S
- TF0 = 0; // 清除TF0标志! S- i' M% ?/ u" F
- TR0 = 1; // 定时器0开始计时# M; O, J- u. {
- }( \( a% f3 {, E% Q" C) a5 n
( d# w5 I' Z7 A' Z, h5 {0 c: q" {- static void Interrupt_Init(){
: ~0 x6 J( p- ^3 k9 W% x) t - // INT0中断触发类型为下降沿触发9 z: d9 T) \, N0 o* m( e! e, s
- IT0 = 1;# Y8 l, S# f: t% h
- // 允许INT0中断
/ X6 b) p% o4 N6 W8 L- `9 E- x - EX0 = 1;
+ T/ b1 \/ ?7 o9 ]8 J o - // INT1中断触发类型为下降沿触发
* X. H# T2 @, _% c6 X - IT1 = 1;8 f ~5 h: Q: y+ u
- // 允许INT1中断! }+ W, `/ T8 `
- EX1 = 1;
" ~2 _0 S. F+ F: `; z% H+ \4 m - // 允许定时器0中断
2 E( M4 I* [9 r+ c3 h) z# o) ?# B - ET0 = 1;
) k# Z, E) F5 I& Q: d ?' w - // 允许全局中断: K* Q5 q- @3 U- q
- EA = 1;: c- q( ]3 }6 R
- }
3 |7 ~/ l* \" T2 M. \2 C
, K* X* j. d9 V2 M( D4 x- static void INT0_Isr() interrupt 0{
. ^) s7 e4 p- H& E - // 矩形波次数累计归零
7 c$ Y8 m0 ^' _5 D - count = 0;$ h/ c) ~6 U, n8 G: q+ @5 ]
- // 重开定时器0中断# z/ z( H5 C8 k6 w
- ET0 = 1;
7 @! ?2 I6 [& E - }' B0 d7 s1 s, e$ {# a+ @7 d( ~4 ~
- * j4 y+ p, b: [) ~8 ^* I9 Y
- static void Timer0_Isr() interrupt 1{8 T2 B: z+ m) E4 \
- // 输出矩形波
$ w- l% y: [3 j6 |6 s H - OUT = ~OUT;" U; T% V' C/ L& b3 {8 A
- // 累计矩形波次数
. j2 B5 O5 e0 U - count++;
! G7 V ?1 {- U# {+ X3 T5 @/ G# R - // 超过250个矩形波,准备停机
- J+ `* p7 o4 i - if(count > 250){
' d4 |( E7 N8 e - count = 0;, v6 V: [+ A4 A' ]8 w/ \) J1 A
- isReadyToHalt = 1;
k9 ?3 j8 K" b* O2 U; \7 r - }1 L0 x3 J4 g, t
- }5 p' j4 {" [. G8 ~' Y
& X, }: ?6 _' @6 Q$ a' _6 j) i/ G; X; M0 P- static void INT1_Isr() interrupt 2{
# Q- R, ~' _* t: [5 T3 a - // 矩形波次数累计归零
/ L* F) Z) y# O9 f" M1 B2 e - count = 0;! d+ D/ U$ ]9 t+ {! [
- // 重开定时器0中断
& [+ {2 Y4 m: O, E - ET0 = 1;0 q& w" }6 R3 n3 ?, x
- }. a9 r" T% q$ e5 S$ r; J5 _. X
- ; }5 |" D# m# r1 M* M
- 7 h* G! F9 \: t5 w4 U% \0 x
- void main(){, ?8 O4 b' j$ Q+ [& m
- // 初始化P3和P5口. x1 f' e# Z1 t3 Z
- GPIO_Init();
, R8 l6 I# J& z. o2 f" ^% V- A5 F ^ - // 初始化定时器0 [5 H9 q/ h# \9 _0 g2 e+ p% K
- Timer0_Init();) v7 ^1 B* U" \- s+ @
- // 初始化中断$ \3 l! m! r) R9 i9 n y I( N$ z
- Interrupt_Init();5 S" t9 T8 M( O
-
/ C3 P" [% R) \2 g! j6 s - while(1){
1 M: ?9 Z8 l" D x - // 检测电量
F9 y! J Y: C! ]( U; r$ b' i! A - if(PCON & (1 << 5)){
' I" {$ w% Y0 n+ A - // 如果电量低,低压指示灯亮
* M7 k) q, i) I5 F! ]5 x3 D! h - LED = 0;3 R; m. n7 s9 H( @+ l$ b
- // 清电量检测硬件标志位
$ c9 f- d, `! S* P0 b1 b - PCON &= ~(1 << 5);1 }8 `# f4 b Z R
- }else{1 x' {: E0 d. {* o+ ?. d7 s
- // 电量正常,低压指示灯灭
+ Z2 d! [& m3 J. |1 h2 A( W5 ] - LED = 1;* k& ?$ V1 l% V" x
- }$ v' ~8 N9 f5 ?+ S
- 1 _1 V" z: c: S% t* h( ?
- // 检测到停机标志
% F6 o& M- V: ] f: @, I - if(isReadyToHalt == 1){: ?9 J# Y& M5 [
- // 暂时关闭定时器0中断
2 m O/ h8 h: k2 @; s* {: ^ - ET0 = 0;$ ~4 @2 R$ u) l: I/ p, [
- // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU% k# d; R8 O, ^" `+ k/ o( s
- OUT = 0;- i# K! O- w, K# [) `5 E
- // 停机之前先把低压指示灯灭掉,以省电0 F A+ E# r+ [0 n# N7 ~, Q, m
- LED = 1;
* J( v" x3 m' _) c. g! c - // 进入停机模式
( L ]6 q+ g& q& ]: _" E+ ^2 K - PCON |= 0x02;
8 g8 e. C0 [) A3 i& z# }& b - _nop_();
: W5 |3 G: V5 `0 X) _, V - _nop_();% o1 L+ {) `1 D) \
- _nop_();
7 C; t' Q! I& p% z$ P$ Z/ ]" t( d - _nop_();) ?+ [3 J4 s8 K; _
- // 唤醒,清标志位
1 p% C; w3 t. \8 \$ F7 @, H. z, F - isReadyToHalt = 0;
: o/ C( n. t+ A$ R2 H9 |* R. ? - // 重开定时器0中断
$ [& e; y. L' @3 B8 } - ET0 = 1;
1 z* n- P4 R+ E- k - }# U3 A% l, F9 M) f$ b7 U/ M% S
- }+ V# b( I, h, H7 [
- }
+ [. Y8 W. R. p/ Z
复制代码
# c2 r" ?. p2 v$ c) o6 @硬件参数配置:
3 G* S& E8 ?7 I
* Y7 v" j7 I0 _$ K7 n' p$ [2 t& F
接收端:/ S! Z' w4 g6 k: K# |! L
- #include <INTRINS.H>
9 w3 e( ~* y7 w - #include "STC15W.h"& C1 W7 W' Z3 t2 j, H. _, J7 `% V
+ b* P$ }$ i) ]1 m2 u; l
! d. u+ D8 k5 H' R0 A- /*
" M5 n& K! f! |! @
2 @$ K! c( O3 n* ?$ @* N- *---------------------------*
2 N# @% q0 r: S% Z - |1 (GPIO2) \__/ (GPIO1)16|
, f" W# O4 A! H) I. n3 E0 H( ` - | |, X# }+ k% k+ n8 _- B! m
- |2 (GPIO3) (GPIO0)15|
% l- e: c5 B5 Z) }4 T - | |
" V8 E0 C: b I4 X( k - |3 (GPIO4) 14|
r" E9 A& p3 J% m8 N, C - | |
6 t ]* Y9 a/ ~# ]9 k' o* i - |4 (GPIO5) (DATA)13|7 H: q7 R: H8 t, e
- | STC15W204S |
$ e& {# G9 v6 U2 G' d4 K+ @ - |5 (GPIO6) (LATCH)12|3 s7 ?. x& {( F3 J! C
- | |
7 [; ]3 G" N7 _! s. _; R - |6 (VCC) (CLK)11|
' Y6 t6 a. Y* r, x; c; G - | |
* X, ~1 m% h+ h5 J# v - |7 (GPIO7) (TXD)10|, N7 g2 `' m c0 T. N
- | |
% |: B) \& c8 _+ E- o - |8 (GND) (RXD) 9|- q& ?7 X u# V4 o
- *---------------------------*- @( K3 @* U* i: R
- Fosc = 12MHz- x: }& ]1 [7 ]; Y& ]3 r$ _
- 9 P5 t1 R- M" T8 |, F
- P1.0 -> 上
0 z' C9 N- m1 Z3 n; _ - P1.1 -> 左
6 ~' i/ L* a9 R4 v e1 ~; p2 v" r+ K - P1.2 -> 下
- ?6 S7 H0 d0 L: f, S* d - P1.3 -> 右
: Q0 v" d% R J R4 q/ P# R6 I - P1.4 -> SEL. b/ f% H0 t3 @( p! c
- P1.5 -> STA1 G8 o& j+ }5 Y8 x
- P5.4 -> B
/ E$ ^3 \& Q5 S - P5.5 -> A1 l9 e# S8 [7 w7 O. ?) ^
- * q6 q# x& v O: O: g& `7 n' Q
- */# m& r) i' ^- E) l
- % e4 @( p% |3 V4 w% i. m9 v( o
7 D9 H2 M% J! K& D! w- i( p- sbit CLK = P3^2;3 Q% T- c+ o" Q M+ H1 |
- sbit LATCH = P3^3;
- \2 P* R6 I# P- p( _) [ - sbit DATA = P3^6;
- L3 B) M/ y: ]* @
0 G4 }" v' H9 Y, ^2 W- bit isReady = 0;0 a3 w! j7 n) f* i( @+ Z
- static unsigned char key = 0;
8 w b% X4 }6 |* q( U" v/ s - static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值
9 Z% B8 o6 q3 D - static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份# ?* P8 f8 T% J4 f
- static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中- t: F% f- s$ F8 v& }# G6 r M
- static unsigned char idx = 0;
3 z* I3 N; l$ s3 c - $ x3 N, B* T8 d3 @( T* \. Y5 G3 Y
2 I$ Q6 j$ W' y p _; A+ q& O" T# A- static void GPIO_Init(){
7 q9 I+ u! p# t2 ^+ p - // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平
' _& |3 p S& x) B8 g( ~0 n - P1M0 = 0;
4 ^* a5 m, T1 w; r- s- a - P1M1 = 0;6 P) M2 Z" ^; P N8 @& N
- P1= 0xFF;
0 Q3 u- m2 J5 Z. N$ B$ w' F! \7 u - P5M0 = 0;/ r6 O9 S; R4 m8 Z4 X9 }$ \% N
- P5M1 = 0;
# o7 \4 G$ J/ T* Y" F ` - P5= 0xFF;3 n% x3 r' G* L! m& J, N
- // P3口初始化为准双向) t2 B8 {: x+ r" T) Z
- P3M0 = 0;% R7 b) J, k$ z: H
- P3M1 = 0;1 |' T9 \% I! p! W0 M
- P3 = 0xFF;
- Y% t. K; o8 M) q8 F( C! S - }# `/ }5 S! u4 ^+ `& z: f3 P: b
$ V9 R4 U5 k+ d4 ^' J+ s- static void Interrupt_Init(){/ y4 P. d1 b- X. X" k% R
- // INT0中断(CLK)触发类型为上升沿+下降沿3 Y0 L6 q1 K9 C! n
- IT0 = 0;
7 i2 `/ T' |# ~7 m - // 允许INT0中断
$ b7 E X. O! }* o5 H9 z6 n9 r - EX0 = 1;& S a3 m, l& h2 r% h% X2 r' O
- // INT1中断(LATCH)触发类型为上升沿+下降沿" |) V) H; R) d! X$ O- H
- IT1 = 0;7 c) {- F, P- G4 [1 v/ m4 e( ?$ E5 s
- // 允许INT1中断
' `% a5 n+ a e6 t5 w, u - EX1 = 1;
5 c1 F9 F4 H+ R6 ]8 | - // 开启全局中断
4 y& d- U1 l5 X# a0 D0 T0 [2 v - EA = 1;" F0 \9 u: l* k5 h. Y v4 }
- }# d) ~: I; _% n/ p2 X8 ?. F
- I: Q5 d9 q0 I; w* V5 [1 P ]. Y5 b- static void INT0_Isr() interrupt 0{
- A+ E" s$ s8 ^8 [, Z/ S' W - // 只有已经成功锁存才允许CLK移位
& X) y1 \+ J' ?* A) i - if(isReady == 0)
$ K$ t6 s; u1 Z; g: u - return;
8 D/ {4 T" ~3 [% Q! X - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断" w1 Z- ?. S8 J3 l- }8 N* k, U
- if(CLK == 0)/ _+ o; k. H6 q) V. Q
- return;
: T! @% a9 o# l- F/ ]6 v. d% }; X - // CLK上升沿到来的时候,取锁存值的下一位输出
4 _) j$ P8 e t3 | - idx++;
9 b& [' G3 X; A" C6 y/ V* u9 v - DATA = key & mask[idx];
4 j; l6 R- o$ w* | - // 如果已经完成7次移位,则一轮读取完成
1 B( c3 i1 m. V - if(idx >= 7), ?7 c8 e: w# c: ^
- isReady = 0;
# x- C/ t+ ~' b5 B) G K6 l% C - }; @ c) J( g# Z+ k
1 I6 [3 n. E, z: g* s- static void INT1_Isr() interrupt 2{' O% S. r3 V" @- Z# S
- // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
! C1 q0 @5 ]1 e7 F9 T |2 u - if(LATCH == 0)
* a' Y/ d* x6 g( q+ x - return;5 F4 ]- m$ S* ] [; D4 t
- // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA
, f! v+ D# w0 ~4 _; J% O' b - key = bufReady;
( A. h4 z3 F: ? - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表
! m0 m/ U% c6 o8 p I9 F - idx = 0;
7 V4 E5 m- g1 y& O - // 允许CLK进行移位; P8 U* F6 E; J7 P+ l E
- isReady = 1;
; M/ t; l* `; I' E - }5 M$ r! K- g3 k$ j* O
- 0 Z* o3 a- r/ S3 y4 s
- void main(){
2 b2 R, B# y0 U' y; E5 L6 | - GPIO_Init();
' Z! F# R" } ]2 Y/ j - Interrupt_Init();+ m: V& X8 O0 @, Y
- + }# w7 j6 Z4 B) I. D/ `8 Z
- while(1){
, L1 q" y1 I2 n, v0 s - //PCON |= 0x01; // 进入省电模式' m/ A- R# F1 q" X, I! X, P
- //_nop_();) Y1 u7 F* ^* W3 o7 O
- //_nop_();
# O0 j7 W) t5 H$ A) v5 r3 \ - //_nop_();5 n! j {0 O( g; A2 B$ Z% \6 m
- //_nop_();( o! q' U$ x; n4 Z' X8 h% }- s
- buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));
+ t y; d4 d4 o - bufReady = buf;, _2 Z, R4 ~2 r$ k: T8 ~% J
- }2 r! Y+ H/ L9 z5 m5 h, w2 T
- }
# g. r# U' p' p, T
复制代码 ) r2 V. q& Z. f: H. v9 T. R' c' u7 X% a
硬件参数配置无特殊要求,晶振频率选择12M即可。- c. r1 C# J) K7 C
# g7 f( O! ^: @; p; [* O# a
这是编译好的固件。 |