基于51单片机的温湿度报警器

2024-08-12

此文章仅供本人学习使用,系统内部存在很多问题,请不要盲目模仿,执意模仿出现损失概不负责

系统基本原理

使用温度传感器(DHT11)检测环境温度和湿度,将温度信号和湿度信号转换成数字信号,然后通过51单片机进行处理,判断当前温度和湿度是否超出设定的阈值,如果超出则触发报警(蜂鸣器)。

系统工作步骤

通过单片机读取DHT11传感器的数据,并将数据显示在LCD1602液晶显示屏上。同时,通过按键模块设置温度和湿度的阈值,当温度或湿度超出阈值时,报警模块会发出声音。

知识储备

温湿度传感器(DHT11)

引脚说明

Pin          名称              注释
 1           VDD           供电 3-5.5V
 2           GND           接地,电源负极
 3           DATA          串行数据,单总线
 4           NC            空脚,请悬空

协议与数据格式

DHT11采用单总线协议与单片机通信,单片机发送一次复位信号后,DHT11 从低功耗模式转换到高速模式,等待主机复位结束后,DHT11 发送响应信号,并拉高总线准备传输数据。
一次完整的数据为 40bit,按照高位在前,低位在后的顺序传输。

数据格式为:8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数数据+8bit 校验和,一共 5 字节(40bit)数据。
由于 DHT11 分辨率只能精确到个位,所以小数部分是数据全为 0。校验和为前 4 个字节数据相加,校验的目的是为了保证数据传输的准确性。

DHT11 只有在接收到开始信号后才触发一次温湿度采集,如果没有接收到主机发送复位信号,DHT11 不主动进行温湿度采集。当数据采集完毕且无开始信号后,DHT11 自动切换到低速模式。

操作时序

主机发送复位信号
DHT11 的初始化过程同样分为复位信号和响应信号。
首先主机拉低总线至少 18ms,然后再拉高总线,延时 20~40us,取中间值 30us,此时复位信号发送完毕。
从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集。采集数据后转换到低速模式。
DHT11发送响应信号
DHT11 检测到复位信号后,触发一次采样,并拉低总线 80us 表示响应信号,告诉主机数据已经准备好了;然后 DHT11 拉高总线 80us,之后开始传输数据。
如果检测到响应信号为高电平,则 DHT11 初始化失败,请检查线路是否连接正常。

当复位信号发送完毕后,如果检测到总线被拉低,就每隔 1us 计数一次,直至总线拉高,计算低电平时间;当总线被拉高后重新计数检测 80us 的高电平。
如果检测到响应信号之后的80us 高电平,就准备开始接收数据。实际上 DHT11 的响应时间并不是标准的 80us,往往存在误差,当响应时间处于 20~100us 之间时就可以认定响应成功。
数据传输
DHT11 在拉高总线 80us 后开始传输数据。每 1bit 数据都以 50us 低电平时隙开始,告诉主机开始传输一位数据了。
DHT11 以高电平的长短定义数据位是 0 还是 1,当 50us 低电平时隙过后拉高总线,高电平持续 26~28us 表示数据“0”;持续 70us 表示数据“1”。

当 最后 1bit 数据传送完毕后,DHT11 拉低总线 50us,表示数据传输完毕,随后总线由上拉电阻拉高进入空闲状态。
区分0/1的小方法
数据“0”的高电平持续 26~28us,数据“1”的高电平持续70us,每一位数据前都有 50us 的起始时隙。如果我们取一个中间值 40us 来区分数据“0”和数据“1”的时隙。

当数据位之前的 50us 低电平时隙过后,总线肯定会拉高,此时延时 40us 后检测总线状态,如果为高,说明此时处于 70us 的时隙,则数据为“1”;如果为低,说明此时处于下一位数据 50us 的开始时隙,那么上一位数据肯定是“0”。

为什么延时 40us?
由于误差的原因,数据“0”时隙并不是准确 26~28us,可能比这短,也可能比这长。
当数据“0”时隙大于 26~28us 时,
如果延时太短,无法判断当前处于数据“0”的时隙还是数据“1”的时隙;
如果延时太长,则会错过下一位数据前的开始时隙,导致检测不到后面的数据

LCD1602

简介

1602液晶也叫1602字符型液晶,它是一种专门用来显示字母、数字、符号的点阵型液晶模块。
它是由若干个5x7或者5x10的点阵字符位组成,每个点阵字符位都可以用显示一个字符,每位之间有一个点距的间隔,每行之间也有间隔,起到了字符间距和行间距的作用,正因为如此,所以它不能很好的显示图片。

相关引脚

序号      名称         注释
1        VSS        电源负极
2        VDD        电源正极
3        VEE        电源负极
4        RS         指令/数据选择信号
5        RW         读写选择信号
6        E          使能信号
7        DB0        数据0
8        DB1        数据1
9        DB2        数据2
10       DB3        数据3
11       DB4        数据4
12       DB5        数据5
13       DB6        数据6
14       DB7        数据7

基本操作时序

读状态        输入:RS=L,RW=H,E=H                      输出:D0~D7=状态字
写指令        输入:RS=L,RW=L,D0~D7=指令码,E=高脉冲    输出:无
读数据        输入:RS=H,RW=H,E=H                      输出:D0~D7=数据
写数据        输入:RS=H,RW=L,D0~D7=数据,E=高脉冲      输出:无

写指令代码

void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS = 0; //指令
	LCD_RW = 0; //写
	LCD_DataPort = Command; //数据存放
	LCD_E  = 1; //使能
	Delay1ms(); //最低要求延迟150纳秒 我们直接给1ms
	LCD_E  = 0; //失能
	Delay1ms();
}

写数据代码

void LCD_WriteData(unsigned char Data)
{
	LCD_RS = 1; //数据
	LCD_RW = 0; //写
	LCD_DataPort = Data; //数据存放
	LCD_E  = 1; //使能
	Delay1ms(); //最低要求延迟150纳秒 我们直接给1ms
	LCD_E  = 0; //失能
	Delay1ms();
}

