基于RS485通讯及Modbus通讯协议的温湿度变送器

news/2025/11/24 22:18:53/文章来源:https://www.cnblogs.com/huaqiwu/p/19265361

基于RS485通讯及Modbus通讯协议的温湿度变送器

组成:

1.主控芯片STC12C5A60S2

STC12C5A60S2是增强型8051CPU,1T,单时钟/机器周期,指令代码完全兼容传统8051,指令执行速度是其8-12倍,响应更快。内置1KB的EEPROM(STC89C51没有),能够直接存储配置参数(如 Modbus 从站地址、温湿度阈值),掉电不丢失。

image

2.DHT11温湿度传感器

DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,其包括一个电容式感湿元件和一个NTC测温元件。

image

VDD 供电3.3~5.5V DC
DATA 串行数据,单总线,一般接4.7k的上拉电阻
NC 空脚
GND 接地,电源负极

整体硬件设计

image

PCB设计

image

仿真

image

软件设计(以仿真为例,注意!仿真用的是STC89C51,实物用的是STC12C5A60S2,实物烧录前应修改代码头文件、引脚和延时函数)

1.软件延时

//stc89
void Delay1ms(void)	//@11.0592MHz
{unsigned char data i, j;_nop_();i = 2;j = 199;do{while (--j);} while (--i);
}void DelayXms(unsigned int xms)
{while(xms--)Delay1ms();
}void Delay30us(void)	//@11.0592MHz  
{unsigned char data i;i = 11;while (--i);
}void Delay40us(void)	//@11.0592MHz
{unsigned char data i;_nop_();i = 15;while (--i);
}// STC12C5A60S2
//void Delay1ms(void)	//@11.0592MHz
//{
//	unsigned char data i, j;
//
//	_nop_();
//	i = 11;
//	j = 190;
//	do
//	{
//		while (--j);
//	} while (--i);
//}//
//void DelayXms(unsigned int xms)
//{
//	while(xms--)
//	Delay1ms();
//}
//
//void Delay1us(void)	//@11.0592MHz
//{
//	_nop_();
//}
//
//void DelayXus(unsigned int xus)
//{
//	while(xus--)
//	Delay1us();
//}
//
//void Delay30us(void)	//@11.0592MHz
//{
//	unsigned char data i;
//
//	i = 80;
//	while (--i);
//}
//
//void Delay40us(void)	//@11.0592MHz
//{
//	unsigned char data i;
//
//	_nop_();
//	_nop_();
//	i = 107;
//	while (--i);
//}

2.DHT11采集

单总线说明

DHT11器件采用简化的单总线通信。单总线即只有一根数据线,系统中的数据交换、控制均由单总线完成。设备(主机或从机)通过一个漏极开路或三态端口连至该数据线,以允许设备在不发送数据时能够释放总线,而让其它设备使用总线;单总线通常要求外接一个约4.7kΩ的上拉电阻,这样,当总线闲置时,其状态为高电平。

由于它们是主从结极,只有主机呼叫从机时,从机才能应答,因此主机访问器件都必须严格遵循单总线序列,如果出现序列混乱,器件将不响应主机。

image

单总线传送数据位定义

DATA用于微处理器与DHT11之间的通讯和同步,采用单总线数据格式,一次传送40位数据,高位先出。

校验位数据定义

8bit校验位等于“8bit 湿度整数数据 + 8bit湿度小数数据 + 8bit温度整数数据 + 8bit温度小数数据”所得结果的末8位。

数据格式:

湿度整数数据 湿度小数数据 温度整数数据 温度整数数据 温度小数数据 校验位
8bit 8bit 8bit 8bit 8bit 8bit

其中湿度小数部分为0。

示例一:接收到的40位数据为

0011 0101 0000 0000 0001 1000 0000 0100 0101 0001
湿度高8位 湿度低8位 温度高8位 温度低8位 校验位

校验位计算:0011 0101+0000 0000+0001 1000+0000 0100= 0101 0001 即接收数据正确

