第07章 梯形算法电机控制
约 2009 字大约 7 分钟
2026-01-26
7.1 问题与策略
步进电机通过脉冲信号控制转速,但直接高速启动或停止会导致堵转(电机卡死)或丢步(转动不精准)。其核心原因是高频脉冲下电机内部反向电动势阻碍转子响应,且负载惯性与扭矩限制瞬时加速能力。因此需采用渐变脉冲频率的加减速策略:
加速阶段:从低于电机启动极限的基础频率开始,逐步提升脉冲频率。
恒速阶段:保持稳定频率运行;
减速阶段:逐步降低脉冲频率,避免惯性过冲。 加减速算法需匹配电机矩频特性(输出扭矩随频率下降),常见曲线包括直线(梯形)、指数、S型,其中S型曲线通过平滑加速度减少机械冲击,适用于高精度场景。
加减速控制策略对比表
| 控制类型 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 梯形加减速 | 脉冲频率线性增减,加速度恒定 | 控制简单,计算量小 | 高速时易因扭矩不足失步,机械冲击明显 | 轻负载、快速定位(3D打印机) |
| S型曲线加减速 | 加速度平滑变化,减少启动/停止时的加速度突变 | 机械冲击小,定位精度高,抑制共振 | 算法复杂,需高算力支持 | 高精度、重负载(CNC机床) |
| 闭环控制 | 通过编码器反馈实时调整脉冲频率,结合PID算法补偿误差 | 抗干扰强,精度高,适应负载变化 | 成本高,系统复杂度增加 | 高动态响应场景(自动化产线) |
7.2 梯形算法

7.3 开发实现
① 流程图

