2018年1月16日 星期二

深入淺出 RS485 通訊 PART 1

已經脫離工控圈一陣子,說不定未來也不會回去了,乾脆把過去的經驗整理一下,分享給有需要的人

PS. 基本 RS485 通訊原理在 RS232 轉 RS485 演進史  一文已經提過,這邊不再贅述,不過該文對於 Linux 的描述已經過時,現行 Linux kernel 已經支援 RS485 通訊。

Single Master/Multi-Slaves

網路拓樸如圖 1 所示,這是最常見也是最簡單的形式,他的原理很簡單,所有 Slaves 都是被動的角色,只有 Master 可以主動送出命令(Request),Slave 只有在收到命令時依據 Station ID(如上圖中 Station#1-#3,業界稱為站號)決定要不要做出回應(Response),如下圖所示:

這種通訊協議,在工控中以 Modbus RTU 最為常見,在 Modbus RTU 中恰好也如圖 2 所示,把站號放在 frame 的第一個 byte,本文後續也會以 Modbus RTU 作為範例。

Modbus RTU 看似簡單,在實作上與實務中卻隱藏了不少問題。

問題 1: Slave 如何處理不屬於自己的命令?

由圖 1 可以發現,所有的 RS485 傳輸線都並聯在一起,所以 Master 送出命令之後所有 Slave 都會收到,前面講過 Slave 是依據站號決定要不要回應,但如果您以為 Slave 收到第一個  byte 發現站號不屬於自己,就後續就直接不理會,您就大錯特錯了。

正確的處理方式是這樣,以前面三台 Slave 為例,如果 Master 發出命令要與 Station#1 通訊,Station#2 與 3 必須接收 Master 與 Station ID#1 的  Request/Response frame 如下圖所示,否則您的程式會精神錯亂。

Station#2, #3 的流程如下:
  1. 接收 Master Request -> Station#1 Request 但不處理(需要做一定程度的 frame parsing,否則不知道 Request 的長度)
  2. 等待 Station#1 Response -> Master
  3. 接收 Station#1 Response -> Master 但不處理,與 1. 同樣要做某種程度的 frame parsing,否則無法得知正確長度。
libmodbus 是一份很好的參考資料,其中 _modbus_rtu_receive() 用了一個狀態變數 confirmation_to_ignore 紀錄是否收到一個不屬於自己的 request frame,當收到不屬於自己的 request frame,就會把 confirmation_to_ignore 設成 TRUE,接著呼叫 _modbus_receive_msg() 進行前述的步驟 2-3。

問題 2: 回應特別慢的 Slave 會拖垮通訊速度

以前面三台 Slave 為例,假設 Station#3 在收到 Request 與回應 Response 間有很大的延遲(如下圖所示),這個延遲時間就會拖垮整體通訊速度,Station#1 與 #3 就要花很久的時間等待 Station#3 。雖然說 Modbus spec 規定至少要延遲 3.5 char 的時間,但筆者碰過很多設備的延遲時間早就是 3.5 的 n 倍了。

(為了對付這個問題,libmodbus 還提供了 response_timeout 這個變數用來調校)

依筆者過去的經驗,最常碰到的是溫控器回應慢、PLC or Remote I/O 回應快,在過去做 HMI 的日子裡,通常就是請客人接到不同的 COM Port,避開這樣的問題。

不過有次碰到一位做真空燒烤爐的客戶,他的溫控器特多,印象中好像是 6-8 台吧?一般 HMI 不可能有那麼多 COM Port 讓他一台接一個 COM Port(一般 HMI 最多 3 個 COM Port)。

後來這位客人自己去買了一台 M 牌還是 A 牌的 Modbus Gateway,網路拓樸變成如下:

那為何透過一台 Modbus Gateway 就能增加通訊速度呢?除了原本需要從第1 台輪詢到第 8 台,變成因為有 8 個 COM Port 可以一次讀 8 台(平行讀取),Modbus Gateway 通常還提供一種功能,在 M 牌稱為 Agent Mode


簡單來說,Modbus TCP Master 讀取的是 Modbus Gateway 的內部記憶體(Cache),這樣帶來的好處是如果有多台 Master,也不會感覺到通訊速度下降:


圖 7、圖 8 中的 Cache 還可以玩出很多花樣,比方圖 8 中兩台 HMI 要讀取的 Modbus Holding Register、Coil... 通常位址會重疊,這時候 Agent 還可以把位址加以拼接,減少左側 RS485 通訊時的 round-trip time,筆者在一篇文章中也提到了相關演算法,有興趣的朋友可以看看

順帶一提,這也不是這些 Modbus Gateway 廠商的獨創發明,早在 1x 年前這就是 HMI 的標準功能,因為 HMI 有太多背景服務都想讀取控制器的資料,如果放任這些背景服務直接跟控制器要資料,鐵定會大塞車,所以只能放在 cache,當然這會創造出另外一個問題「如果控制器的某個位址內的值被某個背景服務更改了怎麼辦?」事實上這個問題正是各家 HMI 廠商最難突破的點,也就是如何同時在效能與資料不同步的機率間取得平衡,筆者在某篇文章中也涉獵了這個問題

尾聲

以現在 LoRa、NB-IoT 等等的聲勢來看...討論 RS485 似乎有點落伍?但事實上 RS485 仍有成本超低,需要零組件極少,抗干擾強,超長傳送距離等優點,不少廠商仍靠著他賺得盆滿缽滿,除非哪天 LoRa 變成白菜價,也沒有資安等問題才可能威脅到 RS485 吧?

本以為一篇就能寫完,後來發現得寫成兩篇,在後續篇章中各位會發現 RS485 這兩根線居然能搞出這麼多花樣,敬請期待...

4 則留言:

  1. 很好。

    有些東西老是死握著不放,也沒有代表你就比較高明。

    因為時代技術演變是瞬息萬變的,你所說的LoRa 或NB-IoT 會不會成為新

    標準?我也不確定,但我確定的是:只要有需求,而傳統的規格跟不上,

    那肯定就會一直出現新規格來突破。這個時代,已經不像以前那樣子,

    一個老東西還可以讓少數人,可以慢慢地吃個十年或二十年的啦。

    不是嗎?

    回覆刪除
    回覆
    1. 非常同意~這是一種時機財,早就沒小弟的機會了

      刪除
  2. 你好,
    一般來說,geteway ModbusTCP端的ip是例如192.168.1.3等等的區域網域ip.
    在處處講求IoT應用的現今環境,透過ModbusTCP geteway的機制,雖然已能將設備device的ModbusRTU格式"打包"上網(例如大大曾提過的mbusd),但卻也只能應用於區域網域內.
    如果客戶的需求是希望geteway的ModbusTCP能由"Server"成為"client"身份,主動connect到遠方主機(例如AWS cloud主機).
    這樣的需求情況下,是否已違反了ModbusTCP的架構機制?(例如mbusd就無法設為client,只能是server).
    怎樣才能讓geteway端的ModbusTCP能連上遠端?進入IoT的應用.

    回覆刪除
    回覆
    1. "...gateway 的 Modbus TCP 能由 "Server"成為"client" 身份
      ..."

      Modbus RTU Master->Modbus TCP client->Modbus TCP server
      是 modbus gateway 標準功能,mbusd 只是還沒做進去而已,筆者也也有寫一套,只是沒時間整理放到 github(否則就可以跟 mbusd 合體了,哈哈哈)

      不過您應該不是問這個,如果您是想問「有哪家大廠已經有Modbus to AWS 解決方案?我只要照著山寨,老闆跟客戶都不會來質疑我」,抱歉我沒有答案,我相信這不是技術問題,您絕對有辦法把 Modbus 轉成 AWS 接受的格式拋上去(資料同步可能會是個技術問題),或是做個 proxy 串接。

      至於有沒有違反 Modbus spec?這樣說好了,Modbus 是 20-30 年前就定下來的通訊協議,您說20-30年前有可能未卜先知今日會有 IoT or AWS?而且市面上早有一堆 Modbus to Ethernet/IP,Modbus to BACnet,Modbus to SNMP...一堆把Modbus轉成XXX的gateway,照這樣說來連上AWS又何錯之有?

      所以我想真正的問題應該是...「要怎麼說服老闆/主管/客戶,我的Modbus to AWS方案不需要什麼維護成本,就能解決客戶的問題?」

      刪除