LCD1602显示程序步骤及如何初始化

1、显示开关控制指令(功能:控制显示器开/关 光标显示/关闭 是否闪烁)
2、进入模式设置指令(功能:每写入一位数据后光标的移动 左/右 显示屏不移动/整体右移 )
3、功能设定指令(功能:设定数据总线的位数 显示的行数 字型 )
4、清屏指令(功能:清屏显示空白 光标归位 地址计数器(AC)的值为0)
5、显示位置设置(功能:指定在1602上哪个位置上显示数据)
显示开关控制指令
LCD_WriteCommand(0x0c); // 0000 1100 显示关,游标不显示,不闪烁

RS  R/W  DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
0    0    0    0    0    0    1    D    C    B

D:显示开/关控制标志:D=1,开显示;D=0,关显示;关显示后,显示数据仍保持在 DDRAM 中,立即开显示可以再现:
C:游标显示控制标志:C=1,游标显示;C=0,游标不显示;不显示游标并不影响模块其它显示功能;显示5x8点阵字符时,游标在第八行显示,显示5x10点阵字符时,游标在第十一行显示;
B:闪烁显示控制标志:B=1,游标所指位置上,交替显示全黑点阵和显示字符,产生闪烁效果,Fosc=250kHz时,闪烁频率为0.4ms左右;通CSDN @ZCY(Yinyuer1过设置,游标可以与其所指位置的字符一起闪烁。
进入模式设置指令
LCD_WriteCommand(0x06) // 0000 0110 写入数据后光标自动右移 整屏不移动

RS  R/W  DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
0    0    0    0    0    0    0    1   I/D   S

I/D:字符码写入或读出 DDRAM后 DDRAM 地址指针 AC 变化方向标志:
    I/D=1,完成一个字符码传送后,游标右移,AC自动加1;
    I/D=0,完成一个字符码传送后,光标左移,AC自动减1;
S:显示移位元标志:
    S=1,将全部显示向右(I/D=0)或者向左(ID=1)移位;S=0,显示不发生移位元;
    S=1时,显示移位元时,游标似乎并不移位:此外,读 DDRAM 操作以及对CGRAM的访问,不发生显示移位元。
功能设定指令
LCD_WriteCommand(0x38); //0011 1000 数据总线8位 16X2显示 5*7点阵

RS  R/W  DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
0    0    0    0    1   DL    N    F    *    *

DL:数据接口宽度标志:
    DL=1,8位数据总线 DB7~DB0;DL=0,4位数据总线DB7~DB4,DB3~DB0 不用,使用此方式传送数据,需分两次进行;
N:显示行数标志:
    N=1,两行显示模式; N=0,单行显示模式;
F:显示字符点阵字体标志:
    F=1:5x10 点阵+游标显示模式;F=0:5x7 点阵+游标显示模式
清屏指令
LCD_WriteCommand(0x01); //0000 0001 清屏 

RS  R/W  DB7  DB6  DB5  DB4  DB3  DB2  DB1  DB0
0    0    0    0    0    0    0    0    0    1

清显示指令将空位字符码 20H 送入全部 DDRAM 位址中,使 DDRAM 中的内容全部清除,显示消失;
地址计数器AC=0,自动增1模式:显示归位,游标或者闪烁回到原点(显示屏左上角);
但并不改变移位元元设置模式。
显示位置设置

如果想在1602屏幕上第一行第一个开始显示 就是0x80

如果想在1602屏幕上第二行第一个开始显示 就是0xc0

void LCD_SetCursor(unsigned char Line,unsigned char Columu)
{
	if(Line == 1)
	{
		LCD_WriteCommand( 0x80 | (Columu-1) );
	}
	else
	{
		LCD_WriteCommand( 0x80 | ( Columu-1 )+0x40 );
	}
}

显示字符

void LCD_ShowChar(unsigned char Line,unsigned char Columu,unsigned char Char)
{
	LCD_SetCursor(Line,Columu);
	
	LCD_WriteData(Char);
}

显示字符串

void LCD_ShowString(unsigned char Line,unsigned char Columu,unsigned char* String)
{
	unsigned char i = 0;
	
	LCD_SetCursor(Line,Columu);
	
	for(i=0;String[i] != '\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

获取数次方

int LCD_Pow(unsigned char x,unsigned char y)
{
	unsigned char i = 0;
	int result = 1;
	
	for(i=0;i<y;i++)
	{
		result *= x;
	}
	
	return result;
}

显示数字

void LCD_ShowNum(unsigned char Line,unsigned char Columu,unsigned int Num,unsigned int Length)
{
	unsigned char i = 0;
	
	LCD_SetCursor(Line,Columu);
	
	for(i=Length;i>0;i--)
	{
		LCD_WriteData('0'+Num/LCD_Pow(10,i-1)%10); //'0' 从0开始
	}
}

硬件电路搭建

程序代码编写

方法一

main.c

#include "reg51.h"
#include "LCD1602.h"
#include "DHT11.h"
#include "TIM.h"

#define uchar unsigned char
#define uint  unsigned int

sbit k1	  = P1^0;//设置
sbit k2	  = P1^1;//加
sbit k3	  = P1^2;//减
sbit beep = P2^3;//蜂鸣器报警输出

uchar disp1[] = "temp:00 C      ";
uchar disp2[] = "humi:00%      ";
uchar disp3[] = "TL:00 C TH:00 C";
uchar disp4[] = "HL:00%  HH:00%";

uchar mode=0,time=0;    //模式
uchar tempL=5,tempH=10;//温度阈值
uchar humiL=5,humiH=10;//湿度阈值


​ void main() ​ { ​ // disp1[7]=0xdf; ​ // disp3[5]=0xdf; ​ // disp3[13]=0xdf; ​ init_1602(); TIM_Init();

	while(1)
	{
		 if(!k1)//设置 模式1、2、3、4
		 {
		 	if(mode < 4)
				mode ++;
			else
				mode = 0;
		 	while(!k1);
		 }
		 if(!k2)//加
		 {
		 	switch(mode)
			{
				case 1://温度报警下限,温度下限不能超过上限,最少需要 1 摄氏度温差
					if(tempL < tempH - 1)
						tempL ++;
					break;
				case 2://温度报警上限,温度上限不能超过 99 摄氏度,上限达到 99 摄氏度不再增加温度上限
					if(tempH < 99)
						tempH ++;
					else
						tempH = 99;
					break;
				case 3://湿度报警下限,湿度下限不能超过上限,最少需要 1% 差值
					if(humiL < humiH - 1)
						humiL ++;
					break;
				case 4://湿度报警上限,湿度上限不能超过 99% ,上限达到 99% 不再增加湿度上限
					if(humiH < 99)
						humiH ++;
					else
						humiH = 99;
					break;	
			}
		 	while(!k2);
		 }
		 if(!k3)//减
		 {
		 	switch(mode)
			{
				case 1://温度报警下限,温度下限最低为 0 摄氏度
					if(tempL > 0)
						tempL --;
					else
						tempL = 0;
					break;
				case 2://温度报警上限,温度下限不能超过上限,最少需要 1 摄氏度温差
					if(tempH > tempL + 1)
						tempH --;
					break;
				case 3://湿度报警下限,湿度下限最低为 0%
					if(humiL > 0)
						humiL --;
					else
						humiL = 0;
					break;
				case 4://湿度报警上限,湿度下限不能超过上限,最少需要 1% 差值
					if(humiH > humiL + 1)
						humiH --;
					break;
			}
		 	while(!k3);
		 }
	}
}

//定时器中断
void Timer0() interrupt 1
{
	if(time<10)//每 50ms time 变量加一,0.5s检测一次
		time++;
	else
	{
		time=0;
		dht11_recive();//测量
		//显示
		disp1[5]=dht11_dat[2]/10+0x30;
		disp1[6]=dht11_dat[2]%10+0x30;
		disp2[5]=dht11_dat[0]/10+0x30;
		disp2[6]=dht11_dat[0]%10+0x30;
		disp3[3]=tempL/10+0x30;
		disp3[4]=tempL%10+0x30;
		disp3[11]=tempH/10+0x30;
		disp3[12]=tempH%10+0x30;
		disp4[3]=humiL/10+0x30;
		disp4[4]=humiL%10+0x30;
		disp4[11]=humiH/10+0x30;
		disp4[12]=humiH%10+0x30;
		write_com(0x0c);
		if(mode==0)
		{
			write_string(1,0,disp1);
			write_string(2,0,disp2);
		}
		else
		{
			write_string(1,0,disp3);
			write_string(2,0,disp4);
		}
		//设置光标
		switch(mode)
		{
			case 1:write_sfm(1,4);break;
			case 2:write_sfm(1,12);break;
			case 3:write_sfm(2,4);break;
			case 4:write_sfm(2,12);
		}
		if(mode>0)
			write_com(0x0e);
		//报警
		if((dht11_dat[2]>tempH)||(dht11_dat[2]<tempL)||(dht11_dat[0]>humiH)||(dht11_dat[0]<humiL))
			beep=0;
		else
			beep=1;
	}
	TH0=0X3C;
	TL0=0XB0;
}

DHT11.c

#include "DHT11.h"

//湿度高低整数部分+湿度高低小数部分+温度高低整数部分+温度高低小数部分+和校验
//  dht11_dat[0]     dht11_dat[1]      dht11_dat[2]     dht11_dat[3]  dht11_dat[4]
unsigned char dht11_dat[5];//湿度高低+温度高低+和校验

void delay1ms(unsigned int i) //延时函数
{ 
unsigned char j; 
 while(i--)
 {
	for(j=0;j<110;j++);
 }
}

void dht11_recive()//接收
{
unsigned char j,k,m;
unsigned int i;
  SDA=0;
  delay1ms(30);//开始信号20ms
  SDA=1;
  for(i=0;i<1000;i++)//等待响应
  {
  if(!SDA)
  	break;
  }
  if(i==1000)
  {	
    SDA=1;
   	return;//如果没有响应,返回
  }
  for(i=0;i<1000;i++)//等待响应结束
  {
  if(SDA)
  	break;
  }
  if(i==1000)
  {
    SDA=1;
   	return;//如果响应超时,返回
  }
  for(i=0;i<1000;i++)//等待开始
  {
  if(!SDA)
  	break;
  }
  if(i==1000)
  {	
    SDA=1;
   	return;//如果没有响应,返回
  }
for(k=0;k<5;k++) //接收5个数据
	{
	for(j=0;j<8;j++)//接收8位
		{ 
			m<<=1;
			//===========================
		  for(i=0;i<1000;i++)//等待0信号结束
  			{
  			if(SDA)
  				break;
  			}
  			if(i==1000)
  			{	
    			SDA=1;
   				return;//如果超时,返回
  			}
			//===========================
		   for(i=0;i<1000;i++)//等待开始信号
  			{
  			if(!SDA)
  				break;
  			}
  			if(i==1000)
  			{
    			SDA=1;
   				return;//如果超时,返回
  			}
			if(i<5)
				m++;
		}
		dht11_dat[k]=~m;
	}
	delay1ms(1);	  	
} 

DHT11.h

#include <reg51.h>

sbit SDA=P2^7;//信号输入输出引脚

extern unsigned char dht11_dat[5];

void dht11_recive();
void delay1ms(unsigned int i);

LCD1602.c

void delay_uint(uint i)
{
	while(i--);
}
/********************************************************************
* 名称 : write_com(uchar com)
* 功能 : 1602命令函数
* 输入 : 输入的命令值
* 输出 : 无
***********************************************************************/
void write_com(uchar com)
{
	e=0;
	rs=0;
	rw=0;
	P0=com;
	delay_uint(20);
	e=1;
	delay_uint(20);
	e=0;
}

/********************************************************************
* 名称 : write_data(uchar dat)
* 功能 : 1602写数据函数
* 输入 : 需要写入1602的数据
* 输出 : 无
***********************************************************************/
void write_data(uchar dat)
{
	e=0;
	rs=1;
	rw=0;
	P0=dat;
	delay_uint(20);
	e=1;
	delay_uint(20);
	e=0;	
}

/********************************************************************
* 名称 : write_sfm(uchar hang,uchar add,uchar date)
* 功能 : 设置当前行和列
* 输入 : 行,列
* 输出 : 无
***********************************************************************/
void write_sfm(uchar hang,uchar add)
{
	if(hang==1)   			   //设置当前行
		write_com(0x80+add);
	else
		write_com(0x80+0x40+add);	
}

/********************************************************************
* 名称 : write_string(uchar hang,uchar add,uchar *p)
* 功能 : 改变液晶中某位的值,如果要让第一行,第五个字符开始显示"ab cd ef" ,调用该函数如下
	 	 write_string(1,5,"ab cd ef;")
* 输入 : 行,列,需要输入1602的数据
* 输出 : 无
***********************************************************************/
void write_string(uchar hang,uchar add,uchar *p)
{
	if(hang==1)   
		write_com(0x80+add);
	else
		write_com(0x80+0x40+add);
		while(1)
		{
			if(*p == '\0')  break;
			write_data(*p);
			p++;
		}	
}

/********************************************************************
* 名称 : init_1602()
* 功能 : 初始化1602液晶 
* 输入 : 无
* 输出 : 无
***********************************************************************/
void init_1602()
{
	write_com(0x38);						//数据总线为8位,显示2行,5x7点阵
	write_com(0x0e);						//开显示,有光标,光标闪烁
	write_com(0x06);						//光标自动右移
	delay_uint(1000);						//等待设置完成
}

LCD1602.h

/**********************************
当使用的是4位数据传输的时候定义,
使用8位取消这个定义
**********************************/
//#define LCD1602_4PINS

/**********************************
包含头文件
**********************************/
#include<reg51.h>

//---重定义关键词---//
#ifndef uchar
#define uchar unsigned char
#endif

#ifndef uint 
#define uint unsigned int
#endif

/**********************************
PIN口定义
**********************************/
sbit rs=P2^0;	 //1602数据/命令选择引脚 H:数据      	L:命令
sbit rw=P2^1;	 //1602读写引脚	         H:数据寄存器  	L:指令寄存器
sbit e =P2^2;	 //1602使能引脚          下降沿触发

/**********************************
函数声明
**********************************/
void write_com(uchar com);						   //LCD1602写入8位数据子函数	
void write_data(uchar dat);						   //1602写数据函数		
void init_1602();								   //LCD1602初始化子程序						  
void write_sfm(uchar hang,uchar add);			   //设置当前行和列
void write_string(uchar hang,uchar add,uchar *p);  //显示字符串
void delay_uint(uint i);

TIM.c

void TIM_Init(void)//5ms 溢出一次
{
	TMOD|=0X01;  //定时器0工作方式1
	TH0=0X3C;
	TL0=0XB0;	
	ET0=1;       //打开定时器0中断允许
	EA=1;		 //打开总中断
	TR0=1;		 //打开定时器
}

TIM.h

void TIM_Init();

方法二

main.c

#include "reg51.h"
#include "LCD1602.h"
#include "DHT11.h"
#include "TIM.h"

#define uchar unsigned char
#define uint  unsigned int

sbit k1	  = P1^0;//设置
sbit k2	  = P1^1;//加
sbit k3	  = P1^2;//减
sbit beep = P2^3;//蜂鸣器报警输出

uchar disp1[] = "temp:00 C      ";
uchar disp2[] = "humi:00%      ";
uchar disp3[] = "TL:00 C TH:00 C";
uchar disp4[] = "HL:00%  HH:00%";

uchar mode=0,time=0;    //模式
uchar tempL=5,tempH=40;//温度阈值
uchar humiL=5,humiH=40;//湿度阈值


​ void main() ​ { ​ disp1[7] = 0xdf; ​ disp3[5] = 0xdf; ​ disp3[13] = 0xdf; ​ init_1602(); TIM_Init();

	while(1)
	{
		 if(!k1)//设置 模式1、2、3、4
		 {
		 	if(mode < 4)
				mode ++;
			else
				mode = 0;
		 	while(!k1);
		 }
		 if(!k2)//加
		 {
		 	switch(mode)
			{
				case 1://温度报警下限,温度下限不能超过上限,最少需要 1 摄氏度温差
					if(tempL < tempH - 1)
						tempL ++;
					break;
				case 2://温度报警上限,温度上限不能超过 99 摄氏度,上限达到 99 摄氏度不再增加温度上限
					if(tempH < 99)
						tempH ++;
					else
						tempH = 99;
					break;
				case 3://湿度报警下限,湿度下限不能超过上限,最少需要 1% 差值
					if(humiL < humiH - 1)
						humiL ++;
					break;
				case 4://湿度报警上限,湿度上限不能超过 99% ,上限达到 99% 不再增加湿度上限
					if(humiH < 99)
						humiH ++;
					else
						humiH = 99;
					break;	
			}
		 	while(!k2);
		 }
		 if(!k3)//减
		 {
		 	switch(mode)
			{
				case 1://温度报警下限,温度下限最低为 0 摄氏度
					if(tempL > 0)
						tempL --;
					else
						tempL = 0;
					break;
				case 2://温度报警上限,温度下限不能超过上限,最少需要 1 摄氏度温差
					if(tempH > tempL + 1)
						tempH --;
					break;
				case 3://湿度报警下限,湿度下限最低为 0%
					if(humiL > 0)
						humiL --;
					else
						humiL = 0;
					break;
				case 4://湿度报警上限,湿度下限不能超过上限,最少需要 1% 差值
					if(humiH > humiL + 1)
						humiH --;
					break;
			}
		 	while(!k3);
		 }
	}
}

//定时器中断
void Timer0() interrupt 1
{
	if(time<10)//每 50ms time 变量加一,0.5s检测一次
		time++;
	else
	{
		time=0;
		DHT11_receive();
		//显示
		
		disp1[5]=rec_dat[4];
		disp1[6]=rec_dat[5];
		disp2[5]=rec_dat[0];
		disp2[6]=rec_dat[1];
		disp3[3]=tempL/10+0x30;
		disp3[4]=tempL%10+0x30;
		disp3[11]=tempH/10+0x30;
		disp3[12]=tempH%10+0x30;
		disp4[3]=humiL/10+0x30;
		disp4[4]=humiL%10+0x30;
		disp4[11]=humiH/10+0x30;
		disp4[12]=humiH%10+0x30;
		
		write_com(0x0c);
		if(mode==0)
		{
			write_string(1,0,disp1);
			write_string(2,0,disp2);
		}
		else
		{
			write_string(1,0,disp3);
			write_string(2,0,disp4);
		}
		//设置光标
		switch(mode)
		{
			case 1:write_sfm(1,4);break;
			case 2:write_sfm(1,12);break;
			case 3:write_sfm(2,4);break;
			case 4:write_sfm(2,12);
		}
		if(mode>0)
			write_com(0x0e);
		//报警
		if((rec_data[0]>tempH)||(rec_data[0]<tempL)||(rec_data[2]>humiH)||(rec_data[2]<humiL))
			beep=0;
		else
			beep=1;
	}
	TH0=0X3C;
	TL0=0XB0;
}

DHT11.c

#include "reg51.h"
#include "DHT11.h"

#define uchar unsigned char
#define uint unsigned int

uchar rec_dat[9]; //储存数据
uchar rec_data[5];

void DHT11_delay_us(uchar n)
{
    while(--n);
}
void DHT11_delay_ms(uint z)
{
   uint i,j;
   for(i=z;i>0;i--)
      for(j=110;j>0;j--);
}

/*
主机(单片机)发送起始信号:
1.主机先拉高data。
2.拉低data延迟18ms。
3.拉高data并延迟等待(通过此操作将单片机引脚设置为输入)。
*/
void DHT11_start()
{
   Data=1;
   DHT11_delay_us(2);
   Data=0;
   DHT11_delay_ms(25);   //拉低延时18ms以上
   Data=1;
   DHT11_delay_us(30);   //拉高 延时 20~40us,取中间值 30us
}

/*------------------------------------------------
              接收八位二进制
------------------------------------------------*/
uchar DHT11_rec_byte()      //接收一个字节
{
  unsigned char i,dat=0;
  for(i=0;i<8;i++)    //从高到低依次接收8位数据
   {          
      while(Data);   //等待进入低电平
      while(!Data);   //等待50us低电平过去
      DHT11_delay_us(8);     //延时60us,如果还为高则数据为1,否则为0 
      dat<<=1;//移位(低位补零)使正确接收8位数据,数据为0时直接移位
      if(Data==1)    //数据为1时,使dat加1来接收数据1
        dat+=1;
      while(Data);  //等待数据线拉低    
   }  
    return dat;
}


​ /———————————————— ​ 接收40bit数据(具体的温湿度) ​ 1.主机先把data线拉高(io设置为输入)。 ​ 2.从机把data线拉低,主机读取data线电平,直到低电平结束(大约50us) ​ 从机拉高data线后,延迟40us左右(28~70us之间)主机再次读取data线电平,如果为低电平,则为“0”,如果为高电平,则为“1”。 ​ 3.继续重复上述1,2步骤累计40次。 ​ ————————————————/ ​ uchar T_H; ​ void DHT11_receive() //接收40位的数据 ​ { ​ uchar R_H,R_L,T_L,RH,RL,TH,TL,revise; ​ DHT11_start();//发送起始信号: ​ if(Data==0) ​ { ​ while(Data==0); //等待拉高
​ DHT11_delay_us(40); //拉高后延时80us ​ R_H=DHT11_rec_byte(); //接收湿度高八位
R_L=DHT11_rec_byte(); //接收湿度低八位
T_H=DHT11_rec_byte(); //接收温度高八位
T_L=DHT11_rec_byte(); //接收温度低八位 revise=DHT11_rec_byte(); //接收校正位

        DHT11_delay_us(25);    //结束

        if((R_H+R_L+T_H+T_L)==revise)      //最后一字节为校验位,校验是否正确
        {
            RH=R_H;
            RL=R_L;
            TH=T_H;
            TL=T_L;
        } 
				
        /*数据处理,转换为字符,方便显示*/
			  //湿度
		rec_dat[0]='0'+(RH/10);
        rec_dat[1]='0'+(RH%10);
		rec_dat[2]=' ';
		rec_dat[3]=' ';
				
				//温度
        rec_dat[4]='0'+(TH/10);
        rec_dat[5]='0'+(TH%10); 
		rec_dat[6]=' ';
		
		rec_data[0] = TH;
		rec_data[2] = RH;
			
    }

}

DHT11.h

#define uchar unsigned char
#define uint unsigned int

sbit Data=P2^7; //数据线

extern uchar rec_dat[9]; //储存数据
extern uchar rec_data[5];

void DHT11_delay_us(uchar n);
void DHT11_delay_ms(uint z);

void DHT11_start();
uchar DHT11_rec_byte();
void DHT11_receive();

LCD1602程序内容同方法一

TIM程序内容同方法一

方法三

main.c程序内容同方法二

DHT11程序内容同方法二

TIM程序内容同方法一

LCD1602.c

#include "reg51.h"
#include "LCD1602.h"
#include "intrins.h"

sbit LCD_RS = P2^5;
sbit LCD_RW = P2^6;
sbit LCD_E  = P2^7;
sbit LED_V0 = P1^0;

#define LCD_DataPort P0

void Delay1ms()		//@11.0592MHz 1ms
{
	unsigned char i, j;

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

void LCD_WriteCommand(unsigned char Command)
{
		LCD_RS = 0;
		LCD_RW = 0;
		LCD_DataPort = Command;
		LCD_E  = 1;
		Delay1ms();
		LCD_E  = 0;
		Delay1ms();
		
}

void LCD_WriteData(unsigned char Data)
{
		LCD_RS = 1;
		LCD_RW = 0;
		LCD_DataPort = Data;
		LCD_E  = 1;
		Delay1ms();
		LCD_E  = 0;
		Delay1ms();
		
}

void LCD_Init()
{
		LCD_WriteCommand(0x38);
		LCD_WriteCommand(0x0c);
		LCD_WriteCommand(0x06);
		LCD_WriteCommand(0x01);
		LED_V0 = 0;
}

void LCD_SetCursor(unsigned char Line,unsigned char Columu)
{
	if(Line == 1)
	{
		LCD_WriteCommand( 0x80 | (Columu-1) );
	}
	else
	{
		LCD_WriteCommand( 0x80 | ( Columu-1 )+0x40 );
	}
}

void LCD_ShowChar(unsigned char Line , unsigned char Columu , unsigned char Char)
{
		LCD_SetCursor(Line,Columu);
	
		LCD_WriteData(Char);
}

void LCD_ShowString(unsigned char Line , unsigned char Columu , unsigned char* String)
{
		unsigned char i = 0;
	
		LCD_SetCursor(Line,Columu);
	
		for(i=0;String[i] != '\0';i++)
		{
				LCD_WriteData(String[i]);
		}
}

int LCD_Pow(unsigned char x,unsigned char y)
{
		int result = 1;
		unsigned char i = 0;
		
		for(i=0;i<y;i++)
		{
				result *= x;
		}
		
		return result;
}

void LCD_ShowNum(unsigned char Line,unsigned char Columu,unsigned int Num,unsigned int Length)
{
		unsigned char i = 0;
		
		LCD_SetCursor(Line,Columu);
		
		for(i=Length;i>0;i--)
		{
				LCD_WriteData('0'+Num/LCD_Pow(10,i-1)%10);
		}
}

LCD1602.h

#ifndef _LCD1602_h_
#define _LCD1602_h_

void Delay1ms(void);
void LCD_WriteCommand(unsigned char Command);
void LCD_WriteData(unsigned char Data);
void LCD_Init(void);
void LCD_SetCursor(unsigned char Line,unsigned char Columu);
void LCD_ShowChar(unsigned char Line,unsigned char Columu,unsigned char Char);
void LCD_ShowString(unsigned char Line,unsigned char Columu,unsigned char* String);
void LCD_ShowNum(unsigned char Line,unsigned char Columu,unsigned int Num,unsigned int Length);

#endif

直接调用使用示例

LCD_Init(); //初始化
LCD_ShowChar(1,7,'Y');        
LCD_ShowString(2,1,"Hello! Yanzijun");
LCD_ShowNum(1,8,689,3);

STM32单片机示例如下

DHT11.c

#include "stm32f10x.h"                  // Device header
#include  "dht11.h"
#include  "delay.h"
//数据
unsigned int rec_data[4];

//对于stm32来说,是输出
void DH11_GPIO_Init_OUT(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP; //推挽输出
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOB, &GPIO_InitStructure);

}

//对于stm32来说,是输入
void DH11_GPIO_Init_IN(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING; //浮空输入
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOB, &GPIO_InitStructure);

}

