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