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