前言:

最近因為工作的關係,需要熟悉 RS485 的通訊協定。這個協定基本上是硬體層 RS232 的差動電壓版本,叫做 RS422,再加上一個傳輸層的協定叫做 Modbus 的 protocol,並不難。

但假如要實作的話,就需要搞懂所謂 CRC (Cyclic redundancy check) 循環冗餘校驗的計算演算法,所有資料在送出前需要透過這個CRC計算後附加上去。主從的雙方收到資料後都依賴這個 CRC 的計算來彼此查驗是否資料的傳輸的過程中發生任何資料丟包的情況,最後來進行後續的處置。

特別是像 RS485 的這種號稱可以傳送長達一公里距離的 bus, 這種訊號因為長距離的傳輸而扭曲丟包的情況是非常容易發生的,所以採用 CRC 的技術來進行檢查變得非常的必要。

上網找了一些資料,因為工作上要使用,當然最後這個 CRC 計算用其他的電腦語言實作出來了。

但是這裡是我的 FORTH 語言推廣的小小部落格,所以順便也寫了一個 FORTH 語言的版本囉,所以不管怎樣,要來簡單的分享 FORTH 語言 CRC16 計算的程式碼囉。

 

關於CRC的理論:

理論方面,維基百科其實做了蠻不錯的整理,按我進連結

理論講得相當複雜,但筆者對 CRC計算有個很簡單的看法,就是把它當作是一群資料的雜湊函數,CRC碼代表這群資料的雜湊結果。當算出來的 CRC 雜湊碼不太一樣時,自然代表這群資料已經不太一樣了,有資料已經掉或失真囉。

 

Modbus CRC16 計算程序:

CRC 的計算有非常多種,有 CRC8, CRC16, CRC32 的各種版本。同樣的,即使是 CRC16 ,也會因為採用的 generation function key 值的不同而算出來的結果不同。

 

先來條列一下 Modbus CRC16 計算的演算法吧:

 

(1) 將 crc 結果變數設為 0xFFFF 初始值 (2bytes)

(2) 取出預計計算的 1 byte 的資料 跟 crc 結果變數的低位元 byte 進行 xor 運算

(3) DO-LOOP 迴圈,對 crc 結果變數的LSB 做檢查,總共8次

        - 當最右的 LSB 為 1 時,將crc 結果變數右移一位,並和 0xA001 做 xor 運算

        - 當最右的 LSB 為 0 時,將crc 結果變數右移一位

(4) 完成此 byte 的 crc 運算,回到 (2) 取出下個資料 byte 繼續進行 crc 運算,直到完成所有資料 byte

(5) 對調crc 結果變數的高低 byte,此即為 CRC16 檢查碼!

 

 

FORTH 程式解說:

這裡因為要用到大量的位元運算,所以對 FORTH 的資料結構要搞清楚,謹記在心的

8位元 CPU 的FORTH系統

FORTH 裡一個字元為 1 byte ,一個 word 的單整數是 2 bytes ,兩個 words 的雙整數是 4 bytes

16位元 CPU 的FORTH系統

FORTH 裡一個字元為 1 byte ,一個 word 的單整數是 2 bytes ,兩個 words 的雙整數是 4 bytes

32位元 CPU 的FORTH系統

FORTH 裡一個字元為 1 byte ,一個 word 的單整數是 4 bytes ,兩個 words 的雙整數是 8 bytes

 

現代的不管是 Mac 或是 PC 都是 32位元以上的系統,自然,所謂一個單整數是指的是 4 bytes 的長度。所以 gFORTH 在這些電腦下的參數堆疊,每個單整數都是 4 bytes 的長度。但是 CRC16 只用到 2bytes 的長度,所以任何位元運算前,都得使用 0xFFFF 的遮罩,以 AND 運算進行截取 32 bits 的 Low Bytes,否則 High Bytes 結果跑進來的話,就會導致後續計算的錯誤囉。

當然,假如你是8位元的系統,剛好參數堆疊的單整數長度是 16 bits ,就不需要這麼麻煩囉!

 

ByteSwap

這個指令顧名思義,將 High Low byte 交換 (Little Endian) 成 Modbus RTU 所規定的順序。

0xFF AND 取出 low byte  左移8位到 high byte

0xFF00 AND 取出 high byte  右移8位到 low byte,再利用 OR 合併後就完成交換

