4. 基于反电动势检测的堵转算法
堵转检测这件事,我做了快十年才敢说「摸透了」。早期做无刷电机驱动时,最头疼的就是怎么在不装传感器的情况下判断转子卡死了。后来发现,反电动势(Back EMF)才是真正的突破口。说白了,电机转起来的时候,线圈切割磁感线会产生一个反向电压,这个电压的波形、幅值、过零点,都藏着转子状态的信息。
核心思想:电机正常旋转时,反电动势呈现规律的正弦波或梯形波;堵转时,反电动势消失或畸变。通过检测这个变化,就能判断堵转。
4.1 反电动势检测原理
先讲清楚反电动势是怎么来的。你想想看,电机通电后转子旋转,定子绕组在磁场中做切割磁感线运动,就会感应出电压。这个电压的方向跟外加电压相反,所以叫「反电动势」。
反电动势的大小跟转速成正比:
E = Ke × ω
其中 Ke 是电机常数,ω 是角速度。转速越高,反电动势越大。堵转时 ω = 0,反电动势自然就没了。
我在项目中遇到过一个问题:有些工程师以为反电动势是「测」出来的,其实它是「算」出来的。你没法直接拿万用表去量,因为电机端子上同时有驱动电压和反电动势。常用的做法是:在 PWM 关断期间采样端电压,这时候驱动电压为零,采到的就是反电动势。
| 状态 | 反电动势特征 | 物理含义 |
|---|---|---|
| 正常运转 | 规律的正弦/梯形波 | 转子匀速旋转 |
| 轻载 | 幅值较高 | 转速接近空载 |
| 重载 | 幅值降低 | 转速下降 |
| 堵转 | 幅值接近零 | 转子卡死 |
4.2 过零点检测法
过零点检测法,是反电动势检测里最经典的方法。我最早接触无刷电机驱动时,用的就是这招。
原理很简单:反电动势波形每次从正变负或从负变正时,会经过零点。这个过零点对应着转子磁极和定子绕组的特定相对位置。检测到过零点,就知道该换相了。
具体怎么做?
- 构建虚拟中性点:把三相绕组的末端连在一起,形成一个虚拟中性点。这个点的电压就是三相电压的平均值。
- 比较端电压:把每一相的端电压跟虚拟中性点电压做比较。当两者相等时,就是过零点。
- 延时换相:过零点出现后,再等 30° 电角度,然后执行换相。
我的经验:过零点检测法在高速时很准,但低速时反电动势信号太弱,容易误判。我曾经在一个低速高扭矩项目里吃过亏,后来加了硬件放大电路才解决。
过零点检测的代码实现,核心就是一个比较器逻辑:
// 过零点检测伪代码
float v_neutral = (v_a + v_b + v_c) / 3.0;
if (phase == PHASE_A) {
if (v_a > v_neutral && last_v_a <= v_neutral) {
zero_cross_detected = 1;
}
last_v_a = v_a;
}
if (zero_cross_detected) {
// 延时30°电角度后换相
delay_30_deg();
commutation();
zero_cross_detected = 0;
}
4.3 反电动势阈值法
过零点检测法有个硬伤:它需要比较器或 ADC 持续采样,对硬件要求高。阈值法就粗暴多了——我直接设一个电压阈值,反电动势低于这个值就认为堵转。
阈值法的逻辑:
- 正常运行时,反电动势幅值在某个范围内波动
- 堵转时,反电动势幅值骤降
- 设定一个阈值(比如正常幅值的 20%),低于阈值持续一段时间就报堵转
注意:阈值不能设得太高,否则重载启动时会误报堵转。也不能设得太低,否则堵转半天检测不出来。我一般建议先做一段时间的自学习,根据实际运行数据动态调整阈值。
阈值法的代码实现:
// 反电动势阈值法检测堵转
#define BEMF_THRESHOLD 0.2 // 正常幅值的20%
#define DETECT_TIME_MS 100 // 持续检测时间
float bemf_amplitude = get_bemf_amplitude();
static uint32_t stall_count = 0;
if (bemf_amplitude < BEMF_THRESHOLD * NORMAL_AMPLITUDE) {
stall_count++;
if (stall_count > DETECT_TIME_MS / SAMPLE_INTERVAL_MS) {
stall_detected = 1;
motor_stop();
}
} else {
stall_count = 0;
}
4.4 无传感器堵转检测
无传感器堵转检测,说白了就是「不用霍尔传感器,全靠反电动势判断」。这在很多场合是刚需——比如压缩机、风扇、水泵,装传感器成本高、可靠性差。
无传感器检测的核心挑战有两个:
- 启动阶段:电机还没转起来,没有反电动势,怎么判断堵转?
- 低速阶段:反电动势信号弱,信噪比低,容易误判。
我的做法是分阶段处理:
| 阶段 | 检测方法 | 说明 |
|---|---|---|
| 启动(0~200 RPM) | 开环强拖 + 电流监测 | 先给一个固定频率的驱动信号,监测相电流是否异常升高 |
| 低速(200~500 RPM) | 反电动势阈值法 | 信号弱但可用,阈值要适当降低 |
| 中高速(>500 RPM) | 过零点检测法 | 信号强,检测精度高 |
避坑指南:我曾经在一个风扇项目里,启动阶段频繁误报堵转。后来发现是开环强拖的电流本身就大,跟堵转电流混在一起分不清。解决办法是:启动时用短脉冲试探,看电流响应曲线——堵转时电流上升快,正常时上升慢。
4.5 代码实现与仿真
下面给出一套完整的无传感器堵转检测代码框架。这个框架我在多个项目里验证过,可以直接移植到 STM32、GD32 等平台上。
// 堵转检测状态机
typedef enum {
STALL_IDLE,
STALL_STARTUP,
STALL_LOW_SPEED,
STALL_HIGH_SPEED,
STALL_DETECTED
} stall_state_t;
stall_state_t stall_state = STALL_IDLE;
uint32_t stall_timer = 0;
void stall_detection_task(void) {
float speed = get_motor_speed();
float current = get_phase_current();
float bemf = get_bemf_amplitude();
switch (stall_state) {
case STALL_IDLE:
if (motor_is_running()) {
stall_state = STALL_STARTUP;
stall_timer = 0;
}
break;
case STALL_STARTUP:
// 启动阶段:监测电流
if (current > CURRENT_THRESHOLD * 1.5) {
stall_timer++;
if (stall_timer > 50) { // 50ms
stall_state = STALL_DETECTED;
}
} else {
stall_timer = 0;
}
// 速度上来后切换状态
if (speed > 200) {
stall_state = STALL_LOW_SPEED;
}
break;
case STALL_LOW_SPEED:
// 低速阶段:阈值法
if (bemf < BEMF_THRESHOLD_LOW * NORMAL_AMPLITUDE) {
stall_timer++;
if (stall_timer > 100) {
stall_state = STALL_DETECTED;
}
} else {
stall_timer = 0;
}
if (speed > 500) {
stall_state = STALL_HIGH_SPEED;
}
break;
case STALL_HIGH_SPEED:
// 中高速阶段:过零点检测
if (!zero_cross_detected_in_last_cycle()) {
stall_timer++;
if (stall_timer > 200) { // 200ms无过零点
stall_state = STALL_DETECTED;
}
} else {
stall_timer = 0;
}
break;
case STALL_DETECTED:
motor_stop();
report_stall();
break;
}
}
仿真方面,我习惯用 MATLAB/Simulink 搭一个反电动势模型,先跑一遍算法再下硬件。下面是一个简单的仿真流程:
- 建立电机模型(包含反电动势生成模块)
- 注入堵转故障(转速强制归零)
- 观察反电动势波形变化
- 验证检测算法的响应时间和误报率
嗯,这里要注意:仿真时一定要加噪声。我见过太多人在仿真里跑得完美,一上硬件就崩——因为仿真环境太干净了。建议在反电动势信号上叠加 ±5% 的白噪声,看看算法能不能扛住。
最后说一句:反电动势检测不是万能的。极端工况下(比如电机被堵死在某个特定角度),反电动势可能刚好为零,这时候算法会失效。我的建议是:反电动势检测作为主方案,同时保留电流检测作为备份。两条腿走路,才走得稳。