dsPIC33FJ16MC102 电流环定点化实战:Q15 精度、40 位累加器与饱和回写七步拆解

电流环从浮点改 Q15,波形却出现削顶和周期性毛刺,问题通常不在算法。结论先给:dsPIC33F 的 DSP 引擎把乘法结果堆到 40 位累加器里,Q15 输入只会得到 1.31 中间值,饱和模式、舍入方式和回写目标三处没配齐,定点化就会在波形上留下痕迹。

2026-10-31
dsPIC33FJ16MC102Q15 定点40 位累加器DSP 引擎电流环饱和与舍入

一台 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 的配置。

一、先看清数据通路:24 位指令、17×17 乘法器和两个 40 位累加器

dsPIC33F 的内核是 16 位数据的改进型哈佛结构,指令字 24 位,程序计数器 23 位,最大可寻址 4M×24 位程序空间。做电流环真正起作用的是 DSP 引擎那一段:一个 17 位×17 位单周期乘法器、一个 40 位加法器/减法器、两个 40 位饱和累加器 ACCA 与 ACCB,以及一个 40 位桶形移位器。乘法器输出 33 位,再符号扩展成 40 位送进累加器,多出来的 8 位(bit 32~bit 39)就是常说的警戒位。

内核资源手册规格对电流环的意义
指令字与 PC24 位指令字,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 指令可以"取两个数、乘、累加"一次完成的原因,也是把系数表和采样值分开放时要注意的地方。

二、Q15 与 1.31:小数点在哪,精度和溢出点就在哪

小数模式下,数据的最高位是符号位,小数点暗含在符号位之后,也就是 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 ~ 327671ADC 原始码、计数器
Q15 小数-1.0 ~ 0.9999694823.01518×10⁻⁵采样值、误差、输出占空比
1.31 中间值由 40 位累加器承载,取高 32 位有效4.65661×10⁻¹⁰PI 运算的乘加中间结果
1.15 回写值-1.0 ~ 0.9999694823.01518×10⁻⁵累加器回写到 RAM 或 W13 的结果

乘法器默认工作在小数模式,由 CORCON 的 IF 位选择:IF=0 为小数乘法,IF=1 为整数乘法。电流环里所有系数都按 Q15 归一化,IF 就必须保持 0;如果某次为了处理计数器临时把它置 1 又忘了改回来,后面的乘法会整体偏移,表现为增益忽然变大或变小。

三、CORCON:七个控制位要在初始化时一次配齐

DSP 引擎的行为几乎都由内核控制寄存器 CORCON 决定。它不像外设寄存器那样可以随时改,尤其 SATA、SATB、ACCSAT 这几位,一旦在控制周期中间变化,同一个 PI 环路的前后两个周期就会用不同的饱和规则,波形上会出现难以复现的跳变。

位名称电流环建议值原因
bit 12US:DSP 乘法符号模式0(有符号)电流与误差都是带符号量;无符号模式只适合功率、幅值类计算
bit 10:8DL<2:0>:DO 循环嵌套层级由循环结构决定,只读观察用来确认嵌套层数,不要手工写
bit 7 / bit 6SATA / SATB:ACCA、ACCB 饱和使能1 / 1(使能)积分项过冲时自动限幅,避免 40 位溢出破坏符号位
bit 5SATDW:数据空间写饱和1(使能)回写到 16 位时把超范围值钳到 1.15 边界
bit 4ACCSAT:饱和模式0(1.31 正常饱和)1 是 9.31 超饱和,只在需要保留大动态范围时用
bit 1RND:舍入模式0(无偏/收敛舍入)长时间积分下,有偏舍入会持续累积正向误差
bit 0IF:整数/小数乘法0(小数)全部系数按 Q15 归一化,必须保持小数模式

同一张表里还有 PSV(程序空间可视性)与 IPL3(中断优先级高位),它们和运算无关,但初始化时经常被整字写入顺带改掉。稳妥做法是读—改—写,只动需要动的位,不要直接给 CORCON 赋一个常数。

四、饱和与溢出:OA/OB 可恢复,SA/SB 是"粘住"的

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. 先在整数域把量纲定死:采样值、给定、输出全部映射到 Q15,中间结果只在 40 位累加器里保持 1.31。
  2. 初始化 CORCON 用读—改—写,只置 SATA、SATB、SATDW,清 ACCSAT 与 RND,保持 IF=0、US=0。
  3. 比例项与积分项分用 ACCA、ACCB,避免回写互相踩踏。
  4. 每个控制周期开头 CLR 比例累加器,积分累加器保持跨周期累加。
  5. 输出统一用 SAC.R 或 MAC 自带回写,回写格式固定 1.15,不要手工右移。
  6. 周期末读 SAB 做饱和计数,超过设定比例就报警,而不是等波形削顶。
  7. 把定标表写进注释:每个变量标 Q 格式与取值范围,换人维护也不会把 Q15 和 1.15 混用。

七、排查对照:现象、根因与改法

现象常见根因改法
轻载时输出有台阶状毛刺过早把 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小时内响应