//主机发送开始信号
void DHT11_Start(void)
{
	DH11_GPIO_Init_OUT(); //输出模式
	
	dht11_high; //先拉高
	delay_us(30);
	
	dht11_low; //拉低电平至少18us
	delay_ms(20);
	
	dht11_high; //拉高电平20~40us
	delay_us(30);
	
	DH11_GPIO_Init_IN(); //输入模式
}

//获取一个字节
char DHT11_Rec_Byte(void)
{
	unsigned char i = 0;
	unsigned char data;
	
	for(i=0;i<8;i++) //1个数据就是1个字节byte,1个字节byte有8位bit
	{
		while( Read_Data == 0); //从1bit开始,低电平变高电平,等待低电平结束
		delay_us(30); //延迟30us是为了区别数据0和数据1,0只有26~28us
		
		data <<= 1; //左移
		
		if( Read_Data == 1 ) //如果过了30us还是高电平的话就是数据1
		{
			data |= 1; //数据+1
		}
		
		while( Read_Data == 1 ); //高电平变低电平,等待高电平结束
	}
	
	return data;
}

//获取数据

void DHT11_REC_Data(void)
{
	unsigned int R_H,R_L,T_H,T_L;
	unsigned char RH,RL,TH,TL,CHECK;
	
	DHT11_Start(); //主机发送信号
	dht11_high; //拉高电平
	
	if( Read_Data == 0 ) //判断DHT11是否响应
	{
		while( Read_Data == 0); //低电平变高电平,等待低电平结束
		while( Read_Data == 1); //高电平变低电平,等待高电平结束
		
		R_H = DHT11_Rec_Byte();
		R_L = DHT11_Rec_Byte();
		T_H = DHT11_Rec_Byte();
		T_L = DHT11_Rec_Byte();
		CHECK = DHT11_Rec_Byte(); //接收5个数据
		
		dht11_low; //当最后一bit数据传送完毕后,DHT11拉低总线 50us
		delay_us(55); //这里延时55us
		dht11_high; //随后总线由上拉电阻拉高进入空闲状态。
		
		if(R_H + R_L + T_H + T_L == CHECK) //和检验位对比,判断校验接收到的数据是否正确
		{
			RH = R_H;
			RL = R_L;
			TH = T_H;
			TL = T_L;
		}
	}
	rec_data[0] = RH;
	rec_data[1] = RL;
	rec_data[2] = TH;
	rec_data[3] = TL;
}

