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