衡阳派盒市场营销有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

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

3天內不再提示

第6章_libmodbus使用

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

第6章 libmodbus使用

6.1 libmodbus開發庫

6.1.1 功能概要

libmodbus是一個免費的跨平臺支持RTU和TCP的Modbus庫,遵循LGPL V2.1+協議。libmodbus支持Linux、Mac Os X、FreeBSD、QNX和Windows等操作系統。libmodbus可以向符合Modbus協議的設備發送和接收數據,并支持通過串口或者TCP網絡進行連接。

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

6.1.2 源碼獲取

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

img

本源碼也放在網盤中如下目錄里:

img

解壓后,簡單查看源代碼根目錄的構成:

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

圖6-2解壓libmodbus源代碼:

img

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

圖6-3libmodbus源碼構成:

img

各文件作用如下:

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

6.1.3 源碼閱讀

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

1. 新建工程

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

img

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

img

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

img

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

img

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

img

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

img

2. 同步文件

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

先點擊菜單“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

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

img

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

img

5. 快捷鍵

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

6.1.4 libmodbus與應用程序的關系

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

圖6-4應用程序與libmodbus的關系:

img

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

6.2 libmodbus源代碼解析

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

6.2.1 核心函數

以Modbus RTU協議為例,主設備、從設備初始化后:

  • 主設備就可以啟動請求,即“發送消息”給從設備
  • 從設備接收到請求后構造數據,啟動響應即“發送回復”
  • 主機收到響應后,會“檢查響應”

如下圖所示:

img

分析“libmodbus-3.1.10testsunit-test-client.c”、“libmodbus-3.1.10testsunit-test-server.c”,可以得到下面核心函數的使用過程:

img

6.2.2 框架分析與數據結構

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

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

img

對于核心層、后端,抽象出了如下結構體:

img

核心層modbus_t結構體的成員含義如下:

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

后端modbus_backend_t結構體的成員含義如下:

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

6.2.3 情景分析

以“modbus_write_bits”函數為例,分析下圖的執行流程:

img

1. 初始化

2. 主設備發送請求

3. 從設備接收請求

4.從設備回應

6.2.4 常用接口函數

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

1. 各類輔助接口函數

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

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

  • RTU模式 :

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

  • TCP模式:

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

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

modbus_t * ctx;

ctx=modbus_new_rtu("COM4"115200'N'81);

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):

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

typedef enum

{

  MODBUS_ERROR_RECOVERY_NONE        =0,             //不恢復

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

  MODBUS_ERROR_RECOVERY_PROTOCOL      =(1< 2)           //協議層恢復

}modbus error recovery mode;

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

基本用法舉例:

modbus_set_error_recovery(ctx,MODBUS_ERROR_RECOVERY_LINK|MODBUS_ERROR_RECOVERY_PROTOCOL);

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

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

#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);

用于獲取或設置響應超時,注意時間單位分別是秒和微秒。

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);

用于獲取或設置連續字節之間的超時時間,注意時間單位分別是秒和微秒。

MODBUS_API intmodbus_get_header_length (modbus_t * ctx);

獲取報文頭長度。

MODBUS_API int modbus_connect (modbus_t * ctx);

此函數用于主站設備與從站設備建立連接。

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

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

MODBUS_API void modbus_close (modbus_t * ctx);

關閉 Modbus 連接。在應用程序結束之前,一定記得調用此函數關閉連接在 RTU 模式下,實質是調用函數 _modbus_rtu_close(modbus_t * ctx) 關閉串口句柄;在 TCP 模式下,實質是調用函數 _modbus_tcp_close(modbust * ctx) 關閉 Socket 句柄。

MODBUS_API void modbus_free (modbus_t * ctx);

釋放結構體 modbus_t 占用的內存。在應用程序結束之前,一定記得調用此函數

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

此函數用于是否設置為DEBUG模式。

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

MODBUS_API const char * modbus_strerror (int errnum);

此函數用于獲取當前錯誤字符串。

2.各類Modbus功能接口函數

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

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

用法舉例:

#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);
}

//釋放空間關閉連接

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);

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

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

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

