历史上的今天
今天是:2024年12月31日(星期二)
2021年12月31日 | STM32F103单片机使用ULN2003驱动步进电机
2021-12-31 来源:eefocus
最近整理东西,突然发现以前买的一个步进电机模块,以前使用5V单片机驱动的。突然想试试能不能用STM32F103单片机的3.3V直接驱动ULN2003这个芯片。

试了一下ULN2003可以用3.3V电压直接驱动。下面分享下电机驱动的过程。
先看看实物图

单片机使用的是STM32F103C8T6最小系统。电机和驱动板是网上买的整套模块。
驱动板的原理图如下

步进电机使用的是5线4相直流减速步进电机 直径:28mm 电压:5V 步进角度:5.625 x 1/64 减速比:1/64
步距角:5.625 / 64 = 0.087度 (也就是说理论上,你给一个脉冲,电机外部轴转动0.087度)也就是说一个脉冲内部转子转5.625度,但是由于减速64倍,所以外部轴只转了0.087度那么外部轴要转一圈的话,需要360/0.087=4096个脉冲。采用4相8拍驱动的话,8个脉冲是一个周期,那么就需要4096/8=512个周期,外部轴刚好转一圈。
电机的驱动一般分为3种方法
一. 1相励磁法:每一瞬间只有一个线圈相通,其它休息。
(优点)简单,耗电低,精确性良好。
(缺点)力矩小,振动大,每次励磁信号走的角度都是标称角度。 1相励磁法 A->B->C->D
二、 2相励磁法:每一瞬间有两个线圈导通。
(优点)力矩大,震动小。
(缺点)每励磁信号走的角度都是标称角度。2相励磁法 AB->BC->CD->DA
三、 1-2相励磁法:1相和2相交替导通。
(优点)精度较高,运转平滑,每送一个励磁信号转动1/2标称角度,称为半步驱动。(前两种称为4相4拍,这一种称为4相8拍)
1-2相励磁法 A-->AB-->B->BC->C-->CD->D-->DA
下面就用代码来实现这三种方式
ULN2003这个小驱动板上IN1、IN2、IN3、IN4四个插针和单片机的 IO口直接用杜邦线连接,为了方便控制,首先定义好要使用的 IO口。就直接在LED的工程上修改。
首先新建一个bsp_motor.h的头文件,在头文件里面进行端口声明。
#define LA PBout(6) //A 相
#define LB PBout(7) //B 相
#define LC PBout(8) //C 相
#define LD PBout(9) //D 相
/* 定义电机连接的GPIO端口, 用户只需要修改下面的代码即可改变控制电机的引脚 */
#define LA_GPIO_PORT GPIOB /* GPIO端口 */
#define LA_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */
#define LA_GPIO_PIN GPIO_Pin_6
#define LB_GPIO_PORT GPIOB
#define LB_GPIO_CLK RCC_APB2Periph_GPIOB
#define LB_GPIO_PIN GPIO_Pin_7
#define LC_GPIO_PORT GPIOB
#define LC_GPIO_CLK RCC_APB2Periph_GPIOB
#define LC_GPIO_PIN GPIO_Pin_8
#define LD_GPIO_PORT GPIOB
#define LD_GPIO_CLK RCC_APB2Periph_GPIOB
#define LD_GPIO_PIN GPIO_Pin_9
这里使用PB口的6、7、8、9四个端口来控制,端口使用了位带定义的方法,这样用程序控制起来更方便。关于位带定义可以自己查阅相关资料。端口和时钟都使用了宏定义重新命名。这样以后需要更改IO口的时候,只需要在头文件中更改,不需要更改程序中的代码,方便程序移植。
下面开始编写驱动代码,首先使用1相励磁法,也就是使4个IO口轮流为高电平,可以直接使用位带操作,依次给LA、LB、LC、LD给高电平。但是这样写的话每一种驱动方式都要写一个驱动函数,为了使操作起来更方便,将这4个口的值组成一个字节,并存在数组中,通过下标轮流调用数组中的数就行。这里将LA作为字节得最低位bit0,将LD作为字节第3位bit3。
这样电机导通相序为 D-C-B-A时,定义数组中的值为
u8 phasecw[4] = {0x08, 0x04, 0x02, 0x01};
电机导通相序为 A-B-C-D 是,数组中的值为
u8 phaseccw[4] = {0x01, 0x02, 0x04, 0x08};
下面新建bsp_motor.c文件,并在里面编写一个函数来调用这个数组就行了
void MotorCW( void )
{
u8 i;
u8 temp = 0;
for( i = 0; i < 4; i++ )
{
temp = phasecw[i];
LD = ( temp >> 3 ) & 0x01; //取bit4的值
LC = ( temp >> 2 ) & 0x01;
LB = ( temp >> 1 ) & 0x01;
LA = ( temp >> 0 ) & 0x01; //取bit0的值
delay_ms( 2 );
}
}
通过一个for循环,依次读取数组中的值,然后根据A、B、C、D四相的位置,去读取对应位的值,直接赋值给 IO口。这样电机驱动的函数就写好了,在主程序中直接调用这个函数就可以控制电机转动了。
如果要改成 2相励磁法驱动的话,直接替换数组中的数据就行。
u8 phasecw[4] = {0x0c, 0x06, 0x03,0x09}; //正转 电机导通相序 DC-CB-BA-AD
u8 phaseccw[4] = {0x03, 0x06, 0x0c, 0x09}; //反转 电机导通相序 AB-BC-CD-DA
如果要改成 1-2相励磁法,依然是直接修改数组中的值就行。
u8 phasecw[8] = {0x08, 0x0c, 0x04, 0x06, 0x02, 0x03, 0x01, 0x09}; //正转 电机导通相序 D-DC-C-CB-B-BA-A-AD
u8 phaseccw[8] = {0x01, 0x03, 0x02, 0x06, 0x04, 0x0c, 0x08, 0x89}; //反转 电机导通相序 A-AB-B-BC-C-CD-D-DA
通过 1-2相励磁法控制的话,每次需要发送8个脉冲,也就是4相8拍,这样一个周期需要发送8个数组,要在for循环中将循环数量改为8。
void MotorCW( void )
{
u8 i;
u8 temp = 0;
for( i = 0; i < 8; i++ )
{
temp = phasecw[i];
LD = ( temp >> 3 ) & 0x01; //取bit4的值
LC = ( temp >> 2 ) & 0x01;
LB = ( temp >> 1 ) & 0x01;
LA = ( temp >> 0 ) & 0x01; //取bit0的值
delay_ms( 2 );
}
}
然后在主程序中直接调用这个函数就可以让电机转起来了。
int main( void )
{
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
Motor_GPIO_Config();
while ( 1 )
{
MotorCW();
}
}
这样看来驱动电机是很简单的用keil模拟的仿真波形
1相励磁法 D-C-B-A

