本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑
# J! }4 s: ^- z* Y- \& \7 Q2 G. @, e' W* k, h
没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。
, E3 d/ |. S/ l+ b4 i3 B$ m( b# b' d. o |' t3 N0 l2 E3 r! D& o
1 k: m- Q8 U) L1 y4 ?0 q+ g
发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。
% j; G7 g; R" \9 L+ R1 b7 K! \) V* F2 Q. o: ~! _! D0 B
买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。 m& Z) q5 A: L8 `- q/ _ S$ i) B
为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?
! Z1 G! m3 d% g1 O" w z然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。
. @5 j! Q1 E J; [ d
% f! E- q; M K0 Y- e0 G& y e! \5 l+ N7 q$ M3 B' s
2 c/ s# S; H6 c$ R% f* j, N: F最后把按键、导电橡胶和电路板装上去。
2 x9 P9 E. N3 M精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。' a t( P( T+ ^8 C* ?" q
$ i1 t: H/ k P( m0 U
( j( \( X8 Z, M1 t5 N- q完工!$ }6 k9 G y+ R
7 a$ s6 E4 O# Q1 ~- {
C B; `% J! e* X% D
这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。
U& j3 J( x& R" b/ \0 K i# g, A* l# h9 O" k
3 @, r2 G9 T, A3 |" F5 T# c9 C
接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。
& m, o9 r' t9 j0 g0 z- v5 o+ S+ k
/ B, K; N4 w, i/ c8 h3 g9 m- f1 O
/ i: r/ \' _$ h& e2 ?
电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。& a$ l; n' o; c! [) y' c4 x
" s0 s7 J% c9 F% m0 s/ P z
实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。
$ }* k. ?. F2 o" |8 {
; a1 Z. I7 Z* P* e, t: j这是电路图,有兴趣的朋友可以参考。
# [2 @( f! {* U: K- _3 S; N3 J" w* F7 }" h% q5 y
' P, W) G: M- H' L2 X! e我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!1 c( `; U4 d# P) P( S
编译器:Keil 51。
1 d3 c7 Z- }" Q; l& l0 s. E3 g* m8 H1 n5 M) M- Q& r' {; M
发射端:
" ~# @5 [5 t0 n+ h) W2 q* W M- #include <INTRINS.H>
# I- ^) B T M3 F9 K, d - #include "STC15W.h". E% O$ x ^" F" ` _7 G' K
- - W% Z' r% b$ c! B( j" T* ]
% [+ f3 k" N, E P, t Z, o5 q- /*
) n0 t0 j6 y4 _+ }" v - --------------------------( {+ y7 T0 k) q7 h: r6 q1 }( {
- |1 (OUT) \__/ (INT1) 8|( j* T9 h9 z+ g; ~" v) C
- | |
! l) P8 r! B& I) n - |2 (VCC) (INT0) 7|* R" e! ~ X' N7 Z! L
- | STC15W204S |; Z) l4 [" Y8 i6 A% i! u. u
- |3 (LED) (TXD) 6|+ s$ N2 X* A, Z$ u1 V2 z' ?* B
- | |7 p* Q% m8 ^+ P7 f: g
- |4 (GND) (RXD) 5|
. C2 n9 w( e( Q5 r$ B2 I - --------------------------4 Y- z( p) v& n! P
- % F7 C$ [" e. X. r; T3 t5 F, Q
- LED ---|<|---[===]--- VCC4 e1 F8 T' E- U; O' K% p
- Red 330
( o, \5 Z% g) M% H* c
2 A( F' J1 M4 y9 X6 R- Fosc = 6MHz& ?( i; l L% m1 o# z
- */2 z2 b" d# V* p0 a: K
' F$ s2 b& a$ S! }
k: G2 }% B( N7 Y- // 矩形波输出脚5 _. K4 {$ ?* j' z
- sbit OUT = P5^4;
+ T# j3 `; }+ X, h/ Q1 E - // 低压指示灯引脚% \7 J1 w- ?9 j1 |
- sbit LED = P5^5;# `+ y" R) U6 `( x/ B. a, y
- & e/ a; [8 b' ^; l7 U# e3 g! E
- // 停机标志位4 u# J7 {0 `" l* L3 n4 }
- bit isReadyToHalt = 0;
; Z) d! a+ Y/ |, v; b
- J8 B- A U. m0 N: L0 u) b+ D- // 矩形波次数累计
1 u5 h8 A' B2 X4 R* h+ P9 I - static volatile unsigned char count = 0;
! C- |2 C, B: p) M; l5 Q. C - 2 D" B- W0 ]. Y# o' S
0 j! {! @- d+ @! e' {; I- static void GPIO_Init(){8 a. Z2 s$ Y) g- V3 D2 N
- // P3口设置为准双向,默认靠内部弱上拉输出高电平
t. L$ k6 R. e9 g5 C - P3M0 = 0;
, J( v7 @3 k+ h V/ K4 @; F - P3M1 = 0;
; l |; w$ W; Z1 b! _ - P3 = 0xFF;
, ]& K2 R0 w w+ ^ - // P5口设置为准双向,默认靠内部弱上拉输出高电平3 l( f- b5 Z2 Q8 K
- P5M0 = 0;
& u) X3 f0 l, l' `( c - P5M1 = 0;6 A" @1 [3 c/ a4 b
- P5 = 0xFF;
b# o. M0 ]& j1 c- J - }3 G7 k7 ?- v, X3 Q4 o- B
- 7 q, v, N G4 j
- static void Timer0_Init(){- l2 p4 a) d, S$ D) P
- // 16毫秒@6.000MHz& H$ L. v. R5 t% E/ x8 `
- AUXR &= 0x7F; // 定时器时钟12T模式
1 V" r4 C7 B% I - TMOD &= 0xF0; // 设置定时器模式
2 o T8 L9 p3 {$ X# U - TL0 = 0xC0; // 设置定时初值- A. @% Z9 [+ L; P
- TH0 = 0xE0; // 设置定时初值+ W8 k7 A! X* {# e% D
- TF0 = 0; // 清除TF0标志& ~! Q, S* u9 {
- TR0 = 1; // 定时器0开始计时1 Z0 H& z5 J+ T# ?
- }: M9 X& Q. H6 s
4 s! S8 C m4 N- static void Interrupt_Init(){
" T& }& S& G, A0 F" ~2 ^8 M; P! D; D - // INT0中断触发类型为下降沿触发
' E! S) P1 b4 B+ u; x% d: f - IT0 = 1;
' c" g5 `$ f8 k. T7 @ - // 允许INT0中断
' {% |* t2 S% } V; a1 e - EX0 = 1;$ Q2 d- E' v. i# ~& b l
- // INT1中断触发类型为下降沿触发* i# d& m5 h1 J& k, F
- IT1 = 1;
) J: a" b1 M" @# A- R/ ] - // 允许INT1中断
3 r" O$ [' ]# {; d - EX1 = 1;# d. N$ I$ H5 ]( v, o
- // 允许定时器0中断
$ r) g% X7 V5 ^7 ~& S - ET0 = 1;
9 E. ^& q( ^+ `; z& i) Y" a0 y: \1 A - // 允许全局中断
0 C2 H( _4 \4 B+ t. E0 V - EA = 1;& ]& R5 h4 o- o2 p
- }. A9 Z3 ^; F6 p% H
( P: i$ B9 F) M6 |8 e0 M- static void INT0_Isr() interrupt 0{( G) `/ U( y+ L( A5 R
- // 矩形波次数累计归零
" _5 G; }/ i- U - count = 0;7 N3 c1 k' e& D: ~6 N, D
- // 重开定时器0中断6 ^3 R" k. L/ t$ a; J& c
- ET0 = 1;* z6 V! P- X2 a4 b. I! x. X
- }1 t* Z: p" ~6 R9 ?
- 5 O! t3 u" y, Y, U6 s
- static void Timer0_Isr() interrupt 1{
$ T& ]$ f# O3 N. x2 X# b - // 输出矩形波' ]# L3 t: w8 S1 R
- OUT = ~OUT;
/ t: Z. D% k2 Q$ ^8 B2 r% p; D# ? - // 累计矩形波次数% s/ M( ~# q9 e! ]- Y3 v
- count++;1 O( g+ r, {2 I
- // 超过250个矩形波,准备停机
- d$ D4 K2 v3 M9 t8 E. ?' B - if(count > 250){
3 ]6 B- F: h4 d2 t - count = 0;
) \0 S0 D5 ~; W6 `/ U7 E: w - isReadyToHalt = 1;, Z3 b2 _6 o3 X3 a
- }
! c' L' r5 c G - }+ @- D4 y5 e4 m" G8 e
- - c! r; z7 Q8 u
- static void INT1_Isr() interrupt 2{
! Q9 b5 J* D. m% z C& Y - // 矩形波次数累计归零; s" J4 X4 b3 b+ g% T; s. y
- count = 0;/ o& s# C0 a, u* s7 e. [
- // 重开定时器0中断
0 b1 ~4 ?( X9 w: f& w' A2 f - ET0 = 1;, c: X- D$ X: a6 ^5 M: t3 e
- }9 o, w$ n" F+ X3 h; ^4 P5 x
- 9 k7 q- q. ?' M L$ [
( |0 H% K1 m( y X/ Z8 ^1 z" C- void main(){
& p* M' A. _& r8 M2 F; f6 ` - // 初始化P3和P5口
k# R4 b4 [3 E7 O* `+ b7 |6 e - GPIO_Init();2 _' x; T; J4 q
- // 初始化定时器0
/ I c% B& l( S - Timer0_Init();5 a$ Z4 p" Q8 F: F9 g. B) [0 @
- // 初始化中断" O K% R" L( @; ~: M$ y
- Interrupt_Init();
& e8 y9 O- B" | Y4 A# ?* H0 g - * H# e3 o- \" p* B+ Q
- while(1){
3 a3 W" X% @' { D# o( d W- W - // 检测电量( h1 {$ i2 |( e0 b \2 L
- if(PCON & (1 << 5)){2 V7 \' Z1 d( z6 D, T
- // 如果电量低,低压指示灯亮0 y& o, ?: q+ t0 ]# d' W
- LED = 0;& b$ |$ U. [3 j
- // 清电量检测硬件标志位
% y2 j, P3 \& |) K; I3 Z1 D - PCON &= ~(1 << 5);
$ @0 U; D- U* n; h, ] - }else{& ~4 r6 Q1 ^) t- t
- // 电量正常,低压指示灯灭
2 x. J# j/ h# U W - LED = 1;% \( `1 B: {( o J4 m# q; o
- }) b2 x3 w/ n! o/ X1 R# m2 l
- w, V. Z( u+ D
- // 检测到停机标志# l+ z7 I% n. R3 A
- if(isReadyToHalt == 1){
% A1 T- _$ V. g3 U3 b3 n1 ^ - // 暂时关闭定时器0中断
) F2 R2 o. m$ p& K6 o: s0 ]8 E( n - ET0 = 0;
8 E2 Z* [* v3 N* w( n2 H# F - // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU2 U6 g# t) s+ c: E6 R5 B
- OUT = 0;$ v5 z t9 M9 \- |$ v- c' N9 r" K: C
- // 停机之前先把低压指示灯灭掉,以省电
4 l7 ~( ~0 U9 u% U - LED = 1;
. G2 N7 {3 _& _" |: v. ^ - // 进入停机模式
' v$ M( v+ G! B# D - PCON |= 0x02;
/ l& m4 ^! ]# H; M. K - _nop_();' B0 s$ i) n `" E1 x
- _nop_();+ G* W1 c# b c/ ~
- _nop_();
) ~1 W' d5 p( J7 ~ p% a; Q0 \ - _nop_();
3 `1 C+ H4 D2 K# f0 ^ - // 唤醒,清标志位
. G2 h7 q- r' _5 o$ |/ D' [ - isReadyToHalt = 0;
7 R$ ?" [; m8 L - // 重开定时器0中断
" z W( ~5 ]; s- F. a7 ~' x4 B - ET0 = 1;
3 @7 ?+ z: \0 Z- u7 `3 f - }: W6 ]7 V, T0 v
- }* ?( e' J3 Z0 m6 G
- }6 O2 y X$ X) c6 z
复制代码
$ y X+ C0 h6 A2 T+ m: V) B7 I硬件参数配置:* Y9 H" a& M6 [8 D h+ H; B$ E# K
5 Z2 K4 G/ c' z9 ~9 Q' t( l& R2 [5 W& z
$ ~. J {7 W1 L3 p
接收端:
1 [' L$ Q ^0 R" ?- #include <INTRINS.H>. o0 d) X" n* ^# m7 P# r [; Y
- #include "STC15W.h") B" c* n& C. _+ j8 Z/ c
# L' S- q' s ~) a
( `5 N* Y: ?8 W& G- /*
, t& j& `. c" C/ L1 @7 y' x - 3 ^! X5 B# a) i0 e- S: g9 M
- *---------------------------*! a; }& } s- V5 Z. I! s7 {% j- k
- |1 (GPIO2) \__/ (GPIO1)16|6 Y$ ]/ l9 f! m* n
- | |, X$ }* j) I7 i' q: T9 j' D
- |2 (GPIO3) (GPIO0)15|
& H" V" Y. m3 n% U2 t% p, } - | |
# z6 d+ a# L) z7 E) q/ Y - |3 (GPIO4) 14|
3 z: F5 ?9 c7 i1 @+ ?0 w9 K - | |
* _+ [, D" q3 F$ ]3 e; U9 W$ M - |4 (GPIO5) (DATA)13|" v8 q( e6 d& \8 e% O4 b
- | STC15W204S |
7 N5 l8 Q! a2 X - |5 (GPIO6) (LATCH)12|
* [9 ~& u. X4 F c - | |
8 E1 U+ O/ ~! [3 x - |6 (VCC) (CLK)11|
' J5 }. {: ?. @, g - | |. W, d: M' O2 [! c9 j# h+ `
- |7 (GPIO7) (TXD)10|& V! M* s: b6 Q
- | |5 P! N. h, @- D% \( n7 E! K
- |8 (GND) (RXD) 9|7 |2 z0 {2 U4 Y; C
- *---------------------------*
" A C" E- F& Z8 x* o - Fosc = 12MHz I6 T/ [2 S4 [! g# m7 w9 k
- 1 r7 `+ ~( U$ t+ y* `' P
- P1.0 -> 上
( k2 _, Y+ [# K" h7 v1 S; a" B, e% V - P1.1 -> 左* m# s$ s" T! g' C( J
- P1.2 -> 下( L9 ` e8 Q6 t3 m
- P1.3 -> 右
) V5 w, q2 L5 @7 e; L8 T - P1.4 -> SEL
7 j* V* ^' e( B6 }: A; w - P1.5 -> STA, \2 k$ l7 V% `6 S" m' i- S9 u
- P5.4 -> B/ O6 ]- M5 i) y- d( h5 {% o
- P5.5 -> A' l1 E4 G* d# D: Q6 W" C2 x: ?
- ' `, q1 {5 q% z/ A4 n( P! l
- */7 C, ~6 Q9 g* s7 h
( s9 [; a- ^7 H- 9 g+ |( M; }% p8 f
- sbit CLK = P3^2;3 c3 l; I) l: L2 K
- sbit LATCH = P3^3;* i6 K/ P" [! f! l9 q1 @6 D. w: L
- sbit DATA = P3^6;
1 }1 y" X7 g% \3 Y - 4 W& Q- N. r# Y. ~* p+ l8 v) p
- bit isReady = 0;
; [! `$ Z" G* v- K: d: f - static unsigned char key = 0;* @5 {+ o% O" G2 \4 R c
- static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值 M" d7 k2 M8 G' \& p
- static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份
z) K' \% V5 ?6 P" y% f - static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中
# i V' B+ G% ~0 U - static unsigned char idx = 0;& [$ T3 X+ \! g
- # l2 @3 y J; s8 ?4 I4 s, F& y
0 t6 Z9 ]9 B5 P0 ?5 V- static void GPIO_Init(){
: H+ |% | M+ ? n/ R# t0 D - // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平
: E" r5 a9 R0 ~3 w5 z - P1M0 = 0;5 a; g4 v- E$ y4 E" L8 b1 ^
- P1M1 = 0;
; E! f, |2 t' L. P - P1= 0xFF;+ P9 i# G# O0 e! g i' ~, R
- P5M0 = 0;
# @; }+ ~6 V2 A: G2 U* j - P5M1 = 0;
, U; W1 o8 w1 l( o' v/ }- N$ T4 f - P5= 0xFF;! k$ @+ q8 ]* b2 k3 n
- // P3口初始化为准双向9 B- Q5 i1 ^, ]- C7 J+ p) j/ ~
- P3M0 = 0;
) K- F- J$ {2 A- O' P s, v+ ] - P3M1 = 0;/ l5 y! F2 H& o5 |9 H* L5 R
- P3 = 0xFF;
9 d V6 w) H2 r: ?3 ~' p: ] - }
4 d+ r% U, r7 p2 i/ }7 B - $ q' t6 V" R# o. g+ X1 p
- static void Interrupt_Init(){
/ u" ?) ~5 f1 d7 U& r( ^ C5 n9 c - // INT0中断(CLK)触发类型为上升沿+下降沿
* Z: d2 ^0 [( Y, Y" B; ] - IT0 = 0;
( v4 z8 G- s1 J) ~ - // 允许INT0中断
; o8 }2 `6 ~, b8 n1 @; r- H/ ~% S - EX0 = 1;4 G v; V4 I1 A6 n6 D6 a( s. I
- // INT1中断(LATCH)触发类型为上升沿+下降沿
* u* x2 W+ y- A - IT1 = 0;
+ C+ T" S9 E6 O9 u - // 允许INT1中断
8 {( l' b! M6 O% b- j' S- x1 l - EX1 = 1;
% C2 X% D" k2 d. N - // 开启全局中断
9 u5 u4 H g0 R- A4 {( e* b/ N - EA = 1;* y/ P) x3 {- o
- }9 X1 x u# @9 ~$ t4 b. n
- ! n. v `! v3 V9 k9 e" V9 ?
- static void INT0_Isr() interrupt 0{. m! O% k+ E* ]( m; L2 x
- // 只有已经成功锁存才允许CLK移位1 x8 Z! e2 h" Z5 S0 a2 ]
- if(isReady == 0)
6 Z6 R- A$ B3 o4 ] - return;
/ a3 L1 I7 @3 |' W8 W/ k - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断2 L/ I. f. I0 g6 |" m, ]4 ?
- if(CLK == 0)
! T, q Z+ h- D4 J - return;- W9 z* @) \% P G5 }- `# h
- // CLK上升沿到来的时候,取锁存值的下一位输出3 f) K( t) p0 T- K' i
- idx++;4 U5 k2 J: x- ` [5 B
- DATA = key & mask[idx];& v) Z3 v2 G2 q' j# r( u
- // 如果已经完成7次移位,则一轮读取完成
) J) n- ]) |* d: s3 K: @ - if(idx >= 7)
4 g# Q* n* b% Q5 n1 Q8 c$ x - isReady = 0;" J& \+ y1 @3 X: K8 C
- }% u8 [' _+ N, b
- ( A9 y& b% @0 n C: R
- static void INT1_Isr() interrupt 2{. U1 E7 ^2 C6 ^# t
- // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
# L x7 |+ t0 w6 ?" g/ ~ - if(LATCH == 0)5 ^. b% P9 m3 `& G$ Y
- return;
1 V( B/ w, A3 @2 I3 f1 U2 ^ - // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA
' x2 n& l" M1 o. y7 q$ K# U3 b - key = bufReady;
. A7 R \* |! K7 d$ e2 t- p9 y - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表
& I& }9 b, f$ j" |5 I - idx = 0;
' {8 }/ }: m; ~$ k+ [ - // 允许CLK进行移位6 W/ H6 [5 a) M% m6 t& x2 H& W9 z4 ?5 I
- isReady = 1;" m2 i4 k1 n2 q% Q
- }
! N( |1 s+ y3 a& q+ j# Z4 U% B
6 y+ H! @' V' b* w( i- {- void main(){
8 _6 G# K' S% e. M, m0 d7 Z, ?, Z - GPIO_Init();
% m% w( j/ P# ?0 ` T1 @6 i - Interrupt_Init();
, i9 ]3 r; Y* z% W R3 i) } -
( z; s# w9 I3 w- f# M; g# _7 L2 C - while(1){
" T7 N$ z+ w' Q1 x4 [# c - //PCON |= 0x01; // 进入省电模式
4 L$ b& b( D9 V/ [6 `) C; {& \: L - //_nop_();
9 ]1 J3 C4 D0 o7 K1 ]4 m; H w' G) s0 Z - //_nop_();
3 P) m' A% c# A+ w5 d" n$ ~ - //_nop_(); W7 A3 s& n* d
- //_nop_();; m, M8 |( K! F+ ^
- buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));
! u, v: o: L5 m - bufReady = buf;- ^9 J2 ~2 {$ m% ~( N9 b
- }
5 e' s. e1 X/ }: T! A1 [4 O2 V' | - }7 H$ u% m1 j8 a$ A" q# N! p- g- {
复制代码 ( @/ @ p( U/ y2 u8 _
硬件参数配置无特殊要求,晶振频率选择12M即可。 [+ {* e1 h8 Y: S# w* }& \' ]
# ~. w U* ]; `, p7 [' J
这是编译好的固件。 |