當讀取成功后,返回值為讀取的寄存器個數;若讀取失敗,則返回-1。此函數調用依賴關系如下圖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開始連續讀取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 );

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

此函數的調用依賴關系如下圖 6-6 所示。

圖6-6函數 modbus_read input_registers()的調用依賴關系

img

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

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

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

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

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

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

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

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

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

參數 addr 代表寄存器起始地址,參數 nb 表示存器的個數而參數 const uint16_t * data 表示待寫人的數據塊。一般情況下,可以使用數組存儲寫入數據數組的各元素取值范圍是 0~0xFFFF 即數據類型 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() 函數應使用以下算法修改遠程設備地址“addr”處的保持寄存器的值:

新值 = (current value AND ‘and’) OR (‘or’ AND (NOT ‘and’)) 。

該功能使用 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() 函數應將 write_nb 保持寄存器的內容從數組 “src” 寫入遠程設備的地址 write_addr ,然后將 read_nb 保持寄存器的內容讀取到遠程設備的地址 read_addr 。讀取結果作為字值(16 位)存儲在 dest 數組中。

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

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

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

用法舉例:

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. 數據處理的相關函數或宏定義

在libmodbus開發庫中,為了方便數據處理在 modbus.h 文件中定義了一系列數據處理宏。

例如獲取數據的高低字節序宏定義:

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

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

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 類型的處理方法,繼續定義其他多字節類型的數據例如int32_t、uint32_t、 int64_t、uint64_t 以及 double 類型的讀寫函數。

6.2.5 RTU/TCP關聯接口函數

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

#include "modbus-tcp.h"

#include "modbus-rtu.h"

可以發現,除了 modbus.h 包含的接口函數之外,modbus-rtu.h 和 modbus-tcp.h 也包含了必要的接口函數。

1. RTU模式關聯函數

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

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

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

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

const char * device = “.COM10”;

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

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

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

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

參數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):

該函數用于設置串口為 MODBUS RTU RS232或MODBUSRTU_RS485模式,此函數只適用于 Linux 操作系統下。

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)。

以上函數只適用于 Linux 操作系統下,RTS 即Request ToSend 的縮寫,具體的意義可通過網絡搜索,一般情況下,此類函數可忽略。

2. TCP模式關聯函數

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

此函數的功能是創建一個TCP/IPv4 類型的modbus_t 結構體。

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

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

此函數創建并監聽一個 TCP/IPv4 上的套接字。

參數int nb_connection 代表最大的監聽數量,在調用此函數之前,必須首先調用modbus_new_tcp() 創建modbus_t結構體。

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

此函數接收一個 TCP/IPv4 類型的連接請求,如果成功將進入數據接收狀態。

6.3 libmodbus移植與使用

6.3.1 移植方法

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

根據下圖的層次,要移植libmodbus的“后端”,就是構造自己的modbus_backend_t結構體:

img

后端modbus_backend_t結構體的成員含義如下:

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

本節先寫出模板:

根據這個源碼:

img

改出:

img

6.3.2 使用USB串口作為后端

基于這2個程序:

img

img

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

img

第2步,繼續修改上圖的代碼,實現USB串口作為后端,得到以下代碼:

img

USB串口的操作函數:

/* 發送數據 */

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

/* 接收數據 */

int ux_device_cdc_acm_getchar(uint8_t *pData, uint32_t timeout);

6.3.3 libmodbus從機實驗(USB串口)

本節源碼為:

img

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

要點:

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

6.3.4 libmodbus主機實驗(USB串口)

本節源碼為:

img

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

6.3.5 使用板載串口作為后端

學習本節課程前,先觀看《3.5.3 面向對象封裝UART》,并且觀看對應視頻《3-9-1_面向對象封裝UART_完善收發功能》、《3-9-2_面向對象封裝UART_實現結構體》。

本節代碼如下:

img

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

img

1. 使用UART_Device

