上次我們聊到了在物聯網裡扮演重要 Modbus通訊協議,一個簡單開放的工業通訊協定,後來發現還有些需要補充的細節,於是決定來一下物聯網 Modbus 補完計畫。這次的內容會包含四個部分:
不囉唆,直接上菜!
當 Modbus 協議走在 Serial (RS232/485) 上的時候,通訊模式可以選 RTU 或 ASCII 這兩種傳輸模式,不過因為兩者的運作機制不一樣,所以通訊時只能選擇其中一種來使用且傳輸端與接收端都必須使用相同的傳輸模式,才不會發生數據解讀錯誤變成一堆亂碼、兩邊雞同鴨講的狀況。關於這兩種傳輸模式的差異之處,主要有三點:
在前一篇的文章我們有提到 ADU,還記得郵局跟宅急便收送包裹的例子嗎?要寄送的物品,就得根據郵局或宅急便的規定包裝。所以 Modbus 的資料得按照 RTU 或者 ASCII 模式的要求包裝成所屬版本的 ADU 後才能進行傳輸。當我們選用 RTU 傳輸模式的時候,ADU 就會以二進位方式進行編碼,ADU 會被編成是一連串的 0101 的形式。我們先假設要傳送的裝置位址編號是數字 1,經過二進位編碼後就變成 0000 0001 。因此 RTU 傳輸模式也被稱為二進位模式。不過為了讓我們可以方便閱讀,還是會習慣把二進位的 0000 0001 轉換成十六進位的數字 0x01 來表示。
ASCII 編碼是字符編碼模式以十六進位數字表示法為基礎。一開始先把二進位的數據轉成十六進位數字表示,再把轉換出來的十六進位數字「看成」文字符號再拿出 ASCII 對照表找出字符對應的數據編碼。繼續沿用前面的通訊位置 1 為例,第一步就是先把二進位表示的 0000 0001 轉成的十六進位的數字 0x01,接著把 01 看成兩個獨立文字符號 ‘0’ 跟 ‘1’ (用單引號刮起來代表文字),經過查 ASCII 對照表得知文字符號 ‘0’ 的編碼是 0x30,’1’ 的編號是 0x31。對 ASCII 傳輸模式來說,原本要傳輸的數字 1 經過 ASCII 編碼後就變成要傳輸 0x30 0x31 了。
RTU 編碼用一個 Byte ,ASCII 就要用兩個 Bytes
你一定也發現了,一樣的資料使用 ASCII 編碼會比使用 RTU 的編碼「胖」一倍,也就是原本在 RTU 傳輸模式只要 1 個 byte (8 個 bits, 8 個 0101 的組合) 就可以處理的資料,改用 ASCII 傳輸模式就得花 2 個 byte (16bits, 16 個 0101 的組合) 來處理。ASCII 傳輸模式有比較容易閱讀的好處,考慮 Modbus Serial 每次資料傳輸封包有最大長度 256 bytes 的上限,變胖的 ASCII 傳輸模式一次能處理的資料內容自然就少了一半,數據傳輸效率自然遜於 RTU 傳輸模式了。
打包好的 ADU 也叫做 Frame(訊號框)是傳輸資料的基礎單位。問題就在於是接收端是如何判斷何時要開始收資料,資料何時傳送完成通訊結束?Modbus 通訊有規定前每次 Frame 傳出去前都要先打招呼,要加一個起始訊號在 ADU 前面出來給對方確認,讓接收端看到起始訊號後開始接收資料,等到所有資料都送出去後,還要在 Frame 的尾端打一個結束訊號跟接收端說再見,接收端要有收到開始跟結束訊號才算完成這次的資料傳輸作業。
RTU 傳輸的起始跟結束是用時間 >=3.5 Chars 來決定,ASCII 傳輸模式的起始跟結束是用字符 0x3A 跟 0x0D 0x0A 來決定
當我們選用 RTU 傳輸模式的時候,要打給對方確認的起始跟結束訊號都是用時間長短來決定,標準是 3.5 chars,也就是數據跑過去 3.5 bytes 所需要的時間。Serial 在待命的時候資料傳輸線路電壓都是高電位 (0),所以只要傳輸線路上出現了一個低電位訊號 (1) 且持續時間超過 3.5 chars 就會被認定通訊開始要開始接收數據,等確認出現 3.5 chars 的時間的高電位訊號的時候,就確認這次數據傳輸已經結束。至於 3.5 char(s) 是多少時間,跟我們設定的通訊參數 baudrate 有關,我們先假設 3.5 chars = 1ms ,只要偵測到有一個低電位訊號且維持了 1ms 的時候,接收端就開始收資料了直到偵測到 1ms 的結束訊號為止,關於通訊參數該怎麼設定、3.5 chars 需要多少時間是怎麼算出來的,這部分等我們後續講到 Serial 的時候再補充吧。
ASCII 模式是使用開始字符跟結束字符來表達 Frame 的頭跟尾。當 ASCII 模式要開始傳輸資料的時候,會先打出一個 0x3A 的訊號,0x3A 查 ASCII 表就是 ‘:’ 冒號,所以看到冒號後就代表後面來的是資料了,等接收端偵測到 0x0D 0x0A 的時候就表示資料已經傳送完畢了。對人來說使用 ASCII 要判斷終點跟起點是相對容易的,因為只要看到冒號就代表後面有資料,看到 0D 0A 就代表資料結束。不過 ASCII 傳輸模式的限制也很明顯,也就是你的原始資料內絕對不可以有 0D 0A,否則就會發生接收端誤以為已經傳輸完畢結果數據接收不完全的錯誤。
資料傳輸的時候是走在電路上,傳輸過程中會有機會被環境電磁波干擾導致要傳給對方的資料會出錯,那麼接收方要如何確認收到的資料對不對呢?這時候就要靠 ADU 的檢查碼了。接收端會把數據需要的檢查碼計算一遍,跟傳送過來的檢查碼做比對,只要兩者檢查碼相同,就代表數據內容正確。
RTU 傳輸模式的 ADU 用的檢查碼 CRC,ASCII 傳輸模式用的演算法是 LRC
不管是哪一種算法都是為了確保收到的數據是正確的。也許你可能會好奇 CRC 或 LRC 究竟是如何計算出來的呢?關於 CRC 或 LRC 的演算法,網路上已經有一堆資料,這裡就不再贅述了。當然我也不建議你去真的去跑去查找 LRC, CRC 的細部資訊,因為根據我多年的觀察,真正有能力看懂 CRC 演算又能夠自己實作的軟體工程師真的是少之又少(也沒必要)。對應用端的軟體開發者而言,知道是什麼該怎麼去呼叫對應的 CRC 或 LRC 的函數絕對都比自己鑽研大半天搞一個自己的算法來得有效率。
能自己寫出 CRC 或 LRC 演算法的人出來固然厲害,但是能正確理解知識且應用好的人才是真正的專業!
以上就是這次 Modbus 補完計畫第一趴的內容,先把 Modbus RTU 跟 ASCII 兩種傳輸模式的三個主要的差異點分享給大家。了解跟應用才是最重要的部分,通訊本來就跟我們講話一樣,都是為了達到溝通的目的,不用特別去糾結文法或艱澀字的問題,用最簡單、最有效率方式把自己的意思清楚表達給對方就好了。這次先補到這裡,我們就下一篇再見了。