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