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