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