本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑 3 m3 a! Y7 u. s V/ `: X
) s; L( W. o; x* k z! }没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。. O9 Z( C' G) h5 J% `
. n: ?6 L! `& a6 O& r4 F5 c
$ o) c+ C9 f" ]" S发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。: P! e7 Q9 p: j, V4 b0 [6 L
% l" @5 Y8 Q# `* d买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。/ q. h7 {. X" \6 T
为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?
4 I$ c+ _- a: M. z8 L' M# g然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。 o" i+ Z3 }4 f) K Q: e. |; I4 `
( V/ [2 I2 X v! \) c0 n
: a2 z. J6 H m5 E" Z0 @: Y, A
) z7 Z% N5 g4 b' x; [1 t0 S$ S
最后把按键、导电橡胶和电路板装上去。2 }8 j. J7 [" o. |5 U
精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。% w$ t2 X, O1 C
* e/ `% R! b* T2 v5 P) M+ Z; x' m' i
完工!" c# \. e9 J6 O: K, c1 v! J6 T+ q
- F8 ^8 S, ~$ p, `# ]* \- x
; w5 O9 E3 ]4 _这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。' w$ a: C$ X8 s
/ O/ T+ Y) Z; J5 f+ [
0 Z7 j- Z+ Y' B( w1 K接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。5 v2 j. ]1 ~# T# F
i4 d$ M- t/ n& E2 m& ^; x% x \- |5 S( C4 f
) c+ W9 M1 r! v9 Y/ S电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。
3 I5 |- D: j7 j9 z5 K5 E p+ [! o
2 k; K) }: X6 T' e! i实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。
$ E# n2 I" c3 q; {, W K9 v' a) A8 Z
这是电路图,有兴趣的朋友可以参考。
% e/ r2 ]/ f$ Z2 g: v
6 X( S* v2 L# Z( K' D8 T5 e& ]+ i# \4 V, u- S% D! I: V
我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!
: p2 d- X$ `. W/ c编译器:Keil 51。
, l4 W0 M, E8 I5 X7 O# N/ h, F+ e3 z( q4 ^/ C
发射端:& f6 @% M! E: E0 f; J6 B a2 s
- #include <INTRINS.H>
1 C8 j" z# X4 R) F- }3 { - #include "STC15W.h"$ s; N$ A9 ^1 D! b' }$ }% b
- 9 T& y0 ~( m/ o1 u! U
- 1 m1 ^ N; P, c. u& G$ _. f3 d# u+ w
- /*5 Q6 g, z$ u. O5 w/ J
- --------------------------. D5 q2 v: k- w% }( R* O1 ]
- |1 (OUT) \__/ (INT1) 8|
7 z6 Y# F. E; M G, \ - | |9 k7 P5 x/ @! f
- |2 (VCC) (INT0) 7|" x ^# a) ]4 h3 f, p: }' W
- | STC15W204S |
1 D. Z2 r f: o$ o - |3 (LED) (TXD) 6|& N) h9 o6 l! D3 j" M, {9 o/ |
- | |
8 G) V' P5 Z% W. ]* M) W& k - |4 (GND) (RXD) 5|- ~1 f E Q" K" q$ c& P& o' U
- --------------------------
8 `+ t( P* l' o4 F+ P B$ _2 B6 B - , D# t1 U$ e; E" ] g( m: F( b
- LED ---|<|---[===]--- VCC% H5 x# c8 m6 U5 i
- Red 330) K! G+ D6 U* p: b0 t- d
- / Z3 t7 i9 j) I) ^" I$ S3 i
- Fosc = 6MHz% e: _: N7 D/ ?
- */
: ?6 E, u9 k% I" c) O6 [ - . H# X/ w( S$ E/ o) s3 I g
5 k# c" c' x9 ?8 o& w9 s: }- // 矩形波输出脚
0 U( b9 h& e8 w$ C9 F* L4 b - sbit OUT = P5^4;
8 r7 q# Y7 ~+ T* q+ g8 t N - // 低压指示灯引脚
% G5 \) c$ l4 T# Z' [+ X - sbit LED = P5^5;$ I) X: S' [+ ]- Z* k1 q
- 0 y* W/ A. P# y
- // 停机标志位
9 Y t" C' J! o; I% F - bit isReadyToHalt = 0;
- |# Y+ Z& o* O. }
2 y/ [8 I7 F( X$ Q0 l5 S5 V z/ b- // 矩形波次数累计
" ^1 e+ X* N7 V: ] - static volatile unsigned char count = 0;. H/ V1 j% z' z- X" v Q
/ w& L g2 ~2 ~7 r* r) E
1 b, ?- E. l4 y# v- static void GPIO_Init(){
# h# ^$ n/ O% u% n - // P3口设置为准双向,默认靠内部弱上拉输出高电平: V5 D9 x$ p4 j7 S& B4 @ H
- P3M0 = 0;+ j( `/ S3 b1 a1 k D9 k2 T; S
- P3M1 = 0;8 s) m1 P9 m& s# i# T% b |) [; O3 |
- P3 = 0xFF;
' c. ^# q6 K6 x) | - // P5口设置为准双向,默认靠内部弱上拉输出高电平2 t, \6 i* ^9 e+ \: L0 { `7 G. J
- P5M0 = 0;# T' Q0 E% h3 t
- P5M1 = 0;
% v. x; B V7 s! s* w/ u! ^/ w* D0 ? - P5 = 0xFF;
+ q$ H4 d5 L$ o c0 B% t; z) `) U6 p - }& E1 A/ G1 p" C! ^) _
( x/ W! M7 X% q% ^3 Y) ` ?- static void Timer0_Init(){
; Y5 s8 K, U# ~: _7 V9 y3 Z5 l) | - // 16毫秒@6.000MHz
; C; B9 h- ?8 W9 G( K. P' t- f - AUXR &= 0x7F; // 定时器时钟12T模式
1 r, R8 Z3 _( j5 S. ?3 \6 k - TMOD &= 0xF0; // 设置定时器模式) X. H8 h) B2 \/ J' X' L& c$ |4 |
- TL0 = 0xC0; // 设置定时初值
1 V! D( n0 F( M, b9 b - TH0 = 0xE0; // 设置定时初值- f7 u8 n3 ?( g" \3 J z
- TF0 = 0; // 清除TF0标志
9 }! `: d* z- ^6 N - TR0 = 1; // 定时器0开始计时- M+ G/ ~- Y; ~
- }
- I# K$ M; g, |. h, w+ f4 K" ? - : {0 F' ?/ O" U( v7 Q
- static void Interrupt_Init(){ V! t3 B% d3 \+ a# p& @8 @
- // INT0中断触发类型为下降沿触发* w0 W! v0 Q( X2 c4 ~1 l' f
- IT0 = 1;
8 I" L' M. e0 W. }( ]8 G& } - // 允许INT0中断" `2 t. d6 P9 M [' P! B
- EX0 = 1;3 I0 l. o2 v6 \8 d
- // INT1中断触发类型为下降沿触发
0 E2 v# z }7 `' i/ Y1 G w* B - IT1 = 1;
, X. C8 |+ Z/ }: N* T4 ]& O - // 允许INT1中断 X; x$ V# A* N
- EX1 = 1;
3 V: }7 ^$ l+ {# h+ ~; @ - // 允许定时器0中断/ V5 Z' H+ ]' }
- ET0 = 1;) C9 M& j+ y$ y9 ]2 Y4 d
- // 允许全局中断' u% f2 u7 I! c5 e* x
- EA = 1;
+ I) a( E: d4 Z5 F - }
. E/ p) |5 e U5 M) z - 4 W1 D, Z. v& r. s" x! y
- static void INT0_Isr() interrupt 0{1 S0 q# R: s8 `) I( Y4 k, f0 g# |
- // 矩形波次数累计归零
. Y) V4 k5 V* s - count = 0;' t. ?- p; D9 b3 y
- // 重开定时器0中断
7 ]# L% g7 u' o/ Y4 V( Q4 L3 T - ET0 = 1;$ J* f- l) S+ Z- _; f5 T
- }7 G1 [; t" s! S5 W
- 8 ~ a7 S7 C+ K5 T8 w9 b# z1 t1 Q, ], b
- static void Timer0_Isr() interrupt 1{9 P! d" X( a* m) [# F# w
- // 输出矩形波
8 B0 ~. K: {5 e0 L2 i+ A - OUT = ~OUT;% U+ P/ X0 K" `5 k$ @/ s( k
- // 累计矩形波次数+ v2 Q- S3 P2 D* U& U) R
- count++;; p5 }7 v" F5 n9 g
- // 超过250个矩形波,准备停机) [1 r: I% A k
- if(count > 250){1 q- z4 m1 J, N7 w9 N
- count = 0;
- [% c% c# S% g- c" T' W - isReadyToHalt = 1;
6 {! X3 z* B9 M3 a4 h+ n6 T* r7 c - }
' Q$ O5 T. `. |4 g E+ l" k - }
5 I1 e5 k/ Q4 ~9 v - " u, L) Y+ w0 p W- c6 E
- static void INT1_Isr() interrupt 2{7 W( q0 `( x& f2 V
- // 矩形波次数累计归零
. a8 {# b' n) C5 h: W/ K- Y2 d - count = 0;
* o3 T. C( ^( S/ A - // 重开定时器0中断9 n- h& d5 f1 ]
- ET0 = 1;
: }- A) B/ o# @9 P - }, j) _3 v; L% S& d5 [
- , `" [3 ~; G1 k6 W. }4 l; {. k9 M+ b4 `
- - i9 f* X5 c, Y+ {% z4 I2 ]
- void main(){- ^* r0 Z, y& S6 h O
- // 初始化P3和P5口
8 V- F: L h Y! K - GPIO_Init();
5 G1 F9 x" d4 a. e - // 初始化定时器0
" }: N) i% l- r - Timer0_Init();
6 e& ^6 v. b- J - // 初始化中断
+ F7 ?, a4 H/ q0 O8 e$ g, L - Interrupt_Init();
5 [) [% w" V% q# @. \! I - & ~( N! m; ^% a( G
- while(1){
3 y$ m {) C3 f8 g - // 检测电量
. n1 g" e6 \1 K) x% D0 U - if(PCON & (1 << 5)){
' g/ |$ u E- X - // 如果电量低,低压指示灯亮
$ h3 P0 N3 M: K" z0 _2 n7 j2 V - LED = 0;8 i- P: B! P' y8 c9 G
- // 清电量检测硬件标志位
/ q/ z$ {$ M k, O: I O/ C - PCON &= ~(1 << 5);
9 |1 H% g, [9 ~4 f( n7 D$ {/ p - }else{% [3 c; c; m! L
- // 电量正常,低压指示灯灭
" R9 T3 `* _, l) ^- U+ Q1 C" D3 K) C- {8 o - LED = 1;
0 g& L8 ~* v& Y$ R* P - }6 \! d1 I/ H, e. `. u2 @7 n" J" d
- 6 h7 ~ W. e' K* U7 j1 j5 L, e" W
- // 检测到停机标志* ^' F. T+ L6 j" q: K* k
- if(isReadyToHalt == 1){
! J0 c" O: Q# e$ Q% y4 A; P - // 暂时关闭定时器0中断% v7 m; v* M, z Y- r+ R
- ET0 = 0;$ n% @4 J$ Y" W- n
- // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU
+ i; Q/ b3 O7 j( i. g - OUT = 0;
1 p+ |" E0 B: Z8 Z8 n - // 停机之前先把低压指示灯灭掉,以省电
% j% r3 a( N; z* C2 U4 F - LED = 1;
3 o; ?$ [: J! ?7 _) m+ v0 A1 ?$ x - // 进入停机模式. P& h% ~0 E8 |. I! \
- PCON |= 0x02;
; P. X) ?$ p( Q$ q! U - _nop_();
% a( a4 `/ ?" u7 P - _nop_();
$ C f' q6 ^0 H4 K - _nop_();- H& D O/ G0 e$ t9 }; x+ Q
- _nop_();% a/ e0 N" T W+ a& P7 z
- // 唤醒,清标志位
+ n. f$ u4 c! |+ L0 G - isReadyToHalt = 0;! u! y# R3 V2 b4 ~" K x, `
- // 重开定时器0中断. P1 m' K8 |* t5 Q+ r
- ET0 = 1;3 y7 Z3 g1 w+ N6 x! M
- }
, t' \% Y. O d8 @ - }
# m( G, ~. n" w - }) y1 r3 i; R! Z( D: }8 |* D
复制代码
- I ]9 W7 u3 O/ f# t5 e硬件参数配置:
+ T( J2 Q, A) z) _) `( Y& }9 _6 v0 U) c8 a V
! T' M: Z7 J6 f/ s
接收端:
' @6 ^8 {% m- `- #include <INTRINS.H>
( h1 P, v' R" R2 g$ @ - #include "STC15W.h") [* [" @, V R G; s& J% L0 {
! H% @6 [7 G6 v! Y" z0 D1 K! N% M- 8 D2 n5 h% @8 ^, n) S+ G
- /** x# N! j6 Y( e# D0 m) z
- ' j' }4 ~- e" M0 K7 M" T
- *---------------------------*2 ^1 b; [7 z# T/ A
- |1 (GPIO2) \__/ (GPIO1)16|$ g1 X4 d% y! ?
- | |
( H$ N1 T( w* t2 G3 V& P; T - |2 (GPIO3) (GPIO0)15|! N' h3 ~6 A( ~$ g' j0 ?
- | |
, R+ G+ ~$ h1 N" N - |3 (GPIO4) 14|
! [+ D) r6 ^: M - | |
/ b6 @: X) }, b; K p - |4 (GPIO5) (DATA)13|* w9 d8 W. q1 L1 I5 R
- | STC15W204S |
3 {" }/ B( }" D6 \ - |5 (GPIO6) (LATCH)12|
: w; u8 b/ u$ {" |% Q% q$ t - | |
9 f0 \' l5 {) U: r# F5 m - |6 (VCC) (CLK)11|
. u2 F1 o; h1 q6 x( X t3 B - | |
& G% x- `" }' @ P" W# j3 M' B I - |7 (GPIO7) (TXD)10|5 ]+ S. b% M$ g& |$ n
- | |9 f8 C" j# d* K6 H8 J
- |8 (GND) (RXD) 9|
1 p! C0 p% x6 h - *---------------------------*8 R$ v2 A; N# S- r+ l2 i
- Fosc = 12MHz
2 }, ]- W7 }7 N
7 |$ A& q# S$ c2 X4 ^. K$ m- P1.0 -> 上( R; Q* S( V e1 [: }0 x0 h# l9 `
- P1.1 -> 左
: o; U" k$ x3 O( k2 Q# | @/ h - P1.2 -> 下; z7 F- t3 {6 I
- P1.3 -> 右
8 W8 z' Y Q: f3 Q - P1.4 -> SEL
! `1 m# f. z/ T# n! _0 O. m - P1.5 -> STA
! t4 ]0 c5 w ]% c - P5.4 -> B
& H& x7 p2 L6 V - P5.5 -> A; A+ }% x4 l+ |/ M* u; f
@! X3 s% U/ a. u0 y- */3 T- \6 y7 Q+ M( o1 v: T1 z3 `$ R- u0 I
' t# f1 J6 o- p+ J" Y$ w/ x- 4 \) w% s, p6 D4 n" b0 p
- sbit CLK = P3^2;
2 I, i2 R/ d y% [ - sbit LATCH = P3^3;
& S5 v, e3 @5 V - sbit DATA = P3^6;
# T$ b# M! Z( M) b - 4 A+ I. e& m1 C9 i }& Z* m
- bit isReady = 0;/ j& j& N9 m) g. C" J" b- H+ U
- static unsigned char key = 0;
5 l# @7 D1 b) K5 A. Z4 J5 E - static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值
) K) I# u' M) v - static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份- m* }4 o/ P6 v0 ?, w3 g
- static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中. p* h+ S: I* U* a
- static unsigned char idx = 0;
9 Y9 z/ n1 f6 |+ M0 W
2 R$ C3 X& v: i6 V; ?1 p; i# x8 H$ O- - ~6 }/ m$ r( [0 A$ _% ~, {0 m
- static void GPIO_Init(){( w- }9 B- I/ F w# m, k; P
- // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平
0 C& ^6 u) k2 |( e; N: _$ J! _) U - P1M0 = 0;
3 v M! e% e; X d4 f: U - P1M1 = 0;& ?# c, Q" `4 X1 l
- P1= 0xFF;" S+ L, e' F: R9 f: n
- P5M0 = 0;
9 ~" S7 `2 D* F( j2 | - P5M1 = 0;0 d7 v" x4 X |, G
- P5= 0xFF;
, d; t" b2 H3 {/ \4 x# { - // P3口初始化为准双向
# y* E4 |" S$ s- y5 G% p - P3M0 = 0;
3 S4 C V" ^# l- y q - P3M1 = 0;
3 \, }. l& p- W2 d" z& A - P3 = 0xFF;
: k+ O& m& b) D! @, O/ t - }
4 l/ b, g2 w5 ^# K
( J/ ?) h8 M. p1 N! d- static void Interrupt_Init(){9 A4 z( e( m8 \# p x2 R' z
- // INT0中断(CLK)触发类型为上升沿+下降沿
, B' A0 `$ j( d. j k - IT0 = 0;
. m4 @5 R2 Q6 p1 ^" u - // 允许INT0中断
o* t$ `+ q/ }& @2 f" | - EX0 = 1;5 M6 c9 R2 v5 S/ F' u' v7 H7 {
- // INT1中断(LATCH)触发类型为上升沿+下降沿
9 Q0 ^3 k: p& A5 |$ L( J1 E - IT1 = 0;& r! o4 V; W, E0 M R$ y, _
- // 允许INT1中断
# y$ M1 T4 z6 m0 _! q0 X) B - EX1 = 1;
( A: o" r% s+ k; g( S2 ~ - // 开启全局中断
5 O8 L, C) w7 f' \+ R. X! y0 r7 r" r, ^$ ? - EA = 1;& J% {% e6 _8 \2 ^
- }
9 S' D2 }7 ?. b' p - # B! K) M( [* q, G7 F- |
- static void INT0_Isr() interrupt 0{. b' ]: A) c' [; S* M
- // 只有已经成功锁存才允许CLK移位& _, }: M( ~* q) `$ p3 d9 M
- if(isReady == 0)9 ^+ D; ]1 H6 H0 u* m0 k
- return;
& o O$ f( @5 o2 N6 z - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
2 x) F' G% C+ a' |; I - if(CLK == 0)
4 o3 i' x0 [, q0 Y1 q+ ] - return;# a2 H+ F* ^, ]: ?4 g4 Q# ^: B
- // CLK上升沿到来的时候,取锁存值的下一位输出
/ d( ` ?1 v, c% Y( O - idx++;
3 ?3 \* u* ~: E0 ], [9 z3 h9 m7 w - DATA = key & mask[idx];
+ s( [: M+ e) u4 w/ H' | - // 如果已经完成7次移位,则一轮读取完成
C- K' E% f, f2 K3 B2 ` - if(idx >= 7)
: L$ f0 o4 I( N8 \* b - isReady = 0;# p5 P+ Y/ L$ O7 ` q$ Z; V
- }
# b( l3 `1 j9 V, P* L - " D% \& ?# b& n
- static void INT1_Isr() interrupt 2{
) `& Y/ o& g8 U+ D `. o& W - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
) \4 N' L4 {: l6 G( L _ - if(LATCH == 0)
- k% ^* X* Y: m2 q/ x - return;/ H: m+ L7 K- v3 N: \9 f$ U
- // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA
- Q: [' B6 W5 J* K( q" I - key = bufReady;
& N0 \5 K$ x6 Q4 _7 B% o* y - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表
& M3 P O( Y2 E- W" G- x* B2 e - idx = 0;0 m a0 ^( w5 |- R0 K- K; [
- // 允许CLK进行移位
+ ?) t8 E0 w# v2 I - isReady = 1;
0 z, I! Y, B7 h+ T& C Q - }: d- T9 F: h$ y' w8 g. o- @0 d0 S
- + W5 u ?% G+ d# r C9 h
- void main(){
3 D" y/ \% k7 W# J2 U% {1 z) l - GPIO_Init();6 r" g) [' z$ S7 C; G
- Interrupt_Init();* o5 @0 u& c7 ` {, O
-
2 ~9 D+ z4 l5 n8 l: } - while(1){
0 K& a" S' V5 V0 j2 B- r3 ], K - //PCON |= 0x01; // 进入省电模式! j( e1 W( T% x; ?5 g
- //_nop_();
% @5 b, K" X* F; ?9 B - //_nop_();* r' H- V- Q" J
- //_nop_();
# ~6 _; ?5 X E - //_nop_();
% P' n6 Z! F( c* s! O0 L - buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));
: ~( y* Q( Q7 u. e O/ _' ` - bufReady = buf;1 O) Y+ v; b. T0 E+ O; u& ^$ k
- }: E7 l2 ~5 R3 ]
- }
; T& U$ e1 O* s$ |; q/ f
复制代码
+ t0 a* r8 X& _+ O& d硬件参数配置无特殊要求,晶振频率选择12M即可。: p9 J) H" Q$ _+ ^6 _% M
: w0 Q+ G6 T. a l$ m, E& K, W这是编译好的固件。 |