最近整理东西,突然发现以前买的一个步进电机模块,以前使用5V单片机驱动的。突然想试试能不能用STM32F103单片机的3.3V直接驱动ULN2003这个芯片。
6 l& U' n" H2 W+ ?, Z/ {8 Z( ?( J* ^: Y
8 k: u+ \( s; }. o
! A" G. c- |1 o9 J9 ~试了一下ULN2003可以用3.3V电压直接驱动。下面分享下电机驱动的过程。
# V6 O6 K3 `' c& J7 E& l3 }& f0 B6 g5 n( r9 R
先看看实物图
+ t3 ]' ?; w+ B1 D# n" Y1 c# U8 e6 P, O' j
! w9 ^5 N4 R% c( Q& |# N
9 B' u5 i9 J1 t7 @& w单片机使用的是STM32F103C8T6最小系统。电机和驱动板是网上买的整套模块。$ c: i' L3 X( E) d
+ m8 ?5 M1 Q! \% M4 o$ _驱动板的原理图如下) s: T) H# N9 X! P
- c; b x/ f# y" C$ u" D$ {
( b2 C$ d( {+ h; H% m( n
3 i* X8 n4 A# i 步进电机使用的是5线4相直流减速步进电机 直径:28mm 电压:5V 步进角度:5.625 x 1/64 减速比:1/64
" b1 O% m# k* E$ F
8 j9 O' U7 e( O 步距角:5.625 / 64 = 0.087度 (也就是说理论上,你给一个脉冲,电机外部轴转动0.087度)也就是说一个脉冲内部转子转5.625度,但是由于减速64倍,所以外部轴只转了0.087度那么外部轴要转一圈的话,需要360/0.087=4096个脉冲。采用4相8拍驱动的话,8个脉冲是一个周期,那么就需要4096/8=512个周期,外部轴刚好转一圈。0 [1 { L8 J5 ?8 C- {9 a
/ G9 @- m# |% C W1 o 电机的驱动一般分为3种方法' P# G7 c% l" ~1 V3 g
! W, ? f& K4 m; R8 E
一. 1相励磁法:每一瞬间只有一个线圈相通,其它休息。
3 C& U3 E; _- r4 ?/ j \
8 B; Q" Q+ o; e (优点)简单,耗电低,精确性良好。
9 U4 I. z7 a a0 K) N0 e' }0 M# q. [0 @, i3 R4 W
(缺点)力矩小,振动大,每次励磁信号走的角度都是标称角度。 1相励磁法 A->B->C->D
- s V/ a5 P$ _+ r$ V `
& \ p5 Y0 Z+ U 二、 2相励磁法:每一瞬间有两个线圈导通。
0 @# l7 [4 W9 j {* e! r2 K
8 u- k c" D/ ~+ @, t (优点)力矩大,震动小。) t8 E. J! ?" X
$ J/ [0 ~: X& @ U. Q$ b4 D3 Y0 G; _6 z (缺点)每励磁信号走的角度都是标称角度。2相励磁法 AB->BC->CD->DA
0 l- k: l4 K: d* p6 C" y
8 Z5 L" }/ t; n 三、 1-2相励磁法:1相和2相交替导通。
, l% N" I; t7 t. G, x) g& x. X: {" S* \0 C" G* v4 X+ \% R2 j: t
(优点)精度较高,运转平滑,每送一个励磁信号转动1/2标称角度,称为半步驱动。(前两种称为4相4拍,这一种称为4相8拍); }$ Z# m9 F1 m/ w/ j
9 J2 {: _* [; ^* f; b$ H9 y
1-2相励磁法 A-->AB-->B->BC->C-->CD->D-->DA
5 D! ^: y. Q7 f3 p! b% R
. y# p0 m. K( A$ i2 b6 ?下面就用代码来实现这三种方式
- h# `+ N3 \9 s; i) n
! g1 n+ {+ l- S ULN2003这个小驱动板上IN1、IN2、IN3、IN4四个插针和单片机的 IO口直接用杜邦线连接,为了方便控制,首先定义好要使用的 IO口。就直接在LED的工程上修改。/ |5 P4 ? m) }) M9 Y- R6 q2 s
0 e( O; |0 R; v6 r' k9 m" ~. m 首先新建一个bsp_motor.h的头文件,在头文件里面进行端口声明。' r: q' h. @' J& G
; ?- W2 O3 o$ b
- #define LA PBout(6) //A 相
- d M, \# p8 k& i2 b+ x - #define LB PBout(7) //B 相
6 O5 y; ]: E. X6 _: f! L - #define LC PBout(8) //C 相
; t" p9 @4 k8 K& X - #define LD PBout(9) //D 相/ T* o4 N% w6 {2 ]+ f1 W
7 ?- D% s7 v" [! u$ N" d& H3 ]- /* 定义电机连接的GPIO端口, 用户只需要修改下面的代码即可改变控制电机的引脚 */
, r1 m; K% H' C" G- @; ~3 l2 o - #define LA_GPIO_PORT GPIOB /* GPIO端口 */
* o T% j; i6 \ - #define LA_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */) \8 K' B% e) f7 X# L
- #define LA_GPIO_PIN GPIO_Pin_6
4 C4 l. m! w, r8 Z' x {$ R! X
# {$ g& {# _ d6 f7 L$ O9 R! G- #define LB_GPIO_PORT GPIOB
) A2 Z! e& S" C1 T" ?; @8 Y - #define LB_GPIO_CLK RCC_APB2Periph_GPIOB
/ s- s- L$ ?. {! ]) m: C - #define LB_GPIO_PIN GPIO_Pin_7
7 k) H* G# Y1 V/ X& n
) _* [2 N% e7 ^ q, ^2 j- #define LC_GPIO_PORT GPIOB2 \# u& _" X5 U4 L3 C; X0 j- o8 Y
- #define LC_GPIO_CLK RCC_APB2Periph_GPIOB; G9 ^% D' f$ V# \
- #define LC_GPIO_PIN GPIO_Pin_87 F! z7 K4 g# M+ y/ U3 K) S
6 s) ^% g) s% g* n2 W5 a2 |, U8 ?- #define LD_GPIO_PORT GPIOB1 g6 G9 e4 n6 x6 g" O- d& u
- #define LD_GPIO_CLK RCC_APB2Periph_GPIOB
/ }" r9 O8 f' w - #define LD_GPIO_PIN GPIO_Pin_9
复制代码 / R _8 o2 d7 m5 N' e% p
这里使用PB口的6、7、8、9四个端口来控制,端口使用了位带定义的方法,这样用程序控制起来更方便。关于位带定义可以自己查阅相关资料。端口和时钟都使用了宏定义重新命名。这样以后需要更改IO口的时候,只需要在头文件中更改,不需要更改程序中的代码,方便程序移植。 4 g. D4 G6 h$ w4 l( S d
: G7 o0 H3 | v. X6 o" n" m
下面开始编写驱动代码,首先使用1相励磁法,也就是使4个IO口轮流为高电平,可以直接使用位带操作,依次给LA、LB、LC、LD给高电平。但是这样写的话每一种驱动方式都要写一个驱动函数,为了使操作起来更方便,将这4个口的值组成一个字节,并存在数组中,通过下标轮流调用数组中的数就行。这里将LA作为字节得最低位bit0,将LD作为字节第3位bit3。2 {/ n5 P. y7 E
/ j0 ^; B8 T% j
这样电机导通相序为 D-C-B-A时,定义数组中的值为
$ y1 \0 d) P4 |, X; p9 k
- V* k2 P3 D% C% } K: b6 X/ J+ G- u8 phasecw[4] = {0x08, 0x04, 0x02, 0x01};
复制代码 $ Z1 t0 J! ?' W8 k! f$ x1 q
电机导通相序为 A-B-C-D 是,数组中的值为
) [0 L! n0 X" d5 t1 o6 ^& I) c. j8 K2 B% D; W
- u8 phaseccw[4] = {0x01, 0x02, 0x04, 0x08};
复制代码
0 o7 G3 h# I5 v" u' U2 ?( t+ y下面新建bsp_motor.c文件,并在里面编写一个函数来调用这个数组就行了& `0 V" ? |/ k; C" w# o* {
6 J" _7 [) L4 H2 b( r ~- void MotorCW( void )) L% f' D u+ M. J
- {/ v" F: m( C* J
- u8 i;6 {; _/ n; e1 Q6 L- V0 M" o
- u8 temp = 0;: _: g. O! G4 }
- for( i = 0; i < 4; i++ )+ C n6 J, Q# f- P0 K
- {# m" Z0 T) H8 U0 V
- temp = phasecw;
4 ~8 f, n3 S5 D6 Y% ?% I1 F
/ c9 Q0 E8 t7 f8 M# x, n- LD = ( temp >> 3 ) & 0x01; //取bit4的值* v8 L" Q3 G' \, M
- LC = ( temp >> 2 ) & 0x01;) K- G8 @ T3 u. `
- LB = ( temp >> 1 ) & 0x01;# p, M/ \ W B% ?( U: R
- LA = ( temp >> 0 ) & 0x01; //取bit0的值
3 `. E/ ]" s1 h m - delay_ms( 2 );
- t- q# r. V2 v$ s - }: n1 ?. s# M8 Y- z# [: n e2 N
- }
复制代码 & y7 v) |1 U t4 n
通过一个for循环,依次读取数组中的值,然后根据A、B、C、D四相的位置,去读取对应位的值,直接赋值给 IO口。这样电机驱动的函数就写好了,在主程序中直接调用这个函数就可以控制电机转动了。, W6 f l; q% s$ Q
1 a0 O. J7 V: N' g# p4 @
如果要改成 2相励磁法驱动的话,直接替换数组中的数据就行。
) O% Q& Q/ S$ J7 u/ x, p4 O. I l& S
- u8 phasecw[4] = {0x0c, 0x06, 0x03,0x09}; //正转 电机导通相序 DC-CB-BA-AD$ j3 @- v8 r% x8 |& {' t
- u8 phaseccw[4] = {0x03, 0x06, 0x0c, 0x09}; //反转 电机导通相序 AB-BC-CD-DA
复制代码 % I9 ^ I* ?3 x2 h" K( M% K$ y1 h
如果要改成 1-2相励磁法,依然是直接修改数组中的值就行。9 L; v. q5 {! }. s
8 l) J( o8 f$ q5 G5 r
- u8 phasecw[8] = {0x08, 0x0c, 0x04, 0x06, 0x02, 0x03, 0x01, 0x09}; //正转 电机导通相序 D-DC-C-CB-B-BA-A-AD
6 ]7 Q- T3 h( v4 ^ V - u8 phaseccw[8] = {0x01, 0x03, 0x02, 0x06, 0x04, 0x0c, 0x08, 0x89}; //反转 电机导通相序 A-AB-B-BC-C-CD-D-DA
复制代码
7 W8 o2 I7 _% F, b 通过 1-2相励磁法控制的话,每次需要发送8个脉冲,也就是4相8拍,这样一个周期需要发送8个数组,要在for循环中将循环数量改为8。4 c, @3 y( l7 p
3 Q& O( E( e. v6 V' ]- [
- void MotorCW( void )- S. E& T& ~ t2 W" H
- {
$ U& Y9 }! w, }# D- K - u8 i;+ G7 S4 b' c* c" W# f" B
- u8 temp = 0;
- u9 U/ b0 \. J - for( i = 0; i < 8; i++ )
" e' K5 O3 p, |5 Z% X/ ?/ |- ` - {
4 {5 z( l8 f3 L( `6 ?) {+ S - <i> temp = phasecw;; y# r$ [+ T5 y* U
- LD = ( temp >> 3 ) & 0x01; //取bit4的值
6 {- `# C2 f, t p - LC = ( temp >> 2 ) & 0x01;
* [; p$ p$ N# Z$ |1 F) N - LB = ( temp >> 1 ) & 0x01;
}( M: s5 p7 `. }7 l5 C! Z - LA = ( temp >> 0 ) & 0x01; //取bit0的值
2 q2 }( u( M1 n - delay_ms( 2 );
% g% P& e8 I" t7 R - }
- `3 V7 X' W- M0 z9 ^ - }</i>
复制代码
) S g' ~7 P: `# e, ^4 V然后在主程序中直接调用这个函数就可以让电机转起来了。
8 }" I) s1 u' q4 G+ v: \; y
2 f" i9 j5 X$ V* b( D2 ]1 x- int main( void )
2 `: \# w E2 T5 A) c( t+ I& E) c - {5 I7 ~0 j4 Y! B! H6 R
- ! u+ n5 u& o+ P/ V3 L3 E
- NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
, F: B6 f# b' U8 Y; ~
' V% G$ M* {$ f' P r l- l- Motor_GPIO_Config();
% S* @3 I/ |! V' m; [ - while ( 1 )
, j; p! Y$ b. S9 w+ j( N - { * u% H4 d( v% f% C" H+ l
- MotorCW(); # C0 l% L" p: Z- j% l) i
- }" M+ Y5 \, @2 i% x! s
- }
复制代码
4 O3 B- N+ c- X0 \这样看来驱动电机是很简单的用keil模拟的仿真波形' k$ {, ^ m; m8 ]! I
; Z0 H% l; ^2 r- K; [
1相励磁法 D-C-B-A2 N1 W8 g" g6 p1 f1 H
5 N; Q/ c2 @$ y# s# W
) t$ e3 o, k/ \0 ^8 c" C
3 _0 N8 ^0 [# D2相励磁法 DC-CB-BA-AD& D! r- L& C8 P+ I, V
+ |; s0 e8 w( H8 `! G1 P" a
6 I3 p7 i r/ c. ]
) i. @: m" C' D4 n$ Y L1-2相励磁法 D-DC-C-CB-B-BA-A-AD7 G! n& L6 ~: G6 R4 f# b
: {1 B3 t! n& o
E1 i4 H/ @" U8 I
3 R a' I3 P& C8 i; ~波形和理论相符,说明驱动是正确的。
; K3 a# O* D' K6 q! f
# `' Y2 Z7 a" y( `到这里应该就结束了,但是又手贱的给主程序加了个LED闪灯的程序,这一加出事了。0 @# Y3 q7 W: Q0 Q- Y* B5 z
- U! g3 t0 _5 i& H7 ]- int main( void )% U4 x( e. }* t1 Q
- {
3 }* Z% S% T& B& a! k6 C - u16 cnt = 0;
& Z% a- M' P, E1 B - NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );, c5 c% P. l5 ^) f7 U
- LED_GPIO_Config();
2 `8 Z1 j: R) u, x - Motor_GPIO_Config();
I& y/ D/ {. @3 P- F - while ( 1 )" ^* J1 `* |( X" v0 K# u& H
- {/ _/ \9 w3 Q$ F! J, a
- LED1_TOGGLE;
3 K" r, Q0 l4 i3 {- m+ M$ |7 K; h - MotorCW();
" Y" Y# q& D% ?. L3 N4 o0 \) R - delay_ms(20);! R' E8 R+ L8 A9 K, B/ k
- }
; p- Q/ y: N! x0 O* F - }
复制代码
/ U, Y% m! s6 h& E$ C2 z主程序中加了一个20ms的延时,同时让LED灯翻转。程序烧进去之后,发现电机不转了。这是怎么回事?赶紧看看仿真波形。, f+ _% Y! A X8 P8 X; D
# c2 B( v$ f3 e8 K" l- D
& s9 O4 Q; {+ `
& S9 Y, C1 g: `0 P 这一看发现了问题,A项的波形怎么高电平突然变长了?. D4 J% i2 n1 B! f6 `
5 |5 s6 S% E$ Z, m# `! ]: c' w- u8 phasecw[4] = {0x08, 0x04, 0x02, 0x01};
复制代码 5 u7 W) ?/ F9 M! Z# z
数组中依次给的值是8、4、2、1,也就是D、C、B、A相依次为高电平,其他三相高电平只持续了2ms,但是A相的电平持续了20多ms,这是什么原因?仔细分析程序执行流程后发现了问题所在。在for循环中依次给4个相赋值,A相位最后一个赋值,当给A项赋值时,其他3项为低电平,A项为高电平,然后程序退出for循环,回到主程序中,此时又执行了20ms的延时,这时A项的电平还是最后一次赋值的高电平。将这20ms延时去掉后,电机正常运转,波形也正常了。也就是说就是这个延时影响了电机的转动。
! k' E7 d' x6 H7 t3 K1 k- J7 d' o' E1 i. M y, q
那么主程序中就不能用延时了吗?LED闪烁功能都实现不了了吗?还能让一个电机驱动程序把单片机霸占了不成?这当然是不行了,一定要将这个问题的根源找到,既然是最后一次给A项赋值后,A项高电平没有复位,那么就手动让A项复位。强制让A项的电平归零。
: ]$ q3 b# L1 H, r
9 d$ J' J% ]9 ^8 T% w 在增加一个复位的函数
* d6 I& ~* x- E. R4 S, d7 o. X$ G5 s+ c: V
- void MotorStop( void )
: B! d9 j8 A5 B j$ i6 ? - {9 P: G) T* a0 d- H* K) X' _
- LA = 0;: E! c" a& ^3 l4 Y$ c
- LB = 0;
3 u# p, Z( u7 N2 T8 _ - LC = 0;
* e O9 r6 s% d7 D/ Y: `: Y* r5 ^# @9 d - LD = 0;6 M% v3 C6 A' J' H
- }
复制代码 ) b3 z4 H1 l+ u. g, H1 a
这个函数强制的将四相复位,然后在电机驱动的时候,每次执行完一个周期,就强制让这4项复位。
$ _# u% H- J, ]6 E: e" g2 k
4 I0 {% c: _; Z6 x, J8 @- void MotorCW( void )
" w. Z4 a; p4 l& I - {
& P1 w6 h3 H3 F2 ?1 V - u8 i;
1 [! T) S9 d1 f - u8 temp = 0;1 I1 k& S. I4 t2 v% `5 h' d: r
- for( i = 0; i < 4; i++ )% e9 a6 L& B- y7 T* U% X9 d2 ^
- {! T0 Y8 r2 k$ s: \' }
- temp = phasecw;% B# k7 v' K" W& H# K5 {; ]
- , W/ Y1 [" U& c4 ]* z2 K5 o
- LD = ( temp >> 3 ) & 0x01; //取bit4的值
) Z) A( F( q+ [0 D. K - LC = ( temp >> 2 ) & 0x01;' R5 Q: d2 d3 A" L" t* _) V n
- LB = ( temp >> 1 ) & 0x01;4 z9 ]6 {' M) [# m
- LA = ( temp >> 0 ) & 0x01; //取bit0的值
! E+ T! X. r% z: N - delay_ms( 2 ); ! d6 G$ J5 y. J* m8 H; s) |3 ?
- }
+ M/ V" M: L/ P1 l5 i5 @5 E( w; f - MotorStop(); //一个周期转动结束后需要复位所有相,否则最后一项如果被设置为高电平后会持续维持高电平。
0 t: `* g* x: ^; L& ~ - }
复制代码
5 c% V% ~. A, f' V9 r$ @2 w2 A 增加复位功能后,主程序中依然加上20ms延时,继续仿真查看波形。
& u2 U% j( N ~0 ]; Y; e# s: ~0 {9 |' S+ L- a0 k0 L6 |) X
- e: V7 U1 r7 D
' w! f- A4 A8 P% M" j" p 这下波形正常了,下载程序后电机也缓慢转动起来了,LED灯也开始闪烁了。说明刚开始驱动时只想到了相位的逻辑,但是没有考虑到相位复位的问题,考虑问题还是不全面。* M! u5 \0 o! v! S" g. L/ R4 |2 M" _
0 [1 G+ G7 n, C& p% V: F
下来在主程序中加上电机正反转功能,让电机正转一圈,然后又反转一圈。8 T; \2 C9 P$ H o P/ Z7 W3 Y6 N
. p1 K- @" a4 H# h+ z& Z
- int main( void )
& E+ J" N2 a" k1 R" \0 j" F" J0 t - {
% Z) P i$ v$ L+ C. V - u16 cnt = 0;8 {1 _7 n4 m/ g" r+ Z5 {- j
- NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
$ H! F- z6 \7 z - LED_GPIO_Config();! }/ B% t; J- i. q9 E4 u
- ) f o p& h: h7 F+ I
- Motor_GPIO_Config();* f9 N5 V! i, n' w% O" `5 A
- while ( 1 )
O" J6 f. [& Z! T5 L - {
( z' c$ V: r: u( e - cnt++; ?% E! t) \) J
4 j- U5 w" w5 s9 Z- if( cnt <= 512 ) //采用4相8拍,512个周期刚好是一圈5 I7 O5 P4 E C: Z: f0 O4 Z
- MotorCW(); //正转0 S$ {, W: X8 j3 F. n( `( N& G) V9 ]
- else if( cnt <= 1024 )
; I Q$ [* `: l5 B- } - MotorCCW(); //反转. V' P% A0 ~7 C* U
- else
" |+ z' |" W3 l - {
5 [# K9 y, w$ d' N - cnt = 0;: n: O9 @, D C9 h: K7 a! L
- LED1_TOGGLE;
% h- ]8 c/ F5 V; f! D9 i7 ~ - delay_ms( 500 );
b; }5 W( ^3 @- `! `1 Q4 r - }1 _: S' `; ]5 ?; m& K) T9 k" z# y
- }
# i! |3 _! A- z% G- d+ z5 `5 d - }
复制代码
/ m) \9 F$ A& O) t 这里采用4相8拍驱动,8个脉冲是一个周期,那么就需要4096/8=512个周期,外部轴刚好转一圈。计数器小于512时正转,计时器在512和1024之间时反转。然后LED灯翻转一次,在延时500ms。 这里还要注意一个问题,在电机驱动函数中,每个脉冲的延时时间经过实际测试不能小于2ms,延时太小的时候,电机抖动严重,但是不会转动。延时越大,电机转动越慢。
2 r% B e7 G4 R- ~: p0 J: k2 k+ I$ S
到此电机驱动程序已经完美运行了。# b8 A7 M: a- ~8 z. M" ]& x
5 u/ Q5 b) Q4 n4 Y- @1 p3 M& J* \; r3 g4 A( O# O+ i z6 y, t
|