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