STM32步进电机精准控制:角度与速度一体化实现梯形加减速
简介面向STM32F103C8T6标准库开发者的一份步进电机驱动程序专注解决电机精确角度定位与速度控制问题。资源共78个文件压缩包仅322KB内含35个H头文件、34个C源文件以及Hex固件、Keil工程uvprojx、STM32启动汇编文件、bat辅助脚本和说明文档源文件按IIC、Input、Output、SYSTEM、USER等模块组织便于直接导入项目或移植。已有969人学习下载。程序示例涵盖了硬件接线、GPIO推挽输出与定时器脉冲配置并实现全/半步进控制序列、脉冲频率调速、脉冲计数定位以及加减速平稳停止等关键逻辑可作为运动控制小车的速度环或位置环参考。配合板载24CXX与按键模块还能快速验证交互控制与参数存储等扩展功能。1. 为什么 STM32F103C8T6 驱动步进电机时角度和速度要放在一起算用 STM32F103C8T6 控制步进电机最常见的做法是拿两个 IO 口加一段延时函数让电机转起来再数几个循环当成角度。这个思路在空载匀速的场景下能应付但一旦换驱动器、换细分延时参数就全部失效。步进电机的角度和速度本质上是同一个变量一个脉冲对应一个固定步距角脉冲频率就是转速。把角度和速度拆开算才会出现“转得快但定位偏了”或“定位到了但转速不对”的现象。这篇按一个常见 .zip 工程的编写顺序从脉冲当量换算、定时器 PWM 生成、梯形加减速状态机到验证方法完整过一遍。适合正在调 F103C8T6 最小系统板配套步进电机驱动想让“能转”变成“转得准”的工程师。2. 步进电机角度和速度控制的理论基础脉冲当量、细分和梯形曲线2.1 脉冲当量换算决定定位精度步进电机定子绕组按顺序换电转子每次前进一个固定角度这个角度叫步距角。两相混合式步进电机常见的步距角是 1.8°也就是 200 步转一整圈。驱动器上的细分开关把它进一步拆成更小的步例如 16 细分后步距角变成 1.8° ÷ 16 0.1125°转一圈需要 3200 个脉冲。实际调试时我习惯说“脉冲当量”也就是一个脉冲走过的角度脉冲当量 步距角 ÷ 细分数单位是度/脉冲。目标角度 θ 对应的脉冲数 N 按下面公式算N θ × 细分数 ÷ 步距角如果目标角度是 90°1.8° 步距角、16 细分需要 90 × 16 ÷ 1.8 800 个脉冲。代码里建议先乘细分数再除以步距角避免浮点截断误差。下面这段转换成脉冲数的函数可以直接放到工程里#define STEP_ANGLE_DEG 1.8f #define MICRO_STEP 16 int32_t AngleToStep(float angle_deg) { return (int32_t)(angle_deg * MICRO_STEP / STEP_ANGLE_DEG 0.5f); }浮点在 F103 上可以跑但如果这个函数在加速曲线里频繁调用建议预先算好查找表中断里只做整数加减。角度换算必须在收到目标位置时执行一次不要在运动过程中反复用当前角度反算否则累计误差会让最后一步判断出错。2.2 脉冲频率和目标速度的换算关系速度的物理对应关系是脉冲频率。目标速度 V 常用每分钟转速 rpm 表示换算成每秒脉冲数的公式是f V × 360 × 细分数 ÷ (60 × 步距角)以 1.8°、16 细分、60rpm 为例f 60 × 360 × 16 ÷ (60 × 1.8) 3200Hz。也就是说定时器要稳定产生 3200 个脉冲每秒。定时器工作在向上计数 PWM 模式时输出频率由预分频 PSC 和自动重装载值 ARR 决定f 定时器时钟 ÷ [(ARR 1) × (PSC 1)]F103 定时器时钟通常配到 72MHz。把 PSC 设为 71计数时钟降到 1MHz则 ARR 1MHz ÷ f - 1。下表列出几组常用速度和细分下的参考值目标转速细分数脉冲频率 f1MHz 计数时的 ARR备注30 rpm161600 Hz624低速平稳60 rpm163200 Hz311常用演示速度120 rpm166400 Hz155提速明显注意失步300 rpm1616000 Hz61ARR 太小脉冲抖动增大ARR 越小定时器计数量化误差对速度稳定性的影响越大。比如 ARR61 时数值增减 1 会造成约 1.6% 的频率跳变而 ARR311 时只有 0.3% 左右。算出的 ARR 小于 50就要考虑提高 PSC 或降低细分不要让速度突变落在高频段。2.3 为什么匀速启动容易丢步步进电机的输出转矩随脉冲频率升高而下降。启动瞬间直接给一个高频脉冲序列转子磁场来不及跟上定子磁场变化就会丢步。同一台电机从 200Hz 起步再加速到 3200Hz 能稳定转直接上 3200Hz 可能只在原地抖动。因此电机控制普遍采用梯形速度曲线先加速、再匀速、最后减速。梯形曲线中每个脉冲周期频率上升的幅度就是加速度单位常用 Hz/s。后面第 4 章的状态机会反复用到这个参数。3. 在 STM32F103C8T6 上生成步进脉冲的最小工程定时器 PWM 与方向 IO3.1 驱动板接线与引脚分配步进电机驱动板常见接口是 PUL、DIR、ENA分别对应脉冲、方向、使能。以 F103C8T6 最小系统板为例我常用 PA8 作为脉冲输出复用 TIM1_CH1PA11 作为方向控制PB1 作为使能控制。表格里的引脚不是唯一选择关键是脉冲引脚必须落在带 PWM 输出的定时器通道上。STM32 引脚驱动板接口电平注意事项PA8PUL若驱动板光耦输入注意和信号地共地PA11DIR方向切换建议在脉冲停止时进行PB1ENA低电平使能具体以驱动板手册为准3.3V 或 5VVCC查驱动板光耦输入限流电阻很多驱动板的 PUL、DIR 输入兼容 3.3V但型号不同可能有差异。A4988、DRV8825 这类模块可以直接接 F103 的引脚ULN2003 四相五线板没有 PUL/DIR 接口只能用四个 IO 输出相位时序但角度和速度换算方式一样只是输出端从 PWM 换成相位状态机。3.2 用 TIM1 PWM 输出稳定脉冲不要在主循环里用 GPIO 翻转配合延时产生脉冲那样一旦进入中断脉冲间隔就会抖动。F103 的定时器 PWM 由硬件产生频率和占空比稳定而且可以在中断里随时修改参数。下面这段标准外设库代码把 TIM1 配成 PWM1 模式PA8 输出 50% 占空比方波void StepMotor_PulseInit(void) { GPIO_InitTypeDef g; TIM_TimeBaseInitTypeDef tb; TIM_OCInitTypeDef oc; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); g.GPIO_Pin GPIO_Pin_8; g.GPIO_Mode GPIO_Mode_AF_PP; g.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, g); tb.TIM_Prescaler 71; // 72MHz / 72 1MHz tb.TIM_CounterMode TIM_CounterMode_Up; tb.TIM_Period 624; // 初始约 1600Hz tb.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM1, tb); TIM_ARRPreloadConfig(TIM1, ENABLE); oc.TIM_OCMode TIM_OCMode_PWM1; oc.TIM_Pulse 312; // 50% 占空比 oc.TIM_OutputState TIM_OutputState_Enable; oc.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM1, oc); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_Cmd(TIM1, ENABLE); }这段代码先把 PA8 配置为复用推挽输出然后让 TIM1 的预分频寄存器写 71得到 1MHz 计数时钟。TIM_Period写 624意味着计数器从 0 数到 624 溢出周期是 625 个计数对应 1600Hz。TIM_Pulse是输出比较值设为周期一半可以得到标准方波。如果后续在中断里改 ARR需要同步修改 Pulse否则占空比会漂移。3.3 通过更新中断动态改变速度加速过程中的每个脉冲都要修改一次 ARR。利用 TIM1 更新中断每次 PWM 周期结束自动进入中断处理void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); Motor_OnStep(motor); } }Motor_OnStep里判断当前位置和目标位置然后调用TIM_SetAutoreload(TIM1, motor.arr_now)设置下一拍周期同时调用TIM_SetCompare1(TIM1, motor.arr_now / 2)保持 50% 占空比。ARR 的修改在下一个计数周期才生效所以不会把当前这半个脉冲截断。这里要特别注意中断服务函数里不要做浮点运算也不要做延时等待否则会错过下一次更新事件。4. 角度和速度程序的核心逻辑位置换算、速度设置和加减速状态机4.1 电机控制结构体设计工程里我会定义一个结构体把所有跟步进电机相关的状态放在一起typedef struct { TIM_TypeDef *tim; int32_t target_step; // 目标绝对位置 volatile int32_t current_step; // 当前位置 uint32_t start_step; // 本次运动起点 uint32_t arr_now; // 当前 ARR uint32_t arr_start; // 启动速度对应的 ARR uint32_t arr_target; // 目标速度对应的 ARR uint32_t step_acc; // 加速段步数 uint32_t step_dec; // 减速段步数 uint8_t dir; // 当前方向 } StepMotor_t;我习惯使用绝对位置驱动。外部传入目标角度后通过AngleToStep得到角度对应的步数再加上当前位置得到target_step。这样即使运动途中改变目标也只需要更新target_step状态机会自动判断还需要继续走多少步不需要重新设置起点和中点。4.2 梯形加减速参数计算梯形加减速的关键参数是加速段和减速段各自需要的步数。设启动脉冲频率为 f_start目标频率为 f_max加速度为 a单位是 Hz/s则加速过程走过的脉冲数约为S_acc (f_max² - f_start²) ÷ (2a)这个公式可以直接在电机启动前计算一次#define F_START_HZ 200 #define F_MAX_HZ 3200 #define ACCEL_HZPS 2000 uint32_t CalcAccSteps(uint32_t f_start, uint32_t f_max, uint32_t acc_hzps) { return (f_max * f_max - f_start * f_start) / (2u * acc_hzps); }如果总步数 N 小于 S_acc S_dec梯形曲线就变成三角形实际达不到目标频率。这时可以用简化处理加速步数和减速步数各取 N/2在到达中间点后立即减速。要求高的时候再计算三角形峰值频率。4.3 中断里运行的状态机下面这段代码放在定时器更新中断里每进来一次表示已经输出一个完整脉冲void Motor_OnStep(StepMotor_t *m) { uint32_t moved (uint32_t)(m-current_step - m-start_step); uint32_t remaining (uint32_t)(m-target_step - m-current_step); if (remaining 0) { TIM_Cmd(m-tim, DISABLE); // 到目标位置停止脉冲 return; } if (moved m-step_acc remaining m-step_dec) { m-arr_now GetNextArr(m-arr_now, ACCEL); } else if (remaining m-step_dec) { m-arr_now GetNextArr(m-arr_now, DECEL); } TIM_SetAutoreload(m-tim, m-arr_now); TIM_SetCompare1(m-tim, m-arr_now / 2); m-current_step (m-dir 1) ? 1 : -1; }这里的GetNextArr根据当前 ARR 和运动阶段返回下一拍的周期值。最简单的方式是让 ARR 按固定步长递减表示频率逐渐上升这种方式不是严格的匀加速但对大多数电机应用够用。如果追求线性加速度需要根据当前频率计算下一拍周期公式略复杂工程里常用预先生成的查找表替代。方向信号切换需要放在脉冲停止时。一般流程是先停定时器再改DIR引脚电平最后重新设定target_step并启动定时器。不要在脉冲运行中直接翻转方向否则驱动器可能把方向判断错。4.4 使用 HAL 库时的差异用 STM32CubeMX HAL 库时配置 TIM1 PWM 后启动方式改为HAL_TIM_PWM_Start_IT(htim1, TIM_CHANNEL_1);中断回调函数是void HAL_TIM_PWM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM1) { Motor_OnStep(motor); } }HAL 库在中断处理上比标准外设库多了一层封装回调里频繁调用TIM_SetAutoreload的效率稍低。如果速度达到 20kHz 以上建议直接用寄存器写入实现减少 HAL 函数调用开销。5. 参数怎么调启动速度、加速度、细分和实际负载5.1 启动速度选择启动 ARR 对应的脉冲频率决定了第一步的转速。频率太低每走一步时间过长整个行程耗时被拖慢频率太高第一步就丢步。常见两相混合式步进电机空载启动频率在 200Hz 到 500Hz。带丝杆或转台负载时从 50Hz 开始试。判断标准是电机第一步必须稳定转动不能听到闷响或高频抖动。场景启动脉冲频率参考加速度空载 42 电机300 Hz3000 Hz/s轻载丝杆滑台150 Hz1500 Hz/s重载转台50 Hz500 Hz/s表中数值只是起点。负载惯量越大加速度越低否则电机会因为加速转矩不足而堵转。5.2 加速度和短距离运动的关系加速度越高到达目标速度需要的距离越短但电机输出转矩必须覆盖加速所需的转矩。实际写程序时不能只在启动前计算一次减速点因为运动途中目标位置可能被上位机更新。我在每个脉冲中断里都用remaining和预计算的step_dec做比较保证无论目标怎样变化都能在正确的脉冲数之前进入减速阶段。常见误用是加减速步数靠人为凑结果在短距离运动中速度还没冲上去就强制减速产生很尖锐的减速冲击。这种情况要回头检查F_MAX_HZ是否设置过大。短行程时允许最高频率达不到设定值系统一样能完成定位。5.3 细分对角度和速度的影响细分调高角度分辨率提高低速振动减小。代价是同样转速下脉冲频率成倍上升。以 1.8° 步距角为例细分每转脉冲数60rpm 频率200rpm 频率200rpm 时 ARR816001600 Hz5333 Hz1871632003200 Hz10667 Hz933264006400 Hz21333 Hz46641280012800 Hz42667 Hz23从表里可以看到64 细分下 200rpm 时 ARR 只有 23定时器每计数一次频率变化接近 4.5%。F103 的 ARR 寄存器是 16 位虽然能写入 23但速度稳定性很差。所以高细分只适合低速高精度场景高速运动要适当降低细分才能同时保证脉冲频率和均匀度。6. 用逻辑分析仪验证脉冲时序避免盲调参数6.1 逻辑分析仪数脉冲和测频率接线时把逻辑分析仪通道接到 PA8地线接 F103 的 GND采样率设 1MHz 以上。让程序走一个固定角度例如 90°在分析软件里数上升沿数量。如果步距角 1.8°、16 细分90° 应该看到 800 个上升沿。多一个或少一个可以直接定位到脉冲计数逻辑或方向切换问题。频率验证看相邻上升沿间隔。设定 60rpm 时目标频率 3200Hz两个脉冲间隔约 312.5 微秒。如果测量到多个不同间隔交替出现说明 ARR 更新和 PWM 周期没有对齐优先检查中断里是否在更新事件后再写 ARR。6.2 用串口输出内部状态工程里保留一个 1ms 周期的状态定时器把current_step、target_step、arr_now和运行标志组成调试帧从 USART1 输出波特率 115200。把日志里的 ARR 换算成频率就能看到梯形曲线的实际形状。加速段频率应单调上升如果中间出现回跳说明减速点判断过早或remaining计算有符号问题。注意串口输出中断的优先级应低于步进脉冲更新中断。否则调试数据发送过程会阻塞脉冲产生测出来的曲线全是不规则抖动干扰判断。6.3 原点回归确认机械误差让电机从机械原点出发走若干圈后再回到原点程序里把绝对位置清零。如果每次都能停回同一个机械位置说明从 CPU 到驱动器的整条脉冲链没有丢步。这个方法比用角度尺量更可靠因为步进电机自身角度一致性很高误差主要来自脉冲计数和时序。如果回归时偏差一两个脉冲优先检查方向切换是否和脉冲上升沿重叠其次查 PUL 空闲电平是否和驱动板要求一致。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →