0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

第6章_libmodbus使用

嵌入式Linux那些事 ? 來源:嵌入式Linux那些事 ? 作者:嵌入式Linux那些事 ? 2024-06-29 14:36 ? 次閱讀

第6章 libmodbus使用

6.1 libmodbus開發(fā)庫

6.1.1 功能概要

libmodbus是一個免費的跨平臺支持RTU和TCP的Modbus庫,遵循LGPL V2.1+協(xié)議。libmodbus支持Linux、Mac Os X、FreeBSD、QNX和Windows等操作系統(tǒng)。libmodbus可以向符合Modbus協(xié)議的設(shè)備發(fā)送和接收數(shù)據(jù),并支持通過串口或者TCP網(wǎng)絡(luò)進行連接。

作為一個開源項目,libmodbus庫還處于開發(fā)測試階段,代碼量還不十分龐大,文檔和注釋也不夠全面,本章通過對libmodbus源代碼的閱讀過程,一方面可以進一步理解Modbus協(xié)議,同時也可以學(xué)習(xí)一個好的開源項目的代碼組織及開發(fā)過程。 libmodbus的官方網(wǎng)站為 http://libmodbus.org/, 可以從 http://libmodbus.org/download/ 下載源代碼。作為開源軟件,還可以從GitHub網(wǎng)站獲取最新版本的代碼GitHub: https://github.com/stephane/libmodbus.git

6.1.2 源碼獲取

libmodbus的源碼不斷更新,本教程選擇版本v3.1.10。打開https://github.com/stephane/libmodbus/tags ,如下圖下載:

img

本源碼也放在網(wǎng)盤中如下目錄里:

img

解壓后,簡單查看源代碼根目錄的構(gòu)成:

  • doc目錄: libmodbus庫的各API接口說明文檔。
  • m4目錄: 存放GNU m4文件,在這里對理解代碼沒有意義,可忽略。
  • src目錄: 全部libmodbus源文件。
  • tests目錄: 包含自帶的測試代碼 其他文件對理解源代碼關(guān)系不大,可以暫時忽略

圖6-2解壓libmodbus源代碼:

img

進一步展開src代碼目錄,如圖6-3所示:

圖6-3libmodbus源碼構(gòu)成:

img

各文件作用如下:

  • win32: 定義在Windows下使用Visual Studio編譯時的項目文件和工程文件以及相關(guān)配置選項等。其中,modbus-9.sln默認使用Visual Studio 2008。
  • Makefile.am: Makefile.am是Linux下AutoTool編譯時讀取相關(guān)編譯參數(shù)的配置文件,用于生成Makefile文件,因為用于Linux下開發(fā),所以在這里暫時忽略
  • modbus.c: 核心文件,實現(xiàn)Modbus協(xié)議層,定義共通的Modbus消息發(fā)送和接收函數(shù)各功能碼對應(yīng)的函數(shù)。
  • modbus.h: libmodbus對外暴露的接口API頭文件。
  • modbus-data.c: 數(shù)據(jù)處理的共通函數(shù),包括大小端相關(guān)的字節(jié)、位交換等函數(shù)
  • modbus-private.h: libmodbus內(nèi)部使用的數(shù)據(jù)結(jié)構(gòu)和函數(shù)定義。
  • modbus-rtu.c: 通信層實現(xiàn),RTU模式相關(guān)的函數(shù)定義,主要是串口的設(shè)置、連接及消息的發(fā)送和接收等。
  • modbus-rtu.h: RTU模式對外提供的各API定義
  • modbus-rtu-private.h: RTU模式的私有定義。
  • modbus-tcp.c: 通信層實現(xiàn),TCP模式下相關(guān)的函數(shù)定義,主要包括TCP/IP網(wǎng)絡(luò)的設(shè)置連接、消息的發(fā)送和接收等。
  • modbus-tcp.h: 定義TCP模式對外提供的各API定義
  • modbus-tcp-private.h: TCP模式的私有定義。
  • modbus-version.h.in: 版本定義文件。

6.1.3 源碼閱讀

對比比較復(fù)雜的源碼,使用sourceinsight可以很方便地閱讀、分析、編輯源碼。

1. 新建工程

運行source Insight,點擊菜單“Project->New Project”,如下圖所示:

img

設(shè)置工程名及工程數(shù)據(jù)目錄:在彈出的New Project對話框中設(shè)置“New project name”(項目的名稱),然后設(shè)置Where do you want to store the project data file? (項目文件保存位置),點擊Browse按鈕選擇源碼的目錄即可,如下圖:

img

指定源碼目錄:在上圖界面中點擊OK后,彈出如下圖所示窗口,填入源碼路徑:

img

添加源碼:在新彈出的對話框中,點擊“Add”或“Add All”?!癆dd”是手動選擇需要添加的文件,而“Add All”是添加所有文件。我們使用“Add All”,在彈出的提示框中選中“Recursively add lower sub-directories”(遞歸添加下級的子目錄)并點擊OK。同樣的Remove File,Remove All是移除單個文件或者移除所有文件,如下圖所示:

img

添加文件完成后會彈出下面窗口,點擊“確定”即可:

img

此時界面會返回到主界面,如下圖所示,點擊“Close”:

img

2. 同步文件

同步文件的意思是讓Source Insight去解析源碼,生成數(shù)據(jù)庫,這樣有助于以后閱讀源碼。比如點擊某個函數(shù)時就可以飛快地跳到它定義的地方。

先點擊菜單“Project->Synchronize Files”,如圖 2.23所示:

img

在彈出的對話框中 選中“Force all files to be re-parsed”(強制解析所有文件),并點擊“Start”按鈕開始同步,如下圖所示:

img

3.打開工程

