4、固件准备与校验:固件编译与Hex/Bin格式、固件CRC校验、固件签名与加密
固件准备,说白了就是给芯片“喂”对的东西。我见过太多产线翻车,最后发现是固件本身出了问题——要么格式不对,要么校验码算错了,要么压根没加密就被抄了。这一章,咱们就把这些坑一个一个填上。
4.1 固件编译:从源码到机器码
你写好的C代码,编译器会把它变成两种常见格式:Hex和Bin。很多人觉得这俩差不多,其实差别大了去了。
| 特性 | Hex(Intel HEX) | Bin(Binary) |
|---|---|---|
| 存储内容 | ASCII文本,含地址、数据、校验 | 纯二进制数据,无地址信息 |
| 文件大小 | 约为Bin的2~3倍 | 紧凑,与Flash容量一致 |
| 地址信息 | 每行都带起始地址 | 无,需烧录器指定基址 |
| 烧录速度 | 较慢(解析开销) | 快,直接按地址写入 |
| 适用场景 | 调试、分段烧录 | 量产、整片烧录 |
编译时还有个小细节——优化等级。我踩过这个坑:开发时用-O0,量产时改成-Os,结果某个延时函数被优化掉了,整批板子跑飞。所以,量产固件必须用最终确定的编译选项重新编译,不能拿开发版的Hex直接上。
4.2 固件CRC校验:给固件上“指纹”
CRC(循环冗余校验)是量产烧录中最常用的校验手段。它不像MD5或SHA那么重,但足够检测出传输或存储中的单比特错误。
我个人习惯用CRC32,多项式0x04C11DB7。为什么?因为大多数MCU硬件都支持这个多项式,软件算起来也快。
来看一个典型的CRC计算流程:
// 伪代码:计算固件CRC32
uint32_t crc32_calculate(uint8_t *data, uint32_t len) {
uint32_t crc = 0xFFFFFFFF;
for (uint32_t i = 0; i < len; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 1) {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc >>= 1;
}
}
}
return crc ^ 0xFFFFFFFF;
}
量产时,我通常把CRC值放在固件的固定偏移位置(比如最后4字节)。烧录器烧完固件后,重新读回整个Flash区域,计算CRC,跟固件里预存的CRC比对。这叫“读回校验”,比只校验烧录过程可靠得多。
4.3 固件签名与加密:防抄板、防篡改
如果你的产品卖得好,一定会有人抄。固件签名和加密,就是最后一道防线。
4.3.1 固件签名
签名的作用是验证固件来源和完整性。流程是这样的:
- 编译固件,得到Bin文件。
- 用私钥对固件哈希(比如SHA256)进行签名。
- 将签名附加到固件末尾。
- 芯片Bootloader用公钥验证签名。
这里有个关键点:私钥必须离线保存。我见过有人把私钥放在编译服务器上,结果服务器被黑,所有固件都能被伪造签名。正确的做法是:用HSM(硬件安全模块)或离线签名机,每次签名都手动操作。
4.3.2 固件加密
加密是为了防止别人直接读Flash就能拿到你的代码。常见方案有两种:
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 对称加密(AES) | 烧录时用密钥加密,芯片内部解密 | 速度快,适合量产 | 密钥需安全存储 |
| 非对称加密(RSA/ECC) | 用公钥加密对称密钥,芯片用私钥解密 | 密钥分发安全 | 计算量大,Bootloader复杂 |
量产时,我推荐AES-128-CTR模式。CTR模式可以并行解密,不影响启动速度。密钥可以烧录在芯片的OTP(一次性可编程)区域,或者由芯片唯一ID派生。
4.4 固件准备与校验流程总图
下面这张图,把整个流程串起来了。你可以把它贴在工位上,每次准备固件时对照检查。
4.5 量产中的常见坑与对策
最后,分享几个我在产线上真实踩过的坑:
- 坑1:CRC算法不一致。 烧录器用软件算CRC,芯片用硬件算CRC,结果两个结果不一样。对策:统一用同一个CRC库,并在烧录前做一次交叉验证。
- 坑2:签名长度没算对。 签名附加到固件末尾后,总长度变了,导致CRC校验失败。对策:先签名,再计算包含签名的CRC。
- 坑3:加密密钥写死在代码里。 有人把AES密钥直接写在源码中,结果固件被反编译后密钥泄露。对策:密钥由芯片唯一ID派生,或者从OTP区域读取。
- 坑4:产线烧录器不支持加密。 有些老式烧录器只能烧明文Bin,加密固件需要先解密再烧。对策:在烧录器前加一个“解密网关”,或者升级烧录器固件。