电流环从浮点改 Q15,波形却出现削顶和周期性毛刺,问题通常不在算法。结论先给:dsPIC33F 的 DSP 引擎把乘法结果堆到 40 位累加器里,Q15 输入只会得到 1.31 中间值,饱和模式、舍入方式和回写目标三处没配齐,定点化就会在波形上留下痕迹。
一台 20 kHz 电流环的驱动器,浮点版本跑满 CPU 也留不出余量,改成 Q15 定点后 CPU 负载立刻降下来,但示波器上的相电流开始周期性削顶,轻载时还能看到台阶状毛刺。结论先给:这不是"定点精度不够",而是 40 位累加器、饱和模式与舍入回写这三层机制没有一起配好——dsPIC33F 的 DSP 引擎把 Q15 乘法结果放在 1.31 的 40 位累加器里,最后一步回写到 16 位数据空间时,饱和边界和舍入方式决定了波形长什么样。
本文按 Microchip《dsPIC33FJ16GP101/102 和 dsPIC33FJ16MC101/102 数据手册》(DS70652B_CN)第 3 章 CPU 与第 24 章指令集整理,只讨论运算与数据通路,不涉及 PWM 与 ADC 的配置。
dsPIC33F 的内核是 16 位数据的改进型哈佛结构,指令字 24 位,程序计数器 23 位,最大可寻址 4M×24 位程序空间。做电流环真正起作用的是 DSP 引擎那一段:一个 17 位×17 位单周期乘法器、一个 40 位加法器/减法器、两个 40 位饱和累加器 ACCA 与 ACCB,以及一个 40 位桶形移位器。乘法器输出 33 位,再符号扩展成 40 位送进累加器,多出来的 8 位(bit 32~bit 39)就是常说的警戒位。
| 内核资源 | 手册规格 | 对电流环的意义 |
|---|---|---|
| 指令字与 PC | 24 位指令字,23 位 PC;除跳转、MOV.D 与表指令外均单周期 | 控制周期预算按单周期指令估算才不会低估 |
| 乘法器 | 17×17 位,单周期,支持有符号、无符号与混合符号;(-1.0)×(-1.0) 结果正确 | Q15 的 -1.0 边界不会算错,省掉软件特判 |
| 累加器 | ACCA、ACCB 各 40 位,带自动符号扩展与饱和逻辑 | 一个做电流环积分,一个做滤波或第二轴,互不干扰 |
| 桶形移位器 | 40 位宽,单周期左移或右移最多 16 位;正数右移、负数左移、0 不变 | 定标换算不用循环移位,一次到位 |
| 除法 | 32/16 与 16/16,须放在 REPEAT 内迭代,共 19 个周期,商在 W0、余数在 W1 | 电流环里少用除法,标定系数尽量预先算成 Q15 乘法 |
数据空间这一侧也有一条容易忽略的规则:MCU 类指令只通过 X 地址发生单元访问,整个存储器映射是线性的;DSP 指令同时通过 X 和 Y 两个地址发生单元,才能在一个周期里取两个操作数。这就是 MAC 指令可以"取两个数、乘、累加"一次完成的原因,也是把系数表和采样值分开放时要注意的地方。
小数模式下,数据的最高位是符号位,小数点暗含在符号位之后,也就是 Q 格式。16 位 Q15 的取值范围是 -1.0(0x8000)到 0.999969482(0x7FFF),分辨率 3.01518×10⁻⁵;两个 Q15 相乘得到 1.31 的 33 位结果,精度 4.65661×10⁻¹⁰。所以"Q15 乘法不丢精度"这句话只在中间结果保持 1.31 时成立,一旦过早截回 16 位,误差就固定下来了。
| 数据格式 | 取值范围 | 精度 | 典型位置 |
|---|---|---|---|
| 16 位整数 | -32768 ~ 32767 | 1 | ADC 原始码、计数器 |
| Q15 小数 | -1.0 ~ 0.999969482 | 3.01518×10⁻⁵ | 采样值、误差、输出占空比 |
| 1.31 中间值 | 由 40 位累加器承载,取高 32 位有效 | 4.65661×10⁻¹⁰ | PI 运算的乘加中间结果 |
| 1.15 回写值 | -1.0 ~ 0.999969482 | 3.01518×10⁻⁵ | 累加器回写到 RAM 或 W13 的结果 |
乘法器默认工作在小数模式,由 CORCON 的 IF 位选择:IF=0 为小数乘法,IF=1 为整数乘法。电流环里所有系数都按 Q15 归一化,IF 就必须保持 0;如果某次为了处理计数器临时把它置 1 又忘了改回来,后面的乘法会整体偏移,表现为增益忽然变大或变小。
DSP 引擎的行为几乎都由内核控制寄存器 CORCON 决定。它不像外设寄存器那样可以随时改,尤其 SATA、SATB、ACCSAT 这几位,一旦在控制周期中间变化,同一个 PI 环路的前后两个周期就会用不同的饱和规则,波形上会出现难以复现的跳变。
| 位 | 名称 | 电流环建议值 | 原因 |
|---|---|---|---|
| bit 12 | US:DSP 乘法符号模式 | 0(有符号) | 电流与误差都是带符号量;无符号模式只适合功率、幅值类计算 |
| bit 10:8 | DL<2:0>:DO 循环嵌套层级 | 由循环结构决定,只读观察 | 用来确认嵌套层数,不要手工写 |
| bit 7 / bit 6 | SATA / SATB:ACCA、ACCB 饱和使能 | 1 / 1(使能) | 积分项过冲时自动限幅,避免 40 位溢出破坏符号位 |
| bit 5 | SATDW:数据空间写饱和 | 1(使能) | 回写到 16 位时把超范围值钳到 1.15 边界 |
| bit 4 | ACCSAT:饱和模式 | 0(1.31 正常饱和) | 1 是 9.31 超饱和,只在需要保留大动态范围时用 |
| bit 1 | RND:舍入模式 | 0(无偏/收敛舍入) | 长时间积分下,有偏舍入会持续累积正向误差 |
| bit 0 | IF:整数/小数乘法 | 0(小数) | 全部系数按 Q15 归一化,必须保持小数模式 |
同一张表里还有 PSV(程序空间可视性)与 IPL3(中断优先级高位),它们和运算无关,但初始化时经常被整字写入顺带改掉。稳妥做法是读—改—写,只动需要动的位,不要直接给 CORCON 赋一个常数。
40 位加法器的溢出分两级。溢出到警戒位(bit 32~bit 39)属于可恢复溢出,状态位 OA、OB 会在每次数据通过加法器时更新;如果 INTCON1 里的 OVATE、OVBTE 使能,还能直接产生算术警告陷阱,让程序在增益算错的当下就介入。溢出到 bit 39 则是灾难性溢出,会破坏累加器的符号位,此时如果 COVTE 置位,同样可以触发陷阱。
| 模式 | 触发条件 | 结果 | 状态位 |
|---|---|---|---|
| bit 39 超饱和(ACCSAT=1) | 累加器超出 9.31 范围 | 装入 0x7FFFFFFFFF 或最小负 9.31 值 | SA 或 SB 置 1 并保持 |
| bit 31 正常饱和(ACCSAT=0) | 累加器超出 1.31 范围 | 装入 0x007FFFFFFF 或最小负 1.31 值,警戒位不参与 | SA 或 SB 置 1 并保持;OA/OB 不置位 |
| bit 39 灾难性溢出 | 饱和被禁止且 bit 39 溢出 | 不饱和,符号位被破坏 | SA 或 SB 置 1;COVTE=1 时触发陷阱 |
要注意 SA、SB 是"粘住"的:只能由软件清零,不能被置 1。程序在控制周期末读一次 SAB,就能同时知道两个累加器有没有饱和过;清 SAB 会连带清掉 SA 和 SB。这条特性非常适合做在线诊断——一旦发现饱和度超过设定比例,就说明给定或增益已经超出设计范围,而不是等波形削顶再去查。
累加器是 40 位,最终要变成 16 位占空比,中间必须舍入。手册给了两种:常规(有偏)舍入在 ACCxL 落在 0x8000~0xFFFF 时把 ACCxH 加 1,长期运行结果会稍稍偏大;收敛(无偏)舍入只在 ACCxL 恰好等于 0x8000 时再看 ACCxH 的最低位,为 1 才加 1,从而消除累积偏差。电流环是长时间连续积分,RND=0 更合适。
回写有两条路。一是 SAC / SAC.R 指令,把目标累加器的内容按截取或舍入形式写入数据空间;二是 MAC 类指令自带的回写:除 MPY、MPY.N、ED、EDAC 之外,MAC、MSC、MOVSAC、CLR 都可以把非目标累加器 bit 16~bit 31 的舍入值写到 W13,或写到 [W13] 指向的地址并把 W13 加 2,格式固定为 1.15,且这类回写总是舍入的。若 SATDW=1,写入前还会做一次饱和:大于 0x007FFF 的钳到 0x7FFF,小于 0xFF8000 的钳到 0x8000,而且不影响源累加器的值——这一点很关键,限幅只发生在写回的那一份数据上,积分器内部仍然保留完整精度。
; 20 kHz 电流环:W4 = 电流给定(Q15),W5 = 采样反馈(Q15)
; CORCON 初始化:IF=0 小数、US=0 有符号、SATA/SATB/SATDW=1、ACCSAT=0、RND=0
MOV #0x00E0, W0 ; SATA|SATB|SATDW,ACCSAT=0、RND=0、IF=0
MOV W0, CORCON
CLR A ; 每个周期清比例累加器
SUB W5, W4, W0 ; W0 = 误差(Q15)
MOV #Kp_Q15, W6
MOV #Ki_Q15, W7
MAC W0*W6, A ; ACCA += Kp * err(1.31 中间值)
MAC W0*W7, B ; ACCB += Ki * err(跨周期积分)
SAC.R A, W1 ; ACCA 舍入后取 1.15 → W1
SAC.R B, W2 ; ACCB 舍入后取 1.15 → W2
ADD W1, W2, W3 ; 比例 + 积分
BRA OV, LIMIT ; 相加溢出则走限幅
MOV W3, PWM1DC1 ; 写入占空比
BRA NEXT
LIMIT
MOV #0x7FFF, W3 ; 正向钳位到 1.15 最大正数
MOV W3, PWM1DC1
NEXT
; 周期末检查 SAB,统计饱和次数用于在线诊断
示例里刻意把比例项和积分项分放在两个累加器:ACCA 每周期清零、只承担本周期乘加,ACCB 跨周期累加。这样做的好处是 MAC 的回写不会互相覆盖,也不会因为一个累加器饱和把另一个带偏。
| 现象 | 常见根因 | 改法 |
|---|---|---|
| 轻载时输出有台阶状毛刺 | 过早把 1.31 截成 16 位,低位被丢弃 | 中间结果留在累加器,只在最后用 SAC.R 舍入一次 |
| 大电流时波形削顶且不可恢复 | 饱和被关闭,或 SOP 用了 9.31 超饱和 | SATA/SATB 使能,ACCSAT=0 用 1.31 饱和 |
| 长时间运行后静差缓慢增大 | 常规舍入持续正偏 | RND=0 改用收敛舍入 |
| 积分器输出莫名跳到反相满幅 | bit 39 灾难性溢出,符号位被破坏 | 开 SATB,必要时用 COVTE 陷阱定位 |
| 某些乘法结果整体偏大或偏小 | IF 被临时置 1 后未恢复 | IF 只在初始化设置,运行期不改 CORCON |
| 回写值总在边界附近抖动 | SATDW 未开,超范围值直接截断 | SATDW=1,写入前钳到 0x7FFF / 0x8000 |
一句话总结:dsPIC33F 的定点电流环不是"把 float 换成 Q15"这么简单,而是把 17×17 乘法器、40 位累加器、9.31 与 1.31 两套饱和边界、收敛舍入和 1.15 回写串成一条链路。链路里每一段的格式对上了,精度才真正落在波形上。
还想接着看:dsPIC30F2010 的 MCPWM 死区与 ADC 同步采样、机器人关节电流环的 LMS/NLMS 去噪与 Q15 定点、STM32F103 双 ADC 同步采样与 DMA。
本文基于 Microchip《dsPIC33FJ16GP101/102 和 dsPIC33FJ16MC101/102 数据手册》(DS70652B_CN)技术资料整理优化。
沧州艾诺威电子 — 国家高新技术企业,20+项国家专利
嵌入式系统开发 · 物联网方案 · AI智能硬件 · 一站式交付
电话:13930711029 | 邮箱:tech@czinv.com | 24小时内响应