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