一.预备知识
1.1 步进电机与驱动器
步进电机是一种将电脉冲信号转换成相应角位移或线位移的电动机。每输入一个脉冲信号,转子就转动一个角度或前进一步,其输出的角位移或线位移与输入的脉冲数成正比,转速与脉冲频率成正比。因此,步进电动机又称脉冲电动机。
目前市面上大多数步进电机一次脉冲产生的角度变化量为1.8°,步进电机需要通过驱动器与单片机连接,单片机通过向驱动器发送脉冲控制驱动器驱动电机。
HB556AC驱动器如下图

驱动器通过拨码开关控制细分数和电流大小,参考驱动器外壳上的表格。
细分数类似于步进电机分辨率扩大,例如若细分数为400,则驱动器收到400脉冲才能使步进电机旋转一周,电流大小是驱动器向步进电机发送的电流大小,通常与步进电机型号有关
1.2驱动器使用
1.2.1 驱动器接口
与驱动器型号有关,参考驱动器说明书,这里只给出简明使用说明,详细内容参考相关驱动器型号说明书,这里使用的是HB556AC
下图为驱动器的强电接口

如图红色部分为供电接口,需要注意的是直流电源可以直接连接
标有“电机”的四个接线口为连接步进电机的四线供电线路,需要与步进电机对应线路连接,与步进电机型号有关,通常情况下为黑A+ 绿A- 红B+ 蓝B-
名称 | 功能 |
---|---|
GND | 直流电源地 |
+Vdc | 直流电源正极,供电电压范围:直流 24~50Vdc,推荐 36Vdc工作。 |
A+、A- | 电机 A相线圈接口。 |
B+、B- | 电机 B相线圈接口。 |

如图的六个接口为控制信号接口,需要按一定规则与单片机连接,详见后文接线法。
名称 | 功能 |
---|---|
PUL+、PUL- | 脉冲信号:脉冲上升沿有效; PUL高电平时 4.5 |
DIR+、DIR- | 方向信号: 高/低电平信号, 为保证电机可靠换向, 方向信号应先于脉冲信号至少2μs建立。电机的初始运行方向与电机的接线有关,互换任一相绕组(如 A+、A-交换)可以改变电机初始运行的方向, DIR高电平时 4.5 |
ENA+、ENA- | 使能信号:此输入信号用于使能或禁止。 ENA+ 接 4.5~28Vdc,ENA-接低电平(或内部光耦导通) 时,驱动器将切断电机各相的电流使电机处于自由状态,此时步进脉冲不被响应。当不需用此功能时,使能信号端悬空即可。 |
此外,驱动器侧面有一红一绿两个指示灯,在正常情况下应该是绿灯常亮红灯常灭,其余指示灯信号对应的含义详见说明书。
1.2.2 接线法
驱动器控制信号接口需要按照一定的规则与单片机连接,通常有共阳极接线法和共阴极接线法。故名思与共阳极即将控制信号的阳极与公共正极并联,共阴极即将阴极与公共地并联。这里需要注意的是采用共阴极接线法时需要注意功率地与信号地的隔离。
HB556AC驱动器采用差分式接口电路可适用差分信号,单端共阴极及单端共阳极等接口,接线图如下