② 代码
Int_Motor.h
修改后如下:
#ifndef __INT_MOTOR_H__
#define __INT_MOTOR_H__
#include <math.h>
#include "gpio.h"
#include "tim.h"
#include "Com_Global.h"
#include "Com_Debug.h"
// 宏定义:加速度
#define MOTOR_ACC_SPEED 40000.0f
/**
* @brief 启动电机
*
*/
void Int_Motor_Start(void);
/**
* @brief 停止电机
*
*/
void Int_Motor_Stop(void);
/**
* @brief 设置电机速度
*
*/
void Int_Motor_SetSpeed(void);
/**
* @brief 获取已经旋转的角度
*
*/
void Int_Motor_GetCurrentAngle(void);
/**
* @brief 获取当前速度,单位转/分
*
*/
void Int_Motor_GetCurrentSpeed(void);
/**
* @brief TIM1更新中断触发后被调用的函数
*
*/
void Int_Motor_UpdateCallback(void);
#endif /* __INT_MOTOR_H__ */Int_Motor.c
修改后如下:
#include "Int_Motor.h"
// 定义静态全局变量:保存电机已经旋转角度对应的ITM1更新周期数
static uint32_t gs_current_angle_update_periods = 0;
// 定义静态全局变量:保存电机目标旋转角度对应的TIM1更新周期数
static uint32_t gs_target_angle_update_periods = 0;
// 定义静态全局变量:保存电机目标速度对应的TIM1更新周期数(周期数/s)
static uint32_t gs_target_speed_update_periods = 0;
// 定义静态全局变量:保存电机当前速度对应的TIM1更新周期数(周期数/s)
static uint32_t gs_current_speed_update_periods = 0;
// 定义静态全局变量:保存加速度所需时间(用周期数表示)
static uint32_t gs_acc_speed_update_periods = 0;
// 定义静态全局变量:保存是否需要匀速阶段, 1表示需要;0表示不需要
static uint8_t gs_need_const_speed_stage = 0;
// 静态函数:计算加速度所需时间(用周期数表示)
static void Int_Motor_CalcAccSpeedUpdatePeriods(void)
{
/*
公式: V1? - V0? = 2as
计算s;
s = (V1? - V0?) / 2 / a
*/
gs_acc_speed_update_periods = (gs_target_speed_update_periods * gs_target_speed_update_periods - gs_current_speed_update_periods * gs_current_speed_update_periods) / 2 / MOTOR_ACC_SPEED;
printf("加速度所需周期数:%d\n", gs_acc_speed_update_periods);
// 判断是否需要允许阶段; 如果加速度所需周期数大于等于总周期数的一半,不需要匀速阶段
if (gs_acc_speed_update_periods >= gs_target_angle_update_periods / 2)
{
gs_need_const_speed_stage = 0;
}
else
{
gs_need_const_speed_stage = 1;
}
}
// 静态函数:实时更新速度
static void Int_Motor_UpdateSpeed(void)
{
// 定义变量,保存下一个速度
uint32_t v1 = 0;
// 加速阶段: 已经旋转的周期数小于加速度所需周期数
if (gs_current_angle_update_periods < gs_acc_speed_update_periods)
{
// 公式:V1? - V0? = 2as; 这里s取1,该函数一个周期调用一次
v1 = sqrt(2 * MOTOR_ACC_SPEED * 1 + gs_current_speed_update_periods * gs_current_speed_update_periods);
}
// 匀速阶段:条件1:已经旋转的周期数 >= 加速所需周期数; 只要能执行到else,该条件一定是满足的
// 条件2: 剩余周期数 > 减速所需周期数(等于加速所需周期数)
// 条件3:需要匀速阶段
else if (gs_target_angle_update_periods - gs_current_angle_update_periods > gs_acc_speed_update_periods &&
gs_need_const_speed_stage)
{
// 匀速阶段,速度保持不变
v1 = gs_target_speed_update_periods;
}
// 减速阶段
else
{
// 公式: V0? - V1? = 2as; 这里s取1,该函数一个周期调用一次
v1 = sqrt(gs_current_speed_update_periods * gs_current_speed_update_periods - 2 * MOTOR_ACC_SPEED * 1);
}
// 限制V1范围
if (v1 > gs_target_speed_update_periods)
{
v1 = gs_target_speed_update_periods;
}
else if (v1 < MIN_TARGET_SPEED * 3200 / 60)
{
v1 = MIN_TARGET_SPEED * 3200 / 60;
}
// 更新当前速度
gs_current_speed_update_periods = v1;
//printf("Speed:%d\n", gs_current_speed_update_periods);
// 设置TIM1的自动重装载值
__HAL_TIM_SET_AUTORELOAD(&htim1, 1000000 / gs_current_speed_update_periods - 1);
}
/**
* @brief 启动电机
*
*/
void Int_Motor_Start(void)
{
// 1. 设置电机的旋转方向,目标角度是正数,设置为顺时针;目标角度是负数,设置为逆时针
if (g_target_angle > 0)
{
// 设置 MOTOR_DIR 为高电平
HAL_GPIO_WritePin(MOTOR_DIR_GPIO_Port, MOTOR_DIR_Pin, GPIO_PIN_SET);
}
else
{
// 设置 MOTOR_DIR 为低电平
HAL_GPIO_WritePin(MOTOR_DIR_GPIO_Port, MOTOR_DIR_Pin, GPIO_PIN_RESET);
}
// 2. 将目标速度(转/分)转换为对应的周期数(周期数/秒)
gs_target_speed_update_periods = g_target_speed * 3200 / 60;
// 3. 设置最小速度作为当前速度(初始速度); 在转为对应的周期数
g_current_speed = MIN_TARGET_SPEED;
gs_current_speed_update_periods = g_current_speed * 3200 / 60;
// 4. 设置TIM1的自动重装载值
__HAL_TIM_SET_AUTORELOAD(&htim1, 1000000 / gs_current_speed_update_periods - 1);
// 5. 计算目标角度对应的TIM1更新周期数
gs_target_angle_update_periods = fabs(g_target_angle) / 360 * 3200;
// 6. 清零已经旋转的角度对应的ITM1更新周期数
gs_current_angle_update_periods = 0;
// 7. 计算加速度所需时间(用周期数表示) !!!这一步一定一定一定要等待计算完gs_target_angle_update_periods
Int_Motor_CalcAccSpeedUpdatePeriods();
// 8. 启动电机,设置 MOTOR_EN 为高电平
HAL_GPIO_WritePin(MOTOR_EN_GPIO_Port, MOTOR_EN_Pin, GPIO_PIN_SET);
// 9. 启动TIM1定时器的更新中断和TIM1_Channel1输出比较
HAL_TIM_Base_Start_IT(&htim1);
HAL_TIM_OC_Start_IT(&htim1, TIM_CHANNEL_1);
}
/**
* @brief 停止电机
*
*/
void Int_Motor_Stop(void)
{
// 停止电机,设置 MOTOR_EN 为低电平
HAL_GPIO_WritePin(MOTOR_EN_GPIO_Port, MOTOR_EN_Pin, GPIO_PIN_RESET);
// 停止TIM1定时器的更新中断和TIM1_Channel1输出比较
HAL_TIM_Base_Stop_IT(&htim1);
HAL_TIM_OC_Stop_IT(&htim1, TIM_CHANNEL_1);
// 强制清零当前速度
gs_current_speed_update_periods = 0;
}
/**
* @brief 设置电机速度
*
*/
void Int_Motor_SetSpeed(void)
{
/*
定时器溢出(更新)2次,产生1个上升沿
转1圈,需要需要200*8个上升沿;合计需要3200个更新周期
TIM1
PSC=71; 1us计1个数
根据转速计算ARR值,转速用rpm表示(转速:转/分钟)
一圈所用时间: (1 / rpm) * 60 * 1000 * 1000 us
定时器1次更新(溢出)所用时间: (1 / rpm) * 60 * 1000 * 1000 / 3200 us
定时器ARR的值:(1 / rpm) * 60 * 1000 * 1000 / 3200 - 1
*/
// 设置当前速度为目标速度
g_current_speed = g_target_speed;
// 计算用周期数表示的速度
gs_current_speed_update_periods = g_current_speed * 3200 / 60;
gs_target_speed_update_periods = g_target_speed * 3200 / 60;
// 设置定时器ARR值
__HAL_TIM_SET_AUTORELOAD(&htim1, 1000000 / gs_current_speed_update_periods - 1);
}
/**
* @brief 获取已经旋转的角度
*
*/
void Int_Motor_GetCurrentAngle(void)
{
// 根据周期数计算已经旋转的角度
g_current_angle = gs_current_angle_update_periods / 3200.0f * 360.0f;
// 如果是逆时针转,将g_current_angle转为负数
if (g_target_angle < 0)
{
g_current_angle = -g_current_angle;
}
}
/**
* @brief 获取当前速度,单位转/分
*
*/
void Int_Motor_GetCurrentSpeed(void)
{
// 根据当前速度对应的周期数计算当前速度
g_current_speed = gs_current_speed_update_periods / 3200.0f * 60.0f;
}
/**
* @brief TIM1更新中断触发后被调用的函数
*
*/
void Int_Motor_UpdateCallback(void)
{
// 增加已经旋转角度对应的ITM1更新周期数
gs_current_angle_update_periods++;
// 实时更新速度
Int_Motor_UpdateSpeed();
// 如果已经旋转角度对应的ITM1更新周期数大于等于目标角度对应的TIM1更新周期数
if (gs_current_angle_update_periods >= gs_target_angle_update_periods)
{
// 停止电机
Int_Motor_Stop();
}
}App_Task.c
添加实时获取当前速度的代码:
/* 代码省略 ...*/
// 显示任务函数的实现
void disaply_task_callback(void *pvParameters)
{
/* 代码省略 ...*/
while (1)
{
// 如果是第0页,获取当前已经旋转的角度
if (g_current_page == 0)
{
// 获取当前已经旋转的角度
Int_Motor_GetCurrentAngle();
}
// 如果是第1页,获取当前速度
else if (g_current_page == 1)
{
// 获取当前速度
Int_Motor_GetCurrentSpeed();
}
/* 代码省略 ...*/
}
}
/* 代码省略 ...*/