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