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