DHT11.h

#ifndef __DHT11_H
#define __DHT11_H
#include "stm32f10x.h"                  // Device header

#define dht11_high GPIO_SetBits(GPIOB, GPIO_Pin_12)
#define dht11_low GPIO_ResetBits(GPIOB, GPIO_Pin_12)
#define Read_Data GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12)

void DHT11_GPIO_Init_OUT(void);
void DHT11_GPIO_Init_IN(void);
void DHT11_Start(void);
unsigned char DHT11_REC_Byte(void);
void DHT11_REC_Data(void);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "delay.h"
#include "OLED.h"
#include  "dht11.h"
#include  "usart.h"

extern unsigned int rec_data[4];

int main()
{

    OLED_Init();
	uart_init(9600);
	
    OLED_ShowCHinese(0, 0, 0);							//温
	OLED_ShowCHinese(0, 16, 1); 						//度
	OLED_ShowCHinese(0, 32, 4);							//:
	OLED_ShowCHinese(0, 60, 5);							//.
	OLED_ShowCHinese(0, 90, 6);							//℃

​ ​ OLED_ShowCHinese(4, 0, 2); //湿 ​ OLED_ShowCHinese(4, 16, 3); //度 ​ OLED_ShowCHinese(4, 32, 4); //: ​ OLED_ShowCHinese(4, 60, 5); //. ​ OLED_ShowCHinese(4, 90, 7); //% ​ while(1) ​ { ​ delay_s(1); ​ DHT11_REC_Data(); //接收温度和湿度的数据 ​ OLED_ShowNum(1,7,rec_data[2],2); OLED_ShowNum(1,10,rec_data[3],1); OLED_ShowNum(3,7,rec_data[0],2); OLED_ShowNum(3,10,rec_data[1],2); printf(“温度:%d\r\n”,rec_data[2]); printf(“湿度:%d\r\n”,rec_data[0]); printf(“\r\n\n”);

	}
}

