多平台下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體系結構實例

 

每種設備(PLCHMI、控制面板、驅動程序、動作控制、輸入/輸出設備)都能使用 MODBUS協議來啟動遠程操作。

在基於串行鏈路和以太 TCP/IP 網絡的 MODBUS 上可以進行相同通信。

一些網關允許在幾種使用 MODBUS 協議的總線或網絡之間進行通信。

1.4.協議描述

MODBUS 協議定義了一個與基礎通信層無關的簡單協議數據單元(PDU) 。特定總線或網絡上的 MODBUS 協議映射能夠在應用數據單元(ADU)上引入一些附加域。

 

啟動 MODBUS 事務處理的客戶機創建 MODBUS 應用數據單元。 功能碼向服務器指示將執行哪種操作。

MODBUS 協議建立了客戶機啟動的請求格式。

用一個字節編碼 MODBUS 數據單元的功能碼域。有效的碼字范圍是十進制 1-255128-255 為異常響應保留) 。當從客戶機向服務器設備發送報文時,功能碼域通知服務器執行哪種操作。

向一些功能碼加入子功能碼來定義多項操作。

從客戶機向服務器設備發送的報文數據域包括附加信息,服務器使用這個信息執行功能碼定義的操作。這個域還包括離散項目和寄存器地址、處理的項目數量以及域中的實際數據字節數。

在某種請求中,數據域可以是不存在的(0 長度) ,在此情況下服務器不需要任何附加信息。功能碼僅說明操作。

如果在一個正確接收的 MODBUS ADU 中,不出現與請求 MODBUS 功能有關的差錯,那么服務器至客戶機的響應數據域包括請求數據。如果出現與請求 MODBUS 功能有關的差錯,那么域包括一個異常碼,服務器應用能夠使用這個域確定下一個執行的操作。

例如, 客戶機能夠讀一組離散量輸出或輸入的開/關狀態, 或者客戶機能夠讀/寫一組寄存器的數據內容。

當服務器對客戶機響應時,它使用功能碼域來指示正常(無差錯)響應或者出現某種差錯(稱為異常響應) 。對於一個正常響應來說,服務器僅對原始功能碼響應。

 

對於異常響應,服務器返回一個與原始功能碼等同的碼,設置該原始功能碼的最高有效位為邏輯 1

 

  注釋:需要管理超時,以便明確地等待可能不會出現的應答。

串行鏈路上第一個MODBUS執行的長度約束限制了MODBUS PDU大小 (最大RS485ADU=256字節) 。

因此, 對串行鏈路通信來說,MODBUS PDU=256-服務器地址(1 字節)-CRC2 字節)=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協議庫,越來越有必要。

設計架構圖:

 

硬件結構設計:

 

在我們進行軟件設計的同時,也同步進行硬件的設計,但是一些前段設備,我們都是從外面的產家進行購買的,包括氣體傳感器(如COCH4等)、電量采集、流量采集(水流、氣體等)的采集,控制一類的主要有燈光控制、門禁、水泵等。同時,如果有相關的同行,或者產家也可以和我聯系,我們正在進行采購測試的,如果合適的話,我們也可以建立長期的合作伙伴。

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;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM