温湿度传感器 DHT11

文章介绍了DHT11传感器的工作原理和通信协议,展示了如何使用C语言处理接收到的温湿度数据并在LCD上显示,以及如何通过串口将数据发送给蓝牙模块。在实现过程中,逐步优化了代码结构,通过分文件编程使代码更精简易读。
摘要由CSDN通过智能技术生成

DHT11介绍

 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。

特点:相对温湿度测量;全校准;数字输出;长期稳定;20米超长信号传输距离;超低能耗;完全互换直接出结果(比如以前超声波测距模块就需要代码转化一下结果,这个就不用)。

接线:只有三根线,VCC,GND和DATA,非常好接

数据传输逻辑:只有一根数据线进行数据接收和发送,模块一次完整的数据为40bit高位先出

数据格式8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验和

DHT11的通讯过程时序

总体过程:

激活过程:(使得DHT11由低功耗转到高速模式并开始测量)

激活过程实现: 

void Start()
{	
	dht = 1;
	dht = 0;
	Delay30ms();
	dht = 1;
	
	while(dht == 1);//不断读取直到DHT再次变低,说明模块响应
	while(dht == 0); //不断读取直到DHT再次拉高,80us之后,再经过50us低电平,就代表要开始变高并开始传输数据了
	while(dht == 1);//不断读取直到DHT再次变低,说明数据传输前的50us低电平开始了

}

数字‘0’信号表示:

数字 ‘1’信号表示:

 其实核心思路就是等待dht信号拉高,之后延时40us,如果还是高电平就是‘1’,不然就是‘0’:

数字01检测实现:

void detect_01()
{
	
	while(dht == 0); //不断读取上拉的一瞬间,也可以写成while(!dht);

	Delay40us();//高电平持续26-28us是‘0’,高电平持续70us是'1',所以delay40us之后观察还是否是高电平

	if(dht == 1){//'1'
	    //
		
		while(dht == 1); //不断读取下拉的一瞬间,因为要延迟80us,如果提前结束,下一次detect01就会出错,因为detect01是从判断上拉开始的
		
	}else if(dht == 0){ //'0'
		//
		
	}
	
}

完整的代码实现

实现1:将温度8个bit显示在LCD上面一行,将湿度8个bit显示再LCD下面一行

#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include <string.h>


sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册
sbit D5 = P3^7;

sbit dht = P3^3; //data数据线
char flag_start = 0;
char buffer;
char wendu[16];
char shidu[16];

int i = 0;

///
#define databuffer P0 //定义8位数据线D0-D7

sbit RS = P1^0;
sbit RW = P1^1;
sbit E = P1^4;
sbit D7 = P0^7;
char flag_w;
char flag_busy = 0;

void Delay5ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 9;
	j = 244;
	do
	{
		while (--j);
	} while (--i);
}


void write(char flag_w, char cmd) //write(1,cmd)代表写内容;write(0,cmd)代表写指令
{
	RW = 0;//全程置0
	
	if(flag_w == 1){
		RS = 1; //写内容
		E = 0;
		_nop_(); //2微秒左右	
		
		databuffer = cmd; //根据时序图,E为0时开始传内容
		
		E = 1;
		_nop_();
		E = 0;
		_nop_();
	}
	
	if(flag_w == 0){
		RS = 0; //写指令
		E = 0;
		_nop_(); //2微秒左右
		
		databuffer = cmd; //根据时序图,E为0时开始传内容
		
		E = 1;
		_nop_();		
		E = 0;
		_nop_();
	}
	
}

void ifBusy() //根据手册,读操作读取并判定忙信号
{
	char tmp;
	
	RS = 0;
	RW = 1;
	E = 0;
	_nop_(); //2微秒左右			
	E = 1;
	_nop_();
	
	tmp = databuffer; //根据时序图,E为1时开始读
	if((tmp & 0x80) == 0x80){//BF(D7)位为高电平,代表忙
		flag_busy = 1;
	}else{
		flag_busy = 0;
	}
	
	E = 0;
	_nop_();
	
	
}

