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