LCD1602.c

	#include  "LCD1602.h"
/******************************************************************************
 * 函数名称:void GPIO_Configuration()                              *
 * 函数功能:LCD1602引脚初始化                                                           *
 * 输入参数:无                                                      *
 * 返回值  :无                                                                  *
 * 其他说明:                                                                         *
 ******************************************************************************/
/*******************根据自己的硬件引脚做修改*****************************************/
void GPIO_Configuration()
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE );
    GPIO_InitStructure.GPIO_Pin    = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14;
    GPIO_InitStructure.GPIO_Speed    = GPIO_Speed_50MHz;//选择工作频率
    GPIO_InitStructure.GPIO_Mode    = GPIO_Mode_Out_PP;//设置工作模式
    GPIO_Init( GPIOB, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin    = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode    = GPIO_Mode_Out_PP;//设置工作模式
    GPIO_InitStructure.GPIO_Speed    = GPIO_Speed_50MHz;//选择工作频率
    GPIO_Init( GPIOA, &GPIO_InitStructure );
}
/******************************************************************************
 * 函数名称:void LCD1602_Init()                              *
 * 函数功能:LCD1602初始化                                                           *
 * 输入参数:无                                                      *
 * 返回值  :无                                                                  *
 * 其他说明:                                                                         *
 ******************************************************************************/