void detectBusy()
{
	databuffer = 0x80; //一定要先将databuffer置为忙
	
	ifBusy(); //先检查一次是否忙并获得flag_busy的初值
	while(flag_busy == 1){ //然后如果flag_busy不变回0,就一直检查是否busy
		ifBusy();
	}
}

void Init()
{
	Delay5ms();
	Delay5ms();
	Delay5ms();
	write(0,0x38);
	Delay5ms();
	
	detectBusy();
	write(0,0x38);
	detectBusy();
	write(0,0x08);
	detectBusy();
	write(0,0x01);
	detectBusy();
	write(0,0x06);
	detectBusy();
	write(0,0x0C);
	
}


void showStr(char *msg, char hang, char lie)
{
	char pos;
	
	if(hang == 0){//如果第一行
			pos = 0x80 + 0x00 + lie; 
	}else if(hang == 1){//如果第二行
			pos = 0x80 + 0x40 + lie;
	}
	detectBusy();
	write(0,pos); //只要定下开始的位置,之后光标会自行移动
	
	while(*msg != '\0'){
		detectBusy();
		write(1,*msg);
		msg++;
	}
		
}
//

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


void Delay30ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

void Delay40us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 15;
	while (--i);
}

void Start()
{	
	dht = 1;
	dht = 0;
	Delay30ms();
	dht = 1;
	
	while(dht == 1);//不断读取直到DHT再次变低,说明模块响应
	while(dht == 0); //不断读取直到DHT再次拉高,80us之后,再经过50us低电平,就代表要开始变高并开始传输数据了
	while(dht == 1);//不断读取直到DHT再次变低,说明数据传输前的50us低电平开始了

}


void detect_01()
{
	
	while(dht == 0); //不断读取上拉的一瞬间

	Delay40us();//高电平持续26-28us是‘0’,高电平持续70us是'1',所以delay40us之后观察还是否是高电平

	if(dht == 1){//'1'
	
		if(i < 16){
			shidu[i] = '1';
		}else{  //else if((i > 15)&&(i < 32)){ 
			wendu[i-16] = '1'; //i-16的细节千万别忘!!!!
		}
		
		while(dht == 1); //不断读取下拉的一瞬间,因为要延迟80us,如果提前结束,下一次detect01就会出错,因为detect01是从判断上拉开始的
		
	}else if(dht == 0){ //'0'
		
		if(i < 16){
			shidu[i] = '0';
		}else{ 
			wendu[i-16] = '0';
		}
		
	}
	
	i++;
	
}

void showDataFromDHT()
{
	int k = 0;
	i = 0;//由于detect01没有i的清零,所以再每次运行这个函数的开始先清零
	
	Start();
	
	for(k = 0; k < 40; k++){ //接收一组完整的数据
		detect_01(); 
		//现在的问题:循环出不去
		D5 = 0; //检测中亮灯
	}
	
	D5 = 1; //检测完灭灯
	showStr(wendu, 0, 0);
	showStr(shidu, 1, 0);	
}

void main()
{
	Delay1000ms();//越过不稳定状态

	Init();//LCD显示模块初始化
	
	while(1){
		showDataFromDHT();
		Delay1000ms();
	}
		
}

实现效果1:

PS:虽然成功完成了想要的效果,但是代码其实存在一定问题: 在我的detect_01函数里,只要赋值wendu[ ],程序就会卡死在这个函数里,很奇怪,唯一的解决办法是在前16位之后直接else不加任何条件赋值,这样虽然可以成功显示,但是明显逻辑不对,因为最后8位明明是校验位,我觉得我可能犯了啥C语言低级错误,但是我好像就是没有发觉,例如:以下两种if else写法就会卡死:

其次,这种实现方法看起来非常不友好,所以还有很大改进空间 

 实现2:将温度和湿度进行数据的转化变成更易读的方式同样显示在LCD的两行

#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include <string.h>


sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册
sbit D5 = P3^7;

sbit dht = P3^3; //data数据线
char buffer;
char datas[5];
char temp[9];
char huma[9];


///
#define databuffer P0 //定义8位数据线D0-D7

sbit RS = P1^0;
sbit RW = P1^1;
sbit E = P1^4;
sbit D7 = P0^7;
char flag_w;
char flag_busy = 0;

