Bootstrap

DHT11温湿度传感器

1.认识DHT11

1、概述:

  1. DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,应用领域:暖通空调;汽车;消费品;气象站;湿度调节器;除湿器;家电;医疗;自动控制等。
  2. DHT11是继LCD1602液晶显示模块之后我们学习的第二种非标准协议外设。也是我们学习的第一个通过单片机获取实时数据的传感器(注:超声波传感器是通过echo信号,我们换算得到的数据,不是直接从传感器获取到的数据)。
  3. 非标准协议特点:需要看时序图,把时序化成代码;数据传输没法走串口,需要用I/O口替代。

2、工作特点:

  • 能够进行相对湿度(RH)和温度(T)测量,大致信息如下(详细信息查阅说明书):
    • 湿度的测量范围是:20-90%RH,量程根据温度不同有所变化;
    • 温度的测量量程是:0-50℃;
    • 测湿精密度是:±5%RH;
    • 测温精密度是:±2℃;
    • 分辨率是:1;
  • 测量湿度的灵敏度高于测量温度的灵敏度
  • 全部校准,数字输出,数据精准
  • 长期稳定性
  • 超长的信号传输距离:20米
  • 超低能耗:休眠
  • 4 引脚安装:可以买封装好的
  • 完全互换 : 直接出结果,不用转化(以前用超声波测距传感器时需要经过公式的转换,这个模块不用套公式)
  • 供电电压:DHT11的供电电压为3-5.5V。传感器上电后,要等待1s以上 以越过不稳定状态在此期间无需发送任何指令。电源引脚(VDD,GND)之间可增加一个100nF 的电容,用以去耦滤波(用的是模块的话就不用考虑这方面问题,因为模块已经帮你做好了)。

3、使用的工作环境:超出建议的工作范围可能导致高达3%RH的临时性漂移信号。返回正常工作条后,传感器会缓慢地向校准状态恢复,加速恢复进程的方法是:置于极限工作条件下或化学蒸汽中的传感器,通过如下处理程序,可使其恢复到校准时的状态。在50-60℃和< 10%RH的湿度条件下保持2 小时(烘干);随后在20-30℃和>70%RH的湿度条件下保持5小时以上。在非正常工作条件下长时间使用会加速产品的老化过程。

  • 电阻式湿度传感器的感应层会受到化学蒸汽的干扰,化学物质在感应层中的扩散可能导致测量值漂移和灵敏度下降。高浓度的化学污染会导致传感器感应层的彻底损坏。在一个纯净的环境中,污染物质会缓慢地释放出去。如上所述的恢复处理将加速实现这一过程。
  • 气体的相对湿度,在很大程度上依赖于温度:因此在测量湿度时,应尽可能保证湿度传感器在同一温度下工作。如果与释放热量的电子元件共用一个印刷线路板,在安装时应尽可能将DHT11远离电子元件,并安装在热源下方,同时保持外壳的良好通风。为降低热传导,DHT11与印刷电路板其它部分的铜镀层应尽可能最小,并在两者之间留出一道缝隙。
  • 光线:长时间暴露在太阳光下或强烈的紫外线辐射中,会使性能降低。
  • DATA信号线材质:DATA信号线材质量会影响通讯距离和通讯质量,推荐使用高质量屏蔽线。
  • 长期保存条件:温度10-40℃,湿度60%以下。
  • 避免结露情况下使用。

4、引脚说明:

  • DHT11模块有四根引脚,如下:
    编号符号说明
    1VDD供电,3~5,5V,DC
    2DATA串行数据,单总线
    3NC空脚,请悬空
    4GND接地,电源负极
  • 注:手动焊接,在最高260℃的温度条件下接触时间须少于10秒。

5、DHT11的单总线数据格式:DHT11模块的串行接口是单线双向的,一次通讯时间在4ms左右。机器与机器之间只能用二进制对话,51单片机和DHT11之间只有一根数据线DATA,它的作用是:

  • 51单片机发送序列指令(主机信号时序)给DHT11模块,驱动DHT11模块。
  • DHT11测量成功后返回数据序列(DHT信号时序)给51单片机。

模块一次能够完整的数据传输为40bit,高位先出,数据格式(从高到低)为:

  1. 8bit的湿度(RH)整数数据
  2. 8bit湿度(RH)小数数据 
  3. 8bit温度(T)整数数据 
  4. 8bit温度(T)小数数据 
  5. 8位校验和:数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据”所得结果的末8位。

2.开发逻辑1:读取前

阅读时序图,需要关心以下三点:开始、结束、转折。

0、和单片机的接线方式:注意一开始把单片机关机,防止接线的时候出错烧毁DHT11传感器。

  • 将DHT11传感器的数字量信号输出(DATA口)接到51单片机某个I/O口,比如说我目前接到了P3^3。此时单片机这个引脚的作用是:①发送驱动指令给DHT11模块,②从DHT11模块获取输入。
  • 将DHT11传感器的VCC针脚接到51单片机的VCC
  • 将DHT11传感器的GND针脚接到51单片机的GND(注意I/O口边上的GND尽量不要用,因为这是美观做上去的)

1、有效数据读取前的通讯时序:明确起见,在时序图中给每个转折点标注一下字母。

 

  1. 主机信号时序
    1. DATA开始是高电平(a点) 
    2. 转折:拉低为低电平,时间未知(b点) 
    3. 低电平,主机至少拉低18ms(b到c点) 
    4. 转折:拉高为高电平,时间未知(c点) 
    5. 高电平,主机延时20~40微秒(c到d点)
  2. DHT信号时序:如果DHT11模块能够响应主机信号,则有如下表现:
    1. DATA被模块拉低成低电平,时间未知(d点) 
    2. 持续80微秒(d到e点) 
    3. 转折:DATA被拉高为高电平,时间未知(e点) 
    4. 高电平,持续80微秒(e到f点)
    5. 转折:DATA被拉低为低电平,时间未知(f点),这时候拉高延时准备输出有效数据
    6. 开始以单总线数据格式传送数据(f点以后):先不考虑传感器数据读取

习题1(检测模块是否存在:用51单片机检测DHT11模块插在单片机引脚上,点亮LED【项目工程文件夹

  • 判断方法:因为我们在程序中DATA最后一次配置是高电平,模块不在的话DATA是不可能变成低电平的,所以从c点开始,检测到低电平(d到e点之间)就说明模块有响应。那么从c点开始数几秒进行判断呢?答:从c点开始大致70微秒读一下。
  • 原因:从c点开始,主机信号拉高的时间为20~40微秒,DHT响应信号(低电平)的持续时间是80微秒,直到e点结束。如下图所示,时间段有两种情况,尽量取重合部分,再在重合部分确定一个中间数(40和100的平均数是70)即可:
  • 时序逻辑
    • DATA置高电平(a点) 
    •  DATA置低电平(b点) 
    • 延时30ms(b到c点)  
    • DATA置高电平(c点)  
    • 在70微秒后读DATA信号,如果是低电平说明DHT11模块存在。
  1. 思路:注意main函数中需要用while(1)死循环来保证程序不退出,否则即使DHT11模块连接上了,但是LED也只能微微闪一下,看不出来效果。
    全局变量:
    1. sbit指令找到P3这个I/O口组的第7位P3^7,也就是D5这个LED: sbit ledD5 = P3^7;
    2. sbit指令找到P3这个I/O口组的第3位P3^3,它与DHT11模块的DATA数据线相连,用来输出主机指令和接收数据: sbit dht11 = P3^3;
    1. 上电后关闭D5: ledD5 = 1;
    2. 调用API4. DHT11传感器上电后等待2s,以越过不稳定状态: 
    	Delay1000ms();
    	Delay1000ms();
    3. 调用API1. 检测DHT11模块是否存在: check_DHT11_exist();
    4. while(1)死循环,防止程序退出
    /* 一级函数:f1、f4 */
    f1. 封装检测DHT11模块是否存在的API: void check_DHT11_exist();
        f1.1 根据DHT11模块的时序逻辑分析,总结出如下过程:
    		dht11 = 1;
    		dht11 = 0;
    		调用API2. 软件延时30ms: Delay30ms();
    		dht11 = 1;
    		调用API3. 软件延时70us: Delay70us();
    	f1.2 紧接着读取DATA数据线上的信息,判据是 dht11 == 0 //如果为低电平,DHT11模块就存在
            如果是,就点亮D5: ledD5 = 0;
    		否则,啥也不干
    f4. 封装软件延时1s的API,用于DHT11模块上电后的稳定: void Delay1000ms();
    /* 二级函数:f2、f3 */
    f4. 封装软件延时30ms的API,用于API1的时序分析: void Delay30ms();
    f5. 封装软件延时5ms的API,用于API1的时序分析: void Delay5ms();

  2. 代码:
    #include "reg52.h"
    #include "intrins.h"
    
    sbit ledD5 = P3^7;
    sbit dht11 = P3^3; 	//DHT11模块的DATA数据线
    
    /* API1. 检测DHT11模块是否存在 */
    void check_DHT11_exist();
    /* API2. 软件延时30ms,用于API1的时序分析 */
    void Delay30ms();
    /* API3. 软件延时70微秒,用于API1的时序分析 */
    void Delay70us();
    /* API4. 软件延时1s,用于DHT11模块上电后的稳定 */
    void Delay1000ms();
    
    void main(void)
    {
    	ledD5 = 1;
    	//传感器上电后,要等待1s以上 以越过不稳定状态
    	Delay1000ms();
    	Delay1000ms();
    	check_DHT11_exist();	
    	while(1){}	//防止程序退出
    }
    
    void check_DHT11_exist()
    {
    	dht11 = 1;
    	dht11 = 0;
    	Delay30ms();
    	dht11 = 1;
    	Delay70us();
    	if(dht11 == 0){
    		ledD5 = 0;
    	}
    }
    
    void Delay30ms()		//@11.0592MHz
    {
    	unsigned char i, j;
    
    	i = 54;
    	j = 199;
    	do
    	{
    		while (--j);
    	} while (--i);
    }
    
    void Delay70us()		//@11.0592MHz
    {
    	unsigned char i;
    
    	_nop_();
    	i = 29;
    	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);
    }

