痞子衡嵌入式:常用的資料差錯控制技術(3)- 和校驗(Checksum)

痞子衡發表於2017-09-13

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家講的是嵌入式裡資料差錯控制技術-和校驗

  在系列前一篇文章裡,痞子衡給大家介紹了比較簡單的校驗法-奇偶校驗,該校驗法主要是針對byte傳輸校驗而言,而在實際應用中我們不僅要保證byte的完整性,還需要校驗由多個byte組成的資料包packet的完整性。今天痞子衡繼續給大家介紹針對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當做無序資料包來處理的,有沒有其他比和校驗法更好且能夠校驗資料次序的檢錯方法呢?痞子衡在下篇會繼續聊。

  至此,嵌入式裡資料差錯控制技術之和校驗痞子衡便介紹完畢了,掌聲在哪裡~~~

相關文章