前面建議工程后,就會自動打開了工程。如果下次你想打開工程,啟動Souce Insight后,點擊菜單“Project -> Open Porject”就可以在一個列表中選擇以前建立的工程,如下圖所示:

img

4. 操作示例

在工程中打開文件:點擊"P"圖標打開文件列表,雙擊文件打開文件,也可以輸入文件名查找文件,如下圖所示:

img

在文件中查看函數(shù)或變量的定義:打開文件后,按住ctrl鍵的同時,用鼠標點擊函數(shù)、變量,就會跳到定義它的位置,如下圖所示:

img

查找函數(shù)或變量的引用:右鍵點擊函數(shù)或變量,彈出對話框選擇“Lookup Reference”;或者雙擊函數(shù)后,使用快捷鍵"ctrl+/"來查找引用,如下圖:

img

5. 快捷鍵

快捷鍵說明
Alt + ,后退
Alt + .前進
F8高亮選中的字符
Ctrl+F查找
F3或Shift+F3往前查找
F4或Shift+F4往后查找

6.1.4 libmodbus與應(yīng)用程序的關(guān)系

libmodbus是一個免費的跨平臺支持RTU和TCP的Modbus開發(fā)庫,借助于libmodbus發(fā)庫能夠非常方便地建立自己的應(yīng)用程序或者將Modbus通信協(xié)議嵌入單體設(shè)備libmodbus開發(fā)庫與應(yīng)用程序的基本關(guān)系如圖6-4所示。

圖6-4應(yīng)用程序與libmodbus的關(guān)系:

img

在對libmodbus的接口及代碼框架簡單了解之后,不妨再深入細節(jié)一探究竟,看看libmodbus都實現(xiàn)了哪些基礎(chǔ)功能,以及源代碼中對Modbus各功能碼和消息頓是如何包裝的。具體內(nèi)容請參看下一章。

6.2 libmodbus源代碼解析

libmodbus作為一個優(yōu)秀且免費開源的跨平臺支持RTU和TCP模式的Modbus開發(fā)庫,非常值得大家借鑒和學(xué)習(xí)。本章對libmodbus源代碼進行閱讀和分析。

6.2.1 核心函數(shù)

以Modbus RTU協(xié)議為例,主設(shè)備、從設(shè)備初始化后:

  • 主設(shè)備就可以啟動請求,即“發(fā)送消息”給從設(shè)備
  • 從設(shè)備接收到請求后構(gòu)造數(shù)據(jù),啟動響應(yīng)即“發(fā)送回復(fù)”
  • 主機收到響應(yīng)后,會“檢查響應(yīng)”

如下圖所示:

img

分析“l(fā)ibmodbus-3.1.10testsunit-test-client.c”、“l(fā)ibmodbus-3.1.10testsunit-test-server.c”,可以得到下面核心函數(shù)的使用過程:

img

6.2.2 框架分析與數(shù)據(jù)結(jié)構(gòu)

站在APP開發(fā)的角度來說,使用上一節(jié)里介紹的libmodbus函數(shù)即可。但是,數(shù)據(jù)的傳輸必定涉及到底層數(shù)據(jù)傳輸。所以,從數(shù)據(jù)的收發(fā)過程,可以把使用libmodbus的源碼分為3層:

  • APP:它知道要做什么,主設(shè)備要讀寫哪些寄存,從設(shè)備提供、接收什么數(shù)據(jù)
  • Modbus核心層:向上提供接口函數(shù),向下調(diào)用底層代碼構(gòu)造數(shù)據(jù)包并發(fā)送、接收數(shù)據(jù)包并解析
  • 后端(數(shù)據(jù)傳輸):進行硬件相關(guān)的數(shù)據(jù)封包與發(fā)送、接收與解包

img

對于核心層、后端,抽象出了如下結(jié)構(gòu)體:

img

核心層modbus_t結(jié)構(gòu)體的成員含義如下:

成員含義
int slave;從站設(shè)備地址
int s;RTU下是串口句柄,TCP下是Socket
int debug;是否啟動Debug模式(打印調(diào)試信息
int error_recovery;錯誤恢復(fù)模式:MODBUS_ERROR_RECOVERY_NONE:由APP處理錯誤MODBUS_ERROR_RECOVERY_LINK:如果有連接錯誤,則重連MODBUS_ERROR_RECOVERY_PROTOCOL:如果數(shù)據(jù)不符合協(xié)議要求,則清空所有數(shù)據(jù)
int quirks;一些奇怪的功能,比如:MODBUS_QUIRK_MAX_SLAVE:從站地址最大值可以到達255MODBUS_QUIRK_REPLY_TO_BROADCAST:回應(yīng)廣播包
struct timeval response_timeout;等待回應(yīng)的超時時間,默認是0.5S
struct timeval byte_timeout;接收一個字節(jié)的超時時間,默認是0.5S
struct timeval indication_timeout;等待請求的超時時間
const modbus_backend_t *backend;硬件傳輸層的結(jié)構(gòu)體
void *backend_data;硬件傳輸層的私有數(shù)據(jù)

后端modbus_backend_t結(jié)構(gòu)體的成員含義如下:

成員含義
unsigned int backend_type;后端類型,是RTU還是TCP
unsigned int header_length;頭部長度,比如RTU數(shù)據(jù)包前面需要有1字節(jié)的設(shè)備地址,頭部長度就是1
unsigned int checksum_length;校驗碼長度,RTU的校驗碼是2字節(jié)
unsigned int max_adu_length;ADU(數(shù)據(jù)包)最大長度
set_slave設(shè)置從站地址
build_request_basis設(shè)置RTU請求包的基本數(shù)據(jù),這些數(shù)據(jù)的格式是一樣的,比如req[0]是從設(shè)備地址,req[1]是功能碼,req[2]和req[3]是寄存器地址,req[4]和req[5]是寄存器數(shù)量
build_response_basis設(shè)置RTU回應(yīng)包的基本數(shù)據(jù),這些數(shù)據(jù)的格式是一樣的,比如req[0]是從設(shè)備地址,req[1]是功能碼
prepare_response_tid生產(chǎn)傳輸標識TID,在TCP中使用
send_msg_pre發(fā)送消息前的準備工作,對于RTU是填充CRC檢驗碼,對于TCP是填充頭部的Length
send發(fā)送數(shù)據(jù)包
receive接收數(shù)據(jù)包
recv接收原始數(shù)據(jù),receive會調(diào)用recv得到原始數(shù)據(jù)然后解析出數(shù)據(jù)包
check_integrity檢查數(shù)據(jù)包的完整性
pre_check_confirmation檢查響應(yīng)數(shù)據(jù)包是否有效時,先執(zhí)行pre_check_confirmation做一些簡單的檢查
connect硬件相關(guān)的連接,對于RTU就是打開串口、設(shè)置串口波特率等;對于TCP則是連接對端
is_connected判斷是否已經(jīng)連接
close關(guān)閉連接
flush清空接收到的、未處理的數(shù)據(jù)
select阻塞一段時間以等待數(shù)據(jù)
free釋放分配的modbus_t等結(jié)構(gòu)體

6.2.3 情景分析

以“modbus_write_bits”函數(shù)為例,分析下圖的執(zhí)行流程:

img

1. 初始化

2. 主設(shè)備發(fā)送請求

3. 從設(shè)備接收請求

4.從設(shè)備回應(yīng)

6.2.4 常用接口函數(shù)

下面分析 libmodbus開發(fā)庫提供的所有接口API函數(shù)。其主要對象文括 modbus.h 和 modbus.c ,接口函數(shù)大致可分為3類,以下分別進行介紹。

1. 各類輔助接口函數(shù)

MODBUS_API int modbus_set_slave(modbus t * ctx,int slave)

此函數(shù)的功能是設(shè)置從站地址,但是由于傳輸方式不同而意義稍有不同。

  • RTU模式 :

如果 libmodbus應(yīng)用于 主站設(shè)備端,則相當于定義 遠端設(shè)備ID ;如果libmodbus應(yīng)用于從站設(shè)備端 ,則相當于定義 自身設(shè)備 ID ;在 RTU 模式下參數(shù) slave 取值范圍為 0~247 ,其中 0(MODBUS_BROADCAST_ADDRESS) 為廣播地址。

  • TCP模式:

通常,TCP 模式下此函數(shù)不需要使用。在某些特殊場合,例如串行 Modbus設(shè)備轉(zhuǎn)換為 TCP模式傳輸?shù)那闆r下,此函數(shù)才被使用。此種情況下,參數(shù) slave取值范圍為 0~247 ,0 為廣播地址;如果不進行設(shè)置,則 TCP 模式下采用默認值 MODBUS TCP SLAVE(OXFF) 。

下面的代碼以 RTU模式、主設(shè)備(MASTER)端為例:

modbus_t * ctx;

ctx=modbus_new_rtu("COM4",115200,'N',8,1);

if (ctx ==NULL)

{

  fprintf(stderr"Unable to create the libmodbus contextn");

  return -1;

}

rc =modbus_set_slave(ctx,YOUR DEVICE ID);

if (rc==-1)

{

  fprintf(stderr,"Invalid slave IDn");

  modbus free(ctx);

  return -1;

}

if (modbus connect(ctx)==-1)

{

  fprintf(stderr,"Connection failed:sn",modbus strerror(errno));

  modbus free(ctx);

  return -1;

}

MODBUS_APIintmodbus_set_error_recovery(modbus_t*ctx,modbus_error_recovery_mode error_recovery):

此函數(shù)用于在連接失敗或者傳輸異常的情況下,設(shè)置錯誤恢復(fù)模式。有 3種錯誤恢復(fù)模式可選。

typedef enum

{

  MODBUS_ERROR_RECOVERY_NONE        =0,             //不恢復(fù)

  MODBUS_ERROR_RECOVERY_LINK        =(1< 1),           //鏈路層恢復(fù)

  MODBUS_ERROR_RECOVERY_PROTOCOL      =(1< 2)           //協(xié)議層恢復(fù)

}modbus error recovery mode;

默認情況下,設(shè)置為 MODBUS_ERROR_RECOVERY_NONE ,由應(yīng)用程序自身處理錯誤;若設(shè)置為 MODBUS_ERROR_RECOVERY_LINK ,則經(jīng)過一段延時 libmodbus 內(nèi)部自動嘗試進行斷開/連接;若設(shè)置為 MODBUS_ERROR_RECOVERY_PROTOCOL ,則在傳輸數(shù)據(jù) CRC 錯誤或功能碼錯誤的情況下,傳輸會進入延時狀態(tài),同時數(shù)據(jù)直接被清除。在 SLAVE/SERVER 端,不推薦使用此函數(shù)。

基本用法舉例:

modbus_set_error_recovery(ctx,MODBUS_ERROR_RECOVERY_LINK|MODBUS_ERROR_RECOVERY_PROTOCOL);

MODBUS_API int modbus_set_socket(modbus t * ctx,int s)

此函數(shù)設(shè)置當前 SOCKET 或串口句柄要用于多客戶端連接到單一服務(wù)器的場合。簡單用法舉例如下,后續(xù)介紹函數(shù) modbus_tcp_listen() 時將會進一步介紹相關(guān)用法。

#define NB_CONNECTION 5

modbus_t * ctx;

ctx=modbus_new_tcp("127.0.0.1", 1502)

server_socket = modbus_tcp_listen(ctx,NB_CONNECTION);

FD_ZERO(&rdset);

