Modbus 是一個簡單且實用的工業用的網路協定,因為標準開放且容易實作,非常廣泛的應用於許多工業設備如PLC, AMI, … 等。Modbus協定目前由 Modbus Organization 維護,可以到該網站下載相關 specifications。Modbus 在 OSI 分層架構中屬於應用層,多數建構於串列傳輸 Serial (RS-232/RS-485) 或乙太網 (TCP/IP) 之上。(關於 Modbus 也可以參考小弟的拙於 slideshare) 即便 Modbus 協定相當容易實作,但基於"不要重新製造輪子"的立場,使用 libmodbus 函式庫來幫助我們快速開發,降低錯誤率與減少偵錯時間是個明智的選擇。
libmodbus
linmodbus 是一個開源函示庫(open source library)使用 LGPL 授權,這意味著我們撰寫的程式若只是單純使用此函示庫,並沒有加以修改的情況下,是可以不用開放原始碼的。也就是說開發商業軟體也可以使用此 library 而不用公開程式碼。唯獨原始碼中的 test 資料夾內的程式碼是屬於 GPLv3 授權,但若不擷取此程式碼作使用是不會有影像的。除此之外,libmodbus 還支援多重平台,不論是 Linux, MAC OS, 或是 Win32 都可以使用。 但以下說明仍以 Linux 平台為主。目前最新版本為 v3.0.5,以下也將會以此版本作為基礎。
Installation
舉例以 Ubuntu 或 Debian 來說,作業系統的套件資料庫已經包含了 libmodbus 的套件,只需要輸入以下指令即可完成安裝:
$ sudo apt-get install libmodbus5 libmodbus-dev
但須注意由此安裝的版本往往都不會是最新的,如果有新版本的需求,還是得透過網路下載最新版後手動安裝:
$ wget http://libmodbus.org/site_media/build/libmodbus-3.0.5.tar.gz $ sudo chmod 755 libmodbus-3.0.5.tar.gz $ tar zxvf libmodbus-3.0.5.tar.gz -C . $ cd libmodbus-3.0.5/ $ ./configure $ make $ sudo make install
於此即完成安裝。
Usage
完整的使用方式請參考官方的 Manual Page,僅舉例以兩個例子做為參考:1. Modbus Slave/Server over serial 2. Modbus Client/Master over TCP/IP
Modbus Slave over Serial
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <modbus.h>
#define MODBUS_SERIAL_DEV "/dev/ttyUSB0"
#define MODBUS_SERIAL_BAUDRATE 9600 /* 9600, 38400, 115200, ... */
#define MODBUS_SERIAL_PARITY 'N' /* 'N', 'E', or 'O' */
#define MODBUS_SERIAL_DATABITS 8 /* 5, 6, 7, or 8 */
#define MODBUS_SERIAL_STOPBITS 1 /* 1 or 2 */
#define MODBUS_DEVICE_ID 1
#define MODBUS_TIMEOUT_SEC 3
#define MODBUS_TIMEOUT_USEC 0
#define MODBUS_DEBUG ON
#define MODBUS_RO_BITS 32
#define MODBUS_RW_BITS 32
#define MODBUS_RO_REGISTERS 64
#define MODBUS_RW_REGISTERS 64
int main(int argc, char *argv[])
{
modbus_t *ctx;
modbus_mapping_t *data_mapping;
struct timeval timeout;
int ret, go = 1;
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
ctx = modbus_new_rtu(MODBUS_SERIAL_DEV,
MODBUS_SERIAL_BAUDRATE,
MODBUS_SERIAL_PARITY,
MODBUS_SERIAL_DATABITS,
MODBUS_SERIAL_STOPBITS);
if (ctx == NULL) {
fprintf(stderr, "Unable to create the libmodbus context\n");
exit(-1);
}
/* set slave device ID */
modbus_set_slave(ctx, MODBUS_DEVICE_ID);
/* Debug mode */
modbus_set_debug(ctx, MODBUS_DEBUG);
data_mapping = modbus_mapping_new(MODBUS_RO_BITS, MODBUS_RW_BITS,
MODBUS_RO_REGISTERS, MODBUS_RW_REGISTERS);
if (data_mapping == NULL) {
fprintf(stderr, "Failed to allocate the mapping: %s\n",
modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
/* FIXME: initiate all data here */
/* open serial interface */
modbus_connect(ctx);
while(go) {
ret = modbus_receive(ctx, query);
if (ret >= 0) {
/* IF SOME ERROR OCCOR */
/* modbus_reply_exception(ctx, query, MODBUS_EXCEPTION_XXXX); */
/* ELSE */
modbus_reply(ctx, query, ret, data_mapping);
} else {
/* Connection closed by the client or server */
break;
}
}
printf("Quit the loop: %s\n", modbus_strerror(errno));
modbus_mapping_free(data_mapping);
modbus_free(ctx);
return 0;
}
Line 31 – 建立一個 modbus RTU over serial,注意帶入的參數要符合實際情況。
Line 43 – 設定 Slave (也就是本機)的 Device ID
Line 46 – 設定 debug mode
Line 48 – 建立 modbus 資料的 mapping。再把對應的 input bits, coils, input regs, holding regs 資料擺放進去。
Line 61 – 開啟 modbus 介面。
Line 65 – 接收封包。
Line 70 – 回覆封包。
Line 79 – 釋放 mapping 記憶體。
Line 81 – 釋放連線內容記憶體。
Modbus Client over TCP/IP(IPv4)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <modbus.h>
#define MODBUS_SERVER_IP "127.0.0.1"
#define MODBUS_SERVER_PORT 502
#define MODBUS_DEVICE_ID 100
#define MODBUS_TIMEOUT_SEC 3
#define MODBUS_TIMEOUT_USEC 0
#define MODBUS_DEBUG ON
#define MODBUS_DISCRETE_ADDR 0
#define MODBUS_DISCRETE_LEN 32
#define MODBUS_COIL_ADDR 0
#define MODBUS_COIL_LEN 32
#define MODUBS_INPUT_ADDR 0
#define MODUBS_INPUT_LEN 64
#define MODUBS_HOLDING_ADDR 0
#define MODUBS_HOLDING_LEN 64
int main(int argc, char *argv[])
{
modbus_t *ctx;
struct timeval timeout;
int ret, ii;
uint8_t bits[MODBUS_MAX_READ_BITS] = {0};
uint16_t regs[MODBUS_MAX_READ_REGISTERS] = {0};
ctx = modbus_new_tcp(MODBUS_SERVER_IP, MODBUS_SERVER_PORT);
/* set device ID */
modbus_set_slave(ctx, MODBUS_DEVICE_ID);
/* Debug mode */
modbus_set_debug(ctx, MODBUS_DEBUG);
/* set timeout */
timeout.tv_sec = MODBUS_TIMEOUT_SEC;
timeout.tv_usec = MODBUS_TIMEOUT_USEC;
modbus_get_byte_timeout(ctx, &timeout);
timeout.tv_sec = MODBUS_TIMEOUT_SEC;
timeout.tv_usec = MODBUS_TIMEOUT_USEC;
modbus_set_response_timeout(ctx, &timeout);
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "Connexion failed: %s\n",
modbus_strerror(errno));
modbus_free(ctx);
exit(-1);
}
/* read discrete input (0x02 function code) */
ret = modbus_read_input_bits(ctx,
MODBUS_DISCRETE_ADDR, MODBUS_DISCRETE_LEN, bits);
if (ret < 0) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
} else {
printf("BITS DISCRETE:\n");
for (ii=0; ii < ret; ii++) {
printf("[%d]=%d\n", ii, bits[ii]);
}
}
/* read coils (0x01 function code) */
ret = modbus_read_bits(ctx, MODBUS_COIL_ADDR, MODBUS_COIL_LEN, bits);
if (ret < 0) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
} else {
printf("BITS COILS:\n");
for (ii=0; ii < ret; ii++) {
printf("[%d]=%d\n", ii, bits[ii]);
}
}
/* read input registers (0x04 function code) */
ret = modbus_read_input_registers(ctx,
MODUBS_INPUT_ADDR, MODUBS_INPUT_LEN, regs);
if (ret < 0) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
} else {
printf("INPUT REGISTERS:\n");
for (ii=0; ii < ret; ii++) {
printf("[%d]=%d\n", ii, regs[ii]);
}
}
/* read holding registers (0x03 function code) */
ret = modbus_read_registers(ctx,
MODUBS_HOLDING_ADDR, MODUBS_HOLDING_LEN, regs);
if (ret < 0) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
} else {
printf("HOLDING REGISTERS:\n");
for (ii=0; ii < ret; ii++) {
printf("[%d]=%d\n", ii, regs[ii]);
}
}
/* write single coil (0x05 function code) */
ret = modbus_write_bit(ctx, 1, TRUE);
if (ret < 0) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
}
/* write single register (0x06 function code) */
ret = modbus_write_register(ctx, 1, 0x1234);
if (ret < 0) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
}
/* write multi coil (0x0F function code) */
bits[1] = TRUE;
bits[2] = FALSE;
ret = modbus_write_bits(ctx, 1, 2, &bits[1]);
if (ret < 0) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
}
/* write multi registers (0x10 function code) */
regs[1] = 0x1234;
regs[2] = 0x5678;
ret = modbus_write_registers(ctx, 1, 2, ®s[1]);
if (ret < 0) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
}
/* Close the connection */
modbus_close(ctx);
modbus_free(ctx);
exit(0);
}
Line 35 – 建立一個 modbus over TCP 組態,Server IP 和 port 須符合實際情況。
Line 38 – 設定欲連入的 device ID
Line 41 – 設定 debug mode
Line 50 – 設定 modbus 回應逾時,超過此時間則 time out。
Line 52 – 連線至 Modbus TCP Server
Line 60 – 讀取 input bits (modbus function code 0x02),需依照實際情況設定範圍。
Line 72 – 讀取 coils bits (modbus function code 0x01),需依照實際情況設定範圍。
Line 83 – 讀取 input registers (modbus function code 0x04),需依照實際情況設定範圍。
Line 95 – 讀取 holding registers (modbus function code 0x03),需依照實際情況設定範圍。
Line 107 – 寫入單一 coil (modbus function code 0x05),需依照實際情況設定位址與值。
Line 113 – 寫入單一 holding register (modbus function code 0x06),需依照實際情況設定位址與值。
Line 121 – 寫入多組 coils (modbus function code 0x0F),需依照實際情況設定位址與值。
Line 129 – 寫入多組 holding registers (modbus function code 0x10),需依照實際情況設定位址與值。
Line 135 – 關閉 modbus 連線。
Line 136 – 釋放連線內容記憶體。
小結
Modbus 是一個簡單且實用的協定,可以藉由理解 Modbus 開始理解各種不同的協定。可以深入 libmodbus 的程式碼了解其背後的運作方式,了解如何開啟一個 serial port 或是 TCP port。
很有用的博文,希望能给予win32下libmodbus库的使用。QQ:969836717
您好,我有些問題,reply的資料應該是在data_mapping裡吧?那麼我要如何將data丟進去data_mapping呢?
你要自己修改 modbus_mapping 去編程。 modbus_mapping 其實就是一個存放數據的內存區。
同问,是如何把自己的数据写进去的?,望指点,QQ29085831