Modbus实践编程
一、网络协议深入了解。练习wireshark抓取网络数据包。在两台的电脑(笔记本电脑win10 主机与ubuntu虚拟机。网卡选择桥接模式,可得到两个子网IPv4地址)上运行 “疯狂聊天室”程序,通过wireshark抓包:1)分析此程序网络连接采用的是哪种协议(TCP、UDP)和什么端口号?2)试着在抓取包中找到窃取到的聊天信息 (英文字符和汉字可能经过了某种编码转换,数据包中不是明文)3)如果是网络连接采取的是TCP,分析其建立连接时的3次握手,断开连接时的4次握手;如果是UDP,解释该程序为何能够在多台电脑之间(只有是同一个聊天室编号)同时传输聊天数据?
1、使用方法
下载
链接:https://pan.baidu.com/s/1swrO0zQmzKCrD3gu63oz3g
提取码:1111
2、使用方法
两个人在必须同一网络下!
解压缩打开运行crazychat.exe
输入昵称和房价号:
二.使用wireshark抓包
打开软件对当前网络抓包
1)分析此程序网络连接采用的是哪种协议(TCP、UDP)和什么端口号?
发送一条信息及时筛选
可以发现是TCP协议
打开几条信息,端口号都是相同的443与4796
2)试着在抓取包中找到窃取到的聊天信息 (英文字符和汉字可能经过了某种编码转换,数据包中不是明文)
在疯狂聊天室里发送几个文字
抓包
在疯狂聊室里发送几个字母
抓包
3)如果是网络连接采取的是TCP,分析其建立连接时的3次握手,断开连接时的4次握手;如果是UDP,解释该程
序为何能够在多台电脑之间(只有是同一个聊天室编号)同时传输聊天数据?
三次握手:
第一次握手:
客户端将TCP报文标志位SYN置为1,随机产生一个序号值seq=J,保存在TCP首部的序列号(Sequence
Number)字段里,指明客户端打算连接的服务器的端口,并将该数据包发送给服务器端,发送完毕后,客户
端进入 SYN_SENT 状态,等待服务器端确认。
第二次握手:
服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将TCP报文标志位SYN和ACK都置
为1,ack=J+1,随机产生一个序号值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入
SYN_RCVD 状态。
第三次握手:
客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数
据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务
器端进入 ESTABLISHED 状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
四次挥手
第一次挥手: Client端发起挥手请求,向Server端发送标志位是FIN报文段,设置序列号seq,此时,Client端
进入 FIN_WAIT_1 状态,这表示Client端没有数据要发送给Server端了。
第二次分手:Server端收到了Client端发送的FIN报文段,向Client端返回一个标志位是ACK的报文段,ack设
为seq加1,Client端进入 FIN_WAIT_2 状态,Server端告诉Client端,我确认并同意你的关闭请求。
第三次分手: Server端向Client端发送标志位是FIN的报文段,请求关闭连接,同时Client端进入 LAST_ACK 状
态。
第四次分手 : Client端收到Server端发送的FIN报文段,向Server端发送标志位是ACK的报文段,然后Client
端进入 TIME_WAIT 状态。Server端收到Client端的ACK报文段以后,就关闭连接。此时,Client端等待2MSL的
时间后依然没有收到回复,则证明Server端已正常关闭,那好,Client端也可以关闭连接了。
二、在消化学习 server.c和client.c 套接字代码、python-modbus-over-tcp.py 代码基础
上,试着用C编程完成modbus协议,从云端服务器读取温湿度数据。
输入指令格式
生成将输入指令转化为hex格式并生成crc16校验码
uint8_t data[length_8];
printf("具体指令给格式为0+传感器编号(1,2,3,4,5)0300010002");
printf("请输入采集传感器的指令):\r\n");
scanf("%s",data);
生成将输入指令转化为hex格式并生成crc16校验码
uint16_t crc;
unsigned char * cmd;
char crc1[8];
cmd = fromhex(data);
crc = CRC_16(cmd);
uint8_t a = 0xFF;
for(int i=0;i<6;i++){
//TODO
crc1[i] = cmd[i];
}
crc1[6] = a & crc;
crc1[7] = (crc >> 8) & a
去校验码低位和高位组成2byte的crc16校验位
对应的fromhex函数和crc16校验码生成函数CRC_16会在后面给出
发送,接收数据并使用wireshark对发送数据进行抓包分析
if (send(client_socket, crc1, 8, 0) < 0) {
printf("Failed to send data!\n");
break;
}
int ret = recv(client_socket, recv_data, BUFFER_SIZE, 0);
if (ret < 0) {
printf("Failed to receive data!\n");
break;
}
recv_data[ret]=0; // correctly ends received string
char yb[4],wd[4];
for(int i=0;i<4;i++){
//TODO
yb[i] = recv_data[4+i];
wd[i] = recv_data[8+i];
}
float mic = hexToDec(yb)/100.0;
float strain_temp = hexToDec(wd)/100.0;
printf("应变:%f\r\n",mic);
printf("温度:%f\r\n",strain_temp);
// printf("Receive data from server: \"%x\"\n",recv_data);
if (strcmp(data,kExitFlag)==0) {
printf("Exit!\n");
break;
}
01:地址 03:指令码 0001:起始寄存器 0002:寄存器个数 95cb:crc校检位
可以看到请求数据和接收数据方式都为TCP
三、用stm32最小核心板+AHT20模块,完成一个 modbus接口的温湿度Slave设备,能够让上位机PC通过modbus协议获取温湿度。主程序采用多任务框架,比如RT-thread Nano。
1、STM32移植RT_THread
CubeMX安装Nano pack
先获取软件包地址:
https://www.rt-thread.org/download/cube/RealThread.RT-Thread.pdsc
打开 CubeMX,从菜单栏 help 进入 Manage embedded software packages 界面,点击 From Url 按钮,进入 User Defined Packs Manager 界面,其次点击 new,填入上述网址,然后点击 check
安装即可
然后keil MDK安装RT_THread
CubeMX新建文件 选择 Nano 组件 (1)点击 Softwares Packages->Select Components,进入组件配置界面,选择 RealThread, 然后根3.1.5版本的,然后点击 OK 按钮
这时会新增Software Packs展开就可以看见添加的RealThread.RT_Thread,勾选相应内容
找到 RTOS,勾选 kernel,点击 OK
RCC设置:
时钟设置:
usart2设置4800bit/s
配置串口一
配置nvic
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rXuCtOIq-1672045109168)(C:\Users\HP\AppData\Local\Temp\1672043367548.png)](https://img-blog.csdnimg.cn/00abc47d36d8477fbf48ef3d4409cdb3.png)
随后打开生成项目就可以了:
修改代码:
usart.c
#include <stdio.h>
int fputc(int ch,FILE *f)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xFFFF);
//等待发送结束
while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET){
}
return ch;
}
添加中断处理 stm32f1xx_it.c的串口2中断处理函数
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
uint32_t tmp = 0;
tmp =__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE); //获取IDLE标志位
if((tmp != RESET))//idle标志被置位
{
//清除标志位
//__HAL_UART_CLEAR_IDLEFLAG(&huart2);
//清除状态寄存器SR,读取SR寄存器可以实现清除SR寄存器的功能
huart2.Instance->SR;
//读取数据寄存器中的数据
huart2.Instance->DR;
HAL_UART_DMAStop(&huart2); //
// 获取DMA中未传输的数据个数
dataLength=hdma_usart2_rx.Instance->CNDTR;
// 接受完成
isDataEnd = 1;
//清除标志位
__HAL_UART_CLEAR_IDLEFLAG(&huart2);
}
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
main.c 添加变量
extern DMA_HandleTypeDef hdma_usart2_rx;
//modbus问询帧
uint8_t commands[3][8]={
{0x01,0x03,0x00,0x00,0x00,0x02,0xC4,0x38},
{0x02,0x03,0x00,0x00,0x00,0x02,0xC4,0x38},
{0x03,0x03,0x00,0x00,0x00,0x02,0xC5,0xE9},
};
//modbus应答帧
uint8_t dataBuff[20];
//中断完成标志
uint8_t isDataEnd=0;
//应答帧长度
uint8_t dataLength;
//温度
uint16_t temperature;
//湿度
uint16_t humidity;
main函数:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_DMA_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
//使能idle中断
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
//打开串口DMA接收
HAL_UART_Receive_DMA(&huart2,dataBuff,sizeof(dataBuff));
//传感器地址
uint8_t i=0;
while (1)
{
//中断完成,解析应答帧
if(isDataEnd){
//提取湿度
humidity=(dataBuff[3] << 8) + (dataBuff[4]);
//提取温度
temperature=(dataBuff[5] << 8) + (dataBuff[6]);
//串口一发送解析结果
printf("温度: %.1f\t 湿度: %.1f\r\n",temperature/10.0,humidity/10.0);
memset(dataBuff,0,sizeof(dataBuff));//清空接收数组
isDataEnd=0;//清除接收结束标志位
}
//发送第 i 个传感器的 modbus 问询帧
HAL_UART_Transmit(&huart2,commands[i],sizeof(commands),0xFFFF);
//轮询读取三个传感器
i=(i+1)%3;
//打开串口DMA接收
HAL_UART_Receive_DMA(&huart2,dataBuff,sizeof(dataBuff));
//程序运行标志,闪灯
HAL_Delay(1000);
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
}
实验效果:
由于AHT20在不在身边,所以没有读取出来数据,如果在身边会显示温度。