FD_SET(server_socket,&rdset);

/* ... */

if (FD_ISSET(master_socket,&rdset))

{

  modbus_set_socket(ctx,master_socket);

  rc =modbus_receive(ctx,query);

  if(rc!=-1)

  {

  modbus_reply(ctx,query, rc,mb_mapping);

  }

}

MODBUS_API int modbus_get_response_timeout (modbus_t * ctx, uint32_t * to_sec, uint32_t * to_usec);

MODBUS_API int modbus_set_response_timeout (modbus_t * ctx, uint32_t * to_sec, uint32_t * to_usec);

用于獲取或設(shè)置響應(yīng)超時,注意時間單位分別是秒和微秒。

MODBUS_API int modbus_get_byte_timeout (modbus_t * ctx, uint32_t * to_sec,uint32_t * to_usec);

MODBUS_API int modbus_set_byte_timeout (modbus_t * ctx, uint32_t * to_sec,uint32_t * to_usec);

用于獲取或設(shè)置連續(xù)字節(jié)之間的超時時間,注意時間單位分別是秒和微秒。

MODBUS_API intmodbus_get_header_length (modbus_t * ctx);

獲取報文頭長度。

MODBUS_API int modbus_connect (modbus_t * ctx);

此函數(shù)用于主站設(shè)備與從站設(shè)備建立連接。

在 RTU 模式下,它實質(zhì)調(diào)用了文件 modbus_rtu.c 中的函數(shù) static int modbus_rtu_connect (modbus_t * ctx) ;在此函數(shù)中進行了串口波特率校驗位、數(shù)據(jù)位、停止位等的設(shè)置。

在 TCP 模式下,modbus_connect() 調(diào)用了文件 modbus_tcp.c 中的函數(shù) static int_modbus_tcp_connect (modbus_t * ctx ) ;在函數(shù) _modbus_tcp_connect() 中,對 TCP/IP 各參數(shù)進行了設(shè)置和連接。

MODBUS_API void modbus_close (modbus_t * ctx);

關(guān)閉 Modbus 連接。在應(yīng)用程序結(jié)束之前,一定記得調(diào)用此函數(shù)關(guān)閉連接在 RTU 模式下,實質(zhì)是調(diào)用函數(shù) _modbus_rtu_close(modbus_t * ctx) 關(guān)閉串口句柄;在 TCP 模式下,實質(zhì)是調(diào)用函數(shù) _modbus_tcp_close(modbust * ctx) 關(guān)閉 Socket 句柄。

MODBUS_API void modbus_free (modbus_t * ctx);

釋放結(jié)構(gòu)體 modbus_t 占用的內(nèi)存。在應(yīng)用程序結(jié)束之前,一定記得調(diào)用此函數(shù)

MODBUS_API int modbus_set_debug (modbust * ctx, int flag);

此函數(shù)用于是否設(shè)置為DEBUG模式。

若參數(shù) flag 設(shè)置為TRUE,則進入 DEBUG模式。若設(shè)置為FALSE,則切換為非 DEBUG模式。在 DEBUG模式下所有通信數(shù)據(jù)將按十六進制方式顯示在屏幕上,以方便調(diào)試。

MODBUS_API const char * modbus_strerror (int errnum);

此函數(shù)用于獲取當前錯誤字符串。

2.各類Modbus功能接口函數(shù)

MODBUS_API int modbus_read_bits (modbus t * ctx, int addr, int nb, uint8_t * dest);

此函數(shù)對應(yīng)于功能碼 01(0x01) 讀取線圈/離散量輸出狀態(tài)(Read Coil Status/DOs),其中,所讀取的值存放于參數(shù) uint8_t * dest 指向的數(shù)組空間因此 dest 指向的空間必須足夠大,其大小至少為 nb * sizeof(uint8_t) 個字節(jié)。

用法舉例:

#define SERVER ID        1
#define ADDRESS START      0
#define ADDRESS END       99

modbus_t * ctx;

uint8_t * tab_rp_bits;
int rc;
int nb;

ctx=modbus_new_tcp("127.0.0.1",502);
modbus_set_debug(ctx,TRUE);
if (modbus_connect(ctx)==-1)

{
  fprintf(stderr,"Connection failed:%sn", modbus_strerror(errno));
  modbus free(ctx);
  return -1;
}

//申請存儲空間并初始化
int nb = ADDRESS_END - ADDRESS_START;
tab_rp_bits = (uint8_t * ) malloc (nb * sizeof(uint8_t));
memset(tab_rp_bits, 0, nb * sizeof(uint8_t));

//讀取一個線圈
int addr =1;
rc =modbus_read_bits(ctx,addr,1,tab_rp_bits);
if (rc !=1)
{
  printf("ERROR modbus_read_bits_single (%d)n", rc);
  printf("address =%dn", addr);
}

//讀取多個線圈

rc =modbus_read_bits(ctx,addr,nb,tab_rp_bits);
if (rc !=nb)
{
  printf("ERROR modbus_read_bitsn");
  printf("Address =%d,nb =%dn", addr, nb);
}

//釋放空間關(guān)閉連接

free(tab_rp_bits);
modbus_close(ctx);
modbus_free(ctx);

MODBUS_API int modbus_read_input_bits (modbus_t * ctx, int addr, int nb,uint8_t * dest);

此函數(shù)對應(yīng)于功能碼 02(0x02) 讀取離散量輸入值(Read Input Status/DIs),各參數(shù)的意義與用法,類似于函數(shù) modbus_read_bits() 。

MODBUS_API int modbus_read_registers (modbus_t * ctx, int addr, int nb,uint16_t * dest);

