DHT11基础:
特点:
- 相对温湿度测量
- 长期稳定性 -- 多次校验
- 超长信号传输距离
- 超低能耗: 休眠
-完全转换 -- 直接出结果,不用转换
接线:
VCC
GND
DATA -- 自己选
数据传送逻辑:
只有一根数据线bit -- C51发送序列指令给DHT11, 模块一次完整的数据传输为40bit,高位先出
数据格式:
16 16 8: 湿度 温度 校验和
8为bit 湿度整数数据 + 8位bit 湿度小数数据 + 8为bit 温度度整数数据 + 8位bit 温度小数数据 + 8位bit校验和
================================
时序分析1
分析时序图:
主要看 3 要素: 开始 结束 转折
观察时序图
时序分析:
A=1
B=0
延时>=18ms -- 20,ms
C=1
可能情况C+D:
20+80us
40+80us
一定可以的情况(可能 的 交集): 40 ~ 100 us -- 取60 us
延时60us
检测 D=0 , if 成立 说明是模块存在,模块拉低了他的电平
注意: DHT11 的 供电电压 为 3-5.5V。 传感器上电后需要等待 1s 以越过不稳定状态在此期间无需发送任何指令
=============================
代码实现判断DHT11模块是否存在
#include"reg52.h"
#include"intrins.h"sbit dnt=P3^3; // 模块的data数据线
sbit led=P3^7;
void Delay30ms() //@11.0592MHz
{
unsigned char i, j;i = 54;
j = 199;
do
{
while (--j);
} while (--i);
}
void Delay60us() //@11.0592MHz
{
unsigned char i;i = 25;
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 check()
{//A=1
dnt=1;
//B=0
dnt=0;
//延时>=18ms -- 30ms
Delay30ms();
//C=1
dnt=1;
/*可能情况C+D:
20+80us
40+80us
一定可以的情况(可能 的 交集): 40 ~ 100 us -- 取60 us
延时60us*/
Delay60us();//检测 D=0 , if 成立 说明是模块存在,模块拉低了他的电平
if(dnt == 0){
led=0; //把灯点亮说明 模块存在
}
}void main()
{
led = 1;
// 上电后先稳定1s ,再给1s缓冲
Delay1000ms();
Delay1000ms();
check();
while(1); // 防止程序退出只能观察到灯是闪了一下
}
========================
时序分析2
继续分析后面的时序图 :
怎么从 D点卡到 E点 --由低电平到高电平,我们不去计算80us --
只需要while(!dnt); //卡着,/当电平跳转的时候就是到E点了
同理: E->F: while(dnt); //当1 -->0 的时候就到F点了,就可发送数据了
//值得注意: DHT11的 有效数据都是高电平,只是持续的时间不一样
比如 表示数字0 高电平持续 的时间是 26 ~ 28us
表示数字1 高电平持续 的时间是 70 us
//so 我们区一个没有二者交集的时间作为判断依据 28 ~ 70 us -- 取50us
if 这时候是 高电平 -- 发送的是1 else 发送的是 0(已经到下一个bit了 -- 电位拉低了0)
// 40 bit = 5char -- 在C51中的 1char =8bit
每个char分别代表下面 5 个数据:
湿度整数
湿度小数
温度整数
温度小数
校验和
所以我们需要读 5 轮,每一轮读8 次 ,没8次形成一个数据,才能读完全部数据
=================================================
由ASCLL 码表 知道 数字0 -- > 字符0 需要 + 48 == 0x30 --- 这样就完成字符和数字的转化
LCD 上面显示 湿度 和 温度 ,并且加装 继电器 控制风扇
#include"reg52.h"
#include"intrins.h"sbit dnt=P3^3; // 模块的data数据线
sbit led=P3^7;
sbit led2=P3^6;
sbit fengshan=P1^6;
sfr AUXR = 0x8E;
char datas[5];
#define dataBuffer P0 // 定义8位数据线,P0端口sbit RS=P1^0;
sbit RW=P1^1;
sbit EN=P1^4;char temp[8];
char hum[8];
void check_busy() // 检查是否繁忙 -- 读时序操作
{
char tmp=0x80;
dataBuffer=0x80;
// 1000 0000 -- 0x80 只更新 bit7
while(tmp &= 0x80){//只要tmp -- 数据位的高位 1 的时候就是一直忙的状态,只有不忙的时候才跳出while循环
RS=0;
RW=1;EN=0;
_nop_();
EN=1;
_nop_();
_nop_();
tmp=dataBuffer;//读取总线上面的数
EN=0;
_nop_();
}}
void Write_Cmd_Func(char cmd) // 写入地址 -- 写时序操作
{
check_busy(); //先检查是否繁忙
RS=0; //写内容
RW=0;EN=0;
_nop_(); // 给一个微秒
dataBuffer=cmd;
_nop_(); // 数据建立时间 --tsp
EN=1;
_nop_(); // tpw tf
EN=0;
_nop_();
}void Write_Data_Func(char dataShow) //写入内容 -- 写时序操作
{
check_busy();
RS=1;//写地址 -- 指令
RW=0;EN=0;
_nop_(); // 给一个微秒
dataBuffer=dataShow;
_nop_();
EN=1;
_nop_(); // tpw tf
EN=0;
_nop_();}
void Delay15ms() //@11.0592MHz
{
unsigned char i, j;i = 27;
j = 226;
do
{
while (--j);
} while (--i);
}
void Delay5ms() //@11.0592MHz
{
unsigned char i, j;i = 9;
j = 244;
do
{
while (--j);
} while (--i);
}
void UartInit(void) //9600bps@11.0592MHz
{
AUXR=0x01; //减少定时器辐射 -- 抗干扰
SCON =0x40; //配置 串口口工作方式1,REN不使能接收
//不配 PCON -- 不设置倍数关系
//配置定时器1为 8位自动重装
TMOD &=0xF0; //高位清零
TMOD |=0x20; //00 10 -- 配置为定时器1为: 1 0 模式 -- 8位自动重装
//定义初值
TL1=0xFD;
TH1=0xFD; // 9600 波特率的初值
TR1= 1; // 定时器 -- 启动
}void Delay30ms() //@11.0592MHz
{
unsigned char i, j;i = 54;
j = 199;
do
{
while (--j);
} while (--i);
}
void Delay60us() //@11.0592MHz
{
unsigned char i;i = 25;
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 Delay40us() //@11.0592MHz
{
unsigned char i;_nop_();
i = 15;
while (--i);
}void Dht11_Start()
{//A=1
dnt=1;
//B=0
dnt=0;
//延时>=18ms -- 30ms
Delay30ms();
//C=1
dnt=1;
//怎么从 D点卡到 E点 --由低电平到高电平,我们不去计算80us --
//只需要while(!dnt); //卡着,/当电平跳转的时候就是到E点了
while(dnt);// 卡C-D
while(!dnt); //卡D -E//同理: E->F: while(dnt); //当1 -->0 的时候就到F点了,就可发送数据了
while(dnt); // 看E -F
}void Read_Data_From_Dnt()
{
int i,j;
char flag;
char tmp;
Dht11_Start(); //每次发送数据都重启,不然就是 低功耗模式
for(i=0;i<5;++i){
for(j=0;j<8;j++){
while(!dnt); //卡G点 -- 发送数据前一个点
Delay40us();// 先延时后读
if(dnt == 1){
flag=1;
while(dnt); // 等待70 us 高电平结束
}
else{
flag=0;
}
tmp<<=1;
tmp |=flag; // 读取flag的数据 0|1 =1 0|0=0
}
datas[i]=tmp; // 拿到一个数据}
}void sendByte(char a)
{
SBUF = a;
while(!TI);
TI=0;
}
void sendString(char *str)
{while(*str!='\0')
{
sendByte(*str);
str++;
}
}
void LCD1602_INIT()
{
//LCD1602 初始化过程(8bit)
//(1)延时 15ms
Delay15ms();
//(2)写指令 38H(不检测忙信号)
Write_Cmd_Func(0x38);
//(3)延时 5ms
Delay5ms();
//(4)以后每次写指令,读/写数据操作均需要检测忙信号
//(5)写指令 38H:显示模式设置
Write_Cmd_Func(0x38);
//(6)写指令 08H:显示关闭
Write_Cmd_Func(0x08);
//(7)写指令 01H:显示清屏
Write_Cmd_Func(0x01);
//(8)写指令 06H:显示光标移动设置
Write_Cmd_Func(0x06);
//(9)写指令 0CH:显示开及光标设置
Write_Cmd_Func(0x0C);}
void LCD1602_ShowLine(char line,char row,char *string)
{
switch(line){
case 1:
Write_Cmd_Func(0x80 + row); //选择开始位置
while(*string){
Write_Data_Func(*string);
string++;
}
break;case 2:
Write_Cmd_Func(0x80 + 0x40 +row); //选择开始位置
while(*string){
Write_Data_Func(*string);
string++;
}
break;}
}
void Build_Datas()
{
//直接打表 输入
hum[0]='H';
hum[1]=datas[0]/10 +0x30;
hum[2]=datas[0]%10 +0x30;
hum[3]='.';
hum[4]=datas[1]/10 +0x30;
hum[5]=datas[1]%10 +0x30;
hum[6]='%';
hum[7]='\0';temp[0]='T';
temp[1]=datas[2]/10 +0x30;
temp[2]=datas[2]%10 +0x30;
temp[3]='.';
temp[4]=datas[3]/10 +0x30;
temp[5]=datas[3]%10 +0x30;
temp[6]='C';
temp[7]='\0';}
void main()
{
Delay1000ms();
led2=1;
UartInit();
// 上电后先稳定1s ,再给1s缓冲
LCD1602_INIT();
Delay1000ms();
Delay1000ms();
led = 0; // 初始化完成 后灯被点亮
while(1){ // 不断的读取数据
Delay1000ms();
Read_Data_From_Dnt();
Build_Datas(); // 创建数据
if(datas[2]>28){
fengshan=0;// 温度超过28 就开启风扇
led2=0; // 开灯作为标志位
}
else{ //温度降下来就复位
fengshan=1;
led2=1;
}
//在串口打印出来
sendString(hum);
sendString("\r\n");
sendString(temp);
sendString("\r\n");
//在LCD打印出来
LCD1602_ShowLine(1,2,hum);
LCD1602_ShowLine(2,2,temp);
}
}
===========================
分文件:
1.先main 里面恶毒内容拿出来
Delay1000ms();
led2=1;
UartInit();
// 上电后先稳定1s ,再给1s缓冲
LCD1602_INIT();
Delay1000ms();
Delay1000ms();
led = 0; // 初始化完成 后灯被点亮
while(1){ // 不断的读取数据
Delay1000ms();
Read_Data_From_Dnt();
Build_Datas(); // 创建数据
if(datas[2]>28){
fengshan=0;// 温度超过28 就开启风扇
led2=0; // 开灯作为标志位
}
else{ //温度降下来就复位
fengshan=1;
led2=1;
}
//在串口打印出来
sendString(hum);
sendString("\r\n");
sendString(temp);
sendString("\r\n");
//在LCD打印出来
LCD1602_ShowLine(1,2,hum);
LCD1602_ShowLine(2,2,temp);
}
2.创建几个发文件,进行分类,如串口的所有函数 放在 uart.c立马
3.创建分文件的头文件,只放函数定义
4.整理函数变量之间的关系,一句句把之前拿出的main内容放回去,一个个消去报错
extern关键字
参考自:【C语言】extern关键字的作用_extern在c语言中作用-CSDN博客
前提:
跨文件 调佣变量 操作 很容易出错 -- 我们需要extern 关键字声明 来 让全局 函数都能调用
定义:
声明一个变量或函数具有外部链接性(external linkage),即这些变量或函数可以被其他文件访问。
使用注意:
1. 声明和定义分离:使用"extern"关键字声明一个变量或函数并不等于定义它。"extern"只是在当前文件中声明该变量或函数是在其他地方定义的。定义通常发生在其他文件中。
2. 变量和函数的重复声明:避免在多个地方重复声明同一变量或函数。多次使用"extern"关键字声明同一变量或函数可能会导致编译器发出警告或错误。
3. 外部实现的可见性:使用"extern"关键字可以访问外部定义的变量和函数,但前提是它们在其他文件中具有合适的可见性。确保所需的变量和函数在其他文件中是可见的(例如,通过使用适当的头文件包含)。
4. 数据类型和函数签名的一致性:在使用"extern"关键字声明变量或函数时,确保其数据类型和函数签名与实际定义的变量或函数的数据类型和函数签名一致。否则,可能会导致编译器错误或运行时错误。
5. 避免重复定义:避免在多个文件中同时定义同一变量或函数。只需要在一个文件中定义,然后在其他文件中使用"extern"关键字声明。
6. 声明在适当的位置:"extern"关键字的声明通常放置在函数外部的全局区域,或者放置在函数内部的局部区域。根据需要和上下文,选择适当的位置。
7. 尽量避免全局变量:虽然"extern"关键字可以让我们在多个文件中共享变量,但过度使用全局变量可能导致代码可读性、可维护性和可测试性的问题。考虑使用更局部化的变量和传递参数的方式来避免过多使用全局变量。