void Delay5ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 9;
	j = 244;
	do
	{
		while (--j);
	} while (--i);
}


void write(char flag_w, char cmd) //write(1,cmd)代表写内容;write(0,cmd)代表写指令
{
	RW = 0;//全程置0
	
	if(flag_w == 1){
		RS = 1; //写内容
		E = 0;
		_nop_(); //2微秒左右	
		
		databuffer = cmd; //根据时序图,E为0时开始传内容
		
		E = 1;
		_nop_();
		E = 0;
		_nop_();
	}
	
	if(flag_w == 0){
		RS = 0; //写指令
		E = 0;
		_nop_(); //2微秒左右
		
		databuffer = cmd; //根据时序图,E为0时开始传内容
		
		E = 1;
		_nop_();		
		E = 0;
		_nop_();
	}
	
}

void ifBusy() //根据手册,读操作读取并判定忙信号
{
	char tmp;
	
	RS = 0;
	RW = 1;
	E = 0;
	_nop_(); //2微秒左右			
	E = 1;
	_nop_();
	
	tmp = databuffer; //根据时序图,E为1时开始读
	if((tmp & 0x80) == 0x80){//BF(D7)位为高电平,代表忙
		flag_busy = 1;
	}else{
		flag_busy = 0;
	}
	
	E = 0;
	_nop_();
	
	
}

void detectBusy()
{
	databuffer = 0x80; //一定要先将databuffer置为忙
	
	ifBusy(); //先检查一次是否忙并获得flag_busy的初值
	while(flag_busy == 1){ //然后如果flag_busy不变回0,就一直检查是否busy
		ifBusy();
	}
}

void Init()
{
	Delay5ms();
	Delay5ms();
	Delay5ms();
	write(0,0x38);
	Delay5ms();
	
	detectBusy();
	write(0,0x38);
	detectBusy();
	write(0,0x08);
	detectBusy();
	write(0,0x01);
	detectBusy();
	write(0,0x06);
	detectBusy();
	write(0,0x0C);
	
}


void showStr(char *msg, char hang, char lie)
{
	char pos;
	
	if(hang == 0){//如果第一行
			pos = 0x80 + 0x00 + lie; 
		}else if(hang == 1){//如果第二行
			pos = 0x80 + 0x40 + lie;
		}
		detectBusy();
		write(0,pos); //只要定下开始的位置,之后光标会自行移动
	
	while(*msg != '\0'){
		detectBusy();
		write(1,*msg);
		msg++;
	}
		
}
//

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


void Delay30ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

void Delay40us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 15;
	while (--i);
}

void Start()
{	
	dht = 1;
	dht = 0;
	Delay30ms();
	dht = 1;
	
	while(dht == 1);//不断读取直到DHT再次变低,说明模块响应
	while(dht == 0); //不断读取直到DHT再次拉高,80us之后,再经过50us低电平,就代表要开始变高并开始传输数据了
	while(dht == 1);//不断读取直到DHT再次变低,说明数据传输前的50us低电平开始了

}


void showDataFromDHT()
{
	int i;//轮
	int j;//每一轮读多少次
  char tmp;
	char flag;
	
	Start();
	for(i= 0;i < 5;i++){ //5组数据
		for(j=0;j<8;j++){ //每组数据8位
			while(!dht);//等待上拉
			Delay40us();//高电平持续26-28us是‘0’,高电平持续70us是'1',所以delay40us之后观察还是否是高电平
			if(dht == 1){
				flag = 1;
				while(dht);//不断读取下拉的一瞬间,因为要延迟80us,如果提前结束,下一次detect01就会出错,因为detect01是从判断上拉开始的
			}else{
				flag = 0;
			} 
			tmp = tmp << 1; //tmp左移一位,再最右侧空出一位
			tmp |= flag; //将Flag的值写入空出的一位,这样经历8次,就得到了完整的tmp,且第一个读入的数据就是最高位,符合DHT的数据传输逻辑
			//!!使用这种移位再赋值的方法可以得到的一个完整的char型数据,而不是像之前char xxx[8]一样,得到的是8个char型的字符串,不利于数据转化!!
		}
		datas[i] = tmp;
	}
	
}

