带一套伺服驱动器做整机调试时,最让人心里没底的往往不是电流环PI参数,而是上电那一瞬间。电机还停在那里,控制器却不知道转子朝哪个方向,如果直接切闭环,轻则电流环发出一阵抖动,重则电机朝着预估错误的方向猛冲一下,带着负载就是一台事故。PMSM脉冲注入初始位置检测,就是解决这个问题的标准做法之一。它不需要电机转起来,也不依赖外部编码器回零,用一组C代码加解析就能在静止状态下把转子电角度算出来,而且精度可以做到伺服高精度场合能用的水平。
这篇博文会把我实际用过的脉冲注入检测方案完整拆开讲:原理层面讲清楚为什么电流响应里藏着转子位置,代码层面把扫描注入、电流峰值采样、二次谐波拟合、N/S极性判别这些函数逐个拆开,再把高精度场合必须处理的误差源、标定方法、以及我在调试中踩过的几个坑一并记录下来。适合正在做伺服驱动、无感FOC预定位,或者刚接手电机初始位置检测模块的开发者参考。
1. 上电即满盘皆输:初始位置检测在伺服系统里的位置
1.1 没有初始电角度,矢量控制就是盲人摸象
FOC的核心是Park变换,而Park变换需要一个实时更新的电角度。这个角度如果初始值就是错的,后续所有解耦、电流环输出、SVPWM占空比都会跟着错。特别是伺服系统里普遍使用增量式编码器,上电时只有Z脉冲之后才有绝对位置参考,可电机往往带着负载停在任意位置,不可能先人为拖到Z相再启动。所以业界才有了一套专门在静止状态检测转子初始位置的技术,脉冲注入是其中实现最直接、成本最低的一种。
1.2 标题场景里到底“高精度”要求什么
普通工业变频器做个初始位置检测,误差三五度也能接受,反正启动之后靠反电动势观察器慢慢收敛。但标题里强调的是“高精度场合”,这个要求实际翻译过来有三条:第一,检测过程中电机不允许产生可见的微动,否则负载位置就变了,定位就失去了意义;第二,检测出的电角度误差最好在±1°以内,否则第一次启动的转矩就会和期望方向出现偏差,带重负载时可能引起短时反向冲击;第三,算法要从代码层面保证可重复性,不能这次测出来是30°,下次同一位置测出来是31.2°,这种漂移在高精度场合比固定偏差更难处理。
这里顺带说一句,标题里“C代码+解析文档”这个组合,说明这是一个要落地到嵌入式MCU的工程方案,不是仿真思路。因此我下面讲的所有细节,都围绕“在真实硬件上跑得通、精度够、抗干扰”来展开。
1.3 主流方法对比:为什么选脉冲注入
先给一个快速对比,方便理解脉冲注入在整个技术图谱中的位置。
| 方法 | 原理基础 | 典型精度 | 是否产生运动 | 主要局限 |
|---|---|---|---|---|
| 高频正弦注入 | 电机的凸极效应,高频阻抗与转子位置相关 | ±0.5°以内 | 基本无 | 需要额外DAC/滤波链路,实现复杂 |
| 脉冲电压注入 | 电感差异导致电流响应方向性 | ±1°左右 | 有极小微动风险 | 饱和差异小时极性判断需谨慎 |
| 反电动势法 | 匀速拖动时感应电压与位置相关 | ±10°以上 | 必须转动 | 静止场合完全不适用 |
| 磁极搜索表 | 遍历施加电压,看转子是否转动 | ±15° | 明显转动 | 只适合老式方波驱动 |
从表格能看出来,脉冲注入是“静止可用”和“实现复杂度”之间最平衡的一个。它不需要额外的信号注入通道,复用现有的三相PWM和电流采样就能完成,这也是它能单靠一段C代码就实现的原因。
2. 脉冲注入为什么能定位转子:三个物理事实和一个数学式子
很多人第一次听说脉冲注入,会直觉地想:电机没转就没有反电动势,凭什么通过给电压就能知道转子在哪儿?答案其实藏在电感的各向异性里。
2.1 物理事实一:PMSM的定子电感不是各向同性的
对于内置式永磁同步电机(IPMSM),d轴磁路上有永磁体,磁阻大,所以d轴电感Ld比q轴电感Lq小;对于表贴式永磁同步电机(SPMSM),虽然Ld和Lq接近相等,但转子磁链会改变磁路的饱和程度,顺着磁极方向施加电压时磁路更饱和,等效电感也会下降。总结成一句话:无论哪种PMSM,转子d轴方向都是“电感最小”的方向。
这个特性和位置检测的关系在于,电感的差异会直接影响电流建立的快慢。同样的电压脉冲下,电感小的方向电流上升快,电感大的方向电流上升慢。
2.2 物理事实二:静止时电压方程退化成纯电气模型
转子不转的时候,反电动势E = 0,同步旋转坐标系下的电压方程变成:
u_d = R·i_d + Ld·(di_d/dt)
u_q = R·i_q + Lq·(di_q/dt)
这里没有机械量参与,只有电阻、电感和电流。换句话说,电机静止时就是一个被动的RL网络,而RL网络的参数分布由转子位置决定。这给了我们一个机会:通过观察电流响应的方向性来反推RL网络的主轴方向,也就是转子d轴方向。
2.3 物理事实三:电流响应幅值是转子角度的二次谐波函数
给定子施加一个任意角度的电压矢量u(θ),由于电感矩阵的椭圆特性,在脉冲结束时刻的电流响应幅值可以展开成:
i(θ) ≈ I0 + Im·cos(2·(θ - θr))
其中θr是转子d轴电角度。这个式子的来源并不难理解:电感矩阵在定子坐标系下是一个对称矩阵,它的倒数矩阵中也包含cos(2θ)和sin(2θ)项,电流响应自然就带上了二次谐波。二次谐波意味着,单单看这一项的峰值,只能确定d轴的“轴线”方向,无法区分N极还是S极,因为相隔180°的电感特性完全相同。
2.4 拟合角度而不是机械地找最大值
如果只在6个或12个离散角度上测量电流响应,直接挑最大的那个点作为位置,分辨率就只有步进角的一半,高精度无从谈起。正确做法是把这些采样点看成一串离散余弦信号,用一次傅里叶级数提取二次谐波的相位:
θr = 0.5 · atan2( Σ i_k·sin(2θ_k), Σ i_k·cos(2θ_k) )
注意前面的0.5系数,因为信号本身是cos(2θ)形式,频率已经翻倍,恢复原始角度相位也要减半。这样做的好处是,即使采样点没有正好落在峰值上,也能通过曲线拟合把精确峰值位置插值出来,等效分辨率远高于扫描步长。
2.5 极性的秘密:磁饱和造成的不对称
知道了轴线还不够,d轴正方向到底是转子N极方向还是S极方向,必须进一步判别。判别原理用一句话说:如果施加的电压矢量方向和永磁体磁链方向一致(指向N极),磁路会被加强饱和,等效电感更小,峰值电流更大;反过来指向S极方向时,磁路退磁,电感相对大,峰值电流小。
实际操作就是:在初步拟合出的轴线方向θ和θ+180°分别注入同样的脉冲,比较峰值电流,电流更大的那个方向就是d轴正方向。这个差异通常在峰值电流的2%到6%之间,看起来很小,但对12位以上ADC来说完全可以分辨,只要做好后面的采样处理和多次平均。
3. C代码逐段拆解:扫描注入、峰值采样、角度拟合、极性判别
下面进入代码环节。我尽量用接近工程实践的写法,而不是教学式伪代码。硬件假设是:12位ADC、10kHz PWM载波、三相电流采样、支持SVPWM的定时器。
3.1 数据结构与顶层流程
先定义结果结构体和一些可调参数:
#define POS_SCAN_NUM 12 /* 粗扫描点数,每隔30°一个 */ #define POS_AVG_TIMES 4 /* 每个方向重复注入次数 */ #define PULSE_WIDTH_US 45 /* 脉冲宽度,微秒 */ #define PULSE_VOLTAGE_NORM 0.25f /* 脉冲电压幅值,相对母线电压 */ #define POLARITY_MIN_MARGIN 8 /* 极性判别最小码值差 */ typedef struct { uint16_t angle_elec_x100; /* 电角度,单位0.01° */ uint16_t angle_mech_x100; /* 机械角度,单位0.01° */ int16_t ipeak_direct; /* d轴正向峰值电流码值 */ int16_t ipeak_reverse; /* d轴反向峰值电流码值 */ uint8_t done; /* 完成标志 */ } InitPosResult_t;顶层函数控制整个流程,按顺序执行“粗扫描→曲线拟合→局部细化→极性判别”四个阶段:
uint8_t PMSM_InitPosDetect(InitPosResult_t *res) { int16_t ipeak_avg[POS_SCAN_NUM]; int16_t d_ipeak, r_ipeak; float theta_est_deg; int8_t polarity; uint8_t i; /* 1. 封锁桥臂,等待母线电压和ADC采样稳定 */ PWM_DisableOutput(); delay_ms(5); /* 2. 12个方向粗扫描,每个方向做4次平均 */ for (i = 0; i < POS_SCAN_NUM; i++) { ipeak_avg[i] = ScanAngleAverage(i * (360 / POS_SCAN_NUM), POS_AVG_TIMES); } /* 3. 二次谐波拟合,得到d轴轴线角度,范围-90°~+90° */ theta_est_deg = FitSecondHarmonic(ipeak_avg, POS_SCAN_NUM); theta_est_deg = WrapAngle(theta_est_deg); /* 4. 局部细化:在初步角度附近做三点抛物线插值 */ theta_est_deg = RefineNearAxis(theta_est_deg); /* 5. 极性判别,决定最终N极方向 */ polarity = JudgePolarity(theta_est_deg, &d_ipeak, &r_ipeak); if (polarity < 0) { return 1; /* 差异太小,需要提高电压或重试 */ } theta_est_deg = WrapAngle(theta_est_deg + polarity); /* 6. 换算并输出 */ res->angle_elec_x100 = (uint16_t)(theta_est_deg * 100.0f) % 36000; res->angle_mech_x100 = (uint16_t)(theta_est_deg * 100.0f / POLE_PAIRS); res->ipeak_direct = d_ipeak; res->ipeak_reverse = r_ipeak; res->done = 1; return 0; }整个流程的时间预算:12个方向×4次平均,加上局部细化的几次脉冲,粗算约50到60个脉冲周期,每个脉冲加续流等待按1ms来算,总耗时在60ms左右。对伺服上电流程来说完全可以接受。
3.2 任意角度电压矢量的生成
这一步很多人会偷懒,只用6个基本矢量方向(60°步长)去扫描,然后靠拟合插值。这种做法不是不行,但基本矢量只有6个,二次谐波需要至少4个点就可以数学上求解,6个点虽然冗余度不高,噪声影响会比较明显。我更推荐用SVPWM实时合成任意方向的空间电压矢量,这样每30°甚至每15°扫描都是精确的,拟合结果也稳定。
static void SetPulseVector(uint16_t angle_deg, float v_norm) { float th = angle_deg * DEG2RAD; float va, vb, vc, vmin, vmax, offset; va = v_norm * cosf(th); vb = v_norm * cosf(th - 2.094f); /* 120° */ vc = v_norm * cosf(th + 2.094f); /* 240° */ /* 中心对齐零序分量 */ vmin = fminf(va, fminf(vb, vc)); vmax = fmaxf(va, fmaxf(vb, vc)); offset = -(vmax + vmin) * 0.5f; TIM1->CCR1 = (va + offset + 0.5f) * TIM1_PERIOD; TIM1->CCR2 = (vb + offset + 0.5f) * TIM1_PERIOD; TIM1->CCR3 = (vc + offset + 0.5f) * TIM1_PERIOD; }给定时器写入比较值后,PWM就开始输出这个方向的空间矢量。整个脉冲期间不再改占空比,让电流保持近似线性上升。
3.3 单角度扫描与电流峰值采样
电流峰值采样的位置很关键。理想情况下应该在脉冲结束前的一两个载波周期采样,因为不饱和时电流近似线性上升,峰值必然出现在脉冲末尾。用“持续找最大值”的方式也可以,但要小心开关噪声毛刺。如果每周期才采样一次,很容易漏掉真正的峰值。比较好的做法是把峰值检测放在PWM中心对齐中断里,每个载波周期都执行一次。
static int16_t InjectPulseAndGetPeak(uint16_t angle_deg) { uint32_t cnt = 0; uint32_t threshold = (uint32_t)(PULSE_WIDTH_US * CPU_MHZ / 1000u); int16_t peak = 0; int16_t ia, ib, i_mag; /* 启动注入:输出指定矢量 */ pos_pulse_active = 1; SetPulseVector(angle_deg, PULSE_VOLTAGE_NORM); while (cnt < threshold) { /* 这段循环里依靠PWM中断采样,见下面的中断函数 */ cnt++; } /* 封锁桥臂 */ pos_pulse_active = 0; SetPulseVector(0, 0.0f); PWM_DisableOutput(); /* 等待续流消失 */ WaitCurrentDecayLoop(); return peak; }PWM中心对齐中断内的采样:
void TIM1_UP_IRQHandler(void) { int16_t ia, ib, ic; int16_t ialpha, ibeta; int16_t imag; if (!pos_pulse_active) { return; } /* 三相电流同时采样 */ ia = ADC_GetChannel(ADC_CH_A); ib = ADC_GetChannel(ADC_CH_B); ic = ADC_GetChannel(ADC_CH_C); /* Clarke变换 */ ialpha = ia - (ib + ic) / 2; ibeta = (ib - ic) * 0.866f; /* 电流矢量幅值 */ imag = sqrtf((float)(ialpha * ialpha + ibeta * ibeta)); if (imag > peak_current_held) { peak_current_held = imag; } }这里有两个细节值得注意。第一,三相电流同时采样可以避免单电阻采样在特定占空比下躲不开死区的问题,虽然成本高一些,但高精度场合值得。第二,用合成矢量幅值而不是某一相电流的峰值,可以消除扫描方向切换时“恰好某一相电流被采到”的偶然性,稳定性好很多。
3.4 二次谐波拟合函数
拟合函数只有十来行,但背后的数学基础刚才已经讲过。代码里直接用sinf/cosf,MCU如果没有FPU会慢一点,但整个扫描阶段只拟合一次,性能压力可以忽略。
static float FitSecondHarmonic(const int16_t *ip, uint8_t n) { float s_sin = 0.0f; float s_cos = 0.0f; float th, rad; for (uint8_t k = 0; k < n; k++) { th = k * (360.0f / n); rad = th * DEG2RAD; s_sin += (float)ip[k] * sinf(2.0f * rad); s_cos += (float)ip[k] * cosf(2.0f * rad); } /* 0.5倍的atan2,是因为cos(2θ)的半角关系 */ return 0.5f * fast_atan2(s_sin, s_cos) * RAD2DEG; }注意返回的角度范围是-90°到+90°,这不代表实际d轴只能在这半区,只是轴线拟合的结果。后面极性判别会决定到底是加0°还是加180°。
3.5 局部细化与抛物线插值
粗扫描的30°步长配合二次谐波拟合,理论上已经能把误差压到1°以内。但为了高精度场合更稳妥,我会在拟合出的轴线附近再扫三个点(中心±10°),用抛物线插值求亚度级峰值位置。
static float RefineNearAxis(float angle_deg) { float a[3]; int16_t v[3]; float denom, delta; a[0] = WrapAngle(angle_deg - 10.0f); a[1] = WrapAngle(angle_deg); a[2] = WrapAngle(angle_deg + 10.0f); v[0] = InjectSingleAngle(a[0]); v[1] = InjectSingleAngle(a[1]); v[2] = InjectSingleAngle(a[2]); denom = v[0] - 2.0f * v[1] + v[2]; if (fabsf(denom) < 1.0f) { return a[1]; /* 退化情况,返回中心角度 */ } delta = 0.5f * (v[0] - v[2]) / denom * 10.0f; return WrapAngle(a[1] + delta); }这里利用的是抛物线顶点公式:x_peak = x_center + Δx·(v₋ - v₊) / (2·(v₋ - 2v₀ + v₊))。实际效果是,在拟合结果附近用一个极小范围的二次搜索把中心位置再校正一下,相当于把30°粗扫描升级成局部高分辨率扫描。
3.6 N/S极性判别
最后一步是判断N极方向。这里必须用差值而不是绝对值比较,因为峰值电流本身就受脉冲电压、温度等因素影响,绝对阈值很容易失效。
static int8_t JudgePolarity(float angle_d_deg, int16_t *i_for, int16_t *i_rev) { int16_t i_direct, i_reverse; int16_t margin; i_direct = InjectSingleAngle(angle_d_deg); i_reverse = InjectSingleAngle(angle_d_deg + 180.0f); *i_for = i_direct; *i_rev = i_reverse; margin = i_direct - i_reverse; /* 差异太小,不信任本次判别,返回错误 */ if (abs(margin) < POLARITY_MIN_MARGIN) { return -1; } /* 正向电流更大 → 当前位置是N极方向 */ return (margin > 0) ? 0 : 180; }POLARITY_MIN_MARGIN这个阈值怎么定?我一般建议取峰值电流的1.5%~2%对应码值,并且不小于5个LSB。太小容易误判,太大又会在弱饱和电机上频繁判失败。工程上稳妥的做法是先做一次大范围电流扫描,统计出峰值电流量级,再动态计算margin阈值,而不是写死一个常数。
4. 高精度场合的误差源与补偿:从±3°做到±0.5°
硬件的非理想特性是脉冲注入精度的大敌。同样的算法在仿真里能做到零点几度,拿到样机上一测可能偏差三五度。下面是我在实际调试中处理过的主要误差源。
4.1 误差源全景清单
| 误差源 | 典型量级 | 对角度的影响 | 处理手段 |
|---|---|---|---|
| 电流采样零漂 | ±0.5%FS | 拟合相位偏移 | 上电零漂校准,差分对消 |
| 逆变器死区 | 1~2μs | 电压矢量畸变,方向偏转 | 脉宽足够宽,死区补偿 |
| 功率管压降 | 0.5~1.5V | 低母线电压时影响最大 | 工作点尽量提高Vbus,记录压降 |
| 温度变化 | 电感变化5%~10% | 角度偏差0.1°~0.5° | 按温度范围标定,限制脉冲电流 |
| 采样开关毛刺 | 幅度可达数LSB | 峰值误判,偶发跳变 | 屏蔽采样窗口,多次平均 |
| 母线电压跌落 | 5%~15% | 后段扫描幅值偏低,拟合失真 | 每次注入同步记录Vbus并归一化 |
4.2 零漂问题:用差动脉冲对消
电流采样通道的零漂是固定偏置,如果不处理,它对每个角度的峰值检测都会叠加一个常量。叠加在余弦信号上的直流分量不会影响二倍频相位,但要注意峰值检测不是线性运算,零漂会导致不同角度的峰值被偏移后,拟合出的二次谐波混入额外分量。最省事的办法是用“正负脉冲对消法”测角:对每个角度,依次施加一个正向脉冲和一个反向脉冲,分别记录峰值I+和I-,响应大小取(I+ - I-)/2。这样零漂在两个测量中方向相反,相减之后正好对消,而且还能把低的2倍信号幅度翻倍提取出来,信噪比也好了。
代价是扫描时间翻倍,但对精度优先的场合值得。实测下来,用了这个技巧之后,同一位置重复检测的标准差从原来的0.6°左右降到了0.2°以内。
4.3 死区与管压降的补偿思路
SVPWM输出的三相占空比是由控制器计算并写入定时器的,但实际作用到电机端的电压并不等于指令电压。每个拓扑都有死区时间,死区期间输出电位由电流续流决定,等效于损失了一个平均电压矢量。管压降也让输出电压偏低,而且方向取决于电流极性。
对于脉冲注入来说,最简单的补偿就是加大脉宽。比如死区2μs,如果脉冲宽度只有10μs,那死区占了20%,电压矢量畸变非常严重;如果把脉宽加到45μs以上,死区占比降到5%以内,角度误差就能压在可接受范围。条件允许时,死区时间做到特定电平的软件补偿,效果会更好,但代码复杂度会显著上升。
还有一个容易忽略的点:如果检测过程中电机微微转了一下,哪怕只转了机械角度一度,在极对数4的电机上电角度就差了4°,直接污染后续结果。所以脉宽和电压幅值的选择必须保证峰值电流产生的电磁转矩小于静摩擦转矩,高精度场合必要时还需要配合抱闸。
4.4 母线电压归一化:被很多人忽略的一步
连续注入十几个脉冲后,母线电容上的电压会明显跌落。如果不做任何处理,后面的扫描点电压低、电流峰值小,拟合出的曲线就畸变了。解决办法是在每次注入脉冲的同时采样母线电压,然后对峰值电流做归一化:
i_normalized = i_measured × Vbus_nominal / Vbus_measured
这一步在代码里就是多读一个ADC通道的事,但对稳定性的提升非常明显,尤其是母线电容容量偏小、或者控制板供电能力不足的场合。
4.5 离线标定:让系统误差变成一张表
无论怎么补偿,总会剩下安装误差、编码器零位偏移、电感不对称这类系统性偏差。量产时我会把电机装在标定台上,用外部高精度编码器作为真实位置参考,在每个扇区内让PMSM_InitPosDetect跑一遍,记录输出与真值的偏差,做成一张表烧写进控制器。这样处理之后,系统的重复精度可以在±0.5°以内。标定表要按温度和电机编号分开存,避免不同物料批次混用。
5. 实测数据与常见坑位记录
写这么多理论,最容易让读者怀疑的是结果到底如何。下面给一组我最近调试某4对极IPMSM样机的实测记录,供参考。
5.1 一组典型实测数据
硬件条件:48V母线、额定电流5A、Ld=0.35mH、Lq=0.9mH、12位三相电流采样、载波10kHz。检测算法用的就是上面的实现,脉冲电压0.25倍母线,脉宽45μs,粗扫12点,局部细化5点。
| 真实电角度(外部编码器换算) | 检测电角度 | 误差 |
|---|---|---|
| 30.2° | 30.7° | +0.5° |
| 95.4° | 94.6° | -0.8° |
| 154.8° | 154.7° | -0.1° |
| 225.3° | 226.5° | +1.2° |
| 285.1° | 284.6° | -0.5° |
| 340.0° | 339.2° | -0.8° |
这个结果整体在±1.2°以内。其中225°附近那个+1.2°偏差,后来查出来是三相采样在某一扇区切换时,有一相电流基准受到了相邻相串扰。做了PCB布线调整和采样窗口屏蔽后,该点误差降到±0.7°以内。
5.2 坑一:N/S极性误判,而且是偶发性的
有段时间检测结果百分之三的概率会直接差180°,排查了很久才定位到原因:极性判别的margin阈值写死为8个码值,而饱和差异在某些温度下只有6个码值左右,结果刚好被噪声盖过,判反了。解决方式是改成动态margin:先统计当次扫描的峰值电流量级,再按峰值电流的2%和10个LSB取较大值作为阈值,误判率降到了千分之一以下。如果现场对安全要求极高,还可以在判别后加一个“验证脉冲”:在判定的N极方向再注入一次反向脉冲,如果差异方向一致才确认通过,否则重测。
5.3 坑二:偶发跳变一个固定角度
另一个现象是同一位置反复测,大多数时候结果很稳定,但偶尔会跳变到一个固定角度,比如从30°跳到210°,或者跳到150°。这类问题通常指向特定方向上的采样毛刺。有一次查到最后是上桥臂开通的瞬间,开关噪声通过ADC采样保持电容耦合到了电流通道,恰好在这个方向上的脉冲开通时刻和ADC采样窗口重合,导致峰值虚高。解决办法是在PWM中断里把采样时刻放到开通死区结束后的稳定区间,并对连续两次扫描结果做“差角>90°就重测”的合理性检查。
5.4 坑三:母线电压拉低导致后段扫描失真
这个坑最隐蔽。小容量母线电容的驱动器上连续脉冲注入会让母线电压逐步下跌,结果前几个角度测出来的电流大,后几个角度测出来的电流小,拟合出的曲线峰值位置被整体拉偏,误差能到两度以上。增加回充等待时间可以缓解,但不是根本办法。后来我在每次注入时同步采样Vbus,用归一化公式把电压影响消掉,这个误差源就基本解决了。这也是我在代码里特意为母线电压归一化预留一个ADC通道的原因。
5.5 排查链路记录:一次典型的角度跳变问题
这里记录一个比较有意思的排查过程。某次样机测试中,检测结果偶尔在15°和195°之间跳,但其他角度都很稳。初步猜测是极性判别失败,但看了日志发现margin值完全正常。于是把每次扫描的12个原始峰值都打出来,发现15°方向对应的那个峰值经常比其他方向明显偏低,低到不像是正常曲线的一部分。再往下查,发现该方向对应的SVPWM矢量恰好让三相输出占空比接近完全导通区,PWM本身已经不能准确合成该矢量。这个时候硬件上还能承受,但占空比受限于死区和最小脉宽约束,实际输出的电压方向已经偏了。解决方法是限制脉冲电压幅值在0.25倍母线以下,保证任何方向的占空比都远离饱和区,问题随即消失。
这个例子说明,分析脉冲注入问题时要始终记住:我们以为施加的电压矢量和实际施加的电压矢量,在极端占空比附近是有差距的。
6. 后续扩展思路与个人体会
说实话,脉冲注入这套方法虽然原理不复杂,但把它优化到高精度可用,是把“数学漂亮”和“工程可行”分开的一道分水岭。做完这个模块之后,我最大的体会是:电机静止时,位置信息并不是不可见的,或者说,它一直都写在电感矩阵里,只是需要用正确的激励把它激发出来。脉冲注入的价值就在于用最少的硬件、最直接的C代码,把这个信息提取出来。
后续如果想继续深入,可以考虑三点扩展。第一,把扫描和拟合做得更智能,比如根据第一次扫描的信噪比自动调整下一步的脉冲幅值和局部扫描范围,让算法在32V到72V的宽母线供电范围内都有稳定的表现。第二,和增量编码器的Z脉冲校准流程结合,一次上电同时完成初始定位和机械零位学习,省掉单独找零的工序。第三,无感FOC的静止启动阶段也可以复用这套逻辑,先把角度预定位做好,再平滑切入高频注入或滑模观测器。
最后分享一个调试小技巧:脉冲注入算法跑起来之后,不要急着看角度精度,先观测每一组12个扫描点的峰值电流曲线。正常的曲线应该呈现出接近余弦的形态,如果有哪个点突然凹陷或者凸起,先别怀疑算法,大概率是采样、占空比或者噪声的问题。曲线形态健康了,角度精度就是水到渠成的事。