void LCD1602_Init()
{
    GPIO_Configuration();            //初始化引脚

    LCD1602_Write_Cmd( 0x38 );      //显示模式设置
    delay_ms( 5 );
    LCD1602_Write_Cmd( 0x0c );      //显示开及光标设置
    delay_ms( 5 );
    LCD1602_Write_Cmd( 0x06 );      //显示光标移动位置
    delay_ms( 5 );
    LCD1602_Write_Cmd( 0x01 );      //显示清屏
    delay_ms( 5 );    
}
/******************************************************************************
 * 函数名称:void LCD1602_Write_Cmd(u8 cmd)                              *
 * 函数功能:写命令函数                                                               *
 * 输入参数:    cmd 命令                                                          *
 * 返回值  :无                                                                  *
 * 其他说明:                                                                         *
 ******************************************************************************/
void LCD1602_Write_Cmd( u8 cmd )
{
    LCD_RS_Clr();
    LCD_RW_Clr();
    LCD_EN_Set();

    GPIO_Write( GPIOA, (GPIO_ReadOutputData( GPIOA ) & 0xff00) | cmd );//对电平的读取

    DATAOUT( cmd );
    delay_ms( 5 );
    LCD_EN_Clr();
}

/******************************************************************************
 * 函数名称:void LCD1602_Write_Dat(u8 date)                              *
 * 函数功能:写数据函数                                                               *
 * 输入参数:    date 数据                                                          *
 * 返回值  :无                                                                  *
 * 其他说明:                                                                         *
 ******************************************************************************/
