最近整理东西,突然发现以前买的一个步进电机模块,以前使用5V单片机驱动的。突然想试试能不能用STM32F103单片机的3.3V直接驱动ULN2003这个芯片。
" ]( S# V1 s# e# A4 i! Y0 [! H z( s E- N7 Y! x. e# J* b% {
% M4 c7 n5 s9 L9 E' m) Z* F# c
^$ l% a9 A9 w; X" y/ n3 a; H试了一下ULN2003可以用3.3V电压直接驱动。下面分享下电机驱动的过程。+ |( C r, E2 |2 ]0 B) O/ w. v
0 X3 ~* z. m Q! l; Y先看看实物图* J! F1 ^; y* ~" h) f% [9 |
) m8 a- Z0 F$ }' X. z0 x
" y( a( W0 A- Q0 Y
5 `( Z; ?% g7 M: c7 G/ v单片机使用的是STM32F103C8T6最小系统。电机和驱动板是网上买的整套模块。# X( e1 F; f+ F" s2 z" i- e0 W
) w# b G0 @0 e( x8 a4 T- [驱动板的原理图如下
3 [7 j' f) J" ^/ W/ e" n7 X+ m" i( p+ [$ ], m# O. }# }8 {& \
" V9 w7 }1 D$ i: |( a: H! n4 k" Q6 q( s
步进电机使用的是5线4相直流减速步进电机 直径:28mm 电压:5V 步进角度:5.625 x 1/64 减速比:1/64- X* q8 a8 h3 ? t4 M
) k& \, F* V/ x# T8 Q! ?2 R$ _) T
步距角:5.625 / 64 = 0.087度 (也就是说理论上,你给一个脉冲,电机外部轴转动0.087度)也就是说一个脉冲内部转子转5.625度,但是由于减速64倍,所以外部轴只转了0.087度那么外部轴要转一圈的话,需要360/0.087=4096个脉冲。采用4相8拍驱动的话,8个脉冲是一个周期,那么就需要4096/8=512个周期,外部轴刚好转一圈。& h) v: H4 r3 x6 ~/ |
8 L. f% i- E" A( n+ V' z& r" H; a
电机的驱动一般分为3种方法
2 j7 Q6 V5 G& J* O0 ?% l& r1 h/ X" C8 |- w% ^& H4 k
一. 1相励磁法:每一瞬间只有一个线圈相通,其它休息。! {( L6 w3 m3 e: D
3 k2 Z& v7 t3 w4 `/ W! p (优点)简单,耗电低,精确性良好。1 F+ l8 j* Y5 k0 q
- K" u8 H+ \; s9 c( q (缺点)力矩小,振动大,每次励磁信号走的角度都是标称角度。 1相励磁法 A->B->C->D
" t h9 L$ x/ o3 Y2 V! ~, L
. W5 w& ~& O- |& w" ] 二、 2相励磁法:每一瞬间有两个线圈导通。- _% h$ R. w- i1 L7 W7 O$ Z
t( c! l( S% y (优点)力矩大,震动小。
1 ~5 m' O' p: _7 [1 w j& |9 P9 Q
6 Y. |) ?6 H. o (缺点)每励磁信号走的角度都是标称角度。2相励磁法 AB->BC->CD->DA+ \* c" a- X p# X
& a1 V& e# l% P# `: g, y% x 三、 1-2相励磁法:1相和2相交替导通。. u, x- e/ d4 q, K
- h3 T* p$ S( Y$ d8 U3 b% j
(优点)精度较高,运转平滑,每送一个励磁信号转动1/2标称角度,称为半步驱动。(前两种称为4相4拍,这一种称为4相8拍) J! I9 P) e6 j) \
4 e% k/ |, M+ m' a7 T5 S 1-2相励磁法 A-->AB-->B->BC->C-->CD->D-->DA( h: m* E6 c" l
! [ t% [0 ~) x8 C& j2 }' P下面就用代码来实现这三种方式
( q+ B/ ?, Z2 N t. _
" J3 D+ a- ~$ f8 |8 x ULN2003这个小驱动板上IN1、IN2、IN3、IN4四个插针和单片机的 IO口直接用杜邦线连接,为了方便控制,首先定义好要使用的 IO口。就直接在LED的工程上修改。# k" n% j1 U0 h+ m, n. C
4 s' N& y5 W. D4 i, @4 h 首先新建一个bsp_motor.h的头文件,在头文件里面进行端口声明。
! G) V+ ~9 u- ^1 r3 W
+ Q7 x5 @. l1 X5 d" e- #define LA PBout(6) //A 相1 E1 T1 q8 w7 X+ e: R/ x
- #define LB PBout(7) //B 相
9 d/ A5 D# M0 o- ~4 | - #define LC PBout(8) //C 相
+ h+ l0 ~4 c$ d5 B. W' q - #define LD PBout(9) //D 相" w$ r8 j0 B/ M) L' F
4 S1 T' _1 v6 U9 g) X9 d) s* ^! J3 {- /* 定义电机连接的GPIO端口, 用户只需要修改下面的代码即可改变控制电机的引脚 */
/ B6 W0 U1 E/ l: X - #define LA_GPIO_PORT GPIOB /* GPIO端口 */
* v# @# J7 o* o- @, B# v - #define LA_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */
/ {* H" D8 c& T- M5 N& P X: } - #define LA_GPIO_PIN GPIO_Pin_6
& P- R. g, i s5 a" v/ Y - 6 r# w; |3 n4 [; j0 t9 R
- #define LB_GPIO_PORT GPIOB% C, a1 r% l$ b( D- z# H
- #define LB_GPIO_CLK RCC_APB2Periph_GPIOB' Y7 ]- k5 }2 E7 J
- #define LB_GPIO_PIN GPIO_Pin_7
' I: N( Z6 M$ C- ^( b$ w; h6 h
1 L/ i' n, a6 k$ x0 v9 c: a- #define LC_GPIO_PORT GPIOB
7 F* _* V2 Y3 y. S2 N - #define LC_GPIO_CLK RCC_APB2Periph_GPIOB8 ^. P+ U- f+ ^* O" t p
- #define LC_GPIO_PIN GPIO_Pin_8! \) G! \& p1 @. d% v+ Z
- ) ?. ]: v! D. [% J0 g5 v
- #define LD_GPIO_PORT GPIOB
, {1 h" K: B+ ?4 p$ R - #define LD_GPIO_CLK RCC_APB2Periph_GPIOB
0 h/ L o+ T2 d - #define LD_GPIO_PIN GPIO_Pin_9
复制代码
8 A& d# I$ y+ J: O" Z3 J 这里使用PB口的6、7、8、9四个端口来控制,端口使用了位带定义的方法,这样用程序控制起来更方便。关于位带定义可以自己查阅相关资料。端口和时钟都使用了宏定义重新命名。这样以后需要更改IO口的时候,只需要在头文件中更改,不需要更改程序中的代码,方便程序移植。
5 e- g g9 _. P9 I- Z
+ e5 A, y# p& _8 O 下面开始编写驱动代码,首先使用1相励磁法,也就是使4个IO口轮流为高电平,可以直接使用位带操作,依次给LA、LB、LC、LD给高电平。但是这样写的话每一种驱动方式都要写一个驱动函数,为了使操作起来更方便,将这4个口的值组成一个字节,并存在数组中,通过下标轮流调用数组中的数就行。这里将LA作为字节得最低位bit0,将LD作为字节第3位bit3。
& f# y7 X! v s" L2 q5 Z
" e# H. e- P& l 这样电机导通相序为 D-C-B-A时,定义数组中的值为) H4 k: ?* _ W) |9 z, [
# j f2 Y W% ]& D. I0 M! o- u8 phasecw[4] = {0x08, 0x04, 0x02, 0x01};
复制代码
! H. |% ~" C1 V; p, }/ i2 r 电机导通相序为 A-B-C-D 是,数组中的值为
h% y+ v& i" w; d1 O9 k z. t$ U$ a8 R! P
- u8 phaseccw[4] = {0x01, 0x02, 0x04, 0x08};
复制代码
) L, S" a" _; y: M7 h下面新建bsp_motor.c文件,并在里面编写一个函数来调用这个数组就行了' T0 g9 w7 U i2 e o, ~8 w& O
, e1 s. W5 }' P1 b9 O9 a; x$ p- N0 Z
- void MotorCW( void ) m$ u2 l- C! Q; R w( K& J
- {+ y) G$ U$ e5 h n" d% D8 T
- u8 i;
M4 S+ [' o8 t+ k - u8 temp = 0;
8 D5 A% C* ^, D# T& U% ^, J* t - for( i = 0; i < 4; i++ )
9 {, {8 c) R: g( X) V1 B1 z - {1 m4 S% I. Q( |$ X
- temp = phasecw;
1 S0 c9 g b5 J! q9 [7 a* B/ c2 P
" W+ ?; h, j* l- LD = ( temp >> 3 ) & 0x01; //取bit4的值
3 r5 t/ S7 E% E3 u ]0 L - LC = ( temp >> 2 ) & 0x01;
6 y$ u, L$ B( p - LB = ( temp >> 1 ) & 0x01;: p) c3 q. S1 ~3 o. i4 h2 R4 f0 k
- LA = ( temp >> 0 ) & 0x01; //取bit0的值- t' G7 L) `7 Z% ?% f7 M& {
- delay_ms( 2 );
T) S/ M# v, K - }
+ F1 k/ K. R2 p8 @; {* Z9 l2 L) f - }
复制代码 ) d' p6 Q* Q" P) E- e
通过一个for循环,依次读取数组中的值,然后根据A、B、C、D四相的位置,去读取对应位的值,直接赋值给 IO口。这样电机驱动的函数就写好了,在主程序中直接调用这个函数就可以控制电机转动了。
8 y/ ^( ?( o/ t1 ^) I
# G' b) X$ V7 h+ f. Q& B 如果要改成 2相励磁法驱动的话,直接替换数组中的数据就行。5 f( R1 Q) Q! n1 \8 T! r
+ b2 w( p! i+ u1 @- u8 phasecw[4] = {0x0c, 0x06, 0x03,0x09}; //正转 电机导通相序 DC-CB-BA-AD
1 d( [# c) }! ^5 @ - u8 phaseccw[4] = {0x03, 0x06, 0x0c, 0x09}; //反转 电机导通相序 AB-BC-CD-DA
复制代码
! C. P* q) y( L9 g. a 如果要改成 1-2相励磁法,依然是直接修改数组中的值就行。
( d0 M1 Y& W7 j+ j$ W. l
6 j. A: F. z, g5 f3 M- u8 phasecw[8] = {0x08, 0x0c, 0x04, 0x06, 0x02, 0x03, 0x01, 0x09}; //正转 电机导通相序 D-DC-C-CB-B-BA-A-AD) f6 ^- P: H6 B/ {7 ^8 Y- u4 ~
- u8 phaseccw[8] = {0x01, 0x03, 0x02, 0x06, 0x04, 0x0c, 0x08, 0x89}; //反转 电机导通相序 A-AB-B-BC-C-CD-D-DA
复制代码
& w2 F- R \- v0 `5 z% d2 i! X; I9 J 通过 1-2相励磁法控制的话,每次需要发送8个脉冲,也就是4相8拍,这样一个周期需要发送8个数组,要在for循环中将循环数量改为8。# N. `* n9 L5 P9 X: x' s1 P f+ {: g
4 H3 e, |4 B" D& F- void MotorCW( void )
8 ]2 s$ [ A o5 j - {. p- C' C" j1 D) ?2 ]9 l9 A6 D
- u8 i;
: T& N/ @2 s. l* g9 t - u8 temp = 0;
$ K( K2 G! N. D, L4 |( I7 Z4 k - for( i = 0; i < 8; i++ )
( C2 @/ a6 s+ | - {4 `- Y) v! K4 {
- <i> temp = phasecw;
V1 G# l& B6 j1 M1 @- K2 I+ ^8 l - LD = ( temp >> 3 ) & 0x01; //取bit4的值
/ }! `* V+ `3 y) v - LC = ( temp >> 2 ) & 0x01;$ U% r6 f/ B0 O; K, F8 ?5 w: o% ~ w
- LB = ( temp >> 1 ) & 0x01;2 `! j7 k( Z6 Q
- LA = ( temp >> 0 ) & 0x01; //取bit0的值# A5 V7 N' {- g
- delay_ms( 2 );
! e/ Y0 |. c0 }1 G - }
u' Q* m$ a* B - }</i>
复制代码
6 m/ E+ N* U' y3 \ t) p" o然后在主程序中直接调用这个函数就可以让电机转起来了。0 p( U6 E& ^0 `
/ r: J+ l9 X* ^
- int main( void )) n, V" ~1 r: \3 ]- h
- {
# A( K2 ]3 k" y$ H" W! r0 T
9 J: t5 W$ Y3 G; w0 I9 o& I! V& p$ d; }- NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );5 q8 g7 _4 @; Z" q
% m' u1 Q1 h" {( f- Motor_GPIO_Config();. O$ n7 I. i$ ~* ~) H3 F5 Z
- while ( 1 )$ B; G4 _) p% _; B$ }
- { - x9 c' c) \+ J9 U l! M& m% G
- MotorCW();
- b6 ?$ f( G) J9 w" i - }' v5 e! N4 T; X+ |
- }
复制代码 5 u- Q& I1 z, l7 `8 ?' f# U3 n5 |
这样看来驱动电机是很简单的用keil模拟的仿真波形% r1 ~8 ^) p1 ?0 \* @: G
8 N& S+ `/ k9 y5 O& z
1相励磁法 D-C-B-A
7 F2 L: e: z& m' H X a& a0 c' o- @* h; y0 l) h
# [! q( r, b5 g9 e$ k. o7 N
: ~% |" N( {, ]. p' N0 [2相励磁法 DC-CB-BA-AD+ h! C. ^3 g) L$ F1 }: k
& M% O% E8 U6 o& \* i
; F% `5 h4 x3 e* Y# D
, A! L6 r0 a8 n7 B+ g+ K1-2相励磁法 D-DC-C-CB-B-BA-A-AD+ a% b- a, T4 _1 T# S# g! G* L
0 {; x7 Z) o% C+ ~+ L- }
, D/ v3 ]& a. b+ W/ N; G: ]7 O: K2 _. N
波形和理论相符,说明驱动是正确的。
5 \# b! l) g/ E# O8 i8 x4 D& v* d% p- D
到这里应该就结束了,但是又手贱的给主程序加了个LED闪灯的程序,这一加出事了。
v" ?# A2 |. H2 x3 D- y# |: G8 M9 s j7 |8 I1 N9 ?' y9 T
- int main( void )' y" ^% R3 f8 I L0 f. m/ ~
- {0 A+ i4 w9 k& f4 v: N! _3 W, _
- u16 cnt = 0;
4 z7 z2 C. z9 P$ U5 A - NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
) t0 q/ X/ [8 \% h - LED_GPIO_Config();
% z$ D. o$ b9 ~ M! I' g - Motor_GPIO_Config();
# \* r/ R# Y8 w' B - while ( 1 )
6 A# \# M L4 C7 T3 i3 @1 |4 `! | - {: O9 O" x0 x( E$ B0 ?) L6 B
- LED1_TOGGLE;. A7 c* g' _0 d- q1 A
- MotorCW();
% [, l2 V% t1 N - delay_ms(20);2 T) j) ?$ X0 C. ^( g1 _1 z
- }
/ s4 [( q4 W+ w* r2 L0 `" r - }
复制代码 l S5 X# }$ P# o2 ^8 G7 N
主程序中加了一个20ms的延时,同时让LED灯翻转。程序烧进去之后,发现电机不转了。这是怎么回事?赶紧看看仿真波形。8 R' ~. k( M) n+ D) Y; [, o
. V7 I! z( J6 W- G/ q6 Z. t
; G0 c" B! E4 [, n; t0 G9 p' x
2 ^ w8 y- h: o7 ~6 J ~ 这一看发现了问题,A项的波形怎么高电平突然变长了?
; E S' d2 x3 {' w" X0 q, y- L1 Q9 H( Q& U7 ~; G8 y4 v8 u' S
- u8 phasecw[4] = {0x08, 0x04, 0x02, 0x01};
复制代码 & ?1 D/ Z% U( L8 z
数组中依次给的值是8、4、2、1,也就是D、C、B、A相依次为高电平,其他三相高电平只持续了2ms,但是A相的电平持续了20多ms,这是什么原因?仔细分析程序执行流程后发现了问题所在。在for循环中依次给4个相赋值,A相位最后一个赋值,当给A项赋值时,其他3项为低电平,A项为高电平,然后程序退出for循环,回到主程序中,此时又执行了20ms的延时,这时A项的电平还是最后一次赋值的高电平。将这20ms延时去掉后,电机正常运转,波形也正常了。也就是说就是这个延时影响了电机的转动。
0 U ~$ Q4 ]2 E, t8 e$ L R7 I& H( h* ~: \" z
那么主程序中就不能用延时了吗?LED闪烁功能都实现不了了吗?还能让一个电机驱动程序把单片机霸占了不成?这当然是不行了,一定要将这个问题的根源找到,既然是最后一次给A项赋值后,A项高电平没有复位,那么就手动让A项复位。强制让A项的电平归零。
' {2 [5 t$ ^) a) T
( ]8 N6 q0 y0 a, U$ ]" u 在增加一个复位的函数5 t4 B7 p4 @" t/ ?
, N) G5 N( g5 B4 A, I# r( v- k
- void MotorStop( void )7 F8 I; @, B, {: a( z y
- {
1 l: n2 u6 w& I$ N; m - LA = 0;
# d- J. a" J5 N0 w- m - LB = 0;7 \" \/ O6 X. E* ~
- LC = 0;' a/ y# Y- a& o: w
- LD = 0;
% K7 N) z/ }: E5 a9 A - }
复制代码
# L3 Z5 `+ m0 n- i1 f) ? 这个函数强制的将四相复位,然后在电机驱动的时候,每次执行完一个周期,就强制让这4项复位。
" M/ H2 v O9 f0 q1 o* j
0 G5 F5 i4 r$ t3 F8 w- void MotorCW( void )
0 f! v1 M; U+ {2 @ D# ^& u - {
0 g1 O d* M4 }9 J8 p - u8 i;
, U8 v( J8 d8 t- k' X - u8 temp = 0;
/ j# A3 f7 a& ?; `. `2 j* q( R - for( i = 0; i < 4; i++ )0 B3 A: [5 b& z# f
- {- d6 ^4 F9 t% s; D& o* m( o
- temp = phasecw;. ]/ }+ M7 N9 y
) M9 a5 p2 V: E8 R* m! D7 m- LD = ( temp >> 3 ) & 0x01; //取bit4的值
; i% N3 e: z h2 T - LC = ( temp >> 2 ) & 0x01;/ F- T$ d- w {! h2 d8 T
- LB = ( temp >> 1 ) & 0x01;
# ?7 k$ q3 E- P: d, T - LA = ( temp >> 0 ) & 0x01; //取bit0的值
7 z" c& f* B% u8 d4 ^2 y - delay_ms( 2 ); 7 g# z3 j' O9 D) V4 P1 {0 B
- }
9 E( u& ~! `: z" d+ l' v - MotorStop(); //一个周期转动结束后需要复位所有相,否则最后一项如果被设置为高电平后会持续维持高电平。- j# v2 d9 B* E4 R/ Z
- }
复制代码
* ~2 P$ \" {& S4 C6 P8 r 增加复位功能后,主程序中依然加上20ms延时,继续仿真查看波形。! F) K7 ~) j7 k1 V: S9 e
: }1 v, \! V# G- I* T. g( n
' U5 n9 S% N' r. W* U) f! s4 I, S8 G: `, O
这下波形正常了,下载程序后电机也缓慢转动起来了,LED灯也开始闪烁了。说明刚开始驱动时只想到了相位的逻辑,但是没有考虑到相位复位的问题,考虑问题还是不全面。$ H, k" `6 m8 T5 M. L
7 T, U6 _3 D) S, ?# e2 u {( o( b 下来在主程序中加上电机正反转功能,让电机正转一圈,然后又反转一圈。7 ~7 W; ]+ K* T" V$ ]: b8 B! l3 s
* P1 r1 N' K( ~5 z t' j- int main( void )
! u ?3 M# V3 D) e4 b: {, b | - {
6 i- h) T* {4 u7 U4 L, { - u16 cnt = 0;
; p; J) e+ |) V - NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );3 C) G6 L; _) e9 Y2 D
- LED_GPIO_Config();
$ o( _; N% S3 I5 o0 B, @: t& d7 P
B; n' z+ E* x5 F. Y& J1 U- Motor_GPIO_Config();* E. T0 l3 ^3 v% X7 E
- while ( 1 )& \' `2 {; C& Y& m7 G, e( U F+ w
- {9 \- d2 v2 r6 Y8 ?6 T
- cnt++;# E3 x0 q; S0 J" y
# j+ U: |/ U) L3 v" y- if( cnt <= 512 ) //采用4相8拍,512个周期刚好是一圈6 W3 A. N8 O' _
- MotorCW(); //正转; X' L$ q9 l/ W
- else if( cnt <= 1024 ), `& N3 Z$ Y! a: t8 L' Y3 d
- MotorCCW(); //反转
' J" r ]& l( R9 u1 U6 g - else
, \' r- R0 V* U4 d9 ?) Z8 Z1 ` - {
2 x/ j' M1 T3 b1 u - cnt = 0;
' H0 J$ R& Y. w* d4 T- |) ~" u - LED1_TOGGLE;
0 w) c5 K! _# f4 A - delay_ms( 500 );
4 x0 O# K+ x6 z0 V% I, m - }
6 l( `* _6 O2 M - }
) m1 X9 ^) w! D3 e - }
复制代码 1 I5 B5 I) H" z) p
这里采用4相8拍驱动,8个脉冲是一个周期,那么就需要4096/8=512个周期,外部轴刚好转一圈。计数器小于512时正转,计时器在512和1024之间时反转。然后LED灯翻转一次,在延时500ms。 这里还要注意一个问题,在电机驱动函数中,每个脉冲的延时时间经过实际测试不能小于2ms,延时太小的时候,电机抖动严重,但是不会转动。延时越大,电机转动越慢。
; G. r9 ~* s* {0 Z1 c
/ u/ ^$ O, A0 E+ U7 h) I 到此电机驱动程序已经完美运行了。
1 W0 `7 |& o2 k6 |/ g" M0 d& _# |- Y2 Z9 h7 M
' d ]' q: N0 Z/ m* D# g |