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