本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑
7 a% O/ G4 I T/ M. N$ W5 s
" M9 r) g/ t' ^4 U没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。
6 C) O+ k( C3 D1 J; z+ q. | E) f+ U7 y5 `$ O/ }
6 b5 x2 d8 h, l$ ]$ L. S7 z
发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。
: Z! A& R r% R7 V! O/ X c) s7 j7 Q: W
买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。+ f$ i$ c) O* \4 P4 ]8 Y5 m& w+ Y# o
为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧?: z9 {& f( ~; L4 [ N$ K
然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。; q: @, ]. S2 O& @# _
+ u/ T( d D+ p4 X$ Z
5 g; k* k. A; r( k1 m# [2 M2 t
5 e# C0 P2 f7 X/ X最后把按键、导电橡胶和电路板装上去。
3 {' }: V; m2 j7 j精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。5 I V% s. i B; l+ T
8 i: G6 ?$ J. w% @4 Y$ M3 V
8 A7 Y: i+ b" ]& |) C! x
完工!9 C( s9 X" }% v* X* s/ |
& k" P+ o7 P( p9 [) Z2 S3 e
# b; J& [+ ~$ ~! n' f这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。
' e* e$ P& u) j Z/ N, q1 p7 d8 o1 _6 y$ g3 W
. T7 ? |2 d+ ~# q/ k5 n6 S- k接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。
! ^% s& |# s# Q# q( d+ g
1 n% G* o7 @4 N+ n# }
F! y) j0 c; O: C
7 W+ d& d P: \6 R1 `+ e电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。8 h# t- {9 q7 w; u
' m& S2 o$ f3 m8 F! D实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。
9 ^7 A- {: ^( u1 i- U! Y# Z1 L
; i* I# J( p; Y! r" Y0 t) }这是电路图,有兴趣的朋友可以参考。- d, S! ]( b9 E) u. c
/ f2 U2 u+ B( B: \/ T
% O5 h( Q( u* D" w3 O我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!
2 y% i% A# M! n: K/ t; O9 K+ Y1 R编译器:Keil 51。
6 P b# J" M% e6 { E0 W' E& @: W8 Y! I0 f& K5 }) e# n9 F; ~
发射端:8 A$ T: k# L- A1 |) o; O
- #include <INTRINS.H>! ?& p7 S2 R; R. T- i% Z
- #include "STC15W.h"7 J1 ]1 X e( H% J6 K' g. I& J
. b2 c; ?) a8 e+ y- / Y' j5 f7 m4 y; D
- /*% B; b) z' k6 f
- --------------------------
0 _: T, q( g! m# G - |1 (OUT) \__/ (INT1) 8|
9 V. R8 m6 l; t - | |
9 B; _ C' v5 I; e! D3 ` - |2 (VCC) (INT0) 7|
" d+ j7 ?3 a, \: R [9 m+ T' c3 ^ - | STC15W204S |( l' v: D9 y/ O/ u
- |3 (LED) (TXD) 6|
: n8 J5 ?* T1 U0 \ - | |
0 C8 O t1 V: s' Z; M - |4 (GND) (RXD) 5|
. ^; I# A0 g8 _* T - --------------------------
+ O4 Y& x) q/ t. o* q1 P0 R
' I6 }: B7 }) N9 Y- LED ---|<|---[===]--- VCC
; @5 p) g# B" e# |/ n- i; X" x2 d - Red 330
0 K% y: e* u- ] i( F* `/ B
5 v$ Z% v3 d! q' w- Fosc = 6MHz
. T" ], {5 z( S. q" R; _, `5 _ - */2 i$ x- y6 ~6 ]4 U2 k0 z0 A
. b/ H* k# x$ l9 A; X- + \7 j- f" ?4 K" K7 \& R5 i1 r. Y
- // 矩形波输出脚& s6 s+ H$ U" h* `. \1 u
- sbit OUT = P5^4;8 W& w8 W) R x1 w9 Y
- // 低压指示灯引脚
4 r9 F# P! p% R( ?6 Q2 p5 P - sbit LED = P5^5;
: M. \+ x- Y* h8 V6 X! A - ) \# z5 Z6 r9 A- O
- // 停机标志位
7 G7 B# Q; A% `4 R8 O4 A# T3 E - bit isReadyToHalt = 0;
! O( K1 w4 N& ` - 7 C% G4 Z% f& A( l# _ D+ n" b/ O
- // 矩形波次数累计, B. `/ J- U1 d# [3 W
- static volatile unsigned char count = 0;. A: `% U4 R7 i; v
- 5 u! u! O) z0 M7 A# b1 Q' b
- # E# O5 k7 R" j) W1 R
- static void GPIO_Init(){
: ?( f! f1 ]7 ~( P - // P3口设置为准双向,默认靠内部弱上拉输出高电平
- [. i& |2 K5 t! d+ _ f; Y - P3M0 = 0;/ n/ z; n& d8 ]$ }
- P3M1 = 0;
% h; H- Q9 t: R& N) A - P3 = 0xFF;5 g C4 ?) R/ k7 E. t8 v& O: u
- // P5口设置为准双向,默认靠内部弱上拉输出高电平
! R; K* l/ E# |1 o - P5M0 = 0;- ~1 D# r! d% e o8 M
- P5M1 = 0;3 D0 V, f) }+ l2 M* E
- P5 = 0xFF;$ ~0 J' ~3 P. t8 x( W
- }: {$ h3 f0 v4 _; o, T: B5 K
! r7 s/ {: |- U1 H4 M- static void Timer0_Init(){$ v6 g9 }9 N) T' I3 b- s, B
- // 16毫秒@6.000MHz; ^& }: ~, g$ ?2 l; T7 e- {
- AUXR &= 0x7F; // 定时器时钟12T模式/ B; L; A: Q* C0 u9 U5 @ x0 g& Z% V5 x
- TMOD &= 0xF0; // 设置定时器模式
! D2 b+ E( j+ }& X - TL0 = 0xC0; // 设置定时初值
, }% f0 U0 g+ x w6 G. F - TH0 = 0xE0; // 设置定时初值' v Y/ _& s/ M Y# F
- TF0 = 0; // 清除TF0标志
( u! b0 u) M9 k; a! c: ~ - TR0 = 1; // 定时器0开始计时& }) ]0 Y" p: t, r: K5 A
- }/ K$ F8 m( h9 l1 F! p; N3 f
8 Z! A: b$ U! N7 K( \, @- static void Interrupt_Init(){
' ~% z- K+ E- F1 P4 I - // INT0中断触发类型为下降沿触发
/ ~5 t' y# r; [ - IT0 = 1;
# n4 s H+ e# @ ? - // 允许INT0中断
8 S* S# W6 [ D8 H$ E - EX0 = 1;
7 N! O2 j9 `7 v5 q0 J( U& l8 G - // INT1中断触发类型为下降沿触发
3 W# }- c2 u3 V$ p - IT1 = 1;+ f4 ~1 ?# |% H. O( l
- // 允许INT1中断
1 a4 u' a J6 I0 X4 i9 n - EX1 = 1;8 ^/ i( b5 P2 @6 u1 U& f
- // 允许定时器0中断
: K# w& e/ d- S7 U( f s - ET0 = 1;. ]- V y9 q& X; h$ Q, u% X( C0 {
- // 允许全局中断
/ {1 o% e+ C0 @0 S2 F+ H - EA = 1;/ J) N: V6 i% w! a$ u% Z* g
- }7 y/ T+ S R& v: |- X0 p& B
- & f& k8 O/ ]& [1 x0 f
- static void INT0_Isr() interrupt 0{9 T/ F+ J, j1 q, S* [! N
- // 矩形波次数累计归零7 M9 t' U* Y6 F2 I) }0 E/ `
- count = 0;4 q" d+ C W9 _+ r F( t
- // 重开定时器0中断
3 F# P& e# _1 y8 s3 j, M - ET0 = 1;
$ Q# c8 }7 }0 ~. i2 k' h; j - }
5 T/ @( Y5 ]: q, T' ]6 H - 6 |/ i# Z8 w0 ]) z4 m7 |
- static void Timer0_Isr() interrupt 1{+ G' n) U/ {" e4 O$ [
- // 输出矩形波' Q+ I1 n9 n7 ^( v9 T
- OUT = ~OUT;* W# u! R- I& F1 d/ T
- // 累计矩形波次数
" y; _0 z0 F0 H - count++;0 b" [# k6 Y& i$ m" [+ E2 B1 z
- // 超过250个矩形波,准备停机& G! `. e5 j N0 {: g& h! P6 z6 Z% N
- if(count > 250){
6 c, M+ T1 \( x6 W# G% x% q: f - count = 0;
: o' O" ~' z, ^- o5 O - isReadyToHalt = 1;
2 D& Z. n& t) t' g, f! e+ \( r - }
$ X. t% M* r8 y/ r* G - }+ f0 e* s8 |* ]8 ], R$ W
- 4 K5 J e/ c( A/ \
- static void INT1_Isr() interrupt 2{: E8 d7 p8 `$ u" t2 c9 T- n
- // 矩形波次数累计归零$ T8 A) l: h; y5 S! J' u( p
- count = 0;7 W& c. ^. |5 J" R$ ?, x& r
- // 重开定时器0中断
' A& G f2 A# }4 p" g1 k, i - ET0 = 1;9 X0 I. T8 k, y" V
- }
0 P: a/ O; R/ x% X - ) a- j3 F) ]7 s; b2 y6 Z5 S
* {) l- Y, a; h- U. K1 c9 M- void main(){
0 ?0 P4 b$ B. h$ g( ~2 Z' G - // 初始化P3和P5口
* @8 {, J+ [' x6 j" t - GPIO_Init();& [ z, H* a$ T( v% _8 f3 I+ k; A
- // 初始化定时器02 C) A/ O! p4 Z4 ^ W, {% M
- Timer0_Init();. O6 m4 m6 L) Q& M3 {5 H
- // 初始化中断
" {8 ?% g' c( [ - Interrupt_Init();1 Y) C( i) _3 P f' l' f
-
' D8 [1 b) d# m+ g; d - while(1){
. W- O1 L) d7 Y- a6 S8 i) J - // 检测电量, \' T5 E. c4 K
- if(PCON & (1 << 5)){
' [/ Y4 F, L9 [3 d0 ]( o - // 如果电量低,低压指示灯亮
: A P* ]' G$ s+ N, K! c3 e2 ~$ N - LED = 0;. ]0 J6 ?% a8 ~) X& b3 e& ^
- // 清电量检测硬件标志位
{/ @6 T6 n1 q2 } - PCON &= ~(1 << 5);/ j4 ]& g: r; P
- }else{+ m' g& M! W" R, ], E
- // 电量正常,低压指示灯灭
- `, y' A0 Q) J( m6 e4 l! T - LED = 1;+ X. o+ |# ?- U1 A' ]: J; h
- }9 l# ?3 W% b9 T, u; r# {' Y6 ^0 c
- ' o. H; S1 `2 U" u, I) b- K
- // 检测到停机标志2 A3 [- h" x5 ?3 H
- if(isReadyToHalt == 1){
; ~9 X8 L8 X* F. l% `$ E: ~ - // 暂时关闭定时器0中断
2 W5 h& S9 D6 d6 L - ET0 = 0;# }* l9 ?8 H5 l' R4 d
- // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU
" N3 C* X, X3 n9 I( V; k: n% x - OUT = 0;8 J+ I/ ^. I R, R
- // 停机之前先把低压指示灯灭掉,以省电
# g; N$ ]5 L8 o" \; ]; K - LED = 1;
+ ?( [/ D5 Y/ ?* h - // 进入停机模式- R3 q2 y: ?3 g- s
- PCON |= 0x02;
& r" z5 a6 m Q1 d! C" b! j1 P - _nop_();; b5 M7 N( M+ g g7 s4 G
- _nop_();8 M. e4 W9 e& h" Y
- _nop_();
6 W( }, v& o& \! q, b9 m - _nop_();7 C4 {- j6 t5 k# s# U+ b6 q- O
- // 唤醒,清标志位( K+ ~- ]9 W. ]( z/ U7 _$ q
- isReadyToHalt = 0;* R0 v0 q7 s* F2 k/ N/ S
- // 重开定时器0中断
' B7 t% o! _4 \/ C/ v - ET0 = 1;/ D R# |7 S5 z0 f
- }% o/ z9 N: A; r% k: U; q A- _
- }
# ]8 Q0 c2 g( B' \ - }
* k! l0 w0 \# l4 d& U
复制代码
1 s( o" p& S: Z0 S0 t硬件参数配置:
7 M) X/ y9 s9 Z" G+ r
5 _" [" U& o3 e2 y1 W0 t* `/ d- h& U/ j" \3 J
接收端:
& Z' d {5 z- Q; i e1 K- #include <INTRINS.H>! {& S+ H+ K; I8 t6 e/ C
- #include "STC15W.h"
: H, H- k! M7 g( |8 |' x4 K0 m
5 \5 j5 V2 W" o0 O# X; |$ k; G- 1 u D5 U9 e6 o: W# `! s# R: V: v; D
- /*0 i/ Y% h+ t% i/ {! T$ A/ S% c
- 4 N# D- _7 B! \8 e- t$ Z4 p
- *---------------------------*
6 e7 D! X$ v5 Q; O - |1 (GPIO2) \__/ (GPIO1)16|
, c* N1 P R4 O- v; U - | |
4 X5 v& A& b2 R - |2 (GPIO3) (GPIO0)15| J" A V! F. Y) J) H( h* r9 j( c
- | |5 G) u$ }) i, n6 a4 t& W6 i, x
- |3 (GPIO4) 14|& \* W4 M- p9 p$ b/ @5 t
- | |
# Z! P" z% K' d1 ]5 a* X3 H- x3 B - |4 (GPIO5) (DATA)13|
: K0 ?2 e9 W6 z4 i - | STC15W204S |# W' r- {; Y) W* E4 B! o
- |5 (GPIO6) (LATCH)12|% F+ ^0 A. c: t
- | |
# I. D$ C. X' n, W' K% f, U - |6 (VCC) (CLK)11|
8 k. P7 _$ y) R0 p$ w* w - | |
2 R5 [) ]9 C7 h( s+ C7 x' u - |7 (GPIO7) (TXD)10|
: H) F( k5 ]0 F1 I2 u - | |
, A! j: {5 Y1 `( r - |8 (GND) (RXD) 9|
' \/ B0 ]" T) F6 H; r - *---------------------------*4 @# W/ t9 h/ g) a$ ?1 w8 O
- Fosc = 12MHz0 v# L8 @; f5 l2 b
7 B' m% g/ m" t: g- P1.0 -> 上" i$ T+ ?' v- K! @: v- O
- P1.1 -> 左
0 E- d5 H5 I- I. K - P1.2 -> 下! W$ e( C: H- z( F
- P1.3 -> 右
; x* i( h0 x8 v% o9 j$ p: k - P1.4 -> SEL" j2 T6 [! I4 b1 c
- P1.5 -> STA
) S$ f z- e# S) g5 K* W - P5.4 -> B9 P. m. {' R. k/ X; x% E
- P5.5 -> A
8 R5 T6 |7 I% p' b - ! `/ p% T# l% \" V9 |
- */
; S7 b- h K0 [3 S$ g# R+ U - k/ y! }: r$ m2 d- s0 z& Q
- ! f1 D5 W: ?, B- S+ Q% P) j j5 a+ f
- sbit CLK = P3^2;: T: X5 d5 ~8 w2 l! T8 k
- sbit LATCH = P3^3;& Y' ?7 b% u& j2 A( ]" q0 M
- sbit DATA = P3^6;
, {; B/ H9 G" Z ^. T1 g
4 Z$ k) t. e; ^% r/ S- bit isReady = 0;
+ w `% q n/ {/ J1 P- I - static unsigned char key = 0;
6 Q6 J @ @! Y+ j" U% l - static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值! I1 B9 H$ I# a3 e) |, A! F5 ?' ^
- static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份: n" O) h6 H) u8 I# o
- static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中
# h& B. y/ d6 ]. c- i: y - static unsigned char idx = 0;6 i$ ?5 x, w6 P9 z8 J
2 t+ u# z _ Q) z% z- * l" ^4 |( K2 @- ]" m/ L( V5 X; D
- static void GPIO_Init(){
$ e- i. t' ?5 U- N, ?3 L& { w8 N - // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平' E8 M+ ]5 h4 h! V. \
- P1M0 = 0;
& z8 r" J6 j6 O {: |' G - P1M1 = 0;# E" [3 k- L0 a4 ?4 T2 l3 g! A& r
- P1= 0xFF;
, g, |8 t3 u1 c: h' q) V( f - P5M0 = 0;
# \* U ^; K, R5 J. s9 |2 Q - P5M1 = 0;( k; y* ]2 c& \7 d* Q7 ^4 n
- P5= 0xFF;. ^ [- e: r I2 Z3 ^7 H s
- // P3口初始化为准双向! V. C, W. X" c$ p$ q
- P3M0 = 0;
+ f- u9 y& i% S8 S) L) q u - P3M1 = 0;# X S0 N$ C1 u( ~ d1 D
- P3 = 0xFF; r3 d# n- E) ]6 w
- }* H+ w" }4 l: k, a$ i0 w, |$ l' @
6 [( O6 q$ S: Z8 L& ~, D; g- static void Interrupt_Init(){
. p3 k L* P! Z' [ - // INT0中断(CLK)触发类型为上升沿+下降沿. e" b7 R8 |9 W/ l/ ?/ h+ x+ `
- IT0 = 0;! g& A+ R: D( {- Y* B( ^ `4 G# k9 m% p
- // 允许INT0中断
! T! k7 `0 m' }3 d; I - EX0 = 1;6 ]4 B2 E- K$ D) t) a& }
- // INT1中断(LATCH)触发类型为上升沿+下降沿# N0 }) |) c' ~) _
- IT1 = 0;
6 ~6 E5 r+ ?$ G# p) o) x - // 允许INT1中断
5 i8 k5 ?4 ^8 g | ]% U. x( B+ ?3 [ - EX1 = 1;+ K# `8 Q2 P" M$ Q7 ^
- // 开启全局中断
6 B* U9 |* `) c. ?. U& B3 I - EA = 1;
5 d: ~" O" q; @$ i1 b8 K - }7 r* D' W) T Q! y# W5 i
- - q( }( F2 I( A" I4 z
- static void INT0_Isr() interrupt 0{
* X8 z# Z2 v: n4 r - // 只有已经成功锁存才允许CLK移位
+ A2 O3 ?1 c5 K! h, B" N# B7 q - if(isReady == 0)
, \6 L8 I6 m- e! x9 y' D1 S$ I; W7 P - return;: t8 ~: Z8 k* b
- // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断* _, V4 q( i8 B, f% I3 D! S! \$ D
- if(CLK == 0); I) S1 ? P6 ~" R1 b% X4 M- [
- return;
l6 w2 W0 c% @- P* S - // CLK上升沿到来的时候,取锁存值的下一位输出
$ p+ K6 n5 s1 |- c4 y; r - idx++;
* B- ]; r) X3 P6 _! ]! _! q - DATA = key & mask[idx];
3 }4 U9 ~" w" p - // 如果已经完成7次移位,则一轮读取完成
" J( z; o% N7 V8 L9 p* T. t# E - if(idx >= 7)! V8 H6 f1 W. _+ M9 V1 ]
- isReady = 0;
7 m- m8 v1 P" x0 O" C x% g - }
. x) [$ q3 v! T
; Y- G6 g" c. ?+ y# M4 P- static void INT1_Isr() interrupt 2{$ s5 B, ^9 @1 W7 v
- // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断( [( {, Z4 s6 H; D0 Z3 H. @
- if(LATCH == 0)( O& p/ r( e& {& C x+ Y
- return;' @. f9 r8 d0 S, l
- // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA! d3 X% e3 G0 I
- key = bufReady;
" o4 u, \( @; O0 r& j - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表
" z, s: c1 h$ L - idx = 0;# c+ a, k. Q9 w0 j
- // 允许CLK进行移位4 J3 l0 ]$ u! w+ j3 _
- isReady = 1;: b8 i& H0 W8 F9 e" n8 b+ u
- }
4 i* U5 w9 p0 q2 s& \6 y4 v/ i M$ Y
' y9 ^) v% D, G0 C; Q. E8 {9 Y4 t: c- void main(){! F2 _8 s0 v! W8 I5 W. y* n
- GPIO_Init();" I" y6 [; t$ @' H6 [
- Interrupt_Init();
" d& ~7 U- G3 N7 O" A! S. d; K -
- X1 {( z9 d! u* f - while(1){
8 C/ I; j& H( ]3 x: _4 D - //PCON |= 0x01; // 进入省电模式
' @, S% Z3 N5 C( F - //_nop_();; m- m/ ?/ C8 l* R* u
- //_nop_();
2 L# L$ M; O2 M e. m2 l - //_nop_();8 ?; M/ b1 M4 o5 Q2 j/ ~
- //_nop_();
# Y0 g3 x) \3 P( \ - buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));5 c' N$ s: C$ s% p: U% a
- bufReady = buf;
2 q1 A R+ M. v5 V/ ] - }
+ I% ~7 f2 u8 c) d9 J. r - }- H# m8 F0 V% _' P/ V: k& E+ _
复制代码 ) J/ U- T+ |. H
硬件参数配置无特殊要求,晶振频率选择12M即可。, m' V4 E2 X- i0 D$ |
7 ^; S3 N9 p, b这是编译好的固件。 |