2相励磁法 DC-CB-BA-AD

1-2相励磁法 D-DC-C-CB-B-BA-A-AD

波形和理论相符,说明驱动是正确的。
到这里应该就结束了,但是又手贱的给主程序加了个LED闪灯的程序,这一加出事了。
int main( void )
{
u16 cnt = 0;
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
LED_GPIO_Config();
Motor_GPIO_Config();
while ( 1 )
{
LED1_TOGGLE;
MotorCW();
delay_ms(20);
}
}
主程序中加了一个20ms的延时,同时让LED灯翻转。程序烧进去之后,发现电机不转了。这是怎么回事?赶紧看看仿真波形。

这一看发现了问题,A项的波形怎么高电平突然变长了?
u8 phasecw[4] = {0x08, 0x04, 0x02, 0x01};
数组中依次给的值是8、4、2、1,也就是D、C、B、A相依次为高电平,其他三相高电平只持续了2ms,但是A相的电平持续了20多ms,这是什么原因?仔细分析程序执行流程后发现了问题所在。在for循环中依次给4个相赋值,A相位最后一个赋值,当给A项赋值时,其他3项为低电平,A项为高电平,然后程序退出for循环,回到主程序中,此时又执行了20ms的延时,这时A项的电平还是最后一次赋值的高电平。将这20ms延时去掉后,电机正常运转,波形也正常了。也就是说就是这个延时影响了电机的转动。
那么主程序中就不能用延时了吗?LED闪烁功能都实现不了了吗?还能让一个电机驱动程序把单片机霸占了不成?这当然是不行了,一定要将这个问题的根源找到,既然是最后一次给A项赋值后,A项高电平没有复位,那么就手动让A项复位。强制让A项的电平归零。
在增加一个复位的函数
void MotorStop( void )
{
LA = 0;
LB = 0;
LC = 0;
LD = 0;
}
这个函数强制的将四相复位,然后在电机驱动的时候,每次执行完一个周期,就强制让这4项复位。
void MotorCW( void )
{
u8 i;
u8 temp = 0;
for( i = 0; i < 4; i++ )
{
temp = phasecw[i];
LD = ( temp >> 3 ) & 0x01; //取bit4的值
LC = ( temp >> 2 ) & 0x01;
LB = ( temp >> 1 ) & 0x01;
LA = ( temp >> 0 ) & 0x01; //取bit0的值
delay_ms( 2 );
}
MotorStop(); //一个周期转动结束后需要复位所有相,否则最后一项如果被设置为高电平后会持续维持高电平。
}
增加复位功能后,主程序中依然加上20ms延时,继续仿真查看波形。