void LCD1602_Write_Dat( u8 data )
{
    LCD_RS_Set();
    LCD_RW_Clr();
    LCD_EN_Set();

    GPIO_Write( GPIOA, (GPIO_ReadOutputData( GPIOA ) & 0xff00) | data );//对电平的读取

    delay_ms( 5 );
    LCD_EN_Clr();
}

/******************************************************************************
 * 函数名称:void LCD1602_ClearScreen()                              *
 * 函数功能:1602清屏函数                                                               *
 * 输入参数:无                                                          *
 * 返回值  :无                                                                  *
 * 其他说明:                                                                         *
 ******************************************************************************/
void LCD1602_ClearScreen()
{
    LCD1602_Write_Cmd( 0x01 );
}

/******************************************************************************
 * 函数名称:void LCD1602_Set_Cursor(u8 x, u8 y)                              *
 * 函数功能:设置1602位置函数                                                       *
 * 输入参数:x 横坐标 y 纵坐标                                                          *
 * 返回值  :无                                                                  *
 * 其他说明:                                                                         *
 ******************************************************************************/
void LCD1602_Set_Cursor( u8 x, u8 y )
{
    u8 addr;

    if ( y == 0 )
        addr = 0x00 + x;
    else
        addr = 0x40 + x;
    LCD1602_Write_Cmd( addr | 0x80 );
}