3.开发逻辑2:读取时

阅读时序图,需要关心以下三点:开始、结束、转折。

0、和单片机的接线方式:同上。

1、启动(从低功耗模式到高速模式):

 

  • 阅读DHT11说明书:用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集,用户可选择读取部分数据,采集数据后转换到低速模式。从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集,采集数据后转换到低速模式。
  • 总结:在每一次数据采集(40bit)前,都要让单片机发送开始(启动)信号,否则将处于低功耗模式。
  • 做法:封装一个DHT11模块有效数据真正开始传输前,单片机发送启动信号的函数。方便起见直接对习题1中的代码做适当修改即可:
    void DHT11_Start()
    {
    	dht11 = 1;
    	dht11 = 0;
    	Delay30ms();
    	dht11 = 1;
    	while(!(dht11==0));	//卡d点
    	while(!dht11);		//卡e点
    	while(!(dht11==0)); //卡f点
    }

    1. 保留单片机的启动信号代码,也就是主机信号时序。
    2. 删去检测模块是否存在的代码。
    3. 加上等待DHT响应信号的代码:技巧是对时序图中时间不好判断且是响应信号而非主机信号的位置,都用while空循环体卡着程序,直到电平信号发生改变。
      1. 卡d点,直到DATA变成低电平:while( !(dht11==0) );
      2. 卡e点,直到DATA变成高电平:while( !dht11 );
      3. 卡f点,直到DATA变成低电平:while( !(dht11==0) );