湿度:0011 0101(整数)=35H=53%RH 0000 0000(小数)=00H=0.0%RH =>53%RH + 0.0%RH = 53.0%RH

温度:0001 1000(整数)=18H=24℃ 0000 0100(小数)=04H=0.4℃ =>24℃ + 0.4℃ = 24.4℃

数据时序图

用户主机(MCU)发送一次开始信号后,DHT11从低功耗模式转换到高速模式,待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信采集。

image

读取步骤

步骤一:

DHT11上电后(DHT11上电后要等待1S以越过不稳定状态在此期间不能发送任何指令),测试环境温湿度数据,并记录数据,同时 DHT11的DATA数据线由上拉电阻拉高一直保持高电平;此时 DHT11的 DATA 引脚处于输入状态,时刻检测外部信号。

步骤二:
单片机输出低电平,且低电平保持时间不能小于18ms(最大不得超过30ms),然后释放总线,等待DHT11作出回答信号,发送信号如图所示:

image

void DHT11_Rst(void)	   
{                 DHT11_DATA=0; 	      //拉低DQDelayXms(20);    	//主机拉低18~30msDHT11_DATA=1; 			//DQ=1 Delay30us();     	//主机拉高10~35us
}

步骤三:
DHT11的DATA引脚检测到外部信号有低电平时,等待外部信号低电平结束,延迟后DHT11的DATA引脚处于输出状态,输出83微秒的低电平作为应答信号,紧接着输出87微秒的高电平通知外设准备接收数据,单片机检测到有低电平(DHT11回应信号)后,等待87微秒的高电平后的数据接收,发送信号如图所示:

image