void Build_Datas() //由于刚刚存储的是8位char型的数字,但是不管是蓝牙还是LCD,需要显示的都是字符,而比如数字“9”和字符"9"是不同的二进制表示,所以,需要转化
{
	huma[0] = 'H';
	huma[1] = ':';
	huma[2] = datas[0]/10 + 0x30; //查阅ASCII码可知,数字Y加上0x30就是字符Y
	huma[3] = datas[0]%10 + 0x30; //同时由于不管整数或小数,都使用两位数字来表示,因此需要对‘十位’和‘个位’分别进行提取再转化成字符
	huma[4] = '.';
	huma[5] = datas[1]/10 + 0x30;
	huma[6] = datas[1]%10 + 0x30;
	huma[7] = '%';
	huma[8] = '\0';
	
	temp[0] = 'T';
	temp[1] = ':';
	temp[2] = datas[2]/10 + 0x30;
	temp[3] = datas[2]%10 + 0x30;
	temp[4] = '.';
	temp[5] = datas[3]/10 + 0x30;
	temp[6] = datas[3]%10 + 0x30;
	temp[7] = 'C';
	temp[8] = '\0';
	
}

void main()
{
	Delay1000ms();//越过不稳定状态

	Init();//LCD显示模块初始化
	
	while(1){
		Delay1000ms();
		showDataFromDHT();	
		Build_Datas();
		showStr(temp,0,4);
		showStr(huma,1,4);
	}
}

 实现效果2:

PS:显然,这种方法一下子变得通俗易懂了!

实现3:在实现2的基础上,LCD显示温湿度的同时,通过串口使用蓝牙模块接收温湿度信息

#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include <string.h>


sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册
sbit D5 = P3^7;

sbit dht = P3^3; //data数据线
char buffer;
char datas[5];
char temp[9];
char huma[9];


///
#define databuffer P0 //定义8位数据线D0-D7

sbit RS = P1^0;
sbit RW = P1^1;
sbit E = P1^4;
sbit D7 = P0^7;
char flag_w;
char flag_busy = 0;

void Delay5ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 9;
	j = 244;
	do
	{
		while (--j);
	} while (--i);
}


void write(char flag_w, char cmd) //write(1,cmd)代表写内容;write(0,cmd)代表写指令
{
	RW = 0;//全程置0
	
	if(flag_w == 1){
		RS = 1; //写内容
		E = 0;
		_nop_(); //2微秒左右	
		
		databuffer = cmd; //根据时序图,E为0时开始传内容
		
		E = 1;
		_nop_();
		E = 0;
		_nop_();
	}
	
	if(flag_w == 0){
		RS = 0; //写指令
		E = 0;
		_nop_(); //2微秒左右
		
		databuffer = cmd; //根据时序图,E为0时开始传内容
		
		E = 1;
		_nop_();		
		E = 0;
		_nop_();
	}
	
}

void ifBusy() //根据手册,读操作读取并判定忙信号
{
	char tmp;
	
	RS = 0;
	RW = 1;
	E = 0;
	_nop_(); //2微秒左右			
	E = 1;
	_nop_();
	
	tmp = databuffer; //根据时序图,E为1时开始读
	if((tmp & 0x80) == 0x80){//BF(D7)位为高电平,代表忙
		flag_busy = 1;
	}else{
		flag_busy = 0;
	}
	
	E = 0;
	_nop_();
	
	
}

void detectBusy()
{
	databuffer = 0x80; //一定要先将databuffer置为忙
	
	ifBusy(); //先检查一次是否忙并获得flag_busy的初值
	while(flag_busy == 1){ //然后如果flag_busy不变回0,就一直检查是否busy
		ifBusy();
	}
}

void Init()
{
	Delay5ms();
	Delay5ms();
	Delay5ms();
	write(0,0x38);
	Delay5ms();
	
	detectBusy();
	write(0,0x38);
	detectBusy();
	write(0,0x08);
	detectBusy();
	write(0,0x01);
	detectBusy();
	write(0,0x06);
	detectBusy();
	write(0,0x0C);
	
}


