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