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