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