本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑
5 h8 d$ T) D/ T/ Y1 g5 m, `. C& E
9 A4 t; ^, Q. N$ i没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。
* e' ?3 A8 K" I$ X
( A$ \: R9 q3 K: H5 P! S
( Y' Y A6 o: _5 }' q# L: [6 B发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。
|. l" u% Z& m+ v4 ^
+ e2 }0 B. @" x1 E4 ]买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。9 E; u! t6 w/ }+ H8 q& h
为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?
) |, x3 Z3 g& D' q然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。1 U5 o+ D# m2 i3 K% s) I6 J* @
2 Z8 \& \" `- M1 p9 R5 m% K# a9 _" k* ^
+ u! G: r/ G1 |; G& s$ u
最后把按键、导电橡胶和电路板装上去。
" o+ E* }) t. l w精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。* g# ~' e7 ]' p6 B, j6 s
, E* z ], P' c! H9 B
" b& J8 b3 V6 Q" T* o$ j6 K8 q完工!
+ \2 O! c: Z e* T4 _& |( c) A
|% A) H; Z3 C! s4 i* ?3 v4 E4 d5 l& v6 J2 U
这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。
% ^( s7 Z, V8 d+ p0 U) A- B' @: ^$ |# O+ o
9 a: y6 t4 a* j: i- s
接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。
0 A j5 _! u, F1 v9 M7 c: s6 l4 s3 [5 b6 g% k1 x3 z" E( u, Q1 m. t+ Q
; w; X0 o+ n6 Y6 I4 v- H3 R% R m' I' e. E
# R; O. \9 l h/ c2 |& H7 }电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。
6 y5 n) A' k. u# K/ i+ _& D; s9 b; r9 M' \: y1 J4 w
实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。) m* U1 D$ j! C; ^) C
, [( J% Q+ {% \6 ^) ^ g
这是电路图,有兴趣的朋友可以参考。
5 z& w& w- w- S4 t7 `* V0 K( Y) r# m( ], Q: }* F& }, p; E6 D
! U3 t3 O, t4 J& y# h& d0 b
我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!2 s D3 y2 t4 r
编译器:Keil 51。& B8 j4 n) M" k+ M* X; O# A4 h. D0 @
) L3 S* v' g6 |8 r1 M发射端:% Q9 c: Z- d$ z( c
- #include <INTRINS.H>
; q9 O7 H7 f( \: i. j2 h - #include "STC15W.h"
: I4 l) [( B( L4 W5 r6 U/ @
9 l' _5 }* p8 a; x" ?- 7 h% b4 J1 r4 U0 p4 q, V
- /*
C8 \( I, Z- S" A! P0 Q2 M6 @ - --------------------------
+ U' p3 P) X- M+ H - |1 (OUT) \__/ (INT1) 8|
b" U2 U x, G5 P" P: Z - | |
/ ^! O8 x3 n: @5 y - |2 (VCC) (INT0) 7|! Z% J+ h6 h+ a N9 G
- | STC15W204S |
( ^5 x, w; N* a( _ - |3 (LED) (TXD) 6|
( ~- a3 I! x; G4 G3 Z - | |
5 i' _ x* Z- o8 L* f - |4 (GND) (RXD) 5|3 V, e0 D5 |% N9 G; J1 ^* H
- --------------------------! L) B5 \+ I4 L
' h- r! [' ~- \$ C- h# p- LED ---|<|---[===]--- VCC
7 ^/ r1 ?3 f8 O2 S - Red 330
, j! q8 |+ Y( }+ w3 B8 ~) x0 x - ) r, `2 f2 H3 k' ~0 f
- Fosc = 6MHz
0 N$ @8 d) G2 k% Z - */* N; _1 K; o6 A
( N& m8 B7 [+ D# M- _5 f. `- 0 b' t" g, T6 g" [/ @- o2 B
- // 矩形波输出脚0 D! _1 {# I( E
- sbit OUT = P5^4;: J; v3 V' e5 e8 t
- // 低压指示灯引脚$ t% I I7 a3 Q- T$ c
- sbit LED = P5^5;
- {5 ?' L v; [7 V4 n0 P9 ` - , b* s; \3 Z( z
- // 停机标志位$ T6 L- m& Z3 w+ f; D
- bit isReadyToHalt = 0;
3 J! w% y; C2 V6 [- u1 I - # z) y5 v) J- l' ]
- // 矩形波次数累计
1 O5 w \; t6 b6 r& r" G0 M - static volatile unsigned char count = 0;9 p3 ^' t; E9 a# H: t- d
- 9 L& [& l5 S' D/ a' n2 \4 U
- . ~3 X( b2 A4 N. K, c
- static void GPIO_Init(){% |; q9 b& ~$ ?* e k8 I9 s
- // P3口设置为准双向,默认靠内部弱上拉输出高电平8 s, ]0 t$ a0 k7 N$ p$ @5 J
- P3M0 = 0;. R" p( j s$ S6 v
- P3M1 = 0;7 l: A0 M0 f3 i8 @& i6 ^8 f
- P3 = 0xFF;
$ F% a( q2 _0 z+ F" V! i) x - // P5口设置为准双向,默认靠内部弱上拉输出高电平
1 d% B8 r$ W* t* L! H5 O3 K* m - P5M0 = 0;# F, p8 w* x4 z+ W
- P5M1 = 0; D( h9 U: ^5 N# v* X
- P5 = 0xFF;+ a$ T* _& z0 n! U2 z
- }
. j" R8 }% x: s9 y& h - ( I: k1 v% e1 y% l3 B! t3 K
- static void Timer0_Init(){
# W$ q0 \+ }- ?, O6 k8 [ - // 16毫秒@6.000MHz+ |2 x' N0 f- T1 R2 z4 h4 @8 {
- AUXR &= 0x7F; // 定时器时钟12T模式$ m; I# a7 L& V. I$ {* q& V8 q' a
- TMOD &= 0xF0; // 设置定时器模式4 a* ?! w) C; g
- TL0 = 0xC0; // 设置定时初值
- ?* @: ~8 n) @5 u# m( m - TH0 = 0xE0; // 设置定时初值+ C+ B; X8 u4 U' e
- TF0 = 0; // 清除TF0标志
" D$ R5 G5 ] Y. Y - TR0 = 1; // 定时器0开始计时6 r/ g) q7 o) }! C x* X
- }1 k8 L2 Q* A1 J( x. X5 m) q
- - H/ d, M: d! O# l3 C9 E! V
- static void Interrupt_Init(){
; \: j5 [" o# X3 A - // INT0中断触发类型为下降沿触发6 C6 H; d3 R8 E+ O0 X
- IT0 = 1;. Y; J) e+ Z4 p; [6 d, L
- // 允许INT0中断
2 o; Y1 ?2 g/ ~1 K - EX0 = 1;
8 J6 ^; s$ ]; @% I% b7 c; H - // INT1中断触发类型为下降沿触发" y4 k! Y/ V/ b; p
- IT1 = 1;+ ?; ^ A9 i" z+ f
- // 允许INT1中断4 y3 u% p5 L! ?4 s& d, g9 r
- EX1 = 1;9 k7 v! Y# f% K
- // 允许定时器0中断
3 @& y. \; C( a. w0 i; S: ^ - ET0 = 1;* Y9 z0 n/ A8 h9 t6 W* K* a
- // 允许全局中断0 \- [3 d' R0 m8 t/ Y- f6 e4 V
- EA = 1;) c) m' T& r5 P3 o) t
- }
9 Z' u& ?- j, ]7 C0 S - 5 W8 H% {# q Y* _
- static void INT0_Isr() interrupt 0{
T( {4 r% r4 O7 _ - // 矩形波次数累计归零+ K6 z% ^% M7 S# X' l8 F. e
- count = 0;
) l9 G. i; f8 f* Y - // 重开定时器0中断+ J1 o! n4 k6 q D4 I3 |% T& _" b
- ET0 = 1;3 m0 M% s8 Y. ~% i; }6 x* E
- }5 f5 \3 h. E8 N# d
- % [( H! l; m6 u
- static void Timer0_Isr() interrupt 1{% J1 J9 K6 m/ z) D1 O. ^) p
- // 输出矩形波: ^' a8 `1 ^) j) E s4 C, E& n; q
- OUT = ~OUT;( d& W3 O J; R( |1 r/ H( u
- // 累计矩形波次数3 r9 D, ]2 u {0 l* R
- count++;
. L4 @/ M* C2 t - // 超过250个矩形波,准备停机
) ^0 t9 N+ U ], r& ` - if(count > 250){- n' P% o% x. T5 r/ g# ]$ ^3 W
- count = 0;5 h. ^3 V; d) r4 _, \* k3 B
- isReadyToHalt = 1;! @" ]- F: o% ^! T5 V
- }
; \: `" {$ t* V% P2 @: v( i! Z - }
4 W3 T+ K; B5 m; L
! [" f3 w- e( Q7 P2 u7 n3 Y- static void INT1_Isr() interrupt 2{
2 d9 p0 \# I0 D9 p; |/ s, Q - // 矩形波次数累计归零
* [7 V9 C- E2 c F# P- G2 U. q1 c a - count = 0;
& y8 o- g: P' s! J6 w: E, B - // 重开定时器0中断: q% V# z8 E8 A! G- {
- ET0 = 1;
% i; m8 }& r1 M3 }3 R, H - }
" K4 V# N% w0 {) p# g, L - ' D O( O8 Z, Q% O: t- G8 D; A
$ W, a u& w3 }$ u; S/ z, f- void main(){
9 N7 ~4 b9 I! f6 i7 p) F' ^ - // 初始化P3和P5口$ i w! |2 X, D* I3 n
- GPIO_Init();9 H* K# R7 g! Z, r, @7 v) O6 \
- // 初始化定时器06 A. x" ?! ]. g
- Timer0_Init();
3 @4 x6 v; `( L; G" U4 H - // 初始化中断
" p$ G3 B9 P2 z, o$ u9 K7 k7 ~ - Interrupt_Init();) f+ X/ [* d r
-
& m6 X" k. M1 F( b( ^3 N - while(1){- s; U) A/ N2 f& w
- // 检测电量
7 _5 u9 m4 q8 G - if(PCON & (1 << 5)){4 R9 _! R3 [+ E
- // 如果电量低,低压指示灯亮
& C9 k# z* k& n& U - LED = 0;9 C/ ]3 V, o4 h4 M s8 Q% r
- // 清电量检测硬件标志位
, f N/ g) R/ B - PCON &= ~(1 << 5);/ u5 p: h' U2 Y0 R. ~3 O9 i8 A" U
- }else{
O% r9 C9 c( ? - // 电量正常,低压指示灯灭
2 \7 V5 S3 H" k. @& W - LED = 1;: n3 z& a2 I) i2 Q9 S
- }
; J' z$ u1 c+ D: x" T, m6 g - 6 W; k; }3 B. a$ E3 s9 [4 E
- // 检测到停机标志
/ C7 `; V( A( O - if(isReadyToHalt == 1){9 E0 b# p8 `( o* u* k$ c/ Z# k
- // 暂时关闭定时器0中断+ M+ G' x. s* h! k$ g
- ET0 = 0;, E( u. ^# t# e
- // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU
/ f$ S: b+ t1 R5 `7 ` - OUT = 0;
! p4 u$ U3 y( G0 Y1 K5 d- y' ?3 R - // 停机之前先把低压指示灯灭掉,以省电
/ i# L' V" x. P$ |" W - LED = 1;0 r% g6 W' }/ A! {' A1 Y2 u: \" |
- // 进入停机模式
7 [3 T; j( V3 e9 |. T: R% B2 A- ? - PCON |= 0x02;6 i5 k3 b2 B. J2 T( u: @+ ]
- _nop_();
' l9 V* s0 M+ M8 e; d: m - _nop_();9 k k1 E& q2 m* U& Q; `
- _nop_();4 j% O4 W: n# h5 s6 F
- _nop_();
8 h' c5 N% X4 Y% C - // 唤醒,清标志位
' r/ g% F7 p" X: g8 { - isReadyToHalt = 0;
/ u7 |# T6 J5 v - // 重开定时器0中断7 q# _, g) j5 @+ t: Z1 w E
- ET0 = 1;0 B; ^1 Z r+ U* [$ [; }
- }3 L& X, i' |% Y8 h- F- X
- }
) P9 [7 K; x2 W - }8 V$ q# G. g1 W
复制代码
, V6 h8 l/ b! u, A) [5 l硬件参数配置:- }' K5 d' |9 T% s2 O" W% t
6 T: O5 B2 A) F
7 K/ D' \" f* b5 @; @" G接收端:
7 V7 u( i( r9 ]2 N5 R- #include <INTRINS.H>0 K$ I3 s y* m9 K+ k
- #include "STC15W.h"9 @& }2 \; C1 u5 O/ q" T+ s7 @
L5 i! |# z8 P
' w/ s; m% @* V. B- r- /*
8 D9 X$ f! d8 T) E: d4 s - 2 |* a: R. W3 |5 ?6 K
- *---------------------------*
$ O4 ^/ {- u3 \' |+ ], t' T - |1 (GPIO2) \__/ (GPIO1)16|
& D& u; w$ R8 c' N8 e1 C8 g' ?: e - | |
) A- Q- V! F, r/ n - |2 (GPIO3) (GPIO0)15|
6 n7 L, K: D" ~4 u! R - | |
8 t2 ?( Q1 j+ q$ E+ M( A- n - |3 (GPIO4) 14|+ h- ~0 J& H6 n2 A# Z$ n/ }2 u; X
- | |
6 C. ^) l3 s4 i% @. k. {% N6 X - |4 (GPIO5) (DATA)13|. Z2 C) B/ X$ `, q1 D) c
- | STC15W204S |# r3 h- N/ M" a3 s
- |5 (GPIO6) (LATCH)12|7 k3 v; ?% Q4 [: }$ o* C
- | |% ^$ x2 R: e: U. m
- |6 (VCC) (CLK)11|+ g! W1 B% e2 |! B" k
- | |9 q8 o1 r3 z" z- L0 y
- |7 (GPIO7) (TXD)10|
; b" a4 M/ e [3 ]1 j, q4 |& s: u - | |
1 ^8 k' e2 _5 W# c4 B+ Y$ H8 R8 L - |8 (GND) (RXD) 9|
% O( m4 ~- M+ o4 o$ C, ] - *---------------------------*9 _. P8 u# l) I4 }1 Z( d
- Fosc = 12MHz
L1 A) U8 G5 A- A7 w) G. b - 0 i) `3 y: r' X- H9 j; U: c
- P1.0 -> 上
, \( P9 S! U/ [. k/ x' d& E - P1.1 -> 左( T& J' h7 Y8 \8 M" E# I
- P1.2 -> 下
/ k- |, R/ t0 d5 j' G! s& \ - P1.3 -> 右
" S5 `7 @4 h- K8 u1 m5 O - P1.4 -> SEL
1 R: ]0 y. \" A0 D% D# `" l/ Y1 T - P1.5 -> STA
5 q) G1 q# y) Z( @, N! M0 d e - P5.4 -> B
% H$ B- f* s" c - P5.5 -> A
/ M G! o$ C5 f2 E' V- r5 ? - 9 R. H# w. j \8 W' P/ s3 k
- *// F% T7 r8 G, t& T- }8 g4 P5 F
- 2 V+ v: _9 T5 z
- \! F- D- E# y& z+ ]! h( Z+ b
- sbit CLK = P3^2;5 M; E! F3 J: X' G. o
- sbit LATCH = P3^3;
/ H1 Z. t0 _& \( v; H - sbit DATA = P3^6;
/ s) E6 c0 H& _9 u9 F# ^2 q - ~7 a, w+ X8 e1 [, V6 Y
- bit isReady = 0;2 L% ^- X# v3 n- V
- static unsigned char key = 0;. `5 F& j q! }. y! Y
- static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值
5 J1 V7 s5 J$ l: o: \6 J8 | - static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份' Q* W# ^6 R9 ]: w: Y0 ~( O
- static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中
& ]0 \* ~, ^- ` - static unsigned char idx = 0;/ n6 k( a R0 J( W7 q1 g6 {) U
4 E4 l+ ?2 \ U# V- 6 |! i7 U. w, H3 B, { h
- static void GPIO_Init(){" |9 P9 M& {+ X$ W
- // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平
- a+ D T+ I7 W6 b( F - P1M0 = 0;/ A% f: f c/ K
- P1M1 = 0; u2 v0 w3 V+ |) N6 Q
- P1= 0xFF;% F! u' f5 u8 W1 [: T8 z
- P5M0 = 0;
: C+ C2 _0 N/ Q( H7 w5 j - P5M1 = 0;
% N) z# w+ {, n, i2 e - P5= 0xFF;
0 T7 J( l- {4 \" W! m5 H - // P3口初始化为准双向0 Q3 w3 W; k6 G8 ?
- P3M0 = 0;
8 w" s l: l( H6 H - P3M1 = 0;" p' U* U! I3 M0 W' q0 l
- P3 = 0xFF;* |: B7 n7 g0 ~2 S) P
- }
2 G T2 T& i4 c: f9 h d# f - , v- L( K1 C' ]$ |: V
- static void Interrupt_Init(){2 [! ?# | E' R% Y
- // INT0中断(CLK)触发类型为上升沿+下降沿% m. j5 a& D3 s# R2 S- I2 k: U1 v) ]
- IT0 = 0;, M6 b9 i9 S" ^ N8 b; ~
- // 允许INT0中断' a7 W5 c$ G8 D3 |" ^
- EX0 = 1;. |0 J: Z7 A3 G3 [1 V+ _
- // INT1中断(LATCH)触发类型为上升沿+下降沿
! Y& Y# m5 w9 o2 A - IT1 = 0;
" I7 [ z# G1 x6 ]# M$ X7 K, ~ - // 允许INT1中断9 z7 f: {7 A4 Z. x- l; A' f, O
- EX1 = 1;) E9 @ b+ [+ N3 @6 [1 }5 j% x& y
- // 开启全局中断0 v: l& _2 A; L7 `
- EA = 1;- u; S0 p- e4 o2 ?( b. I- Z/ w
- }
/ x# y" m# D& H
2 f$ ]0 u* z' y& @2 ]% l/ N7 r- static void INT0_Isr() interrupt 0{- e( S% R$ e0 r* I) j
- // 只有已经成功锁存才允许CLK移位2 n' l: t: a7 T8 T% C. K7 h
- if(isReady == 0)7 y+ A o t# }1 `
- return;3 s8 L( W. W- T R( r+ f
- // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
: I7 a( L! ^% R7 y - if(CLK == 0)% ]% n2 F9 k# t" F, v
- return;
, M4 X. Z5 H. P5 A2 j) _2 l - // CLK上升沿到来的时候,取锁存值的下一位输出
5 K% b( \, R7 _3 l7 x$ t - idx++;
. J m1 l1 _( o3 S7 H - DATA = key & mask[idx];
6 } S& X0 P0 v+ Q' M - // 如果已经完成7次移位,则一轮读取完成
: I& m; G- @5 I/ f) E - if(idx >= 7)4 q. c s# C* p4 z& r' a% r! G
- isReady = 0;
2 e8 a# U1 `9 i. l* G% w' @ - }
1 B* \3 p% a. |& V& n' j6 H2 I
n0 e0 k; w; _( L- static void INT1_Isr() interrupt 2{
% Y* R8 A5 O: w8 B/ K9 S' |9 F - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断. g) I5 A' n ~0 k4 K
- if(LATCH == 0)0 d9 }2 K# C+ |. E2 d; W/ W
- return;
$ L2 G9 {+ ?: y Z% z. K - // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA# l Z5 n' U# j& ~2 H# e& n% S
- key = bufReady;
) K/ \" W3 D* U8 g - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表
3 i( J- j6 B8 w* b0 o8 y; W - idx = 0;
4 z) k1 B8 o/ i% w' e: y$ D - // 允许CLK进行移位
4 s D0 U9 {2 [' e8 G9 P5 v - isReady = 1;7 ~/ g% i j# l y
- }# A% M3 j8 X. g& S0 e; u: F
- 3 U5 e4 p% y3 F5 `/ }
- void main(){, o+ ^5 J1 B2 q7 A; J4 n
- GPIO_Init();: h0 R7 M$ i9 g! h( I9 z9 n
- Interrupt_Init();3 U, k5 l. r# b) X+ j) M
- 5 q) `! L( m4 T [
- while(1){
" o r0 u( |$ g. Y5 S - //PCON |= 0x01; // 进入省电模式
4 m" G; O9 X, H$ F - //_nop_();5 M T9 `; c" l* S- p; A
- //_nop_();: \" E9 b9 T' ]( R; d( H
- //_nop_();
( v! h7 \4 S6 ~ D - //_nop_();
! b9 P4 @# R- T7 H: r9 P7 I6 R - buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0)); R5 b0 R- c, ]$ d* U, g
- bufReady = buf;
5 m: w( W7 C9 G& Q# P! O - }, E- A4 }4 L+ O; I; W- d$ |* Z# O* I
- }
+ Q9 Q3 Y" N2 M
复制代码
. C- q+ x X" M3 o7 [; \) @硬件参数配置无特殊要求,晶振频率选择12M即可。
/ d! O) ~ O0 c
Y4 Y& ?) }7 ?9 |, x6 p这是编译好的固件。 |