3、霍尔信号读取与处理:GPIO配置、中断读取、软件滤波与去抖
霍尔传感器这东西,说白了就是三个开关。它告诉你转子现在转到哪个扇区了。但你要是直接拿GPIO去读,十有八九会被噪声搞疯。我刚开始做FOC驱动时,就吃过这个亏——电机一转,波形跳得跟心电图似的。
这一章,咱们就把霍尔信号的读取和处理彻底讲透。从GPIO配置开始,到中断读取,再到软件滤波和去抖,一步不落。
3.1 GPIO配置:别小看这一步
霍尔传感器的输出是开漏信号,所以GPIO必须配置成上拉输入。我见过有人用浮空输入,结果信号飘忽不定,查了半天才发现是配置问题。
关键配置项:
- 模式:输入模式(GPIO_MODE_INPUT)
- 上拉/下拉:上拉(GPIO_PULLUP)——霍尔开漏输出,需要外部上拉
- 速度:高速(GPIO_SPEED_HIGH)——减少信号边沿抖动
- 中断触发:双边沿触发(上升沿+下降沿)
以STM32为例,配置代码大概长这样:
// 霍尔A相 GPIO配置
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0; // 霍尔A
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING; // 双边沿中断
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 霍尔B相、C相同理,只是Pin不同
// 霍尔B: GPIO_PIN_1, 霍尔C: GPIO_PIN_2
我的习惯:三个霍尔用同一个GPIO端口,这样中断服务函数里可以直接读端口寄存器,比读三次HAL_GPIO_ReadPin快得多。
3.2 中断读取:时机比速度重要
霍尔信号变化很快,尤其是高速电机。用轮询去读?不现实。中断才是正解。
但这里有个坑:中断里不能做太多事。我见过有人直接在中断里算角度、查表、更新PWM……结果中断嵌套把自己搞死了。
我曾经踩过的坑:在霍尔中断里直接调用HAL_Delay(),导致系统卡死。后来才意识到,中断服务函数里绝对不能有阻塞调用。
正确的做法是:中断里只做两件事——
- 读取当前霍尔状态(三个引脚的电平组合)
- 设置一个标志位,通知主循环去处理
中断服务函数示例:
// 霍尔状态全局变量
volatile uint8_t hall_state = 0;
volatile uint8_t hall_updated = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
// 读取三个霍尔引脚状态,组合成一个3位值
uint8_t ha = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
uint8_t hb = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1);
uint8_t hc = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2);
hall_state = (ha << 2) | (hb << 1) | hc;
hall_updated = 1; // 通知主循环
}
注意:hall_updated一定要用volatile修饰,否则编译器优化会把它优化掉。我调试时遇到过这个坑,标志位永远为0,查了半天才发现是优化问题。
3.3 软件滤波:别让噪声骗了你
霍尔信号在换相瞬间会有抖动。你想想看,电机在高速旋转,霍尔传感器检测磁场边沿,难免会有毛刺。如果不做滤波,一个毛刺就能让扇区判断出错,电流环直接崩掉。
常用的软件滤波方法有两种:
| 方法 | 原理 | 适用场景 | 缺点 |
|---|---|---|---|
| 连续采样法 | 连续采样N次,取出现次数最多的值 | 低速、噪声较小时 | 占用CPU时间,延迟较大 |
| 去抖定时器法 | 检测到边沿后启动定时器,延时后再确认 | 高速、噪声较大时 | 需要额外定时器资源 |
我个人更推荐去抖定时器法。为什么?因为它不阻塞CPU,而且延时时间可以精确控制。
去抖定时器法的实现思路:
- 检测到霍尔信号边沿(中断触发)
- 立即启动一个定时器,延时约5-10微秒
- 定时器溢出时,再次读取霍尔状态
- 如果状态与中断时一致,则认为是有效信号
- 如果不一致,则丢弃这次中断
去抖时间怎么选?
- 低速电机(几百转/分):10-20微秒
- 中速电机(几千转/分):5-10微秒
- 高速电机(上万转/分):2-5微秒
去抖时间太长会丢失真实信号,太短又滤不掉噪声。我一般从5微秒开始试,用示波器观察效果再微调。
3.4 状态机:把霍尔信号变成扇区
三个霍尔信号组合起来,有8种状态(2^3=8)。但其中6种是有效状态,对应6个扇区。另外两种(000和111)是非法状态,通常是传感器故障或安装错误导致的。
我习惯用一个查找表来做状态转换:
// 霍尔状态到扇区的映射表
// 索引:霍尔状态(HA, HB, HC)
// 值:扇区编号(0-5)
const uint8_t hall_to_sector[8] = {
0xFF, // 000: 非法状态
0, // 001: 扇区0
1, // 010: 扇区1
2, // 011: 扇区2
3, // 100: 扇区3
4, // 101: 扇区4
5, // 110: 扇区5
0xFF // 111: 非法状态
};
// 主循环中处理霍尔更新
if (hall_updated) {
hall_updated = 0;
uint8_t sector = hall_to_sector[hall_state];
if (sector != 0xFF) {
// 有效扇区,更新FOC算法
foc_set_sector(sector);
} else {
// 非法状态,触发错误处理
error_handler(HALL_ERROR_INVALID_STATE);
}
}
重要:非法状态一定要处理。我遇到过霍尔传感器松动的情况,电机突然出现000状态,如果不处理,电流环会输出错误电压,轻则抖动,重则烧MOS管。
3.5 整体流程:一张图看懂
说了这么多,咱们用一张图把整个流程串起来:
3.6 实战避坑清单
最后,我把这些年踩过的坑整理成一份清单,你调试时对照着检查:
- GPIO上拉电阻没配:霍尔信号浮空,波形乱跳。用示波器一看,全是毛刺。
- 中断优先级太低:被其他中断打断,导致霍尔信号丢失。电机高速时尤其明显。
- 去抖时间太长:高速时真实信号被滤掉,电机换相滞后,效率下降。
- 非法状态没处理:霍尔故障时系统崩溃,严重时烧驱动板。
- volatile关键字漏了:标志位被优化,主循环永远不知道霍尔更新了。
- 三个霍尔不在同一个端口:读取速度慢,中断里多花几个微秒,高速时就出问题。
我的调试习惯:先用示波器看三个霍尔信号的波形,确认每个通道的上升沿和下降沿都干净。然后用逻辑分析仪抓中断触发的时间戳,看看有没有漏掉的中断。最后用串口打印扇区变化,确认换相顺序正确。