
简介基于STM32单片机与THB612驱动的步进电机加减速控制源码包重点实现S型与T梯形曲线SpTA算法面向嵌入式开发者、电机控制工程师以及需要多路电机控制的CPLD/FPGA应用场景。压缩包约21.74MB以源码工程文件为主页面未列出具体文件总数与类型明细但目录组织便于直接移植与查阅。目前已有632人学习下载。SpTA算法相对传统S曲线具有更强自适应性不依赖PWM定时器个数可通过普通IO扩展多路电机控制S型算法支持启动频率、加速时间、最高速度、加加速频率等参数自定义同时内置梯形算法。实现中采用比DMA传输效率更高的数据搬运方式大幅减少CPU占用并能实时获取电机已运行步数解决普通DMA在外部中断下无法获得输出PWM波形个数的问题。1. STM32THB612 控制步进电机难的不是发脉冲而是让电机刹得住用 STM32 驱动 THB6128 步进电机驱动芯片如果只是给 STEP 引脚灌固定频率的脉冲电机当然能转但启动那一刻的冲击和停下来的过冲会立刻以失步和噪音的形式还给你。做过 3D 打印机或者桌面雕刻机的人都知道这个矛盾加速度调小了效率上不去调大了启动就丢步根子在于速度曲线没有规划。SpTA 加减速控制算法把梯形曲线的高平均速度和 S 型曲线的平滑过渡组合起来小位移走纯 S 轮廓大位移用 S 型头尾加梯形匀速段再由 STM32 的定时器逐拍输出精确脉冲。下面按 THB6128 接线、曲线数学模型、状态机实现和示波器验证这条链路完整走一遍42 和 57 步进电机都能直接套用。2. THB6128 硬件接线与电流设置把驱动基础一次做对THB6128 是两相混合式步进电机驱动芯片内部集成了 H 桥功率管、电流斩波比较器和细分译码逻辑。MCU 真正要控制的只有 STEP、DIR、EN 三条信号线STEP 上升沿让内部状态机前进一步DIR 电平决定方向EN 控制输出级使能。硬件上最容易翻车的往往不是波形而是电流档位没配对、细分设定和软件步数对不上、电源退耦不到位这一章把这三件事一次说清。2.1 THB6128 最小驱动电路与 STM32 引脚规划THB6128 的输入引脚是 3.3V 兼容的STM32 的 GPIO 推挽输出直接驱动即可。不过市面上的模块很多带光耦隔离光耦输入侧的限流电阻和供电电压如果和模块设计不匹配会出现信号看着正常、电机纹丝不动的怪问题。我一般先看模块原理图确认 STEP、DIR、EN 的有效电平尤其是 EN不同模块高有效和低有效的都有接反的后果是一上电电机就锁死方向完全不受控。以下是 HAL 库下的引脚初始化代码只处理 DIR 和 EN 两个普通 GPIOSTEP 由定时器复用输出// THB6128 控制引脚初始化STM32F103, HAL 库 // STEP - PA8, 由 TIM1_CH1 复用输出 PWM // DIR - PB1, 方向控制 // EN - PB2, 使能有效电平以模块图纸为准 GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Pin GPIO_PIN_1 | GPIO_PIN_2; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET); // 上电先拉 EN 无效电平这里 PA8、PB1、PB2 只是示例引脚实际按你的 PCB 布局选即可。GPIO 速度档用 LOW 就够STEP 是定时器输出不走这个初始化。TIM1 的复用映射在HAL_TIM_PWM_Init里自动完成不需要手动配置 AFIO。EN 先拉无效电平等方向设好、行程规划完成后再打开使能避免上电瞬间电机带着惯性跑掉位置。2.2 电流档位、细分设定与脉冲当量的换算THB6128 的电流不用电位器调而是 IO1、IO2 两个引脚组合出四档。档位直接决定输出力矩和芯片温升调试期放在中低档跑曲线时再按负载往上升。各档位的输出电流比例大致如下具体值以芯片手册为准IO1IO2电流档位调试建议00最低档首次上电验证接线01约 70% 档42 电机空载跑曲线10约 85% 档带负载调参11满档57 电机或重负载细分通过 CF1、CF2 两个引脚或模块上的拨码选择不同厂家丝印有差异。软件里真正关心的是多少个脉冲转一圈对 1.8° 的两相电机细分脉冲/圈600 rpm 时 STEP 频率1整步2002.0 kHz48008.0 kHz8160016.0 kHz16320032.0 kHz换算关系是脉冲频率 转速(rpm) × 脉冲数/圈 ÷ 60。16 细分下 300 rpm 就是 16 kHz意味着定时器每 62.5µs 要产生一个上升沿这个数直接决定后面中断函数的长度预算。速度、加速度、jerk 在代码里都以脉冲/秒为单位标定先把脉冲当量算清楚后续所有参数才有意义。2.3 电源退耦与地线处理步进电机绕组斩波时电流变化剧烈VM 电源线上的尖峰如果串回逻辑地STEP 引脚上会出现毛刺轻则多走一步重则方向乱跳。THB6128 的 VM 引脚附近至少要放 100µF 电解电容加 100nF 陶瓷电容芯片地和 MCU 地单点连接。示波器探头地线夹在逻辑地上量 STEP 波形如果看到高频振铃多半是电源退耦不够而不是算法问题。42 电机用 12V 供电57 电机直接上 24V逻辑电源和电机电源不要共用一个稳压器这是现场排查电机自己会走类问题时的第一检查点。3. 梯形与 S 型曲线模型SpTA 组合曲线的数学依据3.1 梯形加减速的三段式及其冲击问题纯梯形曲线把运动分成匀加速、匀速、匀减速三段。设启动速度 v0、巡航速度 vmax、加速度 a则加速时间 t1 (vmax − v0)/a加速段位移 s1 v0·t1 ½·a·t1²。如果总位移足够长曲线中间才有一段真正的匀速位移不够时曲线退化成三角形速度刚升上去就得立刻减下来。梯形曲线的优势是平均速度高同样距离下运动时间最短。问题出在折点上匀加速开始的瞬间加速度从 0 跳变到 a加加速度jerk趋近无穷大机械上表现为一次冲击。皮带上就是一顿机架上就是共振噪声脉冲频率越高越明显。这是梯形算法单独使用时最躲不开的短板。3.2 S 型曲线给加速度变化加一个斜坡S 型曲线的做法是把加速度的变化过程本身做成斜坡斜坡斜率就是 jerk J。加速段不再是直线上升而是加加速、匀加速、减加速三段。速度与时间的关系如下加加速段0 ≤ t ≤ t1 v(t) v0 ½·J·t² 匀加速段t1 ≤ t ≤ t2 v(t) v1 a·t 减加速段t2 ≤ t ≤ t3 v(t) v2 a·t − ½·J·t²其中 t1 a/J是加速度从 0 爬到峰值 a 的时间。J 越小速度曲线越圆滑机械冲击越小但同样速度增量消耗的时间越长。全程走 S 型意味着加速段后期加速度先归零才能进匀速减速段又要反向来一遍长距离搬运场景下效率损失相当可观所以工程上很少让整条曲线全程带 S。3.3 SpTA 的组合策略按行程长度路由曲线SpTAS-plus-T 组合加减速在工程上的通用做法不是让两条曲线二选一而是按行程长度路由。短行程总步数不够完成加速到巡航速度再立刻减速直接走纯 S 型轮廓速度到不了设定巡航值也没关系胜在无冲击、定位准适合 Z 轴对位和点动动作。长行程则用 S 型头尾过渡中间保留梯形匀速段速度曲线看起来就是S 型头 直线腰 S 型尾正好对应标题里S 型-T 梯形的组合含义。切换的核心是减速窗口的计算。刹车所需步数公式为 s_dec (vmax² − vstop²) / (2·a)状态机每走一步检查剩余步数是否小于等于 s_dec是则立刻进入减速段。这条判断在中断里只做一次减法和一次比较开销极小。三种曲线特性对比如下特性纯梯形纯 S 型SpTA 组合平均速度高低高启停冲击大小小实现复杂度低中中典型场景快速进给短行程定位通用默认补充一个标定时常遇到的低频陷阱梯形曲线在匀速段开始时加速度瞬间归零如果巡航速度恰好落在机械共振点附近振幅会被显著放大。S 型尾把加速度渐降到 0能明显压掉这一段这也是不建议直接用梯形跑全程的另一个原因。4. STM32 定时器落地 SpTA逐拍调度、状态机与查表优化4.1 PWM 周期决定频率更新中断负责逐拍调度控制步进电机需要两层配合硬件层面由定时器精确输出 STEP 波形软件层面在每个脉冲周期结束后决定下一拍的间隔。常见做法是用定时器 PWM 模式输出 STEPARR 决定脉冲周期CCR 固定在 ARR 的一半保证 50% 占空比定时器更新中断每周期触发一次在中断里调用 SpTA 状态机计算下一拍。// TIM3 输出 PWM 驱动 STEP72MHz 主频预分频 72计数单位 1us void motor_timer_init(void) { htim3.Instance TIM3; htim3.Init.Prescaler 72 - 1; htim3.Init.Period 1000; // 初始 1000us启动后逐拍修改 htim3.Init.CounterMode TIM_COUNTERMODE_UP; HAL_TIM_PWM_Init(htim3); TIM_OC_InitTypeDef oc {0}; oc.OCMode TIM_OCMODE_PWM1; oc.Pulse 500; // 对应 50% 占空比 oc.OCPolarity TIM_OCPOLARITY_HIGH; HAL_TIM_PWM_ConfigChannel(htim3, oc, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); // 启动 PWM 输出 HAL_TIM_Base_Start_IT(htim3); // 使能更新中断 }预分频 72−1 后计数频率为 1MHzARR 的值直接就是脉冲周期微秒数调试时心算方便。注意定时器的 ARR 和 CCR 都是预装载寄存器在更新中断里修改后从下一个周期才生效不会出现半个周期突变的问题。之后每拍修改 ARR 时CCR 必须同步改成新 ARR 的一半否则占空比会漂。更新中断回调里的逐拍调度逻辑// 更新事件每个 PWM 周期来一次即一个 STEP 周期完成 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance ! TIM3) return; if (spta.state SPTA_IDLE) return; spta.remaining--; // 剩余步数递减 if (spta.remaining 0) { spta.state SPTA_IDLE; __HAL_TIM_SET_AUTORELOAD(htim3, 0); return; // 行程结束停脉冲 } uint32_t period_us spta_next_period_us(); // 状态机算下一拍周期 __HAL_TIM_SET_AUTORELOAD(htim3, period_us); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, period_us 1); }remaining在规划阶段由总行程赋值每次更新中断减一减到 0 直接停定时器。spta_next_period_us()是状态机核心只读写结构体字段不做打印、不做延时、不做任何阻塞操作这是保证高速段不掉拍的前提。4.2 SpTA 状态机实现jerk 斜坡与减速窗口状态机把运动分成加速、匀速、减速三段。加速段和减速段里加速度不是常数而是按固定的jerk_step每个脉冲周期爬升或回落这样速度曲线就带上了 S 型头尾。状态机相关字段放在一个结构体里typedef struct { uint8_t state; // SPTA_IDLE / ACC / RUN / DEC uint32_t remaining; // 剩余步数 uint32_t speed; // 当前速度脉冲/秒 uint32_t speed_max; // 巡航速度 uint32_t speed_stop; // 启动/停止速度 int32_t acc; // 当前加速度脉冲/秒² uint32_t acc_max; // 峰值加速度 uint32_t jerk_step; // 每个脉冲周期的加速度增量 uint32_t decel_steps; // 减速窗口步数 } spta_t;每个 STEP 周期调用一次的核心函数// SpTA 状态机每产生一个 STEP 脉冲调用一次返回下一拍周期(us) uint32_t spta_next_period_us(void) { switch (spta.state) { case SPTA_ACC: spta.acc spta.jerk_step; // 加速度按 jerk 斜坡上升 if (spta.acc (int32_t)spta.acc_max) spta.acc spta.acc_max; spta.speed spta.acc; if (spta.speed spta.speed_max) { spta.speed spta.speed_max; spta.state SPTA_RUN; } break; case SPTA_RUN: if (spta.remaining spta.decel_steps) { spta.state SPTA_DEC; spta.acc 0; // 从加速度 0 开始反向 } break; case SPTA_DEC: spta.acc - spta.jerk_step; // 反向斜坡 if (spta.acc -(int32_t)spta.acc_max) spta.acc -(int32_t)spta.acc_max; spta.speed spta.acc; if (spta.speed spta.speed_stop) spta.speed spta.speed_stop; // 低于停止速度则钳位 break; } if (spta.speed 10) spta.speed 10; // 防除零 return 1000000UL / spta.speed; // 周期 us 1e6 / 速度 }jerk_step的物理含义是每个脉冲周期内加速度的增量同样的数值在低速段对应的实际 jerk 更小、高速段更大。标定时以启动段曲线形状为准经验初值取acc_max / 20顿挫明显就往acc_max / 30降。进入减速段前把acc清零很重要否则会带着加速段的加速度直接往下冲。行程规划函数负责计算减速窗口和短行程处理// 按梯形公式算减速窗口并加 10% 余量补偿 S 型尾 static uint32_t decel_window(uint32_t vmax) { uint32_t d (vmax * vmax - spta.speed_stop * spta.speed_stop) / (2 * spta.acc_max); return d d / 10 1; // 宁可提前减速不可刹不住 } void spta_plan(uint32_t total_steps, uint32_t speed_max, uint32_t acc_max) { spta.total total_steps; spta.remaining total_steps; spta.acc_max acc_max; spta.speed_stop BASE_SPEED_PPS; if (total_steps 2 * decel_window(speed_max)) { // 短行程反算可达巡航速度并留 5% 余量 speed_max (uint32_t)sqrtf(2.0f * acc_max * (total_steps / 2)); speed_max speed_max * 95 / 100; } spta.speed_max speed_max; spta.decel_steps decel_window(speed_max); spta.acc 0; spta.speed spta.speed_stop; spta.state SPTA_ACC; }减速窗口多算一点只会让电机提前进入减速最后几拍以停止速度爬行到位不会过冲少算则真正刹不住末端直接撞停。S 型尾的平均减速度低于峰值所以不能直接用理论公式这个 10% 余量是工程上必须的一步。sqrtf只在规划阶段调用一次不在中断里出现性能没有压力。典型参数起点如下参数含义初值16 细分、42 电机、12VBASE_SPEED_PPS启动/停止速度300 脉冲/sspeed_max巡航速度4000 脉冲/sacc_max峰值加速度12000 脉冲/s²jerk_step每拍加速度增量acc_max / 204.3 中断开销与查表优化速度拉高后更新中断进入频率随转速线性上升。按每次中断约 100 个时钟周期估算72MHz 下的开销占比巡航速度(脉冲/s)脉冲周期(us)每秒中断数100 周期开销占比40025004000.06%400025040000.6%1600062.5160002.2%5000020500006.9%16kHz 以下F1 系列做一次 32 位整数除法完全扛得住。真正要小心的是 Cortex-M0 内核的 F0 系列没有硬件除法指令软件除法慢一个量级这时候应该用查表法在规划阶段把加速段每一拍的周期算好放进 uint16 数组运行时中断里直接按下标取表减速段反向查同一张表。速度 16 脉冲/s 以上时周期都不超过 65535µsuint16 足够3000 项的表只占 6KB RAM。这也是把 S 型曲线参数微调和大批量生产一致性拉开差距的地方。急停处理有一个常见误区急停时只停定时器保留 EN 有效让电机以保持力矩停在原地是合理的但如果在急停同时把 EN 拉掉电机瞬间自由位置直接丢失。SpTA 只保证没丢步的前提下位置正确所以运行中留意高速段有没有啸叫和异常温升那才是丢步的前兆。5. 现场验证与参数标定示波器、逻辑分析仪加三个实测技巧5.1 用逻辑分析仪把速度曲线画出来示波器看 STEP 引脚只能判断间距在变看不出曲线是否成形。更直接的办法是用逻辑分析仪采样整个运动过程把上升沿时间戳导成 CSV用脚本按窗口统计脉冲数画成速度-时间曲线# 读逻辑分析仪导出的 step.csv统计 10ms 窗口内的 STEP 上升沿数 import numpy as np, matplotlib.pyplot as plt t [float(r.split(,)[1]) for r in open(step.csv) if Rising in r] h, _ np.histogram(t, binsint(t[-1] / 0.01)) plt.plot(h / 0.01) # 纵轴为脉冲频率等价于瞬时速度 plt.ylabel(STEP Hz) plt.show()CSV 里 Rising 事件行的时间戳列按你的导出格式调整。正确曲线应当呈现缓升—平直—缓降的轮廓启动段有明显台阶说明jerk_step太大匀速段比规划短很多说明减速窗口算多了按图反向调参即可。5.2 三个必调参数和现场判断症状优先调参数调整方向启停顿挫明显jerk_step减小从 acc_max/20 试到 /30高速啸叫、位移偏小acc_max / speed_max减小或把 VM 从 12V 提到 24V刹停后位置偶尔过冲decel_steps余量从 10% 提到 20%速度上到 16kHz 以上时中断里只保留查表、写 ARR、写 CCR 三个动作任何多余的判断都移出去。参数标定以曲线形状为准不要盯着理论值凑数电机本体、负载惯量和安装刚度不同同一组参数在不同机器上表现可能差异很大。5.3 共振频段的两个处理办法步进电机的机械共振频率由转子惯量和负载刚度决定42 电机整步通常在 200~400Hz 区间细分后脉冲频率同比放大。SpTA 的 S 型头以较高加速度快速穿过共振频段缩短振幅建立时间这是算法本身带来的优势但如果匀速段频率恰好落在共振点就把 speed_max 往上或往下挪 20%不要停在峰值上。做完这两步噪音和振纹会明显改善而状态机核心逻辑一行都不用动这也是 SpTA 相比纯梯形曲线在现场标定时最省事的地方。本文还有配套的精品资源点击获取