今天痞子衡继续给大家介绍针对 packet 校验的最简单的校验法 - 即和校验法。

 

一、和校验法基本原理

1.1 校验依据

  

和校验法的校验依据就是判断一次传输的 n bytes 组成的 packet 的所有 byte 累加和结果(仅截取低 byte)在传输前后是否一致。

 

1.2 和校验位

  

为了实现和校验,通常会在传输的这组 n bytes 数据最后插入一个额外的和校验字节(byte),用它来记录这组数据累加和的低 byte 结果。

 

1.3 校验方法

  

前面讲到和校验位实际上是 n bytes 数据包的累加和,那么一包数据的长度 n 到底怎么确定呢?为了确定 n,我们通常会在一包数据开始的时候额外插入一个信息位标明当前数据包长度。
  

在实际应用中,数据包是一包一包连续发送的,如果传输过程中发生数据丢失,则会引起数据包的错位导致接下来一连串数据包的解析错误,如何及时发现数据包错位呢?我们通常还会在数据包最开始的时候再额外插入一个信息位标明一包数据的开始,这个信息位也叫作起始标志字节。
  

所以最终完整的数据包变成如下格式:

 

起始标志字节(1 byte)    长度字节(1-2 byte)    原始数据位(n bytes)    和校验字节(1 byte)
  

有了上述前导信息位,我们便可以准确找到一包数据中的原始数据位进行累加计算得出和,然后与数据包中的校验和字节进行比较验证当前包数据的正确性。
  

需要注意的是,对于校验和字节,有时候并不一定是数据位所有字节之和结果的原码,也有可能是反码或补码(关于三者区别,请参考痞子衡另一篇文章《整数在计算机中的表示》),需要结合不同校验和应用标准区别对待,否则会导致验证结果有误。

 

1.4 C 代码实现

 

实际中校验和字节为数据之和 byte 结果(认定被截断的 bit9 为 1)的补码应用较多,因为在验证数据包时,直接将所有数据连同校验和字节直接相加得到 byte 结果为 0,即表示数据包正确。此处示例代码以补码校验和为例:

 

安装包:codeblocks-17.12mingw-setup.exe


集成环境:CodeBlocks 17.12 rev 11256


编译器:GNU GCC 5.1.0


调试器:GNU gdb (GDB) 7.9.1

 

// checksum.c
//////////////////////////////////////////////////////////
#include <stdint.h>

enum _packet_constants
{
    kPacketStartByte = 0x5a
};

#pragma pack(1)
typedef struct _packet_header
{
    uint8_t startByte;
    uint8_t length;
} packet_header_t;
#pragma pack()

/*!
 * @brief 计算数据块的 checksum(补码)
 *
 * @param src, 待处理的数据块 .
 * @param lenInBytes, 待处理的数据块长度 .
 */
uint8_t get_checksum(uint8_t *src,
                     uint32_t lenInBytes)
{
    uint8_t checksum = 0;
    // 计算数据和,丢弃高 bytes
    while (lenInBytes--)
    {
        checksum += *src++;
    }
    // 转换为补码
    checksum = (~checksum) + 1;
    return checksum;
}

/*!
 * @brief 验证数据包的 checksum
 *
 * @param src, 待处理的数据包 .
 * @retval 0, 数据包 checksum 校验正确 .
 * @retval 1, 数据包起始标志字节错误 .
 * @retval 2, 数据包 checksum 校验错误 .
 */
int32_t verify_packet(uint8_t *src)
{
    uint8_t sum = 0;
    packet_header_t *header = (packet_header_t *)src;
    // 校验数据包头
    if (header->startByte != kPacketStartByte)
    {
        return 1;
    }
    // 求所有数据及校验字节之和
    for (uint32_t i = 0; i < header->length; i++)
    {
        sum += *(src + sizeof(packet_header_t) + i);
    }
    // 结果为非 0,则 checksum 错误
    if (sum)
    {
        return 2;
    }

    return 0;
}

// main.c
//////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include "checksum.h"

int main(void)
{
    uint8_t packet[16];
    packet_header_t *header = (packet_header_t *)packet;
    // 填充包头
    header->startByte = kPacketStartByte;
    header->length = sizeof(packet) - sizeof(packet_header_t);
    // 填充数据
    for (uint32_t i = sizeof(packet_header_t); i < header->length - 1; i++)
    {
        packet[i] = rand();
    }
    // 填充 checksum
    packet[sizeof(packet) - 1] = get_checksum(&packet[sizeof(packet_header_t)], header->length - 1);
    // 显示 packet
    for (uint32_t i = 0; i < sizeof(packet); i++)
    {
        printf("packet[%d] = 0x%x\n", i, packet[i]);
    }

    // 校验 checksum
    int32_t res = verify_packet(packet);
    printf("check res = %d\n", res);

    return 0;
}


1.5 行业应用

 

和校验由于实现简单,检错性能也算理想,因此应用十分广泛,就嵌入式而言,比较典型的应用是在各种 image 格式中。做过编程器或者下载器的朋友肯定会比较了解,常用的 image 格式有 hex、s19,这些 image 文件都是由多个数据包组成的,在下载 image 文件时需要对每一包进行和校验。关于 image 文件详情,可参考痞子衡的文章《ARM 开发中 image 文件详解》。

 

二、和校验法失效分析

在数据包传输中,如果只是单 byte 发生 bit 错误(无论多少个 bit 错误),和校验法一定能够识别出错误。即使有多个 byte 发生 bit 出错,大部分情况下和校验法也能正常检出。但和校验法有如下 3 个明显的缺陷:

 

当多个 byte 发生的 bit 错误发生抵消现象(引起的增量和结果是 0x100 的倍数),无法识别错误。


当 packet 中 byte 数据顺序发生调换时,无法识别错误。


不能纠错,在发现错误后,只能要求重发。
  

和校验法虽然能够校验 packet,且有一定的错误 bit 检测能力,但其是把 packet 当做无序数据包来处理的,有没有其他比和校验法更好且能够校验数据次序的检错方法呢?痞子衡在下篇会继续聊。

  

至此,嵌入式里数据差错控制技术之和校验痞子衡便介绍完毕了,掌声在哪里~~~