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