【第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更快休眠
相关推荐
老孟说禅10 小时前
硬件测试 - 测试用例设计:测试需求分析、测试点提取、测试用例编写方法
单片机·嵌入式硬件·智能硬件
SL_staff10 小时前
设备越多系统越崩?根源在三层抽象失效
spring boot·物联网·github
C++ 老炮儿的技术栈10 小时前
static的作用
c语言·c++·人工智能·单片机·c
LiuYanG10 小时前
设备断电了平台还显示在线?MQTT 遗嘱消息(LWT)与离线判定踩坑实录
物联网
哎呦,帅小伙哦10 小时前
进程监控工具——htop
linux·运维·服务器
逐光者93310 小时前
STM32通信协议——I2C
stm32·单片机·嵌入式硬件
千秋岁rr10 小时前
15.PWR电源控制
单片机·嵌入式硬件
额额额对了10 小时前
嵌入式 Linux 启动程序文件
开发语言·嵌入式硬件·php
夏虫不可与之言冰11 小时前
Windows WSL2 (Ubuntu) 搭建 ESP‑IDF 6.1 开发环境全过程演示
linux·windows·ubuntu·esp32·乐鑫科技·乐鑫eim·乐鑫代理商
甜甜的大香瓜11 小时前
【MT32F006】MT32F006之PWM控制背光灯(白光)
单片机