基于Modbus-RTU通信协议读取RS485温湿度传感器主机功能(源码可直接移植)

目录

1.  概述

2.  代码编写

2.1  数据发送

2.2  定时器修改

2.3  数据接收

2.4  验证


        之前我们详细介绍了Modbus-RTU协议,从机代码部分,从机代码可以参考:

基于Modbus-RTU通信协议实现RS485设备之间的通信(源码可直接移植)-优快云博客

        下面我们对其主机如何编写做一个简单的说明,本章只是对03功能的码的使用,更多功能码,可以参考上方链接的功能码部分,根据本章思路进行拓展。

注:本章只是对之前从机篇章的一个延伸,只是对用到的部分进行简单描述,详细描述可以参考上方链接,搞懂从机的收发原理,主机其实就是加了一个数据发送。

1.  概述

        进行一个简单的描述,Modbus 串行链路协议是一个主-从协议。在同一时刻,只有一个主节点连接于总线,一个或多个子节点 (最大编号为 247 ) 连接于同一个串行总线。

        主节点以两种模式对子节点发出 Modbus 请求:单播模式和广播模式

        我们想要实现对某一从机设备温湿度数据的采集,需要采用单播模式, 主节点以特定地址访问某个子节点,子节点接到并处理完请求后,子节点向主节点返回一个报文(一个'应答')。在这种模式,一个 Modbus 事务处理包含 2 个报文:一个来自主节点的请求,一个来自子节点的应答。

        这里我找了一个就有Modbus协议的温湿度传感器,其应答方式为,主机需要发送的问询帧:

地址码功能码起始地址数据长度校验码低位校验码高位
0x010x030x00 0x000x00 0x020xC40x0B

        从机也就是温湿度传感器做出应答,假如此时温度值为-10.1℃,湿度为65.8%RH,那么此时从机的应道数据为:

地址码功能码返回有效字节数湿度值温度值校验码低位校验码高位
0x010x030x040x02 0x920xFF 0x9B0x5A0x3D

温度计算:
当温度低于 0 ℃ 时温度数据以补码的形式上传。
温度:FF9B H(十六进制)= -101 => 温度 = -10.1℃
湿度计算:
湿度:292 H (十六进制)= 658 => 湿度 = 65.8%RH

2.  代码编写

        大概了解一下主机需要发送的数据,我们直接开始编写代码,这里可以直接使用我们之前编写的从机代码,在其上直接修改。

2.1  数据发送

        我们知道,在Modbus当中,从机设备不能主动向主机设备发送数据,因此需要主机设备向从机设备发送问询帧,告诉从机设备需要向我发送什么数据,这里我们可以直接使用之前写RS485的时候写的数组发送函数:

//串口发送一个数组
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	USART_REDE_TX_MODE_H;

	for (i = 0; i < Length; i ++)		//遍历数组
	{
		Serial_SendByte(Array[i]);		//依次调用Serial_SendByte发送每个字节数据
	}

	USART_REDE_RX_MODE_L;
}

        通过创建一个数组进行数据发送,如:

arr[8] = {0x01,0x03,0x00,0x00,0x00,0x02,0xC4,0x0B};

Serial_SendArray(arr,8);

        不过这样会有一个不方便的一点,不利于后续调整,因为发送的数据后两位是CRC校验后的数据,如果我们改动前方的数据,还要去自己校验CRC的值进行填写,不方便后续调整数据,因此我对发送的数据进行整合一下,首先创建如下几个参数:

 	uint8_t Host_Txbuf[8];	     //modbus发送数组	
 	uint8_t slave_add;		       //要匹配的从机设备地址(做主机实验时使用)

        创建函数:

//参数1从机,参数2起始地址,参数3寄存器个数
void Host_Read03_slave(uint8_t slave,uint16_t StartAddr,uint16_t num)
{
}

        对所要发送的数据,根据上方表格:

地址码功能码起始地址数据长度校验码低位校验码高位
0x010x030x00 0x000x00 0x020xC40x0B
		modbus.slave_add = slave;
		modbus.Host_Txbuf[Send_03_j++] = slave;//这是要匹配的从机地址
		modbus.Host_Txbuf[Send_03_j++] = 0x03;//功能码
		modbus.Host_Txbuf[Send_03_j++] = StartAddr/256;//起始地址高位
		modbus.Host_Txbuf[Send_03_j++] = StartAddr%256;//起始地址低位
		modbus.Host_Txbuf[Send_03_j++] = num/256;//寄存器个数高位
		modbus.Host_Txbuf[Send_03_j++] = num%256;//寄存器个数低位

		crc = crc16(modbus.Host_Txbuf, Send_03_j); //获取CRC校验位
		modbus.Host_Txbuf[Send_03_j++] = crc/256;//寄存器个数高位
		modbus.Host_Txbuf[Send_03_j++] = crc%256;//寄存器个数低位

        将封装好的数据发送出去:

		//发送数据包装完毕
		USART_REDE_TX_MODE_H;//RS485转为发送状态

		for(Send_03_i = 0; Send_03_i < Send_03_j + 1; Send_03_i++)
		{
			Serial_SendByte(modbus.Host_Txbuf[Send_03_i]);
		}

		USART_REDE_RX_MODE_L;//RS485恢复接收状态

        此时的发送函数:

//主机选择从机
//参数1从机,参数2起始地址,参数3寄存器个数
void Host_Read03_slave(uint8_t slave,uint16_t StartAddr,uint16_t num)
{
	uint16_t Send_03_j = 0;
	uint16_t Send_03_i = 0;
	uint16_t crc;//计算的CRC校验位

	modbus.slave_add = slave;
	modbus.Host_Txbuf[Send_03_j++] = slave;//这是要匹配的从机地址
	modbus.Host_Txbuf[Send_03_j++] = 0x03;//功能码
	modbus.Host_Txbuf[Send_03_j++] = StartAddr/256;//起始地址高位
	modbus.Host_Txbuf[Send_03_j++] = StartAddr%256;//起始地址低位
	modbus.Host_Txbuf[Send_03_j++] = num/256;//寄存器个数高位
	modbus.Host_Txbuf[Send_03_j++] = num%256;//寄存器个数低位

	crc = crc16(modbus.Host_Txbuf, Send_03_j); //获取CRC校验位
	modbus.Host_Txbuf[Send_03_j++] = crc/256;//寄存器个数高位
	modbus.Host_Txbuf[Send_03_j++] = crc%256;//寄存器个数低位
	
	//发送数据包装完毕
	USART_REDE_TX_MODE_H;//RS485转为发送状态

	for(Send_03_i = 0; Send_03_i < Send_03_j + 1; Send_03_i++)
	{
		Serial_SendByte(modbus.Host_Txbuf[Send_03_i]);
	}

	USART_REDE_RX_MODE_L;//RS485恢复接收状态

}

        这样我们只要在主函数调用该函数进行数据发送即可:

		Host_Read03_slave(0x01,0x0000,0x0002);

2.2  定时器修改

        上面数据我们会发现主机发送数据过于频繁,不能有效处理数据,并且对于温湿度这些数据,也不需要过于实时发送,因此我们可以加一个定时器,让主机1s发送一次数据,方便数据处理,声明变量:

 	uint8_t Host_send_flag;      //主机设备发送数据完毕标志位
 	int Host_Sendtime;           //发送完一帧数据后时间计数
 	uint8_t Host_time_flag;      //发送时间到标志位,=1表示到发送数据时间了
 	uint8_t Host_End;            //接收数据后处理完毕

        来到定时器服务函数,添加代码:

		modbus.Host_Sendtime++;//发送完上一帧后的时间计数

		if(modbus.Host_Sendtime > 1000)//距离发送上一帧数据1s
		{
			//1s时间到
			modbus.Host_time_flag = 1;//发送数据标志位位置1
			modbus.Host_Sendtime = 0;//清空计数器
		}		

        对于发送函数也要进行修改,添加一个计数器的标志位:

void Host_Read03_slave(uint8_t slave,uint16_t StartAddr,uint16_t num)
{
	uint16_t Send_03_j = 0;
	uint16_t Send_03_i = 0;
	uint16_t crc;//计算的CRC校验位

	if(modbus.Host_time_flag == 1)
	{
		modbus.slave_add = slave;
		modbus.Host_Txbuf[Send_03_j++] = slave;//这是要匹配的从机地址
		modbus.Host_Txbuf[Send_03_j++] = 0x03;//功能码
		modbus.Host_Txbuf[Send_03_j++] = StartAddr/256;//起始地址高位
		modbus.Host_Txbuf[Send_03_j++] = StartAddr%256;//起始地址低位
		modbus.Host_Txbuf[Send_03_j++] = num/256;//寄存器个数高位
		modbus.Host_Txbuf[Send_03_j++] = num%256;//寄存器个数低位

		crc = crc16(modbus.Host_Txbuf, Send_03_j); //获取CRC校验位
		modbus.Host_Txbuf[Send_03_j++] = crc/256;//寄存器个数高位
		modbus.Host_Txbuf[Send_03_j++] = crc%256;//寄存器个数低位
		
		//发送数据包装完毕
		USART_REDE_TX_MODE_H;//RS485转为发送状态

		for(Send_03_i = 0; Send_03_i < Send_03_j + 1; Send_03_i++)
		{
			Serial_SendByte(modbus.Host_Txbuf[Send_03_i]);
		}

		USART_REDE_RX_MODE_L;//RS485恢复接收状态

		modbus.Host_time_flag = 0;//时间标志位清零
		modbus.Host_send_flag=1;//表示发送数据完毕
	}
}

2.3  数据接收

        主机发送完问询帧以后,就需要去应答,也就是接收从机发送的信号,对于应道可以直接套从机的接收部分:


//做一些数据处理
void Send_03_Data_Resive(void)
{

}

