【第05期】数据的微观世界 (五) —— 浮点数 vs 定点数:MCU的数学课

在PC软件开发中,我们习惯了随手写下 double a = 3.14;。但在嵌入式领域,尤其是资源受限的MCU(如Cortex-M0/M3或8位机)上,随意使用浮点数可能是性能的"隐形杀手",也是通信故障的"幕后黑手"。

这一期,我们深入MCU的数学世界,解开浮点数从存储 、运算 到传输的所有秘密。


1. 浮点数的"富贵病" (性能代价)

我们先看一行平平无奇的代码:

// 假设在一个没有FPU的单片机上(如STM32F103)

float voltage = adc_value * 3.3f / 4095.0f;

你以为CPU只是做了两次简单的乘除法?

实际上,编译器在背后为你引入了数百行汇编代码。

1.1 软浮点 (Soft-Float) 的代价

大多数低端MCU没有硬件浮点运算单元 (FPU)。

当你写下 a * b(如果是float)时,编译器会调用一个巨大的库函数(例如 __aeabi_fmul),用一堆整数移位、加减法逻辑来模拟浮点运算。

  • 执行速度暴跌:

    • 整数乘法:约 1~3 个时钟周期。

    • 软浮点乘法:约 50~100 个时钟周期!

    • 软浮点除法:高达数百个周期。

      如果这段代码在中断服务函数 (ISR) 或 高频控制回路(如电机FOC)里,系统极易过载。

1.2 即使有FPU也有坑

即使你用的是带FPU的芯片(如STM32F4/H7),也不要掉以轻心:

  1. Double的陷阱 :大多数MCU的FPU只支持单精度 (float) 。如果你写了 3.14(C语言默认是double),FPU会罢工,转而调用慢速的软件库。修正:必须显式加 f 后缀 -> 3.14f。

  2. 上下文切换:在RTOS中,任务使用FPU会导致上下文切换时需要保存额外的FPU寄存器,增加了中断延迟和内存开销。


2. 深入解剖:浮点数的"肉体" (IEEE 754 存储详解)

在内存里,int32_t a = 12 存的是 0x0000000C,直观易懂。

但是,float f = 12.5 在内存里存的是 0x41480000。

这俩长得完全没关系!为什么?

因为MCU遵循 IEEE 754 单精度浮点数标准,它把 32个 Bit 切成了三块:

  1. 符号位 (Sign, 1 bit): 最高位 (Bit 31)。0代表正,1代表负。

  2. 指数位 (Exponent, 8 bits): Bit 23-30。用于存储科学计数法中的 2^N。

    • 注意:采用"移码" (Bias) 设计,真实指数 = 存储值 - 127。
  3. 尾数位 (Mantissa, 23 bits): Bit 0-22。存储小数点后的有效数字。

    • 注意:IEEE 754 省略了整数部分的 1,以节省一位空间。

内存透视实战:

当你在调试器 (Memory View) 里看到 00 00 48 41 (小端模式) 时,如何确认它是 12.5?

  1. 重组 :0x41480000 -> 二进制 0100 0001 0100 1000 ...

  2. 符号:0 -> 正数。

  3. 指数 :1000 0001 (129)。真实指数 129 - 127 = 2。即 2^2 = 4。

  4. 尾数 :1.100... (二进制)。1 + 0.5 = 1.5。

  5. 结果:4 x 1.5 = 6 ... 等等,这里只是简算,实际上尾数解析是 1 + 2^{-1} + 2^{-4} 等的组合。

    工程结论:你只需要知道,浮点数在内存里是一堆被编码过的位,不能直接当整数解读。


3. 工程实战:如何传输与存储浮点数?

这是嵌入式中最常见的场景:你采集了一个电压值 3.14159,现在要通过串口发给上位机,或者存进 Flash 里。

方法 A:ASCII 字符串法 (笨办法)

char buf16;

sprintf(buf, "%.5f", val); // 转成 "3.14159"

HAL_UART_Transmit(&huart1, buf, strlen(buf), 100);

  • 缺点:慢(sprintf是耗时大户)、占带宽(发了7个字节)、精度丢失(文本转回数字会有误差)。

方法 B:联合体法 (Union) ------ 优雅且高效 (推荐)

利用 union 共用内存的特性,直接把这 4个 Byte 拿出来。

typedef union {

float f_val;

uint8_t bytes4;

} FloatConverter_t;

void send_float(float val) {

FloatConverter_t u;

u.f_val = val;

// 直接发送原始字节,速度最快,精度无损

HAL_UART_Transmit(&huart1, u.bytes, 4, 100);

}

方法 C:指针强转法

float val = 3.14f;

