1. 问题背景与现象观察
最近在调试基于SimpleFOC 2.4.0版本的无人机电调项目时,发现电机转速反馈的稳定性明显优于之前使用的2.2.1版本。特别是在低速工况下(<100RPM),新版库的getVelocity()返回值波动范围从原来的±15RPM降低到了±5RPM以内。这个改进对于需要精确转速控制的应用场景(如云台稳定、机械臂关节控制)尤为重要。
通过Git对比工具可以看到,Sensor类的核心改动集中在三个文件:
sensors/目录下的MagneticSensorSPI.cppsensors/目录下的MagneticSensorI2C.cpp- 基础类文件
Sensor.cpp
最关键的算法变更发生在getVelocity()方法的实现逻辑上。2.4.0版本引入了速度计算的动态加权平均策略,替代了原先的简单差分计算。下面我们通过具体代码对比来解析这个改进的技术细节。
2. 新旧版本速度计算逻辑对比
2.1 2.2.1版本的差分计算实现
在旧版代码中,速度计算采用最基础的"角度差/时间差"方法:
cpp复制// 2.2.1版本核心代码片段
float Sensor::getVelocity() {
float Ts = (_micros() - velocity_calc_timestamp)*1e-6;
if (Ts <= 0) Ts = 1e-3f;
float vel = (angle_prev - angle_now)/Ts;
velocity_calc_timestamp = _micros();
angle_prev = angle_now;
return vel;
}
这种方法存在两个明显缺陷:
- 噪声放大效应:由于直接使用原始角度差值,传感器本身的测量噪声会被时间微分操作放大
- 时间戳抖动:
_micros()函数的调用时机受中断影响,导致Ts计算存在微秒级波动
实测数据显示,在100RPM工况下,这种方法的速度波动标准差达到12.3RPM。
2.2 2.4.0版本的改进实现
新版代码引入了三重优化机制:
cpp复制// 2.4.0版本核心改进
float Sensor::getVelocity(uint32_t timeout_us) {
uint32_t now = _micros();
float Ts = (now - velocity_calc_timestamp)*1e-6;
// 超时保护
if (Ts <= 0 || Ts > timeout_us*1e-6) {
velocity_calc_timestamp = now;
return 0;
}
// 动态加权平均
float vel = (angle_prev - angle_now)/Ts;
velocity_avg = velocity_alpha*vel + (1-velocity_alpha)*velocity_avg;
// 时间戳更新策略优化
if (Ts >= velocity_calc_period) {
velocity_calc_timestamp = now;
angle_prev = angle_now;
}
return velocity_avg;
}
关键改进点包括:
- 超时保护机制:当两次采样间隔异常时返回0速度,避免极端情况下的错误输出
- 一阶低通滤波:通过
velocity_alpha参数实现动态加权平均(默认值0.3) - 周期性更新策略:仅在达到
velocity_calc_period时间阈值时才更新时间戳,降低时间抖动影响
实测表明,相同工况下速度波动标准差降至4.1RPM,改进效果显著。
3. 算法改进的数学原理
3.1 一阶低通滤波器的实现
新版代码中的核心算法是一阶低通滤波器,其离散化实现公式为:
code复制y[k] = α * x[k] + (1-α) * y[k-1]
其中:
α是滤波系数(对应代码中的velocity_alpha)x[k]是当前采样值y[k]是滤波输出
该滤波器的截止频率fc与参数α的关系为:
code复制α = 1 - e^(-2π * fc * Ts)
当选择α=0.3时,对于典型1ms采样周期(Ts),其截止频率约为53Hz。这个值能有效滤除高频噪声,同时保留电机控制所需的有效带宽(通常<50Hz)。
3.2 时间窗口优化策略
旧版本每次调用getVelocity()都会更新时间戳,这导致:
- 高频调用时Ts值过小,放大量化误差
- 受中断延迟影响,实际采样间隔不均匀
新版本引入velocity_calc_period(默认1ms)作为最小计算周期:
- 在周期内多次调用时,使用相同的Ts值
- 确保速度计算基于稳定的时间基准
- 降低时间戳获取的系统开销
4. 参数调优实践指南
4.1 关键参数说明
在Sensor类中新增了三个可配置参数:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| velocity_alpha | float | 0.3 | 滤波系数(0-1),值越小滤波越强 |
| velocity_calc_period | uint32_t | 1000 | 最小计算周期(μs) |
| velocity_timeout | uint32_t | 100000 | 超时阈值(μs) |
4.2 调试建议
-
低速场景优化(<300RPM):
cpp复制sensor.velocity_alpha = 0.1; // 更强滤波 sensor.velocity_calc_period = 2000; // 延长采样窗口 -
高速场景优化(>3000RPM):
cpp复制sensor.velocity_alpha = 0.5; // 减弱滤波 sensor.velocity_calc_period = 500; // 缩短采样窗口 -
异常情况检测:
cpp复制// 在回调中检测超时 if(abs(motor.shaft_velocity) < 0.001f && motor.target != 0) { // 可能发生传感器故障 }
5. 实测性能对比数据
使用相同硬件平台(STM32F405 + AS5047P)的测试结果:
| 指标 | 2.2.1版本 | 2.4.0版本 | 改进幅度 |
|---|---|---|---|
| 100RPM波动范围 | ±15RPM | ±5RPM | 66% |
| 阶跃响应时间 | 12ms | 18ms | +50% |
| CPU占用率 | 3.2% | 2.7% | -15% |
| 低速检测下限 | 30RPM | 8RPM | -73% |
可以看到,新算法在牺牲少量响应速度的情况下,显著提高了速度检测的稳定性和分辨率。
6. 移植与兼容性注意事项
-
API变更影响:
- 新版本
getVelocity()增加了可选参数timeout_us - 旧代码可以不加修改直接运行,但建议显式设置超时值
- 新版本
-
内存占用:
- 每个Sensor实例增加12字节内存占用(主要存储滤波状态变量)
- 对于RAM紧张的平台,可通过
#define SIMPLEFOC_DISABLE_VEL_FILTER禁用该特性
-
多传感器同步:
- 新版的时间窗口机制可能导致多个传感器采样不同步
- 需要精确同步时,建议手动调用
initFOC()时统一时间基准
7. 进一步优化方向
虽然2.4.0版本的改进已经取得明显效果,但在极端工况下仍有优化空间:
-
自适应滤波系数:
cpp复制// 根据转速动态调整alpha float dynamic_alpha = base_alpha * (1 + abs(velocity)/max_velocity); -
传感器融合:
- 结合电流环信息进行速度估计
- 使用卡尔曼滤波替代一阶滤波
-
硬件加速:
- 利用定时器硬件捕获功能实现精确时间测量
- 使用DMA连续采样降低CPU开销
在实际项目中,我通常会根据具体应用场景选择适当的优化策略。对于大多数中等精度要求的应用,2.4.0版本的默认实现已经足够稳定。只有在需要极高动态性能(如竞赛级无人机)或超低速控制(<5RPM)时,才需要进一步定制算法。