这下波形正常了,下载程序后电机也缓慢转动起来了,LED灯也开始闪烁了。说明刚开始驱动时只想到了相位的逻辑,但是没有考虑到相位复位的问题,考虑问题还是不全面。
下来在主程序中加上电机正反转功能,让电机正转一圈,然后又反转一圈。
int main( void )
{
u16 cnt = 0;
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
LED_GPIO_Config();
Motor_GPIO_Config();
while ( 1 )
{
cnt++;
if( cnt <= 512 ) //采用4相8拍,512个周期刚好是一圈
MotorCW(); //正转
else if( cnt <= 1024 )
MotorCCW(); //反转
else
{
cnt = 0;
LED1_TOGGLE;
delay_ms( 500 );
}
}
}
这里采用4相8拍驱动,8个脉冲是一个周期,那么就需要4096/8=512个周期,外部轴刚好转一圈。计数器小于512时正转,计时器在512和1024之间时反转。然后LED灯翻转一次,在延时500ms。 这里还要注意一个问题,在电机驱动函数中,每个脉冲的延时时间经过实际测试不能小于2ms,延时太小的时候,电机抖动严重,但是不会转动。延时越大,电机转动越慢。
到此电机驱动程序已经完美运行了。
史海拾趣
|
EVC 提示错误 one or more file from the emulator for windows ce is missing.please ret 各位大虾们,帮帮菜鸟吧 刚装EVC,新建一工程编译后没问题,但出现报错 one or more file from the emulator for windows ce is missing.please retinstall emulator for window ce and try again。是什么意思啊?我重装好几次也是这样啊,难道与 ...… 查看全部问答> |
|
前断时间把wince上的usb VIDEO device做好了(是slave的不是host),现在老板又需要做USB AUDIO DEVICE. USB部分已经正常能够发送数据,在电脑上录音设备也能录到声音。但是要真正实现把CE设备的声音通过USB传到电脑上来播放还必须要去修改AUDIO驱 ...… 查看全部问答> |
|
怎么从/PROC/DEVICES中得到动态主设备号?脚本怎么样写,应该写在哪个文件里,怎么样在linux启动时运行这个脚本?谢谢! 怎么从/PROC/DEVICES中得到动态主设备号?脚本怎么样写,应该写在哪个文件里,怎么样在linux启动时运行这个脚本?谢谢!… 查看全部问答> |
|
求助:如何去除交流采样AD出来的声音信号结果中含有直流分量? 交流采样AD出来的声音信号结果中含有直流分量(可以认为是零漂),即交直流混合电平,感觉上好像这个直流分量会发生变化,可以排除是AD前面电路造成的问题,也就是说可以确认是AD输出的漂移造成的,我想是否可以用数字滤波的方法去掉它,如果可以, ...… 查看全部问答> |
|
现在重新测试,同样的板子,程序也一样。 用149一切正常, 换为2418,电流有1.3ma,而且会经常自动重启。。。 郁闷啊。。。。。。。 lierda的工程师们知道为什么吗?… 查看全部问答> |
|
最近正在做玩具智能车,不可避免的要用到单片机的捕获功能。 现想通过MC9S08QG8单片机实现单位时间捕获脉冲次数计数,来计算小车的速度。 问题卡在了捕获功能的实现上。 所用单片机:MC9S08QG8 8位机 实现功能 ...… 查看全部问答> |
|
MSP430 ADC12 使用P6.1没有反应啊!很急啊 谢谢 /********************************************************* 程序功能:将ADC对P6.1端口电压的转换结果按转换数据在液晶上显示 **********************************************************/ #include #include \"allfunc.h\" #include \" ...… 查看全部问答> |
|
箝位放大器 箝位放大器允许设计人员指定高(VCH)和低(VCL)输出箝位电压,以使输出信号箝位在特定电平。与传统的输出箝位器件相比,ADI公司独特的CLAMPIN™输入箝位架构显著改善了箝位性能,箝位区的箝位误差和失真极小。 共模线性化放大器 ...… 查看全部问答> |
|
在调试Z-stack 2007的协议栈里的SampleApp的例子中发现,用户事件初始化函数SampleApp_Init中协调器和终端都加入到了group 1了。我操作终端按键K1,协调器能收到数据。但是我操作协调器的按键K1,终端却不能收到数据。这是为什么呢 难道 ...… 查看全部问答> |