//主机接收从机的消息进行处理
void HOST_ModbusRX(void)
{
	u16 crc,rccrc;//计算crc和接收到的crc

  if(modbus.Modbus_RX_Flag == 0)  //如果接收未完成则返回空
	{
	   return;
	}
	//接收数据结束
	
	//(数组中除了最后两位CRC校验位其余全算)
	crc = crc16(&modbus.Modbus_RX_BUFF[0],modbus.Modbus_RX_recount-2); //获取CRC校验位
	rccrc = modbus.Modbus_RX_BUFF[modbus.Modbus_RX_recount-2]*256+modbus.Modbus_RX_BUFF[modbus.Modbus_RX_recount-1];//计算读取的CRC校验位
	
	if(crc == rccrc) //CRC检验成功 开始分析包
	{	
	   if(modbus.Modbus_RX_BUFF[0] == modbus.slave_add)  // 检查地址是是对应从机发过来的
		 {
			switch(modbus.Modbus_RX_BUFF[1])//分析功能码
			{
				case 0: break;
				case 1: break;
				case 2: break;
				case 3: Send_03_Data_Resive();break;//3号功能码处理
				case 4: break;
				case 5: break;
				case 6: break;
				case 7: break;	
				default:break;				
			}
		 }
		 
	}	
	 modbus.Modbus_RX_recount = 0;//接收计数清零
   modbus.Modbus_RX_Flag = 0; //接收标志清零
}

2.4  验证

        让我们来主函数,看看数据能不能正常处理,对于应道数据:

地址码功能码返回有效字节数湿度值温度值校验码低位校验码高位
0x010x030x040x02 0x920xFF 0x9B0x5A0x3D

        可以看出数组的3,4,5,6是温湿度数据,我们将数据合并,并且强转为十进制表示的是具体数据:

	  RX_shi_str = (modbus.Modbus_RX_BUFF[3]<< 8) | modbus.Modbus_RX_BUFF[4];				
	  RX_wen_str= (modbus.Modbus_RX_BUFF[5]<< 8) | modbus.Modbus_RX_BUFF[6];		

	  RX_shi=(int)RX_shi_str;
	  RX_wen=(int)RX_wen_str;		

        完整主函数代码:

#include "stm32f10x.h"  

#include "RS485_USART1.h"
#include "OLED.h"
#include "OLED_Data.h"

#include "Modbus.h"
#include "Modbus_CRC.h"
          
#include "Delay.h"  
#include "Timer.h"

unsigned int RX_wen_str,RX_shi_str;
unsigned int RX_wen,RX_shi;

int main(void)
{	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//定时器分组	
		
	My_RS485_Init();//RS485初始化
	Modbus_Init();//Modbus地址初始化

	OLED_Init();//OLED初始化
	OLED_Clear();//清屏	

	Timer_Init();//定时器初始化

	while (1)
	{
//		Modbus_Data_Treat();

		Host_Read03_slave(0x01,0x0000,0x0002);
		HOST_ModbusRX();

	  RX_shi_str = (modbus.Modbus_RX_BUFF[3]<< 8) | modbus.Modbus_RX_BUFF[4];				
		RX_wen_str= (modbus.Modbus_RX_BUFF[5]<< 8) | modbus.Modbus_RX_BUFF[6];		

		RX_shi=(int)RX_shi_str;
		RX_wen=(int)RX_wen_str;		

//		OLED_ShowHexNum(0, 0, modbus.Modbus_RX_BUFF[0], 2, OLED_8X16);
//		OLED_ShowHexNum(24, 0, modbus.Modbus_RX_BUFF[1], 2, OLED_8X16);
//		OLED_ShowHexNum(48, 0, modbus.Modbus_RX_BUFF[2], 2, OLED_8X16);
//		OLED_ShowHexNum(72, 0, modbus.Modbus_RX_BUFF[3], 2, OLED_8X16);

//		OLED_ShowHexNum(0, 24, modbus.Modbus_RX_BUFF[4], 2, OLED_8X16);
//		OLED_ShowHexNum(24, 24, modbus.Modbus_RX_BUFF[5], 2, OLED_8X16);
//		OLED_ShowHexNum(48, 24, modbus.Modbus_RX_BUFF[6], 2, OLED_8X16);
//		OLED_ShowHexNum(72, 24, modbus.Modbus_RX_BUFF[7], 2, OLED_8X16);

		OLED_ShowNum(0, 0, RX_shi, 4, OLED_8X16);
		OLED_ShowNum(72, 0, RX_wen, 4, OLED_8X16);
		OLED_Update();
	}
}

        看一下效果,OLED屏幕数据除以十就是正常数据:

        我们吹口气,数据正常改变:

        能够正常通信,同样的道理可以看看其他功能码,这里就不在一一验证了。

主机代码:

基于STM32实现modbus-RTU主机通信.zip资源-优快云下载

Modbus_时光の尘的博客-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光の尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值