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