此函數(shù)對應(yīng)于功能碼 03(0x03) 讀取保持寄存器(Read Holding Register),其中,所讀取的值存放于參數(shù) uint16_t * dest 指向的數(shù)組空間因此 dest 指向的空間必須足夠大,其大小至少為 nb * sizeof(uint16_t) 個字節(jié)。

當讀取成功后,返回值為讀取的寄存器個數(shù);若讀取失敗,則返回-1。此函數(shù)調(diào)用依賴關(guān)系如下圖6-5所示。

用法舉例:

img

modbust * ctx;
uint16_t tab_reg[64];
int rc;
int i;

ctx=modbus_new_tcp("127.0.0.1",502);
if (modbusconnect(ctx)==-1)
{
  fprintf(stderr,"Connection failed:%sn", modbus_strerror(errno));
  modbus_free(ctx);
  return -1;
}

//從地址0開始連續(xù)讀取10個

rc =modbus_read_registers(ctx,0,10,tab_reg);
if (rc ==-1)
{
  fprintf(stderr,"%sn",modbus_strerror(errno));
  return -1;
}

for (i=0;i< rc;i++)
{
  printf("reg[%d]=%d(0x%X)n",i,tab_reg[i],tab_reg[i]);
}

modbus_close(ctx);
modbus_free(ctx);

MODBUS_API int modbus_read_input_registers (modbus_t * ctx,int addr, int nb, uint16_t * dest );

此函數(shù)對應(yīng)于功能碼 04(0x04) 讀取輸人寄存器(Read Iput Register),各參數(shù)的意義與用法,類似于函數(shù) modbus_read_registers() 。

此函數(shù)的調(diào)用依賴關(guān)系如下圖 6-6 所示。

圖6-6函數(shù) modbus_read input_registers()的調(diào)用依賴關(guān)系

img

MODBUS_API int modbus_write_bit (modbus_t * ctx, int coil_addr, int status):

該函數(shù)對應(yīng)于功能碼 05(0x05) 寫單個線圈或單個離散輸出(Force SingleCoil)。其中參數(shù) coil_addr 代表線圈地址;參數(shù) status 代表寫值取值只能是TRUE(1)或 FALSE(0) 。

MODBUS_API int modbus_write_register (modbus_t * ctx,int reg_addr, int value):

該函數(shù)對應(yīng)于功能碼 06(0x06) 寫單個保持寄存器(Preset Single Register)。

MODBUS_API int modbus_write_bits (modbus_t * ctx, int addr, int nb, const uint8_t * data):

該函數(shù)對應(yīng)于功能碼 15(0x0F) 寫多個線圈(Force Multiple Coils)

參數(shù) addr 代表寄存器起始地址,參數(shù) nb 表示線圈個數(shù),而參數(shù) const uint8_t * data 表示待寫入的數(shù)據(jù)塊。一般情況下,可以使用數(shù)組存儲寫入數(shù)據(jù),數(shù)組的各元素取值范圍只能是 TRUE(1)或 FALSE(0) 。

MODBUS_API int modbus_write_registers (modbus_t * ctx, int addr, int nb, const uint16_t * data):

該函數(shù)對應(yīng)于功能碼 16(0x10) 寫多個保持存器(Preset MultipleRegisters)

參數(shù) addr 代表寄存器起始地址,參數(shù) nb 表示存器的個數(shù)而參數(shù) const uint16_t * data 表示待寫人的數(shù)據(jù)塊。一般情況下,可以使用數(shù)組存儲寫入數(shù)據(jù)數(shù)組的各元素取值范圍是 0~0xFFFF 即數(shù)據(jù)類型 uint16_t 的取值范圍。

MODBUS_API int modbus_mask_registers (modbus_t * ctx, int addr, uint16_t and_mask, uint16_t or_mask ):

modbus_mask_write_register() 函數(shù)應(yīng)使用以下算法修改遠程設(shè)備地址“addr”處的保持寄存器的值:

新值 = (current value AND ‘a(chǎn)nd’) OR (‘or’ AND (NOT ‘a(chǎn)nd’)) 。

該功能使用 Modbus 功能代碼 0x16(掩碼單個寄存器)。

MODBUS_API int modbus_write_and_read_registers (mobus_t * ctx ,

int writer_addr,

int writer_nb,

const uint16_t * src,

int read_addr,

int read_nb,

uint16_t * dest);

modbus_write_and_read_registers() 函數(shù)應(yīng)將 write_nb 保持寄存器的內(nèi)容從數(shù)組 “src” 寫入遠程設(shè)備的地址 write_addr ,然后將 read_nb 保持寄存器的內(nèi)容讀取到遠程設(shè)備的地址 read_addr 。讀取結(jié)果作為字值(16 位)存儲在 dest 數(shù)組中。

必須注意分配足夠的內(nèi)存來存儲結(jié)果 dest (至少 nb * sizeof(uint16_t))。該功能使用 Modbus 功能代碼 0x17(寫/讀寄存器)。

MODBUS_API int modbus_report_slave_id (modbus_t * ctx, int max_dest, uint8_t * dest):

該函數(shù)對應(yīng)于功能碼 17(0x11) 報告從站ID。參數(shù) max_dest 代表最大的存儲空間,參數(shù) dest 用于存儲返回數(shù)據(jù)。返回數(shù)據(jù)可以包括如下內(nèi)容:從站 ID狀態(tài)值(0x00= OFF狀態(tài), 0xFF=ON狀態(tài)) 以及其他附加信息,具體的各參數(shù)意義由開發(fā)者指定。

用法舉例:

uint8_t tab_bytes[MODBUS_MAX_PDU_LENGTH];

...

rc =modbus_report_slave_id(ctx, MODBUS_MAX_PDU_LENGTH, tab_bytes);

if (rc >1)
{
  printf("Run Status Indicator: %sn",tab_bytes[1] ?"ON":"OFF");
}

