当前位置:首页 > 嵌入式 > 嵌入式分享
[导读]Cortex-M0/M3(无FPU)上做浮点运算,编译器会调用软浮点库,一条float乘法可能消耗几十到上百个CPU周期。对于传感器校准、PID控制、音频处理等场景,浮点运算占比过高会导致系统响应迟缓。定点数(fixed-point)用整数模拟小数,完全绕过浮点库,速度提升数倍,且精度可控。本文以温度PID控制器为例,展示如何将浮点运算改造成高效的Q15定点数。


Cortex-M0/M3(无FPU)上做浮点运算,编译器会调用软浮点库,一条float乘法可能消耗几十到上百个CPU周期。对于传感器校准、PID控制、音频处理等场景,浮点运算占比过高会导致系统响应迟缓。定点数(fixed-point)用整数模拟小数,完全绕过浮点库,速度提升数倍,且精度可控。本文以温度PID控制器为例,展示如何将浮点运算改造成高效的Q15定点数。

一、定点数原理:用整数表示小数

定点数的核心是约定一个“小数点”的位置。例如Q15格式:16位有符号整数,低15位表示小数部分,最高位为符号位,取值范围-1到0.999969。数值 = 整数编码 / 32768。加法直接相加,乘法后需要右移15位恢复比例。

// Q15定点数定义

typedef int16_t q15_t;


// 浮点转定点

q15_t float_to_q15(float f) {

   if (f > 0.999969f) f = 0.999969f;

   if (f < -1.0f) f = -1.0f;

   return (q15_t)(f * 32768.0f);

}


// 定点转浮点(调试用,实际运行不需要)

float q15_to_float(q15_t q) {

   return (float)q / 32768.0f;

}


// Q15乘法:a*b,结果右移15位

q15_t q15_mul(q15_t a, q15_t b) {

   return (q15_t)(((int32_t)a * (int32_t)b) >> 15);

}

注意:乘法前需要将操作数扩展到32位,否则16位相乘会溢出。

二、浮点PID改造为定点PID

一个标准的浮点PID增量式算法:

float kp = 0.5f, ki = 0.1f, kd = 0.05f;

float err, last_err, integral, derivative, output;


void pid_update(float setpoint, float feedback) {

   err = setpoint - feedback;

   integral += err;

   derivative = err - last_err;

   output = kp*err + ki*integral + kd*derivative;

   last_err = err;

}

改造为Q15定点数,需要将系数和变量都转为Q15格式:

#define Q15_0_5   16384  // 0.5 * 32768

#define Q15_0_1   3277   // 0.1 * 32768

#define Q15_0_05  1638   // 0.05 * 32768


q15_t q_err, q_last_err, q_integral, q_derivative, q_output;

q15_t q_kp = Q15_0_5;

q15_t q_ki = Q15_0_1;

q15_t q_kd = Q15_0_05;


void pid_update_q15(q15_t q_setpoint, q15_t q_feedback) {

   q_err = q_setpoint - q_feedback;

   q_integral += q_err;

   q_derivative = q_err - q_last_err;

   // output = kp*err + ki*integral + kd*derivative

   q_output = q15_mul(q_kp, q_err) +

              q15_mul(q_ki, q_integral) +

              q15_mul(q_kd, q_derivative);

   q_last_err = q_err;

}

注意:积分项q_integral可能会溢出,需要根据实际系统选择合适的Q格式或增加防饱和处理。

三、性能实测对比

在STM32G030(Cortex-M0+,64MHz)上测试1000次PID运算:

方案 耗时(us) 代码大小(Flash)

浮点(软浮点) 1820 4.2KB

Q15定点 240 0.6KB

定点数速度快7.5倍,Flash占用减少85%。对于需要高频率执行的PID环路,这个差距决定了系统能否跑起来。

四、精度与范围权衡

Q15的精度约为0.00003(1/32768),对于大多数传感器反馈(12位ADC)绰绰有余。如果数值范围超过[-1,1),可以选用Q31(32位,精度更高)或自定义Q格式。例如Q24表示范围[-128,128),精度1/16777216。选择原则:先确定最大值范围,再确定所需精度,算出小数位数。

// Q24格式示例:32位,8位整数+24位小数

typedef int32_t q24_t;

#define Q24_SCALE 16777216.0f


q24_t float_to_q24(float f) {

   return (q24_t)(f * Q24_SCALE);

}


q24_t q24_mul(q24_t a, q24_t b) {

   return (q24_t)(((int64_t)a * (int64_t)b) >> 24);

}

五、三个翻车高发点

乘法溢出:Q15乘法必须先用32位暂存再移位,否则16位相乘直接溢出。使用int32_t中间变量。

加法饱和:多个Q15数相加可能超过32767,导致符号翻转。建议在累加前缩放到Q14或使用32位累加器。

常数转换错误:浮点0.1转换为Q15是3277,但0.1 * 32768=3276.8,四舍五入后误差0.006%。如果系数精度要求高,用roundf或手动调整。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