以ATmega128为控制核心,从DS18B20温度传感器进行温度数据采集、按键设定预期温度值、LCD1602液晶屏实时显示温度值、PID算法、报警及输出控制加热、制冷模块等几个方面出发,详细研究和设计了基于单片机的温度控制系统,设计了单片机及其外围电路、用保温棉材料制作了保温效果较好的恒温箱箱体,并结合控制效果较好的增量式PID算法程序,给出了一套合理的基于ATmega128单片机的温度控制器软硬件解决方案。
上图给大家看吧 :
恒温箱箱体设计
箱体内部视图---有温度传感器和制冷片
温度显示模块
报警模块
用H桥驱动制冷片
运行效果比你想的要好!哈哈
测试效果
很明显 !
程序设定的预期温度值为30.0℃,刚给恒温箱系统上电如图6-8系统整体硬件电路实物图,液晶屏上显示温度情况如上图:
即刚启动时恒温箱内为温度为26.1摄氏度。
很快可以看到,下行显示的温度值在不停的增加,直到30.0℃附近有一些来回的波动
由于串口接收的是十六进制的数据,需要先将其转换为10进制,然后输入到
Microsoft Excel软件中,画出曲折线图表。我选取了52个翻译为10进制后的温度数据值,制成了曲线图。
··············································································
测试结果分析:
从观察显示屏上温度值较快的变化,感觉恒温箱控制效果较理想。观察采样数据得出的折线图,也可以大致得出相同的结论。
恒温箱温控快速性较好,在设定值附近存在上下波动,分析可能原因一是恒温箱保温效果的限制;二是加热制冷模块在加热与制冷切换有一定的时间延迟,决定了加热时反应快,制冷时相应反应时间稍慢一点。
最后,PID 控制原理的优点在于能够在控制过程中根据预先设定好的控制规律不停地自动调节控制量以使被控系统朝着设定的平衡状态过度, 最后达到控制范围精度内的稳定的动态平衡状态。要使用好PID 控制原理,关键在于根据实际情况确定PID的各种参数,这项工作可能是费时的, 但做好了, 将会提高控制器的使用效果, 达到较高的控制精度, 是值得的。我觉得PID参数的整定还需要经过很多次的测试与调整才会收到更好的效果,这是需要有待改进的地方。
··············································································
附录2:系统的程序源代码
#include<iom128v.h>
#include<macros.h>
#include<delay.h>
#define uint unsigned int
#define uchar unsigned char
#define PC6_CLR PORTC&=~BIT(6) //PC.6清零
#define PC6_SET PORTC|=BIT(6) //PC.6置1
#define PC6_OUT DDRC|=BIT(6) //PC.6设置为输出
#define RS_CLR PORTC&=~BIT(0) //PC.0清零 PC.0-1-2用于1602的控制
#define RS_SET PORTC|=BIT(0) //PC.0置1
#define RS_OUT DDRC|=BIT(0) //PC.0设置为输出
#define RW_CLR PORTC&=~BIT(1) //PC.1清零
#define RW_SET PORTC|=BIT(1) //PC.1置1
#define RW_OUT DDRC|=BIT(1) //PC.1设置为输出
#define E_CLR PORTC&=~BIT(2) //PC.2清零
#define E_SET PORTC|=BIT(2) //PC.2置1
#define E_OUT DDRC|=BIT(2) //PC.2设置为输出
#define DQ_IN DDRF&=~BIT(3) //设置PF3端口为输入 PF3用于18B02与单片机相连
#define DQ_OUT DDRF|=BIT(3) //设置PF3端口为输出
#define DQ_SET PORTF|=BIT(3) //置PF3端口为1
#define DQ_CLR PORTF&=~BIT(3) //置PF3端口为0
#define DQ_R PINF & BIT(3) //检测PF3端口是否为1
#define ring_CLR PORTG&=~BIT(4) //PG.4清零 PG4用于蜂鸣器报警
#define ring_SET PORTG|=BIT(4) //PG.4置1
#define ring_OUT DDRG|=BIT(4) //PG.4设置为输出
#define key1_IN DDRD&=~BIT(0) //设置PD0端口为输入
#define key1_SET PORTF|=BIT(0) //置PD0端口为1
#define key1_R PIND & BIT(0) //检测PD0端口是否为1
#define key2_IN DDRD&=~BIT(1) //设置PD1端口为输入
#define key2_SET PORTF|=BIT(1) //置PD1端口为1
#define key2_R PIND & BIT(1) //检测PD1端口是否为1
#define key3_IN DDRD&=~BIT(2) //设置PD2端口为输入
#define key3_SET PORTF|=BIT(2) //置PD2端口为1
#define key3_R PIND & BIT(2) //检测PD2端口是否为1
uchar table1[16]="SettingTemp:30.0";
uchar table2[16]="Detect Temp:";
uchar table3[16]="0123456789";
uchar table4[16]; //临时数据 1602使用
uchar miao,fen,shi;
uint temp_set;
uint tem;//全局变量 用于存储放大了10倍的实测的温度值
uchar key1,key2,key3;
void ext_init(void) //中断初始化函数 首先把我们要用的端口、寄存器等初始化
{
EICRA=0x2a; //设置外部中断INT0、INT1、INT2的触发方式为下降沿触发
EIMSK=0X07; //使能外部中断INT0、INT1、INT2
//设置中断管脚是否需要上拉电阻
DDRD&=~BIT(0);// 设置外部中断INT0的端口PD.0工作方式为输入
DDRD&=~BIT(1);// 设置外部中断INT1的端口PD.1工作方式为输入
DDRD&=~BIT(2);// 设置外部中断INT2的端口PD.2工作方式为输入
PORTD|=BIT(0);
PORTD|=BIT(1);
PORTD|=BIT(2);//还必须加上将其置1,这样才会有上拉电阻
SREG|=BIT(7);//打开全局中断
}
void write_1602_com(uchar com) //1602的写命令函数
{
E_CLR;
RS_CLR;
RW_CLR;
delay_nms(1);
PORTA=com;
delay_nms(1);
E_SET;
delay_nms(1);
E_CLR;
}
void write_1602_data(uchar data) //1602的写数据函数
{
E_CLR;
RS_SET;
RW_CLR;
PORTA=data;
delay_nms(1);
E_SET;
delay_nms(1);
E_CLR;
}
//1602的初始化函数
void init_1602(void)
{ uint i;
DDRA=0xff;//将PA设置为输出 因为PA是用作1602的数据口
PORTA=0;//让PA口初始输出0
RS_OUT;//因为用到了PC.0 PC.1 PC.2端口,用做输出,所以要先初始化
RW_OUT;
E_OUT;
PC6_OUT;
PC6_SET;
delay_nms(15);
write_1602_com(0x38);
delay_nms(5);
write_1602_com(0x38);
delay_nms(5);
write_1602_com(0x38);
write_1602_com(0x38);
write_1602_com(0x08);
write_1602_com(0x01);
write_1602_com(0x06);
write_1602_com(0x0c);
write_1602_com(0x80);// 给出要显示的位置,此时设的是第一行第一个
for(i=0;i<16;i++)
{
write_1602_data(table1);
delay_nms(2);
}
write_1602_com(0x80+0x40);//给出要显示的位置,此时设的是第二行第一个
for(i=0;i<12;i++)
{
write_1602_data(table2);
delay_nms(2);
}
}
void display_1602(void) //1602的显示函数 显示的是温度检测器得出的温度值
{
uchar num;
write_1602_com(0x80+0x4c);//设定显示位置在第二行第十三个字符
for(num=12;num<16;num++)
{
write_1602_data(table4[num]);
}
}
uchar init_DS18B20(void)
{
uchar i;
DQ_OUT; //先将DQ设置为输出
DQ_CLR; //将DQ拉低
delay_nus(500);//延时500us
DQ_SET;//将DQ拉高
delay_nus(100);//延时100us
DQ_IN;//先将DQ设置为输入
i=DQ_R;
delay_nus(500);//延时500us
return i;
}
//主机往DS18B20写一个字节的数据
void write_DS18B20_byte(uchar value)
{ uchar i;
for(i=0;i<8;i++)
{
DQ_OUT; //先将DQ设置为输出
DQ_CLR; //将DQ拉低
delay_nus(10);
if(value&0x01)
{
DQ_SET;//如果value为1,则表示写1,将DQ拉高
}
delay_nus(100); //如果value为0,则表示写0,只需延时即可
DQ_SET; //写数据完之后需要释放总线 将其拉高
value=value>>1;//每写完一位后需要移位
}
}
//主机从DS18B20读取一个字节的数据
uchar read_DS18B20_byte(void)
{
uchar i;
uchar value;
for(i=0;i<8;i++)
{ value=value>>1;
DQ_OUT; //先将DQ设置为输出
DQ_CLR; //将DQ拉低
delay_nus(10);
DQ_SET;
DQ_IN;//先将DQ设置为输入
if(DQ_R) //如果总线为1,则将赋值给value为1
{
value|=0x80;
}
delay_nus(50);
} //否则仍返回 value为 0
return value;
}
void temp_change(void) //DS18B20开始获取温度并转换
{
init_DS18B20();
write_DS18B20_byte(0xcc);//跳过ROM,通常用于启动所有DS18B20转换之前,或系统中仅有一个DS18B20
write_DS18B20_byte(0x44);//启动温度转换
delay_nms(1);
}
uint get_temp(void) //读取DS18B20寄存器中存储的温度数据
{
float f_temp;
uchar a,b;
init_DS18B20();
delay_nus(1000);
write_DS18B20_byte(0xcc);
write_DS18B20_byte(0xbe);//读暂存器用于读取暂存器中的内容,从字节0开始最多可以读取9个字节,如果不想读完所有字节,主机可以在任何时间发出复位命令来终止读取。
a=read_DS18B20_byte(); //a放低字节 LSB
b=read_DS18B20_byte(); //b放高字节 MSB
tem=b;
tem<<=8; //两个字节组合为1个字节
tem=tem|a;
f_temp=tem*0.0625; //温度在寄存器中为12位,分辨率为0.0625
tem=f_temp*10+0.5; //乘以10表示小数点后面只取1位,加0.5是四舍五入
f_temp=f_temp+0.05;
return tem; //tem是整型 得到的温度值是放大了10倍的数值
}
void dis_temp(uint t) //1602显示温度数值函数t传递的是整型温度值 进行数据处理
{
uchar i;
i=t/100; //除以100得到商,为温度的十位
table4[12]=table3;
i=t%100/10; //100取余再除以10得到商,为温度的个位
table4[13]=table3;
i=t%100%10; //100取余再用10取余,为温度的小数
table4[15]=table3;
table4[14]=table1[14];
}
uchar get_settemp(uint a) //读出设定的温度值
{
return table1[a];
}
uint init_key(void)
{
uchar key1n;
miao=get_settemp(12);
fen=get_settemp(13);
shi=get_settemp(15);
key1_IN;
key2_IN;
key3_IN;
key1_SET;
key2_SET;
key3_SET;
key1=key1_R;
key2=key2_R;
key3=key3_R;
if(key1==0)//---------------key1为功能键(设置键)-----------
{
delay_nms(9);//延时,用于消抖动
if(key1==0)//延时后再次确认按键按下
{
ring_SET;//蜂鸣器短响一次
delay_nms(15);
ring_CLR;
while(!key1);
key1n++;
if(key1n==5)
key1n=1;//设置按键共有秒(百位)、分(十位)、时(个位)、返回,4个功能循环
switch(key1n)
{
case 1: write_1602_com(0x80+0x0c);//设置按键按动一次,秒(百位)位置显示光标
write_1602_com(0x0f);//设置光标为闪烁
break;
case 2: write_1602_com(0x80+0x0d);//按2次 fen(十位)位置显示光标
write_1602_com(0x0f);
break;
case 3: write_1602_com(0x80+0x0f);//按动3次,shi(个位)位置显示光标
write_1602_com(0x0f);
break;
case 4: write_1602_com(0x0c);//按动到第4次,设置 光标不闪烁
break;
}
}
}
//------------------------------加键key2----------------------------
if(key1n!=0)//当key1按下以后 再按以下键才有效(按键次数不等于零)
{
if(key2==0) //上调键
{
delay_nms(10);
if(key2==0)
{
ring_SET;//蜂鸣器短响一次
delay_nms(20);
ring_CLR;
while(!key2);
switch(key1n)
{
case 1: miao++; //设置键按动1次,调秒
if(miao==10)
miao=0; //秒超过9,再加1,就归零
write_1602_com(0x80+0x0c);
write_1602_data(miao);
break;
case 2: fen++;
if(fen==10)
fen=0;
write_1602_com(0x80+0x0d);
write_1602_data(fen);
break;
case 3: shi++;
if(shi==10)
shi=0;
write_1602_com(0x80+0x0f);
write_1602_data(shi);
break;
}
}
}
//------------------减键key3,各句功能参照'加键'注释---------------
if(key3==0)
{
delay_nms(10);//调延时,消抖动
if(key3==0)
{
ring_SET;//蜂鸣器短响一次
delay_nms(20);
ring_CLR;
while(!key3);
switch(key1n)
{
case 1: miao--;
if(miao==-1)
miao=9;//秒数据减到-1时自动变成9
write_1602_com(0x80+0x0c);
write_1602_data(miao);
break;
case 2: fen--;
if(fen==-1)
fen=9;
write_1602_com(0x80+0x0d);
write_1602_data(fen);
break;
case 3: shi--;
if(shi==-1)
shi=9;
write_1602_com(0x80+0x0f);
write_1602_data(shi);
break;
}
}
}
}
temp_set=miao*100+fen*10+shi;
}
void pwm0_init(void) //pwm输出函数
{DDRB=0X10;
TCCR0=0X00;
OCR0=0X7F;//8位的定时计数器的初值设定为0x7f
TCNT0=0; //计数器
TCCR0=0X6A;//设置为快速pwm模式,采取8分频
}
void pwm(void)
{ uchar wide;
char temp;
pwm0_init();
while(1)
{delay_ms(50);
if(++wide==255)
{ wide=0;
}
OCR0=wide;
}
}
struct PID //定义PID函数
{
unsigned int SetPoint; // 设定目标 Desired Value
unsigned int Proportion; // 比例常数 Proportional Const
unsigned int Integral; // 积分常数 Integral Const
unsigned int Derivative; // 微分常数 Derivative Const
unsigned int LastError; // Error[-1]
unsigned int PrevError; // Error[-2]
unsigned int SumError; // Sums of Errors
};
struct PID spid; // PID Control Structure
long rout; // PID Response (Output)
long rin; // PID Feedback (Input)
void PID_Init(struct PID *pp)
{
memset ( pp,0,sizeof(struct PID)); //全部初始化为0
}
unsigned int PID_Calc( struct PID *pp, unsigned int NextPoint )
{
unsigned int dError,Error;
Error = pp->SetPoint - NextPoint; // 偏差
pp->SumError += Error; // 积分
dError = pp->LastError - pp->
revError; // 当前微分
pp->
revError = pp->LastError;
pp->LastError = Error;
return (pp->
roportion * Error // 比例项
+ pp->Integral * pp->SumError // 积分项
+ pp->Derivative * dError); // 微分项
}
void main(void)
{
uint temp_max=320; //设定最大的上下限 注意tem放大了10倍了
uint temp_min=80;
uint temp_set;
ring_OUT;
ring_CLR;
init_1602();//显示器的初始化函数
temp_change();
delay_nms(2);
temp_change();
while(1)
{
temp_change();
dis_temp(get_temp());
display_1602(); //1602显示数据
init_key();
if(tem>=temp_max||tem<=temp_min)
{
ring_SET;//超过上下限时蜂鸣器就叫
}
else
{
ring_CLR;//未超过上下限时蜂鸣器不叫
if(tem>=temp_set*1.05)
{
//启动加热设备
}
if (tem<=temp_set*1.05)
{
//启动制冷设备
}
}
}
}