3. 數(shù)據(jù)處理的相關(guān)函數(shù)或宏定義

在libmodbus開發(fā)庫中,為了方便數(shù)據(jù)處理在 modbus.h 文件中定義了一系列數(shù)據(jù)處理宏。

例如獲取數(shù)據(jù)的高低字節(jié)序宏定義:

#define MODBUS_GET_HIGH_BYTE (data) (((data) > >8) & 0xFF)
#define MODBUS_GET_LOW_BYTE (data) ((data) & 0xFF)

對于浮點數(shù)等多字節(jié)數(shù)據(jù)而言,由于存在字節(jié)序與大小端處理等的問題,所以輔助定義了一些特殊函數(shù):

MODBUS_API float modbus_get_float (const uint16_t * src);

MODBUS_API float modbus_get_float_abcd (const uint16_t * src);

MODBUS_API float modbus_get_float_dcba (const uint16_t * src);

MODBUS_API float modbus_get_float_badc (const uint16_t * src);

MODBUS_API float modbus_get_float_cdab (const uint16_t * src);

MODBUS_API void modbus_set_float (float f,uint16_t * dest);

MODBUS_API void modbus_set_float_abcd (float f,uint16_t * dest);

MODBUS_API void modbus_set_float_dcba (float f,uint16_t * dest);

MODBUS_API void modbus_set_float_badc (float f,uint16_t * dest);

MODBUS_API void modbus_set_float_cdab (float f,uint16_t * dest);

當然,可以參照 float 類型的處理方法,繼續(xù)定義其他多字節(jié)類型的數(shù)據(jù)例如int32_t、uint32_t、 int64_t、uint64_t 以及 double 類型的讀寫函數(shù)。

6.2.5 RTU/TCP關(guān)聯(lián)接口函數(shù)

在文件 modbus.h 的最后位置,有如下語句

#include "modbus-tcp.h"

#include "modbus-rtu.h"

可以發(fā)現(xiàn),除了 modbus.h 包含的接口函數(shù)之外,modbus-rtu.h 和 modbus-tcp.h 也包含了必要的接口函數(shù)。

1. RTU模式關(guān)聯(lián)函數(shù)

MODBUS_API modbus_t * modbus_new_rtu (const char * device, int baud, char parity, int data_bit, int stop_bit):

此函數(shù)的功能是創(chuàng)建一個 RTU 類型的 modbus_t 結(jié)構(gòu)體。參數(shù) const char * device 代表串口字符串,在 Windows 操作系統(tǒng)下形態(tài)如 “COMx” ,有一點需要注意的是,對于串口1串口9來說,,傳遞 “COM1”“COM9” 可以 成功 ,但是如果操作對象為 COM10及以上端口 ,則會出現(xiàn) 錯誤。

產(chǎn)生這種奇怪現(xiàn)象的原因是:微軟預(yù)定義的標準設(shè)備中含有 “COM1” “COM9” 。所以,“COM1” “COM9” 作為文件名傳遞給函數(shù)時操作系統(tǒng)會自動地將之解析為相應(yīng)的設(shè)備。但對于 COM10 及以上的串口,“COM10” 之類的文件名系統(tǒng)只視之為 一般意義上的文件,而非串行設(shè)備。為了增加對 COM10 及以上串行端口的支持,微軟規(guī)定,如果要訪問這樣的設(shè)備,應(yīng)使用這樣的文件名(以COM10 為例):. COM10。

所以,使用時在代碼中可以如此定義:.

const char * device = “.COM10”;

在Linux操作系統(tǒng)下可以使用”/dev/ttySo”或”/dev/ttyUSB0”等形式的字符串來表示。而參數(shù) int baud 表示串口波特率的設(shè)置值,例如:9600、19200、57600、115200等。

參數(shù)char parity 表示奇偶校驗位,取值范圍:

  • ‘N’:無奇偶校驗;
  • ‘E’:偶校驗;
  • ‘O’:奇校驗。

參數(shù) int data_bit 表示數(shù)據(jù)位的長度,取值范圍為 5、6、7和8。

參數(shù)int stop_bit 表示停止位長度,取值范圍為1或2。

用法舉例:

modbus t *ctx;

ctx=modbus_new_rtu("\.COM10",115200,'N',8,1);

if (ctx ==NULL)
{
  fprintf(stderr,"Unable to create the libmodbus contextn");
  return -1;
}

modbus_set_slave(ctx,SLAVE_DEVICE_ID);

if (modbus connect(ctx)==-1)
{
  fprintf(stderr,"Connection failed:%sn",modbus_strerror(errno));
  modbus_free(ctx);
  return -1;
}

MODBUS_API int modbus_rtu_set_serial_mode (modbus_t * ctx, int mode):

該函數(shù)用于設(shè)置串口為 MODBUS RTU RS232或MODBUSRTU_RS485模式,此函數(shù)只適用于 Linux 操作系統(tǒng)下。

MODBUS_API int modbus_rtu_set_rts (modbus_t * ctx, int mode)。

MODBUS_API int modbus_rtu_set_custom_rts (modbus_t * ctx, void ( * set_rts) (modbus_t * ctx, int on))。

MODBUS_API int modbus_rtu_set_rts_delay (modbus_t * ctx, int us)。

以上函數(shù)只適用于 Linux 操作系統(tǒng)下,RTS 即Request ToSend 的縮寫,具體的意義可通過網(wǎng)絡(luò)搜索,一般情況下,此類函數(shù)可忽略。

2. TCP模式關(guān)聯(lián)函數(shù)

*MODBUS_API modbus_t * modbus_new_tcp (const char ip_address, int port) 。

此函數(shù)的功能是創(chuàng)建一個TCP/IPv4 類型的modbus_t 結(jié)構(gòu)體。