void showStr(char *msg, char hang, char lie)
{
	char pos;
	
	if(hang == 0){//如果第一行
			pos = 0x80 + 0x00 + lie; 
		}else if(hang == 1){//如果第二行
			pos = 0x80 + 0x40 + lie;
		}
		detectBusy();
		write(0,pos); //只要定下开始的位置,之后光标会自行移动
	
	while(*msg != '\0'){
		detectBusy();
		write(1,*msg);
		msg++;
	}
		
}
/
void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
}

void printSTR(char *msg)
{
	while(*msg != '\0'){
		SBUF = *msg; //往发送缓冲器里写入数据,就完成了数据的发送
		while(TI == 0); //只有当TI为1时,才往下走,根据手册,TI只有在发送完8位数据后才会硬件自动置1
		TI = 0;
		msg++;
	}
	
}




void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


void Delay30ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

void Delay40us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 15;
	while (--i);
}

void Start()
{	
	dht = 1;
	dht = 0;
	Delay30ms();
	dht = 1;
	
	while(dht == 1);//不断读取直到DHT再次变低,说明模块响应
	while(dht == 0); //不断读取直到DHT再次拉高,80us之后,再经过50us低电平,就代表要开始变高并开始传输数据了
	while(dht == 1);//不断读取直到DHT再次变低,说明数据传输前的50us低电平开始了

}


void showDataFromDHT()
{
	int i;//轮
	int j;//每一轮读多少次
  char tmp;
	char flag;
	
	Start();
	for(i= 0;i < 5;i++){ //5组数据
		for(j=0;j<8;j++){ //每组数据8位
			while(!dht);//等待上拉
			Delay40us();//高电平持续26-28us是‘0’,高电平持续70us是'1',所以delay40us之后观察还是否是高电平
			if(dht == 1){
				flag = 1;
				while(dht);//不断读取下拉的一瞬间,因为要延迟80us,如果提前结束,下一次detect01就会出错,因为detect01是从判断上拉开始的
			}else{
				flag = 0;
			} 
			tmp = tmp << 1; //tmp左移一位,再最右侧空出一位
			tmp |= flag; //将Flag的值写入空出的一位,这样经历8次,就得到了完整的tmp,且第一个读入的数据就是最高位,符合DHT的数据传输逻辑
			//!!使用这种移位再赋值的方法可以得到的一个完整的char型数据,而不是像之前char xxx[8]一样,得到的是8个char型的字符串,不利于数据转化!!
		}
		datas[i] = tmp;
	}
	
}

void Build_Datas() //由于刚刚存储的是8位char型的数字,但是不管是蓝牙还是LCD,需要显示的都是字符,而比如数字“9”和字符"9"是不同的二进制表示,所以,需要转化
{
	huma[0] = 'H';
	huma[1] = ':';
	huma[2] = datas[0]/10 + 0x30; //查阅ASCII码可知,数字Y加上0x30就是字符Y
	huma[3] = datas[0]%10 + 0x30; //同时由于不管整数或小数,都使用两位数字来表示,因此需要对‘十位’和‘个位’分别进行提取再转化成字符
	huma[4] = '.';
	huma[5] = datas[1]/10 + 0x30;
	huma[6] = datas[1]%10 + 0x30;
	huma[7] = '%';
	huma[8] = '\0';
	
	temp[0] = 'T';
	temp[1] = ':';
	temp[2] = datas[2]/10 + 0x30;
	temp[3] = datas[2]%10 + 0x30;
	temp[4] = '.';
	temp[5] = datas[3]/10 + 0x30;
	temp[6] = datas[3]%10 + 0x30;
	temp[7] = 'C';
	temp[8] = '\0';
	
}

void main()
{
	Delay1000ms();//越过不稳定状态
	
	UartInit();
	Init();//LCD显示模块初始化
	
	while(1){
		Delay1000ms();
		showDataFromDHT();	
		Build_Datas();		
		
		printSTR(temp);
		printSTR("\r\n");
		printSTR(huma);
		printSTR("\r\n");
		printSTR("\r\n");
		
		showStr(temp,0,4);
		showStr(huma,1,4);
	}
}