uint8_t *p = (uint8_t*)&val; // 欺骗编译器,把float地址当char地址用

HAL_UART_Transmit(&huart1, p, 4, 100);

关键警告:大小端问题

只要传输原始字节,必须考虑大小端!

  • MCU端 (STM32) :小端模式,内存里是 A B C D (低字节在前)。

  • 接收端:

    • 如果是 Java 上位机 / 网络传输:通常是大端,期待 D C B A。

    • 如果是 x86 PC (C++):通常也是小端。

  • 对策:制定协议时必须明确规定(例如:使用网络字节序/大端)。如果不匹配,发送前要先反转数组顺序。


4. 浮点数的精度噩梦

除了慢和存储复杂,浮点数还有一个致命弱点:不精确。

经典反直觉案例

float a = 1.1f;

if (a == 1.1f) {

// 这里很可能会判断为 false!

}

为什么?因为 1.1 在二进制里是一个无限循环小数(就像十进制里的 1/3),计算机存不下,只能截断。

工程铁律 : 永远不要用 == 去比较两个浮点数! 必须使用"Epsilon"范围比较:

if (fabs(a - 1.1f) < 0.00001f) { ... }

5. 穷人的法拉利:定点数 (Fixed-Point)

如果想要速度快(整数运算),又想要小数(精度),怎么办?

答案是:定点数。本质就是**"缩放"**。

5.1 十进制缩放 (简单易懂)

  • 浮点思维 :电压 1.23 V。

  • 定点思维 :电压 1230 mV (放大1000倍)。存成 int。

5.2 二进制缩放 (Q格式 - 高效之王)

计算机做乘除1000比较慢,但移位 极快。我们把缩放因子设为 2^N。这就是 Q格式。

假设使用 Q15 (缩放 2^15 = 32768):

  • 1.5 (浮点) -> 1.5 * 32768 = 49152 (定点整数)。

运算规则:

  1. 加减法:直接加减。

  2. 乘法:结果会"变大",需要右移归一化。

#define Q_SHIFT 10 // 放大 2^10 = 1024 倍

#define TO_FIX(x) ((int32_t)((x) * (1 << Q_SHIFT)))

int32_t a = TO_FIX(1.5); // 1536

int32_t b = TO_FIX(2.0); // 2048

// 乘法:先乘,再右移

// 注意:乘法结果可能超过int32,必须强转int64暂存!

int32_t prod = (int32_t)(((int64_t)a * b) >> Q_SHIFT);

// 结果 3072 -> 代表 3.0

6. 总结:什么时候该用什么?

场景 推荐方案 理由
人机交互 (UI) 浮点数 屏幕刷新慢,开发效率优先,代码可读性好。
复杂算法 (FFT/滤波) 浮点数 (FPU) 现代MCU带FPU,不用白不用。
高频控制 (电机/电源) 定点数 (Q格式) 必须在几微秒内算完,且无FPU时唯一的选择。
数据传输 (串口/CAN) 原始字节 (Union) 效率最高,但需注意大小端。
低功耗设备 定点数 FPU耗电大,定点运算让CPU更快休眠
相关推荐
华清远见IT开放实验室10 分钟前
实验室建设案例 | 安徽三联学院物联网实验室
stm32·单片机·嵌入式硬件·物联网·华清远见
一只鹿鹿鹿1 小时前
智算算力、AI-芯片与产品解析(PPT文件)
大数据·运维·网络·人工智能·物联网·1024程序员节
Zw-awa1 小时前
串口为什么会丢字节:从轮询到中断和环形缓冲区
stm32
沫璃染墨1 小时前
从零入门计算机网络系列(十一):HTTP协议进阶——从重定向原理到GET/POST与Fiddler抓包
linux·服务器·开发语言·网络·c++·tcp/ip·html
深圳光特通信豆子2 小时前
TTL 光模块在工业控制与串口通信中的应用指南(10M BIDI 单模系列)
单片机·嵌入式硬件
桀人2 小时前
SPI概念引入
stm32·单片机·嵌入式硬件
离凌寒3 小时前
一、基于stm32+freertos下LD2410D和R60ADB1两款雷达的使用效果
stm32·单片机·嵌入式硬件
AI_Lab013 小时前
宝塔用了两年,有没有必要换成轻量方案?
linux·运维·服务器
by209993 小时前
Linux GDB 调试与 CGDB 使用
linux·运维·服务器·经验分享
国科安芯3 小时前
商业光学遥感载荷焦平面供电链的低噪声稳压器研究综述——以国科安芯ASP7A84AS型3A低压差线性稳压器为器件级参照
大数据·网络·嵌入式硬件·平面·卫星·商业航天·抗辐射