4. 校验和(Checksum)的实现:手动计算与工具生成
校验和这东西,说白了就是给镜像文件算一个「指纹」。烧录前比对一下,能发现数据有没有被篡改或损坏。我刚开始做量产那会儿,觉得这步可有可无,直到有一次整批固件烧进去全变砖……嗯,从那以后我再也不敢跳过校验和了。
这一章我们聊聊校验和怎么算、怎么用。我会从最原始的手动计算讲起,再带你看看量产工具里怎么自动生成。两种方式都搞明白,你才算真正掌握了这个基本功。
4.1 什么是校验和?
校验和(Checksum)是一种简单的数据完整性校验方法。它的核心思想是:把数据看作一串数字,通过某种算法算出它们的「和」,然后把这个和附加到数据后面。接收方用同样的算法重新计算,对比结果就知道数据有没有变过。
举个例子,假设你有三个字节:0x01、0x02、0x03。最简单的校验和就是把它们加起来:0x01 + 0x02 + 0x03 = 0x06。这个 0x06 就是校验和。
关键点:校验和不是加密,它只防意外损坏,不防恶意篡改。如果有人故意修改数据并重新计算校验和,你根本发现不了。但在量产烧录场景下,我们主要防的是文件传输错误、存储介质坏块、烧录器误码——这些场景下校验和完全够用。
4.2 手动计算:理解原理
我建议你先用手算一遍,哪怕只算几个字节。这样你才能真正理解校验和是怎么工作的,而不是只会敲命令。
4.2.1 最简单的累加和
假设我们有一段镜像数据(十六进制):
0xA5, 0x5A, 0x01, 0x02, 0xFF, 0x00
手动计算步骤:
- 把所有字节加起来:
0xA5 + 0x5A + 0x01 + 0x02 + 0xFF + 0x00 - 先算
0xA5 + 0x5A = 0xFF - 再加
0x01 = 0x100—— 注意,这里溢出了! - 再加
0x02 = 0x102 - 再加
0xFF = 0x201 - 再加
0x00 = 0x201 - 取低 8 位:
0x201 & 0xFF = 0x01
所以这个简单累加校验和是 0x01。
我的习惯:手动算的时候,我一般用 Windows 自带的计算器(程序员模式),或者用 Python 交互式环境。算完再跟工具生成的结果对比,能快速发现理解上的偏差。
4.2.2 带进位的累加和(One's Complement)
上面那种直接取低 8 位的做法有个问题:进位信息全丢了。稍微改进一点的做法是把进位再加回去。这就是所谓的「带进位累加」。
还是上面那组数据:
- 累加过程同上,得到
0x201 - 把高 8 位和低 8 位相加:
0x02 + 0x01 = 0x03 - 如果还有进位,继续加,直到没有进位为止
- 最终结果取反(One's Complement):
~0x03 & 0xFF = 0xFC
这个 0xFC 就是带进位累加校验和。TCP/IP 协议栈里的校验和用的就是这种算法。
4.2.3 手动计算练习
我给你一组数据,你自己算算看:
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC
算完之后,你可以用下面的 Python 代码验证:
data = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC]
# 简单累加和
simple_sum = sum(data) & 0xFF
print(f"简单累加和: 0x{simple_sum:02X}")
# 带进位累加和
total = sum(data)
while total > 0xFF:
total = (total & 0xFF) + (total >> 8)
checksum = (~total) & 0xFF
print(f"带进位累加和: 0x{checksum:02X}")
我曾经踩过的坑:手动计算时,最容易犯的错误是忘记处理进位。尤其是数据量大的时候,进位可能连续出现多次。我见过有人算出来的校验和跟工具对不上,查了半天发现是进位只加了一次。记住:只要高 8 位不为 0,就要继续加,直到高 8 位变成 0。
4.3 工具生成:量产场景下的自动化
手动计算只适合学习和调试。真正量产的时候,镜像文件动辄几百 KB 甚至几 MB,你不可能手算。这时候就需要工具来帮忙。
4.3.1 常用校验和算法
量产烧录中,常见的校验和算法有这几种:
| 算法 | 输出长度 | 碰撞概率 | 计算速度 | 适用场景 |
|---|---|---|---|---|
| 简单累加和 | 8 位 / 16 位 | 高 | 极快 | 低端 MCU、Bootloader |
| 带进位累加和 | 16 位 | 中 | 快 | 通信协议、固件校验 |
| CRC-8 / CRC-16 | 8 / 16 位 | 低 | 中等 | 存储校验、量产烧录 |
| CRC-32 | 32 位 | 极低 | 较慢 | 文件完整性校验 |
| MD5 / SHA-1 | 128 / 160 位 | 极低 | 慢 | 安全校验(非量产场景) |
我的建议:量产烧录场景下,CRC-16 或 CRC-32 是最佳选择。它们碰撞概率低,计算速度也能接受。简单累加和虽然快,但碰撞概率太高——两个不同的镜像文件算出同一个校验和的概率不小。我遇到过两次,排查起来非常痛苦。
4.3.2 使用命令行工具生成校验和
Linux 下最常用的校验和工具是 cksum 和 crc32。Windows 下可以用 certutil 或者第三方工具。
生成 CRC-32 校验和:
# Linux
crc32 firmware.bin
# 输出: 3a4b5c6d
# 或者用 cksum(默认输出 CRC-32)
cksum firmware.bin
# 输出: 1234567890 1048576 firmware.bin
# 第一个数字是 CRC-32,第二个是文件大小(字节)
生成 MD5 校验和(虽然我不推荐量产用,但有时客户要求):
# Linux
md5sum firmware.bin
# 输出: d41d8cd98f00b204e9800998ecf8427e firmware.bin
# Windows
certutil -hashfile firmware.bin MD5
4.3.3 在烧录脚本中集成校验和生成
量产的时候,我习惯把校验和生成集成到烧录脚本里。这样每次烧录前自动计算,不用手动操作。
下面是一个 Python 脚本示例,它读取镜像文件,计算 CRC-32,然后把校验和附加到文件末尾:
import zlib
import struct
def append_checksum_to_firmware(input_file, output_file):
# 读取固件数据
with open(input_file, 'rb') as f:
data = f.read()
# 计算 CRC-32
crc = zlib.crc32(data) & 0xFFFFFFFF
# 把校验和写入文件末尾(4 字节,小端序)
with open(output_file, 'wb') as f:
f.write(data)
f.write(struct.pack('<I', crc))
print(f"原始大小: {len(data)} 字节")
print(f"CRC-32: 0x{crc:08X}")
print(f"输出文件: {output_file}")
# 使用示例
append_checksum_to_firmware('firmware.bin', 'firmware_with_crc.bin')
我的经验:校验和附加在文件末尾还是开头?我建议附加在末尾。因为 Bootloader 读取固件时,通常从开头顺序读取,校验和放在末尾不影响正常加载流程。如果放在开头,Bootloader 需要先跳过校验和字段,增加复杂度。
4.4 校验和验证:烧录前的最后一道防线
生成校验和只是第一步。真正重要的是在烧录前验证它。量产工具应该做两件事:
- 验证镜像文件本身的校验和——确保文件在传输或存储过程中没有损坏
- 计算烧录后的校验和——确保数据被正确写入 Flash
下面是一个验证脚本:
import zlib
import struct
def verify_firmware_with_checksum(file_path):
with open(file_path, 'rb') as f:
data = f.read()
# 分离固件数据和校验和
firmware_data = data[:-4]
stored_crc = struct.unpack('<I', data[-4:])[0]
# 重新计算 CRC-32
calculated_crc = zlib.crc32(firmware_data) & 0xFFFFFFFF
if calculated_crc == stored_crc:
print("✓ 校验通过!")
return True
else:
print(f"✗ 校验失败!")
print(f" 存储的 CRC: 0x{stored_crc:08X}")
print(f" 计算的 CRC: 0x{calculated_crc:08X}")
return False
# 使用示例
verify_firmware_with_checksum('firmware_with_crc.bin')
我曾经踩过的坑:有一次量产,校验和总是对不上。查了半天发现是字节序问题——工具生成的是大端序,但 Bootloader 按小端序解析。从那以后,我每次都会在文档里明确标注字节序,并且在代码里用 struct.pack 和 struct.unpack 显式指定。
4.5 本章知识体系
下面这张图总结了校验和的核心逻辑和实现路径:
嗯,这一章的内容就到这里。手动计算让你理解本质,工具生成让你提高效率,验证环节确保万无一失。这三步走下来,校验和这块你就拿捏住了。