实现效果3: 

 PS:这样在显示的同时可以使用蓝牙不断接收,更加方便了一些,但是由于即使用到了LCD又使用到了HC08所以代码显得比较冗长。

实现4:在实现3的基础上,使用分文件编程让代码更为精简

1. 添加新的代码

2. 选择C语言,并命名为uart.c

 3. 重复1,2步,分别创建uart.c, LCD1602.c, dht11.c, delay.c

 4. 将原来冗长的代码,通过这四个分类分别把对应的代码移动到相应的C文件下

uart.c:
#include "reg52.h"

sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册

void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
}

void printSTR(char *msg)
{
	while(*msg != '\0'){
		SBUF = *msg; //往发送缓冲器里写入数据,就完成了数据的发送
		while(TI == 0); //只有当TI为1时,才往下走,根据手册,TI只有在发送完8位数据后才会硬件自动置1
		TI = 0;
		msg++;
	}
	
}
 LCD1602.c:
#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错

#define databuffer P0 //定义8位数据线D0-D7

sbit RS = P1^0;
sbit RW = P1^1;
sbit E = P1^4;
sbit D7 = P0^7;
char flag_w;
char flag_busy = 0;

void write(char flag_w, char cmd) //write(1,cmd)代表写内容;write(0,cmd)代表写指令
{
	RW = 0;//全程置0
	
	if(flag_w == 1){
		RS = 1; //写内容
		E = 0;
		_nop_(); //2微秒左右	
		
		databuffer = cmd; //根据时序图,E为0时开始传内容
		
		E = 1;
		_nop_();
		E = 0;
		_nop_();
	}
	
	if(flag_w == 0){
		RS = 0; //写指令
		E = 0;
		_nop_(); //2微秒左右
		
		databuffer = cmd; //根据时序图,E为0时开始传内容
		
		E = 1;
		_nop_();		
		E = 0;
		_nop_();
	}
	
}

void ifBusy() //根据手册,读操作读取并判定忙信号
{
	char tmp;
	
	RS = 0;
	RW = 1;
	E = 0;
	_nop_(); //2微秒左右			
	E = 1;
	_nop_();
	
	tmp = databuffer; //根据时序图,E为1时开始读
	if((tmp & 0x80) == 0x80){//BF(D7)位为高电平,代表忙
		flag_busy = 1;
	}else{
		flag_busy = 0;
	}
	
	E = 0;
	_nop_();
	
	
}

void detectBusy()
{
	databuffer = 0x80; //一定要先将databuffer置为忙
	
	ifBusy(); //先检查一次是否忙并获得flag_busy的初值
	while(flag_busy == 1){ //然后如果flag_busy不变回0,就一直检查是否busy
		ifBusy();
	}
}

void Init()
{
	Delay5ms();
	Delay5ms();
	Delay5ms();
	write(0,0x38);
	Delay5ms();
	
	detectBusy();
	write(0,0x38);
	detectBusy();
	write(0,0x08);
	detectBusy();
	write(0,0x01);
	detectBusy();
	write(0,0x06);
	detectBusy();
	write(0,0x0C);
	
}


void showStr(char *msg, char hang, char lie)
{
	char pos;
	
	if(hang == 0){//如果第一行
			pos = 0x80 + 0x00 + lie; 
		}else if(hang == 1){//如果第二行
			pos = 0x80 + 0x40 + lie;
		}
		detectBusy();
		write(0,pos); //只要定下开始的位置,之后光标会自行移动
	
	while(*msg != '\0'){
		detectBusy();
		write(1,*msg);
		msg++;
	}
		
}
dht11.c:
#include "reg52.h"

sbit dht = P3^3; //data数据线
char datas[5];
char temp[9];
char huma[9];

void Start()
{	
	dht = 1;
	dht = 0;
	Delay30ms();
	dht = 1;
	
	while(dht == 1);//不断读取直到DHT再次变低,说明模块响应
	while(dht == 0); //不断读取直到DHT再次拉高,80us之后,再经过50us低电平,就代表要开始变高并开始传输数据了
	while(dht == 1);//不断读取直到DHT再次变低,说明数据传输前的50us低电平开始了

}


