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