unsigned char DHT11_Check(void) 	   
{   	unsigned int t=0;while (DHT11_DATA)//DHT11会拉低40~83us{t++;_nop_();if(t>1000) return -1;}	 t=0;while (!DHT11_DATA)//判断低电平的合法性{t++;_nop_();if(t>100) return -2;}t=0;while (DHT11_DATA)//判断高电平的合法性{t++;_nop_();if(t>100) return -3;}return 0;//DHT11正常返回0}

步骤四:
由DHT11的DATA引脚输出40位数据,微处理器根据I/O电平的变化接收40位数据,位数据“0”的格式为54微秒的低电平和23-27微秒的高电平,位数据“1”的格式为:54微秒的低电平加68-74微秒的高电平。位数据“0”、“1”格式信号如图所示:

image

image

//从DHT11读取一个字节
//返回值:读到的数据
unsigned char DHT11_Read_Byte(void)    
{        unsigned char i,dat,t;dat=0;t=0;for (i=0;i<8;i++) //高位先出低位后出{while (!DHT11_DATA)//等待50us低电平{t++;_nop_();if(t>100) return 0;}t=0;Delay40us();           dat<<=1; if(DHT11_DATA==1){dat|=1;while (DHT11_DATA){t++;_nop_();if(t>100) return 0;}t=0;	}	}return dat;					    	
}//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
unsigned char DHT11_Read_Data(unsigned char *tempH,unsigned char *tempL,unsigned char *humiH,unsigned char *humiL)    
{        unsigned char buf[5];unsigned char i;DHT11_Rst();if(DHT11_Check()==0){	 for(i=0;i<5;i++)//读取40位数据{buf[i]=DHT11_Read_Byte();}Delay40us();  if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]){*humiH=buf[0];*humiL=buf[1];                       *tempH=buf[2];*tempL=buf[3];}else return -1;}else return -2;return 0;	    
}

结束信号:

DHT11的DATA引脚输出40位数据后,继续输出低电平54微秒后转为输入状态,由于上拉电阻随之变为高电平,见步骤1。

DHT11参考文献来源:[https://item.szlcsc.com/datasheet/DHT11/118309.html](DHT11 -PDF数据手册-参考资料-立创商城)

3.Modbus通讯协议

数据帧格式定义

采用 ModBus-RTU 通讯规约,格式如下:

地址码 功能码 数据区 CRC码
1 字节 1 字节 N 字节 2 字节

地址码:为变送器的地址,在通讯网络中是唯一的(出厂默认 0x01)。

数据区:数据区是具体通讯数据,注意 16bits 数据高字节在前!

CRC 码:二字节的校验码

主机问询帧结构:

image

从机应答帧结构:

image

寄存器地址说明

功能码:03 读取单个或多个寄存器

功能码:06 写单个寄存器

主机发送问询帧,从机接收后发送应答帧

举例:读取设备地址0x01的温湿度数据

问询帧:

地址码 功能码 寄存器起始地址 寄存器个数 校验码低位 校验码高位
01 03 00 00 00 01 84 0A

应答帧:

地址码 功能码 字节数 温度值 校验码低位 校验码高位
01 03 02 00 1C 8D 89

数据说明:命令中数据为十六进制,以温度值为例,00 1C 转为十进制数值为28,则真实值为28℃,其它以此类推。当温度值为负数时,数据是以补码的形式上传的。

举例:设备地址 0x01 修改为 0x02

问询帧:

地址码 功能码 寄存器地址 目标地址 校验码低位 校验码高位
01 06 07 D0 00 02 08 86

应答帧:

地址码 功能码 寄存器地址 目标地址 校验码低位 校验码高位
02 06 07 D0 00 02 08 B5
#include "modbus.h"//全局变量unsigned char temp_hum_addr=0x01;//ModBus协议报文判断解析处理
void modbus_handle(unsigned char *buf,unsigned char len)
{unsigned int crc;unsigned char crch,crcl;unsigned int reg_addr;	 //寄存器地址unsigned int reg_num; //寄存器个数if(buf[0] !=temp_hum_addr) return;else if(buf[0] == temp_hum_addr){crc = GetCRC16(buf,len-2);crch = crc >> 8; crcl = crc & 0xff;if((buf[len-2] != crch) || (buf[len - 1] != crcl)) return;switch(buf[1])  //查看功能码{case 0x03:reg_addr = (((unsigned int )buf[2]) << 8) +buf[3]; //查看寄存器switch(reg_addr){case 0x0000:reg_num = (((unsigned int )buf[4]) << 8) +buf[5];switch(reg_num){case 0x01://读温度send_buf[0] = temp_hum_addr;send_buf[1] = 0x03;send_buf[2] = 0x02;send_buf[3] = 0;send_buf[4] = tempH;crc = GetCRC16(send_buf,5);send_buf[5]=	crc&0xFF;send_buf[6]=	crc>>8;uart_send_buf(send_buf,7);break;case 0x02:send_buf[0] = temp_hum_addr; send_buf[1] = 0x03;          send_buf[2] = 0x04;          send_buf[3] = 0;             send_buf[4] = tempH;         send_buf[5] = 0;             send_buf[6] = humiH;         crc = GetCRC16(send_buf, 7); send_buf[7] = crc & 0xFF;    send_buf[8] = crc >> 8;      uart_send_buf(send_buf, 9);  break;case 0x03:send_buf[0] = temp_hum_addr; send_buf[1] = 0x03;          send_buf[2] = 0x06;          send_buf[3] = 0;             send_buf[4] = tempH;         send_buf[5] = 0;             send_buf[6] = humiH;send_buf[7] = 0;             send_buf[8] = temp_alarm;          crc = GetCRC16(send_buf, 9); send_buf[9] = crc & 0xFF;    send_buf[10] = crc >> 8;      uart_send_buf(send_buf, 11);  break;							case 0x04:send_buf[0] = temp_hum_addr; send_buf[1] = 0x03;          send_buf[2] = 0x08;          send_buf[3] = 0;             send_buf[4] = tempH;         send_buf[5] = 0;             send_buf[6] = humiH;send_buf[7] = 0;             send_buf[8] = temp_alarm;send_buf[9] = 0;             send_buf[10] = humi_alarm;           crc = GetCRC16(send_buf, 11); send_buf[11] = crc & 0xFF;    send_buf[12] = crc >> 8;      uart_send_buf(send_buf, 13);  break;}break;case 0x0001:reg_num = (((unsigned int )buf[4]) << 8) +buf[5];switch(reg_num){case 0x01://读湿度send_buf[0] = temp_hum_addr;send_buf[1] = 0x03;send_buf[2] = 0x02;send_buf[3] = 0;send_buf[4] = humiH;crc = GetCRC16(send_buf,5);send_buf[5]=	crc&0xFF;send_buf[6]=	crc>>8;uart_send_buf(send_buf,7);break;						   case 0x02:send_buf[0] = temp_hum_addr; send_buf[1] = 0x03;          send_buf[2] = 0x04;                                              send_buf[3] = 0;             send_buf[4] = humiH;send_buf[5] = 0;             send_buf[6] = temp_alarm;          crc = GetCRC16(send_buf, 7); send_buf[7] = crc & 0xFF;    send_buf[8] = crc >> 8;      uart_send_buf(send_buf, 9);  break;							case 0x03:send_buf[0] = temp_hum_addr; send_buf[1] = 0x03;          send_buf[2] = 0x06;         send_buf[3] = 0;             send_buf[4] = humiH;send_buf[5] = 0;             send_buf[6] = temp_alarm;send_buf[7] = 0;             send_buf[8] = humi_alarm;           crc = GetCRC16(send_buf, 9); send_buf[9] = crc & 0xFF;    send_buf[10] = crc >> 8;      uart_send_buf(send_buf, 11);  break;}break;case 0x07D0:reg_num = (((unsigned int )buf[4]) << 8) +buf[5];switch(reg_num){case 0x01://读地址send_buf[0] = temp_hum_addr;send_buf[1] = 0x03;send_buf[2] = 0x01;send_buf[3] = temp_hum_addr;								 crc = GetCRC16(send_buf,4);send_buf[4]=	crc&0xFF;send_buf[5]=	crc>>8;uart_send_buf(send_buf,6);break;						   case 0x02: //读地址、波特率send_buf[0] = temp_hum_addr; send_buf[1] = 0x03;          send_buf[2] = 0x02;                                              send_buf[3] = temp_hum_addr;             send_buf[4] = baud;								          crc = GetCRC16(send_buf, 5); send_buf[5] = crc & 0xFF;    send_buf[6] = crc >> 8;      uart_send_buf(send_buf, 7);  break;}	break;}break;case 0x06:reg_addr = (((unsigned int )buf[2]) << 8) +buf[3]; //查看寄存器switch(reg_addr){case 0x07D0://改地址temp_hum_addr = (((unsigned int )buf[4]) << 8) +buf[5];						send_buf[0] = temp_hum_addr;send_buf[1] = 0x06;send_buf[2] = 0x07;send_buf[3] = 0xD0;send_buf[4] = 0;send_buf[5] = temp_hum_addr;								 crc = GetCRC16(send_buf,6);send_buf[6]= crc&0xFF;send_buf[7]= crc>>8;uart_send_buf(send_buf,8);break;						   case 0x07D1://改波特率baud = (((unsigned int )buf[4]) << 8) +buf[5];						send_buf[0] = temp_hum_addr;send_buf[1] = 0x06;send_buf[2] = 0x07;send_buf[3] = 0xD1;send_buf[4] = 0;send_buf[5] = baud;								 crc = GetCRC16(send_buf,6);send_buf[6]= crc&0xFF;send_buf[7]= crc>>8;uart_send_buf(send_buf,8);break;}				break;default:break;}}
}
#ifndef __MODBUS_H__
#define __MODBUS_H__#include <REG51.H>
#include "CRC16.h"
#include "uart.h"extern unsigned char tempH,humiH,temp_alarm,humi_alarm;
extern unsigned int baud;
//问询的从机地址
//#define TEMP_HUM_ADDR 0x01//对外函数接口
void modbus_handle(unsigned char *buf,unsigned char len);
extern unsigned char temp,humi;#endif
#include"CRC16.h"
//CRC高位字节值表
unsigned char code CRCH[]=
{0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41, 0x00,0xC1,0x81, 0x40};//CRC 低位字节值表
unsigned char code CRCL[]=
{0x00,0xC0,0xC1,0x01,0xC3,0x03,0x02,0xC2,0xC6,0x06,0x07,0xC7,0x05,0xC5,0xC4,0x04,0xCC,0x0C,0x0D,0xCD,0x0F,0xCF,0xCE,0x0E,0x0A,0xCA,0xCB,0x0B,0xC9,0x09,0x08,0xC8,0xD8,0x18,0x19,0xD9,0x1B,0xDB,0xDA,0x1A,0x1E,0xDE,0xDF,0x1F,0xDD,0x1D,0x1C,0xDC,0x14,0xD4,0xD5,0x15,0xD7,0x17,0x16,0xD6,0xD2,0x12,0x13,0xD3,0x11,0xD1,0xD0,0x10,0xF0,0x30,0x31,0xF1,0x33,0xF3,0xF2,0x32,0x36,0xF6,0xF7,0x37,0xF5,0x35,0x34,0xF4,0x3C,0xFC,0xFD,0x3D,0xFF,0x3F,0x3E,0xFE,0xFA,0x3A,0x3B,0xFB,0x39,0xF9,0xF8,0x38,0x28,0xE8,0xE9,0x29,0xEB,0x2B,0x2A,0xEA,0xEE,0x2E,0x2F,0xEF,0x2D,0xED,0xEC,0x2C,0xE4,0x24,0x25,0xE5,0x27,0xE7,0xE6,0x26,0x22,0xE2,0xE3,0x23,0xE1,0x21,0x20,0xE0,0xA0,0x60,0x61,0xA1,0x63,0xA3,0xA2,0x62,0x66,0xA6,0xA7,0x67,0xA5,0x65,0x64,0xA4,0x6C,0xAC,0xAD,0x6D,0xAF,0x6F,0x6E,0xAE,0xAA,0x6A,0x6B,0xAB,0x69,0xA9,0xA8,0x68,0x78,0xB8,0xB9,0x79,0xBB,0x7B,0x7A,0xBA,0xBE,0x7E,0x7F,0xBF,0x7D,0xBD,0xBC,0x7C,0xB4,0x74,0x75,0xB5,0x77,0xB7,0xB6,0x76,0x72,0xB2,0xB3,0x73,0xB1,0x71,0x70,0xB0,0x50,0x90,0x91,0x51,0x93,0x53,0x52,0x92,0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54,0x9C,0x5C,0x5D,0x9D,0x5F,0x9F,0x9E,0x5E,0x5A,0x9A,0x9B,0x5B,0x99,0x59,0x58,0x98,0x88,0x48,0x49,0x89,0x4B,0x8B,0x8A,0x4A,0x4E,0x8E,0x8F,0x4F,0x8D,0x4D,0x4C,0x8C,0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,0x43, 0x83, 0x41, 0x81,0x80,0x40
};unsigned int GetCRC16(unsigned char *ptr,unsigned char len)
{unsigned int index;unsigned char crch = 0xFF; //高CRC 字节 unsigned char crcl = 0xFF; //低CRC 字节 while (len--) //计算指定长度的 CRC{index = crch ^*ptr++;crch = crcl^CRCH[index]; crcl = CRCL[index];}return ((crch<<8) | crcl);
}
#ifndef __CRC16_H__
#define __CRC16_H__unsigned int GetCRC16(unsigned char *ptr,unsigned char len);
#endif

5.串口收发

#include "time.h"unsigned char send_flag = 1;
unsigned char time_flag = 1;//定时器T0初始化 
void Timer0_Init(void)
{TMOD &=0xF0; //设置定时器模式 TMOD|=0x01; //设置定时器模式TL0=0x66;//设置定时初值TH0=0xFC;//设置定时初值TF0=0;//清除 TFO 标志ET0=1;//使能中断TR0=1;//定时器0开始计时
}//定时器T0中断服务程序,实现串口超时判断
void timer0_ISR(void) interrupt 1
{static unsigned int send_cnt =0; TR0=0;if(uart_recv_timer == 1){recv_timer_cnt++;//1、累加定时时间计数器if(recv_timer_cnt > MAX_REV_TIME)//2、判断定时时间是否超过了设定的最大的阈值,//超过则说明等待一段时间后没有新的数据到,//判断一包数据接收完毕{recv_timer_cnt = 0;//3、清除定时计数器处理数据清除buffer(放到数据处理之后) recv_flag=1;//置接收完成标志}}send_cnt++;if(send_cnt >=2000)//2S时间到{send_cnt =0;send_flag =1;//设置发送标志位}TL0=0x66;//设置定时初值TH0=0xFC; //设置定时初值 TR0=1;
}
#include "uart.h"
unsigned int baud=2;//默认波特率9600   0:2400 1:4800 3: 19200
//全局变量声明
unsigned char recv_flag =0;
unsigned char uart_recv_timer =0;
unsigned char recv_buf[MAX_REV_NUM]; 
unsigned char recv_cnt;
unsigned char recv_timer_cnt;
unsigned char send_buf[MAX_REV_NUM]; 
//串口初始化
void uart_init(void)
{RS485_DIR=0; //初始接收方向PCON &= 0x7F;//波特率不倍速
//	 AUXR &= 0xBF;		//定时器时钟12T模式
//	 AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器SCON=0x50;//8 位数据,可变波特率TMOD &=0x0F;//清除定时器1模式位TMOD|= 0x20; //设定定时器1为8位自动重装方式switch(baud){case 0:TL1 = 0xF4;			//设置定时初始值TH1 = 0xF4;			//设置定时重载值break;case 1:TL1 = 0xFA;			//设置定时初始值TH1 = 0xFA;			//设置定时重载值break;case 2:TL1=0xFD;//设定定时初值TH1=0xFD;//设定定时器重装值break;case 3:TL1 = 0xFE;			//设置定时初始值TH1 = 0xFE;			//设置定时重载值break;}     ET1=0;//禁止定时器1中断ES=1;//使能串口中断TR1=1;//启动定时器1}//串口发送一个字节
void uart_send_byte(unsigned char byte)
{RS485_DIR=1;//使能发送 SBUF =byte; while(!TI);TI=0;RS485_DIR=0;//发送完成转换成接收
}//串口发送字符串参数
void uart_send_string(unsigned char *str)
{while(*str != '\0'){uart_send_byte(*str++);}}//串口发送一组数据
void uart_send_buf(unsigned char *buf,unsigned char len)
{while(len--){uart_send_byte(*buf++);}}//printf串口重定向
char putchar(char c)
{uart_send_byte(c);	  return c;
}//清零接收缓冲区数组
void clr_recvbuffer(unsigned char *buf)
{unsigned char i;for(i = 0;i<MAX_REV_NUM;i++){buf[i]=0;}
}void uart_ISR() interrupt 4
{if(RI){RI=0;uart_recv_timer =1;//1、每接收一帧数据的时候,打开软件定时器,去计数 if(recv_cnt <MAX_REV_NUM)//因未采用循环队列,因此需要限制接收字符数{recv_buf[recv_cnt] =SBUF;//2、接收数据到数据缓冲区,注意缓冲区的大小范围 recv_cnt++;}else{recv_cnt =MAX_REV_NUM;}recv_timer_cnt = 0;//3、每接收一帧数据,记得把定时计数器清零相当于是喂狗信号//但是在定时中断里面会不断累加}
}

6.主函数

#include <REG51.H>
#include "LCD1602.h"
#include "delay.h"
#include "dht11.h"
#include "uart.h"
#include "time.h"
#include "modbus.h"//问询帧报文消息
unsigned char code cmd_buf[] = {0x01,0x03,0x00,0x00,0x00,0x02,0xC5,0xE9};
//从机地址01 功能码03or06 起始寄存器地址0000 读取寄存器数量0002 CRC校验码
//1字节      1字节        2字节 高前低后	 2字节				前6字节的循环冗余校验
//01 06 07D0 0002 CRC校验码
unsigned char tempH,tempL,humiH,humiL;
unsigned char temp_alarm = 40;
unsigned char humi_alarm = 65;void dis_temp_humi(void)
{LCDShowStr(0, 0, "Temp:"); if(tempH > 100)LCDWriteData(tempH/100+'0'); elseLCDWriteData(' ');LCDWriteData(tempH/10%10+'0'); LCDWriteData(tempH%10+'0'); LCDWriteData(' '); LCDWriteData('C');LCDShowStr(0,1, "Humi: "); LCDWriteData(humiH/10+'0'); LCDWriteData(humiH%10+'0');LCDWriteData(' ');	  LCDWriteData('%');
}void main()
{	  Timer0_Init();uart_init();LCDInit();StarMenu();DHT11_Rst();EA=1;while(1){// printf("11");ET0 = 0;DHT11_Read_Data(&tempH,&tempL,&humiH,&humiL);ET0 = 1;DelayXms(1000);
// 			 printf("TEMP:%bu HUMI:%bu",tempH,humiH);
//             printf("\r\n");if(recv_flag)//如果接收到数据{recv_flag = 0;//清零接收标志位 uart_recv_timer = 0;//关软件定时器			     modbus_handle(recv_buf,recv_cnt);//ModBus 解析处理 recv_cnt = 0;clr_recvbuffer(recv_buf);//清除缓冲 buffer}			  dis_temp_humi();}
}

调试结果

DHT11数据采集

image

串口通讯

使用虚拟串口。

image

modbus通讯

使用modbus调试助手。

image

实物结果

RS485通讯

IMG20251124145606

结束!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/975355.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小额支付系统:详细处理逻辑(底层)

小额支付系统:不止看懂还看透,细节里藏精华(附全流程步骤) 想了好久,决定还是写一下​小额批量支付系统​,尽管这块内容在各个平台被许多大佬普及了很多次了,或许你已经看腻了,或许你也了然于胸了,但是我觉得…

“大概率上涨”的推荐

基于提供的CSV文件数据,我整合并筛选了“大概率上涨”的推荐股票列表。筛选标准侧重于模型预测的一致性和出现频率(即上涨比例),同时考虑平均上涨概率和测试准确率。从数据中,我发现两只股票(sz.000798 和 sh.60…

Day1 Scrum冲刺博客

Day1 Scrum冲刺博客 1. 任务安排与感想成员 角色定位 主要负责任务 认领任务 明日任务 敏捷开发前的感想王韵清 算法开发 数据爬取、大模型调用与后端联调 1. 负责通知公告、学校系统等页面的爬虫开发。2. 实现后端 AP…

六、设备树与设备树插件

一、概述设备树主要文件:dts源文件,dtsi头文件,dtc编译工具、dts输出文件 dtc -I dts -O dtb 1.dts -o 1.dtb这是编译命令,dtc -I dtb -O dts 1.dtb -o 1.dts这是反编译命令 平台设备与驱动匹配的优先级:of_match…

【设计模式笔记06】:单一职责原则 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

102302124_严涛_作业3

作业1. (1)代码及结果截图: 单线程代码:点击查看代码 import requests from bs4 import BeautifulSoup import os import time from urllib.parse import urljoin, urlparseclass SingleThreadImageCrawler:def __…

CF1799G Count Voting 笔记

容斥好题。 发现本题限制有点多,其中棘手的是不能投给自己阵营的人,不然考虑每个人投给了谁,最终答案就是一个可重集的排列数。 于是考虑容斥,考虑求出至少有 \(i\) 人投给了自己阵营的人的方案数。 那么可以做背包…

2025年11月美国本科申请机构深度测评:藤校Offer领航者全解析

2025年11月美国本科申请机构深度测评:藤校Offer领航者全解析美国本科申请向来以流程复杂、竞争激烈著称,从标化考试规划、文书打磨到背景提升、院校匹配,每一步都关乎申请成败。专业的申请机构能凭借对美国高校招生…

20251124 - 月度检测 总结

20251124 - 月度检测 总结题号 实际分数 应得分数 罚时A AC AC 0B AC AC 0C AC AC 0D WA AC nullE AC AC -2F null AC nullG null null/AC nullH null null null后面不用看了! null null null一句话总结:CF 思维的题…

2026美国硕士留学中介推荐:从背景提升到签证获批全程护航!

2026美国硕士留学中介推荐:从背景提升到签证获批全程护航!美国硕士留学以其优质的教育资源、灵活的培养模式及广阔的职业发展空间,成为全球学子的热门选择。然而,2026年美国硕士申请竞争更趋激烈,藤校及TOP30院校…

踩坑日记20251124

踩坑日记20251124 1.51单片机C语言 typedef union {uint8_t Byte; // 整体访问8位struct {uint8_t B0 : 1; // 位域访问每一位uint8_t B1 : 1;uint8_t B2 : 1;uint8_t B3 : 1;uint8_t B4 : 1;uint8_t B5 : 1;u…

2025年度楼梯厂商推荐榜单与选择指南:一份基于行业专业数据的权威分析报告,整木/实木/原木等材质楼梯十大主流供应商解析

随着人们对家居生活品质要求的不断提升,楼梯行业迎来了快速发展的机遇。本榜单基于产品品质、设计创新、服务体系、行业影响力四大维度,结合行业权威数据及消费者反馈,对2025年十大楼梯品牌的综合实力进行深度解析,…

Consciousness Preservation and Synthetic Life

people know AI just can not propose questions. so this is the question. bios, βίος, life, because synthesis concept is various. F is synthesis. so the analytical question is consciousness preservat…

详细介绍:Nginx 高效动静分离:从原理到实战

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025美国留学中介实测榜单:从藤校到小众专业,核心竞争力深度对比!

2025美国留学中介实测榜单:从藤校到小众专业,核心竞争力深度对比!美国凭借顶尖的教育资源、灵活的培养体系及广阔的发展空间,始终是全球留学生的首选目的地之一。但美国高校申请涉及“整体评估”录取逻辑、复杂的文…

2025美国留学机构TOP榜:从申请到就业的全链条护航者

2025美国留学机构TOP榜:从申请到就业的全链条护航者美国凭借顶尖的教育资源、多元的文化环境及广阔的职业发展空间,始终是全球学子留学的首选目的地。但其高校申请体系复杂,藤校及TOP30名校竞争白热化,签证政策动态…

MySQL 数据备份 - 教程

MySQL 数据备份 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", &quo…

复制 deepseek think 思考 内容 的方法

复制 deepseek think 思考 内容 的方法 框选,复制,然后直接ctrl+v粘贴到博客园的输入框中就ok了我们之前讨论的是EHCI控制器和USB通信的细节。现在问题聚焦在qTD与管道的关系上。在USB术语中,管道(Pipe)是对应于一个…

狂神说Java(基础版)

记录下java基础的学习创建项目 首先创建一个空项目 命名注意不能有中文但是一个空项目里面我们右键连新的类都不能创建于是我们就要新建一个模块(Moudle)选中java然后一路next,记得命名创建完成之后就可以看到我们熟…

2025优质留学中介全景推荐:从藤校OFFER到职业落地,谁是你的专属引路人?

2025优质留学中介全景推荐:从藤校OFFER到职业落地,谁是你的专属引路人?在全球化教育趋势下,留学申请早已不是单一的材料递交,而是涵盖院校定位、背景提升、文书打磨、签证办理及后续发展的系统工程。专业的留学中…