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