4、定点数基础:Q格式表示、定点加减乘除、溢出与饱和处理、舍入模式(截断、四舍五入、收敛舍入)
在CMSIS-DSP滤波器链路设计中,浮点运算虽然直观,但在嵌入式MCU(特别是无FPU的Cortex-M0/M3内核)上,浮点运算会带来显著的周期开销与代码体积膨胀。定点数(Fixed-Point)通过将实数映射到整数域,利用硬件整数运算单元完成信号处理,是工业级音频、电机控制、传感器融合等场景的基石。本章将系统讲解Q格式的数学本质、定点四则运算的位宽管理、溢出与饱和策略,以及三种舍入模式的工程取舍,为后续FIR/IIR滤波器定点化实现打下坚实基础。
4.1 Q格式的数学本质与表示方法
Q格式(Qm.n)是一种将实数映射到整数域的定点表示法,其中m表示整数部分位数(含符号位),n表示小数部分位数。对于32位系统,常见的Q15(即Q1.15)表示16位有符号定点数,Q31(Q1.31)表示32位定点数。其核心思想是:实数x = 整数码值 × 2^(-n)。例如,Q15格式下,整数码值32767对应实数32767 × 2^(-15) = 0.999969,而-32768对应-1.0。
选择Q格式时需要权衡动态范围与精度。动态范围由整数位m决定,精度由小数位n决定。对于音频信号(通常幅度在±1.0内),Q15足够;对于中间累加结果(如FIR滤波器卷积和),可能需要Q31甚至Q63(CMSIS-DSP内部累加器)。工程上,Q格式的选择应遵循“先定范围,再定精度”的原则:先估算信号最大幅度,确定m;再根据所需信噪比确定n。
| Q格式 | 位宽 | 数值范围 | 精度 | 典型应用 |
|---|---|---|---|---|
| Q1.15 | 16位 | [-1.0, 0.99997] | 3.05e-5 | 音频采样、FIR系数 |
| Q1.31 | 32位 | [-1.0, 0.9999999995] | 4.66e-10 | 高精度滤波、IIR状态 |
| Q8.23 | 32位 | [-128.0, 127.9999999] | 1.19e-7 | 传感器数据融合 |
| Q16.15 | 32位 | [-32768, 32767.99997] | 3.05e-5 | 电机控制、功率转换 |
4.2 定点加减乘除运算规则
加减法:两个定点数相加/减,必须保证Q格式一致(即小数点对齐)。若Q格式不同,需先通过左移/右移将较小精度数对齐到较大精度。例如,Q15数a与Q31数b相加,需先将a左移16位转换为Q31,再执行加法。CMSIS-DSP中,arm_add_q15要求两个输入均为Q15,输出也为Q15,内部自动处理饱和。
乘法:定点乘法的位宽会翻倍。Qm1.n1 × Qm2.n2 = Q(m1+m2).(n1+n2)。例如,两个Q15相乘得到Q30(实际为Q2.30,因为两个符号位相乘会多出1位整数位)。工程上通常需要将结果右移n位以回到目标Q格式。CMSIS-DSP提供arm_mult_q15,内部将两个Q15相乘后左移1位再截断为Q15,避免溢出。
除法:定点除法需要先将被除数左移n位(n为目标小数位数),再执行整数除法。例如,Q15数a除以Q15数b,结果为Q15,需执行(int16_t)(((int32_t)a << 15) / b)。注意CMSIS-DSP没有直接提供Q15除法函数,通常用arm_div_q15(基于牛顿-拉夫逊迭代)或自行实现。
// 示例:Q15乘法(手工实现)
int16_t q15_mult(int16_t a, int16_t b) {
int32_t temp = (int32_t)a * b; // 结果为Q30
temp >>= 15; // 右移15位回到Q15
// 饱和处理
if (temp > 32767) temp = 32767;
if (temp < -32768) temp = -32768;
return (int16_t)temp;
}
// 示例:Q15除法(手工实现)
int16_t q15_div(int16_t a, int16_t b) {
if (b == 0) return 0; // 除零保护
int32_t temp = ((int32_t)a << 15) / b; // 先左移15位
// 饱和处理
if (temp > 32767) temp = 32767;
if (temp < -32768) temp = -32768;
return (int16_t)temp;
}
4.3 溢出检测与饱和处理策略
定点运算最危险的场景是溢出——当运算结果超出目标位宽可表示范围时,数值会回绕(wrap-around),导致严重失真。例如,Q15数32767加1会变成-32768,这在音频中表现为爆音。饱和处理(Saturation)将结果钳位到最大或最小值,避免回绕。
CMSIS-DSP中,几乎所有Q15/Q31运算函数都内置饱和逻辑。例如arm_add_q15内部使用SSAT指令(Cortex-M3/M4)或软件比较实现饱和。对于自定义运算,需手动添加饱和判断:
// 饱和加法示例
int16_t sat_add_q15(int16_t a, int16_t b) {
int32_t sum = (int32_t)a + b;
if (sum > 32767) return 32767;
if (sum < -32768) return -32768;
return (int16_t)sum;
}
溢出检测有两种常用方法:位扩展检测法(将操作数扩展到更宽位宽后运算,检查结果是否超出目标范围)和符号位检测法(比较运算前后符号位是否变化)。在ARM Cortex-M系列中,可利用QADD/QSUB指令(如果编译器支持内联汇编)或CMSIS提供的__QADD8等内置函数。
arm_biquad_cascade_df1_q31(内部使用Q63累加器)。
4.4 舍入模式:截断、四舍五入与收敛舍入
当定点运算需要从高精度降低到低精度时(如Q31转Q15),必须丢弃低位比特。不同的舍入模式会影响信号的信噪比和直流偏移。
- 截断(Truncation):直接丢弃低位,等效于向下取整(对于正数)或向上取整(对于负数)。实现最简单(直接右移),但会引入直流偏移(bias),平均误差为-0.5 LSB。
- 四舍五入(Round-to-Nearest):在截断前加上0.5 LSB(即1 << (n-1)),然后右移。实现为
(x + (1 << (n-1))) >> n。消除了直流偏移,但最大值可能溢出(如32767四舍五入后变为32768),需要额外饱和。 - 收敛舍入(Round-to-Even):当结果恰好为0.5 LSB时,向偶数舍入。避免统计意义上的舍入偏差累积。实现较复杂,通常用于高精度科学计算或需要严格无偏的场景。
| 舍入模式 | 实现方法 | 直流偏移 | 最大误差 | 适用场景 |
|---|---|---|---|---|
| 截断 | x >> n | -0.5 LSB | 1 LSB | 快速处理、误差不敏感 |
| 四舍五入 | (x + (1<<(n-1))) >> n | 0 | 0.5 LSB | 音频、通用信号处理 |
| 收敛舍入 | 判断LSB后决定进位 | 0 | 0.5 LSB | 高精度测量、控制环路 |
// 三种舍入模式实现(Q31转Q15)
int16_t truncate_q31_to_q15(int32_t x) {
return (int16_t)(x >> 16);
}
int16_t round_q31_to_q15(int32_t x) {
int32_t temp = x + (1 << 15); // 加0.5 LSB
if (temp > 0x7FFF0000) return 32767; // 饱和
return (int16_t)(temp >> 16);
}
int16_t round_even_q31_to_q15(int32_t x) {
int32_t lsb = (x >> 16) & 1;
int32_t bias = (1 << 15) - 1 + lsb; // 收敛舍入偏置
return (int16_t)((x + bias) >> 16);
}
4.5 实战案例:音频FIR滤波器定点化链路
假设我们需要在Cortex-M4(无FPU)上实现一个128阶FIR低通滤波器,输入为16位ADC采样(Q15格式),输出驱动16位DAC。浮点原型滤波器系数已设计完成,现需定点化。
初始参数:采样率48kHz,截止频率8kHz,通带纹波0.1dB,阻带衰减60dB。浮点系数范围[-0.02, 0.2],系数个数128。
定点化步骤:
- 系数Q格式选择:浮点系数最大绝对值0.2,远小于1.0,因此Q15足够。将浮点系数乘以32768后四舍五入取整。
- 累加器位宽:128阶乘加,每个乘积为Q15×Q15=Q30,128个累加结果最大可能达到128×0.2×1.0≈25.6,超出Q15范围。因此CMSIS-DSP内部使用Q63累加器(64位),确保不溢出。
- 输出处理:累加结果右移15位回到Q15,并做饱和处理。CMSIS-DSP的
arm_fir_q15自动完成此操作。 - 舍入模式:在系数转换时使用四舍五入,避免系数误差引入直流偏移。
// 使用CMSIS-DSP实现
#define NUM_TAPS 128
q15_t fir_coeffs[NUM_TAPS];
q15_t fir_state[NUM_TAPS + 16]; // 状态缓冲区
arm_fir_instance_q15 S;
// 初始化:浮点系数转Q15
for (int i = 0; i < NUM_TAPS; i++) {
fir_coeffs[i] = (q15_t)(float_coeffs[i] * 32768.0f + 0.5f);
}
arm_fir_init_q15(&S, NUM_TAPS, fir_coeffs, fir_state, 128);
// 每个采样点处理
q15_t input = (q15_t)(adc_value); // ADC 16位直接作为Q15
q15_t output;
arm_fir_q15(&S, &input, &output, 1);
dac_write(output);
结果对比:定点实现与浮点原型相比,通带纹波增加约0.05dB,阻带衰减降至58dB,满足系统指标(≥55dB)。处理周期从浮点的约1200周期/采样降至定点约180周期/采样,性能提升6.7倍。
4.6 常见误区与设计复盘
误区1:所有系数都用Q15。如果滤波器系数动态范围很大(如IIR反馈系数可能接近1.0),Q15精度不足,应使用Q31。判断标准:系数最小非零值对应的量化误差是否影响滤波器频率响应。
误区2:忽略中间节点溢出。在级联IIR实现中,每个二阶节的输出可能超过输入范围。必须逐级检查状态变量的最大值,必要时在级间插入缩放(scale)因子。
误区3:舍入模式一刀切。在反馈环路(如IIR)中,截断会导致极限环振荡(limit cycle),产生可闻噪声。此时应使用四舍五入或收敛舍入。而在前馈路径(如FIR)中,截断的直流偏移可通过后续高通滤波消除。
误区4:饱和处理只加在最终输出。在乘法链中,中间结果也可能溢出。例如Q15乘法后立即右移,但右移前结果可能超过int16范围。CMSIS-DSP的arm_mult_q15内部先左移1位再饱和,正是为了避免此问题。
复盘建议:在定点化调试时,建议先用MATLAB/Simulink的Fixed-Point Tool或Python的numpy.fixed_point进行位真仿真,对比定点与浮点输出差异。重点关注:①直流偏移(截断导致);②大信号下的饱和失真;③小信号下的量化噪声底。通过仿真提前发现位宽不足或舍入策略不当的问题,避免在硬件上反复调试。