本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑
2 `. A, F0 B* d; x' R% s& m" b0 j% h8 K: A% n5 Y
没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。
, f+ Y1 i; O& k/ b9 @6 V
# c1 F; q) O ~- V7 l0 ~, V- M- T3 _( K j Q
发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。. L5 S: @; U, B/ Y
" J& Q# a1 b0 A1 W$ m; J
买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。 U# X7 m: p4 h; R% F
为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?- a1 F9 o/ e+ E- d& _0 c
然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。
* e: u* x& w1 H$ J* h. Y, x& L: e3 L0 n
* w- Z% r3 h8 ~4 M
2 e. z& K9 j, |3 T; C n. z7 i2 P( U
最后把按键、导电橡胶和电路板装上去。
- M5 @2 l3 U7 Z9 i- e精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。
- c" Y! f, q& Q- v0 u
5 {& j6 x, W* p2 I4 \% [" q8 \4 `" T$ @1 S
完工!9 ~7 Y, t% c2 @: A' |% v7 }
j; g; ?: q, T, ~, z* y: j- y2 V/ m
/ n2 `* x! q9 a2 a& J R
这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。
$ Z/ f2 h t" @# W% e% i' d% H8 q( L1 b, I& J- m, L: r3 x9 h
1 E3 ], k |3 C0 Y9 q! j4 m接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。
) n+ J0 e; p( R% U: B5 B& _& [. S5 T% B4 Q# H* N" B
: Q _' A0 I8 K# W% ~
5 S. B. w4 D& a2 c电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。
- L- r0 E6 E' |$ H* @
9 M8 h2 a) y. _实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。: G6 u) Y- o5 ^/ Z, i
0 m4 g1 R: G$ Q+ e2 f这是电路图,有兴趣的朋友可以参考。
/ p7 g$ a" \1 X8 T# c/ H
% A6 n2 w5 x8 d5 v# \+ y8 B' F
* m; D; C8 }% {: g我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!
% C2 j8 z; b. J' Z$ ?3 y0 q编译器:Keil 51。* }7 V% J2 j5 n, A1 N- D/ o
5 z7 |8 l, [; w9 j2 [! {9 ]
发射端:2 f. P& b w, [2 J5 L' l
- #include <INTRINS.H>
% m$ }8 Z$ a$ F/ L" w. o6 p - #include "STC15W.h"1 \1 ]- @- }. [% _$ z
5 L- J- X. x4 a- & `9 W8 J/ Y5 ~6 ?6 c
- /*
6 F0 b& E5 _! J k1 x, ]$ y - --------------------------, M- d& g& ~0 b2 R* J
- |1 (OUT) \__/ (INT1) 8|/ a6 t0 C7 ^8 n1 Z
- | |" S0 I6 \8 T+ r' h" M# {$ P
- |2 (VCC) (INT0) 7|% D/ R: H' K6 `
- | STC15W204S |
/ e" k) [ G p - |3 (LED) (TXD) 6|) p4 M9 I4 x2 F( V. H8 L
- | |
# Y9 F+ H& T7 z - |4 (GND) (RXD) 5|
& _1 [, x; v: v - --------------------------
& j8 j) ^. Q; a5 H5 K: t* z
) o; ^- ~/ g& t5 R: X- LED ---|<|---[===]--- VCC
0 L" s! [7 T8 e7 n4 m - Red 330
! P% L+ h/ K: R) X" W
# D9 D- K( U+ K8 _4 i- Fosc = 6MHz8 S/ ~3 h5 w3 s8 g. a J1 v. J, a
- */
. E4 p/ ^1 a& |$ o+ u# [7 K
/ L7 W( a% `+ V1 O' \+ C
; x4 ~0 N9 S6 q0 d* e% B- // 矩形波输出脚8 N$ B* r; I$ w8 \
- sbit OUT = P5^4;. p8 @- T; |6 I2 ] x* g3 Z& }
- // 低压指示灯引脚
C$ C1 \8 F; O( \/ L& g - sbit LED = P5^5;
5 ], V0 H; z d9 @ x( W - & B* V' R% _2 y5 c( P5 ?3 @! m
- // 停机标志位# f) T6 |& _, S& }5 l7 r8 _% ?
- bit isReadyToHalt = 0;: N, w9 K$ j7 t! O9 W
- : g" c6 {: D6 n6 n) N
- // 矩形波次数累计
4 K. Y& ]* {2 v: B R - static volatile unsigned char count = 0;
. }; _ y7 Q! f' z6 a - " b4 v7 W8 C7 o! F: x
- 8 u- O) [: q7 v/ v2 s
- static void GPIO_Init(){7 R5 h. N, T+ e O D
- // P3口设置为准双向,默认靠内部弱上拉输出高电平/ e/ {- b1 ]5 u6 o' H4 m3 q% r
- P3M0 = 0;# e p; f7 E4 a) p8 L
- P3M1 = 0;
& H+ G. T2 f1 X$ I8 U) }8 E. K/ k - P3 = 0xFF;
5 k! R7 T0 w1 `4 N" H - // P5口设置为准双向,默认靠内部弱上拉输出高电平 A: S' p) n9 O9 p, v$ `2 l1 E
- P5M0 = 0;1 X H4 I) z' F8 B- j- l
- P5M1 = 0;+ n4 Y$ P1 X. W+ H' {) V' F
- P5 = 0xFF;
5 k% r4 C5 h$ l, ~0 w - }
h' P; u4 ?1 O9 K! p0 X" o. s - / H/ G: r* c/ B4 ~5 v0 `
- static void Timer0_Init(){
( ?; F5 }* r$ s6 G6 ?8 Z - // 16毫秒@6.000MHz
1 Y6 Q5 ?% ^/ G$ O2 W( ]' C! { - AUXR &= 0x7F; // 定时器时钟12T模式! _# f1 x$ y# S& l2 s" n
- TMOD &= 0xF0; // 设置定时器模式
2 `+ A3 c9 A) I2 I$ a4 c - TL0 = 0xC0; // 设置定时初值
$ _- c K- n4 J- K7 |' ^' } - TH0 = 0xE0; // 设置定时初值7 X7 u( x" i5 f
- TF0 = 0; // 清除TF0标志
7 A" ^2 { A U5 ~4 Y% s# w. b - TR0 = 1; // 定时器0开始计时' k" C# C4 W5 _- ^. d9 Y" \2 |
- } h& Z! x, q& B, l g9 _
- & M9 p @1 v% a. d9 m& t
- static void Interrupt_Init(){" [3 e& I: w9 t. Z, N1 O( `( o, U* {
- // INT0中断触发类型为下降沿触发
1 N" |9 Y* i2 t0 f2 [- K( Q6 P: ?0 Q! x/ Y - IT0 = 1;' {; j+ k- S" R+ S
- // 允许INT0中断
' B0 v3 z. t2 D! {5 F: e - EX0 = 1;
2 X3 @, I" z7 f; ] - // INT1中断触发类型为下降沿触发+ V* ~8 q0 a6 M- A) r3 n/ P
- IT1 = 1;
; p, [1 F! d1 |: K8 S5 j) M) a - // 允许INT1中断
+ G- s! V& f5 Q* | - EX1 = 1;
5 u: I. q2 u! c E- M o; j8 l( Z - // 允许定时器0中断
7 C- `" f& v+ b: g1 r - ET0 = 1;. m5 J# Q. ~! f2 G! Q8 |4 f' J
- // 允许全局中断* _5 h- t" _9 B' b; ]% o
- EA = 1;
? o+ i* \2 x& q, A/ r - }) s7 ?& s% r* Q' J- p/ X
, `! S, S) t& W4 K+ ^7 ?& u- static void INT0_Isr() interrupt 0{
6 j# U' C* D9 E6 M2 G - // 矩形波次数累计归零
9 W; d p' r& h8 m: ^ - count = 0;
7 W4 b1 F6 z9 m' | - // 重开定时器0中断+ z7 P9 W* o" J% M( V; V. U. W
- ET0 = 1;# Q5 P v$ u$ e8 M, P' L# ?
- }
) ^8 N3 R3 o+ S5 @# J - ; }% W5 }1 j2 D- f9 k9 c
- static void Timer0_Isr() interrupt 1{
; O3 V. f- S8 E- T2 d - // 输出矩形波" L# c) r3 _0 Z$ ]7 R0 N: P: k3 K' h" Y
- OUT = ~OUT;: Q; t3 a' c6 K
- // 累计矩形波次数+ N5 H5 u# W" `
- count++;
5 G; k5 |' K+ m' r! r, @1 m5 u - // 超过250个矩形波,准备停机
3 l. K3 J1 v9 E% I - if(count > 250){
8 f+ P& C& n& p& I - count = 0;
6 e* O! B4 D) }$ Z - isReadyToHalt = 1;
! k3 ]9 R7 n+ u& p) U - }
7 Q" _6 N$ @* E# N2 }9 @/ \ - }
; \& j0 p( r. i& }/ `
6 C7 X. y5 _/ u3 f( D. Z# p$ o- static void INT1_Isr() interrupt 2{+ E. M- t4 ?" m' _* y) @
- // 矩形波次数累计归零
! }3 ?* M2 S8 S - count = 0;
# @- M$ T2 h* O' a- a - // 重开定时器0中断
( I6 Z2 }# r9 U: U% v! Z$ ?* M - ET0 = 1;
9 m. l# V& J t. p - }
; s; a6 D9 l+ k
" ~9 f- ?* e* \- 4 k I( q6 Z" w0 y4 o+ D
- void main(){/ h$ M# [ C7 @' ^* b" q! R* x
- // 初始化P3和P5口0 n, N7 n; ]6 n7 `/ Q+ x
- GPIO_Init();5 T. Q* k' Y) {1 @) ]2 u; n* {: r' E
- // 初始化定时器0
; S" ^/ H: h& N- r6 C9 t - Timer0_Init();) Q+ P4 w! j2 A5 ^, M; F
- // 初始化中断1 n9 Y8 B+ F) Q' V
- Interrupt_Init();5 q- ~& \" s* V- Z
- * Z& E8 t; l! e" Q: }
- while(1){! s9 p0 h1 U Q4 f, V
- // 检测电量* a/ q4 ]" w' @0 f% {' d
- if(PCON & (1 << 5)){
2 e" t% `+ V7 C( r; }6 s o - // 如果电量低,低压指示灯亮! J/ j+ S, }: {3 h
- LED = 0;* Y" ^1 }; q& ]6 h" }+ i+ e
- // 清电量检测硬件标志位
8 ~3 g/ [8 i8 r5 b# }& M - PCON &= ~(1 << 5);8 n; v: f, _) e% u P) O
- }else{
/ S! s# I! g9 `9 n4 r - // 电量正常,低压指示灯灭, G: Q8 E9 X4 r& t; v
- LED = 1;
& F- ]: s: r# I1 _; T9 Q, V' M" S - }
7 T( H/ `, n3 P$ _) T
: l D+ K: U0 R& q2 }2 R& k8 t4 i- // 检测到停机标志$ |4 p' H( C9 y4 z/ |
- if(isReadyToHalt == 1){
# A" Q! J) w6 | k- r/ h7 @! ? - // 暂时关闭定时器0中断
3 l$ E: Y! U2 v0 I8 u6 O - ET0 = 0;
" d: ?! p! S2 @0 M. t6 }: | - // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU2 n2 K, `/ [4 o6 R- N+ A
- OUT = 0;
s* _# L: z( q- t3 U2 I - // 停机之前先把低压指示灯灭掉,以省电
7 y K0 G# p% r* d - LED = 1;. g) x x( {' h& {" @4 y
- // 进入停机模式
, v W$ S. _; M+ Q: U) }. r - PCON |= 0x02;
; Y8 U* p1 _1 b$ z* ^3 P& r [& S - _nop_();; y$ z7 n) b8 r6 |7 C
- _nop_();
5 o) ]) G0 e6 W" ? - _nop_();
1 \0 S0 W8 P9 ~ s - _nop_();- x) t+ @# f% j! I% T# o
- // 唤醒,清标志位
4 r2 s" |, q' D - isReadyToHalt = 0;
! o1 b. Q! W7 C: t - // 重开定时器0中断
7 B: M' g; `. |0 `" M) Q4 T2 m - ET0 = 1;
( w8 {& j9 N; {8 X - }
" i* f j( b. P0 q# j8 R j3 B - }
F7 _7 {# ~: |, p Q3 u; r - }
6 ~9 R0 k$ k% h( X: y4 X0 P$ K" S- C
复制代码
! j- i0 ^8 ` a. @8 |$ n硬件参数配置:
5 _7 n+ T4 s0 q+ L; w! x; w4 I; m* k# L: {( h' K. p
8 T: E! x, c6 y* _6 g# c- a
接收端:
4 J% `& T- s/ a3 S+ |3 i$ s- #include <INTRINS.H>8 a2 t& J% T# o/ `, V! d* T0 O
- #include "STC15W.h"
' t! B" J. A+ R - / a, B# Y$ g% d" s: C: t
- * I6 S) x) l' ^# t' S- @( S
- /*
* Z" `7 E, j; Q5 p - + X7 q$ b$ E% q
- *---------------------------*
2 _+ ?+ }5 }3 L D. i& L$ E - |1 (GPIO2) \__/ (GPIO1)16|
8 A+ O. Z3 U7 l z6 K - | |; E+ R: w: F4 y+ ?* \
- |2 (GPIO3) (GPIO0)15|
7 R* ]% k P/ S3 l5 B - | |& l5 Z. K, z' B# A4 d$ d$ K
- |3 (GPIO4) 14|
3 ^$ [1 J2 e4 {7 N: P' O' ] - | |
; E+ A- I, ~3 m7 _% f - |4 (GPIO5) (DATA)13|
+ a: B$ T- u# X- m5 E0 {' w - | STC15W204S |% O# v. f) T4 G3 |; j
- |5 (GPIO6) (LATCH)12|, e' T4 S$ A7 W! g
- | |3 T8 W& `& y$ q! m* W5 [5 _" T
- |6 (VCC) (CLK)11|
% x. {5 Y2 ~4 _ - | |
: O2 X: m6 E1 T9 {$ Z - |7 (GPIO7) (TXD)10|
7 v4 s* h g+ J - | |
8 l* U2 ^! O0 ~# q( R+ { - |8 (GND) (RXD) 9|
, ^. [' P4 v5 |9 f" m: l - *---------------------------*
6 w2 x/ b8 M3 G2 e1 k) ^: O) S: Z - Fosc = 12MHz
4 C0 z5 p5 ` ~( P/ O. L4 V
! O7 l. e7 d6 Y' [* ~4 D! x- P1.0 -> 上
3 Z, s- P. Z) `# I# i5 o/ k1 t - P1.1 -> 左
" T5 f$ v: f/ g2 A$ n6 H - P1.2 -> 下
( f/ e4 j! K0 v* t) ~ - P1.3 -> 右/ u9 o( ~5 }9 M. g- M; y
- P1.4 -> SEL
5 j; q6 B: B& S \. [8 E - P1.5 -> STA
2 Y/ W9 h U0 U* p u - P5.4 -> B
! e$ n4 j8 p- q. g# S - P5.5 -> A
1 ~8 ]9 m a5 ?3 ~4 z* Z- r - * y# j( m9 D# u& z
- *// d& X4 M# I" t% {) C3 d
- ! P0 |' l0 i- r0 A
. k, t$ o4 H# w- sbit CLK = P3^2;
# h8 j n, B- p) G. s$ p; W8 G - sbit LATCH = P3^3;
1 F U7 c0 M) i9 h( G, C - sbit DATA = P3^6;
( W3 Y, s7 L, Z8 i# \" V2 ^+ j
$ ^5 s& Q- A4 y* i- bit isReady = 0;
; z! {6 O' [# x1 K* X - static unsigned char key = 0;: T- l% C7 `9 E% `( M
- static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值
, b1 x# `0 l9 L. o. t - static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份
% q8 p; a& ?( r - static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中
4 H: j( U7 B& P. j$ K - static unsigned char idx = 0;+ Y) Y- o$ h; W5 _
- 6 p4 l. O: Y1 q' ?; _
- $ z& n0 ]0 G1 K
- static void GPIO_Init(){) h8 D- d3 M; l' U% ~$ Y# W% y7 O
- // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平
. b2 b, t' _6 d0 s! | ?, v - P1M0 = 0;
3 V8 I8 V _( ~( _ - P1M1 = 0;4 ^$ f" Y$ U: ]4 p# ~7 O
- P1= 0xFF;2 f3 m4 N% H0 z; _# x2 V8 Z# b
- P5M0 = 0;3 I# H( e# y2 V+ ~
- P5M1 = 0;
& J. z* D- A( y& v - P5= 0xFF;
0 { q3 n0 d h4 @0 i - // P3口初始化为准双向
% s# Q/ @3 {- W% b - P3M0 = 0;& l/ ^4 O. N! Y7 X
- P3M1 = 0;- F$ D, ]- _& |
- P3 = 0xFF;* x; Y5 U/ u* `3 ~" O3 Y
- }1 p: P& d0 K' i/ O4 t% K+ ^1 m
- , d* c) }8 D& }- h
- static void Interrupt_Init(){ _+ U% a R' _5 n4 \, u: K
- // INT0中断(CLK)触发类型为上升沿+下降沿
/ O+ h8 [2 k5 ?( r/ `: q - IT0 = 0;7 q8 [* e) f0 F
- // 允许INT0中断5 r1 x h3 `/ O% F x0 a, V
- EX0 = 1;9 Y+ ^5 J6 D4 ?7 T
- // INT1中断(LATCH)触发类型为上升沿+下降沿: _4 d) D- F* i n! u( P* R; p
- IT1 = 0;* P0 T& [+ e- K& d6 b
- // 允许INT1中断
) n) V) G5 c. A8 o6 H1 g& T6 d - EX1 = 1;: w& I1 l0 A4 t G: o4 L
- // 开启全局中断8 d% ^& |+ V3 U7 l
- EA = 1;. j/ j1 W' U+ X h. b" }
- }
$ H/ x8 b9 e& i$ w" E6 C; v - 3 w+ F# n1 Y/ A1 ^% {* [$ i
- static void INT0_Isr() interrupt 0{
4 Z+ z0 j, }* u5 C( Z. g - // 只有已经成功锁存才允许CLK移位
. c4 H N8 Q! h9 `4 l; e - if(isReady == 0)
3 f5 J1 w5 o; B. M3 k - return;
2 P( v: b* C* X) | - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断9 c1 I$ z4 s. s" ^
- if(CLK == 0)
( _! w4 j. S9 ~$ B p1 O - return;
' N& L% T9 |$ Q! R* e; o7 `' _ - // CLK上升沿到来的时候,取锁存值的下一位输出3 o4 X" x( U$ `9 f. l* W
- idx++;
" @ t1 R" v- x7 x - DATA = key & mask[idx];
, e' Y: v* _; q4 N - // 如果已经完成7次移位,则一轮读取完成9 v0 o" j/ f* s- D
- if(idx >= 7)4 b) B% V% y& [3 `, k0 ]" E- Y3 r
- isReady = 0;
, }: w, k8 D6 d* t( @5 A7 B - }
* X' k" f$ l3 S ]+ x9 |1 d - # c3 k) x' M6 U' _0 t% V! }- U2 L* e( D
- static void INT1_Isr() interrupt 2{* R# V1 @/ _3 r: y! C
- // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断4 U7 ]/ x& o6 ^: y, U. R* C# V( m
- if(LATCH == 0)2 v! v" }9 a* B/ T0 g
- return;$ }/ f; b; _* Q- z- O+ ?
- // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA) x, [, y: p, W3 y, m
- key = bufReady;
$ F( ?2 }3 c0 Z1 ~ - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表: w- T+ X! A( I A. `2 o+ i
- idx = 0;
4 M' u. B' W: M - // 允许CLK进行移位2 a# D2 ]1 S* H
- isReady = 1;
. ?" b& }/ N* t U* t9 q - }
: d( r% @6 G5 G- m$ F n3 A6 e
; k1 y, ]- N* F1 o. T" U' u- void main(){* I! j# H, ]$ |- ~8 [
- GPIO_Init();& h# y% H$ H H- ]$ m
- Interrupt_Init();6 b3 Q% S b0 D) b7 [
-
4 S5 O6 \8 w/ a$ t - while(1){
8 {7 C" [/ r" L; g - //PCON |= 0x01; // 进入省电模式4 R) ?2 R7 ` V4 u( L6 X# c5 t
- //_nop_();
. r/ e; s+ X1 x; w. l - //_nop_();3 R/ }- C( h& u- c' _2 i
- //_nop_();. L! ~- r% S! m4 ]$ Z
- //_nop_();* ?4 G& T, d+ d) {
- buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));
2 g1 x. z. h$ T- g; A2 \* R - bufReady = buf;
0 b6 N( I3 u& }) J5 Z3 r2 M w - }3 f( a1 t2 [0 c# u3 d
- }
: w) }$ T" P: U0 W2 G
复制代码
5 x" J3 x; }9 g- {硬件参数配置无特殊要求,晶振频率选择12M即可。& ~8 t3 q5 T( _
% h5 Q, S& t* _% }# o" G" M1 K
这是编译好的固件。 |