01.思維導圖
1 什么是 modbus
他是一個在工控領域非常好用的通信寫 modbus協議本質上是一個 基于 tcp 協議二次封裝的一個協議 什么叫做基于tcp二次封裝的協議:我們自己寫的pack_t(無論靜態還是動態),都是屬于二次封裝的協議
modbus協議是一種 “主從問答式”的協議 主機:向從機發出查詢指令或者修改指令 從機:接受到指令之后回答問題 或者 做出對應打完修改
2 modbus 協議包中內容
2.1 協議包組成
第0~1 字節 :存放當前協議包的序號,從0開始
modbus協議會對協議包的序號做自增操作
第2~3 字節 :存放modbus協議的標識符
該數據固定性寫 0
第4~5 字節:存放接下來的所有數據的字節數
第6 字節:放從機的序列號
一臺主機允許連接多臺從機,該字節就是用來明確,我要向第幾臺從機發送數據
第7個字節:modbus 協議的操作方式
也就是說,現在主機要如何操作從機
到底是讀還是寫,到底是讀什么東西,寫什么東西
簡稱 "操作碼"
剩下的所有字節都是數據位
根據操作碼的不同,數據位的格式也是不同的
2.2 ? ?modbus協議具體操作內容
這里的操作碼,其實就類似于我們自己寫打完 enum Type type,用來告訴接收端,這個協議包發過去之后干嘛用的
modbus協議一共有4個操作內容
03.測試電腦是大端還是小端???
#include <25051head.h>
int main(int argc, const char *argv[])
{char a[2]={0x03,0x00};//0x0003//0000 0011 0000 0000if(htons(3)==3){printf("大端序列..\n");}else{printf("小端序列..\n");}printf("%d\n",*(short*)a);return 0;
}
04.軟件
第一種:modbus
第一種:01.Modbus Poll
?
?第一種情況:寫單個線圈(寫一個bit位)【特別注意,代碼中綁定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
?
#include <25051head.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{/*if(argc<2){printf("請輸入端口號..\n");return 1;}*///atoi函數,將字符串類型轉換成整形//short port=atoi(argv[1]);int client = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET;// 如果用的是ipv4結構體,固定寫AF_INET// 其功能為:標記位// 用來標記,當前傳入的結構體是 ipv4 結構體,還是ipv6結構體,// 還是域套接字本地結構體addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");// 如果地址是 0.0.0.0,這里直接寫個 = 0 也是沒問題的if(connect(client,(struct sockaddr*)&addr,sizeof(addr))==-1){perror("bind_error");return 1;}modbus_t ms={0};ms.id=0x0000;ms.protocol=0x0000;ms.len=htons(0x0006);ms.no=0x01;ms.type=0x05;ms.buf[0]=0x00;ms.buf[1]=0x00;ms.buf[2]=0xff;ms.buf[3]=0x00;write(client,&ms,sizeof(ms));return 0;
}
?第二種情況:寫多個線圈(寫一個bit位)【特別注意,代碼中綁定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
#include <25051head.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{/*if(argc<2){printf("請輸入端口號..\n");return 1;}*///atoi函數,將字符串類型轉換成整形//short port=atoi(argv[1]);int client = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET;// 如果用的是ipv4結構體,固定寫AF_INET// 其功能為:標記位// 用來標記,當前傳入的結構體是 ipv4 結構體,還是ipv6結構體,// 還是域套接字本地結構體addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");// 如果地址是 0.0.0.0,這里直接寫個 = 0 也是沒問題的if(connect(client,(struct sockaddr*)&addr,sizeof(addr))==-1){perror("bind_error");return 1;}modbus_t ms={0};ms.id=0x0000;ms.protocol=0x0000;ms.len=htons(9);ms.no=0x01;ms.type=0x0f;ms.buf[0]=0x00;ms.buf[1]=0x00;//我們寫10個線圈 10->0000 1010 0000 0000 小端字節序列ms.buf[2]=0x00;//0000 0000ms.buf[3]=0x0a;//0000 1010ms.buf[4]=0x02;ms.buf[5]=0x55;ms.buf[6]=0x01;write(client,&ms,sizeof(ms));return 0;
}
?第三種情況:寫一個寄存器【特別注意,代碼中綁定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
#include <25051head.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{/*if(argc<2){printf("請輸入端口號..\n");return 1;}*///atoi函數,將字符串類型轉換成整形//short port=atoi(argv[1]);int client = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET;// 如果用的是ipv4結構體,固定寫AF_INET// 其功能為:標記位// 用來標記,當前傳入的結構體是 ipv4 結構體,還是ipv6結構體,// 還是域套接字本地結構體addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");// 如果地址是 0.0.0.0,這里直接寫個 = 0 也是沒問題的if(connect(client,(struct sockaddr*)&addr,sizeof(addr))==-1){perror("bind_error");return 1;}modbus_t ms={0};ms.id=0x0000;ms.protocol=0x0000;ms.len=htons(6);ms.no=0x01;ms.type=0x06;ms.buf[0]=0x00;ms.buf[1]=0x00;//我們寫10個線圈 10->0000 1010 0000 0000 小端字節序列ms.buf[2]=0x00;//0000 0000ms.buf[3]=0xff;//0000 1010write(client,&ms,sizeof(ms));return 0;
}
?第四種情況:寫多個寄存器【特別注意,代碼中綁定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
#include <25051head.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{/*if(argc<2){printf("請輸入端口號..\n");return 1;}*///atoi函數,將字符串類型轉換成整形//short port=atoi(argv[1]);int client = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET;// 如果用的是ipv4結構體,固定寫AF_INET// 其功能為:標記位// 用來標記,當前傳入的結構體是 ipv4 結構體,還是ipv6結構體,// 還是域套接字本地結構體addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");// 如果地址是 0.0.0.0,這里直接寫個 = 0 也是沒問題的if(connect(client,(struct sockaddr*)&addr,sizeof(addr))==-1){perror("bind_error");return 1;}modbus_t ms={0};ms.id=0x0000;ms.protocol=0x0000;ms.len=htons(17);ms.no=0x01;ms.type=0x10;ms.buf[0]=0x00;ms.buf[1]=0x00;//我們寫5個寄存器 10->0000 1010 0000 0000 小端字節序列ms.buf[2]=0x00;//0000 0000ms.buf[3]=0x05;//0000 1010//5個寄存器需要10個字節ms.buf[4]=0x0a;ms.buf[5]=0x00;ms.buf[6]=0x0a;ms.buf[7]=0x00;ms.buf[8]=0x14;ms.buf[9]=0x00;ms.buf[10]=0x1e;ms.buf[11]=0x00;ms.buf[12]=0x28;ms.buf[13]=0x00;ms.buf[14]=0x32;write(client,&ms,sizeof(ms));return 0;
}
?第五種情況:讀寄存器【特別注意,代碼中綁定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】特別特別注意:讀普通線圈和讀離散線圈01和02回復的是不允許訪問
?
#include <25051head.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{int client = socket(AF_INET, SOCK_STREAM, 0);if (client == -1) {perror("socket creation failed");return 1;}struct sockaddr_in addr = {0};addr.sin_family = AF_INET;addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");if (connect(client, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("connect failed");close(client);return 1;}// 構造讀普通線圈請求報文modbus_t read_coils_msg = {0};read_coils_msg.id = 0x0000;read_coils_msg.protocol = 0x0000;read_coils_msg.len = htons(6); // 功能碼 + 起始地址 + 線圈數量共 6 字節read_coils_msg.no = 0x01; // 從站地址read_coils_msg.type = 0x03; // 讀普通線圈功能碼read_coils_msg.buf[0] = 0x00; // 起始地址高字節read_coils_msg.buf[1] = 0x00; // 起始地址低字節,從地址 0 開始讀read_coils_msg.buf[2] = 0x00; // 要讀取的線圈數量高字節read_coils_msg.buf[3] = 0x08; // 要讀取的線圈數量低字節,讀取 8 個線圈// 發送讀普通線圈請求ssize_t sent_bytes = write(client, &read_coils_msg, sizeof(read_coils_msg));if (sent_bytes == -1) {perror("write read coils request failed");close(client);return 1;}printf("Read coils request sent successfully.\n");// 接收響應uint8_t response[256];ssize_t recv_bytes = read(client, response, sizeof(response));if (recv_bytes == -1) {perror("read response failed");close(client);return 1;} else if (recv_bytes == 0) {printf("No response received.\n");} else {printf("Received %zd bytes response:\n", recv_bytes);for (int i = 0; i < recv_bytes; i++) {printf("%02x ", response[i]);}printf("\n");}close(client);return 0;
}
?第六種情況:寫單個寄存器【特別注意,代碼中綁定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
#include <25051head.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{int client = socket(AF_INET, SOCK_STREAM, 0);if (client == -1) {perror("socket creation failed");return 1;}struct sockaddr_in addr = {0};addr.sin_family = AF_INET;addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");if (connect(client, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("connect failed");close(client);return 1;}// 構造寫單個寄存器請求報文modbus_t write_single_reg_msg = {0};write_single_reg_msg.id = 0x0000;write_single_reg_msg.protocol = 0x0000;write_single_reg_msg.len = htons(6); // 功能碼 + 寄存器地址 + 寄存器值共 6 字節write_single_reg_msg.no = 0x01; // 從站地址write_single_reg_msg.type = 0x06; // 寫單個寄存器功能碼write_single_reg_msg.buf[0] = 0x00; // 寄存器地址高字節write_single_reg_msg.buf[1] = 0x00; // 寄存器地址低字節,選擇地址 0write_single_reg_msg.buf[2] = 0x00; // 要寫入的值高字節write_single_reg_msg.buf[3] = 0x12; // 要寫入的值低字節,寫入 0x0012// 發送寫單個寄存器請求ssize_t sent_bytes = write(client, &write_single_reg_msg, sizeof(write_single_reg_msg));if (sent_bytes == -1) {perror("write single register request failed");close(client);return 1;}printf("Write single register request sent successfully.\n");// 接收響應uint8_t response[256];ssize_t recv_bytes = read(client, response, sizeof(response));if (recv_bytes == -1) {perror("read response failed");close(client);return 1;} else if (recv_bytes == 0) {printf("No response received.\n");} else {printf("Received %zd bytes response:\n", recv_bytes);for (int i = 0; i < recv_bytes; i++) {printf("%02x ", response[i]);}printf("\n");}close(client);return 0;
}
?第七種情況:寫多個寄存器【特別注意,代碼中綁定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
#include <25051head.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{int client = socket(AF_INET, SOCK_STREAM, 0);if (client == -1) {perror("socket creation failed");return 1;}struct sockaddr_in addr = {0};addr.sin_family = AF_INET;addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");if (connect(client, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("connect failed");close(client);return 1;}// 要寫入的寄存器數量uint16_t reg_count = 3;// 構造寫多個寄存器請求報文modbus_t write_multiple_reg_msg = {0};write_multiple_reg_msg.id = 0x0000;write_multiple_reg_msg.protocol = 0x0000;write_multiple_reg_msg.len = htons(7 + reg_count * 2); // 功能碼 + 起始地址 + 寄存器數量 + 字節數 + 數據write_multiple_reg_msg.no = 0x01; // 從站地址write_multiple_reg_msg.type = 0x10; // 寫多個寄存器功能碼write_multiple_reg_msg.buf[0] = 0x00; // 起始寄存器地址高字節write_multiple_reg_msg.buf[1] = 0x00; // 起始寄存器地址低字節,從地址 0 開始write_multiple_reg_msg.buf[2] = 0x00; // 寄存器數量高字節write_multiple_reg_msg.buf[3] = reg_count & 0xFF; // 寄存器數量低字節write_multiple_reg_msg.buf[4] = reg_count * 2; // 字節數// 要寫入的數據uint16_t values[] = {0x0012, 0x0034, 0x0056};for (int i = 0; i < reg_count; i++) {write_multiple_reg_msg.buf[5 + i * 2] = (values[i] >> 8) & 0xFF; // 高字節write_multiple_reg_msg.buf[5 + i * 2 + 1] = values[i] & 0xFF; // 低字節}// 發送寫多個寄存器請求ssize_t sent_bytes = write(client, &write_multiple_reg_msg, sizeof(write_multiple_reg_msg));if (sent_bytes == -1) {perror("write multiple registers request failed");close(client);return 1;}printf("Write multiple registers request sent successfully.\n");// 接收響應uint8_t response[256];ssize_t recv_bytes = read(client, response, sizeof(response));if (recv_bytes == -1) {perror("read response failed");close(client);return 1;} else if (recv_bytes == 0) {printf("No response received.\n");} else {printf("Received %zd bytes response:\n", recv_bytes);for (int i = 0; i < recv_bytes; i++) {printf("%02x ", response[i]);}printf("\n");}close(client);return 0;
}
第一種:02.Modbus Slave
第二種:Wireshark【特別注意,綁定的服務器的ip地址是虛擬機中查詢出來的ifconfig】
server.c代碼:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;int main(int argc, const char *argv[])
{if(argc < 2){printf("請輸入端口號\n");return 1;}short port = atoi(argv[1]);// atoi 函數,將字符串類型轉換成整形// "123" -> 123// "123abc" -> 123// "12abc3" -> 12 // "abc123" -> 0int server = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET; addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr("0.0.0.0");if(bind(server,(struct sockaddr*)&addr,sizeof(addr)) == -1){perror("bind");return 1;}listen(server,10);struct sockaddr_in client_addr;int client_len = sizeof(client_addr);int client = accept(server,(struct sockaddr*)&client_addr,&client_len);// 這個 client 就是成功接受連接的客戶端的套接字printf("有客戶端連接\n");while(1){char buf[128] = "";int res = read(client,buf,128);// 一旦client客戶端關閉,則read(client) 函數就會從阻塞變成非阻塞//int res = recv(client,buf,128,MSG_DONTWAIT);if(res == 0){// 無論如何,read、recv函數只要客戶端斷開連接,都會返回0printf("客戶端斷開連接\n");return 0;}printf("客戶端發來消息:%s\n",buf);sleep(1);}return 0;
}
第三種:網絡調試工具-飛機
特別注意:
小飛機給modbus傳數據有問題: