本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑 9 o+ ?: f$ A7 L
+ s) Z5 Z( Q3 E$ H1 i. c
没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。9 j: Y5 d, B7 Y" p+ _6 A: F8 O% I; R
5 Z+ M0 O: }& _# |
4 I; l6 w1 ?( m( ~* ?' y$ o
发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。
1 l) d3 V3 l1 N8 v" Q3 W' E5 w, @3 P, n/ N; e
买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。, e6 X' j. p7 n' n3 g4 q
为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?
. e; r3 c9 g, O2 `, p然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。
2 F9 a, y8 d& a
- d$ c$ Y- p; u# B. q" Q4 i* S
! B' A1 k T, k+ Q3 ~2 J m/ ]: c- F4 F
最后把按键、导电橡胶和电路板装上去。
2 [' P7 F3 b) S; O+ ~& O8 ?% M精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。
( r8 o0 {" ^; s3 \! \% d
+ H; |2 @+ O3 l$ L! S
% U- }3 Z" B' m, r4 w: t7 R" {1 }完工!/ J! |0 `7 z# _
9 Y; c5 l0 o% J6 T0 ?# t
5 v- }2 p. F, W5 c$ y. t- [" S这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。8 ~. e3 e9 A1 t2 J7 \4 K
& y+ R8 c& q4 n& Z K$ {; e7 Y/ l( n0 A% K0 t4 L
接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。
; o9 e1 r0 R/ [2 R
$ v, }. h! s9 `3 U: E2 Z4 t* R: p6 y5 I! t8 m* m8 F
2 M# r% h/ Q7 R4 t" e电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。, l% V3 W# J. J$ P4 `
T0 F: d" \9 A) T5 F- H
实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。& r- v4 }5 }; n8 V$ Z
# n+ [- p' B. K6 k5 G K8 G这是电路图,有兴趣的朋友可以参考。
; s: o* i: T' X5 C% r, z g
6 j, F l1 c' U; T, q" h. _. i1 \1 ^: L9 g; O- A# }% U" c
我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!
- q1 P8 y) d2 N4 T编译器:Keil 51。0 h- k% L0 y0 \: \
8 D) O- a) G8 p/ C6 b% F# _
发射端:
/ q; y! z6 C- m2 e" B- #include <INTRINS.H>: N+ l! k/ T2 w3 \, n% D8 `
- #include "STC15W.h"
" i* N5 i. H; G1 W0 I y u2 y - 5 v' x8 o; A6 o0 M! z+ p: n' q6 F
- a" x. t& z8 ^8 {8 `) k! G
- /*, H9 |7 i( L& k8 T& D
- --------------------------
& B" i" R* o* C - |1 (OUT) \__/ (INT1) 8|) _& A! N* m6 w+ t
- | |$ s4 O1 d3 A# M2 ?1 B; i* O& P
- |2 (VCC) (INT0) 7|! W- t& O# `7 V7 b9 s8 ^
- | STC15W204S |5 V, _. M7 w( y6 g3 [; s( C6 q }
- |3 (LED) (TXD) 6|
7 o/ \: ]. w2 f" `$ { - | |
% X* K# T+ S% z ~1 u3 d - |4 (GND) (RXD) 5|
3 A! c G- i- B4 [7 t- v - --------------------------
! P0 |* L7 E1 t% d# N) k, E - / A S# O6 O7 |5 U2 @
- LED ---|<|---[===]--- VCC
* q# y- [+ F9 y - Red 3306 T5 m3 O( K. H/ L8 q
9 x, {' t1 B( y9 k( G4 f- Fosc = 6MHz9 q7 u r# k/ Z& k* }# W
- */: y& P0 B9 y9 f( X: w# |+ A
9 D/ e# }5 Q$ x) E- G0 G1 a2 ^( N- : j4 \. N! W& f/ a3 r
- // 矩形波输出脚) T( m% U# S1 n/ h0 }) N- N( _: S
- sbit OUT = P5^4;
; i2 }4 o) z$ v - // 低压指示灯引脚) a: F7 r l% |7 q9 n* ^6 p$ h
- sbit LED = P5^5;
2 }. R4 c. t* C$ n - - Z9 L! u' O& S6 l9 ?) |; w
- // 停机标志位, U5 K) ]2 Q! q: r' K: X2 ]
- bit isReadyToHalt = 0;
3 w! z( b% i3 a7 @3 j% s& ~- ` - ! H% d* V( h9 Z
- // 矩形波次数累计
# ^8 Q! }4 t8 K( h: x5 l - static volatile unsigned char count = 0; ^# Q8 n: R- ~% B5 U& [
$ q0 j2 q6 C4 ^8 u! G- - B) e$ X9 `# _
- static void GPIO_Init(){' h, T/ s6 A" j# Y# h4 G8 h0 Q M2 g
- // P3口设置为准双向,默认靠内部弱上拉输出高电平" @% X; R! Q' K
- P3M0 = 0;
9 [; u; ^4 o3 L8 N3 ^/ N; g0 q - P3M1 = 0;
8 a9 m: F% p' x$ E - P3 = 0xFF;7 r' f' h- U* R( \5 B, ]5 @
- // P5口设置为准双向,默认靠内部弱上拉输出高电平3 h$ X1 `1 A' H2 o8 |6 u- J
- P5M0 = 0;
1 p) {( j4 G: I0 h/ y% E4 Z' ` - P5M1 = 0;2 b7 K# x- W% m
- P5 = 0xFF;
" y. {# r" C/ `0 B - }1 \. j4 d. C* [% _
) R* X p8 R( D$ u2 o1 G- \3 g- static void Timer0_Init(){
# p+ H) T U7 l) V$ ] - // 16毫秒@6.000MHz2 ?/ \% ^! o2 P9 K0 c# O
- AUXR &= 0x7F; // 定时器时钟12T模式
( \; b; y; M( t3 v1 Z/ i - TMOD &= 0xF0; // 设置定时器模式$ [; J& m" @/ x, u
- TL0 = 0xC0; // 设置定时初值/ k- j6 y2 ^3 y: L. m
- TH0 = 0xE0; // 设置定时初值
& ]0 K/ Y$ D# G% z. g5 p: t1 d0 _ - TF0 = 0; // 清除TF0标志
) ^ f6 \4 e, v5 H2 ` - TR0 = 1; // 定时器0开始计时0 @' W, {. i u0 |4 Y4 W$ E
- }
2 w" d1 u6 K {1 K
# k9 p# m( u, X; _/ q- static void Interrupt_Init(){, m' p; @( @& \1 }% d* x- B
- // INT0中断触发类型为下降沿触发/ v. U1 d9 e" g3 b$ |3 Y( _( g- }' i& f
- IT0 = 1;
7 ]& ^3 _/ t& C% z8 s; l - // 允许INT0中断
& }: q5 N5 Y" h* W8 J& | - EX0 = 1;
% P- N3 ]8 H- l - // INT1中断触发类型为下降沿触发
& x8 v0 x) `4 c1 G4 S; ` - IT1 = 1;
& a& u# f* g- O1 J - // 允许INT1中断4 W" e V7 ~9 G' u% J
- EX1 = 1;% b' |" m2 r) [
- // 允许定时器0中断
" R) i$ _+ \3 I; p. L8 @+ j - ET0 = 1;+ e$ R0 y* b0 W1 @7 @
- // 允许全局中断, a4 ^) p) i% l: p
- EA = 1;
% X7 t' l. M5 v3 l - }7 W9 H5 s8 N. q3 z" e' G3 }
# D# L+ K8 W) m8 S- static void INT0_Isr() interrupt 0{
, G3 ~6 ~6 |2 k' U* D1 O7 o4 b - // 矩形波次数累计归零
$ g9 N2 B X3 t - count = 0;
" F' ?# ~6 ?# g1 w( X - // 重开定时器0中断
8 |/ I2 i6 F7 E - ET0 = 1;# @- B$ u5 M/ a( H" J. e% A
- } G) v" f& v5 a" S3 O( `* Y3 r& x
4 T9 w0 H* T+ a# E' V- static void Timer0_Isr() interrupt 1{
! F( k @* r7 E, v- q - // 输出矩形波
1 ^; I/ O7 r, @/ \ - OUT = ~OUT;% }: k9 z9 I( e& M2 e) K
- // 累计矩形波次数
8 l3 L1 E2 B; o- O) g1 H - count++;
5 F7 Z/ L- V( t1 T F4 D" Z - // 超过250个矩形波,准备停机
0 a2 [' u ~: O% _7 ]& f; T: p - if(count > 250){
z. x* w) H( e6 ]$ ~& U3 E! C - count = 0;
3 Q. k' y% W6 v% C - isReadyToHalt = 1;
6 K) G9 g: a* y. c1 M( v - }
0 w8 n5 S. X- Z0 K$ `4 M0 ]- ~, [3 I - }3 N H- J6 k( F( ~9 G
- % i- a) Q$ t; T1 ?. x1 L- Y
- static void INT1_Isr() interrupt 2{6 J2 e( B1 u2 }/ `3 l- C2 S
- // 矩形波次数累计归零$ H$ x" U2 I+ Y6 C1 [. J
- count = 0;% E: [4 ?* \1 j1 \3 D
- // 重开定时器0中断 c+ ?$ K1 h' O7 h2 c: C9 n
- ET0 = 1;
+ K- b' J1 Y; {# J - }" F. C' X& V# ^# Y4 m
- . R* t6 n% G; C2 Z
; t8 `( U* u' O, j8 S; H- void main(){0 J: {0 i: n) f* K0 p
- // 初始化P3和P5口9 r# j+ x0 j7 v1 a" Q: q
- GPIO_Init();' a S8 l5 S5 h
- // 初始化定时器0
7 i4 A) X% y4 L+ ^) J8 F5 K/ v) Z - Timer0_Init();
0 P- d- A- u4 ? - // 初始化中断. b0 c3 l- t/ A! T
- Interrupt_Init();& o) |6 s; R9 U. N; c
- 8 t/ A+ _2 W, e6 p: [/ @
- while(1){
! x& b# g. v+ }1 C' H0 Z - // 检测电量* }) T( V/ `4 l
- if(PCON & (1 << 5)){
1 M% v7 V& ]3 q* F+ Z. D - // 如果电量低,低压指示灯亮
" Q8 V/ J6 K0 ]! K! o - LED = 0;6 U; X2 V3 k. M) B" n8 ~: Y5 E
- // 清电量检测硬件标志位
& D! N: w" Y0 V9 u `7 o2 j - PCON &= ~(1 << 5);
- o5 a& D- ~! l+ G' O {5 a - }else{
! s. p3 h0 y4 n( K$ N! q) v - // 电量正常,低压指示灯灭) q/ m& {: ]+ A' ?# M
- LED = 1;
6 a2 k; ?5 `& i - }
" B$ k R+ w/ l2 Z) Y - + D" H% z- O) D0 N
- // 检测到停机标志
0 P# U2 x, z6 s @ - if(isReadyToHalt == 1){
2 D. G( q9 o* [: m( b - // 暂时关闭定时器0中断% T; i; W- F+ l5 A
- ET0 = 0;
' p; j# }1 `# s" J4 i# Z( ?# l) M - // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU
0 l( l& o+ K3 a9 w4 w' B - OUT = 0;
$ A! q" Z4 b: U; T& } - // 停机之前先把低压指示灯灭掉,以省电# ]: G6 q" {- B2 p' C- x7 r& C
- LED = 1;+ y# w6 A, D x' i& B
- // 进入停机模式
9 m/ p2 B& G8 |1 X - PCON |= 0x02;" ^' q5 G) R9 ^" V: Y3 i
- _nop_();( `1 K% Q N4 ]7 X
- _nop_();
' a" S, Q0 o& p2 `. N( Q8 Q - _nop_();1 u# R! m; ?+ L
- _nop_();
% V' z: b7 [5 ~5 \3 i0 R' t - // 唤醒,清标志位
. F/ c3 e- a- W8 H0 B8 Y Q7 e - isReadyToHalt = 0;/ ]; N/ t/ T- u2 H" ]3 W
- // 重开定时器0中断
5 u" d; E% n- i, m! A$ q ]" a - ET0 = 1;$ l3 j& z- K/ w# I- ?( c: Y
- } f/ t5 u% v G0 U/ ^- i
- }- X0 M1 ~7 K0 M( V
- }' r ~3 f% M6 j0 U0 }( R6 t
复制代码
( Y h$ H& H5 x. l( b, B硬件参数配置:: h2 V% E* j O$ }7 Y! _- m4 b
% ^6 u$ @% X( @: b5 R; E
5 @( }8 f; h8 J8 D
接收端:
9 d8 i2 `' _" j3 n+ h5 \- #include <INTRINS.H>
( v1 N1 Y; u8 c - #include "STC15W.h"! |4 ?9 l) B/ x6 h4 `0 g1 o# P* t
- L; d+ `/ [. f8 w/ Q, N- y9 P
2 r0 c* Q6 J' C& r) k- i- /*
' y4 ^; |. f+ P% {/ g: y - 6 s) s9 i0 d% d0 K8 j
- *---------------------------*+ U$ c# B* G1 l: |
- |1 (GPIO2) \__/ (GPIO1)16|
: k2 S/ Z9 m" \, J$ C3 B' D: c - | |
, ~2 e: v' { @" L - |2 (GPIO3) (GPIO0)15|6 ]( b# N! {# v5 }+ P* Y6 ~
- | |3 z, _8 p7 w" L/ k
- |3 (GPIO4) 14|
5 e4 a! `' _: A) C6 J P( U - | |$ o- v+ r+ j. q1 G, P0 I' H
- |4 (GPIO5) (DATA)13|% m9 `$ @* O# Y* H' z: t, q
- | STC15W204S |
$ p% i5 l* U$ c! E6 n0 r( [ - |5 (GPIO6) (LATCH)12|
" _' L/ ?3 R' @+ X" R% I! D, ~5 x# p - | |5 k3 o. Z, s) E; d3 b. Q
- |6 (VCC) (CLK)11|4 u9 f y2 Z6 u, L/ x5 W
- | |& w" X# { J) f! X% z. x
- |7 (GPIO7) (TXD)10|3 W8 w: c* A4 @/ f0 c2 ]5 u
- | |
9 t' M1 Y6 ]- d/ ?* z - |8 (GND) (RXD) 9|4 }( s7 r( M5 J/ O* e1 k N
- *---------------------------*
! G" m4 N3 \3 y2 ^ - Fosc = 12MHz
) }; f' ?) |0 c) L$ u% e
; K3 \+ Y" m: f3 l! d- P1.0 -> 上
- X; O/ c/ ^4 Y% u& G# j3 g6 t - P1.1 -> 左) J9 b7 L6 \; L' y8 a
- P1.2 -> 下
* u% R$ J$ o# }; W. T$ t - P1.3 -> 右* N/ R% S* M1 J/ y5 p3 W! P; r3 m" w
- P1.4 -> SEL
& [$ t( A. q2 r3 W' w# \0 X - P1.5 -> STA
; P' L- w5 ]# g- D5 S - P5.4 -> B; e6 F7 p) G& _$ z4 A0 {
- P5.5 -> A! |2 }& \7 f% L2 N
3 |" o n' t) b) T% T# O5 [/ _- */5 q7 |: z2 {5 w$ i' H
7 v8 k* _+ P# B J1 x3 \$ s' R% n
* q8 w4 V6 K h. M& Y8 P- sbit CLK = P3^2;
; l8 E7 m: l1 f& N0 g - sbit LATCH = P3^3;8 w4 `* J/ A& t5 y' }
- sbit DATA = P3^6;3 F \6 `+ z6 a5 _
$ s/ W. @5 ^8 e& k! b2 ^4 i- bit isReady = 0;
$ K% C! ]+ c6 ^% X( q - static unsigned char key = 0;' A* _4 s$ G" A q' i y& P
- static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值
, K1 c0 `7 t! Y. i - static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份
- P4 K: F! R( u. ]1 l$ V+ {$ [ - static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中1 {6 k2 V, l5 f8 r8 h9 }
- static unsigned char idx = 0;
3 q6 j, [! o7 {1 ?
) O; p6 T8 O \/ [% P7 B4 ~- ( Q" C% W/ \2 \/ ?" h) [
- static void GPIO_Init(){3 Y# Q K8 l) P
- // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平
7 `- E$ i h7 Y. J6 p) D* J5 V - P1M0 = 0;
& T9 c7 u% I2 N/ F9 _, w3 u - P1M1 = 0;
+ v* ~9 e; U& [ - P1= 0xFF;
2 d" m0 e- P, |5 E2 A/ Y - P5M0 = 0;8 I4 v7 m, s, W/ G* p! h
- P5M1 = 0;2 e1 ?$ k+ Y w4 W: F1 O" X
- P5= 0xFF;
: S7 [ h, Y8 E" F) i - // P3口初始化为准双向
3 o0 F9 }+ J3 V& q2 T - P3M0 = 0;
2 ~/ h" o+ p8 Z6 F4 ?) ~ - P3M1 = 0;+ J5 C) b3 u5 z( H
- P3 = 0xFF;
& ^( o' |3 L. a" w" t - }4 U8 r* |1 s6 Y$ r
( G/ z' \1 }. C- static void Interrupt_Init(){" n4 z5 D" @! s: g' i. ]
- // INT0中断(CLK)触发类型为上升沿+下降沿
1 O3 @7 ^9 u1 ]; k+ ~8 Z# q - IT0 = 0;
6 S9 h0 N2 n: h: Z* u c w3 _) J8 ^ - // 允许INT0中断
1 c3 u/ @- {$ a- F - EX0 = 1;$ Z7 W9 n. [) i2 Q
- // INT1中断(LATCH)触发类型为上升沿+下降沿
$ j" p1 l+ m; ^ - IT1 = 0;
0 |6 B% d4 z* C' B! O! H - // 允许INT1中断
6 x4 S( k, h- ?. M+ @6 u - EX1 = 1;9 ?. Y! \& R8 G4 U
- // 开启全局中断* l' W8 f$ i" m6 o
- EA = 1;
" a: e. Z: n- Q4 f F, D7 w3 l - }
6 U; L. i% B7 H# [0 n; ~% l( l
. E5 `2 y' ^5 X/ `3 o+ q' Q- static void INT0_Isr() interrupt 0{) q$ f* i1 C( D, Y8 g+ ]' m
- // 只有已经成功锁存才允许CLK移位+ E% p# `" ^( {- X% t2 b8 `1 K
- if(isReady == 0)
' {) E* X% n p9 i% f$ u% [ - return;
# N8 X3 O0 r' ]3 F0 B - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
0 w& M7 J, R/ g8 G - if(CLK == 0)0 I& L/ f, N: X4 H
- return;
) ?# _ o7 H; @( `1 R. T/ j, T - // CLK上升沿到来的时候,取锁存值的下一位输出' C2 ?. Y# m% t9 t
- idx++;
4 O& a @7 s% A, c- K' s - DATA = key & mask[idx];* ^7 b* f" c: R" d: T
- // 如果已经完成7次移位,则一轮读取完成
1 S! Z' Y; X' _' _7 c3 A+ j - if(idx >= 7)5 W$ o$ w. U& g B4 P+ F
- isReady = 0;) h$ ?) E0 u0 ^" A4 }/ K6 r
- }3 G0 D% [( B% x; B* e: C9 m
/ j. ]% h2 c/ D$ k0 Z- static void INT1_Isr() interrupt 2{7 k$ w& I) Q% D" g
- // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
, g( F) w/ y' o6 e) n5 F$ v Q - if(LATCH == 0)4 J) Q0 t$ W- u
- return;; K! a3 g6 X7 C& c
- // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA# A7 j0 f3 f# D% y" y6 c3 A1 |
- key = bufReady;" |) i$ v" {( U/ s. ~: d3 n
- DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表6 D3 o3 \) Z( Q) c
- idx = 0;# m3 y9 o2 h$ c: j Z
- // 允许CLK进行移位' ^0 W" K8 z4 D. _2 b* \2 X
- isReady = 1;" o- u! j& W- ]* [" W
- }/ j( T; L1 Y+ Z E% A
- % C, G6 G8 F D7 a
- void main(){
2 a/ x% J$ q6 \ K, ?) z - GPIO_Init();
( C+ d+ H* R, a3 ]% j2 g4 D8 I - Interrupt_Init();
3 D& h8 w5 h, A! t& x- } - 2 [6 \# M, H6 Z! @
- while(1){
% v" V, |( o k9 ?# J - //PCON |= 0x01; // 进入省电模式, m1 J% j1 T* s4 L: a& e# k
- //_nop_();
3 n6 k' n& R e+ y8 S. K - //_nop_();
* m. E/ J) X5 u: x+ ^' }* o) K3 n - //_nop_();
3 S' W# Y. x$ Y" \ - //_nop_();
, m6 Q( y- s- v5 I( {% D - buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));
5 t4 G- U1 }) D7 X% n - bufReady = buf;! O# S ?* v$ y& h+ Y0 Q% ?
- }
7 L2 A3 ?. L# j/ j" [0 v - }
( m: a8 M+ a& d6 W6 V Z
复制代码 5 ^! l9 V' I9 q+ l# E; m+ z
硬件参数配置无特殊要求,晶振频率选择12M即可。
3 k: e$ F8 g$ ^2 L* w: S; a
9 g: Y/ P/ Z- J这是编译好的固件。 |