概述
【实验目的】
- 学习DS18B20温度传感器的编程结构。
- 了解温度测量的原理。
- 掌握PID控制原理及实现方法。
- 加深C51编程语言的理解和学习。
【实验内容】
- 掌握使用传感器测量与控制温度的原理与方法,使用C51语言编写实现温度控制的功能,使用超声波/温度实验板测量温度,将温度测量的结果(单位为摄氏度)显示到液晶屏上。
- 编程实现测量当前教室的温度,显示在LCM液晶显示屏上。
- 通过S1设定一个高于当前室温的目标温度值。
- 通过S2设定一个低于S1目标温度的新目标温度值。
- 编程实现温度的控制,将当前温度值控制到目标温度值并稳定的显示。
【实验原理】
- 本实验使用的DS18B20是单总线数字温度计,测量范围从—55℃到+125℃,增量值为0.5 ℃。
- 用于贮存测得的温度值的两个8位存贮器RAM 编号为0号和1号。 1号存贮器存放温度值的符号,如果温度为负(℃),则1号存贮器8位全为1,否则全为0。 0号存贮器用于存放温度值的补码LSB(最低位)的1表示0.5℃ 。
- 将存贮器中的二进制数求补再转换成十进制数并除以2,就得到被测温度值。
- 温度检测与控制系统由加热灯泡,温度二极管,温度检测电路,控制电路和继电器组成。温度二极管和加热灯泡封闭在一个塑料保温盒内,温度二极管监测保温盒内的温度,用温控实验板内部的A/D转换器ADC7109检测二极管两端的电压,通过电压和温度的关系,计算出盒内空气的实际温度。
- 本实验使用STC89C516RD+单片机实验板。单片机的P1.4与DS18B20的DQ引脚相连,进行数据和命令的传输。 单片机的P1.1连接热电阻。当P1.1为高电平时,加热热电阻。 温度控制的方法采用PID控制实现
【DS18B20介绍】
【原理图】
//字模方式:列行式,逆向,16*16
#include<reg52.h>
#include<intrins.h> //声明本征函数库
#include<math.h>
typedef unsigned char uchar;
typedef unsigned int uint;
sbit s1 = P3^6;
sbit s2 = P3^7;
sbit RS=P3^5;//寄存器选择信号
sbit RW=P3^4;//读写操作选择信号,高电平读,低电平写
sbit EN=P3^3;//使能信号
sbit CS1=P1^7;//左半屏显示信号,低电平有效
sbit CS2=P1^6;//右半屏显示信号,低电平有效
sbit DQ=P1^4;
sbit up=P1^1;
uchar Ek,Ek1,Ek2;
uchar Kp,Ki,Kd;
uint res,Pmax;
uint xx=0; //页面
uint times=0;//延时函数
void delay_us(uchar n)
{
while (n--)
{
_nop_();
_nop_();
}
}
unsigned char code shu[10][32]={
{0x00,0x00,0x00,0xF8,0x04,0x02,0x02,0x02,0x02,0x02,0x04,0xF8,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x1F,0x20,0x40,0x40,0x40,0x40,0x40,0x20,0x1F,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x08,0x04,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x7F,0x40,0x40,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x18,0x04,0x02,0x02,0x02,0x82,0x82,0x84,0x78,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x78,0x44,0x42,0x41,0x41,0x40,0x40,0x40,0x70,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x0C,0x02,0x02,0x02,0x82,0x82,0x42,0x22,0x1C,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x30,0x40,0x40,0x40,0x40,0x40,0x41,0x22,0x1C,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x80,0x60,0x1C,0x02,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x0C,0x0A,0x09,0x08,0x48,0x48,0x7F,0x48,0x48,0x08,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0xFE,0x82,0x42,0x42,0x42,0x42,0x42,0x82,0x02,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x31,0x40,0x40,0x40,0x40,0x40,0x40,0x20,0x1F,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0xF8,0x04,0x82,0x82,0x82,0x82,0x82,0x04,0x18,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x1F,0x21,0x40,0x40,0x40,0x40,0x40,0x21,0x1E,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x0E,0x02,0x02,0x02,0x02,0x82,0x42,0x32,0x0E,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x70,0x0E,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x38,0x44,0x82,0x82,0x82,0x82,0x82,0x44,0x38,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x1E,0x21,0x40,0x40,0x40,0x40,0x40,0x21,0x1E,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x78,0x84,0x02,0x02,0x02,0x02,0x02,0x84,0xF8,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x18,0x20,0x41,0x41,0x41,0x41,0x41,0x20,0x1F,0x00,0x00,0x00,0x00}
};
unsigned char code shiji[2][32]={
{0x10,0x0C,0x04,0x84,0x14,0x64,0x05,0x06,0xF4,0x04,0x04,0x04,0x04,0x14,0x0C,0x00,
0x04,0x84,0x84,0x44,0x47,0x24,0x14,0x0C,0x07,0x0C,0x14,0x24,0x44,0x84,0x04,0x00},
{0x00,0xFE,0x22,0x5A,0x86,0x00,0x20,0x22,0x22,0x22,0xE2,0x22,0x22,0x22,0x20,0x00,
0x00,0xFF,0x04,0x08,0x07,0x10,0x0C,0x03,0x40,0x80,0x7F,0x00,0x01,0x06,0x18,0x00}
};
unsigned char code mubiao[2][32]={
{0x00,0x00,0xFE,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0xFE,0x00,0x00,0x00,
0x00,0x00,0xFF,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0xFF,0x00,0x00,0x00},
{0x10,0x10,0xD0,0xFF,0x90,0x10,0x20,0x22,0x22,0x22,0xE2,0x22,0x22,0x22,0x20,0x00,
0x04,0x03,0x00,0xFF,0x00,0x13,0x0C,0x03,0x40,0x80,0x7F,0x00,0x01,0x06,0x18,0x00}
};
unsigned char code du[]=
{0x00,0x00,0xFC,0x24,0x24,0x24,0xFC,0x25,0x26,0x24,0xFC,0x24,0x24,0x24,0x04,0x00,
0x40,0x30,0x8F,0x80,0x84,0x4C,0x55,0x25,0x25,0x25,0x55,0x4C,0x80,0x80,0x80,0x00};
void delay(uint i)//延时子程序,i最大256,超过256部分无效
{
while(--i);
}
void Read_busy() //等待BUSY=0
{ //busy p2^7
P2=0xff;
RS=0;//RS/RW=0/1,读取状态字指令
RW=1;
EN=1;//控制LCM开始读取
while(P2&0x80);//判忙,循环等待P2.7=0.
EN=0;//控制LCM读取结束
}
void write_command(uchar value)//设置地址或状态
{
P2=0xff;
Read_busy();//等待LCM空闲
RS=0;//RS/RW=00,设置LCM状态或选择地址指令
RW=0;
P2=value;//设置
EN=1;//控制LCM开始读取
delay(100);
EN=0;//控制LCM读取结束
}
void write_data(uchar value)//写数据到显示存储器
{
P2=0xff;
Read_busy();
RS=1;// RS/RW=10,写数据指令
RW=0;
P2=value;//写数据
EN=1;
delay(100);
EN=0;
}
void Set_column(uchar column)//选择列地址(Y)
{
column=column&0x3f;//高两位清零0,保留后六位的地址
column=0x40|column;//01000000|column,根据后六位选择列地址
write_command(column);
}
void Set_line(uchar startline)//显示起始行设置
{
startline=0xC0|startline;// 11000000|startline, 根据startline后六位选择起始行
write_command(startline);
}
void Set_page(uchar page)//选择页面地址(X)
{
page=0xb8|page;//10111000|page,根据page后三位确定选择的页
write_command(page);
}
void display(uchar ss,uchar page,uchar column,uchar *p)
{//ss选择屏幕,page选择页面,column选择列,P是要显示的数据数组的指针
uchar i;
switch(ss)
{
case 0: CS1=1;CS2=1;break; //全屏
case 1: CS1=1;CS2=0;break; //左半屏
case 2: CS1=0;CS2=1;break; //右半屏
default: break;
}
page=0xb8|page;//10111000|page,根据page后三位确定所选择的页
write_command(page);
column=column&0x3f;//高两位清0,保留后六位的列地址
column=0x40|column;//01000000|column,根据后六位选择列地址
write_command(column);
for(i=0;i<16;i++)//列地址自动+1
{
write_data(p[i]);//写前16个长度数据
}
page++;
write_command(page);
// column--;
write_command(column);
for(i=0;i<16;i++)//列地址自动+1
{
write_data(p[i+16]);//写后16个数据长度
}
}
void SetOnOff(uchar onoff)//显示开关设置
{
onoff=0x3e|onoff;//00111110|onoff,根据最后一位设置开关触发器状态,从而控制显示屏的显示状态
write_command(onoff);
}
void ClearScreen()//清屏
{
uchar i,j;
CS2=1;
CS1=1;
for(i=0;i<8;i++)
{
Set_page(i); //依次选择个页面
Set_column(0);//选择第0列
for(j=0;j<64;j++)//列地址具有自动加一功能,依次对页面的64列写入0从而清屏
{
write_data(0x00);
}
}
}
void InitLCD()//初始化
{
Read_busy();
CS1=1;CS2=1;
SetOnOff(0);
CS1=1;CS2=1;
SetOnOff(1);//打开显示开关
CS1=1;CS2=1;
ClearScreen();//清屏
Set_line(0);//设置显示起始行
}
bit DS_init()
{
bit flag;
DQ = 0;
delay_us(255); //500us以上
DQ = 1; //释放
delay_us(40); //等待16~60us
flag = DQ;
delay_us(150);
return flag; //成功返回0
}
uchar read() //byte
{
uchar i;
uchar val = 0;
for (i=0; i<8; i++)
{
val >>= 1;
DQ = 0; //拉低总线产生读信号
delay_us(1);
DQ = 1; //释放总线准备读信号
delay_us(1);
if (DQ) val |= 0x80;
delay_us(15);
}
return val;
}
void write(char val) //byte
{
uchar i;
for (i=0; i<8; i++)
{
DQ = 0; //拉低总线产生写信号
delay_us(8);
val >>= 1;
DQ = CY;
delay_us(35);
DQ = 1;
delay_us(10);
}
}
void PID()
{
uchar Px,Pp,Pi,Pd,a,b,c;
uint count;
Pp = Kp*(Ek-Ek1);
Pi = Ki*Ek;
Pd = Kd*(Ek-2*Ek1+Ek2);
Px = Pp+Pi+Pd;
res = Px;
a=res/100;
b=res%100/10;
c=res%10;
display(1,4,2*16,shu[a]);delay(255);
display(1,4,3*16,shu[b]);delay(255);
display(2,4,0*16,shu[c]);delay(255);
Ek2 = Ek1;
Ek1 = Ek;
count = 0;
if(res>Pmax)
res =Pmax ;
while((count++)<=res)
{
up = 1;
delay_us(250);
delay_us(250);
}
while((count++)<=Pmax)
{
up = 0;
delay_us(250);
delay_us(250);
}
}
void main()
{
uchar aim,low,high,b,c;
uint result;
InitLCD();
Set_line(0);
aim = 40;
Kp = 4;
Ki = 5;
Kd = 2;
Pmax = 5;
Ek1 = 0;
Ek2 = 0;
res = 0;
while(1)
{
if(s1 == 0)
aim++;
if(s2 == 0)
aim--;
while(DS_init());
write(0xcc); //跳过ROM命令
write(0x44); //温度转换命令
delay(600);
while(DS_init());
write(0xcc);
write(0xBE); //读DS 温度暂存器命令
low = read(); //采集温度
high = read();
delay(255);
result = high;
result <<= 8;
result |= low;
result >>= 4 ; //result /= 16;
Ek = aim - result;
b=result/10;
c=result%10;
display(1,0,0*16,shiji[0]);delay(255);
display(1,0,1*16,shiji[1]);delay(255);
display(1,0,3*16,shu[b]);delay(255);
display(2,0,0*16,shu[c]);delay(255);
display(2,0,1*16,du);delay(100);
b=aim/10;
c=aim%10;
display(1,2,0*16,mubiao[0]);delay(255);
display(1,2,1*16,mubiao[1]);delay(255);
display(1,2,3*16,shu[b]);delay(255);
display(2,2,0*16,shu[c]);delay(255);
display(2,2,1*16,du);delay(100);
if(aim>=result)
PID();
else
up = 0;
}
}
【思考题】
1. 进行精确的延时的程序有几种方法?各有什么优缺点?
答:实现延时通常有两种方法:一种是硬件延时,要用到定时器/计数器,这种方法可以提高CPU的工作效率,也能做到精确延时;另一种是软件延时,这种方法主要采用循环体进行。
(1)使用定时器/计数器实现精确延时
单片机系统一般常选用11.059 2 MHz、12 MHz或6 MHz晶振。第一种更容易产生各种标准的波特率,后两种的一个机器周期分别为1 μs和2 μs,便于精确延时。本程序中假设使用频率为12 MHz的晶振。最长的延时时间可达216=65 536 μs。若定时器工作在方式2,则可实现极短时间的精确延时;如使用其他定时方式,则要考虑重装定时初值的时间(重装定时器初值占用2个机器周期)。
在实际应用中,定时常采用中断方式,如进行适当的循环可实现几秒甚至更长时间的延时。
(2)软件延时与时间计算
2.1 短暂延时
可以在C文件中通过使用带_NOP_( )语句的函数实现,定义一系列不同的延时函数,如Delay10us( )、Delay25us( )、Delay40us( )等存放在一个自定义的C文件中,需要时在主程序中直接调用。如延时10 μs的延时函数可编写如下:
void Delay10us( ) {
_NOP_( );
_NOP_( );
_NOP_( );
_NOP_( );
_NOP_( );
_NOP_( );
}
可以把这一函数当作基本延时函数,在其他函数中调用,即嵌套调用,以实现较长时间的延时。
2.2 在C51中嵌套汇编程序段实现延时
在C51中通过预处理指令#pragma asm和#pragma endasm可以嵌套汇编语言语句。用户编写的汇编语言紧跟在#pragma asm之后,在#pragma endasm之前结束。
如:#pragma asm
…
汇编语言程序段
…
#pragma endasm
延时函数可设置入口参数,可将参数定义为unsigned char、int或long型。根据参数与返回值的传递规则,这时参数和函数返回值位于R7、R7R6、R7R6R5中。
2.3 使用示波器确定延时时间
编写一个实现延时的函数,在该函数的开始置某个I/O口线如P1.0为高电平,在函数的最后清P1.0为低电平。在主程序中循环调用该延时函数,通过示波器测量P1.0引脚上的高电平时间即可确定延时函数的执行时间。方法如下:
sbit T_point = P1^0;
void Dly1ms(void) {
unsigned int i,j;
while (1) {
T_point = 1;
for(i=0;i<2;i++){
for(j=0;j<124;j++){;}
}
T_point = 0;
for(i=0;i<1;i++){
for(j=0;j<124;j++){;}
}
}
}
void main (void) {
Dly1ms();
}
2.4 使用反汇编工具计算延时时间
对于不熟悉示波器的开发人员可用Keil C51中的反汇编工具计算延时时间,在反汇编窗口中可用源程序和汇编程序的混合代码或汇编代码显示目标应用程序。在程序中加入“for (i=0;i<dlyt;i++) {;}”这一循环结构,首先选择build="" taget,然后单击start="" stop="" debug="" session按钮进入程序调试窗口,最后打开disassembly="" window,找出与这部分循环结构相对应的汇编代码。
优缺点:
定时器延时可以提高MCU工作效率,稳定性高,定时精确,程序移植性好。缺点是设置定时器本身需要消耗一定时间,要求延时较短的情况下不满足要求,且实现复杂。
软件定时的优点是实现简单,可实现任意时间长短的延时。缺点是不精确,严重依赖机器。
2. 参考其他资料,了解DS18B20的其他命令用法。
答:
最后
以上就是冷静野狼为你收集整理的【实验六】温度测量与控制的全部内容,希望文章能够帮你解决【实验六】温度测量与控制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复