注意: VCC值为 4.5~28Vdc时, R短接或不接。
1.3 定时器与单脉冲
由于这里使用的步进电机驱动器控制步进电机需要通过pwm单脉冲实现,即发送一次单脉冲步进电机即步进一步,其转速与产生脉冲频率有关。
arr:自动重装载寄存器的值
psc:定时器频率
α为步距角(1.8°),x为驱动器细分倍数(设置为2,也就是说转动一圈需要400个脉冲,每个脉冲转动0.9度)
此时脉冲频率 = Fck_int(72MHZ) / ((arr+1)*(psc+1))
转速(r/min)= 脉冲频率 * 60 / ((360/α)*x)
1.4 梯形加减速
由于步进电机存在空载启动频率,也就是在没有负载的情况下能够正常启动的最大脉冲频率,如果脉冲频率大于该值,步进电机则不能够正常启动,发生丢步或者堵转的情况;或者也可以理解为由于步进脉冲变化过快,转子由于惯性的作用跟不上电信号的变化。所以要使用加减速来解决启动频率低的问题,在启动时使用较低的脉冲频率,然后逐渐的加快频率。
另一方面,由于步进电机高速状态下扭矩降低的特性,在存在负载的情况下使用步进电机在扭矩临界位置时会产生丢布,因此缓加减速就至关重要。
其中脉冲间隔时间(Tn)的动态调整是实现平滑速度变化的核心。
梯形速度曲线分为三个阶段(设总脉冲数为N):
- 加速阶段( 0≤n<N1 ) :脉冲间隔Tn逐渐减小,频率上升。
- 匀速阶段(N1≤n
- 减速阶段(N−N2≤n
其中:
- N1:加速段脉冲数
- N2:减速段脉冲数
- N:总脉冲数
1.4.1 加速阶段
设步进电机的角加速度为α(步数/秒²),速度v(t)(步数/秒)与时间t的关系为:
v(t)=v0+αt
初始速度v0=0时,简化为:
v(t)=αt
(2) 离散化处理
将时间离散化为脉冲时刻tn,第n个脉冲的瞬时速度vn为:
vn=Tn1
根据加速度定义:
vn=vn−1+αTn−1
代入vn=1/Tn,得到递推关系:
Tn1=Tn−11+αTn−1
(3) 近似简化
当加速度α较小且Tn变化缓慢时,可近似为:
Tn≈Tn−1−αTn−13
物理意义:
- 脉冲间隔的减小量(ΔT)与当前间隔的立方成正比。
- 初始阶段(Tn−1较大时),间隔缩短明显;接近匀速时变化趋缓。
1.4.2 匀速阶段
脉冲间隔恒定:
Tn=Tconst=vmax1
其中vmax为最大速度(由电机和驱动器性能决定)。
1.4.3 减速阶段
减速是加速的逆过程,角加速度为−α。递推公式变为:
Tn1=Tn−11−αTn−1
近似简化:
Tn≈Tn−1+αTn−13
综合三个阶段,脉冲间隔的通式可写为:

由于实时计算平方根开销较大,通常采用以下方法:
- 预计算表法:提前计算Tn表,存入数组,运行时查表。
- 迭代逼近法:使用递推公式Tn=Tn−1±αTn−13,通过浮点运算或定点数优化。
二.环境配置
2.1 单片机
此次使用的时stm32f405单片机,连接8MHz晶振,在RCC开启外部晶振以后时钟树配置参考下图

配置完时钟树后,需要开启定时器和pwm单脉冲,这里我使用定时器2通道4,即TIM2_CH4,1ms定时器,配置pwm单脉冲输出如下图


注意需要打开定时器中断
另外,需要依照单片机原理图打开两个GPIO以提供DIR信号和ENA信号
2.2 接线
采用共阴极接线法,顾名思义将步进电机驱动器上的阴极接在一起,阳极分别连接单片机对应引脚,PUL+需要连接TIM2_CH4,DIR+和ENA+连接之前打开的GPIO
详细接线法参考本文1.2章节介绍的驱动器接线法。
三.代码编写
为减轻单片机计算压力,在初次启动时候只计算一次c0,并且在发送单脉冲时才会启动定时器,代码参考如下
其中,需要判断加减速的两种形态,即梯形加减速和三角形加减速,在总步数不足以实现匀速阶段时需要三角形加减速
在定时器开启后自动发送一次pwm单脉冲
void start(float angle)
{
total_steps = angle / 1.8;
T1_FREQ = 0.676*10000/10;
//T1_FREQ = 1000000;
A_SQ = 2* 0.0314 *100000;
c0 = T1_FREQ * sqrt(A_SQ/accel);
//c0 = T1_FREQ * sqrt(2 * 1.8 /accel)/10;
//c0 = ((float)(0.676*T1_FREQ)) * sqrt(((float)(2*10*0.0314)) / accel);
n1 = speed * speed / (0.0628 * accel);
n2 = speed * speed / (0.0628 * decel);
n = n1 + n2;
HAL_TIM_Base_Start_IT(&htim2);
//period = (1000000 / speed) - 1;
__HAL_TIM_SET_AUTORELOAD(&htim2, c0);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, c0 / 2);
ct = c0;
if(n >= total_steps)
{
//三角形
mode = 1;
n1 = decel * total_steps / (accel + decel); //加速段步数
n2 = total_steps - n1; //减速段步数
}
else
{
//梯形
mode = 2;
ny = total_steps - n1 - n2;
cm = (10 * 0.0314 * T1_FREQ)/speed;
}
status = 1;
step_count = 1;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
//pwm输出定时器
if(htim == (&htim2))
{
if(step_count < total_steps)
{
//启动电机
if(mode == 1)
{
if(step_count <= n1)
{
ct = ct - (2 * ct)/(4 * step_count + 1);
//ct = ct / 10;
//__HAL_TIM_SET_AUTORELOAD(&htim2, ct);
//__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, ct / 2);
}
else
{
//减速阶段
ct = ct + (2 * ct)/(4 * n2 - 1);
n2--;
}
}
else
{
if(step_count <= n1)
{
ct = ct - (2 * ct)/(4 * step_count + 1);
}
else if(step_count > n1 && step_count <= (n1 + ny))
{
ct = ct;
}
else
{
ct = ct + (2 * ct)/(4 * n2 - 1);
n2--;
}
}
__HAL_TIM_SET_AUTORELOAD(&htim2, ct);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, ct / 2);
__HAL_TIM_ENABLE(&htim2);
HAL_TIM_OnePulse_Start(&htim2,TIM_CHANNEL_1);
step_count++;
}
}
}
3.1 完整代码
#include "main.h"
#include "start.h"
#include "math.h"
#define ACCEL_STEPS 100 // 加速步数
#define DECEL_STEPS 100 // 减速步数
#define MAX_SPEED 500 // 最大速度
#define MIN_SPEED 1000 // 最小速度
float step_count = 0.0f; // 当前步数
float total_steps = 0.0f; // 总步数
int current_speed = MIN_SPEED;
int status = 0; // 0停止 1加速 2匀速 3减速
float c0 = 0;
float cm = 0;
float ct = 0;
//参数设置
float accel = 2;
float decel = 2;
float speed = 20;
float min_delay = 0;
float A_SQ = 0;
float T1_FREQ = 0;
float ny = 0;
float n1 = 0;
float n2 = 0;
float n = 0;
float period = 0;
int mode = 0; //三角形或梯形
void start(float angle)
{
total_steps = angle / 1.8;
T1_FREQ = 0.676*10000/10;
//T1_FREQ = 1000000;
A_SQ = 2* 0.0314 *100000;
c0 = T1_FREQ * sqrt(A_SQ/accel);
//c0 = T1_FREQ * sqrt(2 * 1.8 /accel)/10;
//c0 = ((float)(0.676*T1_FREQ)) * sqrt(((float)(2*10*0.0314)) / accel);
n1 = speed * speed / (0.0628 * accel);
n2 = speed * speed / (0.0628 * decel);
n = n1 + n2;
HAL_TIM_Base_Start_IT(&htim2);
//period = (1000000 / speed) - 1;
__HAL_TIM_SET_AUTORELOAD(&htim2, c0);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, c0 / 2);
ct = c0;
if(n >= total_steps)
{
//三角形
mode = 1;
n1 = decel * total_steps / (accel + decel); //加速段步数
n2 = total_steps - n1; //减速段步数
}
else
{
//梯形
mode = 2;
ny = total_steps - n1 - n2;
cm = (10 * 0.0314 * T1_FREQ)/speed;
}
status = 1;
step_count = 1;
}
/*****************************************************************
* 函数功能: 定时器中断
* 输入参数: TIM_HandleTypeDef
* 返 回 值: 无
* htim2: pwm输出定时器
* htim3: 计数定时器
****************************************************************/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
//pwm输出定时器
if(htim == (&htim2))
{
if(step_count < total_steps)
{
//启动电机
if(mode == 1)
{
if(step_count <= n1)
{
ct = ct - (2 * ct)/(4 * step_count + 1);
//ct = ct / 10;
//__HAL_TIM_SET_AUTORELOAD(&htim2, ct);
//__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, ct / 2);
}
else
{
//减速阶段
ct = ct + (2 * ct)/(4 * n2 - 1);
n2--;
}
}
else
{
if(step_count <= n1)
{
ct = ct - (2 * ct)/(4 * step_count + 1);
}
else if(step_count > n1 && step_count <= (n1 + ny))
{
ct = ct;
}
else
{
ct = ct + (2 * ct)/(4 * n2 - 1);
n2--;
}
}
__HAL_TIM_SET_AUTORELOAD(&htim2, ct);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, ct / 2);
__HAL_TIM_ENABLE(&htim2);
HAL_TIM_OnePulse_Start(&htim2,TIM_CHANNEL_1);
step_count++;
}
}
}