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