: ByteSwap ( n -- n')
   dup  00FF and 8 LSHIFT
   swap FF00 and 8 RSHIFT
   or
;

 

CRC16

這是主要計算 CRC16 的指令,需要透過堆疊告訴它兩個參數:資料的起始位址,跟長度

: CRC16 ( addr n -- crc )
   FFFF   ( addr n crc )
   rot rot over + swap  ( crc addr+n addr)
   do    ( crc)
       FFFF and   ( if you are using 8 bits system, remove this line.)
       i c@ xor
       8 0
       do dup 1 and 
          if   1 RSHIFT
               A001 XOR
          else 1 RSHIFT   then
       loop
    loop
    
    ByteSwap
;    

 

首先將 0xFFFF 的 crc16 起始值堆上堆疊

FFFF   ( addr n crc )

再來根據資料的起始位址,跟長度計算設定一下主要的 DO-LOOP 迴圈,要以1 byte 的方式逐步掃描取出所有資料進行 CRC 累積計算

   rot rot over + swap  ( crc addr+n addr)
   do    ( crc)
       ...
   loop

 

然後 Byte 計算前,如前所述,如果是 32 bits 的 FORTH 系統,需用 0xFFFF AND 一下,把無用的 high bytes 截掉

       FFFF and   ( if you are using 8 bits system, remove this line.)

 

將位址的 1 byte 資料,用 c@ 取出,跟堆疊上的 crc16 進行 XOR 運算

       i c@ xor

 

接下來用個 DO-LOOP 掃過 crc16 Low Byte 的 8 bits

0x01 AND 取出最右 bit

假如最右 bit 為 1  ,  crc16 右移一位 ,然後跟 0xA001 這個 generation key XOR 後放回

假如最右 bit 為 0 ,  crc16 右移一位

       8 0
       do dup 1 and 
          if   1 RSHIFT
               A001 XOR
          else 1 RSHIFT   then
       loop

 

這樣就完成了單一 byte 的 CRC16 計算

 

逐次完成所有計算後, 將堆疊上的 crc16 透過 ByteSwap 指令交換一下高低位,這樣就完成所有 CRC16 的計算

 

 

 

結果驗證:

 

用兩筆資料來進行驗證

第一筆

0x2D 0x00 0x03 0x00 0x07

這樣的資料, CRC16 算出來應該是  0x39C4

 

第二筆

0x01 0x03 0x00 0x00 0x00 0x01

這樣的資料, CRC16 算出來應該是  0x840A

 

經過執行後結果如下,完全正確無誤

CRC16 Test Result.png

 

 

 

 

原始程式碼列表

 

\
\   Modbus CRC16 calculation
\         2020.9.19   Frank Lin
\


hex

: ByteSwap ( n -- n')
   dup  00FF and 8 LSHIFT
   swap FF00 and 8 RSHIFT
   or
;

: CRC16 ( addr n -- crc )
   FFFF   ( addr n crc )
   rot rot over + swap  ( crc addr+n addr)
   do    ( crc)
       FFFF and   ( if you are using 8 bits system, remove this line.)
       i c@ xor
       8 0
       do dup 1 and 
          if   1 RSHIFT
               A001 XOR
          else 1 RSHIFT   then
       loop
    loop
    
    ByteSwap
;    


\
\ Verification
\

\
\   0x2D 0x00 0x03 0x00 0x07
\   The CRC should be 0x39C4
\

create test1     2D c,  00 c,  03 c,  00 c,  07 c,

test1 5 CRC16 

cr cr
.( == Verify #1 ==) cr
test1 5 dump   .( CRC should be: 39C4) cr   
.( The calculated value is ) .    cr
cr

\
\   0x01 0x03 0x00 0x00 0x00 0x01
\   The CRC should be 0x840A
\


create test2     01 c,  03 c,  00 c,  00 c,  00 c,  01 c,

test2 6 CRC16 

cr cr
.( == Verify #2 ==) cr
test2 6 dump   .( CRC should be: 840A) cr   
.( The calculated value is ) .    cr
cr

 

 

 

 

arrow
arrow
    創作者介紹
    創作者 ohiyooo2 的頭像
    ohiyooo2

    早安,苦命工程師的胡言亂語

    ohiyooo2 發表在 痞客邦 留言(0) 人氣()