2、有效数据读取时的通讯时序:完整的时序图如下,明确起见,在时序图中给每个转折点标注一下字母。

  • 当DHT11模块被上述开始信号驱动了之后,有效数据开始从模块传输给单片机。注意图中开始传送数据部分只显示了2bit,实际上一次通讯有40bit的有效数据从DHT11模块传输给单片机,这里是为了讲清楚传输1和传输0的时序分析。

 

  • 认知:DHT11模块的设计很有意思:不论数据线上是0还是1,有效数据的表现形式都是高电平,只是持续的时间不一样。
  1. 传输0的时序
    1. DATA保持低电平,持续50微秒(f到g点)
    2. 转折:DATA被拉高为高电平,时间未知(g点)
    3. 如果有效数据是0,DATA高电平持续26~28微秒(g到f'点)
    4. 转折:DATA被拉低为低电平,时间未知(f'点)
  2. 传输1的时序
    1. DATA保持低电平,持续50微秒(f到g点)
    2. 转折:DATA被拉高为高电平,时间未知(g点)
    3. 如果有效数据是1,DATA高电平持续70微秒(g到f'点)
    4. 转折:DATA被拉低为低电平,时间未知(f'点)
  3. 区分该bit是0还是1的方法:从时序图的g点开始,延时一段时间去读数据,比如说我选择用50微秒(选择28到70的中间值,大约是50微秒)来进行区分,50微秒以后如果读到低电平,说明该bit是0;50微秒以后如果读到高电平,说明该bit是1。
    1. 注:观察时序图,注意到每一bit采集前都有一个50微秒的低电平延时,所以不用担心50微秒后DHT11转而发送下一个bit。

3、总体时序逻辑:

  1. 主机启动信号
    1. DATA置高电平(a点) 
    2. DATA置低电平(b点) 
    3. 延时30ms(b到c点)  
    4. DATA置高电平(c点)  
  2. DHT响应信号
    1. 有效数据输出前的响应:
      1. 卡d点,直到DATA变成低电平:while( !(dht11==0) );
      2. 卡e点,直到DATA变成高电平:while( !dht11 );
      3. 卡f点,直到DATA变成低电平:while( !(dht11==0) );
    2. 有效数据输出时的响应(读5轮,每一轮读8次,每一轮的8bit形成一个字符型数据):
      1. 卡g点,直到DATA变成高电平:while( !dht11 );
      2. 二选一:在70微秒后读取DATA信号,如果读到0就代表是低电平:
        1. 随后保存该bit数据。
      3. 二选一:在70微秒后读取DATA信号,如果读到1就代表是高电平:
        1. 随后卡f'点,直到DATA变成低电平:while( !(dht11==0) );
        2. 最后保存该bit数据。

4、编程逻辑与读取1bit数据的方法:我们用字符型变量temp来保存8bit的采样数据,用字符数组dataDHT11[5]来保存5个这样的采样数据,编程方式就是循环嵌套

  1. temp存放在哪里?答:根据下标i来给数组赋值,要读5个temp。这是外层循环。
  2. temp怎么来?答:每个temp要读8次,一次1bit,这是内层循环。
  3. 每次怎么读1bit数据?答:时序到达g点后,延时50微秒,进行判断,如果读到1就是高电平,读到0就是低电平。保存时,将字符型变量它temp进行移位:每次读取1bit就让temp进行移位运算:左移1位,移位成功后让它进行按位运算:位或上1。也就是说每次读取1个bit,就是把它存放在字符型变量temp的最低位。

 

习题2(读取温湿度数据:封装单片机通过DHT11模块读取温湿度数据的函数【项目工程文件夹

  • 代码心得
    • DHT11单总线上,高位先出的数据格式方便我们的编程。
    • 由于需要判断读取到的这一bit数据是什么,所以读取1bit数据后,不着急马上把它保存在temp的最低位,定义一个标志位bitFlag可以精简程序。
  1. 思路:
    全局变量:
    1. sbit指令找到P3这个I/O口组的第3位P3^3,它与DHT11模块的DATA数据线相连,用来输出主机指令和接收数据: sbit dht11 = P3^3;
    2. 用于保存DHT11和单片机一次通讯所传输的有效数据,一共40bit: char dataDHT[5];
    //dataDHT[5]的传输路线为:dht11(P3.3) ——> API2: Read_Data_From_DHT11() ——> bitFlag ——> temp ——> dataDHT[i]
    1. 调用API5. DHT11传感器上电后等待2s,以越过不稳定状态: 
    	Delay1000ms();
    	Delay1000ms();
    2. while(1)死循环,每隔1s读取数据
    	2.1 调用API5. 软件延时1s: Delay1000ms();
    	2.2 调用API2. 与DHT11完成一次通讯,读取DHT11模块数据(40bit)放在数组dataDHT[5]中: 
    		Read_Data_From_DHT11();
    /* 一级函数:f2、f5 */
    f2. 封装读取DHT11模块数据(40bit)放在数组dataDHT[5]中的API: void Read_Data_From_DHT11();
    	f2.1 定义局部变量temp,用于移位存放1bit数据: char temp;
    	f2.2 定义1bit数据的标志位,1代表读到的是1,0代表读到的是0: char bitFlag;
    	f2.3 调用API1. 每次数据采集(40bit)前都要发送开始信号: DHT11_Start();
    	f2.4 for循环嵌套,实现40bit数据的采集,代表数组下标的外层循环变量i从0开始,<5时进入循环
        	f2.4.1 for循环,循环变量j从0开始,<8时进入循环
            	f2.4.1.1 根据DHT11模块的时序逻辑分析,总结出如下过程:
    				空循环体,卡g点,直到DATA变成高电平:while(!dht11);
    				调用API4. 软件延时50微秒: Delay50us();
    			f2.4.1.2 紧接着判断数据线DATA上的电平是否是高电平,判据是dht11 == 1
                    f2.4.1.2.1 如果是,说明该bit是高电平,那么:
                    	将该位数据以标志位形式保存在标志位变量bitFlag中: bitFlag = 1;
    					空循环体,卡f’点,直到DATA变成低电平:while(!(dht11==0));
    				f2.4.1.2.2 否则,说明该bit是低电平,那么:
                    	将该位数据以标志位形式保存在标志位变量bitFlag中: bitFlag = 0;
               	f2.4.1.3 通过移位和位运算,将1bit数据保存在变量temp中:
                	temp = tem << 1;
    				temp |= bitFlag;
    		f2.4.2 经过内层循环,将已经获取到的8bit数据依次保存到字符数组dataDHT中: 
    			dataDHT[i] = temp;
    f5. 封装软件延时1s的API,用于DHT11模块上电后的稳定,以及每隔1s读取DHT11数据: void Delay1000ms();
    /* 二级函数:f1、f3、f4 */
    f1. 封装启动DHT11模块的API: void DHT11_Start();
        f1.1 根据DHT11模块的时序逻辑分析,总结出如下过程:
    		dht11 = 1;
    		dht11 = 0;
    		调用API2. 软件延时30ms: Delay30ms();
    		dht11 = 1;
    		空循环体,卡d点,直到DATA变成低电平:while(!(dht11==0));
    		空循环体,卡e点,直到DATA变成高电平:while(!dht11);
    		空循环体,卡f点,直到DATA变成低电平:while(!(dht11==0));
    f3. 封装软件延时30ms的API,用于API1的时序分析: void Delay30ms();
    f4. 封装软件延时50微秒的API,用于DHT11传输数据中每1bit的时序分析: void Delay50us();

  2. 代码:
    #include "reg52.h"
    #include "intrins.h"
    
    sbit dht11 = P3^3; 	//DHT11模块的DATA数据线
    char dataDHT[5];
    
    /* API1. 启动DHT11模块 */
    void DHT11_Start();
    /* API2. 读取DHT11模块数据(40bit)放在数组dataDHT[5]中 */
    void Read_Data_From_DHT11();
    /* API3. 软件延时30ms,用于API1的时序分析 */
    void Delay30ms();
    /* API4. 软件延时50微秒,用于DHT11传输数据中每1bit的时序分析 */
    void Delay50us();
    /* API5. 软件延时1s,用于DHT11模块上电后的稳定 */
    void Delay1000ms();
    
    void main(void)
    {
    	//传感器上电后,要等待1s以上 以越过不稳定状态
    	Delay1000ms();
    	Delay1000ms();
    	while(1){	//每隔一秒进行数据读取
    		Delay1000ms();
    		Read_Data_From_DHT11();
    	}
    }
    
    void DHT11_Start()
    {
    	dht11 = 1;
    	dht11 = 0;
    	Delay30ms();
    	dht11 = 1;
    	while(!(dht11==0));	//卡d点
    	while(!dht11);		//卡e点
    	while(!(dht11==0)); //卡f点
    }
    
    void Read_Data_From_DHT11()
    {
    	char i;			//轮
    	char j;			//每轮读多少次
    	char temp;		//移位
    	char bitFlag;	//该bit的标志位,1代表读到的是1,0代表读到的是0	
    	DHT11_Start();		//每次数据采集(40bit)前都要发送开始信号
    	for(i=0; i<5; i++){ 
    		for(j=0; j<8; j++){
    			while(!dht11);	//卡g点
    			Delay50us();
    			if(dht11 == 1){	//说明该bit是高电平
    				bitFlag = 1;
    				while(!(dht11==0));	
    			}else{			//说明该bit是低电平
    				bitFlag = 0;
    			}
    			temp = temp << 1;	//temp <<= 1;
    			temp |= bitFlag;
    		}
    		dataDHT[i] = temp;
    	}
    }
    
    void Delay30ms()		//@11.0592MHz
    {
    	unsigned char i, j;
    
    	i = 54;
    	j = 199;
    	do
    	{
    		while (--j);
    	} while (--i);
    }
    
    void Delay50us()		//@11.0592MHz
    {
    	unsigned char i;
    
    	_nop_();
    	i = 20;
    	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);
    }

4.开发逻辑3:串口显示数据

1、数字到字符的转化:

  • 原因:我们不能将字符数组中的元素直接输出给串口缓冲寄存器SBUF,因为DHT11模块的一字节有效数据就是0~128范围内的数字,而非字符,直接输出容易导致乱码,需要把数字转化为与之对应的字符。回顾ASCII码表,字符'0'的ASCII码是0x30(16进制)或48(10进制)。
  • 注意点:一字节的有效数据包含两个字符,所以需要用到除法和取余运算取出数据的十位和个位,分别将它们发送给SBUF。
  1. 取十位后转成字符:dataDHT[i]/10 + 0x30;
  2. 取个位后转成字符:dataDHT[i]%10 + 0x30;

2、代码心得:

  1. 基于习题2的代码,将串口通信的代码整合进来时,第一步是把串口一节中需要的代码拷贝进来后做个发送数据的简单测试,证明拷贝的代码和串口显示是没问题的(不要一口气整合太多代码,防止出问题后懵逼)。
  2. 上述验证没问题后,就进行数字转字符的操作,并发送给PC上的串口调试助手,如果串口上没法显示数据,可能的原因是:我们在根据时序图进行区分该bit是0还是1中(3.2.3小节)给的50微秒的延时略长,导致在读0时直接延时到下一bit的数据上了,从而导致有效数据的读取出错。解决方法是缩短50微秒的延时,比如只延时40微秒。

习题3(串口显示温湿度数据:【项目工程文件夹

 

  1. 思路:
    全局变量:
    1. sbit指令找到P3这个I/O口组的第3位P3^3,它与DHT11模块的DATA数据线相连,用来输出主机指令和接收数据: sbit dht11 = P3^3;
    2. 用于保存DHT11和单片机一次通讯所传输的有效数据,一共40bit: char dataDHT[5];
    //dataDHT[5]的传输路线为:
    //路线1:dht11(P3.3) ——> API2: Read_Data_From_DHT11() ——> bitFlag ——> temp ——> dataDHT[i]
    //路线2:dataDHT[i] ——> API9:Send_Data_From_DHT11() ——> SBUF ——> PC的串口助手
    3. sfr指令直接找到AUXR寄存器: sfr AUXR = 0X8E;    //因为AUXR没有在reg52.h中声明
    1. 调用API6. 初始化串口: UartInit();
    2. 调用API5. DHT11传感器上电后等待2s,以越过不稳定状态: 
    	Delay1000ms();
    	Delay1000ms();
    3. while(1)死循环,每隔1s读取数据,并发送给串口
    	3.1 调用API5. 软件延时1s: Delay1000ms();
    	3.2 调用API2. 与DHT11完成一次通讯,读取DHT11模块数据(40bit)放在数组dataDHT[5]中: 
    		Read_Data_From_DHT11();
    	3.3 调用API9. 将DHT11模块测得的温湿度数据发送给串口: Send_Data_From_DHT11();
    /* 一级函数:f2、f5、f6、f9 */
    f2. 封装读取DHT11模块数据(40bit)放在数组dataDHT[5]中的API: void Read_Data_From_DHT11();
    	f2.1 定义局部变量temp,用于移位存放1bit数据: char temp;
    	f2.2 定义1bit数据的标志位,1代表读到的是1,0代表读到的是0: char bitFlag;
    	f2.3 调用API1. 每次数据采集(40bit)前都要发送开始信号: DHT11_Start();
    	f2.4 for循环嵌套,实现40bit数据的采集,代表数组下标的外层循环变量i从0开始,<5时进入循环
        	f2.4.1 for循环,循环变量j从0开始,<8时进入循环
            	f2.4.1.1 根据DHT11模块的时序逻辑分析,总结出如下过程:
    				空循环体,卡g点,直到DATA变成高电平:while(!dht11);
    				看时序图, 调用API4. 软件延时50微秒: Delay50us();
    			f2.4.1.2 紧接着判断数据线DATA上的电平是否是高电平,判据是dht11 == 1
                    f2.4.1.2.1 如果是,说明该bit是高电平,那么:
                    	将该位数据以标志位形式保存在标志位变量bitFlag中: bitFlag = 1;
    					空循环体,卡f’点,直到DATA变成低电平:while(!(dht11==0));
    				f2.4.1.2.2 否则,说明该bit是低电平,那么:
                    	将该位数据以标志位形式保存在标志位变量bitFlag中: bitFlag = 0;
               	f2.4.1.3 通过移位和位运算,将1bit数据保存在变量temp中:
                	temp = tem << 1;
    				temp |= bitFlag;
    		f2.4.2 经过内层循环,将已经获取到的8bit数据依次保存到字符数组dataDHT中: 
    			dataDHT[i] = temp;
    f5. 封装软件延时1s的API,用于DHT11模块上电后的稳定,以及每隔1s读取DHT11数据: void Delay1000ms();
    f6. 封装初始化串口的API: void UartInit(void);
        f6.1 禁用ALE信号: AUXR = 0X01;
        f6.2 让串口以方式1工作(8位UART,可变波特率),并允许串口接收: SCON = 0x50;
        f6.3 让定时器1以8位重载工作模式工作:
            TMOD &= 0xDF;
            TMOD |= 0x20;
        f6.4 根据波特率为9600,波特率不翻倍,设置定时器1的初值:
            TH1 = 0xFD;
            TL1 = 0xFD;
        f6.5 定时器开始数数: TR1 = 1;
    f9. 封装将DHT11模块测得的温湿度数据发送给串口的API: void Send_Data_From_DHT11();
    	f9.1 调用API8. 给串口发送字符串"RH(%):",来表示相对湿度: sendString("RH(%):");
    	f9.2 给串口发送湿度(RH)数据:
    		f9.2.1 调用API7. 根据数字到字符的转化方式,给串口发送代表湿度整数位的两个字符,
        		湿度整数位数字保存在字符数组dataDHT[]的第0个元素中: 
    			sendByte(dataDHT[0]/10 + 0x30);
    			sendByte(dataDHT[0]%10 + 0x30);
    		f9.2.2 调用API7. 给串口发送字符'.',代表小数点: sendByte('.');
    		f9.2.3 调用API7. 根据数字到字符的转化方式,给串口发送代表湿度小数位的两个字符,
        		湿度小数位数字保存在字符数组dataDHT[]的第1个元素中: 
    			sendByte(dataDHT[1]/10 + 0x30);
    			sendByte(dataDHT[1]%10 + 0x30);
    		f9.2.4 调用API8. 给串口发送字符串"\r\n",表示换行: sendString("\r\n");
    	f9.3 给串口发送温度(T)数据:
    		f9.3.1 调用API7. 根据数字到字符的转化方式,给串口发送代表温度整数位的两个字符,
        		温度整数位数字保存在字符数组dataDHT[]的第2个元素中: 
    			sendByte(dataDHT[2]/10 + 0x30);
    			sendByte(dataDHT[2]%10 + 0x30);
    		f9.3.2 调用API7. 给串口发送字符'.',代表小数点: sendByte('.');
    		f9.3.3 调用API7. 根据数字到字符的转化方式,给串口发送代表温度小数位的两个字符,
        		温度小数位数字保存在字符数组dataDHT[]的第3个元素中: 
    			sendByte(dataDHT[3]/10 + 0x30);
    			sendByte(dataDHT[3]%10 + 0x30);
    		f9.3.4 调用API8. 给串口发送字符串"\r\n\r\n",表示显示空行: sendString("\r\n\r\n");
    /* 二级函数:f1、f3、f4、f7、f8 */
    f1. 封装启动DHT11模块的API: void DHT11_Start();
        f1.1 根据DHT11模块的时序逻辑分析,总结出如下过程:
    		dht11 = 1;
    		dht11 = 0;
    		调用API2. 软件延时30ms: Delay30ms();
    		dht11 = 1;
    		空循环体,卡d点,直到DATA变成低电平:while(!(dht11==0));
    		空循环体,卡e点,直到DATA变成高电平:while(!dht11);
    		空循环体,卡f点,直到DATA变成低电平:while(!(dht11==0));
    f3. 封装软件延时30ms的API,用于API1的时序分析: void Delay30ms();
    f4. 封装软件延时50微秒的API,用于DHT11传输数据中每1bit的时序分析: void Delay50us();
    f7. 封装定时给PC发送一个字符的API: void sendByte(char data_msg);  //形参是字符值
        f7.1 往SBUF寄存器中写入字符data_msg: SBUF = data_msg;
        f7.2 根据串口发送中断触发位TI,利用空循环体暂停程序: while(TI == 0);
        f7.3 程序复位TI: TI = 0;
    f8. 封装给PC发送字符串的API: void sendString(char *str); //形参是字符串的地址
        f8.1 定义一个字符指针变量p用来保存字符串首地址: char *p = str;
        f8.2 while循环,控制循环的变量是*p,当*p != '\0' 时,进入循环,进行单个字符的发送
            f8.2.1 通过指针间接访问字符串字符,再调用API3. 发送单个字符: sendByte(*p);
            f8.2.2 修改循环变量p的值,让指针p偏移: p++;
    /* 三级函数:f7 */
    f7. 同上,f7也做f8的三级函数

  2. 代码:
    #include "reg52.h"
    #include "intrins.h"
    
    sbit dht11 = P3^3; 	//DHT11模块的DATA数据线
    char dataDHT[5];
    sfr AUXR = 0x8E;
    
    /* API1. 启动DHT11模块 */
    void DHT11_Start();
    /* API2. 读取DHT11模块数据(40bit)放在数组dataDHT[5]中 */
    void Read_Data_From_DHT11();
    /* API3. 软件延时30ms,用于API1的时序分析 */
    void Delay30ms();
    /* API4. 软件延时50微秒,用于DHT11传输数据中每1bit的时序分析 */
    void Delay50us();
    /* API5. 软件延时1s,用于DHT11模块上电后的稳定、每隔1s读取DHT11数据并发送给串口缓冲寄存器 */
    void Delay1000ms();
    /* API6. 初始化串口 */
    void UartInit(void);
    /* API7. 通过串口给PC发送一个字符 */
    void sendByte(char data_msg);
    /* API8. 通过串口给PC发送一个字符串 */
    void sendString(char *str);
    /* API9. 将DHT11模块测得的温湿度数据发送给串口 */
    void Send_Data_From_DHT11();
    
    void main(void)
    {
    	UartInit();	//初始化串口
    	//传感器上电后,要等待1s以上 以越过不稳定状态
    	Delay1000ms();
    	Delay1000ms();
    	while(1){	//每隔一秒进行数据读取,并串口显示
    		Delay1000ms();
    		Read_Data_From_DHT11();
    		Send_Data_From_DHT11();
    	}
    }
    
    void DHT11_Start()
    {
    	dht11 = 1;
    	dht11 = 0;
    	Delay30ms();
    	dht11 = 1;
    	while(!(dht11==0));	//卡d点
    	while(!dht11);		//卡e点
    	while(!(dht11==0)); //卡f点
    }
    
    void Read_Data_From_DHT11()
    {
    	char i;			//轮
    	char j;			//每轮读多少次
    	char temp;		//移位
    	char bitFlag;	//该bit的标志位,1代表读到的是1,0代表读到的是0	
    	DHT11_Start();		//每次数据采集(40bit)前都要发送开始信号
    	for(i=0; i<5; i++){ 
    		for(j=0; j<8; j++){
    			while(!dht11);	//卡g点
    			Delay50us();
    			if(dht11 == 1){	//说明该bit是高电平
    				bitFlag = 1;
    				while(!(dht11==0));	
    			}else{			//说明该bit是低电平
    				bitFlag = 0;
    			}
    			temp = temp << 1;	//temp <<= 1;
    			temp |= bitFlag;
    		}
    		dataDHT[i] = temp;
    	}
    }
    
    void Delay30ms()		//@11.0592MHz
    {
    	unsigned char i, j;
    
    	i = 54;
    	j = 199;
    	do
    	{
    		while (--j);
    	} while (--i);
    }
    
    void Delay50us()		//@11.0592MHz
    {
    	unsigned char i;
    
    	_nop_();
    	i = 20;
    	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 UartInit(void)		//[email protected]
    {
    	AUXR = 0x01;
    	SCON = 0x50;	//8位UART,允许串口接收
    	TMOD &= 0xDF;
    	TMOD |= 0x20;	//定时器8位重载工作模式
    	TH1 = 0xFD;
    	TL1 = 0xFD;		//9600波特率初值
    	TR1 = 1;
    }
    
    void sendByte(char data_msg)
    {
    	SBUF = data_msg;
    	// Delay10ms();
    	while(TI == 0);
    	TI = 0;
    }
    
    void sendString(char *str)
    {
    	char *p = str;
    	while(*p != '\0'){
    		sendByte(*p);
    		p++;
    	}
    }
    
    void Send_Data_From_DHT11()
    {
    	sendString("RH(%):");
    	sendByte(dataDHT[0]/10 + 0x30);	//湿度整数位
    	sendByte(dataDHT[0]%10 + 0x30);
    	sendByte('.');
    	sendByte(dataDHT[1]/10 + 0x30);	//湿度小数位
    	sendByte(dataDHT[1]%10 + 0x30);
    	sendString("\r\n");
    	sendString("T(celcius):");
    	sendByte(dataDHT[2]/10 + 0x30);	//温度整数位
    	sendByte(dataDHT[2]%10 + 0x30);
    	sendByte('.');
    	sendByte(dataDHT[3]/10 + 0x30);	//温度小数位
    	sendByte(dataDHT[3]%10 + 0x30);
    	sendString("\r\n\r\n");
    }

;