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