參數(shù) const char * ip_address 為IP地址,port 表示遠端設(shè)備的端口號。

MODBUS_API int modbus_tcp_listen (modbus_t * ctx, int nb_connection)。

此函數(shù)創(chuàng)建并監(jiān)聽一個 TCP/IPv4 上的套接字。

參數(shù)int nb_connection 代表最大的監(jiān)聽數(shù)量,在調(diào)用此函數(shù)之前,必須首先調(diào)用modbus_new_tcp() 創(chuàng)建modbus_t結(jié)構(gòu)體。

MODBUS_API int modbus_tcp_accept (modbus_t * ctx,int * s)。

此函數(shù)接收一個 TCP/IPv4 類型的連接請求,如果成功將進入數(shù)據(jù)接收狀態(tài)。

6.3 libmodbus移植與使用

6.3.1 移植方法

以串口為例,libmodbus支持了windows系統(tǒng)、Linux系統(tǒng)。如果要在Freertos或者裸機上使用libmodbus,需要移植libmodbus里操作硬件的代碼。

根據(jù)下圖的層次,要移植libmodbus的“后端”,就是構(gòu)造自己的modbus_backend_t結(jié)構(gòu)體:

img

后端modbus_backend_t結(jié)構(gòu)體的成員含義如下:

成員含義
unsigned int backend_type;后端類型,是RTU還是TCP
unsigned int header_length;頭部長度,比如RTU數(shù)據(jù)包前面需要有1字節(jié)的設(shè)備地址,頭部長度就是1
unsigned int checksum_length;校驗碼長度,RTU的校驗碼是2字節(jié)
unsigned int max_adu_length;ADU(數(shù)據(jù)包)最大長度
set_slave設(shè)置從站地址
build_request_basis設(shè)置RTU請求包的基本數(shù)據(jù),這些數(shù)據(jù)的格式是一樣的,比如req[0]是從設(shè)備地址,req[1]是功能碼,req[2]和req[3]是寄存器地址,req[4]和req[5]是寄存器數(shù)量
build_response_basis設(shè)置RTU回應(yīng)包的基本數(shù)據(jù),這些數(shù)據(jù)的格式是一樣的,比如req[0]是從設(shè)備地址,req[1]是功能碼
prepare_response_tid生產(chǎn)傳輸標識TID,在TCP中使用
send_msg_pre發(fā)送消息前的準備工作,對于RTU是填充CRC檢驗碼,對于TCP是填充頭部的Length
send發(fā)送數(shù)據(jù)包
receive接收數(shù)據(jù)包
recv接收原始數(shù)據(jù),receive會調(diào)用recv得到原始數(shù)據(jù)然后解析出數(shù)據(jù)包
check_integrity檢查數(shù)據(jù)包的完整性
pre_check_confirmation檢查響應(yīng)數(shù)據(jù)包是否有效時,先執(zhí)行pre_check_confirmation做一些簡單的檢查
connect硬件相關(guān)的連接,對于RTU就是打開串口、設(shè)置串口波特率等;對于TCP則是連接對端
is_connected判斷是否已經(jīng)連接
close關(guān)閉連接
flush清空接收到的、未處理的數(shù)據(jù)
select阻塞一段時間以等待數(shù)據(jù)
free釋放分配的modbus_t等結(jié)構(gòu)體

本節(jié)先寫出模板:

根據(jù)這個源碼:

img

改出:

img

6.3.2 使用USB串口作為后端

基于這2個程序:

img

img

第1步:合并上述2個源碼,并修改到能編譯成功(但是libmodbus里對USB串口的操作),結(jié)果放在如下目錄:

img

第2步,繼續(xù)修改上圖的代碼,實現(xiàn)USB串口作為后端,得到以下代碼:

img

USB串口的操作函數(shù):

/* 發(fā)送數(shù)據(jù) */

int ux_device_cdc_acm_send(uint8_t *datas, uint32_t len, uint32_t timeout);

/* 接收數(shù)據(jù) */

int ux_device_cdc_acm_getchar(uint8_t *pData, uint32_t timeout);

6.3.3 libmodbus從機實驗(USB串口)

本節(jié)源碼為:

img

參考“l(fā)ibmodbus-3.1.10testsunit-test-server.c”,把開發(fā)板當做從機,使用PC上Modbus Poll軟件讀寫開發(fā)板:控制LED。

要點:

① printf、fprintf、vfprintf都不能使用,改成空的宏

6.3.4 libmodbus主機實驗(USB串口)

本節(jié)源碼為:

img

參考“l(fā)ibmodbus-3.1.10testsunit-test-client.c”,把開發(fā)板當做主機,去讀寫PC上Modbus Slave軟件模擬的從機。

6.3.5 使用板載串口作為后端

學(xué)習(xí)本節(jié)課程前,先觀看《3.5.3 面向?qū)ο蠓庋bUART》,并且觀看對應(yīng)視頻《3-9-1_面向?qū)ο蠓庋bUART_完善收發(fā)功能》、《3-9-2_面向?qū)ο蠓庋bUART_實現(xiàn)結(jié)構(gòu)體》。

本節(jié)代碼如下:

img

按照下圖連線:調(diào)試、供電、兩個485互連,使用CH1(左邊的RS485接口)作為主設(shè)備,訪問CH2(右邊的RS485接口):

img

1. 使用UART_Device

這2個視頻,是在開始本節(jié)課程之前才補錄的:《3-9-1_面向?qū)ο蠓庋bUART_完善收發(fā)功能》、《3-9-2_面向?qū)ο蠓庋bUART_實現(xiàn)結(jié)構(gòu)體》。這兩個視頻里,把UART2、UART4的發(fā)送、接收功能都補全了,并且構(gòu)造了對應(yīng)的UART_Device結(jié)構(gòu)體,里面實現(xiàn)了初始化、發(fā)送、接收一個自己的的函數(shù),如下:

