一 摘要
温度报警在生活、工业以及农业上都具有广泛的用途,比如生活上设置一个温度报警功能,可以转换为对火灾的监控;工业上锅炉对温度报警功能,实现对锅炉异常监控等,所以高可靠高精度温度报警控制系统的设计具有重要意义。
本文设计以温度报警和控制功能为核心的温度控制系统,首先实现对实现该功能的要素进行分析,设计系统方案,选择确定以DS18B20传感器来实现温度的检测功能,采用ARM单片机接受传感器检测到的温度数据后,与设定的温度上限范围,温度下限范围进行比较,自动识别温度是否异常,如果识别到温度异常,设计蜂鸣器和LED进行报警,实现温度报警功能,同时采用继电器调节升温和降温功能,使得温度维持在给定范围内;通过分析各个模块的特点,对嵌入式温度控制系统的硬件电路做了详细的分析,对软件算法进行详细的分析,最后对实物制作,完成了以温度控制为核心的嵌入式温度控制系统的实物焊接与制作,设计出一款温度控制系统,其中系统工作稳定,检测灵敏度高,实现对温度的检测、显示以及控制。
二 嵌入式温度控制系统方案设计
2.1总体方框图
以温度控制功能为核心的报警系统,通过满足系统要求的传感器检测温度,单片机读取传感器检测温度值后,对该温度值显示,同时与设定温度上限和下限比较后,判断是否存在温度异常,对蜂鸣器和继电器进行控制,系统框图如图2-1所示。
图2-1 系统框图
三 嵌入式温度控制系统的硬件设计
3.1硬件总体框图
本文系统硬件包括单片机控制电路,温度检测电路,显示电路,按键控制电路以及温度控制电路,硬件总体框图如图3-1所示。
图3-1 硬件总体框图
DS18B20传感器电路:将温度数据传送给单片机,实现对温度的检测。
三个按键电路:实现对温度调节界限的上限和下限调节。
显示电路:采用LCD1602对检测的实时温度值和设置的界限值实时显示。
升温和降温继电器:采用两路继电器实现对加热片和风扇驱动,实现升温和降温。
报警电路:采用蜂鸣器当温度出现异常时,进行报警。
单片机控制电路:接受传感器和按键电路的信息,对继电器以及报警和显示电路驱动。
四 嵌入式温度控制系统软件设计
4.1主程序设计
在开发软件时,基本上都使用C语言和汇编语言来完成设计任务。在使用汇编语言来开发软件系统时,里面所包含的机器指令主要通过文字助记来表达,这种表达也是与机器码最接近的一个。它在执行程序时有突出的优点,效率很高,不会占用很多的存储空间,但是也有很多缺点,比如兼容性比较差,不能适用于多种不同的CPU。C语言则比汇编语言所具有的优点更多,它不仅融合了汇编语言的优势同时还具备计算机高级语言的特点,兼容性较高,比较容易读写,可以直接控制硬件动作,修改和扩充系统功能时也比较方便,更加迎合人们的想法。
从以上两种编程语言的对比可知,C语言更适合用来编写本系统的软件程序,所以本系统所有的软件部分都使用C语言来完成。将系统的软件首先按照需要实现的功能划分为几个彼此独立的部分,然后再依据每个部分要完成的具体动作来完成各个子程序的编写工作,通过不停调用各个子程序实现系统程序的循环运行,以此就可以达到各司其职的目的。上述已经提到单片机一般采用汇编和C语言来进行编写程序。这两种语言相比较而言,C语言更加的方便被使用,并且也更容易被读写和移植,在开发代码方面也表现得更加突出,所以现在一般编程都会优先选择C语言。
主程序先设置定时模块初始化、LCD模块,再设置显示设置,在显示模块设置显示位置,显示温度上限值,温度下限值以及实时温度值,然后进入while控制的循环,实时读取温度值,按键值以及对温度进行比较,实现报警,设置等功能,主程序流程图如图4-1所示。
图4-1 主程序流程图
为了提高系统运行效率,300ms实现对温度数据进行读取,然后调用不同的子函数,实现具体功能,其中子函数有显示子函数,按键子函数以及报警子函数。
部分代码
附录
#include "LCD1602.h"
#include "delay.h"
#include "sys.h"
//把8位数按位逆序
u8 BitReversed(u8 x)
{
u8 temp[8];
u8 i = 0;
u8 j = 7;
u8 result = 0;
for(i = 0;i<8;i++)
{
temp[i] = (x>>i)&0x01;
}
for(i = 0;i<8;i++)
{
result = result|(temp[j]<<(i));
j--;
}
return result;
}
void LCD_Check_Busy(void)
{
LCD1602_RS0;
delay_us(1);
LCD1602_RW1;
delay_us(1);
LCD1602_EN0;
delay_us(1);
GPIO_Write(GPIOC,0Xff);
delay_ms(1);
LCD1602_EN1;
delay_us(100);
}
/*------------------------------------------------
写入命令函数
------------------------------------------------*/
void LCD_Write_Com(unsigned char com)
{
LCD1602_RS0;
delay_us(1);
LCD1602_RW0;
delay_us(1);
LCD1602_EN1;
delay_us(1);
com = BitReversed(com);
GPIO_Write(GPIOA,(GPIO_ReadOutputData(GPIOA)&0XFF00)+com);
delay_us(100);
LCD1602_EN0;
}
/*------------------------------------------------
写入数据函数
------------------------------------------------*/
void LCD_Write_Data(unsigned char Data)
{
LCD1602_RS1;
delay_us(1);
LCD1602_RW0;
delay_us(1);
LCD1602_EN1;
delay_us(1);
Data = BitReversed(Data);
GPIO_Write(GPIOA,(GPIO_ReadOutputData(GPIOA)&0XFF00)+Data);
delay_us(100);
LCD1602_EN0;
}
/*------------------------------------------------
写入字符串函数
------------------------------------------------*/
void LCD_Write_String(unsigned char x,unsigned char y,unsigned char *s)
{
if (y == 0)
{
LCD_Write_Com(0x80 + x);
}
else
{
LCD_Write_Com(0xC0 + x);
}
while (*s)
{
LCD_Write_Data( *s);
s ++;
}
}
/*------------------------------------------------
写入字符函数
------------------------------------------------*/
void LCD_Write_Char(unsigned char x,unsigned char y,unsigned char Data)
{
if (y == 0)
{
LCD_Write_Com(0x80 + x);
}
else
{
LCD_Write_Com(0xC0 + x);
}
LCD_Write_Data( Data);
}
void LCD1602_write_long(unsigned char x,unsigned char y,u32 data,unsigned char num)
{
unsigned char temp[12],i = 12;
while(i--)
{
temp[i] = ' ';
}
temp[num] = '\0';
while(num--)
{
if(data || data%10)
temp[num] = data % 10 + 0x30;
data = data/10;
}
LCD_Write_String(x,y,temp);
}
/*------------------------------------------------
清屏函数
------------------------------------------------*/
void LCD_Clear(void)
{
LCD_Write_Com(0x01);
delay_ms(5);
}
/*------------------------------------------------*/
void LCD_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOA GPIOB GPIOC时钟
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; //输出速度50MHZ
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1|GPIO_Pin_10; // LCD1602 RS-RW-EN?
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出速度50MHZ
GPIO_Init(GPIOB, &GPIO_InitStructure); //GPIOC
LCD_Write_Com(0x38);
delay_ms(5);
LCD_Write_Com(0x08); /*显示关闭*/
delay_ms(5);
LCD_Write_Com(0x01); /*显示清屏*/
delay_ms(5);
LCD_Write_Com(0x06); /*显示光标移动设置*/
delay_ms(5);
LCD_Write_Com(0x0C); /*显示开及光标设置*/
delay_ms(5);
}
#include "key.h"
#include "sys.h"
#include "delay.h"
//按键初始化函数
void KEY_Init(void) //IO初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
//初始化KEY0-->GPIOA.13,KEY1-->GPIOA.15 上拉输入
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);//使能PORTA,PORTE时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;//PE2~4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE2,3,4
//初始化 WK_UP-->GPIOA.0 下拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0
}
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,KEY0按下
//2,KEY1按下
//3,KEY2按下
//4,KEY3按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY2>KEY3!!
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(KEY0==0||KEY1==0||KEY2==0||KEY3==1))
{
delay_ms(10);//去抖动
key_up=0;
if(KEY0==0)return 1;
else if(KEY1==0)return 2;
else if(KEY2==0)return 3;
else if(KEY3==1)return 4;
}else if(KEY0==1&&KEY1==1&&KEY2==1&&KEY3==0)key_up=1;
return 0;// 无按键按下
}
#include "ds18b20.h"
#include "delay.h"
//复位DS18B20
void DS18B20_Rst(void)
{
DS18B20_IO_OUT(); //SET PA0 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 PA0 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) // read one bit
{
u8 data;
DS18B20_IO_OUT();//SET PA0 OUTPUT
DS18B20_DQ_OUT=0;
delay_us(2);
DS18B20_DQ_OUT=1;
DS18B20_IO_IN();//SET PA0 INPUT
delay_us(12);
if(DS18B20_DQ_IN)data=1;
else data=0;
delay_us(50);
return data;
}
//从DS18B20读取一个字节
//返回值:读到的数据
u8 DS18B20_Read_Byte(void) // read one byte
{
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 PA0 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)// ds1820 start convert
{
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_GPIOA, ENABLE); //使能PORTA口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //PORTA0 推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_8); //输出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;
}
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart1.h"
#include "LCD1602.h"
#include "string.h"
#include "stdio.h"
#include "ds18b20.h"
#include "stmflash.h"
#define WriteFlashAddress ((u32)0x08010000)//读写起始地址
u8 Temperature;//温度
u16 Temperature_Min = 15;//温度报警值:下限
u16 Temperature_MAX = 40;//温度报警值:上限
u8 setnum = 0;//设置变量
void STMFLASH_WRITE()//STM32 内部FLASH写入
{
STMFLASH_Write(WriteFlashAddress,(u16*)"FDYDZ",5);//用于校验
STMFLASH_Write(WriteFlashAddress+0x02,&Temperature_Min,1);//存储报警值
STMFLASH_Write(WriteFlashAddress+0x04,&Temperature_MAX,1);//存储报警值
delay_ms(100);
}
void STMFLASH_READ()//STM32 内部FLASH读取
{
char STMFLASH_CHECK[6];
STMFLASH_Read(WriteFlashAddress,(u16*)STMFLASH_CHECK,5);//读出校验字符串
STMFLASH_CHECK[5] = '\0';
if(strstr(STMFLASH_CHECK,"FDYDZ") == NULL) //新的单片机,需要先存储一遍值,然后再读取,值才不会出错
{
STMFLASH_WRITE();//存储
}
STMFLASH_Read(WriteFlashAddress+0x02,&Temperature_Min,1);//读出报警值
STMFLASH_Read(WriteFlashAddress+0x04,&Temperature_MAX,1);//读出报警值
if(Temperature_Min<0 || Temperature_MAX<0 || Temperature_Min>99 || Temperature_MAX>99)//内部FLASH出错,则赋原始值
{
Temperature_Min = 15;
Temperature_MAX = 40;
}
}
void display_set_val()//显示报警值
{
LCD_Write_Char(4,1,Temperature_Min/10+0x30);
LCD_Write_Char(5,1,Temperature_Min%10+0x30);
LCD_Write_Char(13,1,Temperature_MAX/10+0x30);
LCD_Write_Char(14,1,Temperature_MAX%10+0x30);
}
void KEY_SCAN(void)//按键扫描
{
if(!KEY1)//设置键
{
delay_ms(10);
if(!KEY1)
{
while(!KEY1);//卡死
setnum++;
if(setnum > 2)//按下次数超过两次,退出设置
{
setnum = 0;
LCD_Write_Com(0x0C); //关闭光标
}
if(setnum == 1)
{
LCD_Write_Com(0x80+0x40+5);
LCD_Write_Com(0x0F);
}
if(setnum == 2)
{
LCD_Write_Com(0x80+0x40+14);
LCD_Write_Com(0x0F);
}
}
}
if(!KEY2)//加键
{
delay_ms(10);
if(!KEY2)
{
while(!KEY2);//卡死
if(setnum == 1)
{
if(Temperature_MAX-Temperature_Min>1)Temperature_Min++;
display_set_val();
LCD_Write_Com(0x80+0x40+5);
LCD_Write_Com(0x0F);
STMFLASH_WRITE(); //存储
}
if(setnum == 2)
{
if(Temperature_MAX<99)Temperature_MAX++;
display_set_val();
LCD_Write_Com(0x80+0x40+14);
LCD_Write_Com(0x0F);
STMFLASH_WRITE(); //存储
}
}
}
if(!KEY3)//减键
{
delay_ms(10);
if(!KEY3)
{
while(!KEY3);//卡死
if(setnum == 1)
{
if(Temperature_Min>0)Temperature_Min--;
display_set_val();
LCD_Write_Com(0x80+0x40+5);
LCD_Write_Com(0x0F);
STMFLASH_WRITE(); //存储
}
if(setnum == 2)
{
if(Temperature_MAX-Temperature_Min>1)Temperature_MAX--;
display_set_val();
LCD_Write_Com(0x80+0x40+14);
LCD_Write_Com(0x0F);
STMFLASH_WRITE(); //存储
}
}
}
}
五 系统调试
5.1焊接实物与调试
根据原理图对实物焊接并调试,首先进行单元调试,再进行系统整体调试。在对每一单元部分进行调试的时候应首先弄清楚调试标准,调试的先后顺序一定要与系统中信号的传输方向保持一致,来完成整个系统的分步调试过程。单元调试分为静态和动态这两种方式。静态调试指的是通过测量在无外界信号干扰下系统各元器件的工作状况,通过静态调试,可以让已经处于异常或者即将异常的元器件第一时间从系统中排除出去。动态调试则是指在系统运行的过程中接收前一部分单元信号后,此单元的工作参数达标与否。当完成系统中每一个独立单元的调试工作以后,联合构成的系统是否能够正常工作还不一定,所以对系统整体电路进行调试过程必不可少。整体的系统调试主要依靠当系统运行时检测动态参数是否达标来完成的性能测试,将正在运行的系统各个参数及时记录下来,并把参数与设计值进行对比就能够找到问题出在哪个地方,最后想办法去解决它,及时的调整动态参数值,直至全部达到设计标准才算是成功完成了整个调试过程。实物图如图5-1所示。
图5-1实物图
六 总结
利用传感器技术和单片机技术,以实现温度报警为出发点,来设计整个系统,选择DS18B20高精度数字式温度传感器实现对温度的检测,检测到温度异常后,就会通过灯光和声音指示报警,本文完成的工作以及得到的总结如下:
1.以温度报警功能为基础,围绕温度的检测、显示、报警等方面展开工作,实现检测0到+125摄氏度的温度范围。
2.研究单片机与DS18B20传感器之间的通信时序,单片机按照DS18B20传感器的工作时序,来精确的采集温度数据。
3.采用显示、蜂鸣器以及LED灯实现温度的显示功能和核心的温度异常报警功能。
4.围绕单片机及外围电路,传感器及外围电路以及报警等电路的研究,设计各部分硬件电路和软件算法,实现对嵌入式温度控制系统的实物验证。
设计出来的嵌入式温度控制系统,实物通过调试,实现以温度报警为主要功能的温度检测报警系统,因受时间和能力条件的限制,系统还能做的更加完善,比如通常温度和湿度不分家,可以增加湿度的报警功能,同时该系统不具有对温度远程检测和对温度远程控制的功能,因此功能相对单一,如果能在系统中增加保湿和去湿功能以及无线发送数据到监控端实现远程监控,那么系统将会得到非常大的升级。