這2個視頻,是在開始本節課程之前才補錄的:《3-9-1_面向對象封裝UART_完善收發功能》、《3-9-2_面向對象封裝UART_實現結構體》。這兩個視頻里,把UART2、UART4的發送、接收功能都補全了,并且構造了對應的UART_Device結構體,里面實現了初始化、發送、接收一個自己的的函數,如下:

把UART2、UART4封裝為UART_Device的代碼為:“3_程序源碼?1_視頻配套的源碼3-9_面向對象封裝UARTuart_rtos_all_ok.7z”。需要把它的代碼移植到本節的工程里:

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

使用STM32CubeMX配置的過程如下:

  • 使能DMA通道:

img

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

img

2. 用作后端

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

img

有兩個UART_Device,調用哪個UART_Device?在使用“modbus_new_st_rtu”創建modbus_t時,根據傳入的設備名在modbus_t結構體里記錄對應的UART_Device。_modbus_rtu_connect、_modbus_rtu_send、_modbus_rtu_recv這三個函數,就可以直接調用modbus_t結構體里的UART_Device函數了。

6.3.6 libmodbus實驗(板載串口)

本節源碼為:

img

按照下圖連線:調試、供電、兩個485互連:

img

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

創建一個ServerTask,使用CH1(左邊的RS485接口)讀寫從設備數據。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • MODBUS
    +關注

    關注

    28

    文章

    1820

    瀏覽量

    77308
  • 操作系統
    +關注

    關注

    37

    文章

    6892

    瀏覽量

    123742
  • 開源
    +關注

    關注

    3

    文章

    3402

    瀏覽量

    42711
收藏 人收藏

    評論

    相關推薦

    信號與系統6大例題(2

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

    6管理文件系統

    6 - 管理文件系統
    發表于 05-13 11:24

    正弦波振蕩電路基礎 6

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

    基于FPGA的嵌入式系統設計6介紹

    基于FPGA的嵌入式系統設計 -6-
    發表于 10-30 10:44 ?0次下載

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

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

    3 MAXPLUS軟件的使用(6節)

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

    信號與系統6大例題(2

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

    DSP嵌入式系統開發典型案例,6 數字和IP電話系統設計

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

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

    6 部件工作原理與編程示例
    發表于 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 源代碼
    發表于 03-05 14:51 ?5次下載

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

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

    6 端接.zip

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

    電工電子技術基礎6 電工測量

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

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

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

    libmodbus源碼框架分析

    libmodbus作為一個優秀且免費開源的跨平臺支持RTU 和 TCP模式的Modbus開發庫,非常值得大家借鑒和學習。本章對libmodbus源代碼進行閱讀和分析。
    的頭像 發表于 11-21 13:47 ?560次閱讀
    <b class='flag-5'>libmodbus</b>源碼框架分析
    大发888娱乐官方网站| 蓝盾百家乐官网平台租用| 百家乐官网网络游戏信誉怎么样 | 百家乐看不到视频| 皇冠现金网信誉| 百家乐官网娱乐平台网| 威尼斯人娱乐骰宝| 太阳城百家乐官网网址--| 百家乐免费改单| 百家乐官网棋牌游戏皇冠网| 百家乐永利娱乐| 百家乐官网视频游戏盗号| 百家乐网盛世三国| 百家乐官网注册送彩金平台| 至尊百家乐娱乐平台| 投真钱百家乐官网必输吗| 百家乐皇室百家乐| 足球百家乐官网投注| 威尼斯人娱乐场 澳门| 线上百家乐官网是如何作弊| 德州扑克软件| 百家乐出租平台| 金赞娱乐成| 百家乐推锅| 真人百家乐官网软件博彩吧| 威尼斯人娱乐城存款多少起存 | 百家乐官网园胎教网| 百家乐棋牌辅助| 百家乐官网视频打麻将| 新葡京百家乐的玩法技巧和规则| 百家乐官网双面数字筹码怎么出千| 大发888设置| 百家乐代理龙虎| 济源市| K7百家乐的玩法技巧和规则 | 百利宫百家乐官网现金网| 聚众玩百家乐的玩法技巧和规则 | 网页百家乐游戏| 百家乐官网怎么玩啊| 威尼斯人娱乐城网| 大发百家乐游戏|