本帖最后由 雷精灵2046 于 2019-6-14 09:28 编辑
: X* K4 ]8 X x6 e: c
7 H+ L7 p. O2 U/ n2 j7 x) w没有用复杂的分立元件搭建发射接收电路,直接用的现成的2.4G无线模块jdy-40。
. C5 T) Q8 W8 Q9 F' `9 h1 i8 t/ N0 F& U& O# J6 d
: `6 c! R8 q; `$ g0 Y发射端靠3.7V锂电池供电,用了一片SOP8封装的STC单片机,负责产生连发脉冲,同时负责检测电池电量,电压低于3V左右的时候触发低压中断,低压指示LED就会亮起来告知该充电了。平时发射模块和单片机均处于睡眠状态,有按键按下才会唤醒,从而实现省电。实测睡眠状态下整机电流约1~2uA。板子洗了一下稍微干净了一点。因为对于微功耗设备来说,焊接残余的助焊剂可能会增加漏电流。, e. ^3 ]$ f4 G: _5 V7 |
! q# |- ]3 F$ ]; e0 @
买了一个火花的FC山寨手柄,去掉里面的牛屎板子,并修正一下塑料挡板,把锂电池和充电模块装进去。3 v9 z3 y% B) m3 K
为了减少体积,充电模块裁剪得十分小,并且更换了充电电阻使之适应这个小小的锂电池。这么小容量的电池,也不需要多么大的充电电流是吧? V6 ~$ m. Q7 R# s. q
然后统统用热熔胶粘上。充电电流比较小,所以发热很少,不会熔化热熔胶。! l7 L. m, I5 d; v$ c
# |: E( E3 R, E; H0 {; }& ^/ _5 }9 [" h8 ]2 \ V6 C0 E
* V/ u. V* H2 Z, e
最后把按键、导电橡胶和电路板装上去。
% c* o/ T2 n+ T* ^& T q精心裁剪了电路板,正好扣上,不会阻碍其他元件,也不会影响后盖和拧螺丝。, R3 b1 \6 z" O4 s
% J. Z/ M9 V; [3 p
" h9 v g7 t0 @
完工!! i. `7 i) T7 d( o+ b: t. o
9 t% W8 e. m/ z7 Q
8 M0 _% c3 q! M( j$ e$ f这是充电的MicroUSB插座。旁边的小孔用于透出低压报警LED的光线。4 X4 ] N+ O/ ~# G+ y ?& K% V( x
) m. e; r/ E. [6 b( ]% t! e8 {: B
7 P1 {7 J9 A# X/ ]$ G- n接收端用一片DIP16封装的STC单片机模拟CD4021。为啥要用单片机模拟而不是直接用CD4021?因为jdy-40模块接收端在有按键按下的时候输出高电平,无按键按下输出低电平,和普通手柄正好相反。当然我也可以用两片74HC00或者74HC04之类的逻辑门进行反相,但那就增加了芯片的数量。对于FC手柄这种工作频率并不高的设备来说,STC单片机完全可以胜任模拟工作。
8 E; h' C0 [, X% D! m: C2 m( N' d& r! |) }, o% y8 F
1 A' `; O* T7 d
# H. W) g" N+ `% R/ Y电路设计有点小小的错误,所以有几根飞线。原本设计使用低压差LDO是XC6206,结果在做这个接收模块的时候,买的LDO居然找不到了!无奈只好用了AMS1117。幸好对于接收模块来说是5V降到3.3V,1117可以胜任。要是发射端手柄的话,3.7V降到3.3V,那就非得用6206不可了。
% q* k( C( K$ s
. x' e: t1 z }! p( F! b实测十分灵敏,延迟极小几乎感觉不出来。可惜这山寨手柄的手感并不佳,尤其是方向键,软塌塌的。看来我得买个剪线手柄改装。4 `2 Q/ V+ P$ w& w8 [7 O- J# b
* O; @( |; H4 L' r9 y* a/ ~; I8 C
这是电路图,有兴趣的朋友可以参考。
, @# E' N! i3 n' `. P4 ^
/ B4 u/ w- {; @% }5 ?) ?& \5 p! H% |- O7 u5 r! l
我信奉开源主义,十分痛恨把技术藏着掖着。好东西大家分享嘛!所以两个单片机的源代码大公开!5 V$ l( D7 p9 d
编译器:Keil 51。$ m/ A6 I6 x, O
: n, k1 _$ ? B% }7 n8 b) B发射端:1 `; R2 J# c6 r
- #include <INTRINS.H>! }- M7 C" y* a" \: C2 E
- #include "STC15W.h"5 l+ w' w9 j! Y3 p
- " m- f3 {9 u. P' d3 y5 r1 R
- 7 h/ L7 x+ j* S; ]. c
- /*4 ~( R4 w" }7 U) B
- --------------------------7 Q) B3 C. a" c( N/ X
- |1 (OUT) \__/ (INT1) 8|
+ ]8 f" }4 P* z& x6 ` - | |
8 E' P( r4 _, } - |2 (VCC) (INT0) 7|
+ B4 }- L0 O) m8 p - | STC15W204S |
- {( T. I/ o5 R9 ?: [ - |3 (LED) (TXD) 6|
' O L e5 o) {, { - | |
: b0 z& y9 }. @# _# j8 h& V0 x - |4 (GND) (RXD) 5|! _1 E2 N# d' E& |; K2 N" c
- --------------------------
b* |. S( U" r - ! m; W4 ^! g( `* y, {6 I9 `
- LED ---|<|---[===]--- VCC
. @2 W! v# e; n/ A$ N$ E9 w( A p - Red 330
: [% f7 y2 {6 H4 x+ ~ K2 | - - Q: n2 ^* g5 C+ j. }: M, }
- Fosc = 6MHz
2 S$ C7 F5 V: K* K/ d - */! R w4 N5 @0 ?) S- K* `8 j
- ' C) I0 c3 V3 U; x
s% O8 U+ }7 q/ C6 c' w- // 矩形波输出脚
: G0 M% z- E) y z: G, U+ w - sbit OUT = P5^4;
, A8 }4 v, m) n - // 低压指示灯引脚- }7 m7 V4 B, o: T* w; D
- sbit LED = P5^5;' y+ N- P; b. U$ F5 F( I
# A C2 D- M$ L( h& O# x& A2 T- // 停机标志位3 q4 i( P: T, D" Y9 Z
- bit isReadyToHalt = 0;
. T2 D6 z S9 _ - . ^' r ~* n! N7 ?3 V7 L
- // 矩形波次数累计
* A* q/ [- N4 t. k2 P - static volatile unsigned char count = 0;: e5 ^* T+ g. j( @& @ t7 g6 F
- % g+ k4 F' w- q# c5 G! y8 p
- : q; r) y3 w( I$ B4 e. p. I
- static void GPIO_Init(){! ^7 R+ @9 y% `1 n+ C( z
- // P3口设置为准双向,默认靠内部弱上拉输出高电平; n/ Z+ N1 D; R: N& e
- P3M0 = 0;+ g' U1 l) s$ C( h( I& a
- P3M1 = 0;" Q. Y% x; z/ @! T+ w0 Y+ D+ L* w, x2 K
- P3 = 0xFF;
+ W) ~" ~/ D H D) m - // P5口设置为准双向,默认靠内部弱上拉输出高电平
' A6 p* p6 V4 T3 k1 x7 y - P5M0 = 0;3 Y; | Z, b! f
- P5M1 = 0;
+ |9 N! o! c+ {( h6 C7 x) L7 Y: @ - P5 = 0xFF;
& J: O7 z2 o6 v9 S& V - }
; y7 Z: @+ T) q. C$ I - $ j8 d; |6 u: l, U
- static void Timer0_Init(){
9 W+ T: s& r$ R" P9 u4 G - // 16毫秒@6.000MHz S7 N/ G. r/ T
- AUXR &= 0x7F; // 定时器时钟12T模式/ e$ u# } q7 d- g) l
- TMOD &= 0xF0; // 设置定时器模式
0 T2 W4 Y, ~8 ]; d - TL0 = 0xC0; // 设置定时初值5 V6 ~3 f( m, N9 S2 [
- TH0 = 0xE0; // 设置定时初值
" |, B2 g: }7 D3 g - TF0 = 0; // 清除TF0标志0 A; t! |3 y b# _3 s6 D' q7 N
- TR0 = 1; // 定时器0开始计时
: g! ]- ?2 e1 a- N# g - }
8 @, i8 o7 y* e" q2 `( M/ ~
9 [5 f* V" ?; n; c- static void Interrupt_Init(){: \" `& P4 D5 K+ P4 F) j- l# r1 r+ V
- // INT0中断触发类型为下降沿触发
( ~, j; V. ^; U# ~0 G" \3 I/ I( J - IT0 = 1;; T' K2 V- Y7 {+ X" y: q2 M2 ^# s! I
- // 允许INT0中断3 N0 J! X7 | E2 A: o* a
- EX0 = 1;
3 e3 }1 f8 W; N$ I' d - // INT1中断触发类型为下降沿触发) }6 D: m+ T, p/ v( |
- IT1 = 1;
& ` u" Z# o$ K1 ]1 z - // 允许INT1中断- ]5 H' w* b8 D7 I
- EX1 = 1;! p4 L6 P v3 y; T- l1 T
- // 允许定时器0中断8 d) m2 L- S H' G: i( B9 O
- ET0 = 1;. T5 k+ d6 O" w
- // 允许全局中断
2 t( X& p6 J; N+ W9 V) m - EA = 1;/ H3 [$ x. [$ }2 S4 |$ i9 Z5 `
- }
9 ?9 C' [2 \. d) a$ l' w - 5 M: D4 c1 C( ]+ C/ M$ V, k+ f+ z' n
- static void INT0_Isr() interrupt 0{
7 s3 b5 ^+ v+ Z1 K! \) ]- ` - // 矩形波次数累计归零9 V6 i$ e4 u7 g* m3 c9 y/ [1 Z
- count = 0;# e f: ]$ i" \( j+ u" P
- // 重开定时器0中断
9 |1 o2 Y! @+ b; o. b2 {. Z - ET0 = 1;
9 e1 {) Y2 g" G f' i- v8 s - }
# R; b U3 j3 c0 C! H6 _
! D: T% K" o7 W0 G- static void Timer0_Isr() interrupt 1{3 q4 _( u9 Z7 R! S, L5 Q
- // 输出矩形波4 A S8 H# [# y1 ?2 O
- OUT = ~OUT;$ W. u! c6 \( _. P/ _ Z7 h( {) G! g
- // 累计矩形波次数
) H, `9 v2 ?7 ~: j# Q - count++;( G' t6 t$ d5 s" \
- // 超过250个矩形波,准备停机8 Z- ?+ ]+ s% E D& }, v
- if(count > 250){
5 @ F$ g$ s. N: O - count = 0;
% f. p; P5 a6 G: _ - isReadyToHalt = 1;
1 D8 D4 N9 A) T' Z- p, A - }- _, U2 \ w$ M2 D/ [- C
- }- O$ v) B, R# M6 p
- 5 \$ h, v) S' Q' C( A! ^6 B
- static void INT1_Isr() interrupt 2{! u- h/ o5 X; n) T7 D: k
- // 矩形波次数累计归零6 u6 k/ y' n7 x8 R% I
- count = 0;
3 b; s/ \7 r+ }4 M) K - // 重开定时器0中断
6 ~- U' X4 U' j. [; s - ET0 = 1;
1 Z2 P4 n+ J; e0 J) I T - }. ?7 Z8 M+ t' v; [' a/ W# T
, ` ]6 e6 T& T/ x8 q% r6 ^- 9 U5 C5 {- a T' D7 F. }! D/ V
- void main(){
5 o0 `/ J3 [& {4 z3 Y - // 初始化P3和P5口3 j9 k0 ]. W4 l. ?& ?
- GPIO_Init();% U6 I! A5 f; d3 Q+ G, i. s. ?5 C. M
- // 初始化定时器0
6 A+ V4 q3 Q5 O" M - Timer0_Init();: f- m$ n% y3 Z! U, V+ ]
- // 初始化中断) V: y; K" c7 A3 N
- Interrupt_Init();6 I2 i. [8 A; ?' c* Y& W: F
-
8 G. q) F; t9 D7 B2 m - while(1){: p4 U: A; z* `8 e& t( C- d
- // 检测电量
. [( A \( ?7 v& @4 n; R - if(PCON & (1 << 5)){
9 d- A9 ~ @, x* t: X& l7 z0 `; } - // 如果电量低,低压指示灯亮0 w# H2 u, X2 `4 w( {
- LED = 0;
0 `4 }7 v( z5 d v( R9 _$ ^- ~ - // 清电量检测硬件标志位
6 f/ {1 [' p4 X" k* E - PCON &= ~(1 << 5);9 Q; S' d ]# R: ?
- }else{
' U3 j; D5 i3 R9 D9 r - // 电量正常,低压指示灯灭& \ C# }6 g2 z& T6 j- B$ T
- LED = 1;( y6 N" H( F8 t1 w/ P' m, S
- }% ] v6 A% p! K
- / m% O8 f4 g% j0 x* d- p
- // 检测到停机标志" z, A9 T% L+ ^. n" K. v: Z+ ?
- if(isReadyToHalt == 1){
$ a2 J8 h, a$ K" Z% n# S0 ] - // 暂时关闭定时器0中断; F" c) c1 S: k# l' E! Z- \
- ET0 = 0;
$ h7 c8 P9 m0 h - // 停机之前先把矩形波输出脚置低电平,以方便INT0和INT1唤醒CPU
% r6 s7 z. h0 a" { - OUT = 0;; r" {- C# ]4 J0 `; w# W4 d
- // 停机之前先把低压指示灯灭掉,以省电0 X4 i: _ u1 |6 N' R, @
- LED = 1;% }$ e" z7 B3 G( S
- // 进入停机模式
9 s: l$ e7 N$ C D - PCON |= 0x02;
0 T- x b' S: i% A5 | - _nop_();4 X) q" p ?( H
- _nop_();' ]8 E7 P7 L: G4 C) I
- _nop_();
. d6 o& E. g: o' `5 N1 \# N5 C0 Z( j0 J - _nop_();
8 y" S# z7 a/ }* z$ Z7 ~/ ?% k - // 唤醒,清标志位# I& G, d! u1 m( ?* I S3 T. E9 w
- isReadyToHalt = 0;
7 i" q* W+ g/ @ M - // 重开定时器0中断
! D1 W9 H% W& T; g - ET0 = 1;: X, g7 m7 l/ O/ H! F+ Q# [0 ?: D
- }
' i: C% ^8 ]( \) p9 b2 K5 Z - }% x0 P& ]9 g& |$ F& Q! A8 N* w
- }
( M+ \! i9 ~0 @/ C) }- _
复制代码 : t% M, k+ j9 m* @2 I
硬件参数配置:* @5 b* L# `3 Y6 Q( n ]
, M! ?* h1 M: Y8 D- _; T7 u [3 A+ X2 n; U3 H$ J* h8 @
接收端:1 |. {+ ~+ l& Z r
- #include <INTRINS.H>
8 P. N3 T* t8 M - #include "STC15W.h"
& |0 r/ z c7 t& G7 Z - % h& p y1 H+ u% a$ {0 |1 W$ j' W
- 9 s4 |0 n/ R* c" ^& x- ?) L3 N
- /*
6 `& V, t7 J. D3 s6 H
9 S$ x2 Z3 w5 l+ w0 M; M- *---------------------------*- p: S6 ?6 T1 b" z0 p8 {; D4 Y
- |1 (GPIO2) \__/ (GPIO1)16|/ I- n: D0 x9 c2 t& u6 U9 ?5 L8 J* Z
- | |
! j9 U" o# ?9 u3 g# O0 D# S% B - |2 (GPIO3) (GPIO0)15|
, Q! _' B1 t5 G4 f# X, \' a - | |7 z" u$ Q6 n- S' T. k1 |* V. @5 l
- |3 (GPIO4) 14| j2 V. Q, V" Y. C% [$ L( n
- | |
1 J |) D7 j: ^8 W+ ?# ] - |4 (GPIO5) (DATA)13|
% ^) Q: r$ F" b - | STC15W204S |2 b+ l7 @& k: k+ F) u
- |5 (GPIO6) (LATCH)12|
+ L$ E, i" ^ O* J - | |
# n) e) m/ `; d - |6 (VCC) (CLK)11|
3 z3 V2 B" G- g - | |
' `$ s% V( Y, u9 G5 a' Y$ j - |7 (GPIO7) (TXD)10|
/ k1 K. W9 O- `- ` - | |$ u& K7 I! G/ r% h
- |8 (GND) (RXD) 9|
/ G5 k+ a$ R8 [# v& Q - *---------------------------*/ b8 U0 L/ m8 L! e# o5 y- C% |/ z
- Fosc = 12MHz' w( t/ {7 h0 Q1 m. p
0 O( `/ ]4 Y. Q* S- P1.0 -> 上& R: f S) C) [1 b
- P1.1 -> 左
' q4 p: p# p/ a- n8 R% e - P1.2 -> 下
# P* F6 D' j8 S! O! c) h - P1.3 -> 右) S& \# G! d$ `7 L% b! [" h
- P1.4 -> SEL
3 D+ k# u4 w7 l$ i - P1.5 -> STA
}8 w! L: v9 u - P5.4 -> B8 K/ k- B, ?: ~4 G/ y: F1 n' b
- P5.5 -> A
& x: f) C. ^, E2 l$ r; a! |$ { S - + \1 [; }3 U3 T# N* g4 R7 `
- */
7 X* u& n+ i- M7 C7 h H( | - 4 l9 z* I- @7 S$ W
: u! Y2 X" ]" [- \( o4 Y' i r$ a- sbit CLK = P3^2;! o5 j7 y; t* O3 b, ]/ c% `
- sbit LATCH = P3^3;; m/ J. T; Y0 `
- sbit DATA = P3^6;0 U l+ h& e/ E: j/ }) j. \
0 g) ?# A9 I6 h! N& `- bit isReady = 0;8 {/ V: T5 {6 @# `& y( D2 Q6 S
- static unsigned char key = 0;2 ?3 n0 M6 o* H6 E7 d% D
- static unsigned char buf = 0; // 双缓冲。这个缓冲区保存从P1和P5组合而来的键值
% G" h+ \* ]+ A' |& G5 H' | - static unsigned char bufReady = 0; // 双缓冲。这个缓冲区保存上面那个缓冲区的备份
/ L* q& U+ o2 A' e5 s1 `) E' F - static const unsigned char data mask[] = {0x80, 0x40, 0x10, 0x20, 0x01, 0x04, 0x02, 0x08}; // A B SELECT START UP DOWN LEFT RIGHT。为提升速度,这个表放到RAM中
t& t" v& \$ m) N+ ]0 i) S1 B - static unsigned char idx = 0;/ I! N; _. w* l$ `
- * ]% }+ O1 f; N( j( w
- ( D6 T4 O' Q! f: C
- static void GPIO_Init(){0 [" @( s9 G, W
- // P1口和P5口用于接收并行信号,全部初始化为准双向,依靠内部弱上拉输出高电平+ |) m1 U- x* t* b" C
- P1M0 = 0;
5 g K) C, c: ^" F# { - P1M1 = 0;; R2 s2 e6 N$ B4 O
- P1= 0xFF;' p$ L4 z) c4 H
- P5M0 = 0;. E$ O% x8 T$ b9 L7 v( ~
- P5M1 = 0;
% x# n" {8 H0 H [* l3 M* E7 W- } - P5= 0xFF;
- ^! V, Z) h# P0 @% C8 A2 H2 j# Y - // P3口初始化为准双向
9 B. q" ?5 q, E4 ^ - P3M0 = 0;3 }6 G& b5 {- X# p0 a8 e
- P3M1 = 0;, A, [. l3 M0 I4 F' o
- P3 = 0xFF;
7 ~- P: G( ^9 k5 V2 n# F - }1 U: v ?+ q4 ?' o) K' \# U
7 R! U, W* y8 F5 x. _2 {) {! |- static void Interrupt_Init(){
5 z6 N0 _! h5 O- J% c$ q% O - // INT0中断(CLK)触发类型为上升沿+下降沿
# D3 L' E0 N5 ~ - IT0 = 0;
7 Y% e2 Q- D3 w1 m* [ - // 允许INT0中断
, k ~1 q9 T: E$ }0 }) h: | - EX0 = 1;
6 w) f) k- T: d. ] - // INT1中断(LATCH)触发类型为上升沿+下降沿
6 n9 V, c" j, A" \ - IT1 = 0;
: ~6 Q6 t+ _8 J0 R - // 允许INT1中断& r1 o6 S4 H) l- N: T9 C3 o. w7 v
- EX1 = 1;
. R: h. O. q* K+ x5 d - // 开启全局中断) N, l8 j( a4 {9 B$ z5 l a
- EA = 1;
' K: {( z0 \$ T - }; e: S6 E1 ?! j+ F
- $ _5 p1 T. ~0 D
- static void INT0_Isr() interrupt 0{/ v( R! o7 F3 b9 `+ g8 U; H% X ?
- // 只有已经成功锁存才允许CLK移位
4 m9 H2 P+ c: K& e. f - if(isReady == 0)
# j& E- t* ]8 b - return;
c' \3 |& G! N4 { - // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断
( w K: @( ]) t+ c; v - if(CLK == 0)4 Z8 X( T; X# D5 s8 i& ^6 I
- return;
) S$ z% B: v, `- Z. n, n - // CLK上升沿到来的时候,取锁存值的下一位输出
1 Q1 f6 D" E$ Q X8 w, X4 K - idx++;
! Y' X. ^/ d$ Q' g - DATA = key & mask[idx];
Y2 U8 ~* u9 k; P! U- g2 G - // 如果已经完成7次移位,则一轮读取完成
' V* w/ z, V1 R' q% \2 U - if(idx >= 7)" ~$ Z0 Y7 r* }1 m
- isReady = 0;
8 W" Q$ c! K7 D3 T6 z - }0 k& J! ?2 y7 ]/ o0 Z! t
- 0 @* _! x. N4 S D
- static void INT1_Isr() interrupt 2{" l2 e7 _4 t8 O, x* Q7 p/ Q
- // 读当前引脚电平,如果是低电平则说明是下降沿,此时直接忽略该中断; Q9 r) |0 d/ H( R( L
- if(LATCH == 0)
! I+ V6 u( ~/ m+ v - return;2 D- h2 \; C( ^0 o. P1 a
- // 当LATCH上升沿到来的时候,锁存所有按键状态,同时把键值A输出到DATA( ~3 l6 c# z* k- i8 C
- key = bufReady;
9 r# [; `, F% v H# h' Q - DATA = key & 0x80;//mask[0]; // 为了加速运算,直接取表中的值而不是读表5 i! e7 |5 X9 z z% [/ _
- idx = 0;) q) }% Y9 Q' }6 F
- // 允许CLK进行移位/ m8 U' P2 N" l8 x
- isReady = 1;( }6 m3 L( `5 c7 O5 b
- }+ d: F n$ [- m% Q
- 3 A' d1 M2 u! ^3 C
- void main(){
P! e* X& a* P. [ - GPIO_Init();
, W# m8 u/ ?# C7 }! ]2 a3 l - Interrupt_Init();: d, y) {$ a2 z# D" `
- . O4 B% G# k5 M7 P+ D
- while(1){: u; l+ d6 i- v5 p5 K. g
- //PCON |= 0x01; // 进入省电模式
8 W0 N* }3 y! ?+ v - //_nop_();
# E; z8 V6 ~: x% D8 \( b5 M" u - //_nop_();, A$ O2 r5 J5 M- l
- //_nop_();; S/ e! c# B% g) Y
- //_nop_();9 s! [' G+ r9 r7 t; j
- buf = ~((P1 & 0x3F) | ((P5 << 2) & 0xC0));7 A& ?2 N" E# r+ l, k" _
- bufReady = buf;
2 g7 _ q5 M# E+ _9 [9 O - }2 c/ h5 e; k; C# y
- }
7 m0 ~2 m- {8 y4 {1 Q
复制代码
4 T% c. [5 D- I: b, T5 [4 @硬件参数配置无特殊要求,晶振频率选择12M即可。
# P. K! S) V3 l0 w
- m/ o+ a$ U" Z这是编译好的固件。 |