/******************************************************************************
 * 函数名称:void LCD1602_Show_Str( u8 x, u8 y, u8 *str )                              *
 * 函数功能:指定位置显示字符串函数                                                       *
 * 输入参数:x 横坐标 y 纵坐标        *str 字符串                                  *
 * 返回值  :    无                                                              *
 * 其他说明:                                                                         *
 ******************************************************************************/
void LCD1602_Show_Str( u8 x, u8 y, u8 *str )
{
    LCD1602_Set_Cursor( x, y );
    while ( *str != '\0' )
    {
        LCD1602_Write_Dat( *str++ );
    }
}

LCD1602.h

#ifndef __LCD1602_H
#define __LCD1602_H 

/***************************根据自己的硬件引脚做修改*****************************/
#define LCD_RS_Set()    GPIO_SetBits( GPIOB, GPIO_Pin_12 )//1602的数据/指令选择控制线
#define LCD_RS_Clr()    GPIO_ResetBits( GPIOB, GPIO_Pin_12 )

#define LCD_RW_Set()    GPIO_SetBits( GPIOB, GPIO_Pin_13 )//1602的读写控制线
#define LCD_RW_Clr()    GPIO_ResetBits( GPIOB, GPIO_Pin_13 )

#define LCD_EN_Set()    GPIO_SetBits( GPIOB, GPIO_Pin_14 )//1602的使能控制线
#define LCD_EN_Clr()    GPIO_ResetBits( GPIOB, GPIO_Pin_14 )

#define DATAOUT( x ) GPIO_Write( GPIOA, x )    //1602的8条数据控制线

void GPIO_Configuration();

void LCD1602_Init();

void LCD1602_Wait_Ready();

void LCD1602_Write_Cmd( u8 cmd );

void LCD1602_Write_Dat( u8 data );

void LCD1602_ClearScreen();

void LCD1602_Set_Cursor( u8 x, u8 y );

void LCD1602_Show_Str( u8 x, u8 y, u8 *str );

#endif

LCD1602 STM32驱动(6线)

LCD1602.c

#include “LCD1602.h” #include “delay.h”//程序中的延时函数根据自己32单片机来就行
void hc595SendData(unsigned char sendVal) { unsigned char i; //从CPU中向595一位一位发送,595一位一位接收 for(i = 0; i < 8; i++) { if((sendVal « i) & 0x80) MOSIO = 1; else MOSIO = 0; S_CLK = 0; S_CLK = 1; } //CPU发送完后,R_CLK将数据并行输出, //实现了只占用CPU一个输出口就可以输出8bit数据 R_CLK = 0; R_CLK = 1; } void LCD1602_write_com(unsigned char com)
{ hc595SendData(com); LCD_RS_0; LCD_EN_0; delay_ms(10);
LCD_EN_1; } void LCD1602_write_data(unsigned char date)
{ hc595SendData(date); LCD_RS_1; LCD_EN_0; delay_ms(10); LCD_EN_1; } void LCD1602_Init(void)
{ GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE ); GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = LCD_RS_PIN | LCD_EN_PIN | GPIO_Pin_0; GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_Init(GPIOC, &GPIO_InitStructure);
LCD1602_write_com(0x01); LCD1602_write_com(0x38); LCD1602_write_com(0x0C);//开显示,不需要光标
LCD1602_write_com(0x06); } void LCD1602_Clear(void) { LCD1602_write_com(0x01); } void LCD1602_ShowChar(unsigned char xpos,unsigned char ypos,char xsz)
{ ypos%=2; if(ypos==0) { LCD1602_write_com(0x80+xpos); } else { LCD1602_write_com(0x80+0x40+xpos); } LCD1602_write_data(xsz); } void LCD1602_ShowLineStr(unsigned char xpos,unsigned char ypos,char p) { unsigned char i=0; if(ypos>1 || xpos>=16)return; while(p!=’\0’ && i<16 && xpos<16) { i++; LCD1602_ShowChar(xpos++,ypos,*p++); delay_us(500); } } void LCD1602_ShowStr(unsigned char xpos,unsigned char ypos,char p) { if(ypos>1)return; while(p!=’\0’) { LCD1602_ShowChar(xpos++,ypos,p++); if(p==’\n’)//当检测到换行符时,进行换行检测 { xpos=0; ypos++; p++; } } }

LCD1602.h

#ifndef __LCD1602_H #define __LCD1602_H
//1602液晶指令/数据或选择引脚 #define LCD_RS_PORT GPIOB #define LCD_RS_PIN GPIO_Pin_10 #define LCD_RS_0 GPIO_ResetBits(LCD_RS_PORT, LCD_RS_PIN) #define LCD_RS_1 GPIO_SetBits(LCD_RS_PORT, LCD_RS_PIN)

​ ​ //1602液晶使能引脚 ​ #define LCD_EN_PORT GPIOB ​ #define LCD_EN_PIN GPIO_Pin_11 //PB11 ​ #define LCD_EN_0 GPIO_ResetBits(LCD_EN_PORT, LCD_EN_PIN) #define LCD_EN_1 GPIO_SetBits(LCD_EN_PORT, LCD_EN_PIN)

//1602液晶数据端口    
#define MOSIO PBout(0)    
#define R_CLK PCout(5)
#define S_CLK PCout(4)

/*********************函数声明*****************/
void LCD1602_Init(void);
void LCD1602_Clear(void);
void LCD1602_ShowStr(unsigned char xpos,unsigned char ypos,char *p);
void LCD1602_BKLSet(unsigned char on);
unsigned char LCD1602_BKLGet(void);
void LCD1602_ShowLineStr(unsigned char xpos,unsigned char ypos,char *p);
void LCD1602_ShowState(unsigned char Signal, unsigned char GprsState);
/**********************************************/
#endif  

文章内容版权归作者阎子君所有,转载请与我联系获得授权许可