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