本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑
O! L: y0 c6 h* x& L9 M2 M0 e7 Y3 g# u3 e! j. p1 p- c4 y1 f
没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。* M, `6 ~% F0 s6 m: ^) C P$ r( ^
! A$ L* R# f6 ~' \8 d) o( B7 b; p3 W9 Z9 D( o( D' _
发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。+ d, @* U, B- O) Y
) U& A* j0 d9 S; |买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。* @ I& S: q0 [/ G* j
为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?
. \5 T/ Q1 t% m: [" W然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。- _7 D. m: X) N1 |$ y
7 S0 s, K: P1 h& t# n; O( \; k/ Q: g' q
! G$ g0 @- @) V% J5 w: J
3 i) y8 {# r, j. h* h4 U5 w/ \$ q8 J最后把按键、导电橡胶和电路板装上去。1 j6 P: P- E' I& _3 K9 O
精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。
8 j1 e: }6 L5 x3 N
& i" N" f: \# R: }! t2 L- M
( x; }% j. _; z# F7 o H1 C! G! O' M完工!
/ B& W7 ^* B/ f1 X) A
+ }- p: F$ T: H* S, Q1 z
# {( S# ?5 `* S3 _4 Z这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。
" j* J! c2 `1 s
6 r9 P6 p+ ]+ J/ G9 _$ T0 o/ j9 `& M
4 x" @- D5 `. h X" x+ n接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。$ Y$ d. w1 _* ?+ R1 x. Q' B) L3 d
$ @7 b3 v+ d2 h+ \1 |
* O% p& w2 T8 m( g8 n4 X
: }0 Y( E- O, R! h& p! e. L3 H: S
电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。2 ~: _# [$ _ L1 L! A4 ^
2 e& M8 k& g' g: {0 b9 \实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。
& V* a' c9 X. J% ]* f) b4 _8 }7 J
这是电路图,有兴趣的朋友可以参考。
0 i) A h8 B% j, t4 R. B
% i8 O1 D. Y: n
8 d; T. i5 f) X+ K) K我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!! Z& r3 J1 n9 j8 O. O" e. U
编译器:Keil 51。4 V& @0 X& G: q0 h+ B; e
+ }: X' }% z4 f9 g# ?7 N
发射端:) E* F3 f3 F0 L, d+ L8 i! ?
- #include <INTRINS.H>
! r4 x6 }$ q& o - #include "STC15W.h") r* u9 ~1 _) r" p) u! k
- " `* k( w* t7 M' V, o+ O) F
: q9 r6 }; q( S8 ~: d- /*7 L% _3 o% e; | C/ R- W8 x' _
- --------------------------
. h: H9 J: v2 o - |1 (OUT) \__/ (INT1) 8|
7 o7 e0 Q. I% f& i* J - | |9 k# I* \/ y. Z0 v1 U! \& B
- |2 (VCC) (INT0) 7|* |7 c- [$ m5 N |5 l* N9 q: s
- | STC15W204S |
0 ~+ h* s _) |& G5 m& p8 F7 C - |3 (LED) (TXD) 6|
9 q6 V+ N. {$ p9 R1 D - | |7 f; X! F7 R+ E5 Y! R* o
- |4 (GND) (RXD) 5|& v x& D9 ~5 u$ g# K2 c n3 u2 H
- --------------------------
8 m+ v2 `* J* G3 \
" P6 C& l X2 J- LED ---|<|---[===]--- VCC! S+ f& c9 Z, m* l* O, K
- Red 330
: b( F% q5 X( R% f
; D; S) ]1 F- p+ V& |- Fosc = 6MHz
! z3 y2 K7 g4 Q3 T8 _3 |2 q' P+ s - */
9 y' T/ ~7 k8 \4 a9 X# ?
* f( J4 ]# m# l# t+ O4 t3 P. ?0 o- 4 N) G& V! Y) }7 a+ x# G# j
- // 矩形波输出脚
, l; v$ |" F6 `0 f' l - sbit OUT = P5^4;% y+ o, L5 K% d. Y- [
- // 低压指示灯引脚
' C! C$ f" d8 v - sbit LED = P5^5;
5 X" t( c$ \: V0 I& L - ; w3 g& h- t, x
- // 停机标志位* S6 b. C5 f1 k I: i
- bit isReadyToHalt = 0;
+ O+ i. v9 ]' H0 H! G0 e; L - 9 {6 a9 q4 p7 a8 t9 M3 b) \8 |
- // 矩形波次数累计7 H/ F+ i6 [) A# r- A8 \7 _
- static volatile unsigned char count = 0;
7 e: F1 e) w( e, Y6 z
) Q8 r* p$ U5 `) F- " s& C) j8 d: s8 ~5 e( n/ H
- static void GPIO_Init(){, h, U2 K; F6 m' U. ]' W
- // P3口设置为准双向,默认靠内部弱上拉输出高电平 s+ \7 R$ K+ V
- P3M0 = 0;
0 S! s( L9 y$ ~7 Y - P3M1 = 0;* ~4 z4 u6 B1 j( z1 q# j
- P3 = 0xFF;8 s4 R3 W6 F$ N
- // P5口设置为准双向,默认靠内部弱上拉输出高电平
$ a# X) R, j: g - P5M0 = 0;
! C& N- d, D$ V - P5M1 = 0;
8 E. ~. w) ^1 z* k, q - P5 = 0xFF;9 Z* J. A. J1 @; `! J9 T0 {
- }, I- ?4 L6 B! ^% k0 @$ ~/ c5 ~+ Y
- 8 S& J. c7 Z. s; f: g
- static void Timer0_Init(){
4 } ?* g0 \6 A; t - // 16毫秒@6.000MHz
; o+ U2 g: x- ?2 q- i) q7 S - AUXR &= 0x7F; // 定时器时钟12T模式% j) g5 W( F/ m0 B: x4 s
- TMOD &= 0xF0; // 设置定时器模式
+ N5 ?* ] c* y: G9 ] - TL0 = 0xC0; // 设置定时初值& ~* g7 f( F9 F" P
- TH0 = 0xE0; // 设置定时初值
4 r" ^3 }7 E! K H! f& U - TF0 = 0; // 清除TF0标志# H6 f. c! S4 ?; ?' l$ t, G
- TR0 = 1; // 定时器0开始计时
" Y. L6 J, O# c5 V/ { - }9 P. E6 j3 z/ c5 O4 H2 G9 ] C9 c; g7 q
: p( S2 @( W0 ?% q- static void Interrupt_Init(){
8 G6 `; p/ v1 W - // INT0中断触发类型为下降沿触发
) W- m" S! a: H- q - IT0 = 1;
' e3 ^2 ]/ o" @ - // 允许INT0中断* }' x$ `; A' q- x8 `
- EX0 = 1;
; c/ g0 u3 G( W) z( e - // INT1中断触发类型为下降沿触发& Z1 X p$ t6 Y9 U
- IT1 = 1;
9 X/ a2 R: @; C. `4 y4 d - // 允许INT1中断
1 ]4 ? D' h8 Y8 t3 j- A+ N6 ] - EX1 = 1;* j5 v0 R7 f+ R% h
- // 允许定时器0中断
. g- u: F, Q2 c; U+ S' K7 L - ET0 = 1;' r4 [) a% U8 B1 d# Y
- // 允许全局中断
8 e9 B4 j$ G, p% V0 d; F4 v - EA = 1;
; `/ H& a, s( I# {# p( k - }
# P5 Q! R* u. C% L$ Z - % H4 \9 l- i6 Y& P6 p7 {2 q
- static void INT0_Isr() interrupt 0{, l9 [# E1 o$ ?1 s- S$ ^
- // 矩形波次数累计归零# R# d, |; A4 v
- count = 0;5 e) H6 @& p: L8 D& w
- // 重开定时器0中断
" ~$ ~, t! I' ?; i9 ^ - ET0 = 1;
+ U) ]1 z9 z7 a6 y - }
5 f) W/ V7 c2 B
6 c4 P0 D0 Y6 O; f5 U- static void Timer0_Isr() interrupt 1{' V+ L$ p7 q& e0 X% u: z
- // 输出矩形波3 P6 Z2 \0 e2 M/ x8 }2 ]
- OUT = ~OUT;4 B% W2 N! U% D/ \9 o
- // 累计矩形波次数
: o. n0 [& b. P' N1 Y/ l: e* t - count++;7 R5 z* p- ?* E! r' r: ?
- // 超过250个矩形波,准备停机
$ d$ A* y/ L" F# h" \2 ?/ K - if(count > 250){
7 `8 z( H0 h3 v# F - count = 0;
! w- A8 U' z* i+ k2 I - isReadyToHalt = 1;
. d6 ~/ M7 }4 R; n! J* P9 R - }0 H) I u7 x( I' y
- }
T# e2 f( `/ @# D( X, f% N, h
' D3 x& @2 N$ M% A- A% r1 {- static void INT1_Isr() interrupt 2{" s$ f. a0 S0 X' u& h, x
- // 矩形波次数累计归零' n6 v: i: ^2 L2 h+ Y
- count = 0;* D* M; n; e1 p+ }: J2 f
- // 重开定时器0中断
+ i( I. ^1 q a9 j5 m$ w - ET0 = 1;3 E. D; p' ~% m6 p1 f2 S3 m
- }
/ H2 E R! |7 U8 Q& i - 1 \6 Y" B+ T8 M7 w9 M9 y1 D2 l
1 w f4 d! u# E# ?" h- void main(){) A- L- k6 L8 o9 i0 L
- // 初始化P3和P5口$ A8 L$ r# U7 {) v( o$ U. b
- GPIO_Init();9 z/ a+ A6 R: t) |1 B% l. f; P
- // 初始化定时器0
# o" X9 `4 B6 k* P A - Timer0_Init();# ?! A6 ]/ Q w4 Q) K3 Q- E
- // 初始化中断
: J) y9 A* H" X, O* I - Interrupt_Init();# K1 a/ i8 a0 Y% W9 c; g9 ?1 ]' A: }
-
f& @7 K! n$ p4 S' j& ^. h - while(1){
) m* L7 D; m$ ^/ f - // 检测电量. B8 f6 K+ s7 h: ]! `3 X
- if(PCON & (1 << 5)){
7 ?' }8 ]( @0 |$ I) H4 O' ] - // 如果电量低,低压指示灯亮
" Y- O/ j; r4 y - LED = 0;" q( T- x2 ]* L0 Q; s
- // 清电量检测硬件标志位. k9 H% K. r/ P0 c
- PCON &= ~(1 << 5);! P0 r- B( c, g+ d
- }else{
( q/ _' I# t9 q0 A% p' b! G. Z - // 电量正常,低压指示灯灭
- H( e2 `6 \; z) k& R9 w2 v - LED = 1;
! d. e. r, k9 r1 l; u4 w: R - }0 ^% F5 m, T9 b
- & a! `/ h/ j F. B1 E/ j8 ^
- // 检测到停机标志6 T7 H- m. N% y- S
- if(isReadyToHalt == 1){
% g5 g1 J& _) s D - // 暂时关闭定时器0中断7 Y2 h/ P7 i% j5 T3 J
- ET0 = 0;! F, M0 L$ l) r% H
- // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU
: r+ q2 c/ q9 { N4 t- e& M; a7 i4 f+ X - OUT = 0;
; L2 W0 a% W- q; j5 m. O - // 停机之前先把低压指示灯灭掉,以省电 e1 c2 R, q. I/ V! F
- LED = 1; p6 N' Q4 S5 M, v% R$ W3 m4 U
- // 进入停机模式
. s/ }; S6 N: l# _; } - PCON |= 0x02;
( `. Q* R6 v! [6 Q9 `& f - _nop_();
8 F9 R# \3 r3 w& S! r$ g, C - _nop_();
2 }+ j- q/ t0 S4 | - _nop_();1 N: a$ p4 F1 n) J! G' k3 [
- _nop_();
6 q \9 O/ A4 t# u2 y - // 唤醒,清标志位
, W$ f# j4 C5 i1 q2 [ - isReadyToHalt = 0;& d( W: c8 @3 H7 g; T
- // 重开定时器0中断9 B i4 G, L- Y5 p2 [ ?" I
- ET0 = 1;
5 n: r: L, b/ M1 A& M; J, v$ u - }0 p5 w+ D( c- ^7 E
- }2 Q' o) J+ V+ C
- }) b# p9 D$ }, K4 c' z9 @$ s
复制代码
1 M" x3 Y; a) m% t硬件参数配置:
5 d# G1 n# f! E) m- X: H3 G* `5 o4 r* ~$ O( m
' U2 g, W( o5 q. Y
接收端:; ]8 y' ~4 T6 W
- #include <INTRINS.H>: t8 X/ z) M7 E' _5 h
- #include "STC15W.h" k( {% n+ t6 Y+ B4 ~
! _: m' b% j: P R! _) W! R- ( T7 A9 z2 W; `4 g/ \& i
- /*2 o/ x" x4 M& q1 J: T9 w
- 9 j# A, X; w. n6 A: g
- *---------------------------*
* S, f, H9 [2 n1 J+ B6 m - |1 (GPIO2) \__/ (GPIO1)16|, D3 x* A+ S$ a0 P$ W
- | |6 Y8 t$ K& B* i6 w
- |2 (GPIO3) (GPIO0)15|
6 D/ ?; [% L" T m9 l8 p1 ~ - | |3 Z1 A- L0 K( ?, k! t1 @* g0 `
- |3 (GPIO4) 14|
! W3 Q! |/ h3 U. e9 g - | |
0 T, p3 u1 L* D) j - |4 (GPIO5) (DATA)13|9 H; M8 E8 C7 K W
- | STC15W204S |6 s) \/ {% g% u) T3 R6 h
- |5 (GPIO6) (LATCH)12|; L( Q+ C1 [+ w+ X' h
- | |9 Q+ {$ i& V, X+ d: v
- |6 (VCC) (CLK)11|
* I* u% R+ m# B8 Y - | |
* u. I6 |8 F4 D. d, i* [8 ]9 Y3 S Z - |7 (GPIO7) (TXD)10|+ I7 ^0 o$ m; Q5 \& ?: @+ e
- | |
% p# s* E6 T; A# _3 \ - |8 (GND) (RXD) 9|
- j! q/ [: ~; k - *---------------------------*
1 E4 m$ k! a- N3 v - Fosc = 12MHz
h# O) a; K4 t - ) I* f' [& ?# K% t7 G" y
- P1.0 -> 上
0 g7 m9 f) l/ |- } - P1.1 -> 左3 i# y) g# P4 N4 y
- P1.2 -> 下
) c* h2 m. B$ x O4 h - P1.3 -> 右3 l- h; x2 w$ [; J9 y
- P1.4 -> SEL+ q! I* K: i, Y
- P1.5 -> STA
! z% c0 `* t1 Y8 {$ }. y - P5.4 -> B }: ~; [6 t8 u `
- P5.5 -> A, R8 C- g* c1 j# k* G
/ o! A# [( b# A( w' W9 h- */
0 `% C+ G7 y( Z+ P, [4 F - ; C2 K: p1 @( Q9 ]# `
3 ~0 O% P. X4 V7 U C- sbit CLK = P3^2;
2 D h. c- k5 J. g9 y - sbit LATCH = P3^3;
( K1 b j. J0 l6 i+ ?! y+ i( y# u- @ - sbit DATA = P3^6;: ?& k0 q& S2 m# }7 h
% X- `! Z1 w, J) b5 b: n" M- bit isReady = 0;
/ J4 y: F/ Y/ K) ?( b - static unsigned char key = 0;3 T Q5 p9 T$ x1 K
- static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值
) v i# G) H$ _9 a' ] - static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份
. {: Y, s( S# H `; {9 n - static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中
2 P/ u8 C/ q8 P, l - static unsigned char idx = 0; M8 _$ m' S! b% g/ j
& u, h: ~& n- d: P) j i0 F d- " [& }' k! p2 B: L! w4 [: }% Q% y
- static void GPIO_Init(){6 W( V$ ? ^& X5 x
- // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平
( U/ q8 K4 ~& I* Z" x6 g/ e, J - P1M0 = 0;3 _7 l& i b7 h0 D% e* s7 R% g5 Z
- P1M1 = 0;
- z1 }. J6 Z5 j0 _' ]" ]2 _. J' N - P1= 0xFF;2 |! q+ i+ h& E; W) Q" v/ S
- P5M0 = 0;
8 z. q1 [, Q X( S" ]7 f - P5M1 = 0;# x; v: z5 g" ?# d' y
- P5= 0xFF;) K. U* |8 `1 }4 C, T
- // P3口初始化为准双向$ a @; T" k" l
- P3M0 = 0;
0 o, i$ s0 j- U. q - P3M1 = 0;0 c0 Q9 R# F. E+ T* t4 a) O
- P3 = 0xFF;4 R5 a: C( O9 f" ?! q4 _
- }6 `/ f( Y x& D) Z+ j7 U
- 3 g D/ E |" k( [ |( e/ C' P$ k. H
- static void Interrupt_Init(){
E- M: }" ?( S - // INT0中断(CLK)触发类型为上升沿+下降沿
0 c7 k: v: X" G) ~% F - IT0 = 0;
# m, k9 W2 G8 I0 l- a - // 允许INT0中断2 ]( _1 `$ t& w( |( r8 R
- EX0 = 1;
% z" r& H1 j* [1 z+ D0 u r6 [8 Y - // INT1中断(LATCH)触发类型为上升沿+下降沿
' ?: v8 y; O7 M3 X9 M4 y3 e - IT1 = 0;
9 c* H5 [: S/ F& q9 G; ] - // 允许INT1中断
: O. P! `3 d6 J% V - EX1 = 1;
- t+ j; G: Y& h( _! x - // 开启全局中断
( @+ W+ ^: R) X) o - EA = 1;
; y& d7 t J0 i8 j& S$ Q - }
6 c1 r1 s" g) J1 h - 7 D! j$ G0 _$ w6 M! i. K
- static void INT0_Isr() interrupt 0{
5 k) r/ X, S6 i6 j5 E' M! c' T8 k2 v: J - // 只有已经成功锁存才允许CLK移位) b' l \# d0 l8 h+ s2 C
- if(isReady == 0), c$ M3 r2 y5 {
- return;1 u! O/ Z- z) e. ?
- // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
1 \7 [' g" n( a3 f" Z/ Q - if(CLK == 0)
2 F" C; c; ~# T% [' @, D - return;
2 c" ?; R$ x. Q9 S( c4 a2 l - // CLK上升沿到来的时候,取锁存值的下一位输出' P4 [5 {/ C! N6 }
- idx++;3 Z3 ~3 {5 n; l7 K- ?. m; b
- DATA = key & mask[idx]; h8 y# o0 I. b& m5 ~1 j) Y. n
- // 如果已经完成7次移位,则一轮读取完成
, p/ k# A( p! u - if(idx >= 7)6 A6 c F$ r! Y+ Z7 U
- isReady = 0;) X5 U$ D8 B, S, f" f: f$ z
- }; f# r# I% F+ D. c+ y! Q* {
- # A$ i. B! z$ p7 w9 E% U
- static void INT1_Isr() interrupt 2{
' G6 Z: G% \7 x( n - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断: H, Y% ?% v1 I- i) U$ e ]. ?" B9 M
- if(LATCH == 0)5 {$ y* a4 {9 N" d4 ?
- return;/ `! Z8 L% M2 }+ z- V: k5 n) {5 j+ c( z
- // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA# o& e2 T7 s, b" l
- key = bufReady;
8 U9 j2 R2 V! a3 f; n; ~ - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表
6 j0 ~" R- F$ { - idx = 0;7 g( A% M! t& h/ v: R. ]$ J
- // 允许CLK进行移位
7 l- U0 D5 r* i - isReady = 1;
0 V$ V/ t) R, |3 O - }0 L1 M- @& d3 g; i
- # J4 z. L9 s8 Y$ H9 h0 o
- void main(){
+ ^9 w3 O# y2 M5 m" O - GPIO_Init();
# C4 B6 m: p$ i - Interrupt_Init();
% R; o+ W& o9 L2 L5 I* M5 X9 p -
1 F0 F+ z. K. X( |, y - while(1){, E4 ]8 T4 y' I. J! ^( ?. `: k
- //PCON |= 0x01; // 进入省电模式& J4 Z7 ?6 W2 T
- //_nop_();
/ H7 ?9 y3 P9 h0 o - //_nop_();
3 z0 n. D- g1 U" ~8 M - //_nop_();
. `( {* z2 R4 O, l0 \8 {( W7 V) }3 f - //_nop_();, y+ I" [! `( Q) w t
- buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));
+ U' w- g0 c) \# O* U8 h+ A& | - bufReady = buf;9 M5 R" C# s4 Q: J& I
- }
9 v5 H7 i, _$ n; m' x - }
% X* N" g5 G5 C5 L2 W" b
复制代码 & ~" i7 a. X7 L0 p& b1 _; K, v! A
硬件参数配置无特殊要求,晶振频率选择12M即可。
4 g7 g( ^) D% p [ F* u8 d% D' ^! t# A6 m
这是编译好的固件。 |