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