3. 霍尔信号滤波与去抖:硬件RC滤波设计、软件数字滤波、边沿抖动处理
霍尔传感器信号,说白了就是三个方波。但实际电机跑起来,这方波可没那么干净。我见过太多工程师,上来就写中断、算速度,结果波形一抖,整个控制就乱了。霍尔信号滤波,是调参的第一步,也是容易被忽略的一步。
核心观点:霍尔信号滤波的目标不是“消除所有噪声”,而是“在延迟和准确性之间找到平衡”。滤波太狠,换相滞后;滤波太弱,误触发频繁。
3.1 霍尔信号为什么需要滤波?
霍尔传感器输出的本质是磁感应强度变化。电机运行时,有几个因素会让信号变脏:
- 机械振动:转子抖动,导致霍尔元件感应到的磁场边缘来回跳动
- PWM开关噪声:功率管开关瞬间,电流突变会耦合到霍尔信号线上
- 磁滞效应:霍尔元件本身有磁滞,过零点附近输出不稳定
- 线缆耦合:长线传输时,电机线对霍尔线的串扰
我有个项目,霍尔线跟电机线绑在一起走。一开始没滤波,电机一转,霍尔信号上全是毛刺,速度计算值跳得像心电图。后来加了RC滤波,世界清净了。
3.2 硬件RC滤波设计
硬件滤波是最直接的手段。一个电阻加一个电容,简单粗暴,但参数选不对,反而坏事。
3.2.1 基本原理
RC低通滤波的截止频率公式:
f_c = 1 / (2π × R × C)
霍尔信号的频率范围,取决于电机极对数和转速。举个例子:一个4对极的电机,转速3000RPM,霍尔信号频率就是:
f_hall = (3000 / 60) × 4 = 200Hz
截止频率一般取霍尔基频的3~5倍。这样既能滤掉高频噪声,又不会把有效边沿削得太圆。
3.2.2 参数选择实战
| 电机类型 | 典型转速范围 | 推荐R值 | 推荐C值 | 截止频率 |
|---|---|---|---|---|
| 低速大扭矩 | 0~500 RPM | 10kΩ | 100nF | ~159 Hz |
| 中速通用 | 500~3000 RPM | 4.7kΩ | 47nF | ~720 Hz |
| 高速主轴 | 3000~10000 RPM | 2.2kΩ | 22nF | ~3.3 kHz |
我的习惯:先按表格选一组参数,然后用示波器看霍尔信号的上升沿。如果边沿斜率太缓(超过10μs),就减小R或C。如果毛刺还在,就增大C。调试时,我一般从4.7kΩ+47nF开始试。
3.2.3 施密特触发器配合
RC滤波后的信号边沿会变缓。如果直接进MCU的GPIO,可能会在阈值附近来回跳。这时候需要施密特触发器整形。
我常用的方案:
- 分立元件:74HC14(六反相施密特触发器),便宜又好买
- MCU内部:部分MCU的GPIO自带施密特触发输入,比如STM32的某些引脚
- 软件整形:如果硬件来不及改,可以在中断里做滞回比较
注意:RC滤波会引入延迟。RC时间常数τ = R × C。比如4.7kΩ+47nF,τ ≈ 220μs。这个延迟在低速时影响不大,但高速电机(比如10000RPM以上)可能造成换相角度偏差。我曾经在高速风机项目上吃过这个亏,后来把R改成了1kΩ。
3.3 软件数字滤波
硬件滤波搞定了大部分噪声,但总有些顽固的毛刺。这时候软件滤波上场。
3.3.1 中值滤波
原理很简单:连续采样N次,取中间值作为有效值。对脉冲噪声特别有效。
// 中值滤波示例(N=5)
uint8_t hall_median_filter(uint8_t *buf, uint8_t len) {
uint8_t temp[5];
memcpy(temp, buf, 5);
// 冒泡排序(数据量小,无所谓效率)
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4 - i; j++) {
if (temp[j] > temp[j+1]) {
uint8_t t = temp[j];
temp[j] = temp[j+1];
temp[j+1] = t;
}
}
}
return temp[2]; // 取中间值
}
N取奇数,一般3或5就够了。N越大,滤波效果越好,但响应越慢。
3.3.2 均值滤波
连续采样N次,取平均值。对高斯噪声效果好,但对脉冲噪声效果一般。
// 均值滤波示例
uint8_t hall_mean_filter(uint8_t *buf, uint8_t len) {
uint32_t sum = 0;
for (int i = 0; i < len; i++) {
sum += buf[i];
}
return (uint8_t)(sum / len);
}
我的经验:均值滤波适合用在速度计算上,不适合用在换相判断上。换相需要的是“边沿时刻”,而不是“平均电平”。我曾经在换相中断里用了均值滤波,结果换相时刻被平均掉了,电机反而抖得更厉害。
3.3.3 卡尔曼滤波(轻量版)
卡尔曼滤波听起来高大上,但用在霍尔信号上,其实可以简化成一维模型。核心思想:用上一刻的估计值和当前测量值,加权平均得到新的估计值。
// 一维卡尔曼滤波(简化版)
typedef struct {
float x; // 估计值
float p; // 估计误差协方差
float q; // 过程噪声协方差
float r; // 测量噪声协方差
float k; // 卡尔曼增益
} Kalman1D;
uint8_t kalman_update(Kalman1D *kf, uint8_t z) {
// 预测
kf->p = kf->p + kf->q;
// 更新
kf->k = kf->p / (kf->p + kf->r);
kf->x = kf->x + kf->k * ((float)z - kf->x);
kf->p = (1 - kf->k) * kf->p;
return (uint8_t)(kf->x + 0.5f);
}
参数调优:
- q(过程噪声):霍尔信号变化快,q设大一点,比如0.1~0.5
- r(测量噪声):噪声大,r设大,比如1.0~5.0
- 初始p:设1.0就行,卡尔曼会自己收敛
注意:卡尔曼滤波在MCU上跑,要注意浮点运算开销。如果MCU没有FPU,建议用定点数实现。我在一个Cortex-M0项目上用过,每个通道更新一次大约需要50μs,三个通道就是150μs,对高速电机来说有点吃力。
3.4 边沿抖动处理
边沿抖动,是霍尔信号滤波里最头疼的问题。明明电机在匀速转,霍尔边沿的时间间隔却忽大忽小。
3.4.1 抖动的来源
- 机械原因:转子偏心、轴承间隙、负载波动
- 电气原因:PWM开关噪声、地弹、电源纹波
- 传感器原因:霍尔元件安装偏差、磁钢充磁不均匀
3.4.2 软件去抖策略
我常用的几种方法:
- 边沿锁定(Debounce Timer):检测到边沿后,启动一个定时器,在定时器超时前忽略后续边沿
- 连续确认:连续采样N次,如果N次都检测到边沿,才认为有效
- 窗口比较:记录上一次边沿的时间,设定一个窗口(比如±20%),只有落在窗口内的边沿才接受
// 边沿锁定示例
#define DEBOUNCE_US 50 // 50μs去抖时间
volatile uint32_t last_edge_time[3] = {0};
uint8_t hall_edge_debounce(uint8_t channel, uint32_t now_us) {
if ((now_us - last_edge_time[channel]) > DEBOUNCE_US) {
last_edge_time[channel] = now_us;
return 1; // 有效边沿
}
return 0; // 抖动,忽略
}
避坑指南:我曾经在一个项目里,去抖时间设了200μs。结果电机高速运行时,霍尔信号周期只有500μs,去抖时间吃掉了一半的周期,换相完全乱套。后来我把去抖时间改成了20μs,配合硬件RC滤波,效果很好。记住:去抖时间不能超过霍尔信号最小周期的10%。
3.5 滤波方案选择与组合
没有一种滤波方案是万能的。实际项目中,我通常这样组合:
我的推荐组合:
| 应用场景 | 硬件滤波 | 软件滤波 | 去抖策略 |
|---|---|---|---|
| 低速大扭矩(<500 RPM) | 10kΩ + 100nF | 中值滤波(N=3) | 边沿锁定 100μs |
| 中速通用(500~3000 RPM) | 4.7kΩ + 47nF | 均值滤波(N=5) | 边沿锁定 50μs |
| 高速主轴(>3000 RPM) | 2.2kΩ + 22nF | 卡尔曼滤波 | 连续确认(N=3) |
| 高噪声环境(如电钻) | 10kΩ + 220nF | 中值+均值组合 | 窗口比较 ±15% |
总结:霍尔信号滤波,硬件是基础,软件是补充。先搞定RC参数,再根据实际波形决定要不要加软件滤波。别一上来就上卡尔曼,很多时候一个简单的边沿锁定就够用了。记住:滤波的目的是让信号可用,不是让信号完美。