简介:面向汽车电机控制开发者的AN5327应用笔记,聚焦三相无传感器、单分流电阻电流采样的永磁同步电机矢量控制方案。内容围绕MC9S12ZVM器件展开,讲解磁场定向控制原理、dq坐标系电机模型、输出电压激励与相电流测量、转子位置与速度估算,并延伸到MC9S12ZVML128上的软件架构、设备初始化及FreeMASTER、MCAT调试调校界面,涉及嵌入式编程与电机控制算法的工程实现。资源包为1个PDF文件,约2.58MB,主题集中,适合作为方案设计、算法移植和调试排错的参考。已有212人学习,读者可梳理从系统概念、FOC控制策略到软件实现与工具调校的完整链路,理解单分流采样和无传感器估算的工程取舍,并借助MCAT调参思路优化电机性能。
1. 一颗分流电阻撑起整套无感 FOC:这套 PMSM 控制套件解决什么问题
做小功率车用电机(电子水泵、油泵、冷却风扇)的同行,大概都遇到过同一个矛盾:客户觉得成本高,BOM 上多一颗相电流传感器都嫌贵;客户又要性能好,低速大扭矩、正反转平滑一样不能少。这套基于 MC9S12ZVM 的 3 相无传感器单分流电流采样 PMSM 控制套件,针对的就是这个矛盾:整机只保留一颗 DC-Link 分流电阻,转子位置和转速由扩展反电动势观测器估算,硬件上不再使用位置传感器。它适合两类人:手上有个电机想快速跑通 FOC 的嵌入式工程师;以及已经跑通、想把现成工程迁移到自己板子上的二次开发者。下面按硬件信号链、算法链路、软件架构、避坑记录的顺序拆解。
2. 系统方案与硬件拓扑:一颗分流电阻怎么还原三相电流
2.1 信号链全貌:从逆变桥到 ADC 输入寄存器
先把硬件这条链路走通,后面的软件才有依据。整个功率级是标准的 3 相电压源逆变器,六颗 MOS 管(或 IPM 内置)组成三个半桥,母线正极到地之间串一颗分流电阻 R71。板上虽然预留了三颗分流电阻的焊盘位置,但这份设计只用 R71 这一颗,另外两颗不贴。母线电流流过 R71 产生压降,经芯片内部两个运算放大器做电平搬移和增益调整后,送入 ADC 输入。
关键点在于“电平搬移”。电机正反转时母线电流有正有负,R71 上的压降也就有正有负,而 ADC 只能吃 0~5V 的单极性输入。常见做法是把运放接成差分放大,同相端加一个 Vref/2 左右的偏置,让零电流对应 ADC 量程的中点,正负电流对称摆开。这颗偏置电压的精度直接决定低速时的电流测量精度,我在自己板子上一般会用一颗低温漂的基准源分压,而不是直接拿 5V 电源电阻分压——后者在温度变化下会带来明显零漂。
增益怎么定?按最大相电流反推。假设量程想到 ±20A,分流电阻 5mΩ,那么满量程压降 100mV,要让它在 0~5V 的 ADC 上占满 ±2.5V 摆幅,运放增益就是 25 倍。增益定好之后,ADC 每个 LSB 对应的电流值就固定了:I_LSB = Vref / (2^N × Gain × Rshunt)。这个换算系数在工程里通常以宏或者常量表的形式固化,后面所有电流采样都乘它。
滤波电路也值得说一下。运放输出到 ADC 引脚之间一般有一级 RC 低通,截止频率设在 PWM 频率的 1/10 到 1/5 之间比较合适。比如 20kHz 开关频率,RC 截止放在 2~4kHz,既能滤掉开关纹波,又不至于把电流环需要的动态响应拖慢。截止频率设太低(比如 500Hz)的后果是电流环相位裕度不够,阶跃响应会明显过冲,调 PI 怎么调都不对,这类问题排查起来很费时间。
| 信号链环节 | 承担的角色 | 关键参数 | 改动时注意 |
|---|---|---|---|
| 三相逆变桥 | 输出空间矢量电压 | 开关频率 20kHz 量级 | 死区时间与最小脉宽 |
| 分流电阻 R71 | 母线电流 I→V | 5mΩ 量级,精度 1% | 功率额定与温漂 |
| 片内运放 | 差分放大 + 偏置 | 增益 20~30 倍 | 偏置点对准量程中点 |
| RC 低通 | 抑制开关纹波 | 截止 2~4kHz | 别为了“干净”把带宽压死 |
| ADC | 采样量化 | 12 位,由 PWM 触发 | 采样时刻与矢量对齐 |
2.2 八个电压矢量与电流重构:为什么一个周期必须采两次
单分流方案的难点全在这里。逆变器三个半桥的开关状态组合共 8 种,其中 000 和 111 是零矢量,此时母线电流不流经分流电阻(三相短路或全开通),测不到任何相电流信息;剩下 6 个非零矢量,每一个状态下母线电流恰好等于某一相电流或者它的相反数。
具体对应关系是:矢量 100(A 上桥导通)时,母线电流等于 ia;矢量 110 时等于 −ic;矢量 010 时等于 ib;矢量 011 时等于 −ia;矢量 001 时等于 ic;矢量 101 时等于 −ib。这几个符号一定要记牢,一旦算错就会导致磁场定向大概偏转 60°,电流需求要大幅增加,但电机可能看上去仍能转动——这正是单分流方案最隐蔽的问题之一。
一个 PWM 周期内,SVPWM 会在两个相邻的非零矢量之间分配时间,再加零矢量补足。于是在同一个周期里可以采到两次不同相位的电流,比如扇区 I 内先出现矢量 100 采到 ia,再出现矢量 110 采到 −ic,第三次用基尔霍夫电流定律 ia + ib + ic = 0 算出来。这就是“两次采样还原三相电流”的全部原理,简洁但条件苛刻:两个非零矢量各自必须持续足够长的时间,让运放输出能够稳定下来,ADC 采样保持也才能完成。
实现上用查表最省事,按扇区把“采哪一相、带什么符号”写成常量表,中断里只做索引和乘加:
/* 六个扇区对应的两次采样相序与符号 * 相位索引: 0=A, 1=B, 2=C * 采样点 1 落在第一个有效矢量中间,采样点 2 落在第二个有效矢量中间 */ static const int8_t kSamplePhase[6][2] = { {0, 2}, /* 扇区 I : ia(+), ic(-) -> ib = -(ia+ic) */ {2, 1}, /* 扇区 II : ic(-), ib(+) */ {1, 0}, /* 扇区 III: ib(+), ia(-) */ {0, 2}, /* 扇区 IV : ia(-), ic(+) */ {2, 1}, /* 扇区 V : ic(+), ib(-) */ {1, 0} /* 扇区 VI : ib(-), ia(+) */ }; static const int8_t kSampleSign[6][2] = { {+1, -1}, {-1, +1}, {+1, -1}, {-1, +1}, {+1, -1}, {-1, +1} }; /* 把两次 ADC 原始值还原成三相电流,单位 A */ static void ReconstructCurrents(uint16_t raw1, uint16_t raw2, uint8_t sector, int32_t offset, int32_t gain_num) { int32_t i0 = ((int32_t)raw1 - offset) * gain_num >> 12; int32_t i1 = ((int32_t)raw2 - offset) * gain_num >> 12; int32_t i2; /* 按扇区查表落相,符号在表里已经定义 */ int32_t i_a = 0, i_b = 0, i_c = 0; int32_t v0 = kSampleSign[sector][0] * i0; int32_t v1 = kSampleSign[sector][1] * i1; switch (kSamplePhase[sector][0]) { case 0: i_a = v0; break; case 1: i_b = v0; break; default: i_c = v0; break; } switch (kSamplePhase[sector][1]) { case 0: i_a = v1; break; case 1: i_b = v1; break; default: i_c = v1; break; } /* 基尔霍夫补齐第三相,同时兼作采样合法性校验 */ i2 = -(i_a + i_b + i_c); if (kSamplePhase[sector][0] == 0 && kSamplePhase[sector][1] == 2) { i_b = i2; } else if (kSamplePhase[sector][0] == 2 && kSamplePhase[sector][1] == 1) { i_a = i2; } else { i_c = i2; } /* 此处把 i_a / i_b / i_c 交给 Clarke 变换 */ gIabc.a = i_a; gIabc.b = i_b; gIabc.c = i_c; }这段代码里几个参数的含义得说清楚。offset是零电流时的 ADC 码值,上电初始化阶段会通过多次采样取平均得到,不能写死成 2048,因为每块板子的偏置电阻和运放失调都不一样。gain_num是电流换算系数,用整数定点表示并右移 12 位,是为了在中断里避免浮点运算——100μs 的中断周期内跑双精度浮点,在中低端主频上会明显占用执行时间。sector由 SVPWM 模块算完占空比后直接给出,不用再重复计算一次。
还有一个隐含约束:这套重构只对非零矢量有效。如果某个扇区里两个有效矢量之一的时间太短(比如刚刚换扇区,或者占空比极端),采样结果就是无效的。工程上的处理办法是加入有效性判断,当有效矢量持续时间低于阈值时,直接沿用上一周期的电流值,并且把这次采样标记为“估算”,同时降低观测器的信任权重。这一块如果省略,低速轻载时电流波形上会出现明显尖刺。
2.3 器件资源分配:MC9S12ZVM 里哪些模块在参与工作
这颗芯片之所以适合做单分流方案,是因为它把电机控制需要的外设集中放在一起。PWM 模块负责生成三对互补带死区的信号,并且能够在计数器到达指定值时触发 ADC,做到采样时刻与矢量严格对齐;两个 ADC 可以流水式工作,一边采母线电流,一边采母线电压和温度;片内两个运算放大器直接承担了前级的信号调理,省掉外部运放和外围电阻;模拟比较器接在母线电流通路上,做过流硬保护,一旦超过阈值即可关闭 PWM 输出,不依赖软件响应速度。
| 片上资源 | 在本方案中的用途 | 配置要点 |
|---|---|---|
| PWM 模块 | 生成 3 对互补 PWM,触发 ADC | 中心对齐,死区 0.5~1μs |
| ADC 通道 0/1 | 两次母线电流采样 | 由 PWM 重载点触发,转换时间要算准 |
| ADC 通道 2/3 | 母线电压、板温 | 慢速通道,几十个周期采一次即可 |
| 片内运放 ×2 | 电流信号差分放大与偏置 | 增益电阻外置,偏置接基准 |
| 模拟比较器 | 过流硬件保护 | 阈值留 20% 余量,输出直连 PWM 故障输入 |
| 定时器 | 100μs 控制节拍 | 与 PWM 同步,避免节拍漂移 |
| 串口 / CAN | FreeMASTER 通信 | 波特率别设太高,留出余量 |
这里有一个经常被忽视的细节:ADC 转换时间。12 位 ADC 在较高主频下也需要几个微秒才能完成一次转换,如果两个采样点之间只隔了 3μs,那么第二个采样还没结束,第一个结果可能还没读出。工程上的做法是在采完第一次后立刻启动第二次转换,把读取结果放到下一次中断的入口处,用“上一拍的结果”来算“这一拍的输出”。这会引入一个控制周期的延迟,在电流环整定时必须在相位裕度里预留出来,不然 PI 参数算得再漂亮,实际响应也会振荡。
3. FOC 算法链路:从 Clarke/Park 到 dq 电流环与观测器
3.1 dq 旋转坐标系下的电机模型与交叉耦合补偿
FOC 的核心思路不再赘述,直接看这套方案里真正会影响代码实现的部分。三相静止坐标系下的电压方程经过 Clarke 和 Park 两次变换后,落到与转子同步旋转的 dq 坐标系里,d 轴对准转子磁链方向,q 轴就是产生转矩的方向。变换后得到的是一个带交叉耦合项和反电动势项的方程组:d 轴电压方程里含有 ωe·Lq·iq 这一项,q 轴电压方程里含有 ωe·Ld·id 和 ωe·ψPM 两项。
这三项的处理方式决定了电流环的动态品质。 ωe·Lq·iq 和 ωe·Ld·id 是交叉耦合,转速越高、电流越大,耦合越严重,如果不补偿,d 轴和 q 轴的电流会出现明显相互干扰——给 q 轴一个阶跃指令,d 轴电流也会跟着变动,表现出来的现象是加载瞬间磁场被削弱,效率下降,严重时还会影响观测器收敛。所以工程实现里几乎都会加前馈补偿:
/* 电流环输出前的解耦补偿,vd/vq 为 PI 输出 */ static void Decouple(int32_t id, int32_t iq, int32_t we, int32_t ld, int32_t lq, int32_t flux_pm, int32_t *vd, int32_t *vq) { /* 交叉耦合项:单位与 PI 输出一致,均为标幺后的电压 */ int32_t cross_d = MUL(we, MUL(lq, iq)); int32_t cross_q = MUL(we, MUL(ld, id)); int32_t bemf_q = MUL(we, flux_pm); /* 反电动势,只在 q 轴出现 */ *vd = *vd - cross_d; /* d 轴减掉耦合项 */ *vq = *vq - cross_q - bemf_q; /* q 轴减掉耦合项与反电动势 */ }we是电角速度,由观测器输出;ld、lq是 dq 轴电感,从电机参数表里拿;flux_pm是永磁体磁链。这三个参数如果不准,补偿反而会变成干扰源,尤其是lq在表贴式电机上接近ld,在内置式电机上可能差两三倍,填错一个数量级,高速段电流会完全失控。我的习惯是先在低速小电流下把flux_pm反推出来(拿空载反电动势除以电角速度),再单独做一组 dq 轴电感辨识,两个参数都验证过之后再开高速。
至于反电动势项 ωe·ψPM,它随转速线性增长,低速时几乎可以忽略,高速时却是 q 轴电压的主要成分之一。补偿得好,母线电压利用率能上去一截;补偿得不好,转速上到额定值 70% 以上就会顶到电压限幅,表现为转速“上不去”,但电流并没有达到限流值——这个现象和弱磁没做好很像,容易被误判。
3.2 电流环 PI 的极点配置整定:两个公式就够
补偿做完之后,d 轴和 q 轴的被控对象各自变成一个一阶惯性环节:输入是电压,输出是电流,时间常数由 L/R 决定。两个通道结构完全一样,只是电感数值不同,所以 PI 参数可以用同一套公式算,只是在增益上分开。
按极点配置法,把闭环系统配置成自然频率 ω0、阻尼比 ξ 的二阶系统,可以得到:
- 比例增益 Kp = 2ξ·ω0·L − R
- 积分增益 Ki = ω0²·L
其中 L 是对应轴的电感,R 是定子电阻。ω0 一般取电流环期望带宽的 1/5 到 1/3 的开关角频率,ξ 取 0.707 附近。实际写代码时,因为积分项在离散域里是累加的,还需要把 Ki 乘以采样周期 Ts 变成增量形式:
/* 电流环 PI 参数计算,输入单位统一为标幺值 */ void CurrentPiDesign(int32_t rs, int32_t ld, int32_t lq, int32_t w0, int32_t xi, int32_t ts, int32_t *kp_d, int32_t *ki_d, int32_t *kp_q, int32_t *ki_q) { /* Kp = 2*xi*w0*L - R ;Ki_discrete = w0^2 * L * Ts */ *kp_d = MUL(2 * xi, MUL(w0, ld)) - rs; *ki_d = MUL(MUL(w0, w0), MUL(ld, ts)); *kp_q = MUL(2 * xi, MUL(w0, lq)) - rs; *ki_q = MUL(MUL(w0, w0), MUL(lq, ts)); /* 防止计算出的 Kp 为负,负增益会直接把环路变成正反馈 */ if (*kp_d < 0) *kp_d = 0; if (*kp_q < 0) *kp_q = 0; }w0的单位是 rad/s,用定点表示,ts是 100μs 对应的标幺值。这个函数通常在参数辨识完成后调用一次,结果写进 RAM 变量,FreeMASTER 上可以直接看和改。
注意那个Kp < 0的保护。当 ω0 取得偏低、而定子电阻 R 又偏大(小功率电机常见)时,2ξω0L有可能小于 R,算出来是负数。负的比例增益不会报错,只会让系统变成正反馈,一上电就过流。我在第一次做这类整定时遇到过这个问题,当时以为是硬件故障,排查了一整天才发现问题出在公式的符号上,现在习惯了不管三七二十一先加个下限保护。
还有一点,电流环整定之后建议做一次阶跃响应验证。给 q 轴电流一个 20% 额定值的阶跃,用 FreeMASTER 的高速记录器抓波形,看上升时间和超调量。上升时间大致等于 1/ω0 的量级,超调控制在 10% 以内算健康。如果超调大得离谱,先别急着降 Kp,检查一下采样延迟和滤波截止频率,很多时候问题出在相位滞后上。
3.3 扩展反电动势观测器与无感启动:三段式状态机
无位置传感器方案最难的部分不是观测器本身,而是从零速起步。零速时反电动势为零,观测器没有任何输入,输出全靠猜。所以这类方案普遍采用“对齐—开环加速—切换闭环”的三段式流程,每一段的切换条件都要设得有依据。
对齐段:给 d 轴一个固定的电流指令(比如额定电流的 30%),q 轴给 0,持续几百毫秒。转子会被拉到 d 轴方向,位置就“已知”了,等于人为地建立了一个初始位置。时间太短转子没到位,时间太长电机发热,而且如果负载本身有静摩擦,可能需要给一点抖动或者两次不同方向的对齐来消除齿槽影响。
开环加速段:按设定的加速度把电角度斜坡往上推,同时保持电流幅值不变,频率从零逐渐升高。这个阶段本质上是让电机“跟着”电流矢量转,转速由软件给定,和实际转子是否同步无关。加速斜率设太陡会失步,设太缓则启动时间长、发热多。经验值是扭矩电流不大于额定的一半,加速度按负载惯量算,留 1.5 倍余量。
切换闭环段:等反电动势观测器输出的角度与开环角度的偏差收敛到一个阈值以内,并且持续若干个周期,就可以切闭环。切换瞬间要做角度和速度的“交接”,把观测器的积分器初值设为当前开环角度,避免转矩突跳。
/* 无感启动状态机,100us 调用一次 */ typedef enum { ST_STOP, ST_ALIGN, ST_RAMP, ST_RUN, ST_FAULT } RunState; static void SensorlessSm(void) { switch (gState) { case ST_ALIGN: gIdRef = ALIGN_CURRENT; /* d 轴拉电流,q 轴给 0 */ gIqRef = 0; if (++gAlignCnt > ALIGN_TICKS) { /* 对齐持续约 300ms */ gAlignCnt = 0; gState = ST_RAMP; } break; case ST_RAMP: gSpeedRef += RAMP_STEP; /* 开环速度斜坡 */ gIdRef = 0; gIqRef = RAMP_CURRENT; /* 加速段电流恒定 */ /* 观测器角度与开环角度误差持续小于阈值 -> 切闭环 */ if (ABS(gObsAngle - gOpenAngle) < ANGLE_TOL && ++gConvCnt > CONV_TICKS) { gConvCnt = 0; gObsAngle = gOpenAngle; /* 交接:角度对齐 */ gState = ST_RUN; } break; case ST_RUN: gIdRef = 0; /* 稳态下 d 轴给 0 */ gIqRef = SpeedPiOut; /* 速度环输出 */ if (gObsAngleValid == 0) { /* 观测器失锁,退回对齐 */ gState = ST_ALIGN; } break; default: gIqRef = 0; break; } }ALIGN_CURRENT和RAMP_CURRENT这两个参数是启动成功与否的关键,取值和电机相电阻、负载惯量强相关。相电阻大、电流上不去的电机,对齐电流要往上提;带减速箱或者皮带轮的负载,加速电流要留更多余量。ANGLE_TOL和CONV_TICKS决定什么时候切闭环,前者设太严会导致永远切不过去,只能在开环里干转,后者设太小则容易被噪声骗过去,切过去的瞬间就失步。
观测器本身用的是扩展反电动势的龙贝格结构:把 dq 轴电流作为状态量,用估算电流和实测电流的误差去驱动一个增益矩阵,误差收敛的同时就能把反电动势估出来,再由反电动势的反正切得到角度。离散化之后每一拍做两三次乘法累加,开销很小。需要注意的是观测器增益里的 ωe 项,在低速时它很小,观测器收敛慢;高速时它又很大,容易放大噪声。所以工程上常把观测器增益做成随速度变化的,低速段加强,高速段收敛。
4. 软件架构拆解:状态机、100μs 中断与外设初始化
4.1 分层结构与工程目录
这套工程的代码组织方式是典型的“中断驱动 + 后台任务”结构,分成三个明显的层次:最底下是外设驱动层,直接操作寄存器,负责 PWM、ADC、运放、定时器的初始化;中间是电机控制算法层,Clarke/Park 变换、PI 调节、观测器、SVPWM 都在这一层,运行在 100μs 定时中断里;最上层是应用层,包括状态机、故障管理、速度指令处理和 FreeMASTER 通信,跑在主循环里。
| 目录 / 文件组 | 职责 | 运行上下文 |
|---|---|---|
| 外设初始化 | 时钟、PWM、ADC、运放、比较器配置 | 上电一次 |
| ADC 中断服务 | 读电流、重构、Clarke/Park | 100μs 硬中断 |
| FOC 算法 | 电流环、观测器、SVPWM 占空比计算 | 100μs 中断内 |
| 速度环 | 速度 PI、限幅 | 1ms 软定时 |
| 状态机与保护 | 启停、对齐、故障判定 | 主循环 |
| FreeMASTER 接口 | 变量映射、Scope、Recorder | 主循环 + 通信中断 |
这种分层的价值在于:中断里只跑确定性强的计算,凡是带条件判断、带通信、带字符串处理的都挪到后台,避免中断执行时间抖动。我见过不少初学者把 FreeMASTER 的变量刷新也塞进定时中断,结果 100μs 的节拍被拉长到 130μs,电流环带宽直接掉一截,怎么调参都调不出手册上的波形。
4.2 上电初始化顺序:时钟、PWM、ADC、运放、中断
初始化顺序不能随意调整,核心原则是“先让外设处于安全状态,再打开中断”。PWM 模块必须在配置好死区和输出极性之后再使能,而且使能之前要把占空比寄存器清零,否则上电瞬间可能出现一个宽度不定的脉冲,直接触发过流保护。ADC 要先完成校准再启用,运放要在 ADC 之前配置好增益,否则前几次采样得到的都是无效值。
void SystemInit(void) { ClockInit(); /* 锁相环 -> 总线时钟,确认稳定后再继续 */ GpioInit(); /* 先把功率级使能脚拉低,保证桥臂关断 */ PwmInit(); /* 中心对齐、死区、极性;此时输出仍被禁止 */ AdcInit(); /* 通道、触发源、转换精度 */ AdcCalibrate(); /* 校准必须在使能转换之前完成 */ OpampInit(); /* 增益与偏置,顺序在 ADC 之后无所谓,但要在采样前 */ CompInit(); /* 过流比较器,阈值寄存器写入后使能 */ TimerInit(); /* 100us 控制节拍,先不启动 */ CurrentOffsetCalib(); /* 关断桥臂状态下采多次平均,得到零漂 */ PwmEnable(); /* 最后一步才真正输出 */ TimerStart(); }CurrentOffsetCalib()这一步经常被漏掉或者做得不认真。正确做法是在三个下桥全部关断、母线电压已经建立但电机没有电流的状态下,连续采 64 次或 128 次 ADC 值求平均,作为零电流基准。采样次数太少,噪声消不掉;采样时如果功率级还在开关,测出来的偏置里混着纹波,后面低速运行时电流会有个恒定的小偏差,表现为电机轻微抖动或者空载电流偏大。
PwmEnable()放在最后是有原因的。PWM 一旦使能,桥臂就开始按占空比动作,此时如果占空比寄存器里还是上一次的残留值,或者电流环因为中断没开而输出 0,都可能造成短时直通。稳妥的做法是在使能前显式把三相比较值都写成 50%(对应零电压矢量),并且把 PI 积分器清零。
4.3 控制中断里到底跑了什么
中断服务函数是整个方案的核心部分,100μs 一次,也就是 10kHz 的控制频率。这一节按执行顺序过一遍。入口处先读 ADC 结果,然后做电流重构,接着 Park 变换到 dq,再算电流环,最后一拍输出经逆 Park 和 SVPWM 变成占空比。
void AdcIsr(void) /* 100us 触发一次 */ { /* 1. 读上一拍启动的转换结果 */ uint16_t raw1 = ADC_ReadResult(RESULT0); uint16_t raw2 = ADC_ReadResult(RESULT1); /* 2. 单分流重构出三相电流 */ ReconstructCurrents(raw1, raw2, gSector, gOffset, gGain); /* 3. Clarke -> alpha/beta */ gIalpha = gIabc.a; gIbeta = (gIabc.a + 2 * gIabc.b) * ONE_BY_SQRT3; /* 4. Park -> dq,角度来自观测器 */ gId = gIalpha * gCos + gIbeta * gSin; gIq = -gIalpha * gSin + gIbeta * gCos; /* 5. dq 轴电流 PI,输出为电压指令 */ gVd = PiCalc(&gPiD, gIdRef - gId); gVq = PiCalc(&gPiQ, gIqRef - gIq); /* 6. 解耦补偿(第 3.1 节的函数) */ Decouple(gId, gIq, gWe, gLd, gLq, gFluxPm, &gVd, &gVq); /* 7. 逆 Park -> alpha/beta 电压 */ gValpha = gVd * gCos - gVq * gSin; gVbeta = gVd * gSin + gVq * gCos; /* 8. SVPWM 算占空比并更新比较寄存器 */ SvpwmUpdate(gValpha, gVbeta); /* 9. 启动下一次 ADC 转换,触发点由 PWM 决定 */ AdcStartConversion(); }gCos和gSin这两个值不是在中断里现算的,而是查表加线性插值,或者用观测器输出角度索引一张 256 点或 1024 点的正弦表。现算三角函数在这个节拍下开销太大,很多中低端 MCU 没有硬件浮点,一次 sinf 调用就要吃掉几微秒。
PiCalc里有两个保护必须加:一是积分饱和限制,积分器累加到限幅值就停住,否则退出饱和需要很长时间,表现为转速超调后回不来;二是输出限幅,dq 电压的合成幅值不能超过母线电压对应的最大矢量幅值(SVPWM 下约为 Vdc/√3),超了之后波形就失真,观测器也会跟着出错。
第 9 步的AdcStartConversion()看起来多余,实际上决定了采样点位置。真正启动转换的时刻是由 PWM 比较匹配事件触发的,这一句只是软件上的状态复位。采样点要放在两个有效矢量的中间位置,这需要在 SVPWM 算完占空比之后,把两个比较值再算一次并写进触发寄存器。这块逻辑如果和 PWM 的重载时刻没对齐,采到的电流就是正在跳变的波形,噪声会大到没法用。
4.4 故障保护与 FreeMASTER/MCAT 通信层
保护这块分软硬两层。硬件层是模拟比较器直接连到 PWM 的故障输入,检测到过流立刻封锁输出,响应时间在微秒级,不依赖软件。软件层在主循环里周期检查母线电压、电流有效值、转速偏差,一旦越限就进入故障状态,关 PWM、清积分器、点亮故障标志。
保护项包括:母线过压、母线欠压、过流、过载(电流持续超过阈值一段时间)、启动失败(开环阶段超过设定时间仍未切换闭环)。每一项都有对应的阈值和延时,延时的作用是躲开瞬时尖峰,比如启动瞬间母线电压会跌落一下,如果欠压保护没有延时,一按启动就报故障。
FreeMASTER 这条链路主要干三件事:一是实时变量监视,把转速、电流、角度、母线电压映射成可观察的变量;二是 Scope 示波,能看电流波形和转速响应;三是 Recorder 高速记录,按 100μs 的节拍把一片 RAM 缓冲区填满,再一次性传上来,用来看开关周期级别的细节。MCAT 工具则是把这些参数按电机参数、电流环、速度环、无感参数分成几页,做成滑块和输入框,调完直接写进目标板的 RAM。
这里有个实践上的取舍:Recorder 的采样率设得越高,能记录的时间窗口就越短,因为缓冲区大小有限。看启动过程用 1ms 的采样率、记录几秒钟;看电流环细节用 100μs、记录几十毫秒。两个场景分开配置,不要指望一次抓全。
5. 避坑与排查:单分流采样最容易出问题的几个地方
5.1 占空比贴近边界时采样窗口消失
现象:电机低速轻载运行,或者转速指令接近零时,相电流波形上出现周期性的尖刺,电流环输出抖动,电机有轻微噪声;负载加重后现象消失。
原因:这是单分流方案的固有问题。当某个扇区内两个有效矢量的作用时间很短(占空比接近 0% 或 100%,或者刚好在扇区切换边界上),运放输出还没来得及稳定,ADC 就已经采样了,采到的是过渡过程的中间值,重构出来的电流自然不对。第三个用基尔霍夫算出来的相电流误差更大。
解决:在 SVPWM 里加最小脉宽限制,保证每个有效矢量至少有 1.5~2μs 的持续时间,不够就做矢量插入或者占空比钳位。同时在采样逻辑里加有效性判断,两次采样中只要有一次的窗口不足,就沿用上一拍的电流值,并把这拍标记为估算,观测器的角度更新权重相应降低。这套办法不能消除误差,但能把误差控制在不影响控制的范围内。
5.2 运放偏置漂移导致低速电流失真
现象:空载运行时,用 FreeMASTER 看三相电流,波形不是干净的正弦,而是“长毛”的,而且直流分量随时间缓慢漂移;低速段尤其明显,高速段好转。
原因:偏置电压的温漂。上电时校准的零电流基准,随着板子发热会偏移几十个 LSB,换算成电流就是几百毫安。低速时相电流本身只有一两安,这个偏差占比很高,导致电流波形畸变,进而影响观测器收敛。
解决:一是硬件上把偏置分压电阻换成低温漂的,或者直接用一个基准源;二是软件上做在线零漂跟踪,在电机停止或者零矢量期间重新采样取平均,慢慢更新基准值;三是提高运放增益的同时注意 ADC 量程,别让满量程电流把 ADC 顶死,那样连正负都分不清了。
5.3 无感启动反转或反复启停
现象:按下启动后电机不转,或者先反转一下再正转,或者转到某个速度就停住、过一会儿又重启,如此反复。
原因有三种常见情况。一是对齐电流不够或者对齐时间太短,转子没被拉到指定位置,开环加速时初始角度就是错的;二是开环加速度设得太陡,转子跟不上电流矢量的旋转速度,直接失步;三是切换闭环的角度误差阈值设得太严,观测器永远收敛不到,状态机在开环里超时后重启。
解决:先把对齐电流提到额定电流的 30%~40%,对齐时间延长到 300~500ms 试一次;再把加速度降一半,看能不能顺利切闭环;切换阈值放宽到 15~20 电角度,收敛计数从 5 次提到 20 次。三个参数一次只改一个,改完记录波形,否则出了问题不知道是哪个参数导致的。这套流程我一般是写成一张表,每次调试都填,比凭感觉改靠谱。
5.4 FreeMASTER 变量观察把 100μs 节拍拖长
现象:接上 FreeMASTER 之后,电机噪声变大,电流波形变差,断开 FreeMASTER 就恢复正常;用示波器测 PWM 中断的翻转引脚,发现中断周期从 100μs 变成了 120μs 以上。
原因:把通信相关的处理放进了中断,或者中断优先级配置不当,让通信中断打断了控制中断。另一种情况是 Recorder 缓冲区配置得太大,每次采样都要做大量数据搬运(DMA 没配好,靠 CPU 搬),CPU 负载超过 70%,控制节拍就被拖长了。
解决:通信中断优先级设得比控制中断低,控制中断里绝不调用任何通信函数;Recorder 用 DMA 搬运,或者在主循环里分块拷贝;观察变量本身用普通的全局 RAM 变量,FreeMASTER 通过后台轮询读取,不要用带回调或者带计算的变量。判断标准很简单:接不接调试器,中断周期应该完全一样。
5.5 过流保护误触发
现象:电机运行正常,但偶尔莫名其妙报过流,复位后又能跑,负载稍大就更容易出现。
原因:模拟比较器的阈值设得太贴近正常工作电流,开关瞬间的电流尖峰(由死区、寄生电容、二极管反向恢复引起)超过了阈值;或者比较器输出没有做数字滤波,一个毛刺就触发封锁。
解决:阈值在额定峰值电流的基础上留 30%~50% 的余量,同时给比较器输出加一个几微秒的数字消抖(用定时器计数或者 RC 滤波)。另外检查分流电阻的功率额定,瞬时大电流下电阻发热会导致阻值漂移,采样值跟着变化,这类问题在长时间满载运行时才会暴露。
6. 二次开发:把这套套件改成自己的板子
拿到工程之后,直接烧进去能跑,但只要换一块板子,基本都要改三处:引脚映射、参数表、保护阈值。我一般按这个顺序动。
第一步改引脚。把 PWM 三对输出、ADC 两个电流通道、运放输入、比较器输入、串口引脚按新板子的原理图重新映射,改完之后先不接电机,用示波器看六路 PWM 波形,确认死区时间、极性、互补关系都正确,再往下一步走。这一步如果跳过,后面所有调试都是在错误的基础上进行。
第二步改参数表。电机极对数、定子电阻、dq 轴电感、永磁体磁链、额定电流、额定转速、母线电压范围,这几项填进去之后,电流环和速度环的 PI 参数用第 3.2 节的公式重新算一遍,不要直接沿用手册上的数值。参数辨识的土办法是:万用表测相间电阻除以 2 得相电阻,电感表测相间电感除以 2 得相电感,反电动势常数用外力拖动电机测线电压峰值除以角速度得到。
| 需要改的地方 | 位置 | 怎么确认改对了 |
|---|---|---|
| 引脚映射 | 外设初始化段 | 示波器看六路 PWM 与死区 |
| 电机参数 | 参数结构体 / MCAT 页 | 辨识值与实测值误差 < 10% |
| PI 增益 | 由公式重新计算 | 电流阶跃超调 < 10% |
| 保护阈值 | 比较器与软件保护 | 满载不误报,堵转必报 |
| ADC 零漂 | 上电校准函数 | 空载电流直流分量接近 0 |
第三步做验证。先空载跑,看电流波形是不是干净的正弦;再加 20%、50%、100% 负载,看转速跌落和恢复时间;最后做正反转切换和急停,确认保护动作正确。
提示:每次改参数之前,先在 FreeMASTER 里把当前的一套参数存成文件,改坏了能退回去。我吃过一次亏,调观测器的时候连着改了八个参数没记录,最后不知道哪个是对的,只能从头再来一遍。
从那以后,我每次改动电机参数或者 PI 增益,都会强制走一遍“存参数—改一个—看波形—记录结论”的流程,哪怕只改一个数字。这套单分流方案本身的算法并不复杂,难点都在参数和边界条件上,把每一次改动的因果关系记清楚,比任何调试技巧都管用。希望帮到你。
本文还有配套的精品资源,点击获取