脉冲输出性能优化:频率精度、加减速曲线与抖动抑制
脉冲输出,说白了就是运动控制卡跟电机驱动器之间的“对话语言”。你发的脉冲准不准,直接决定了电机走位精不精。我这些年调试过的设备,十有八九的问题都出在这个环节上。今天咱们就把脉冲输出的三个核心问题掰开揉碎了讲。
一、脉冲频率精度控制
先问个问题:你发的 100kHz 脉冲,实际到驱动器那里真的是 100kHz 吗?
很多工程师觉得,只要定时器中断里翻转 IO 就行了。嗯,没那么简单。我见过一个项目,客户说电机跑着跑着就丢步,查了半天,原来是脉冲频率波动太大,驱动器内部滤波直接把部分脉冲当噪声滤掉了。
核心原则:脉冲频率精度 = 定时器时钟源精度 + 中断响应抖动 + 软件补偿算法
1.1 定时器时钟源选择
我个人习惯,优先用专用的 PWM 定时器,而不是通用定时器。为什么?
- 专用 PWM 定时器:硬件自动装载比较值,CPU 几乎不参与,频率稳定
- 通用定时器:每次中断都要软件重装载,中断延迟直接导致频率抖动
举个例子,我用 STM32 的 TIM1 做脉冲输出,时钟源选内部 PLL 倍频到 180MHz,分频后做 1MHz 基准。这时候发 100kHz 脉冲,理论误差是 0。但如果你用 SysTick 做,每次进中断再翻转,误差至少 0.5% 起步。
| 时钟源方案 | 典型精度 | CPU 占用 | 适用场景 |
|---|---|---|---|
| 专用 PWM 定时器 | ±0.01% | 几乎为 0 | 高速高精度运动 |
| 通用定时器中断 | ±0.5%~2% | 中等 | 低速或非关键运动 |
| DMA + 定时器 | ±0.05% | 低 | 中等速度,波形灵活 |
1.2 频率补偿算法
实际晶振总有温漂,尤其是工业现场温度变化大的时候。我建议加一个简单的频率补偿:
// 频率补偿示例
// 实际频率 = 目标频率 * (1 + 补偿系数)
// 补偿系数通过测量实际晶振频率与标称值的偏差得到
float freq_compensation(float target_hz) {
static float comp_factor = 0.0f;
static int calibrated = 0;
if (!calibrated) {
// 测量 1 秒内实际脉冲数
uint32_t measured = measure_pulses_in_1s();
comp_factor = (float)REFERENCE_HZ / measured - 1.0f;
calibrated = 1;
}
return target_hz * (1.0f + comp_factor);
}
我的经验:补偿系数建议每 10 分钟重新校准一次。工业现场温度变化没那么快,10 分钟足够跟踪温漂了。别每秒钟都校准,反而引入噪声。
二、加减速曲线算法
电机启动时直接给目标速度,你想想看会发生什么?轻则丢步,重则堵转。加减速曲线就是解决这个问题的。
我做过一个贴片机项目,最开始用梯形加减速,结果贴装头到终点时总是过冲。后来换成 S 形曲线,问题就解决了。选什么曲线,得看你的应用场景。
2.1 梯形加减速
梯形曲线最简单,加速段和减速段都是恒加速度。适合对启停时间要求不高的场合。
// 梯形加减速:速度分段计算
// t_acc: 加速时间, t_run: 匀速时间, t_dec: 减速时间
// v_max: 最大速度, a: 加速度
float trapezoid_speed(float t, float t_acc, float t_run, float t_dec, float v_max, float a) {
if (t < t_acc) {
// 加速段
return a * t;
} else if (t < t_acc + t_run) {
// 匀速段
return v_max;
} else if (t < t_acc + t_run + t_dec) {
// 减速段
return v_max - a * (t - t_acc - t_run);
} else {
return 0.0f;
}
}
注意:梯形曲线在加速结束和减速开始的瞬间,加速度突变。这会导致机械冲击。我曾经在一个精密平台上用梯形曲线,结果平台振动幅度直接超了规格书 3 倍。
2.2 S 形加减速
S 形曲线加了一个加加速度(Jerk)控制,让加速度平滑变化。说白了就是让速度变化更“温柔”。
我个人习惯用 7 段式 S 形曲线:加加速、匀加速、减加速、匀速、加减速、匀减速、减减速。每一段都有对应的加加速度值。
// S 形曲线:7 段式速度计算(简化版)
// jerk: 加加速度, a_max: 最大加速度, v_max: 最大速度
float s_curve_speed(float t, float jerk, float a_max, float v_max) {
float t1 = a_max / jerk; // 加加速段时间
float t2 = v_max / a_max - t1; // 匀加速段时间
if (t < t1) {
// 加加速段:速度 = 0.5 * jerk * t^2
return 0.5f * jerk * t * t;
} else if (t < t1 + t2) {
// 匀加速段:速度 = 0.5 * jerk * t1^2 + a_max * (t - t1)
float v1 = 0.5f * jerk * t1 * t1;
return v1 + a_max * (t - t1);
} else if (t < 2 * t1 + t2) {
// 减加速段
float v2 = 0.5f * jerk * t1 * t1 + a_max * t2;
float dt = t - t1 - t2;
return v2 + a_max * dt - 0.5f * jerk * dt * dt;
}
// ... 后续匀速、减速段类似
return v_max;
}
2.3 多项式曲线
当 S 形曲线还不够平滑时,可以用多项式曲线。比如 5 次多项式,速度和加速度都连续,加加速度也连续。
我记得有个半导体封装项目,要求启停时振动小于 0.1g。梯形曲线根本做不到,S 形曲线勉强达标,最后用了 7 次多项式才搞定。
| 曲线类型 | 加速度连续性 | 加加速度连续性 | 计算量 | 适用场景 |
|---|---|---|---|---|
| 梯形 | 不连续 | 不连续 | 极低 | 低速、大惯量 |
| S 形(7 段) | 连续 | 分段常数 | 中等 | 通用运动控制 |
| 5 次多项式 | 连续 | 连续 | 较高 | 高精度、低振动 |
| 7 次多项式 | 连续 | 连续且平滑 | 高 | 极端精密场景 |
三、脉冲抖动(Jitter)抑制技术
脉冲抖动,就是每个脉冲的周期长度不一致。你想想看,如果第一个脉冲周期是 10μs,第二个变成 12μs,第三个又变成 9μs,电机能不抖吗?
我曾经调试一台激光切割机,切割边缘总是有锯齿。查了三天,最后发现是脉冲抖动导致激光功率和位置不同步。从那以后,我对抖动抑制格外重视。
3.1 硬件级抖动抑制
- 专用脉冲发生器芯片:比如 TI 的 TMS320 系列内置 ePWM 模块,硬件产生脉冲,抖动几乎为 0
- FPGA 实现:用 Verilog 写一个脉冲发生器,时钟域完全可控,抖动可以做到 ±1 个时钟周期
- DMA 传输:用 DMA 把脉冲参数提前准备好,定时器自动装载,避免 CPU 干预
3.2 软件级抖动抑制
如果只能用 MCU 的定时器中断,那就要在软件上下功夫:
- 中断优先级提升:把脉冲定时器中断设为最高优先级,避免被其他中断打断
- 中断服务函数精简:ISR 里只做最必要的操作,其他计算放到主循环
- 双缓冲技术:准备两个比较值寄存器,一个当前在用,一个后台更新,切换时无缝隙
// 双缓冲脉冲输出示例
// 使用定时器的影子寄存器(shadow register)
void pulse_timer_isr() {
// 清除中断标志
TIM_ClearFlag(TIM1, TIM_FLAG_UPDATE);
// 直接读取下一个比较值(已在后台准备好)
// 不需要在这里计算,减少 ISR 耗时
TIM_SetCompare1(TIM1, next_compare_value);
// 通知主循环准备下一个值
pulse_ready_flag = 1;
}
void main_loop() {
while (1) {
if (pulse_ready_flag) {
pulse_ready_flag = 0;
// 在这里计算下一个脉冲的比较值
next_compare_value = calculate_next_pulse();
}
}
}
避坑指南:我曾经在 ISR 里做浮点运算,结果脉冲抖动直接飙到 5μs。后来把所有浮点运算都移到主循环,抖动降到了 0.2μs。记住,ISR 里只做整数赋值,别做复杂计算。
知识体系总览
下面这张图把脉冲输出优化的三个维度串起来了。你可以把它当作调试时的检查清单。
实战建议
最后给几个实在的建议:
- 先测抖动:用示波器看脉冲波形,测 1000 个脉冲的周期,算标准差。如果标准差超过平均周期的 5%,必须优化
- 曲线选择:普通设备用梯形就够了,精密设备上 S 形,极端场景才上多项式。别杀鸡用牛刀
- 预留裕量:脉冲频率别跑满定时器的极限,留 20% 裕量给中断延迟和温度变化
最后提醒一句:脉冲输出优化没有银弹。你必须在频率精度、曲线平滑度和抖动抑制之间做权衡。我见过太多人追求极致的 S 形曲线,结果 CPU 算不过来,抖动反而更大。适可而止,够用就好。