Modbus编程实践

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-np19P4HA-1672045109159)(C:\Users\HP\AppData\Local\Temp\1672042550469.png)]

安装即可

然后keil MDK安装RT_THread

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uxhtO1lR-1672045109160)(C:\Users\HP\AppData\Local\Temp\1672042623513.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ns2YoKNf-1672045109160)(C:\Users\HP\AppData\Local\Temp\1672042641111.png)]

CubeMX新建文件 选择 Nano 组件 (1)点击 Softwares Packages->Select Components,进入组件配置界面,选择 RealThread, 然后根3.1.5版本的,然后点击 OK 按钮

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ZD187hZ-1672045109161)(C:\Users\HP\AppData\Local\Temp\1672042781790.png)]

这时会新增Software Packs展开就可以看见添加的RealThread.RT_Thread,勾选相应内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kX6yrWKa-1672045109162)(C:\Users\HP\AppData\Local\Temp\1672042814199.png)]

找到 RTOS,勾选 kernel,点击 OK

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bycHI011-1672045109162)(C:\Users\HP\AppData\Local\Temp\1672043132091.png)]

RCC设置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gqshwGRc-1672045109163)(C:\Users\HP\AppData\Local\Temp\1672043160067.png)]

时钟设置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n0Mf4uVn-1672045109164)(C:\Users\HP\AppData\Local\Temp\1672043183252.png)]

usart2设置4800bit/s

在这里插入图片描述

配置串口一

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6rHyzNwS-1672045109167)(C:\Users\HP\AppData\Local\Temp\1672043270954.png)]

配置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);

}

实验效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQwQd410-1672045109169)(C:\Users\HP\AppData\Local\Temp\1672043482748.png)]

由于AHT20在不在身边,所以没有读取出来数据,如果在身边会显示温度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值