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