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