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

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

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

PCB设计

仿真

软件设计(以仿真为例,注意!仿真用的是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Ω的上拉电阻,这样,当总线闲置时,其状态为高电平。
由于它们是主从结极,只有主机呼叫从机时,从机才能应答,因此主机访问器件都必须严格遵循单总线序列,如果出现序列混乱,器件将不响应主机。

单总线传送数据位定义
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的数据,并触发一次信采集。

读取步骤
步骤一:
DHT11上电后(DHT11上电后要等待1S以越过不稳定状态在此期间不能发送任何指令),测试环境温湿度数据,并记录数据,同时 DHT11的DATA数据线由上拉电阻拉高一直保持高电平;此时 DHT11的 DATA 引脚处于输入状态,时刻检测外部信号。
步骤二:
单片机输出低电平,且低电平保持时间不能小于18ms(最大不得超过30ms),然后释放总线,等待DHT11作出回答信号,发送信号如图所示:

void DHT11_Rst(void)
{
DHT11_DATA=0; //拉低DQ
DelayXms(20); //主机拉低18~30ms
DHT11_DATA=1; //DQ=1
Delay30us(); //主机拉高10~35us
}
步骤三:
DHT11的DATA引脚检测到外部信号有低电平时,等待外部信号低电平结束,延迟后DHT11的DATA引脚处于输出状态,输出83微秒的低电平作为应答信号,紧接着输出87微秒的高电平通知外设准备接收数据,单片机检测到有低电平(DHT11回应信号)后,等待87微秒的高电平后的数据接收,发送信号如图所示:

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”格式信号如图所示:


//从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 码:二字节的校验码
主机问询帧结构:

从机应答帧结构:

寄存器地址说明
功能码: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');
else
LCDWriteData(' ');
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数据采集

串口通讯
使用虚拟串口。

modbus通讯
使用modbus调试助手。

实物结果
RS485通讯

结束!!!
浙公网安备 33010602011771号