校验码计算说明
本文档说明MCU协议中使用的补码校验码计算方法。
校验码定义
校验码 = 所有数据字节之和的补码
将数据包所有字节(从头码到数据区最后一个字节,不包含校验码本身)相加,取结果的补码作为校验码。
计算步骤
- 累加:将数据包所有字节(除校验码)相加
- 取低8位:取累加和的低8位(
sum & 0xFF) - 求补码:
checksum = (0x100 - low_byte) & 0xFF
计算示例
以MCU心跳数据包为例:
原始数据(不含校验码)
A8 00 20 30
38 36 30 36 30 32 30 36 39 31 36 35 33 35 32
43 53 51 3A 32 37 3B 53 54 3A 30 30
步骤详解
步骤1:累加所有字节
A8 + 00 + 20 + 30 = 0xF8
+ 38 + 36 + 30 + 36 + 30 + 32 + 30 + 36 + 39 + 31 + 36 + 35 + 33 + 35 + 32 = 0x3D4
+ 43 + 53 + 51 + 3A + 32 + 37 = 0x1F2
+ 3B + 53 + 54 + 3A + 30 + 30 = 0x16C
最终累加和 = 0x709
步骤2:取低8位
0x709 & 0xFF = 0x09
步骤3:计算补码
校验码 = 0x100 - 0x09 = 0xF7
完整数据包
A8 00 20 30
38 36 30 36 30 32 30 36 39 31 36 35 33 35 32
43 53 51 3A 32 37 3B 53 54 3A 30 30
F7 ← 校验码
数据包解析:
A8 ------------------------------------------------ 头码
00 20 --------------------------------------------- 包长度:32
30 ------------------------------------------------ 指令码:0x30 心跳检测
38 36 30 36 30 32 30 36 39 31 36 35 33 35 32 ------- IMEI:"860602069165352"
43 53 51 3A 32 37 3B 53 54 3A 30 30 --------------- CSQ:27;ST:00
F7 ------------------------------------------------ 校验码
验证方法
接收方验证时,将所有字节(包括校验码)相加,结果的低8位应为 0x00:
累加和(不含校验码) + 校验码 = 0x09 + 0xF7 = 0x100 → 低8位为 0x00 ✓
代码实现
MicroPython 示例
# checksum.py
# MCU协议补码校验码计算 - MicroPython实现
def calculate_checksum(data):
"""
计算补码校验码
Args:
data: bytes或bytearray,不含校验码的数据包
Returns:
int: 校验码(0-255)
"""
total = 0
for byte in data:
total += byte
low_byte = total & 0xFF
return 0 if low_byte == 0 else (0x100 - low_byte)
def verify_checksum(data):
"""
验证校验码
Args:
data: bytes或bytearray,完整数据包(含校验码)
Returns:
bool: True=校验通过,False=校验失败
"""
total = 0
for byte in data:
total += byte
return (total & 0xFF) == 0
def add_checksum(data):
"""
将校验码添加到数据包末尾
Args:
data: bytes或bytearray,不含校验码的数据包
Returns:
bytes: 带校验码的完整数据包
"""
checksum = calculate_checksum(data)
if isinstance(data, bytes):
return data + bytes([checksum])
else:
result = bytearray(data)
result.append(checksum)
return bytes(result)
# ==================== 使用示例 ====================
if __name__ == "__main__":
# 示例:MCU心跳数据包(不含校验码)
heartbeat_data = bytes([
0xA8, 0x00, 0x20, 0x30, # 头码、长度、指令码
# IMEI: 860602069165352
0x38, 0x36, 0x30, 0x36, 0x30, 0x32, 0x30, 0x36,
0x39, 0x31, 0x36, 0x35, 0x33, 0x35, 0x32,
# 扩展内容: CSQ:27;ST:00
0x43, 0x53, 0x51, 0x3A, 0x32, 0x37,
0x3B, 0x53, 0x54, 0x3A, 0x30, 0x30
])
# 计算校验码
checksum = calculate_checksum(heartbeat_data)
print("校验码: 0x{:02X}".format(checksum)) # 输出: 校验码: 0xF7
# 添加校验码
full_packet = add_checksum(heartbeat_data)
print("完整数据包长度: {} 字节".format(len(full_packet))) # 输出: 32 字节
# 验证校验码
is_valid = verify_checksum(full_packet)
print("校验结果: {}".format("通过 ✓" if is_valid else "失败 ✗")) # 输出: 校验结果: 通过 ✓
# 打印完整数据包(Hex格式)
print("\n完整数据包(Hex):")
print(" ".join(["{:02X}".format(b) for b in full_packet]))
# ==================== 构建心跳包示例 ====================
def build_heartbeat_packet(imei, csq, status):
"""
构建MCU心跳数据包(cmd=0x30)
Args:
imei: str, 设备IMEI(15位数字)
csq: int, 信号强度(0-31或99)
status: int, 系统状态(0-3)
Returns:
bytes: 完整数据包(含校验码)
"""
content = "CSQ:{};ST:{:02d}".format(csq, status)
packet = bytearray()
packet.append(0xA8) # 头码
packet.append(0x00) # 长度高字节(占位)
packet.append(0x00) # 长度低字节(占位)
packet.append(0x30) # 指令码:心跳
packet.extend(imei.encode('ascii')) # IMEI(15字节)
packet.extend(content.encode('ascii')) # 扩展内容
# 回填长度(整包字节数,含校验码)
total_length = len(packet) + 1
packet[1] = (total_length >> 8) & 0xFF
packet[2] = total_length & 0xFF
# 计算并添加校验码
checksum = calculate_checksum(packet)
packet.append(checksum)
return bytes(packet)
C 语言示例
#include <stdint.h>
/**
* 计算补码校验码
* @param data 数据包(不含校验码)
* @param len 数据长度
* @return 校验码
*/
uint8_t calculate_checksum(const uint8_t *data, uint16_t len) {
uint32_t sum = 0;
for (uint16_t i = 0; i < len; i++) {
sum += data[i];
}
uint8_t low_byte = sum & 0xFF;
return (low_byte == 0) ? 0 : (0x100 - low_byte);
}
/**
* 验证校验码
* @param data 完整数据包(含校验码)
* @param len 数据长度
* @return 0=校验通过,非0=校验失败
*/
int verify_checksum(const uint8_t *data, uint16_t len) {
uint32_t sum = 0;
for (uint16_t i = 0; i < len; i++) {
sum += data[i];
}
return (sum & 0xFF);
}
Python 示例
def calculate_checksum(data: bytes) -> int:
"""计算补码校验码"""
total = sum(data)
low_byte = total & 0xFF
return 0 if low_byte == 0 else (0x100 - low_byte)
def verify_checksum(data: bytes) -> bool:
"""验证校验码"""
return (sum(data) & 0xFF) == 0
# 使用示例
data = bytes([0xA8, 0x00, 0x20, 0x30, ...]) # 不含校验码
checksum = calculate_checksum(data)
full_packet = data + bytes([checksum])
print(f"校验码: 0x{checksum:02X}")
print(f"校验{'通过' if verify_checksum(full_packet) else '失败'}")
补码校验特点
| 特点 | 说明 |
|---|---|
| 简单高效 | 只需一次遍历计算,计算量小 |
| 错误检测 | 可检测单字节错误和部分多字节错误 |
| 自验证 | 接收方将所有字节相加,结果应为0 |
| 无符号运算 | 使用8位无符号整数,溢出自动忽略 |
| 适用性 | 适合资源受限的嵌入式设备(如ESP32、STM32) |
注意事项
- 字节顺序:计算校验码时,数据包的字节顺序必须与发送时完全一致
- 长度字段:长度字段本身也参与校验计算
- 校验码位置:校验码位于数据包最后一个字节,不参与自己的计算
- 边界情况:当累加和的低8位为0时,校验码也为0
常见错误
| 错误 | 现象 | 解决方法 |
|---|---|---|
| 长度字段错误 | 校验始终失败 | 检查长度是否为实际数据长度 |
| 字节序错误 | 多字节字段解析错误 | 确认使用大端序(Big-Endian) |
| 校验码位置错误 | 验证失败 | 校验码必须是最后一个字节 |
| ASCII编码错误 | 字符串解析异常 | 确保使用标准ASCII编码 |