4、扇区判断实战:基于ADC采样的三相电压获取、软件实现扇区判断、边界条件处理与防抖

扇区判断,说白了就是让控制器知道「转子现在在哪个60°区间里」。没有这一步,后面的电压矢量合成全是瞎蒙。我刚开始做SVPWM时,总觉得扇区判断不就是几个比较器的事吗?结果一上硬件,噪声、采样延迟、边界抖动全来了。嗯,这一章咱们就把这些坑一个一个填上。

4.1 三相电压的ADC采样基础

要判断扇区,首先得拿到三相电压的真实值。实际系统中,我们通常用电阻分压加运放调理,把母线电压和相电压降到ADC可采的0~3.3V范围。

采样时刻的选择:我个人习惯在PWM载波的谷底或峰顶触发ADC采样。为什么?因为此时开关噪声最小,采样值最干净。如果你在开关动作瞬间采样,那采回来的数据基本没法用。

假设我们有三相电压 Ua、Ub、Uc,ADC转换后得到数字量 ADC_Ua、ADC_Ub、ADC_Uc。需要先做一下标幺化处理:

// 假设ADC为12位,参考电压3.3V,分压比K
#define ADC_MAX  4095
#define V_REF    3.3f
#define K_DIV    10.0f   // 分压系数

float Ua = (float)ADC_Ua / ADC_MAX * V_REF * K_DIV;
float Ub = (float)ADC_Ub / ADC_MAX * V_REF * K_DIV;
float Uc = (float)ADC_Uc / ADC_MAX * V_REF * K_DIV;

这里有个细节:三相电压是浮地的,你得先确认参考地是母线负端还是电机中性点。我在项目中遇到过把中性点电压当参考地,结果算出来的扇区全反了——折腾了两天才发现是参考点选错了。

4.2 软件实现扇区判断

拿到三相电压后,我们通常先做Clark变换,把三相静止坐标系转到两相静止坐标系:

float Ualpha = Ua - 0.5f * Ub - 0.5f * Uc;
float Ubeta  = (sqrtf(3)/2.0f) * (Ub - Uc);

然后根据 Ualpha 和 Ubeta 的符号及比例关系,就能判断扇区。经典方法是计算三个中间变量:

float B1 = Ubeta;
float B2 = -0.5f * Ubeta + 0.866f * Ualpha;
float B3 = -0.5f * Ubeta - 0.866f * Ualpha;

int sector = 0;
if (B1 > 0) sector |= 1;
if (B2 > 0) sector |= 2;
if (B3 > 0) sector |= 4;

这样得到的 sector 值(1~6)直接对应六个扇区。你想想看,这个判断逻辑其实就是在看电压矢量落在哪个60°楔形里。

sector值 对应扇区 角度范围
3 扇区I 0° ~ 60°
1 扇区II 60° ~ 120°
5 扇区III 120° ~ 180°
4 扇区IV 180° ~ 240°
6 扇区V 240° ~ 300°
2 扇区VI 300° ~ 360°
核心要点:扇区判断的本质是「符号比较」,不是精确角度计算。所以计算量很小,适合在中断里快速执行。

4.3 边界条件处理

理想情况下,扇区边界是清晰的。但实际采样值总有噪声,当电压矢量正好落在扇区边界附近时,B1、B2、B3 会在0附近来回跳变,导致扇区频繁切换。这就是所谓的「边界抖动」。

我曾经在一个风机项目上遇到过这个问题:扇区在边界处每秒切换上千次,电机发出尖锐的啸叫声。后来加了滞环才解决。

滞环处理的思路很简单:给比较阈值加一个死区。比如原来判断 B1 > 0,现在改成 B1 > THRESHOLD,其中 THRESHOLD 是一个很小的正数(比如0.01)。

#define HYST_THR  0.01f

int sector = 0;
if (B1 > HYST_THR) sector |= 1;
if (B2 > HYST_THR) sector |= 2;
if (B3 > HYST_THR) sector |= 4;

但这样会引入一个副作用:边界附近的扇区判断会「延迟」一点点。不过对于大多数电机控制应用,这个延迟完全可以接受。

注意:滞环阈值不能设得太大,否则扇区判断会失真,导致电压矢量合成错误。我一般取满量程的1%~2%作为阈值。

4.4 防抖策略:软件滤波与状态保持

除了滞环,还有几种常用的防抖手段:

  • 连续确认法:连续N次采样都判断为同一扇区,才更新扇区值。N一般取3~5。
  • 一阶低通滤波:对 Ualpha、Ubeta 做滤波,再判断扇区。适合噪声频率较高的场景。
  • 状态机保持:扇区变化时,强制保持当前扇区一段时间(比如几个PWM周期),避免频繁切换。

我个人最常用的是「连续确认+滞环」的组合。代码实现大概是这样:

static int last_sector = 0;
static int confirm_count = 0;

int get_sector_with_debounce(float Ualpha, float Ubeta) {
    int raw_sector = calc_sector(Ualpha, Ubeta);
    
    if (raw_sector == last_sector) {
        confirm_count++;
        if (confirm_count >= 3) {
            return raw_sector;
        }
    } else {
        confirm_count = 0;
        last_sector = raw_sector;
    }
    
    return last_sector;  // 保持旧值
}

你想想看,这个防抖逻辑其实和按键消抖是一个道理——都是「别信单次采样,多确认几次再说」。

4.5 扇区判断整体流程

把上面所有环节串起来,完整的扇区判断流程如下:

  1. ADC触发采样,读取三相电压数字量
  2. 标幺化、滤波(可选)
  3. Clark变换得到 Ualpha、Ubeta
  4. 计算中间变量 B1、B2、B3
  5. 滞环比较 + 防抖处理
  6. 输出最终扇区号(1~6)

下面这张图把整个流程串起来了,你可以对照着看:

扇区判断完整流程图 ADC采样三相电压 标幺化 + 滤波 Clark变换 → Uα, Uβ 计算 B1, B2, B3 滞环比较 + 防抖 输出扇区号 (1~6) 说明:滞环阈值建议取满量程1%~2%,防抖连续确认次数建议3~5次
调试小技巧:我习惯在调试阶段把 Ualpha、Ubeta 和扇区号通过DAC或串口输出,用示波器或上位机观察。如果扇区切换时有毛刺,说明防抖参数需要调整。