最近整理东西,突然发现以前买的一个步进电机模块,以前使用5V单片机驱动的。突然想试试能不能用STM32F103单片机的3.3V直接驱动ULN2003这个芯片。2 q2 i! |% x2 B3 W1 h
" ?( o; ~ l: b. f
0 _3 ^2 g+ V8 P* \6 _' j
% i9 j* i! m, c2 q, p3 U' j/ [/ S
试了一下ULN2003可以用3.3V电压直接驱动。下面分享下电机驱动的过程。3 X$ `* h- t% w. z: |
. b! R( f% U) G5 v1 }6 ^' b2 H先看看实物图
: g2 ~ a5 L7 u( Y, O' z. ~7 |4 W
8 ]+ } o4 M- _1 T2 a7 z6 \8 O
! n1 g& h6 K; K/ }: x! @4 P6 R; Q
单片机使用的是STM32F103C8T6最小系统。电机和驱动板是网上买的整套模块。8 r1 A9 r& B# |' U) J$ }, }+ Q
+ q: D$ J7 g/ E) Q* f
驱动板的原理图如下. G; p9 T s- s& m0 ?
& h8 u0 ]5 h9 K
/ K# q% @8 }# L' C
; N! g" [, p+ n% R 步进电机使用的是5线4相直流减速步进电机 直径:28mm 电压:5V 步进角度:5.625 x 1/64 减速比:1/64( O2 A! w6 J0 M% D
# a, `0 d( I# e0 }6 r8 W
步距角:5.625 / 64 = 0.087度 (也就是说理论上,你给一个脉冲,电机外部轴转动0.087度)也就是说一个脉冲内部转子转5.625度,但是由于减速64倍,所以外部轴只转了0.087度那么外部轴要转一圈的话,需要360/0.087=4096个脉冲。采用4相8拍驱动的话,8个脉冲是一个周期,那么就需要4096/8=512个周期,外部轴刚好转一圈。
! B9 @& r0 n/ _1 g8 u* G
+ }' v' o% D5 {( I 电机的驱动一般分为3种方法
. _$ q; ?" p5 F
* g1 Y2 n( b3 J( X3 n0 I 一. 1相励磁法:每一瞬间只有一个线圈相通,其它休息。# l% f- K# @1 P4 G6 `: B; Q
0 [, c9 D/ N1 j& p1 s2 o (优点)简单,耗电低,精确性良好。& Y& H( r$ X7 O9 D! h* K
2 X# u$ Z& F: E3 M (缺点)力矩小,振动大,每次励磁信号走的角度都是标称角度。 1相励磁法 A->B->C->D7 `# B# z7 b$ `2 Q) z9 n3 |
6 \4 P0 Y! e& O* e 二、 2相励磁法:每一瞬间有两个线圈导通。) C% m& A1 t; ] A8 Q
3 h" c# z' Q$ A
(优点)力矩大,震动小。
+ Z8 i6 x( M" r& j D) H% s1 ^2 ?, J$ g2 ], h, K9 w5 d4 ^' k: b
(缺点)每励磁信号走的角度都是标称角度。2相励磁法 AB->BC->CD->DA4 r: C1 V7 _. }; t& ~
2 J# Q4 I, g- J' p9 d$ o4 j 三、 1-2相励磁法:1相和2相交替导通。8 V: k# v& V! D, \$ n
1 L: E o0 A- ]; ]7 b7 h- V8 J4 ~
(优点)精度较高,运转平滑,每送一个励磁信号转动1/2标称角度,称为半步驱动。(前两种称为4相4拍,这一种称为4相8拍)8 g* a$ W) x$ }' ?# U
3 f3 N& L, C$ _8 V5 O) _ 1-2相励磁法 A-->AB-->B->BC->C-->CD->D-->DA
y. A7 W. p# ?/ Z2 s1 Z/ W3 {) h& H5 V/ i& Y
下面就用代码来实现这三种方式
! i+ O3 J O' G6 X, @* @( J" h; B/ J6 A9 A5 T) a
ULN2003这个小驱动板上IN1、IN2、IN3、IN4四个插针和单片机的 IO口直接用杜邦线连接,为了方便控制,首先定义好要使用的 IO口。就直接在LED的工程上修改。3 z6 n2 {( z* ^9 e% R% f6 s9 C" m
, V! A4 d4 b6 v$ R7 k( l% A/ Z) w
首先新建一个bsp_motor.h的头文件,在头文件里面进行端口声明。) P% R9 X7 z% V. x: i
4 j: K( @% v# b0 ]) @! U
- #define LA PBout(6) //A 相
( i2 a- W1 t) z$ N2 v2 n& b - #define LB PBout(7) //B 相# f% s2 c. n+ F, R. u7 l1 Z
- #define LC PBout(8) //C 相
% [+ X+ U+ ~6 s% v# }* O! ?2 G# R8 J - #define LD PBout(9) //D 相
8 [' e! n+ I1 H2 o - + |% R, d* [/ b/ ]
- /* 定义电机连接的GPIO端口, 用户只需要修改下面的代码即可改变控制电机的引脚 */
9 \$ i+ V; l1 s% A* S/ N - #define LA_GPIO_PORT GPIOB /* GPIO端口 *// L4 k5 H$ k& z$ y
- #define LA_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */- |: [, U/ E! c- v- V S
- #define LA_GPIO_PIN GPIO_Pin_6% f( O" o: N' I6 E ^4 R# D$ C" x
* l" U; \& F2 F) v0 q- #define LB_GPIO_PORT GPIOB1 \' j# e# V. @) `+ }7 e
- #define LB_GPIO_CLK RCC_APB2Periph_GPIOB7 V" {5 m( k+ f. p$ z
- #define LB_GPIO_PIN GPIO_Pin_7/ F+ r" `1 n. ~6 F3 T2 G
- 4 \- s1 H& F% E r0 ?4 i e0 B6 _
- #define LC_GPIO_PORT GPIOB
! }& B {6 E$ E; `" t/ }3 o - #define LC_GPIO_CLK RCC_APB2Periph_GPIOB
! G3 Y" L$ V4 R. K& X7 J5 i - #define LC_GPIO_PIN GPIO_Pin_80 b, Q; P& V% O7 o7 j. z
- 5 f1 z4 _% B" k. J7 ~
- #define LD_GPIO_PORT GPIOB
, \) }' t& y7 G3 z: u! H" C, q - #define LD_GPIO_CLK RCC_APB2Periph_GPIOB
: \/ a" k: R2 C, g- H+ L - #define LD_GPIO_PIN GPIO_Pin_9
复制代码 + y( d5 ?! Y; @& T- i( @( _" h7 x
这里使用PB口的6、7、8、9四个端口来控制,端口使用了位带定义的方法,这样用程序控制起来更方便。关于位带定义可以自己查阅相关资料。端口和时钟都使用了宏定义重新命名。这样以后需要更改IO口的时候,只需要在头文件中更改,不需要更改程序中的代码,方便程序移植。
X8 ?; W; O7 N2 H/ D) P: ~* |" U$ L* Q" v ~
下面开始编写驱动代码,首先使用1相励磁法,也就是使4个IO口轮流为高电平,可以直接使用位带操作,依次给LA、LB、LC、LD给高电平。但是这样写的话每一种驱动方式都要写一个驱动函数,为了使操作起来更方便,将这4个口的值组成一个字节,并存在数组中,通过下标轮流调用数组中的数就行。这里将LA作为字节得最低位bit0,将LD作为字节第3位bit3。
- N. P& M7 Y1 U0 ?4 D! t. N
. n2 n1 S& ^: `6 R 这样电机导通相序为 D-C-B-A时,定义数组中的值为
: S) ^! W5 v* X9 S8 O* d$ |0 A( V
- u8 phasecw[4] = {0x08, 0x04, 0x02, 0x01};
复制代码
0 I# J4 C0 E8 |; t( C. p1 u- i 电机导通相序为 A-B-C-D 是,数组中的值为) h: n* z7 q1 ^% ^( R! u
. _: Q8 B0 ? g, m% z" Z/ i0 H) I" {
- u8 phaseccw[4] = {0x01, 0x02, 0x04, 0x08};
复制代码
) ]: h& A7 L, C" F7 r6 R下面新建bsp_motor.c文件,并在里面编写一个函数来调用这个数组就行了4 v" h" L( x P
! \# f6 j; I$ T! k/ v
- void MotorCW( void )
0 o- o. M6 _, X Z; B" ]" B( O - {
7 g5 I f9 ?- I+ b J& N. F, l6 ] - u8 i;
8 w& e1 {/ ^! d7 m! Y - u8 temp = 0;
& A+ y+ e( M/ k3 K8 R - for( i = 0; i < 4; i++ )
4 U C0 D: M$ k5 ?& r) E - {. P r' @! F% `. n0 ~9 [9 \
- temp = phasecw;
# r0 S- T) ]5 u; L+ g* D5 } - : o. H2 w0 T5 p( u6 u6 ~
- LD = ( temp >> 3 ) & 0x01; //取bit4的值
: r7 u, n9 f; ]) o6 l& G- Z0 o, f - LC = ( temp >> 2 ) & 0x01;
, C0 p% ^% z8 L! T" D3 G9 ? - LB = ( temp >> 1 ) & 0x01;
* _* f$ w1 o) ?( v$ c, U! P - LA = ( temp >> 0 ) & 0x01; //取bit0的值 a$ e: U& `# z) e8 F
- delay_ms( 2 ); 0 o9 i v3 P$ `4 z
- }
" }4 l, \6 R) N% G# | - }
复制代码 7 T" u% T. U: P/ F/ V1 n
通过一个for循环,依次读取数组中的值,然后根据A、B、C、D四相的位置,去读取对应位的值,直接赋值给 IO口。这样电机驱动的函数就写好了,在主程序中直接调用这个函数就可以控制电机转动了。- |/ `. J3 h) _$ x
7 @0 \' \& y& y5 U
如果要改成 2相励磁法驱动的话,直接替换数组中的数据就行。0 B' v# d8 s1 s; V: ~2 j+ c
: |9 F! r; [7 n a
- u8 phasecw[4] = {0x0c, 0x06, 0x03,0x09}; //正转 电机导通相序 DC-CB-BA-AD" m" D1 O1 `, `$ s* ^/ h( W
- u8 phaseccw[4] = {0x03, 0x06, 0x0c, 0x09}; //反转 电机导通相序 AB-BC-CD-DA
复制代码
& X- q, P& I! R, s$ U9 [ }( }4 O 如果要改成 1-2相励磁法,依然是直接修改数组中的值就行。
. b9 V- j; S1 Z2 j) o* a: p
2 g3 y7 W3 _& Q) Z: j- u8 phasecw[8] = {0x08, 0x0c, 0x04, 0x06, 0x02, 0x03, 0x01, 0x09}; //正转 电机导通相序 D-DC-C-CB-B-BA-A-AD3 K1 H8 @% [" ^7 }7 K: ]' h: S& o6 \8 y
- u8 phaseccw[8] = {0x01, 0x03, 0x02, 0x06, 0x04, 0x0c, 0x08, 0x89}; //反转 电机导通相序 A-AB-B-BC-C-CD-D-DA
复制代码
: V5 P" y! {* }& D9 f 通过 1-2相励磁法控制的话,每次需要发送8个脉冲,也就是4相8拍,这样一个周期需要发送8个数组,要在for循环中将循环数量改为8。
7 D( C1 c) L6 p& d8 J6 C: [" r
9 Z4 N! m$ D, ?' ]: B8 w- void MotorCW( void )
. @2 U3 R0 T1 U - {' {" g( h/ x5 @; e5 k
- u8 i;
5 m$ f4 ]3 P( S - u8 temp = 0;
9 h3 S0 o' r0 @. f! z! e; u - for( i = 0; i < 8; i++ )
2 ?/ y" R2 y; l/ w: o+ [8 m - {" x. h9 ]% \) n+ n
- <i> temp = phasecw;( |% D! g: ]9 [. E# a9 C8 o
- LD = ( temp >> 3 ) & 0x01; //取bit4的值
, g8 D5 ]; q- |. I+ j - LC = ( temp >> 2 ) & 0x01;
# c( C* m1 ~" ]# k - LB = ( temp >> 1 ) & 0x01;
+ z& a- A) i8 j: @& i - LA = ( temp >> 0 ) & 0x01; //取bit0的值
4 h& U, g8 d$ K: A# M& T( R# d - delay_ms( 2 ); 2 ~) q1 V" `! x' t3 ]; J O
- }$ M: B( z4 s0 n- \! D
- }</i>
复制代码 + W8 D8 d" [8 Q7 L
然后在主程序中直接调用这个函数就可以让电机转起来了。
# o+ R& k+ f& S, E( `' R; R& O8 t4 ]9 j. u! E) g$ y8 r# N) H
- int main( void )& i/ V- B6 M+ {; x' s
- {( L9 ^: X, z. f" p
- 7 b1 J y8 f# X/ w) `5 p. J) A& L
- NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
- _3 l4 h e$ o. L* i) X- y - ; l W7 n# Z: F; ?& a( @4 M5 A
- Motor_GPIO_Config();
& d9 {. @5 r S6 ?. r w - while ( 1 )
0 K5 F8 a, p2 b/ U - {
2 }* n$ z# Q: S: u3 k9 o8 A - MotorCW();
9 ?1 b& U7 x! E* T - }3 y8 `4 B! \2 l7 Q
- }
复制代码
! O: j6 z. e- y) s$ V9 @" B这样看来驱动电机是很简单的用keil模拟的仿真波形
: a% v" e. b$ k' V0 T$ l9 T% M, k- Q# t' s6 [. P, r1 N- |
1相励磁法 D-C-B-A- X8 v: i1 [; j: y! o; r
# v% I6 }$ q& X# F& U* ^( j
& G5 V3 Y) D% N8 h S1 K3 C9 U2 B/ ^& y0 @& I& m4 x
2相励磁法 DC-CB-BA-AD
/ T$ p6 ]9 _/ }; \8 s9 u1 ^
( F7 J9 P' @; `/ m, a' S0 l
) r# \4 f3 l+ L
9 Y9 h. r0 X; ~3 |& S8 E$ |) F. S1-2相励磁法 D-DC-C-CB-B-BA-A-AD0 ?3 E; z( e) L) U
. N9 ]0 C5 Z/ [2 g' }. |
4 m* s- I t1 w' T6 t/ K
: ]3 S- G2 |3 ?! W/ q
波形和理论相符,说明驱动是正确的。
# @/ p" E% k5 _% H% a% b9 T, H. T, q: p7 m; H# _4 Z! r
到这里应该就结束了,但是又手贱的给主程序加了个LED闪灯的程序,这一加出事了。
. f% O! c% _8 ] Q4 l- Y: p& ^3 z
/ p& S& i( V+ a6 t$ v Z& l. D- int main( void )6 `1 _, I5 e9 V6 V0 k1 ~' a( T
- {
! T7 H7 x2 V8 C6 Q3 n - u16 cnt = 0;
, v7 U. A# O: X% [. N( B8 B. j% R - NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );6 n& M5 F D- x5 u: G
- LED_GPIO_Config();
$ P# S! P& |) ?' I - Motor_GPIO_Config();
% A3 D! F! R) q+ D - while ( 1 )
4 p1 I5 @! A5 U1 N1 Q; F, N - {
) p1 f+ ~7 A8 z6 O - LED1_TOGGLE;" k4 N$ j, q: h0 h
- MotorCW();
8 |2 V. n S8 d; w - delay_ms(20);) e' _ l; ]4 f6 {( a( B2 H
- }
3 Z" v0 P, [$ E8 y) e, R - }
复制代码 + H5 a5 f5 K9 W' v& Y! g2 a& G# z
主程序中加了一个20ms的延时,同时让LED灯翻转。程序烧进去之后,发现电机不转了。这是怎么回事?赶紧看看仿真波形。1 Z: {; c& p# a: m& g
# k4 I) u, d5 i
9 ]; y: \1 c9 K. L7 w4 i; ?7 f
F6 H V+ l& o8 ?. H 这一看发现了问题,A项的波形怎么高电平突然变长了?" o# z- V% C& N# Z. c2 U# d
. x& z' m) e8 V$ o2 D8 w3 w- u8 phasecw[4] = {0x08, 0x04, 0x02, 0x01};
复制代码 Z5 e+ b! U) {6 ]3 m0 J* A
数组中依次给的值是8、4、2、1,也就是D、C、B、A相依次为高电平,其他三相高电平只持续了2ms,但是A相的电平持续了20多ms,这是什么原因?仔细分析程序执行流程后发现了问题所在。在for循环中依次给4个相赋值,A相位最后一个赋值,当给A项赋值时,其他3项为低电平,A项为高电平,然后程序退出for循环,回到主程序中,此时又执行了20ms的延时,这时A项的电平还是最后一次赋值的高电平。将这20ms延时去掉后,电机正常运转,波形也正常了。也就是说就是这个延时影响了电机的转动。
: [. ]/ X) ?) F# G$ \! T& Y6 y5 _; N7 _( a; I- \. `" n& B) D+ W/ y
那么主程序中就不能用延时了吗?LED闪烁功能都实现不了了吗?还能让一个电机驱动程序把单片机霸占了不成?这当然是不行了,一定要将这个问题的根源找到,既然是最后一次给A项赋值后,A项高电平没有复位,那么就手动让A项复位。强制让A项的电平归零。5 {/ Z1 f u# ]2 j" N
. W+ v4 M# Y# y
在增加一个复位的函数5 Y2 O( |" Q. u* K
1 @" L W& y& y: Q6 m
- void MotorStop( void )
" {' E7 T/ C8 R" t - {4 L0 W, u( F) X4 J& n; m4 d# S
- LA = 0;& |) L5 U- b. @# P
- LB = 0;$ l0 C- J8 z% J2 n) B" l; l1 s! j. B& P
- LC = 0;6 E! \6 p) T7 p& A3 c- l; A4 e
- LD = 0;5 N7 I. I2 E2 c2 M
- }
复制代码
8 Q( z" ~( x- M( Q 这个函数强制的将四相复位,然后在电机驱动的时候,每次执行完一个周期,就强制让这4项复位。( n7 ~: r" E" o3 D9 t
5 X) A, O. g% k
- void MotorCW( void )5 j$ X& @2 B( \5 o7 |
- {3 f/ {; c6 O/ W) E
- u8 i;2 x9 ?( Q+ { m7 a/ R
- u8 temp = 0;
2 V+ H9 D# u3 u2 T - for( i = 0; i < 4; i++ )
4 `% A+ o, ?- v$ c: Q0 ^* u: m - { F: @4 n- J4 ^
- temp = phasecw;
: H* r+ ]; ~" n2 a. }7 K* h - 9 {6 D: \4 L* T% L3 a! `( w
- LD = ( temp >> 3 ) & 0x01; //取bit4的值$ T0 W; G1 w9 P1 {- G7 T
- LC = ( temp >> 2 ) & 0x01;
' e+ ?9 s1 a7 O* l6 I) A - LB = ( temp >> 1 ) & 0x01;
6 v3 ^% ~- G( d! v* u$ f - LA = ( temp >> 0 ) & 0x01; //取bit0的值
$ }3 g: n8 L" d+ { - delay_ms( 2 );
" H, z! a) T1 S0 {6 ` - }/ G" Z$ Z+ L+ @( `
- MotorStop(); //一个周期转动结束后需要复位所有相,否则最后一项如果被设置为高电平后会持续维持高电平。
* r4 ^4 d* G( y2 }3 ^ - }
复制代码 ! W. Y3 W4 b8 E7 M4 `% o1 I" B
增加复位功能后,主程序中依然加上20ms延时,继续仿真查看波形。: @* t- q% P% D
' u9 u" ?8 | H# h- }* i
" H! R; b, R9 H) b6 {( f3 {
8 I6 o$ ?$ j- A/ H% [7 y 这下波形正常了,下载程序后电机也缓慢转动起来了,LED灯也开始闪烁了。说明刚开始驱动时只想到了相位的逻辑,但是没有考虑到相位复位的问题,考虑问题还是不全面。
! H; ^+ n! m8 B. r( y$ T5 k4 w3 B8 d2 q- b/ ^
下来在主程序中加上电机正反转功能,让电机正转一圈,然后又反转一圈。
# q) x) j c$ y4 x9 b0 ?/ \ N- e" ^& N
- int main( void ): F: X, i) j. C
- {2 z; n. w( v: L* _, v, V) R, h
- u16 cnt = 0;
( k3 I8 x9 e! I: Q% P8 c - NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
) X; d. u, s* K4 @" B% e5 I5 R - LED_GPIO_Config();
8 ^4 B/ {! Q5 {$ ~ V. Y- G- _ - % U7 `9 N+ D8 E0 z. i0 z8 B; i# c
- Motor_GPIO_Config();
& @2 x* K/ x( I7 I5 _ - while ( 1 )( b" X% P5 [3 r% q
- {+ ^; r) n' i. z0 m! p$ k& A' @
- cnt++;( e. e% F; N3 q" W8 B% ]: X. k0 H& C
- & s+ f. H) u+ }
- if( cnt <= 512 ) //采用4相8拍,512个周期刚好是一圈
, |) Y& |0 e) R( O - MotorCW(); //正转# I9 d v$ h, j+ Q7 i P, \
- else if( cnt <= 1024 )* M _! b' G! @# f& U/ d
- MotorCCW(); //反转4 V1 l0 K/ u* {* Q# q" M
- else$ H, R' h z0 B+ u: A& z
- {
( u1 U! E }7 l! Y - cnt = 0;
& Q2 Q7 O; @0 T6 N) Q - LED1_TOGGLE;
+ f) f2 s$ s+ o" a1 g( _, X - delay_ms( 500 );& \' h' Y% C4 F2 a
- }7 ]; I1 x1 C1 A5 Z. Z: }9 N
- }
1 G. `3 J- y7 o/ }+ _ - }
复制代码 ) e1 w' o) w6 y9 s) U+ y( f
这里采用4相8拍驱动,8个脉冲是一个周期,那么就需要4096/8=512个周期,外部轴刚好转一圈。计数器小于512时正转,计时器在512和1024之间时反转。然后LED灯翻转一次,在延时500ms。 这里还要注意一个问题,在电机驱动函数中,每个脉冲的延时时间经过实际测试不能小于2ms,延时太小的时候,电机抖动严重,但是不会转动。延时越大,电机转动越慢。6 V( B6 r& X+ A+ K+ h
$ N0 \) H7 `) P$ r$ ?8 X. A5 ]
到此电机驱动程序已经完美运行了。
# A& {; n. L1 l
* T1 D. g' a7 i+ O, |" c% [* {! W9 H$ y) P- [
|