void showDataFromDHT()
{
	int i;//轮
	int j;//每一轮读多少次
  char tmp;
	char flag;
	
	Start();
	for(i= 0;i < 5;i++){ //5组数据
		for(j=0;j<8;j++){ //每组数据8位
			while(!dht);//等待上拉
			Delay40us();//高电平持续26-28us是‘0’,高电平持续70us是'1',所以delay40us之后观察还是否是高电平
			if(dht == 1){
				flag = 1;
				while(dht);//不断读取下拉的一瞬间,因为要延迟80us,如果提前结束,下一次detect01就会出错,因为detect01是从判断上拉开始的
			}else{
				flag = 0;
			} 
			tmp = tmp << 1; //tmp左移一位,再最右侧空出一位
			tmp |= flag; //将Flag的值写入空出的一位,这样经历8次,就得到了完整的tmp,且第一个读入的数据就是最高位,符合DHT的数据传输逻辑
			//!!使用这种移位再赋值的方法可以得到的一个完整的char型数据,而不是像之前char xxx[8]一样,得到的是8个char型的字符串,不利于数据转化!!
		}
		datas[i] = tmp;
	}
	
}

void Build_Datas() //由于刚刚存储的是8位char型的数字,但是不管是蓝牙还是LCD,需要显示的都是字符,而比如数字“9”和字符"9"是不同的二进制表示,所以,需要转化
{
	huma[0] = 'H';
	huma[1] = ':';
	huma[2] = datas[0]/10 + 0x30; //查阅ASCII码可知,数字Y加上0x30就是字符Y
	huma[3] = datas[0]%10 + 0x30; //同时由于不管整数或小数,都使用两位数字来表示,因此需要对‘十位’和‘个位’分别进行提取再转化成字符
	huma[4] = '.';
	huma[5] = datas[1]/10 + 0x30;
	huma[6] = datas[1]%10 + 0x30;
	huma[7] = '%';
	huma[8] = '\0';
	
	temp[0] = 'T';
	temp[1] = ':';
	temp[2] = datas[2]/10 + 0x30;
	temp[3] = datas[2]%10 + 0x30;
	temp[4] = '.';
	temp[5] = datas[3]/10 + 0x30;
	temp[6] = datas[3]%10 + 0x30;
	temp[7] = 'C';
	temp[8] = '\0';
	
}
delay.c:
#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错


void Delay5ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 9;
	j = 244;
	do
	{
		while (--j);
	} while (--i);
}

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


void Delay30ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 54;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

void Delay40us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 15;
	while (--i);
}

5. 由于多个C文件都需要使用delayh, 因此需要创建delay的头文件:

 6. 再h文件中,删除函数的实现体,只保留需要在其他C文件调用的函数的申明

delay.h:
void Delay5ms();		//@11.0592MHz

void Delay1000ms();		//@11.0592MHz

void Delay30ms();		//@11.0592MHz

void Delay40us();		//@11.0592MHz

7. 在需要使用到delay的C代码上include delay.h

8. 同样的过程,在main函数中还需要用到很多其他C文件中的函数,都需要申明

 👈(main的C代码中)

9. 由于“huma”和“temp”变量是在dht11中被定义,但是在main函数中被使用,但现在dht11变成了单独的C文件,所以为了让main的C文件也认识这两个变量,需要将他们在main的C文件中也定义一下,并加上“extern”前缀!

main.c:

#include "reg52.h"
#include <string.h>
#include "delay.h"
#include "uart.h"
#include "dht11.h"
#include "LCD1602.h"

extern char temp[9];
extern char huma[9];


void main()
{
	Delay1000ms();//越过不稳定状态
	
	UartInit();
	Init();//LCD显示模块初始化
	
	while(1){
		Delay1000ms();
		showDataFromDHT();	
		Build_Datas();		
		
		printSTR(temp);
		printSTR("\r\n");
		printSTR(huma);
		printSTR("\r\n");
		printSTR("\r\n");
		
		showStr(temp,0,4);
		showStr(huma,1,4);
	}
}

10. 此时编译成功,并且实现效果和“实现3"一样。

 实现效果3: 

PS: 此时,在功能不变的情况下,代码变得更加精简易读!! 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值