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