1.背景 1.1.範圍 MODBUS 是 OSI 模型第 7 層上的應用層報文傳輸協議, 它在連接至不同類型匯流排或網路的設備之間提供客戶機/伺服器通信。 自從 1979 年出現工業串列鏈路的事實標準以來, MODBUS 使成千上萬的自動化設備能夠通信。 目前,繼續增加對簡單而雅觀的 MODBUS 結 ...
1.背景
1.1.範圍
MODBUS 是 OSI 模型第 7 層上的應用層報文傳輸協議, 它在連接至不同類型匯流排或網路的設備之間提供客戶機/伺服器通信。
自從 1979 年出現工業串列鏈路的事實標準以來, MODBUS 使成千上萬的自動化設備能夠通信。
目前,繼續增加對簡單而雅觀的 MODBUS 結構支持。互聯網組織能夠使 TCP/IP 棧上的保留系統埠 502 訪問 MODBUS。
MODBUS 是一個請求/應答協議,並且提供功能碼規定的服務。MODBUS 功能碼是 MODBUS請求/應答 PDU 的元素。
1.2.縮略語
ADU 應用數據單元
HDLC 高級數據鏈路控制
HMI 人機界面
IETF 網際網路工程工作組
I/O 輸入/輸出設備
IP 互連網協議
MAC 介質訪問控制
MB MODBUS 協議
MBAP MODBUS應用協議
PDU 協議數據單元
PLC 可編程邏輯控制器
TCP 傳輸控制協議
1.3.MODBUS體繫結構實例
每種設備(PLC、HMI、控制面板、驅動程式、動作控制、輸入/輸出設備)都能使用 MODBUS協議來啟動遠程操作。
在基於串列鏈路和以太 TCP/IP 網路的 MODBUS 上可以進行相同通信。
一些網關允許在幾種使用 MODBUS 協議的匯流排或網路之間進行通信。
1.4.協議描述
MODBUS 協議定義了一個與基礎通信層無關的簡單協議數據單元(PDU) 。特定匯流排或網路上的 MODBUS 協議映射能夠在應用數據單元(ADU)上引入一些附加域。
啟動 MODBUS 事務處理的客戶機創建 MODBUS 應用數據單元。 功能碼向伺服器指示將執行哪種操作。
MODBUS 協議建立了客戶機啟動的請求格式。
用一個位元組編碼 MODBUS 數據單元的功能碼域。有效的碼字範圍是十進位 1-255(128-255 為異常響應保留) 。當從客戶機向伺服器設備發送報文時,功能碼域通知伺服器執行哪種操作。
向一些功能碼加入子功能碼來定義多項操作。
從客戶機向伺服器設備發送的報文數據域包括附加信息,伺服器使用這個信息執行功能碼定義的操作。這個域還包括離散項目和寄存器地址、處理的項目數量以及域中的實際數據位元組數。
在某種請求中,數據域可以是不存在的(0 長度) ,在此情況下伺服器不需要任何附加信息。功能碼僅說明操作。
如果在一個正確接收的 MODBUS ADU 中,不出現與請求 MODBUS 功能有關的差錯,那麼伺服器至客戶機的響應數據域包括請求數據。如果出現與請求 MODBUS 功能有關的差錯,那麼域包括一個異常碼,伺服器應用能夠使用這個域確定下一個執行的操作。
例如, 客戶機能夠讀一組離散量輸出或輸入的開/關狀態, 或者客戶機能夠讀/寫一組寄存器的數據內容。
當伺服器對客戶機響應時,它使用功能碼域來指示正常(無差錯)響應或者出現某種差錯(稱為異常響應) 。對於一個正常響應來說,伺服器僅對原始功能碼響應。
對於異常響應,伺服器返回一個與原始功能碼等同的碼,設置該原始功能碼的最高有效位為邏輯 1。
註釋:需要管理超時,以便明確地等待可能不會出現的應答。
串列鏈路上第一個MODBUS執行的長度約束限制了MODBUS PDU大小 (最大RS485ADU=256位元組) 。
因此, 對串列鏈路通信來說,MODBUS PDU=256-伺服器地址(1 位元組)-CRC(2 位元組)=253位元組。
從而:
RS232 / RS485 ADU = 253 位元組+伺服器地址(1 byte) + CRC (2 位元組) = 256 位元組。
TCP MODBUS ADU = 249 位元組+ MBAP (7 位元組) = 256 位元組。
MODBUS 協議定義了三種 PDU。它們是:
MODBUS 請求 ,modbus_request
MODBUS 響應 ,modbus_reply
MODBUS 異常響應 ,modbus_reply_exception
1.5.數據模型
MODBUS 以一系列具有不同特征表格上的數據模型為基礎。四個基本表格為:
基本表格 |
對象類型 |
訪問類型 |
內容 |
離散量輸入 |
單個比特 |
只讀 |
I/O 系統提供這種類型數據 |
線圈 |
單個比特 |
讀寫 |
通過應用程式改變這種類型數據 |
輸入寄存器 |
16-比特字 |
只讀 |
I/O 系統提供這種類型數據 |
保持寄存器 |
16-比特字 |
讀寫 |
通過應用程式改變這種類型數據 |
1.6.設計背景
因為公司發展需要需要研發一個基於ARM9晶元的中央控制器(如下:設計架構圖和硬體設計圖),可以用來控制前端設備,並與雲平臺和用戶進行交互。其中一個重要的功能就是要求設備設備能採集前端設備(表示在控制器之前的所有設計,就是前段設備)的信息,同時也可以對前端設備採集來的信息進行反饋控制。經過市場研究和調查,現在採集設備485通信用的比較多,而且大多設備都支持MODBUS通信協議,因此開發一個Modbus協議庫,越來越有必要。
設計架構圖:
硬體結構設計:
在我們進行軟體設計的同時,也同步進行硬體的設計,但是一些前段設備,我們都是從外面的產家進行購買的,包括氣體感測器(如CO、CH4等)、電量採集、流量採集(水流、氣體等)的採集,控制一類的主要有燈光控制、門禁、水泵等。同時,如果有相關的同行,或者產家也可以和我聯繫,我們正在進行採購測試的,如果合適的話,我們也可以建立長期的合作伙伴。
2.功能碼
2.1.功能碼分類
有三類 MODBUS 功能碼。它們是:
公共功能碼
- 是較好地被定義的功能碼,
- 保證是唯一的,
- MODBUS 組織可改變的,
- 公開證明的,
- 具有可用的一致性測試,
- MB IETF RFC 中證明的,
- 包含已被定義的公共指配功能碼和未來使用的未指配保留供功能碼。
用戶定義功能碼
- 有兩個用戶定義功能碼的定義範圍,即 65 至 72 和十進位 100 至 110。
- 用戶沒有 MODBUS 組織的任何批准就可以選擇和實現一個功能碼
- 不能保證被選功能碼的使用是唯一的。
- 如果用戶要重新設置功能作為一個公共功能碼,那麼用戶必須啟動 RFC,以便將改變引入
- 公共分類中,並且指配一個新的公共功能碼。
保留功能碼
- 一些公司對傳統產品通常使用的功能碼,並且對公共使用是無效的功能碼。
2.2.公共功能碼定義
3.MODBUS通信模塊設計
3.1.模塊概述
Modbus通信模塊是多功能控制器中必不可少的一個功能,有了它才能使外部設備(如除濕裝置、熒光測溫、溫濕度檢測、六氟化硫檢測)與COM控制器的進行數據傳輸、遠程式控制制。因此Modbus通信協議的地位自然不言而喻。
3.2.設計目標
實現對外設數據的讀取和控制功能。
3.3.設計原則
儘量做到模塊的分層設計。
3.4.運行環境
操作系統:Linux
3.5.模塊結構設計
4.模塊功能設計
4.1.發送組包功能設計
4.2.接收解包功能設計
4.3.串口管理模塊設計
4.3.1.電腦串口的引腳說明
序號 |
信號名稱 |
符號 |
流向 |
功能 |
2 |
發送數據 |
TXD |
DTE→DCE |
DTE發送串列數據 |
3 |
接收數據 |
RXD |
DTE←DCE |
DTE 接收串列數據 |
4 |
請求發送 |
RTS |
DTE→DCE |
DTE 請求 DCE 將線路切換到發送方式 |
5 |
允許發送 |
CTS |
DTE←DCE |
DCE 告訴 DTE 線路已接通可以發送數據 |
6 |
數據設備準備好 |
DSR |
DTE←DCE |
DCE 準備好 |
7 |
信號地 |
|
|
信號公共地 |
8 |
載波檢測 |
DCD |
DTE←DCE |
表示 DCE 接收到遠程載波 |
20 |
數據終端準備好 |
DTR |
DTE→DCE |
DTE 準備好 |
22 |
振鈴指示 |
RI |
DTE←DCE |
表示 DCE 與線路接通,出現振鈴 |
4.3.2.串口操作的頭文件定義
#include <stdio.h> /*標準輸入輸出定義*/
#include <stdlib.h> /*標準函數庫定義*/
#include <unistd.h> /*Unix 標準函數定義*/
#include <sys/types.h> /*數據類型,比如一些XXX_t的那種*/
#include<sys/stat.h> /*定義了一些返回值的結構,沒看明白*/
#include<fcntl.h> /*文件控制定義*/
#include<termios.h> /*PPSIX 終端控制定義*/
#include<errno.h> /*錯誤號定義*/
4.3.3、串口設置
(1)、串口文件位於/dev目錄下,而且以tty開飛的,
其中:串口一 為 /dev/ttyS0,串口二 為 /dev/ttyS1,等等。其中/dev/ttyUSB* 表示USB轉串口。如:
(2)、串口的打開和設置
打開串口是通過使用標準的文件打開函數操作:
int fd; /*以讀寫方式打開串口*/ fd = open( "/dev/ttyS0", O_RDWR); if (-1 == fd){ /* 不能打開串口一*/ MFS_LOG_TRACE_ERR(" 提示錯誤!"); }
(3)、設置串口
最基本的設置串口包括波特率設置,效驗位和停止位設置,串口的設置主要是設置 struct termios 結構體的各成員值。
struct termio { unsigned short c_iflag; /* 輸入模式標誌 */ unsigned short c_oflag; /* 輸出模式標誌 */ unsigned short c_cflag; /* 控制模式標誌*/ unsigned short c_lflag; /* local mode flags */ unsigned char c_line; /* line discipline */ unsigned char c_cc[NCC]; /* control characters */ };
(4)、串口的讀寫
如果不是開發終端之類的,只是串口傳輸數據,而不需要串口來處理,那麼使用原始模式(Raw Mode)方式來通訊。
發送數據:
char buffer[1024]; int Length; int nByte; nByte = write(fd, buffer ,Length)
讀取串口數據:
使用文件操作read函數讀取,如果設置為原始模式(Raw Mode)傳輸數據,那麼read函數返回的字元數是實際串口收到的字元數。
可以使用操作文件的函數來實現非同步讀取,如fcntl,或者select等來操作。
char buff[1024]; int Len; int readByte = read(fd,buff,Len); 關閉串口: 關閉串口就是關閉文件。 close(fd);
4.4.介面設計
Modbus通信協議設計:
/************************外部介面************************************/ /*發送組包*/ /*參數說明: Modbus_t *ctx : 操作設備的簡要信息 modbus_msg *msg:modbus消息結構體,指需要進行打包或解包的信息 */ int modbus_pack(modbus_t *ctx,msg_src *src ,modbus_msg *msg);//pack *msg); /*參數說明: Modbus_t *ctx : 操作設備的簡要信息 unsigned int *src:數據的目標地址 modbus_msg *msg:modbus消息結構體,指需要進行打包或解包的信息 */ Int modbus_unpack(modbus_t *ctx,msg_src* req,msg_src* rsp,resolve_src* dest); /*modbus上下文信息結構體*/ typedef struct modbus_t{ modbus_type_t type; //modbus的通信類型,rtu、ascii、tcp等 int slave; //客戶端地址 int *s; //表示實例化之後的串口編號 unsigned int devicecode; //設備編碼 unsigned int functiontype; //功能類別的編碼 struct timeval timeout; //延時 char *devicename; //設備名稱 modbus_error_recovery_mode error_recovery; //錯誤的恢復模式 int debug; };
說明:設備類別編碼優先順序大於功能類別編碼,解決部分設備可能由於更換產家等原因導致功能相同,但是數據協議不同的情況
/*Modbus消息結構體*/ typedef struct modbus_msg{
uint8_t function_code; //modbus的功能碼 int start_addr; //數據的起始地址 int data_length; //數據長度(數據個數) int write_data; //寫入數據的值 uint8_t *s_dest; //small dest uint16_t *dest; //線圈、離散量數據 uint16_t *regisdate; //寄存器操作的數據 }
/*********************內部介面**************************************/
//源消息結構體 typedef struct _modbus_src_t { uint8_t *msg_src; //數組的地址 int msg_len; //數組的長度 }msg_src; typedef enum { MODBUS_ERROR_RECOVERY_NONE = 0, MODBUS_ERROR_RECOVERY_LINK = (1<<1), MODBUS_ERROR_RECOVERY_PROTOCOL = (1<<2) } modbus_error_recovery_mode;
串口管理模塊設計:
/*************************介面的設計*************************/ typedef struct _dts { serial_mode serial_mode; //串口的通信類型,RS485、RS232等 int s; //表示實例化之後的串口編號 unsigned int devicecode; //設備編碼 struct timeval timeout; //延時 char *devicename; //設備名稱 int error_recovery; //錯誤的恢復模式 int debug; void *backend_data; }dts_t,dts; (命名方式:)device to seial dts* serial_set_new(const char *device, int baud, char parity, int data_bit, int stop_bit,struct timeval timeout); //串口的結構設計 typedef struct _serial { /* Device: "/dev/ttyS0", "/dev/ttyUSB0" or "/dev/tty.USA19*" on Mac OS X. */ char *device; /* Bauds: 9600, 19200, 57600, 115200, etc */ int baud; /* Data bit */ uint8_t data_bit; /* Stop bit */ uint8_t stop_bit; /* Parity: 'N', 'O', 'E' */ char parity; #if defined(_WIN32) struct win32_ser w_ser; DCB old_dcb; #else /* Save old termios settings */ struct termios old_tios; #endif #if HAVE_DECL_TIOCSRS485 int serial_mode; #endif #if HAVE_DECL_TIOCM_RTS int rts; int rts_delay; int onebyte_time; void (*set_rts) (dts *ctx, int on); #endif /* To handle many slaves on the same link */ int confirmation_to_ignore; } serial_t; //串口的工作模式 typedef enum _serial_mode { SERIAL_RS232=0, SERIAL_RS485 }serial_mode;