基于STM32的舱内温湿度显示设计
本次设计使用正点原子的STM32F103ZET6精英开发板。
数据采集:温湿度传感器采用DHT11来采集湿度,使用DS18B20来采集温度。
显示:显示部分分为TFT-LCD显示和上位机Labview显示。
本设计只供学习和交流不涉及商业用途。
设计简介
恒温恒湿系统应用广泛,主要用在药品生产车间,用智能设备,保持车间内的温度和湿度在极小的范围内变化。通常所说的就是空调系统和空气净化系统。
在微生物实验室里,培养箱也是恒温恒湿系统。生活中无处不在都需要检测温度和湿度,通过测量的数据进行相应的操作。
提示:以下是本篇文章正文内容,下面案例可供参考
一、设计要求
(1)在STM32F103开发板基础进行设计。
(2)使用DS18B20温度传感器实现温度采集;使用DHT11温湿度传感器测量湿度数据。
(3)使用开发板自带液晶屏对设定温度、测量温度和湿度进行显示。
(4)使用开发板自带按键对设定温度进行修改。
(5)使用继电器控制加温、制冷和加湿系统,在液晶屏上显示当前各系统工作状态。
(6)使用串口将数据上传上位机。
(7)上位机用LabVIEW编写显示控制界面,包含当前温度,设定温度,当前湿度,设定湿度,温湿度报警灯,温湿度时间曲线。
(8)上位机将温湿度数据保持到硬盘文件中。
二、设计步骤
1.DS18B20温度采集
(1)简介:DS18B20是常用的数字温度传感器,其输出的是数字信号,具有体积小,硬件开销低,抗干扰能力强,精度高的特点。 [1] DS18B20数字温度传感器接线方便,封装成后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式,型号多种多样,有LTM8877,LTM8874等等。主要根据应用场合的不同而改变其外观。封装后的DS18B20可用于电缆沟测温,高炉水循环测温,锅炉测温,机房测温,农业大棚测温,洁净室测温,弹药库测温等各种非极限温度场合。耐磨耐碰,体积小,使用方便,封装形式多样,适用于各种狭小空间设备数字测温和控制领域。
(2)特点:独特的一线接口,只需要一条口线通信 多点能力,简化了分布式温度传感应用 无需外部元件 可用数据总线供电,电压范围为3.0 V至5.5 V 无需备用电源 测量温度范围为-55 ° C至+125 ℃ 。华氏相当于是-67 ° F到257° F。在摄氏度-10 ° C至+85 ° C范围内精度为±0.5 ° C。温度传感器可编程的分辨率为9~12位,温度转换为12位数字格式最大值为750毫秒,用户可定义的非易失性温度报警设置,应用范围包括恒温控制、工业系统、消费电子产品温度计、或任何热敏感系统。
(3)控制过程:
初始化
①先将数据线置高电平“1”。
②延时(该时间要求的不是很严格,但是尽可能的短一点)
③ 数据线拉到低电平“0”。
④延时750微秒(该时间的时间范围可以从480到960微秒)。
⑤ 数据线拉到高电平“1”。
⑥延时等待(如果初始化成功则在15到60微秒时间之内产生一个由DS18B20所返回的低电平“0”。据该状态可以来确定它的存在,但是应注意不能无限的进行等待,不然会使程序进入死循环,所以要进行超时控制)。
⑦若CPU读到了数据线上的低电平“0”后,还要做延时,其延时的时间从发出的高电平算起(第(5)步的时间算起)最少要480微秒。
⑧ 将数据线再次拉高到高电平“1”后结束。
写操作
①数据线先置低电平“0”。
②延时确定的时间为15微秒。
③按从低位到高位的顺序发送字节(一次只发送一位)。
④延时时间为45微秒。
⑤将数据线拉到高电平。
⑥ 重复上①到⑥的操作直到所有的字节全部发送完为止。
⑦最后将数据线拉高。
读操作
①将数据线拉高“1”。
②延时2微秒。
③将数据线拉低“0”。
④延时3微秒。
⑤将数据线拉高“1”。
⑥延时5微秒。
⑦读数据线的状态得到1个状态位,并进行数据处理。
⑧延时60微秒。
(4)与单片机通讯:采用一线总线串行传送给CPU,同时课传送CRC校验码。
代码如下(DS18B20):
DS18B20.h文件
#ifndef __DS18B20_H
#define __DS18B20_H
#include "sys.h"
//IO方向设置
#define DS18B20_IO_IN() {GPIOF->CRH&=0XFFFF0FFF;GPIOF->CRH|=8<<12;}
#define DS18B20_IO_OUT() {GPIOF->CRH&=0XFFFF0FFF;GPIOF->CRH|=3<<12;}
IO操作函数
#define DS18B20_DQ_OUT PFout(11) //数据端口 PA0
#define DS18B20_DQ_IN PFin(11) //数据端口 PA0
u8 DS18B20_Init(void);//初始化DS18B20
short DS18B20_Get_Temp(void);//获取温度
void DS18B20_Start(void);//开始温度转换
void DS18B20_Write_Byte(u8 dat);//写入一个字节
u8 DS18B20_Read_Byte(void);//读出一个字节
u8 DS18B20_Read_Bit(void);//读出一个位
u8 DS18B20_Check(void);//检测是否存在DS18B20
void DS18B20_Rst(void);//复位DS18B20
#endif
DS18B20.c文件
#include "ds18b20.h"
#include "delay.h"
//复位DS18B20
void DS18B20_Rst(void)
{
DS18B20_IO_OUT(); //SET PG11 OUTPUT
DS18B20_DQ_OUT=0; //拉低DQ
delay_us(750); //拉低750us
DS18B20_DQ_OUT=1; //DQ=1
delay_us(15); //15US
}
//等待DS18B20的回应
//返回1:未检测到DS18B20的存在
//返回0:存在
u8 DS18B20_Check(void)
{
u8 retry=0;
DS18B20_IO_IN(); //SET PG11 INPUT
while (DS18B20_DQ_IN&&retry<200)
{
retry++;
delay_us(1);
};
if(retry>=200)return 1;
else retry=0;
while (!DS18B20_DQ_IN&&retry<240)
{
retry++;
delay_us(1);
};
if(retry>=240)return 1;
return 0;
}
//从DS18B20读取一个位
//返回值:1/0
u8 DS18B20_Read_Bit(void)
{
u8 data;
DS18B20_IO_OUT(); //SET PG11 OUTPUT
DS18B20_DQ_OUT=0;
delay_us(2);
DS18B20_DQ_OUT=1;
DS18B20_IO_IN(); //SET PG11 INPUT
delay_us(12);
if(DS18B20_DQ_IN)data=1;
else data=0;
delay_us(50);
return data;
}
//从DS18B20读取一个字节
//返回值:读到的数据
u8 DS18B20_Read_Byte(void)
{
u8 i,j,dat;
dat=0;
for (i=1;i<=8;i++)
{
j=DS18B20_Read_Bit();
dat=(j<<7)|(dat>>1);
}
return dat;
}
//写一个字节到DS18B20
//dat:要写入的字节
void DS18B20_Write_Byte(u8 dat)
{
u8 j;
u8 testb;
DS18B20_IO_OUT(); //SET PG11 OUTPUT;
for (j=1;j<=8;j++)
{
testb=dat&0x01;
dat=dat>>1;
if (testb)
{
DS18B20_DQ_OUT=0; // Write 1
delay_us(2);
DS18B20_DQ_OUT=1;
delay_us(60);
}
else
{
DS18B20_DQ_OUT=0; // Write 0
delay_us(60);
DS18B20_DQ_OUT=1;
delay_us(2);
}
}
}
//开始温度转换
void DS18B20_Start(void)
{
DS18B20_Rst();
DS18B20_Check();
DS18B20_Write_Byte(0xcc); // skip rom
DS18B20_Write_Byte(0x44); // convert
}
//初始化DS18B20的IO口 DQ 同时检测DS的存在
//返回1:不存在
//返回0:存在
u8 DS18B20_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF, ENABLE); //使能PORTG口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PORTG.11 推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_SetBits(GPIOF,GPIO_Pin_11); //输出1
DS18B20_Rst();
return DS18B20_Check();
}
//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250)
short DS18B20_Get_Temp(void)
{
u8 temp;
u8 TL,TH;
short tem;
DS18B20_Start (); // ds1820 start convert
DS18B20_Rst();
DS18B20_Check();
DS18B20_Write_Byte(0xcc); // skip rom
DS18B20_Write_Byte(0xbe); // convert
TL=DS18B20_Read_Byte(); // LSB
TH=DS18B20_Read_Byte(); // MSB
if(TH>7)
{
TH=~TH;
TL=~TL;
temp=0; //温度为负
}else temp=1; //温度为正
tem=TH; //获得高八位
tem<<=8;
tem+=TL; //获得底八位
tem=(float)tem*0.625; //转换
if(temp)return tem; //返回温度值
else return -tem;
}
2.DHT11温湿度采集
(1)DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器,它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性和卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,使其成为该类应用中,在苛刻应用场合的最佳选择。产品为4针单排引脚封装,连接方便。
(2)DHT11分辨率:分辨率分别为8b(温度)、8b(湿度)。
(3)DHT11串行通信:单片机与DHT11之间的通信与同步,采用单总线数据格式。一次通信时间4ms左右,数据分为小数部分和整数部分。一次完整的数据公八个传输为40b,高位先出。
(4)与单片机通信过程
(5)DHT11传感器发送的每一位数据都已以50us低电平时间隙
开始,之后高电平的长短决定了数据位是0还是1。
代码如下(DHT11):
DHT11.h文件
#ifndef __DHT11_H
#define __DHT11_H
#include "sys.h"
//IO方向设置
#define DHT11_IO_IN() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=8<<12;}
#define DHT11_IO_OUT() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=3<<12;}
IO操作函数
#define DHT11_DQ_OUT PGout(11) //数据端口 PA0
#define DHT11_DQ_IN PGin(11) //数据端口 PA0
u8 DHT11_Init(void);//初始化DHT11
u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度
u8 DHT11_Read_Byte(void);//读出一个字节
u8 DHT11_Read_Bit(void);//读出一个位
u8 DHT11_Check(void);//检测是否存在DHT11
void DHT11_Rst(void);//复位DHT11
#endif
DHT11.c文件
#include "dht11.h"
#include "delay.h"
//复位DHT11
void DHT11_Rst(void)
{
DHT11_IO_OUT(); //SET OUTPUT
DHT11_DQ_OUT=0; //拉低DQ
delay_ms(20); //拉低至少18ms
DHT11_DQ_OUT=1; //DQ=1
delay_us(30); //主机拉高20~40us
}
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void)
{
u8 retry=0;
DHT11_IO_IN();//SET INPUT
while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
{
retry++;
delay_us(1);
};
if(retry>=100)return 1;
else retry=0;
while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
{
retry++;
delay_us(1);
};
if(retry>=100)return 1;
return 0;
}
//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void)
{
u8 retry=0;
while(DHT11_DQ_IN&&retry<100)//等待变为低电平
{
retry++;
delay_us(1);
}
retry=0;
while(!DHT11_DQ_IN&&retry<100)//等待变高电平
{
retry++;
delay_us(1);
}
delay_us(40);//等待40us
if(DHT11_DQ_IN)return 1;
else return 0;
}
//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)
{
u8 i,dat;
dat=0;
for (i=0;i<8;i++)
{
dat<<=1;
dat|=DHT11_Read_Bit();
}
return dat;
}
//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp,u8 *humi)
{
u8 buf[5];
u8 i;
DHT11_Rst();
if(DHT11_Check()==0)
{
for(i=0;i<5;i++)//读取40位数据
{
buf[i]=DHT11_Read_Byte();
}
if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
{
*humi=buf[0];
*temp=buf[2];
}
}else return 1;
return 0;
}
//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE); //使能PG端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; //PG11端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOG, &GPIO_InitStructure); //初始化IO口
GPIO_SetBits(GPIOG,GPIO_Pin_11); //PG11 输出高
DHT11_Rst(); //复位DHT11
return DHT11_Check();//等待DHT11的回应
}
3.TFT-LCD
LCD使用正点原子自带的显示屏3.5寸的。由于LCD程序过于太大,以下只展示部分主要程序。
//显示数字,高位为0,则不显示
//x,y :起点坐标
//len :数字的位数
//size:字体大小
//color:颜色
//num:数值(0~4294967295);
void LCD_ShowNum(u16 x,u16 y,u32 num,u8 len,u8 size)
{
u8 t,temp;
u8 enshow=0;
for(t=0;t<len;t++)
{
temp=(num/LCD_Pow(10,len-t-1))%10;
if(enshow==0&&t<(len-1))
{
if(temp==0)
{
LCD_ShowChar(x+(size/2)*t,y,' ',size,0);
continue;
}else enshow=1;
}
LCD_ShowChar(x+(size/2)*t,y,temp+'0',size,0);
}
}
//显示字符串
//x,y:起点坐标
//width,height:区域大小
//size:字体大小
//*p:字符串起始地址
void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p)
{
u8 x0=x;
width+=x;
height+=y;
while((*p<='~')&&(*p>=' '))//判断是不是非法字符!
{
if(x>=width){x=x0;y+=size;}
if(y>=height)break;//退出
LCD_ShowChar(x,y,*p,size,0);
x+=size/2;
p++;
}
}
4.主程序
#include "stm32f10x.h" // Device header
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "key.h"
#include "usmart.h"
#include "malloc.h"
#include "sdio_sdcard.h"
#include "w25qxx.h"
#include "ff.h"
#include "exfuns.h"
#include "text.h"
#include "dht11.h"
#include "ds18b20.h"
#include "relay.h"
u8 H = 50; //设定湿度默认值
short T = 255; //设定温度默认值
int main(void)
{
u8 key1;
u8 fontx[2];//gbk码
u8 t=0;
u8 temperature1;
u8 humidity;
short temperature;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
usmart_dev.init(72); //初始化USMART
KEY_Init(); //按键中断初始化
LED_Init(); //初始化与LED连接的硬件接口
LCD_Init(); //初始化LCD
W25QXX_Init(); //初始化W25Q128
my_mem_init(SRAMIN); //初始化内部内存池
exfuns_init(); //为fatfs相关变量申请内存
f_mount(fs[0],"0:",1); //挂载SD卡
f_mount(fs[1],"1:",1); //挂载FLASH.
while(font_init()) //检查字库
{
LCD_ShowString(30,110,200,16,16,"Font Update Success! ");
delay_ms(1500);
LCD_Clear(WHITE);//清屏
}
while(DHT11_Init()) //DHT11初始化
{
LCD_ShowString(30,130,200,16,16,"DHT11 Error");
delay_ms(200);
LCD_Fill(30,130,239,130+16,WHITE);
delay_ms(200);
}
while(DS18B20_Init()) //DS18B20初始化
{
LCD_ShowString(30,130,200,16,16,"DS18B20 Error");
delay_ms(200);
LCD_Fill(30,130,239,130+16,WHITE);
delay_ms(200);
}
//界面显示部分
POINT_COLOR=RED;
Show_Str(100,30,200,16,"舱内恒温恒湿系统",16,0);
POINT_COLOR=BLUE;
Show_Str(30,70,200,16,"设定温度: . C",16,0);
Show_Str(30,90,200,16,"设定湿度: %",16,0);
Show_Str(30,110,200,16,"测量温度: . C",16,0);
Show_Str(30,130,200,16,"测量湿度: %",16,0);
POINT_COLOR=GREEN;
Show_Str(30,170,200,16,"加热:",16,0);
Show_Str(30,190,200,16,"制冷:",16,0);
Show_Str(30,210,200,16,"加湿:",16,0);
while(1)
{
if(t%10==0) //每100ms读取一次
{
DHT11_Read_Data(&temperature1,&humidity); //读取温湿度值
temperature=DS18B20_Get_Temp();
//向串口打印数据
printf("\r\n %d.%d C,%d RH,%d.%d C,%d RH \r\n",T/10,T%10,H,temperature/10,temperature%10,humidity);
delay_ms(10);
//读取按键值,进而改变设定值
key1=KEY_Scan(0);
switch(key1){
case(1):H++;break;
case(2):T++;break;
case(3):T--;break;
}
//控制加热和制冷
if(temperature>(T)){
Show_Str(30+40,170,200,16,"打开",16,0);
GPIO_SetBits(GPIOD,GPIO_Pin_7); //打开加热开关
}else{
Show_Str(30+40,170,200,16,"关闭",16,0);
GPIO_ResetBits(GPIOD,GPIO_Pin_7); //关闭加热开关
}
if(temperature<(T)){
Show_Str(30+40,190,200,16,"打开",16,0);
GPIO_SetBits(GPIOD,GPIO_Pin_8); //打开制冷开关
}else{
Show_Str(30+40,190,200,16,"关闭",16,0);
GPIO_ResetBits(GPIOD,GPIO_Pin_8); //关闭制冷开关
}
//控制加湿
if(humidity<H){
Show_Str(30+40,210,200,16,"打开",16,0);
GPIO_SetBits(GPIOD,GPIO_Pin_9); //打开加湿开关
}else{
Show_Str(30+40,210,200,16,"关闭",16,0);
GPIO_ResetBits(GPIOD,GPIO_Pin_9); //关闭加湿开关
}
//温度显示
if(temperature<0)
{
LCD_ShowChar(30+40,150,'-',16,0); //显示负号
temperature=-temperature; //转为正数
}else LCD_ShowChar(30+40,150,' ',16,0); //去掉负号
LCD_ShowNum(30+70+8,110,temperature/10,2,16); //显示正数部分
LCD_ShowNum(30+70+32,110,temperature%10,1,16); //显示小数部分
LCD_ShowNum(30+70,130,humidity,2,16); //显示湿度
LCD_ShowNum(30+70+8,70,T/10,2,16); //显示设定温度正数部分
LCD_ShowNum(30+70+32,70,T%10,1,16); //显示设定温度小数部分
LCD_ShowNum(30+70,90,H,2,16); //显示设定湿度
}
delay_ms(10);
t++;
if(t==20)
{
t=0;
LED0=!LED0;
}
}
}
主程序中采用了小灯、按键、挂载SD等不再赘述。
5.上位机设计
(1)与下位机如何通信:采用UART串口通信,下位机将读取到的温湿度通过printf()函数打印到串口。
(2)如何处理接受到的数据:labview通过截取字符串函数将接受的数据对应的截取,然后保存到簇中。
(3)如何显示温湿度曲线:本次设计了三个显示程序,第一个采用两个波形图表,分别显示温度和湿度曲线;第二个采用一个波形图表同时显示两条曲线;第三个采用一个波形图表并采用分格显示。
6.成果展示
(1)下位机显示
(2)上位机显示
①两个波形图表显示
②一个波形图表显示两条曲线
③一个波形图表分格显示两条曲线
总结
本次设计主要针对单片机如何与上位机进行多数据通讯,主要采用字符串发送和字符串截取的方法;STM32单片机在进行多数据采集时,要注意如果采用1-6口读取数据是要配置CRL寄存器,7-12口读取数据时需要配置CRH寄存器,如果是F4的开发板则要配置MODE寄存器。本设计难度一般,有兴趣的朋友可以自己创新设计。
程序连接
老版的连接:
链接:https://pan.baidu.com/s/12Bg6eE14CWQRX-hs_jkv4g
提取码:tv7q
更新后的连接:
链接:https://pan.baidu.com/s/1Q12d00iBI0mHQovKt40GDg
提取码:w8nj