参考文章与课程:
【视频课程】步进电机基础原理和应用——程子华主讲
【视频课程】电机系列教学视频(基于STM32硬件)——野火
【霄耀在努力】STM32驱动步进电机(原理、程序、解决电机只震动不转动问题)
步进控制系统由以下三个部分组成:
步进电机是一种特种电机,又称为脉冲电动机,是一种将电脉冲信号转化为角位移或线位移的开环(无反馈)控制元件。在非超载的条件下,电机的转速、角位移只取决于控制脉冲信号的频率和脉冲数。
“步进”的意义是电机转动遵从固定的步幅,即每一个控制脉冲来临,电机就转动一个步进角 θ \theta θ。步进角 θ \theta θ与电机本身的结构(和其拓展结构,例如减速齿轮可以减小步进角)有关。脉冲数越多,电机转动的角度就越大。同时,脉冲的频率越高,电机转速就越快,但不能超过最高频率,否则电机的力矩将迅速减小,电机停转。
下图所示的是较为常见的42步进电机。“42”的意思是该电机的外壳尺寸是42mm×42mm。可以在其转轴上加装减速齿轮实现减速功能,来增大输出力矩和减小步进角 θ \theta θ;也可以在将转轴替换为丝杠,常见于需要驱动设备直线运动的场合。
博主在这里使用的步进电机型号是28BYJ-48,是套件中常见的步进电机。它也是一种减速步进电机,内部的减速齿轮由塑料制成,具有重量轻,体积小,结构简单等特点。它常被用在监控探头的云台上。这种电机及其驱动模块如下图所示:
步进电机的分类方法非常多,按照不同的分类方法,步进电机可以被分为以下几种:
磁阻式:又称为反应式步进电机。转子采用软磁材料,一般式硅钢片,本身没有磁性,但极易被磁化。其特点是结构简单,步进角小(可达1.2°),但效率低,发热量大,可靠性难以保障,很早之前就被市场淘汰了。
永磁式:又称为PM步进电机,转子使用永磁性材料,通过改变定子线圈的磁极来驱动转子。内部的圆柱形转子外表均匀分布着N极和S极。一般都为两相,扭矩和体积都比较小。步进角 θ \theta θ一般为3.75°、7.5°、15°、18°,特点是步进角一般较大,力矩较小,精度比较低,发热小,结构简单,价格低廉,一般用在一些较为低端的产品中。今年来设备小型化,微型永磁式步进电机的应用范围也有了进一步的扩展,例如带可升降型的摄像头的手机。
混合式:定子由两个转子铁芯(一般是硅钢片)和一个磁钢(永磁体)组成,两个转子铁芯极性相反。它的特点是产生的力矩相较于永磁式步进电机更大,发热较小,效率高,转速相对较大,噪音低,步进角小等。两相混合式步进电机的步进角一般为1.8°,三相混合式步进电机的步进角一般为1.2°,五相混合式步进电机的步进角可以达到0.72°。它的相应速度快,适用于频繁启停的场合。
按照以上的一些分类方法,可以举出一些例子:
28BYJ-48就是一种常见的单极性五线四相步进电机,“单极性”指线圈中电流的方向是确定的,不可翻转;对应的,“双极性”指线圈冲存在两种不同地电流方向。
对于双极性步进电机和单极性步进电机,它们二者绕组极性的不同,它们的工作方式也略有差异。
单极性步进电机有共阴极接法和共阳极接法,两种接法对于控制信号而言只是控制信号的极性的不同。要控制电机的旋转方向,只需要将拍之间的导电顺序颠倒即可。接下来的几种驱动方式都采用共阴极接法为例说明,且电机为顺时针转动。
单相整步驱动:“单相”指每一拍只有一相导电,“整步”指每一拍走过的角度是相邻两相之间的一整步。如下图所示,四个相的导电顺序为: A → B → C → D → A → . . . A\rightarrow B \rightarrow C \rightarrow D \rightarrow A \rightarrow... A→B→C→D→A→...,依次循环,步距角 θ = 90 ° \theta=90° θ=90°。
双相整步驱动:“双相”指每一拍有两相同时导通,且在数字信号驱动下,两相线圈通电产生的磁场大小相等。四拍的导电顺序为 A B → B C → C D → D A → A B → . . . AB\rightarrow BC \rightarrow CD \rightarrow DA \rightarrow AB \rightarrow... AB→BC→CD→DA→AB→...,与单相整步驱动相比,双相整步驱动拥有更大的转动力矩(是单相整步驱动力矩的 2 \sqrt{2} 2倍)。
半步驱动:半步驱动方式实际上是单相整步驱动和双相整步驱动的结合。相较于前两者,半步驱动有更小的步距角(45°),八拍的导电顺序为: A → A B → B → B C → C → C D → D → D A → A → . . . A\rightarrow AB \rightarrow B \rightarrow BC \rightarrow C \rightarrow CD \rightarrow D \rightarrow DA \rightarrow A \rightarrow... A→AB→B→BC→C→CD→D→DA→A→...,其缺点为转动力矩不稳定,有可能会导致电机本身的震动或者驱动设备的动力不稳定等问题。
双极性步进电机中的线圈中的电流方向是双相的,通过配置 A + A^+ A+和 A − A^- A−, B + B^+ B+和 B − B^- B−的高低电平来控制电机的旋转。其原理与单极性步进电机类似,优点是相较于前者可以具有更大的转动力矩(可以通过配置一个线圈上的两端电压分别为+5V和-5V来使线圈上的电流增大),缺点是驱动电路和程序较为复杂。由于原理与单极性步进电机类似,以下不做过多赘述。
如果驱动电路可以改变每一相通电时的电流大小,就可以控制每一相产生的磁场大小。这样不仅能解决半步驱动时力矩忽大忽小的问题,还能使步距角进一步减小以达到更高的精度控制。
ULN2003是一个单片高电压(最高可达50V)、高电流(单个额定输出500mA)的达林顿晶体管阵列集成电路。 它是由7对NPN达林顿晶体管组成的,它的高电压输出特性和阴极钳位二极管可以转换感应负载。单个达林顿晶体管对的集电极电流为500mA,达林顿管并联可以承受更大的电流。
ULN2003可以作为继电器驱动器,字锤驱动器、灯驱动器、显示驱动器(LED气体放电),线路驱动器和逻辑缓冲器。ULN2003的每一对达林顿晶体管的基极都有一个2.7k的串联电阻,可以直接和TTL或者5V的CMOS装置连接。它实际上就是一个功率放大器,输出端具有较大的驱动能力(电流较大)。
ULN2003的芯片内部原理图和引脚定义图如下所示:
与28BYJ-48配套的ULN2003驱动模块原理图如下图所示:
该模块的电路原理比较简单,具体使用时IN1、IN2、IN3、IN4分别对应A、B、C、D四相,且都为高电平有效。输入某一相为高电平时对应相的LED指示灯亮起,标识该相目前输入为有效电平。
博主在写代码的时候饶了很多弯路……,最后也参考了一些网上的代码。参考的文章和课程在文章开头有所标识。IN1~IN4分别接STM32的PA0,PA1,PA2,PA3,驱动模块的电源(5V)直接连接到ST-Link的电源输入口上,驱动模块与STM32共地。接线图略。
Stepper.h
#ifndef __STEPPER_H_
#define __STEPPER_H_
// 电机的旋转方向
typedef enum
{
Forward = 0,
Reversal = 1
} RotDirection;
// 需要使用其他端口时,只需要更改以下的宏定义即可
// 这里需要保证四个输出端口同属一个GPIO
// 如果不能满足这一点,需要更改Stepper.c中初始化函数Stepper_Init和Stepper_RotateByStep中的一些变量名称
// 这里的宏定义是为了提高程序的可读性和可移植性,但使用stm32f10x.h中定义的原始名称也未尝不可
#define Stepper_CLK RCC_APB2Periph_GPIOA
#define Stepper_Output_GPIO GPIOA
#define Stepper_LA GPIO_Pin_0
#define Stepper_LB GPIO_Pin_1
#define Stepper_LC GPIO_Pin_2
#define Stepper_LD GPIO_Pin_3
void Stepper_GPIOInit(void);
void Stepper_Stop(void);
void Stepper_SingleStep(uint8_t StepNum, uint16_t Delay_Time_xms);
void Stepper_RotateByStep(RotDirection direction, uint32_t step, uint16_t Delay_Time_xms);
void Stepper_RotateByLoop(RotDirection direction, uint32_t Loop, uint16_t Delay_Time_xms);
#endif
Stepper.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Key.h"
#include "Stepper.h"
uint8_t STEP; // 用于存储电机正在走过的整步编号
/**
* @brief 步进电机输出端GPIO初始化函数
* @param 无
* @retval 无
*/
void Stepper_GPIOInit(void)
{
RCC_APB2PeriphClockCmd(Stepper_CLK, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStruct.GPIO_Pin = Stepper_LA | Stepper_LB | Stepper_LC | Stepper_LD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(Stepper_Output_GPIO, &GPIO_InitStruct);
GPIO_ResetBits(Stepper_Output_GPIO, Stepper_LA | Stepper_LB | Stepper_LC | Stepper_LD);
}
/**
* @brief 电机停转函数
* @param 无
* @retval 无
*/
void Stepper_Stop(void)
{
GPIO_ResetBits(Stepper_Output_GPIO, Stepper_LA | Stepper_LB | Stepper_LC | Stepper_LD);
}
/**
* @brief 4拍单相整步驱动函数
* @param StepNum 整步编号,0~3对应A~D
* @param Delay_Time_xms 每步旋转后延时时间x ms,用于控制步进电机速度(一般需大于等于2)
* @retval 无
*/
void Stepper_SingleStep(uint8_t StepNum, uint16_t Delay_Time_xms)
{
switch(StepNum)
{
case 0: // A
GPIO_WriteBit(Stepper_Output_GPIO, Stepper_LA, Bit_SET);
GPIO_WriteBit(Stepper_Output_GPIO, Stepper_LB | Stepper_LC | Stepper_LD, Bit_RESET);
break;
case 1: // B
GPIO_WriteBit(Stepper_Output_GPIO, Stepper_LB, Bit_SET);
GPIO_WriteBit(Stepper_Output_GPIO, Stepper_LA | Stepper_LC | Stepper_LD, Bit_RESET);
break;
case 2: // C
GPIO_WriteBit(Stepper_Output_GPIO, Stepper_LC, Bit_SET);
GPIO_WriteBit(Stepper_Output_GPIO, Stepper_LA | Stepper_LB | Stepper_LD, Bit_RESET);
break;
case 3: // D
GPIO_WriteBit(Stepper_Output_GPIO, Stepper_LD, Bit_SET);
GPIO_WriteBit(Stepper_Output_GPIO, Stepper_LA | Stepper_LB | Stepper_LC, Bit_RESET);
break;
default: break;
}
Delay_ms(Delay_Time_xms); // 延时,控制电机速度
Stepper_Stop(); // 断电,防止电机过热
}
/**
* @brief 步进电机按步旋转
* @param direction 电机旋转方向,可以是Forward(正传)或者Reversal(反转)
* @param step 电机转过的步数
* @param Delay_Time_xms 每步旋转后延时时间x ms,用于控制步进电机速度(一般需大于等于2)
* @retval 无
*/
void Stepper_RotateByStep(RotDirection direction, uint32_t step, uint16_t Delay_Time_xms)
{
for (uint32_t i = 0; i < step; i ++)
{
if (direction == Forward) // 电机正传
{
STEP ++;
if (STEP > 3)
{
STEP = 0;
}
}
else if (direction == Reversal) // 电机反转
{
if (STEP < 1)
{
STEP = 4;
}
STEP --;
}
Stepper_SingleStep(STEP, Delay_Time_xms);
}
}
/**
* @brief 步进电机按整数圈旋转
* @param direction 电机旋转方向,可以是Forward(正传)或者Reversal(反转)
* @param Loop 电机旋转的圈数
* @param Delay_Time_xms 每步旋转后延时时间x ms,用于控制步进电机速度(一般需大于等于2)
* @retval
*/
void Stepper_RotateByLoop(RotDirection direction, uint32_t Loop, uint16_t Delay_Time_xms)
{
Stepper_RotateByStep(direction, Loop * 2048, Delay_Time_xms);
}
Stepper_RotateByLoop
函数中Loop * 2048是博主根据28BYJ-48步进电机的性能参数列表计算和实践调试所得。这里博主使用四拍驱动方式,如果要使用8拍的半步驱动方式,2048应该改为4096。读者手中的28BYJ-48的齿轮减速比可能与博主的有所不同(1:16 or 1:64),根据测试,博主手头的28BYJ-48的参数如下表所示:
型号 电压 相数 步距角 减速齿轮减速比 最大空载启动频率 最大空载运行频率 28BYJ-48 5V 4 5.625° / 32 1:32 600Hz 1000Hz
Key.c
(Key.h
在这里省略,在头文件中将函数进行声明即可)#include "stm32f10x.h" // Device header
#include "Delay.h"
/**
* @brief 按键初始化函数
* @param 无
* @retval 无
*/
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 这里的速度是GPIO的输出速度,在输入模式下这个参数选择没有用处
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/**
* @brief 返回按下按键的值,若不按下按键默认返回0
* @param 无
* @retval KeyNum 按键对应的值,按下PB1按键返回1,按下PB11按键返回2
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0;
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) // 读取1端口的值
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); // 如果不松手,程序将在此等待
Delay_ms(20);
KeyNum = 1;
}
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) // 读取11端口的值
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); // 如果不松手,程序将在此等待
Delay_ms(20);
KeyNum = 2;
}
return KeyNum;
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "Stepper.h"
uint8_t KeyNum;
int main()
{
Key_Init();
Stepper_GPIOInit();
// Stepper_RotateByStep(Forward, 512, 3);
// Stepper_RotateByStep(Reversal, 512, 3);
// Stepper_RotateByLoop(Forward, 1, 3);
while(1)
{
KeyNum = Key_GetNum();
if (KeyNum == 1) // 按下PB1上的按键,步进电机正转一圈
{
Stepper_RotateByLoop(Forward, 1, 3);
}
if (KeyNum == 2) // 按下PB11上的按键,步进电机反转一圈
{
Stepper_RotateByLoop(Reversal, 1, 3);
}
}
}
原创内容,整理不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、机器学习方面的学习笔记~