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