本文介绍嵌入式中的浮点数与青稞系列芯片的FPU硬件浮点单元,并说明如何使用MRS2配置FPU提升浮点性能以及串口打印浮点数。
一、单精度浮点数与双精度浮点数
嵌入式处理器使用的浮点数遵循 IEEE 754 标准。这套标准规定了一种将实数编码为有限位二进制的方式,其核心思想是把一个数分解为三个独立的字段:符号位(Sign)、指数位(Exponent)和尾数位(Mantissa)。这三个字段在内存中是紧密排列的。
值 = (−1)^S × 2^(E − bias) × (1 + Mantissa)
浮点数一般根据其占用的位宽度分为单精度浮点数(float)与双精度浮点数(double)。其在内存中的布局如下所示。
单精度float占用 4 字节,能够提供约 7 位有效十进制数的精度,数值范围覆盖 ±3.4×10³⁸。双精度double则占用 8 字节,精度提升到约 15~16 位,范围扩展到 ±1.7×10³⁰⁸。从数学上看,double显然更精确,但在嵌入式领域,精度优势无法弥补它花费的巨大开销,优先使用float才是更好的选择。
二、浮点数常量
嵌入式开发中浮点数有一个非常容易被忽视的细节。
C 语言里所有不带后缀的浮点字面量,比如0.01、3.14,其类型默认是double而不是float 。
例如一个简单的浮点计算并赋值的代码:
float x = 3.14 * 2.0;
3.14和2.0都没有f后缀,所以编译器会把这两个常量都当作double来处理。由于赋值给float变量,因此在以double运算后编译器会进行一次截断,将64位运算结果截断为32位的float。
还有一种混合的情况:
float x = 3.14f * 2.0;
3.14添加了f后缀,显式定义为float常量,2.0未加f后缀,依然默认为double常量。C 语言规定,当 float 和 double 混合运算时,float 那一方会被隐式提升为 double,然后以 double 精度完成乘法,最后再截断回 float。也就是说,只要包含一个未加后缀的操作数,表达式就会以double进行运算。
建议给所有浮点数添加后缀f显式声明其为float常量。
float x = 3.14f * 2.0f;
此时由于全部操作数都为float,表达式会以float进行运算。在使用软件浮点库中的函数进行运算时似乎与使用double没有太大差别,但当芯片内置支持单精度运算的硬件浮点单元(FPU)时,float运算会有巨大的提升。
三、FPU 硬件浮点单元
FPU(Floating Point Unit,浮点运算单元)是处理器中专门用于执行浮点数运算的硬件模块。利用软件浮点库进行浮点运算时,调用相关的浮点运算函数进行模拟,一般需要十几到几十条指令才能完成。而在FPU的帮助下,加法、乘法等基础浮点运算可以在单个时钟周期内完成,极大提高浮点运算效率。
WCH部分芯片支持FPU,例如青稞V3F\V5F内核芯片(CH32H416、CH32H415、CH32H417)和青稞V4F内核芯片(CH32V303、CH32V305、CH32V307、CH32V317)。以上型号支持RISC-V F指令集,拥有F0-F31共32个浮点寄存器,用于存储单精度浮点指令所需数据。
使用MRS2开发时,通过几步配置即可使用芯片的FPU提高浮点运算性能。
- 勾选浮点扩展与ABI
打开工程配置Build Settings->Tool Settings->Target Processor页面:
Floating point选择Single precision extension**(R****VF)**单精度浮点扩展;
Floating point ABI选择**Single precision(f)**单精度浮点。
2. 勾选浮点优化选项
打开工程配置Build Settings->Tool Settings->****Optimization页面
勾选Single precision constants**(-fsingle-precision-constant****)**选项。
开启这个选项之后,会将源码里所有不带后缀的浮点常量强制当作float来处理,效果等同于在每一个浮点常量后手动加了f。可确保浮点运算为单精度,充分利用FPU硬件浮点单元提高浮点运算性能。
四、浮点数打印
嵌入式开发环境一般默认使用裁剪过的C库(如newlib-nano),出于代码体积考虑,printf的实现删去了浮点格式化的支持。如果直接调用printf("%.2f", val) ,串口浮点数输出为空。
若需通过串口打印浮点数,需要链接相应的浮点打印库。
MRS2中通过勾选链接选项,选择合适的浮点打印库,即可在串口打印浮点数。
打开工程配置Build Settings->Tool Settings->****GNU RISC-V Cross Linker->Miscellaneous页面。
勾选Use float with nano printf**(-u _printf_float)**会使用标准库newlib-nano中的浮点打印函数,支持更全面的输出格式,相应的链接库体积会稍大;
勾选Use **wchprint****float(-lprintfloat)**会使用MRS提供的精简浮点打印函数,仅保留常用的输出格式,能够有效减小代码体积。
同时,**Use wchprintfloat(-lprintfloat)**还可支持64位数据打印。
根据自身需求选择合适的选项即可。推荐使用Use wchprintfloat选项,基本可满足常见浮点打印格式需求,且代码体积优势较为明显。