项目介绍:
C51、DHT11、LCD1602、蓝牙模块、继电器、直流电机及扇叶。实时监测温湿度并通过LCD显示和串口蓝牙传输,蓝牙控制风扇开、关和自动温控模式,自动温控模式可以设置需要的温度阈值,达到阈值启动风扇,低于阈值自动关闭。附有源码。
c51蓝牙温湿度监测系统
目录
一 DHT11温湿度传感器
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,应用领域:暖通空调;汽车;消费品;气象站;湿度调节器;除湿器;家电;医疗;自动控制
特点:
相对湿度和温度测量
全部校准,数字输出
长期稳定性
超长的信号传输距离:20米
超低能耗:休眠
4 引脚安装:可以买封装好的
完全互换 : 直接出结果,不用转化
DHT11的供电电压为3-5.5V。传感器上电后,要等待1s 以越过不稳定状态在此期间无需发送任何指令。电源引脚(VDD,GND)之间可增加一个100nF 的电容,用以去耦滤波。
数据传送逻辑:
从一根数据线DATA,C51发送序列指令给DHT11模块,模块一次完整的数据传输为40bit,高位先
出
数据格式:
8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验和
通讯过程时序图:
检测模块是否存在
根据如下时序图,做通信初始化,并检测模块是否存在,功能是否正常
时序逻辑分析:从左到右
1 先拉高
2 拉低至少18ms
3 拉高后,持续20~40微秒后模块会自动拉低,输出响应信号
4 模块自动拉低的响应信号有80微秒
检测:结合3和4中时间取交集,判断出第3步拉高后,只要在40~100微秒内为低电平,模块存在。
代码:模块通电要先稳定一秒。
Delay1000ms();
DHT = 1;
DHT = 0;
Delay30ms();
DHT = 1;
Delay70us();
if(!DHT) LED = 0;
LED被点亮,检测到模块存在。
二 温湿度通过串口传给PC
关于串口知识参考本人之前的博客
检测到存在后,会再主动拉高80us然后主动拉低,开始传送数据
卡开始传输数据的点:
检测到存在后,while低,while高,开始接收数据
用户MCU发送一次开始信号后,DHT11从低功耗模式 转换到高速模式, 等待主机开始信号结束后,DHT11发送响应信号, 送出40bit的数据, 并触发一次信号采集,用户可选择读取部分数据.从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集. 采集数据后转换到低速模式 。
DHT11传输0的时序分析
DHT11传输1的时序分析
代码:
获得数据:因为温度整数、小数,湿度整数、小数,校验和,共5段数据,每段8bit
输出数据:串口发送给电脑,参考本人C51串口文章内代码
参照ANSI码表,低位高位分别+0x30
sfr AUXR = 0x8E;
char datas[5];
//串口部分代码
void UartInit(void) //9600bps@11.0592MHz
{
AUXR = 0x01;
SCON = 0x50; //配置串口工作方式1,REN使能接收
TMOD &= 0x0F;
TMOD |= 0x20;//定时器1工作方式位8位自动重装
TH1 = 0xFD;
TL1 = 0xFD;//9600波特率的初值
TR1 = 1;//启动定时器
EA = 1;//开启总中断
ES = 1;//开启串口中断
}
void sendByte(char data_msg)
{
SBUF = data_msg;
while(!TI);
TI = 0;
}
void sendString(char* str)
{
while( *str != '\0'){
sendByte(*str);
str++;
}
}
//模块初始化开始
void start_DHT()
{
DHT = 1;
DHT = 0;
Delay30ms();
DHT = 1;
while(DHT);
while(!DHT);
while(DHT);
}
//读取模块数据
void GET_DATA_FROM_DHT()
{
int i;//读5轮
int j;//每轮读8次
char tmp;
char flag;
//重新初始模块
start_DHT();
for(i=0;i<5;i++)
{
for(j=0;j<8;j++)
{
//等待主动拉高
while(!DHT);
//检测0/1
Delay40us();
if(DHT){
flag = 1;
//等待拉低
while(DHT);
}
else{
flag = 0;
}
//DATA左移一位,用来储存数据
tmp <<= 1;
//移出来的最后一位储存数据
tmp |= flag;
}
datas[i] = tmp;
}
}
void main()
{
//串口初始化
UartInit();
while(1)
{
//每秒获取一次数据
Delay1000ms();
GET_DATA_FROM_DHT();
sendString("hum: ");
sendByte(datas[0]/10 + 0x30);
sendByte(datas[0]%10 + 0x30);
sendByte('.');
sendByte(datas[1] + 0x30);
sendString(" ");
sendString("tmp: ");
sendByte(datas[2]/10 + 0x30);
sendByte(datas[2]%10 + 0x30);
sendByte('.');
sendByte(datas[3] + 0x30);
sendString("\r\n");
}
}
吐槽自己 for(j=0;j<8;j++) 写成了 for(j=0;j<8;i++) 竟然调试几个小时没发现问题在哪!!
三 在LCD1602上显示温湿度
参考本人上个LCD1602外设的博客
原本传给PC的数据,改为传给LCD屏
代码:
... ...
#define dataBuffer P0 //P0端口组
//LCD1602
sbit RS = P1^0;
sbit RW = P1^1;
sbit EN = P1^4;
//DHT11
sbit DHT = P3^3;
char temp[12];
char huma[12];
char datas[5];
... ...
//LCD1602代码
void check_busy()//查忙
{
char tmp = 0x80;
dataBuffer = 0x80;
while(tmp & 0x80){
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;
EN = 1;
_nop_();
_nop_();
EN = 0;
_nop_();
}
void Write_Data_Func(char Data){//发数据
check_busy();
RS = 1;//高电平时选择数据寄存器
RW = 0;//低电平时进行写操作
EN = 0;//变成低电平时,液晶模块执行命令
_nop_();
dataBuffer = Data;
EN = 1;
_nop_();
_nop_();
EN = 0;
_nop_();
}
void LCD1602_INIT()//lcd初始化
{
//(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 PrintChar(char Data,char Position)//显示字符
{
char position = 0x80 + Position;
char dataShow = Data;
Write_Cmd_Func(position);
Write_Data_Func(dataShow);
}
void PrintString(char str[],char Position)//显示字符串
{
int i = 0;
while(str[i] != '\0'){
if(Position+i == 0x10) Position += 0x30;
if(Position+i == 0x50) break;
PrintChar(str[i],Position + i);
i++;
}
}
void print_datas(){//LCD显示温湿度
huma[0] = 'H';
huma[1] = 'U';
huma[2] = 'M';
huma[3] = ':';
huma[4] = ' ';
huma[5] = datas[0]/10 + 0x30;
huma[6] = datas[0]%10 + 0x30;
huma[7] = '.';
huma[8] = datas[1] + 0x30;
huma[9] = ' ';
huma[10] = '%';
huma[11] = '\0';
temp[0] = 'T';
temp[1] = 'E';
temp[2] = 'P';
temp[3] = ':';
temp[4] = ' ';
temp[5] = datas[2]/10 + 0x30;
temp[6] = datas[2]%10 + 0x30;
temp[7] = '.';
temp[8] = datas[3] + 0x30;
temp[9] = 0xDF;//'°'
temp[10] = 'C';
temp[11] = '\0';
PrintString(temp,0x02);
PrintString(huma,0x42);
}
... ...
void main()
{
//LCD1602初始化
LCD1602_INIT();
while(1)
{
//每秒获取一次数据
Delay1000ms();
GET_DATA_FROM_DHT();
//LCD显示
print_datas();
}
}
温控风扇:
通过外部连接一个 继电器 ,来控制风扇的开关,继电器为低电平导通,可以设置温度超过多少摄氏度,就打开风扇电机。
分文件实现代码优化:
全在一个文件里显得又臭又长,不便于阅读。
这里做分文件处理,各文件之间以函数互相调用进行联系,因此封装了一些调用函数,如:
//温度值调用
char* get_temp()
{
return datas[2];
}
//温度字符串调用
char* temp_string()
{
return temp;
}
//湿度字符串调用
char* humi_string()
{
return humi;
}
分出 .c 文件:
分出 .h 文件:(只包含被外部调用的函数的声明)
最终结构如下
各部分代码:
各个 .c :
main.c
#include "reg52.h"
#include "delay.h"
#include "dht11.h"
#include "lcd1602.h"
#include "uart.h"
sbit fan = P3^2;
void main()
{
fan = 1;
//串口初始化
UartInit();
//LCD1602初始化
LCD1602_INIT();
while(1)
{
//每秒获取一次数据
Delay1000ms();
GET_DATA_FROM_DHT();
//串口传输
uart_send();
//LCD显示
print_datas();
//温度大于29度打开风扇
if(get_temp() >= 29) fan = 0;
else fan = 1;
}
}
uart.c
#include "reg52.h"
#include "dht11.h"
sfr AUXR = 0x8E;
char fan_ON = 1;
//串口部分代码
void UartInit(void) //9600bps@11.0592MHz
{
AUXR = 0x01;
SCON = 0x50; //配置串口工作方式1,REN使能接收
TMOD &= 0x0F;
TMOD |= 0x20;//定时器1工作方式位8位自动重装
TH1 = 0xFD;
TL1 = 0xFD;//9600波特率的初值
TR1 = 1;//启动定时器
EA = 1;//开启总中断
ES = 1;//开启串口中断
}
//发送字符
void sendByte(char data_msg)
{
SBUF = data_msg;
while(!TI);
TI = 0;
}
//发送字符串
void sendString(char* str)
{
while( *str != '\0'){
sendByte(*str);
str++;
}
}
//发送温湿度信息
void uart_send(){
sendString(temp_string());
sendString(" ");
sendString(humi_string());
sendString("\r\n");
}
dht11.c
#include "reg52.h"
#include "delay.h"
sbit DHT = P3^3;
char temp[12];
char humi[12];
char datas[5];
//DHT11代码
void start_DHT()//模块初始化开始
{
DHT = 1;
DHT = 0;
Delay30ms();
DHT = 1;
while(DHT);
while(!DHT);
while(DHT);
}
void build_string(){
humi[0] = 'H';
humi[1] = 'U';
humi[2] = 'M';
humi[3] = ':';
humi[4] = ' ';
humi[5] = datas[0]/10 + 0x30;
humi[6] = datas[0]%10 + 0x30;
humi[7] = '.';
humi[8] = datas[1] + 0x30;
humi[9] = ' ';
humi[10] = '%';
humi[11] = '\0';
temp[0] = 'T';
temp[1] = 'E';
temp[2] = 'P';
temp[3] = ':';
temp[4] = ' ';
temp[5] = datas[2]/10 + 0x30;
temp[6] = datas[2]%10 + 0x30;
temp[7] = '.';
temp[8] = datas[3] + 0x30;
temp[9] = 0xDF;//'°'
temp[10] = 'C';
temp[11] = '\0';
}
void GET_DATA_FROM_DHT()//读取模块数据
{
int i;//读5轮
int j;//每轮读8次
char tmp;
char flag;
//重新初始模块
start_DHT();
for(i=0;i<5;i++)
{
for(j=0;j<8;j++)
{
//等待主动拉高
while(!DHT);
//检测0/1
Delay40us();
if(DHT){
flag = 1;
//等待拉低
while(DHT);
}
else{
flag = 0;
}
//DATA左移一位,用来储存数据
tmp <<= 1;
//移出来的最后一位储存数据
tmp |= flag;
}
datas[i] = tmp;
}
build_string();
}
//温度值调用
char* get_temp()
{
return datas[2];
}
//温度字符串调用
char* temp_string()
{
return temp;
}
//湿度字符串调用
char* humi_string()
{
return humi;
}
lcd1602.c
#include "reg52.h"
#include "intrins.h"
#include "delay.h"
#include "dht11.h"
#define dataBuffer P0 //P0端口组
//LCD1602
sbit RS = P1^0;
sbit RW = P1^1;
sbit EN = P1^4;
//LCD1602代码
void check_busy()//查忙
{
char tmp = 0x80;
dataBuffer = 0x80;
while(tmp & 0x80){
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;
EN = 1;
_nop_();
_nop_();
EN = 0;
_nop_();
}
void Write_Data_Func(char Data){//发数据
check_busy();
RS = 1;//高电平时选择数据寄存器
RW = 0;//低电平时进行写操作
EN = 0;//变成低电平时,液晶模块执行命令
_nop_();
dataBuffer = Data;
EN = 1;
_nop_();
_nop_();
EN = 0;
_nop_();
}
void LCD1602_INIT()//lcd初始化
{
//(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 PrintChar(char Data,char Position)//显示字符
{
char position = 0x80 + Position;
char dataShow = Data;
Write_Cmd_Func(position);
Write_Data_Func(dataShow);
}
void PrintString(char str[],char Position)//显示字符串
{
int i = 0;
while(str[i] != '\0'){
if(Position+i == 0x10) Position += 0x30;
if(Position+i == 0x50) break;
PrintChar(str[i],Position + i);
i++;
}
}
void print_datas(){//LCD显示温湿度
PrintString(temp_string(),0x02);
PrintString(humi_string(),0x42);
}
delay.c (略,内为各种软件延时函数)
各个 .h : (只有被外部调用的函数需要声明)
//软件延时函数集
void Delay1000ms();
void Delay30ms();
void Delay40us();
void Delay15ms();
void Delay5ms();
//DHT11
void GET_DATA_FROM_DHT();//读取模块数据
char* get_temp();
char* temp_string();
char* humi_string();
//LCD1602
void LCD1602_INIT();//LCD初始化
void print_datas();//LCD显示温湿度
void UartInit(void);
void uart_send();
四 蓝牙控制风扇电机实现
蓝牙功能,基于串口,在串口的代码进行修改。
参考本人博客
串口代码文件中增加:手机蓝牙端输入 o 或 c ,即代表开关风扇,a 为自动模式,随温度阈值开关。
uart.c:
//调取蓝牙命令内容
char If_fan_ON(){
return fan_ON;
}
//蓝牙指令接收(中断)
void UART_HANDLER() interrupt 4
{
char cmd;
if(RI)//中断处理函数中,对于接收中断的响应
{
RI = 0;//清除接收中断标志位
cmd = SBUF;//接收命令
if(cmd == 'o'){
fan_ON = 1;
sendString("已经为您打开风扇!\r\n");
}
if(cmd == 'c'){
fan_ON = 0;
sendString("已经为您关闭风扇!\r\n");
}
if(cmd == 'a'){
fan_ON = 2;
sendString("风扇进入温控模式!\r\n");
}
}
}
main.c:
//安装蓝牙指令开关风扇或自动根据温度阈值开关风扇
if(If_fan_ON() == 1){
fan = 0;
}
if(If_fan_ON() == 0){
fan = 1;
}
if(If_fan_ON() == 2){
if(get_temp() >= 29) fan = 0;
else fan = 1;