把UART2、UART4封裝為UART_Device的代碼為:“3_程序源碼?1_視頻配套的源碼3-9_面向?qū)ο蠓庋bUARTuart_rtos_all_ok.7z”。需要把它的代碼移植到本節(jié)的工程里:

  • 使用STM32CubeMX配置UART2、UART4:發(fā)送、接收都使用DMA
  • 復(fù)制代碼:CoreSrcusart.c、DriversModule_driveruart_device.c/h

使用STM32CubeMX配置的過程如下:

  • 使能DMA通道:

img

  • 各個DMA通道的配置如下:

img

2. 用作后端

把UART2、UART4用作libmodbus后端時,只需要修改這幾個函數(shù)即可:

img

有兩個UART_Device,調(diào)用哪個UART_Device?在使用“modbus_new_st_rtu”創(chuàng)建modbus_t時,根據(jù)傳入的設(shè)備名在modbus_t結(jié)構(gòu)體里記錄對應(yīng)的UART_Device。_modbus_rtu_connect、_modbus_rtu_send、_modbus_rtu_recv這三個函數(shù),就可以直接調(diào)用modbus_t結(jié)構(gòu)體里的UART_Device函數(shù)了。

6.3.6 libmodbus實驗(板載串口)

本節(jié)源碼為:

img

按照下圖連線:調(diào)試、供電、兩個485互連:

img

創(chuàng)建一個ClientTask,使用CH2(右邊的RS485接口)對外通信。

創(chuàng)建一個ServerTask,使用CH1(左邊的RS485接口)讀寫從設(shè)備數(shù)據(jù)。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • MODBUS
    +關(guān)注

    關(guān)注

    28

    文章

    1805

    瀏覽量

    77003
  • 操作系統(tǒng)
    +關(guān)注

    關(guān)注

    37

    文章

    6825

    瀏覽量

    123333
  • 開源
    +關(guān)注

    關(guān)注

    3

    文章

    3349

    瀏覽量

    42501
收藏 人收藏

    評論

    相關(guān)推薦

    信號與系統(tǒng)6大例題(2

    `信號與系統(tǒng)6大例題(2)[hide][/hide]`
    發(fā)表于 05-13 23:17

    6管理文件系統(tǒng)

    6 - 管理文件系統(tǒng)
    發(fā)表于 05-13 11:24

    正弦波振蕩電路基礎(chǔ) 6

    正弦波振蕩電路基礎(chǔ) 6  本章主要內(nèi)容:6.1  自激振蕩的基本原理 6.2  LC振蕩電路 6.
    發(fā)表于 04-19 18:12 ?58次下載

    基于FPGA的嵌入式系統(tǒng)設(shè)計6介紹

    基于FPGA的嵌入式系統(tǒng)設(shè)計 -6-
    發(fā)表于 10-30 10:44 ?0次下載

    6單片機定時器串口中斷(20150709213857)

    6單片機定時器串口中斷(20150709213857)
    發(fā)表于 12-15 22:28 ?6次下載

    3 MAXPLUS軟件的使用(6節(jié))

    3 MAXPLUS軟件的使用(6節(jié))
    發(fā)表于 07-13 10:07 ?0次下載

    信號與系統(tǒng)6大例題(2

    信號與系統(tǒng)6大例題(2
    發(fā)表于 08-07 11:06 ?0次下載

    DSP嵌入式系統(tǒng)開發(fā)典型案例,6 數(shù)字和IP電話系統(tǒng)設(shè)計

    DSP嵌入式系統(tǒng)開發(fā)典型案例,6 數(shù)字和IP電話系統(tǒng)設(shè)計
    發(fā)表于 10-20 14:28 ?6次下載
    DSP嵌入式系統(tǒng)開發(fā)典型案例,<b class='flag-5'>第</b><b class='flag-5'>6</b><b class='flag-5'>章</b> 數(shù)字和IP電話系統(tǒng)設(shè)計

    6 部件工作原理與編程示例

    6 部件工作原理與編程示例
    發(fā)表于 10-27 09:44 ?3次下載
    <b class='flag-5'>第</b><b class='flag-5'>6</b><b class='flag-5'>章</b> 部件工作原理與編程示例

    STM8S BDLC電機 6源代碼

    STM8S BDLC電機 6 6.5.1 源代碼
    發(fā)表于 03-05 14:51 ?5次下載

    6:數(shù)字輸入/輸出模塊(I/O)

    6:數(shù)字輸入/輸出模塊(I/O)PPT下載
    發(fā)表于 10-08 14:51 ?19次下載

    6 端接.zip

    6端接
    發(fā)表于 12-30 09:22 ?3次下載

    電工電子技術(shù)基礎(chǔ)6 電工測量

    電子發(fā)燒友網(wǎng)站提供《電工電子技術(shù)基礎(chǔ)6 電工測量.ppt》資料免費下載
    發(fā)表于 11-21 14:53 ?3次下載
    電工電子技術(shù)基礎(chǔ)<b class='flag-5'>第</b><b class='flag-5'>6</b><b class='flag-5'>章</b> 電工測量

    2 ANSYS分析基本過程--7 通用后處理器

    2 ?ANSYS分析基本過程--7 通用后處理器
    發(fā)表于 08-31 09:31 ?0次下載

    libmodbus源碼框架分析

    libmodbus作為一個優(yōu)秀且免費開源的跨平臺支持RTU 和 TCP模式的Modbus開發(fā)庫,非常值得大家借鑒和學(xué)習(xí)。本章對libmodbus源代碼進行閱讀和分析。
    的頭像 發(fā)表于 11-21 13:47 ?361次閱讀
    <b class='flag-5'>libmodbus</b>源碼框架分析