第4章:量产环境的第一道坎:计算资源受限(MCU vs PC),RAM/Flash/算力瓶颈

说实话,很多算法工程师第一次接触量产项目时,都会栽在这个坑里。

你在PC上跑得好好的SOH算法,精度0.5%,速度飞快。然后你把它移植到MCU上——嗯,直接死机了。或者跑一次要3分钟,电池都换了两轮了还没出结果。

这不是你的算法不好,是环境变了。PC上的资源是「无限」的,MCU上的资源是「有限」的。这个差距,比你想象的大得多。

4.1 PC与MCU的「资源鸿沟」

先看一组数据,你就明白了。

资源项 PC(典型) MCU(典型) 差距倍数
RAM 8~32 GB 16~512 KB 约10万倍
Flash 256 GB~2 TB 64 KB~2 MB 约10万倍
主频 2~5 GHz 16~200 MHz 约100倍
浮点运算 硬件FPU+SIMD 软件模拟或单精度FPU 数量级差异

你看,RAM差了10万倍。你在PC上随手定义一个float数组[10000],在MCU上可能直接就把整个内存撑爆了。

我个人习惯,在开始移植前,先做一件事:算账。把算法需要的RAM、Flash、算力全部列出来,跟MCU的规格做对比。这一步省不了,省了后面全是坑。

4.2 RAM瓶颈:你的数据到底占了多少地方?

很多SOH算法需要存储历史数据。比如基于容量衰减的模型,需要存过去100次充放电循环的容量值。

在PC上,你可能会这么写:

float capacity_history[100];  // 100个float,400字节
// 嗯,400字节,毛毛雨

但在MCU上,400字节可能占了总RAM的1%~5%。如果你有10个这样的数组,直接爆了。

关键问题:MCU的RAM通常只有几十KB,而且还要分给系统堆栈、中断向量、通信缓冲区。留给算法的,可能只有几KB。

我在项目中遇到过最夸张的一次:一个同事把PC上的SOH算法直接搬过来,里面有个查表用的数组,大小是64KB。而那颗MCU的总RAM才32KB。结果呢?编译通过了,但一运行就hardfault。查了两天才发现是数组越界了——其实不是越界,是根本没地方放。

怎么办?

  • 压缩数据精度:float转int16,甚至int8。容量值用0.01Ah为单位,存成uint16,精度够用,内存减半。
  • 分段加载:历史数据不需要全量在内存里。需要哪一段,从Flash里读哪一段。
  • 滑动窗口:只保留最近N个点,老数据丢掉。很多SOH算法其实只需要最近的数据。

我的经验:在MCU上,能用uint8就别用uint16,能用uint16就别用float。每省一个字节,都是胜利。

4.3 Flash瓶颈:代码和常量表谁更占地方?

Flash主要放两样东西:代码和常量数据。

代码方面,SOH算法里最占Flash的是数学函数。比如你用到了pow()、exp()、log(),这些函数库一链接进来,可能直接吃掉几十KB。

常量数据方面,最典型的是查表用的数组。比如OCV-SOC曲线表,一个表可能几百个点,每个点两个float,就是几KB。

我建议的做法是:

  • 用查表代替计算:比如指数函数exp(),在MCU上跑一次可能要几百个周期。提前算好存成表,查表只要几个周期。
  • 表数据压缩:用线性插值代替全精度表。比如原来1000个点的表,压缩到100个点,查表时做线性插值,精度损失很小,但Flash省了90%。
  • 代码裁剪:去掉所有调试代码、printf、assert。这些在量产环境里一个都不要留。

注意:有些编译器默认会链接完整的数学库。你需要手动指定只链接需要的函数,否则一个printf就能把你的Flash吃光。

4.4 算力瓶颈:一个除法可能就要你命

PC上你写个除法,CPU一个周期就搞定了。MCU上呢?

以常见的Cortex-M0为例,没有硬件除法器。一个32位整数除法,需要软件模拟,可能要几十甚至上百个周期。浮点除法更慢,几百个周期起步。

你想想看,如果你的SOH算法里有个循环,每次迭代都做一次除法,循环1000次——那就是几十万个周期。在16MHz的MCU上,这就是好几毫秒。

怎么优化?

  • 用移位代替除法:除以2的幂次,直接用右移。比如 x/4 写成 x>>2。
  • 用乘法代替除法:比如 x/10,可以写成 x*0.1。但注意浮点乘法也慢,最好用定点数。
  • 查表代替复杂运算:三角函数、指数、对数,全部查表。
  • 定点数运算:这是最核心的技巧。把浮点数放大成整数,用整数运算代替浮点运算。

举个例子:你要计算 capacity * 0.95。可以写成 (capacity * 95) / 100。如果capacity是uint16,95是常数,那这个运算就是一次乘法和一次除法。但如果你把0.95放大256倍,变成243(0.95*256≈243),那计算就变成 (capacity * 243) >> 8。一次乘法,一次移位,快多了。

我曾经在一个项目里,把SOH算法从浮点全部改成定点数。原来跑一次要120ms,改完之后只要8ms。客户当场就愣住了。

4.5 避坑指南:我踩过的三个雷

嗯,这里我要说几个我亲身经历过的坑,你遇到了可以少走弯路。

第一个坑:堆栈溢出。

我曾经在移植一个卡尔曼滤波的SOH算法时,函数嵌套调用了七八层,每层都有局部数组。结果一运行就死机。查了两天,发现是堆栈溢出了。MCU的堆栈通常只有1~2KB,你一个局部数组定义个512字节,再递归几次,直接爆了。

解决方案:把大数组改成静态全局变量,或者用动态分配(但MCU上我一般不推荐malloc)。

第二个坑:中断延迟。

SOH算法里如果有耗时的计算,比如几百毫秒的矩阵运算,会阻塞中断响应。电池管理系统里,过流保护、过压保护都是靠中断的。你算法跑起来,中断响应不了,电池可能就出事了。

解决方案:把计算拆成小块,每块控制在1ms以内,中间让出CPU给中断。或者用DMA+双缓冲。

第三个坑:编译器优化。

我遇到过最诡异的一次:同样的代码,Debug版跑得好好的,Release版就出错了。后来发现是编译器把某个volatile变量优化掉了。你想想看,SOH算法里有些变量是中断里修改的,主循环里读取。不加volatile,编译器可能直接把它优化成常量。

解决方案:所有被中断和主循环共享的变量,一律加volatile。别偷懒。

4.6 总结:从PC到MCU,你要做的三件事

说白了,从PC到MCU的移植,不是简单的「换个编译器重新编译」。你要做三件事:

  1. 算账:列出算法需要的RAM、Flash、算力,跟MCU规格对比。不够就砍。
  2. 压缩:数据精度压缩、查表代替计算、定点数代替浮点数。
  3. 测试:在MCU上逐模块测试,特别是边界条件和中断响应。

我记得第一次做量产移植时,觉得这步很简单。结果被现实狠狠教育了一顿。从那以后,我每次做移植前都会先做资源预算,再动手写代码。这个习惯,救了我很多次。

下一章,我们会聊具体怎么在MCU上实现定点数运算。这个技巧,是量产SOH算法的核心中的核心。