DS28EC20 的 32 字节 Extended Read CRC16 为何与设备结果不一致?
原脚本有两处错误:循环只处理了 34 个字节,漏掉末尾的 0x34;右移算法应使用反射多项式 0xA001。即使修正这两处,对题中的完整 35 个字节算出的原始 CRC 仍是 0xB55B,取反后为 0x4AA4,与记录的 0x1181 不一致。脚本错误可以确定,但设备返回值与所列数据为何不一致,还需要核对实际通信记录。
CRC16 的计算规则
DS28EC20 使用多项式 x^16 + x^15 + x^2 + 1,正常表示为 0x8005。采用最低有效位优先的右移实现时,使用其反射表示 0xA001,初始值为 0x0000,发送前将结果按位取反。DS28EC20 数据手册的 CRC GENERATION 章节
这里需要区分两个操作:
- 反射:决定多项式表示与移位方向的搭配,例如右移实现使用
0xA001。 - 取反:对最终的 16 位结果执行
crc ^ 0xFFFF。
0xA001 不是对 0x8005 按位取反得到的。使用下面的右移算法时,也不需要再手动反转每个输入字节。
原脚本为什么得到 0xE578
两个计算循环都写成了:
for(my $i = 0; $i < 34; $i++) {
这个循环只处理下标 0 到 33。输入共有 35 个字节,下标 34 对应的最后一个字节 0x34 被漏掉了。
使用初始值 0x0000 和右移多项式 0xA001,对题中数据计算可得:
| 参与计算的数据 | 未取反 CRC | 取反后 CRC |
|---|---|---|
前 34 字节,最后为 0x33 | 0x1A87 | 0xE578 |
完整 35 字节,最后为 0x34 | 0xB55B | 0x4AA4 |
表中第一行与原脚本的第二组输出一致,说明这组输出符合实际处理的 34 个字节。
第一组计算则把 0x8005 直接用于检查最低位并右移的循环,移位方向与多项式表示不匹配。原描述中的 0x8001 也与脚本不一致,应以脚本实际使用的 0x8005 为准。
Python 实现
下面将原始 CRC 计算与最终取反分开,便于逐项检查:
def crc16_raw(data, crc=0x0000):
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc & 0xFFFF
message = bytes.fromhex(
"A5 00 00 "
"03 04 05 06 07 08 09 "
"10 11 12 13 14 15 16 17 18 19 "
"20 21 22 23 24 25 26 27 28 29 "
"30 31 32 33 34"
)
raw_crc = crc16_raw(message)
transmitted_crc = raw_crc ^ 0xFFFF
crc_bytes = transmitted_crc.to_bytes(2, byteorder="little")
print(f"Length: {len(message)}")
print(f"Raw CRC: 0x{raw_crc:04X}")
print(f"Inverted CRC: 0x{transmitted_crc:04X}")
print("CRC bytes:", crc_bytes.hex(" "))
对所列字节的计算结果为:
Length: 35
Raw CRC: 0xB55B
Inverted CRC: 0x4AA4
CRC bytes: a4 4a
这里的 0x10 是十进制 16,不是十进制 10。bytes.fromhex() 按十六进制解析,与原脚本的 hex($line) 一致。
Perl 脚本的修改方法
保留现有文件读取部分,将两组尝试性计算替换为以下循环。循环直接遍历数组,避免因手写长度而遗漏字节。
$crc = 0x0000;
for my $byte (@number_set_1) {
$crc ^= $byte;
for (1 .. 8) {
if ($crc & 1) {
$crc = ($crc >> 1) ^ 0xA001;
} else {
$crc >>= 1;
}
}
}
$crc &= 0xFFFF;
$crc_n = (~$crc) & 0xFFFF;
printf("Length: %d\n", scalar @number_set_1);
printf("Raw CRC: 0x%04X\n", $crc);
printf("Inverted CRC: 0x%04X\n", $crc_n);
printf("CRC bytes: %02X %02X\n",
$crc_n & 0xFF, ($crc_n >> 8) & 0xFF);
为什么仍然不能确认 0x1181
首次读取时,CRC 覆盖 0xA5、TA1、TA2,以及从起始地址到当前页末尾的数据。从 0x0000 开始读取时,这些就是题中的 35 个字节。同一次命令继续读取后续页时,每页的 CRC 都从零开始计算,只覆盖该页数据。若起始地址不在页首,首次 CRC 前的数据会少于 32 字节。DS28EC20 数据手册的 Extended Read Memory 与 CRC GENERATION 章节
建议按以下顺序核对实际读写日志:
- 确认发送的是
A5 00 00,并记录紧随其后的 32 个接收数据字节。 - 将再后面的两个 CRC 字节单独记录,保留接收顺序。不要把用于产生读取时隙的占位字节当作返回数据。
- 确认取到的是本次命令第一页的 CRC,计算输入中没有混入 ROM 选择命令、ROM ID 或上一页的 CRC。
- 核对驱动是否已经对 CRC 做过字节交换或取反,避免重复转换。
两个 CRC 字节按低字节在前组合。例如,本例计算出的 0x4AA4 对应接收顺序 A4 4A。若 0x1181 是按这种顺序组合后的整数,其原始字节应为 81 11;若日志原始顺序其实是 11 81,组合后则为 0x8111。两种情况都与本例的 0x4AA4 不一致,单纯交换字节无法解释差异。
对于所列的完整输入,计算结果是 0x4AA4。仅凭这些字节和合并后的 0x1181,无法判断差异来自读取偏移、数据记录、驱动处理还是总线传输。还需要同一次事务的原始收发字节才能继续定位。
备注:内容仅供参考。
