摘要
本系统以集成测温芯片DS18B20为核心,基于两个单片机的串口通信实现远程温控目的。设计综合利用单片机的可编程性,灵活利用其中的2个定时计数器,完成温度采集、运算控制、输出显示、主从机通信等功能。 DS18B20能够较高精度和较大范围的进行温度测量,保证了系统设计的精度要求;运算控制部分主要使用单片机小系统对采集的数据进行处理,方便快捷;输出显示部分使用八段式四位一体共阴极数码管实现,简单明了。系统性能指标均达到了设计要求。整个系统电路简单,操作方便,用户界面友好。
远程数字测温系统
实验目的:
1、了解电子系统的设计方法
2、学习 DS18B20 数字温度传感器的测温原理;
3、掌握MCS-51单片机工作原理及其应用技术;
4、掌握串口通信协议及其编程方法。
5、学会用EDA软件(Protel99se或ORCAD)进行电路原理图和PCB图的绘制。
6、学习用PSPICE、 Multisim 8等仿真软件进行电路设计和仿真。
实验任务和要求:
(一)实验任务
制作一个数字测温系统,包括主站和从站部分,主从站控制核心均采用AT89S51单片机。
基本部分:
1、从站单片机采用DS18B20数字温度传感器测量温度,数码管实时显示温度,测温范围:0°C~100°C,误差:±0.1°C。
2、主站能够接收从站传送过来的温度并同步显示。
3、主站能够设定一个阈值温度,当温度达到或超过此阈值温度时发出声光报警信号,并且能够手动解除报警。
发挥部分:
1、从站可以检测多点温度,数码管顺序显示各点温度,显示时间≧3秒,并且能够设定报警温度范围,当某点温度低于温度下限时LED1亮,当某点温度高于温度上限时LED2亮。
2、主从站之间采用232/485通信协议。
系统方案设计与选择:
1、系统总体构成图和系统功能
2、控制单元电路
控制部分的选择较多,但是作为温度计,在成本上最合适的是单片机,对于题目要求的控制能力也能胜任,利用AT89S52自身强大的功能和优异的可扩展性,配上电路实验箱、四位一体数码管和按键等少量外围电路,就能搭建合适本次实验的小系统。从而大大缩短设计流程,把设计的重点放在温度探测单元,串行通信协议两个部分。
3、稳压电源电路
89S52以及74LS04等芯片都工作在+5V的电压之下,因此可以直接采用电路实验箱,省略外围电源的设计。
4、温度探测单元
方案一:本设计是测温电路,可以使用热敏电阻之类的器件利用其感温效应,将被测温度变化的电压或电流采集过来,进行A/D转换后,就可以用单片机进行数据的处理,在显示电路上,就可以将被测温度显示出来,这种设计需要用到A/D转换电路,感温电路比较麻烦。
方案二:考虑到用温度传感器。在单片机电路设计中,大多都是使用传感器,所以可以用实验室熟悉的单片机小系统,采用一只温度传感器DS18B20,直接读取被测温度值,再进行转换,就可以满足设计要求。该数字温度计提供12位(二进制)温度读数,指示器件的温度。信息经过单线接口送 入DSl8B20或从DSl8B20送出,因此从主机CPU到DSl820仅需一条线(和地线)。DSl8B20的电源可以由数据线本身提供而不需要外部电源。因为每一个DSl820在出厂时已经给定了唯一的序号,因此任意多个DSl8B20可以存放在同一条单线总线上。这允许在许多不同的地方放置温度敏感器件。DSl8B20的测量范围从-55到+125,可在l s(典型值)内把温度变换成数字。
比较以上两种方案,方案二其电路比较简单,各项功能通过编程容易实现,能达到实验提出的精度要求,故采用方案二。
5、键盘控制功能
合理设计键盘功能,以实现更加人性化的人机交互。初步将键盘功能定为复位、温度报警上下限设定、警报解除等功能。
6、时间—温度数字显示
考虑到对系统成本的控制,我们选择使用八段式四位一体共阴极数码管。用数码管显示,设计简洁,实现容易。由于本次实验要求显示的数据量不是很大,数码管足以达到设计要求。
7、温度控制报警输出
如果当前温度跳出设定上下限范围,系统会自动做出一些反应,即由当前温度产生报警输出,如屏幕显示、声光、语音等。基于本系统外围电路的设计,我们选用LED和蜂鸣器组成声光报警系统。
8、串行通信单元
MCS-51系列的单片机都带有串口,在两个MCS-51单片机应用系统相距较近的情况下直接利用串口进行互连就可以了。如果通信距离较长,则可以利用RS-232或者RS-485等标准总线接口来进行双机通信。
理论分析与计算
为达到精度要求,我们采用DS18B20的12位存储温度值,最高位为符号。位下图为18B20的温度存储方式,负温度S=1,正温度S=0。
各位的权值如下:
Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 | |
LSByte | 2^3 | 2^2 | 2^1 | 2^0 | 2^-1 | 2^-2 | 2^-3 | 2^-4 |
Bit15 | Bit14 | Bit13 | Bit12 | Bit11 | Bit10 | Bit9 | Bit8 | |
MSByte | S | S | S | S | S | 2^6 | 2^5 | 2^4 |
当我们选用12位存储温度值时,精度可以达到0.0625,如果通过更精确的测温装置,用最小二乘法拟和出误差曲线,可校正误差,进一步提高精度。
系统设计
1、整体设计
初始化后,单片机控制单元从DS18B20中读取时间和温度,调用数码管显示出来;在主循环中不断扫描键盘,通过按键可实现不同的功能,分别对应调超温报警上下限、解除报警的功能。
2、测温模块设计
DS18B20 的内部结构框图如下所示。DS18B20内部结构主要由4部分组成:64位光刻的ROM,温度传感器,非挥发的温度报警触发器TH和TL和配置寄存器。
DS18B20的读写时序:
DS18B20所有通信都必须先初始化。初始化时,单片机控制发出复位脉冲,DS18B20在其后发出存在脉冲,存在脉冲让控制器知道该DS18B20已在总线上并作好了操作准备。
写流程:为产生写‘0’时序,在将总线拉低后,总线控制器在整个时序内必须持续总线为60us。为产生写‘1’时序,在将总线拉低后,总线控制器必须在15us内释放总线。
读流程:读时序从控制设备将总线拉低1us后释放总线开始,所有读时序必须最少持续60us。每个读时序之间必须有至少1us的恢复时间,
基于上述特性,在单片机控制下的DS18B20读温度的子程序设计流程如图:
3、串行通信过程
(1)接收数据的过程。
在进行通信时,当CPU允许接收时,即SCON的REN位设置1时,外界数据通过引脚RXD(P3.0)串行输入,数据的最低位首先进入移位寄储器,一帧接收完毕后再并行送入接收数据缓冲寄存器SBUF中,同时将接收控制位即中断标志位RI置位,向CPU发出中断请求。
CPU响应中断后读取输入的数据,同时用软件将RI位清除,准备开始下一帧的输入过程,直到所有数据接受完毕。
(2)发送数据的过程。
CPU要发送数据时,将数据并行写入发送数据缓冲寄存器SBUF中,同时启动数据由TXD(P3.1)引脚串行发送,当一帧数据发送完毕,即发送缓冲器空时,由硬件自动将发送中断标志位TI置位,向CPU发送中断请求。CPU响应中断后用软件将TI位清除,同时又将下一帧写入SBUF,重复上述过程直到所有数据发送完毕。
系统软件设计
系统软件基于单片机开发系统keilC51开发,采用C语言,本系统软件流程图如下图所示,单片机通过扫描用户键盘输入进入相应功能模块:
测试方法与测试结果
1、温度显示及报时测试
系统加电后,四位一体数码管第一位显示多路温度探头的序号,后三位显示相应的温度,整数部分两位,小数部分一位,精确到0.1。每三秒自动切换一路温度探头,并显示相应的温度。用手摸住DS18B20芯片,液晶温度数字发生变化,显示随环境温度增加,频率为1Hz,表示温度升高。主站和从站温度同步显示,没有延迟现象。各项设计达到指标要求。
2、温度设定及报警测试
按一下左键为报警高温上限值十位设定,按两下左键为报警高温上限值个位设定,按三下左键为报警低温下限值十位设定,按四下左键为报警低温下限值个位设定,按五下左键回到正常显示模式。数码管显示报警温度上限,温度上下调节正常,范围在0-99摄氏度。当温度超出温限时,高温警报与低温警报各自对应的二极管灯亮,同时蜂鸣器发出报警信号。按下主站的报警解除温度控制模块及报警设计达到指标要求。
附录:
源程序:主站:
#include
#include
#include
#define uchar unsigned char
sbit WARNLED = P3^7;
#define COUNT0 (-8000)
int x,count = 0,temp;
uchar u8,u1,mhigh,mlow,shigh,slow,state;
bit ks,warn;
uchar led[4]={0};
uchar code segtab[19] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x76,0x38,0x7c,0x5e,0x79,0x71,0x73,0x00,0xff}; //七段码段码表
// "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "H", "L", "B", "D", "E", "F", "P" ,"black"
uchar lednum = 0;
bit light,ws;
sbit first_key= P1^0 ; sbit second_key= P1^1; bit first_getkey = 0,second_getkey = 0,control_readkey = 0; bit getkey1= 0,getkey2 = 0; /*******************************************
键盘扫描函数
原型: void readkey(void);
功能: 当获得有效按键时,令getkey=1,keynum为按键值
*****************************************/
void readkey(void)
{
if( first_key != 1) {
if(first_getkey == 0)
{
first_getkey = 1;
}
else {
if(keyon1 == 0)
{
getkey1 = 1; keyon1 = 1; }
}
}
else
{
first_getkey = 0;
keyon1 = 0; }
if( second_key != 1) {
if(second_getkey == 0)
{
second_getkey = 1;
}
else {
if(keyon2 == 0) / {
getkey2 = 1; keyon2 = 1; }
}
}
else
{
second_getkey = 0;
keyon2 = 0; }}
void leddisp(void)
{
P2 = (1 << lednum);
if (state == 0 && lednum == 2) P0 = segtab[led[lednum]] | 0x80;
else P0 = segtab[led[lednum]];
if (lednum == 3) lednum = 0; else lednum++;
}
void fj()
{
uchar j;
for (j = 0; j < 4; j++)
{
led[3-j] = x % 10;
x /= 10;
}
}
void INTT0() interrupt 1
{
TH0 = COUNT0 >> 8; //定时器中断时间间隔 4ms
TL0 = COUNT0 & 0xff;
if (count == 750)
{
count = 0;
if (state == 0)
{
x = temp;
fj();
//led[3] = 17;
}
}
else count++;
if (state)
{
if (state == 1)
x = shigh;
else
x = slow;
fj();
if (count % 50 == 0)
{
light = ~light;
}
if (light) led[3-(uchar)ks]=17;
led[0] = 9 + state;
led[1] = 17;
}
leddisp();
if(control_readkey == 1) //每两次定时中断扫描一次键盘
{
readkey();
}
control_readkey = !control_readkey;
}
void time_delay(unsigned char time) //延时程序要求大于10us
{
time=time-10;
time=time/6;
while(time!=0)
time--;
}
void send_data(uchar d8,uchar d1)
{
TB8 = d1;
SBUF = d8;
while (!TI);
TI = 0;
}
void recieve_data()
{
while (!RI);
u8 = SBUF;
u1 = RB8;
RI = 0;
}
void INTES() interrupt 4
{
u8 = SBUF;
u1 = RB8;
RI = 0;
if (u8 & 0x80)
{
shigh = u8 & 0x7f;
recieve_data();
slow = u8 & 0x7f;
}
else
{
temp = u8;
recieve_data();
temp = temp * 10 + u8;
count = 0;
}
}
void main()
{
TMOD = 0x01;
SCON = 0x90;
PCON = 0x80;
IP = 0X02;
TH0 = COUNT0 >> 8; //定时器中断时间间隔 4ms
TL0 = COUNT0 & 0xff;
TCON = 0x10;
ET0 = 1;
EA = 1; ES = 1; RI = 0; TI = 0;
count = 0;
WARNLED = 0;
shigh = mhigh = 99; slow = mlow = 0;
state = 0; ks = 0; warn = 0; ws = 1;
while (1)
{
if (temp > shigh * 10)
{
if (ws) WARNLED = 1;
else WARNLED = 0;
}
else
if (temp < slow * 10)
{
if (ws) WARNLED = 1;
else WARNLED = 0;
}
else
{
WARNLED = 0;
}
if (getkey1)
{
getkey1 = 0;
if (!ks)
{
state += 1;
if (state >= 3) {state = 0; EA = 0; send_data(shigh | 0x80,1); time_delay(10); send_data(slow | 0x80,1); EA = 1; x = temp; fj(); ks = ~ks;}
}
ks = ~ks;
}
if (getkey2)
{
getkey2 = 0;
if (state == 0) ws = ~ws;
if (state == 1) {
if (ks)
{
if (shigh / 10 == 9) shigh -= 90;
else shigh += 10;
}
else
{
if (shigh % 10 == 9) shigh -= 9;
else shigh += 1;
}
}
if (state == 2) {
if (ks)
{
if (slow / 10 == 9) slow -= 90;
else slow += 10;
}
else
{
if (slow % 10 == 9) slow -= 9;
else slow += 1;
}
}
}
}
}
从站:
#include
#include
#include
#include
#define uchar unsigned char
#define COUNT0 (-8000)
#define BUSY (DQ==0)
sbit DQ = P3^2;
sbit LED1 = P2^7;
sbit LED2 = P3^7;
sbit L0 = P2^0;
sbit L1 = P2^1;
sbit L2 = P2^2;
sbit L3 = P2^3;
uchar TMP;//温度整数部分
uchar TMP_d; //温度小数部分
uchar f; //标志位 0正 1负
int x,count = 0,temp;
uchar u8,u1,mhigh,mlow,shigh,slow,state;
bit ks,warn;
uchar led[4]={0};
uchar code segtab[19] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x76,0x38,0x7c,0x5e,0x79,0x71,0x73,0x00,0xff}; //七段码段码表
// "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "H", "L", "B", "D", "E", "F", "P" ,"black"
uchar lednum = 0;
bit light,ws;
/*扫描键盘使用的变量 */
sbit first_key= P1^0 ; //键盘第一行控制
sbit second_key= P1^1; //键盘第二行控制
bit first_getkey = 0,second_getkey = 0,control_readkey = 0; //读键盘过程中的标志位
bit getkey1= 0,getkey2 = 0;//获得有效键值标志位 等于1时代表得到一个有效键值
bit keyon1 = 0,keyon2 = 0 ; //防止按键冲突标志位
bit flag;
unsigned char code ROM[16]={0x28,0x05,0xC0,0x27,0x01,0x00,0x00,0x8A,0x28,0x3B,0x66,0x27,0x01,0x00,0x00,0xDB};
void time_delay(unsigned char time) //延时程序要求大于10us
{
time=time-10;
time=time/6;
while(time!=0)
time--;
}
void nNop(unsigned char i)
{
for(;i>0;i--);
}
void ds_reset(void) //ds18b20初始化
{
unsigned char i=0;
unsigned char idata count=0;
DQ=0;
time_delay(200);
time_delay(250);
time_delay(250);
time_delay(200);
DQ=1;
return;
}
void check_pre(void) //检查器件是否存在
{
while(DQ);
while(~DQ);
time_delay(30);
}
void wr_ds18b20(char d) // 写字节到ds18b20
{
signed char idata i=0;
unsigned char idata k;
bit q;
for(k=1;k<=8;k++)
{
q=(bit)(d & 0x01);
d=d>>1;
if(q==1)
{
DQ=0;
_nop_();
_nop_();
_nop_();
_nop_();
DQ=1;
time_delay(120);
}
else
{
DQ=0;
time_delay(100);
DQ=1;
_nop_();
_nop_();
_nop_();
_nop_();
}
}
}
bit read_bit_ds18b20(void)
{
idata char i=0;
bit j;
DQ=0;
_nop_();
_nop_();
DQ=1;
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
j=DQ;
time_delay(100);
return j;
}
unsigned char read_ds18b20()
{
unsigned char idata i,t,d=0;
for(i=1;i<=8;i++)
{
t=read_bit_ds18b20(); //假设先送高位被读
d=t<<(i-1)|d;
}
return d;
}
void get_temperature(void) //获得温度值 整数+小数
{
unsigned char idata a=0,b=0;
unsigned char idata i,j;
ds_reset();
check_pre();
wr_ds18b20(0xcc);
wr_ds18b20(0x44);
while(BUSY);
ds_reset();
check_pre();
wr_ds18b20(0xcc);
wr_ds18b20(0xbe);
a=read_ds18b20();
b=read_ds18b20();
i=b;
i=(i>>4);
if(i==0)
{
f=0;
TMP=((a>>4)|(b<<4));
a=(a&0x0f);
TMP_d=a;
}
else
{
f=1;
a=~a;
a=(a+1);
b=~b;
b=(b+1);
j=a;
a=a>>4;
b=b<<4;
TMP=(a|b);
j=(j&0x0f);
TMP_d=j;
}
}
/*******************************************
键盘扫描函数
原型: void readkey(void);
功能: 当获得有效按键时,令getkey=1,keynum为按键值
*******************************************
void readkey(void)
{
if( first_key != 1) {
if(first_getkey == 0)
{
first_getkey = 1;
}
else {
if(keyon1 == 0)
getkey1 = 1; keyon1 = 1; }
}
}
else
{
first_getkey = 0;
keyon1 = 0; }
if( second_key != 1) {
if(second_getkey == 0)
{
second_getkey = 1;
}
else {
if(keyon2 == 0)
{
getkey2 = 1; keyon2 = 1; }
}
}
else
{
second_getkey = 0;
keyon2 = 0; }
}
void leddisp(void)
{
switch (lednum)
{
case 0: L3 = 0; L0 = 1; break;
case 1: L0 = 0; L1 = 1; break;
case 2: L1 = 0; L2 = 1; break;
case 3: L2 = 0; L3 = 1; break;
}
if (state == 0 && lednum == 2) P0 = segtab[led[lednum]] | 0x80;
else P0 = segtab[led[lednum]];
if (lednum == 3) lednum = 0; else lednum++;
}
void fj()
{
uchar j;
for (j = 0; j < 4; j++)
{
led[3-j] = x % 10;
x /= 10;
}
}
void send_data(uchar d8,uchar d1)
{
TB8 = d1;
SBUF = d8;
while (!TI);
TI = 0;
}
void INTT0() interrupt 1
{
TH0 = COUNT0 >> 8; //定时器中断时间间隔 4ms
TL0 = COUNT0 & 0xff;
if (count == 750)
{
count = 0;
flag = 1;
if (state == 0)
{
x = temp;
fj();
//led[3] = 17;
}
}
else count++;
if (state)
{
if (state == 1)
x = shigh;
else
x = slow;
fj();
if (count % 50 == 0)
{
light = ~light;
}
if (light) led[3-(uchar)ks]=17;
led[0] = 9 + state;
led[1] = 17;
}
leddisp();
if(control_readkey == 1) //每两次定时中断扫描一次键盘
{
readkey();
}
control_readkey = !control_readkey;
}
void recieve_data()
{
while (!RI);
u8 = SBUF;
u1 = RB8;
RI = 0;
}
void INTES() interrupt 4
{
u8 = SBUF;
u1 = RB8;
RI = 0;
if (u8 & 0x80)
{
shigh = u8 & 0x7f;
recieve_data();
slow = u8 & 0x7f;
}
}
void get()
{
TMP = rand()%100;
TMP_d = rand()%10;
}
void main()
{
TMOD = 0x01;
SCON = 0x90;
PCON = 0x80;
IP = 0X02;
TH0 = COUNT0 >> 8; //定时器中断时间间隔 4ms
TL0 = COUNT0 & 0xff;
//TH1 = TL1 = 0xfc;
TCON = 0x10; //TR1 = 1;
ET0 = 1;
EA = 1; ES = 1; RI = 0; TI = 0;
count = 0;
shigh = mhigh = 99; slow = mlow = 0;
state = 0; ks = 0; warn = 0;
LED1 = 0; LED2 = 0;
while (1)
{
if (flag)
{
get_temperature();
//get();
EA = 0;
send_data(TMP,0);
time_delay(10);
send_data(TMP_d,0);
EA = 1;
flag = 0;
count = 0;
}
temp = TMP * 10 + TMP_d;
if (temp > shigh * 10)
{
LED2 = 0;
LED1 = 1;
}
else if (temp < slow * 10)
{
LED2 = 1;
LED1 = 0;
}
else
{
LED1 = LED2 = 0;
}
if (getkey1)
{
getkey1 = 0;
if (!ks)
{
state += 1;
if (state >= 3) {state = 0; EA = 0; send_data(shigh | 0x80,1); time_delay(10); send_data(slow | 0x80,1); EA = 1; count = 750; ks = ~ks;}
}
ks = ~ks;
}
if (getkey2)
{
getkey2 = 0;
if (state == 1) {
if (ks)
{
if (shigh / 10 == 9) shigh -= 90;
else shigh += 10;
}
else
{
if (shigh % 10 == 9) shigh -= 9;
else shigh += 1;
}
}
if (state == 2) {
if (ks)
{
if (slow / 10 == 9) slow -= 90;
else slow += 10;
}
else
{
if (slow % 10 == 9) slow -= 9;
else slow += 1;
} } }
}}