這篇文章是接上一篇Modbus協議簡介,主要介紹Modbus實際項目應用—libmodbus驅動庫的使用,斷斷續續寫了近一周時間。
都有哪些內容?為什麼要使用驅動庫
libmodbus簡介
libmodbus常用函數
Windows平臺使用libmodbus
Linux平臺使用libmodbus
ARM平臺使用libmodbus
libmodbus從機地址限制的問題
測試代碼獲取
為什麼要使用驅動庫?上一篇文章,我們介紹了Modbus協議物理層和協議層,我們知道了Modbus是一種總線協議,它可以基於串口或網口,以基於串口的Modbus-RTU為例,我們需要在Windows或Linux下實現一個上位機,上位機的功能是讀寫Modbus接口傳感器設備的數據,或者是和單片機等從設備進行交互。
當需要向某個從機寄存器寫入某個值時,如向01地址的設備,0x0105保持寄存器寫入1個數據:0x0190為例,那麼需要構建這樣一個數據幀:
主機發送:01 06 01 05 01 90 99 CB
01表示從機地址,06功能碼表示寫單個保持寄存器,0105表示寄存器地址,0190表示寫入寄存器的數值,99 CB為CRC校驗值。
如果從機正確的收到了數據,會回復一個數據幀:
從機回覆:01 06 01 05 01 90 99 CB
所以作為主機,寫數據的流程是:
讀數據也是同樣的流程,我們可以基於串口發送、串口接收函數、定時器等,自己寫一個Modbus驅動庫,來實現對從設備的讀寫。當然,也可以直接使用別人寫好的Modbus驅動庫,比如libmodbus,本文將介紹如何使用libmodbus驅動庫,Windows/Linux/ARM平臺實現Modbus主機和從機。
libmodbus簡介libmodbus,是一個基於C語言實現的Modbus驅動庫,作者是Stephane,支持Linux, Mac OS X, FreeBSD, QNX and Win32作業系統,主要應用在PC上,用來開發上位機,也可以對原始碼進行交叉編譯,以適配更多的平臺,比如ARM Linux。原始碼開源,遵循 LGPL-2.1 許可。目前最新版本是3.1.6,Github倉庫最新提交時間是2021年5月21日。
官方網站:www.libmodbus.org
開源地址:
github.com/stephane/libmodbus
libmodbus支持如下功能:
支持Modbus-RTU和Modbus-TCP
支持常用功能碼,如01/02/03/04/05/06/07/0F/10/11/16/17
支持線圈類型讀寫、寄存器讀寫、離散量讀取等
支持廣播地址0,從機地址1-247
支持浮點數和整形數據轉換,大端小端等多種模式
參數根據Modbus_Application_Protocol_V1_1b.pdf官方標準文檔設計,比如最大讀寫線圈個數,最大讀寫寄存器個數等。
原始碼基於C編寫,方便在各平臺移植,只有11個文件。
libmodbus常用函數libmodbus庫函數非常簡潔,讀寫操作函數對於RTU和TCP完全通用,RTU和TCP切換隻需要修改一行代碼就可以實現無縫切換。
modbus_t *mb;
int ret;
//創建一個modbus-rtu對象,指定串口號,波特率,校驗位,數據位,停止位
//成功返回指針,否則返回NULL, 會調用malloc申請內存
mb = modbus_new_rtu("/dev/ttySP1", 115200, 'N', 8, 1); //linux
mb = modbus_new_rtu("COM1", 115200, 'N', 8, 1); //windows
//創建modbus-tcp對象,指定IP位址和埠號
mb = modbus_new_tcp("127.0.0.1", 502); //TCP/IP
//設置從機地址,成功返回0, 否則返回-1
ret = modbus_set_slave(mb, slave);
//連接Modbus主機,成功返回0, 否則返回-1
ret = modbus_connect(mb);
//設置響應超時時間1s,200ms
ret = modbus_set_response_timeout(mb, 1, 200000);
//讀取寄存器數據,起始地址2, 數量5, 保存到table數組中
//成功返回5, 否則返回-1
uint16_t *table;
ret = modbus_read_registers(mb, 2, 5, table);
//modbus設備關閉和釋放內存
modbus_close(mb);
modbus_free(mb);
//寫單個寄存器, 地址2寫入56, 成功返回1,否則返回-1
ret = modbus_write_register(mb, 2, 56);
//寫多個寄存器, 地址12起始,寫入5個數據,成功返回5,否則返回-1
uint16_t table[5] = {11, 22, 33, 44, 55};
ret = modbus_write_registers(mb, 12, 5, table);
//寫單個線圈,線圈地址寫入TRUE,成功返回1,否則返回-1
ret = modbus_write_bit(mb, 11, TRUE);
//查看錯誤信息
char *err_str;
err_str = modbus_strerror(errno);
以Windows下使用libmodbus實現從機和主機為例,Linux下類似。
1.獲取原始碼使用Git工具下載GitHub代碼倉庫原始碼到本地,這樣可以獲取到最新的libmodbus代碼,但是也會有一些Bug。
git clone https://github.com/stephane/libmodbus/
如果下載速度緩慢,可以到我的Gitee倉庫下載:
git clone https://gitee.com/whik/libmodbus
或者到官方倉庫下載最新穩定發布版本v3.1.6:
libmodbus.org/releases/libmodbus-3.1.6.tar.gz
下載完成之後,解壓到本地,Linux系統可以使用tar -zxvf libmodbus-3.0.6.tar.gz命令行解壓:
我們重點關注以下3個文件夾:doc,src,tests。
包括Modbus-RTU/TCP客戶端和伺服器單元測試,隨機測試,效率測試,讀寫10萬個線圈狀態,10萬個寄存器,記錄消耗時間。
//部分代碼
nb_points = MODBUS_MAX_READ_BITS;
start = gettime_ms();
for (i=0; i<n_loop; i++) {
rc = modbus_read_bits(ctx, 0, nb_points, tab_bit);
if (rc == -1) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
return -1;
}
}
end = gettime_ms();
elapsed = end - start;
官方提供的測試代碼太繁瑣,後面我們會寫兩個簡單的示例程序,來演示主機和從機的使用。
2.生成config.h配置文件無論是Windows還是Linux,在使用libmodbus庫之前,我們需要先調用configure工具來生成config.h文件和Makefile。configure工具會根據當前系統環境,生成適用於當前平臺的config.h文件。
在libmodbus庫文件夾下執行./configure命令。
whik@windows_7 MINGW64 /d/libmodbus-3.1.6
$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for strings.h... yes
..
checking for inttypes.h... yes
config.status: creating tests/unit-test.h
config.status: executing libtool commands
libmodbus 3.1.6
===============
prefix: /usr/local
sysconfdir: ${prefix}/etc
libdir: ${exec_prefix}/lib
includedir: ${prefix}/include
compiler: gcc
cflags: -g -O2
ldflags:
documentation: no
tests: yes
整個過程需要1分鐘左右的時間,等待運行完成之後,會發現在當前目錄下多了一些文件,主要是config.h和Makefile
如果想使用libmodbus官方提供的測試代碼,可以直接在根目錄執行make命令,就可以直接編譯tests目錄下的測試代碼,Linux系統可以使用make install命令進行和安裝。
3.編寫測試代碼新建一個文件夾my_test,把libmodbus/src文件夾中的.c和.h文件,config.h複製到my_test。
學習了libmodbus常用函數之後,我們就可以寫一個簡單的測試代碼了。
Modbus-RTU主機測試:test_rtu_master.c,實現對地址為1的從機設備,讀取地址15/16/17的保持寄存器數據,進行+1操作後,再寫入。
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "modbus.h"
#define PORT_NAME "COM1"
int main(int argc, char *argv[])
{
int ret;
uint16_t table[3];
modbus_t *mb;
char port[20];
printf("argc = %d, argv[1] = %s\n", argc, argv[1]);
if(argc == 2)
strcpy(port, argv[1]);
else
strcpy(port, PORT_NAME);
printf("libmodbus modbu-rtu master demo: %s, 115200, N, 8, 1\n", port);
mb = modbus_new_rtu(port, 115200, 'N', 8, 1);
if (mb == NULL)
{
modbus_free(mb);
printf("new rtu failed: %s\n", modbus_strerror(errno));
return 0;
}
modbus_set_slave(mb, 1);
ret = modbus_connect(mb);
if(ret == -1)
{
modbus_close(mb);
modbus_free(mb);
printf("connect failed: %s\n", modbus_strerror(errno));
return 0;
}
while(1)
{
ret = modbus_read_registers(mb, 0x0F, 3, table);
if(ret == 3)
printf("read success : 0x%02x 0x%02x 0x%02x \n", table[0], table[1], table[2]);
else
{
printf("read error: %s\n", modbus_strerror(errno));
break;
}
for(int i = 0; i < 3; i++)
table[i] += 1;
ret = modbus_write_registers(mb, 0x0F, 3, table);
if(ret == 3)
printf("write success: 0x%02x 0x%02x 0x%02x \n", table[0], table[1], table[2]);
else
{
printf("write error: %s\n", modbus_strerror(errno));
break;
}
Sleep(1000);
}
modbus_close(mb);
modbus_free(mb);
system("pause");
return 0;
}
Modbus-RTU從機測試:test_rtu_slave.c,創建從機設備,地址為1,初始化了3個保持寄存器,地址分別為15/16/17,數據分別為0x1001/0x1002/0x1003。
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "modbus.h"
#define PORT_NAME "COM2"
int main(int argc, char *argv[])
{
int ret = 0;
uint8_t device = 1;
uint8_t *query;
modbus_t *mb;
modbus_mapping_t *mb_mapping;
char port[20];
printf("argc = %d, argv[1] = %s\n", argc, argv[1]);
if(argc == 2)
strcpy(port, argv[1]);
else
strcpy(port, PORT_NAME);
printf("libmodbus modbu-rtu slave demo: %s, 115200, N, 8, 1\n", port);
mb = modbus_new_rtu(port, 115200, 'N', 8, 1);
if (mb == NULL)
{
modbus_free(mb);
printf("new rtu failed: %s\n", modbus_strerror(errno));
return 0;
}
//register: 15/16/17
mb_mapping = modbus_mapping_new_start_address(0, 0, 0, 0, 15, 3, 0, 0);
if(mb_mapping == NULL)
{
modbus_free(mb);
printf("new mapping failed: %s\n", modbus_strerror(errno));
return 0;
}
//保持寄存器數據
mb_mapping->tab_registers[0] = 0x1001;
mb_mapping->tab_registers[1] = 0x1002;
mb_mapping->tab_registers[2] = 0x1003;
modbus_set_slave(mb, device);
ret = modbus_connect(mb);
if(ret == -1)
{
modbus_free(mb);
printf("connect failed: %s\n", modbus_strerror(errno));
return 0;
}
printf("create modbus slave success\n");
while(1)
{
do {
ret = modbus_receive(mb, query); //輪詢串口數據,
} while (ret == 0);
if(ret > 0) //接收到的報文長度
{
printf("len=%02d: ", ret);
for(int idx = 0; idx < ret; idx++)
{
printf(" %02x", query[idx]);
}
printf("\n");
modbus_reply(mb, query, ret, mb_mapping);
}
else
{
printf("quit the loop: %s", modbus_strerror(errno));
modbus_mapping_free(mb_mapping);
break;
}
}
modbus_close(mb);
modbus_free(mb);
return 0;
}
現學了Makefile語法,湊合用。需要注意的是,windows下libmodbus依賴於ws2_32.dll庫,需要添加編譯參數-lws2_32:
.PHONY: all
all: test_rtu_master test_rtu_slave
test_rtu_master : test_rtu_master.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o
gcc test_rtu_master.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o -o test_rtu_master -lws2_32
test_rtu_slave : test_rtu_slave.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o
gcc test_rtu_slave.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o -o test_rtu_slave -lws2_32
test_rtu_slave.o : test_rtu_slave.c
gcc test_rtu_slave.c -c -I.
test_rtu_master.o : test_rtu_master.c
gcc test_rtu_master.c -c -I.
modbus.o : modbus.c
gcc modbus.c -c -I.
modbus-rtu.o : modbus-rtu.c
gcc modbus-rtu.c -c -I.
modbus-tcp.o : modbus-tcp.c
gcc modbus-tcp.c -c -I.
modbus-data.o : modbus-data.c
gcc modbus-data.c -c -I.
clean:
rm -rf *.o *.exe
最終的文件目錄:
Windows下Make工具我使用的是Qt自帶的mingw32-make.exe工具,位於\Qt5.7.0\Tools\mingw530_32\bin目錄下,執行mingw32-make命令進行,會對兩個測試文件進行編譯:
whik@Windows_7 MINGW64 /d/my_test
$ mingw32-make.exe
gcc test_rtu_master.c -c -I.
gcc modbus.c -c -I.
gcc modbus-tcp.c -c -I.
gcc modbus-rtu.c -c -I.
gcc modbus-data.c -c -I.
In file included from modbus-data.c:24:0:
./config.h:171:0: warning: "WINVER" redefined
#define WINVER 0x0501
^
In file included from D:/Program/Qt5.7.0/Tools/mingw530_32/i686-w64-mingw32/include/windows.h:10:0,
from D:/Program/Qt5.7.0/Tools/mingw530_32/i686-w64-mingw32/include/winsock2.h:23,
from modbus-data.c:19:
D:/Program/Qt5.7.0/Tools/mingw530_32/i686-w64-mingw32/include/sdkddkver.h:162:0: note: this is the location of the previous definition
#define WINVER _WIN32_WINNT
^
gcc test_rtu_master.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o -o test_rtu_master -lws2_32
gcc test_rtu_slave.c -c -I.
gcc test_rtu_slave.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o -o test_rtu_slave -lws2_32
會在當前目錄下生成目標文件:test_rtu_master.exe和test_rtu_slave.exe。
這裡,我的電腦本機虛擬了兩個串口COM1和COM2,兩個串口直接進行連接。
先啟動從機設備,配置為COM1:
$ ./test_rtu_slave.exe "COM1"
再啟動主機設備,配置為COM2:
$ ./test_rtu_master.exe "COM2"
可以看到,從機可以正確的對接收的數據幀進行相應,主機可以正確的進行讀取和寫入。
如果需要測試Modbus-TCP,只需要修改modbus設備創建函數:
//modbus-rtu
mb = modbus_new_rtu(port, 115200, 'N', 8, 1);
//modbus-tcp
mb = modbus_new_tcp("127.0.0.120", 502); //指定IP位址
其他無需任何改動!
Linux平臺下libmodbus使用Ubuntu下使用libmodbus和Windows幾乎一樣:
//1.解壓
tar -zxvf libmodbus-3.0.6.tar.gz
//2.配置
./configure
//3.編譯
make
//4.安裝
make install
測試文件和Windows幾乎一樣,不過不需要ws2_32庫的支持了。
(來自:blog.csdn.net/qq_30650153/article/details/83385626)
ARM平臺下libmodbus使用ARM開發板下使用libmodbus,需要使用交叉編譯器進行交叉編譯,生成so庫文件。
1.解壓:
tar -zxvf libmodbus-3.0.6.tar.gz
2.創建安裝目錄:
mkdir install
3.配置編譯選項:
./configure --host=arm-fsl-linux-gnueabi --enable-static --prefix=[安裝路徑]/install/
4.編譯:make
5.安裝:make install
在install目錄會生成3個文件夾:include lib share
進入install/lib目錄,執行file libmodbus*,出現如下列印信息,信息中有「ARM」說明libmodbus庫移植成功。
libmodbus.a: current ar archive
libmodbus.la: libtool library file,
libmodbus.so: symbolic link to `libmodbus.so.5.0.5'
libmodbus.so.5: symbolic link to `libmodbus.so.5.0.5'
libmodbus.so.5.0.5: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, not stripped
將libmodbus.so、libmodbus.so.5、libmodbus.so.5.0.5複製到ARM開發板中的/usr/lib目錄下
執行cp libmodbus.so* /usr/lib
如果出現無法創建的問題(cannot create 『/usr/lib/libmodbus.so*』: Read-only file system)。
可以執行 wr cp libmodbus* /usr/lib
測試與使用,和Windows一樣,對測試文件使用ARM交叉編譯器進行編譯。
(來自:www.cnblogs.com/happybirthdaytoyou/p/11301612.html)
libmodbus 從機地址限制的問題libmodbus支持1-247從機地址,0為廣播地址,但是有些非標準的Modbus傳感器,並不是採用0作為廣播地址,而是0xfe作為廣播地址:
所以使用libmodbus會出現報錯終止運行的問題,這是因為libmodbus原始碼中限制了從機地址1-247,我們只需要修改原始碼即可。
modbus-rtu.c文件95行:
modbus-tcp.c文件80行:
只需要修改這兩個數值就可以取消從機地址限制的問題。
詳細的從站最大地址限制問題排查記錄,可以查看:
blog.csdn.net/qingzhuyuxian/article/details/80391553
其實這個問題,早在2011年,就有人在官方GitHub倉庫提Issues了:
github.com/stephane/libmodbus/issues/38
issue對此問題,作者的答覆是,為了遵循Modbus官方標準,所以一直以來都沒有進行修改。
測試代碼獲取關注公眾號:電子電路開發學習(ID:mcu149),後臺回復【libmodbus】獲取文中介紹的兩個測試文件、Makefile和我修改好從機地址最大值的驅動庫。
(建議複製過去不會錯)
更多