云计算百科
云计算领域专业知识百科平台

分析一段DS18B20温度显示代码

主函数

#include "public.h"

void main()
{
u8 i=0;
int temp_value;
u8 temp_buf[5];

ds18b20_init();//初始化DS18B20

while(1)
{
i++;
if(i%50==0)//间隔一段时间读取温度值,间隔时间要大于温度传感器转换温度时间
temp_value=ds18b20_read_temperture()*10;//保留温度值小数后一位
if(temp_value<0)//负温度
{
temp_value=-temp_value;
temp_buf[0]=0x40;//显示负号
}
else
temp_buf[0]=0x00;//不显示
temp_buf[1]=gsmg_code[temp_value/1000];//百位
temp_buf[2]=gsmg_code[temp_value%1000/100];//十位
temp_buf[3]=gsmg_code[temp_value%1000%100/10]|0x80;//个位+小数点
temp_buf[4]=gsmg_code[temp_value%1000%100%10];//小数点后一位
smg_display(temp_buf,4);
}
}

数码管函数

#define SMG_A_DP_PORTP0//使用宏定义数码管段码口

//定义数码管位选信号控制脚
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
//共阴极数码管显示0~F的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

void smg_display(u8 dat[],u8 pos)
{
u8 i=0;
u8 pos_temp=pos-1;

for(i=pos_temp;i<8;i++)
{
switch(i)//位选
{
case 0: LSC=1;LSB=1;LSA=1;break;
case 1: LSC=1;LSB=1;LSA=0;break;
case 2: LSC=1;LSB=0;LSA=1;break;
case 3: LSC=1;LSB=0;LSA=0;break;
case 4: LSC=0;LSB=1;LSA=1;break;
case 5: LSC=0;LSB=1;LSA=0;break;
case 6: LSC=0;LSB=0;LSA=1;break;
case 7: LSC=0;LSB=0;LSA=0;break;
}
SMG_A_DP_PORT=dat[i-pos_temp];//传送段选数据
delay_10us(100);//延时一段时间,等待显示稳定
SMG_A_DP_PORT=0x00;//消音
}
}

DS18B20函数

sbit DS18B20_PORT=P3^7;//DS18B20数据口定义

void ds18b20_reset(void)
{
DS18B20_PORT=0;//拉低DQ
delay_10us(75);//拉低750us,
DS18B20_PORT=1;//DQ=1
delay_10us(2);//20US
}

u8 ds18b20_check(void)
{
u8 time_temp=0;

while(DS18B20_PORT&&time_temp<10)
{
time_temp++;
delay_10us(1);
}
if(time_temp>=10)return 1;
else time_temp=0;
while((!DS18B20_PORT)&&time_temp<20)
{
time_temp++;
delay_10us(1);
}
if(time_temp>=20)return 1;
return 0;
}

u8 ds18b20_read_bit(void)
{
u8 dat=0;

DS18B20_PORT=0;
_nop_();_nop_();
DS18B20_PORT=1;
_nop_();_nop_();
if(DS18B20_PORT)dat=1;
else dat=0;
delay_10us(5);
return dat;
}

u8 ds18b20_read_byte(void)
{
u8 i=0;
u8 dat=0;
u8 temp=0;

for(i=0;i<8;i++)
{
temp=ds18b20_read_bit();
dat=(temp<<7)|(dat>>1);
}
return dat;
}

void ds18b20_write_byte(u8 dat)
{
u8 i=0;
u8 temp=0;

for(i=0;i<8;i++)
{
temp=dat&0x01;
dat>>=1;
if(temp)
{
DS18B20_PORT=0;
_nop_();_nop_();
DS18B20_PORT=1;
delay_10us(6);
}
else
{
DS18B20_PORT=0;
delay_10us(6);
DS18B20_PORT=1;
_nop_();_nop_();
}
}
}

void ds18b20_start(void)
{
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc);
ds18b20_write_byte(0x44);
}

u8 ds18b20_init(void)
{
ds18b20_reset();
return ds18b20_check();
}

float ds18b20_read_temperture(void)
{
float temp;
u8 dath=0;
u8 datl=0;
u16 value=0;

ds18b20_start();
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc);
ds18b20_write_byte(0xbe);

datl=ds18b20_read_byte();
dath=ds18b20_read_byte();
value=(dath<<8)+datl;

if((value&0xf800)==0xf800)
{
value=(~value)+1;
temp=value*(-0.0625);
}
else
{
temp=value*0.0625;
}
return temp;
}

以上代码实现的功能是,在动态数码管上显示4位温度值,包括3位整数和1位小数。

直接看代码可能有些繁杂,如果我们把它们按功能模块划分,就像摩托车包括核心部分——发动机、控制部分——龙头、驱动部分——轮胎,这样我们就可以集中一点。

由于DS18B20在温度检测起到十分重要的作用,本文几乎80%的内容都是介绍它,然后花费较少的笔墨介绍数码管按显示个十百以及小数点位的原理,最后会整合前面两者,再分析一下主函数的含义。前言就讲这么多,接下来我们来看看这篇文章的主角——DS18B20

一、DS18B20部分

什么是DS18B20?你的直觉一定是“温度传感器”,实际上它还有很多你不知道的本事。

首先,DS18B20并不全是我们熟知的三引脚,它还有其他两种形态

我们使用的DS18B20采用的TO-92封装,只有三个引脚。

其次,这块小小的温度传感器并不只负责测温,其内部还嵌入了一个AD转换器,也就是说,DS18B20在自己测得温度模拟量后,又自己将其转换成数字量输出,一气呵成。

最后,别看DS18B20引脚很少,实际上,可以更少。在寄生电源模式下,VDD引脚都可以不需要,只靠DQ引脚在接收数据的同时,顺便给DS18B20供电。


1.概述

DS18B20数字温度计,可提供9~12位分辨率的摄氏温度测量,可配置上下限报警功能(断电后不擦除),可通过DQ引脚与中央MCU连接。此外,每一块DS18B20都有一个独一无二的“身份证”——存储于ROM的8位家族码,理论上单片机可以同时控制2的8次方个DS18B20。

接下来我们把目光转向那个传奇引脚——DQ,它都能传输哪些数据?这取决于DS18B20里面放了什么数据

忽略左边的电路,右边就是DS18B20的三个存储器——64位的ROM、Scratchpad(暂存器)、EEPROM(非易失性存储器),我们看看都放了些什么在里面

64位光刻ROM

ROM只可读,不可写入。64位ROM分成三部分,分别是

字节内容说明
Byte 0 8位家族码(family code) 固定为0x28(DS18B20的标识)
Byte 1~6 48位唯一序列号 全球唯一的设备ID,由厂家光刻写入
Byte 7 8位CRC校验码 循环冗余校验冗余

单片机通过Read Rom[0x33]或Search Rom[0xf0]命令读取ROM中的64位数据

暂存器(Scatchpad)

这里是温度测量和通信的核心缓冲区,断电后数据丢失,所有温度转换结果和配置数据首先写入此处(注意这个“首先”)。暂存器共9个字节,72位,可分成五部分

字节地址名称功能是否可映射到EEPROM
Byte 0 温度LSB 温度值低四位(包含小数部分) 否(运行时数据)
Byte 1 温度MSB 温度值高 8 位(包含符号位) 否(运行时数据)
Byte 2 TH寄存器 高温报警阈值 是(字节 2 的 EEPROM 备份)
Byte 3 TL寄存器 低温报警阈值 是(字节 3 的 EEPROM 备份)
Byte 4 配置寄存器 分辨率设置 是(字节 4 的 EEPROM 备份)
Byte 5 保留
Byte 6 保留
Byte 7 保留
Byte 8 CRC字节 对Byte0~7的循环冗余校验码

注意到,上表中增加了一列“是否可映射到EEPROM”,可以把这个EEPROM理解成保险箱,我们把比较重要的数据存进去,断电后其他数据被清除,保险箱的数据依然保留,这样下次上电时这些数据就可以直接取出来用了。这个过程用DS自己的语言表述如下:

Write Scratchpad[0x4e]:单片机通过 向暂存器中写入TH、TL和配置数据;(努力挣钱)

Read Scratchpad[0xbe]:单片机通过 向暂存器中读取TH、TL和配置数据,但可以随时通过复位信号终止读取;(合理花钱)

Copy Scratchpad[0x48] :把暂存器中的TH、TL和配置数据复制到EEPROM;(往保险箱存钱)

Recall EE[0xb8] :(再次上电后)把EEPROM中的TH、TL和配置数据复制到暂存器(从保险箱取钱)

EEPROM(非易失性存储器)

这个存储器相当于保险箱,用于保护报警阈值和分辨率配置,掉电也不丢失。这个保险箱很小,只存了三样东西

EEPROM地址映射的暂存器字节存储内容
Address 0 Byte2 (TH) 高温报警器
Address 1 Byte 3(TL) 低温报警器
Address 2 Byte 4(Configuration) 分辨率配置

EEPROM的主要功能在介绍暂存器中就提过了,分别是存钱和取钱

Copy Scratchpad[0x48] :把暂存器中的TH、TL和配置数据复制到EEPROM;(存钱)

Recall EE[0xb8] :(再次上电后)把EEPROM中的TH、TL和配置数据复制到暂存器(取钱)


现在,我们清楚了DS18B20三个存储器中,ROM存放“身份证号”,暂存器存放温度数据、报警阈值和配置数据,EEPROM“存钱”,如此多功能,如此多数据,我们来看看势单力薄的DQ引脚是如何连接两者的。

2.代码分析

复位函数

void ds18b20_reset(void)
{
DS18B20_PORT=0;//拉低DQ
delay_10us(75);//拉低750us,
DS18B20_PORT=1;//DQ=1
delay_10us(2);//20US
}

DS18B20的通信流程始于初始化序列,该序列由主设备发送的复位脉冲和DS18B20发出的应答脉冲组成,上面代码体现的是复位脉冲部分。为什么要先拉低DQ一段时间再拉高呢?我们看一下DS手册里的初始化时序图

左边部分是复位脉冲,先拉低总线(DQ)最少480微秒,代码中拉低了750微秒;之后再缓慢拉高


检测函数

u8 ds18b20_check(void)
{
u8 time_temp=0;

while(DS18B20_PORT&&time_temp<10)//等待DQ为低电平,循环检测DQ引脚(DS_PORT)是否为低电平
{
time_temp++;
delay_10us(1);
}
if(time_temp>=10)return 1;//如果超时则强制返回1
else time_temp=0;//如果没超时,就把time_temp复位
while((!DS18B20_PORT)&&time_temp<20)//等待DQ为高电平
{
time_temp++;
delay_10us(1);
}
if(time_temp>=20)return 1;//如果超时则强制返回1
return 0;
}

在复位完成之后,DS还需要产生一个60~240微秒的低电平,以此向单片机表明 “我活着呢”。上面代码的目的就是产生这个60~240微秒的低平。如何产生呢?

while(DS18B20_PORT&&time_temp<10)//等待DQ为低电平,循环检测DQ引脚(DS_PORT)是否为低电平
{
time_temp++;
delay_10us(1);
}

DS18B20_PORT就是DQ,循环检测10次DQ是否是低电平,即是否有100微秒的低电平状态

if(time_temp>=10)return 1;//如果超时则强制返回1
else time_temp=0;//如果没超时,就把time_temp复位

如果检测10次之后第11次DQ还是低电平,一定是这孩子睡着了,就强制返回1;

如果检测十次之内DQ能自己拉高,就把计数器time_temp重启,以备下一次检测。

while((!DS18B20_PORT)&&time_temp<20)//等待DQ为高电平
{
time_temp++;
delay_10us(1);
}
if(time_temp>=20)return 1;//如果超时则强制返回1

DQ自己拉高后,还要检测它拉高了多少微秒,因为上升沿要保持至少200微秒


按位读取

u8 ds18b20_read_bit(void)
{
u8 dat=0;

DS18B20_PORT=0;
_nop_();_nop_();
DS18B20_PORT=1;
_nop_();_nop_();
if(DS18B20_PORT)dat=1;
else dat=0;
delay_10us(5);//确保整个时隙长度
return dat;
}

DS18初始化完成后,就要进行数据传输,以上代码中,单片机通过DQ从DS18中读一个位。我们注意到,这里出现了和初始化的复位很像的拉低延时再拉高的操作,我们看看写数据的时序图

单片机要从DS18中读取数据,需要自己产生一个60微秒的“读取时隙”,且时隙之间需预留至少 1µs 的恢复时间。主设备通过将单线总线拉低至少 1µs 来触发读取时隙(即上图的“L”黑线)

DS18B20_PORT=0;
_nop_();_nop_();//_nop_()通常产生1~2微秒延时,两个nop满足大于1微秒的条件
DS18B20_PORT=1;
_nop_();_nop_();

随后单片机从DS18中读取位数据,若读取低位,就如左图;若读取高位,就如右图。无论读取到什么数据,读取所花的时间必须保持在15微秒之内

if(DS18B20_PORT)dat=1;
else dat=0;

最后再补上一段延时(50微秒),确保整个读取时隙的时长。


字节读取

u8 ds18b20_read_byte(void)
{
u8 i=0;
u8 dat=0;
u8 temp=0;

for(i=0;i<8;i++)//循环8次,每次读取一位,且先读低位再读高位,读取八次位就形成一个字节
{
temp=ds18b20_read_bit();
dat=(temp<<7)|(dat>>1);//dat右移1位,并将temp的第0位填入dat的最高位
}
return dat;
}

这部分就与DS18内部特性没什么关系了,原理是用8次循环读取8个位,然后把串行输入并行输出成8位的dat


字节写入

void ds18b20_write_byte(u8 dat)
{
u8 i=0;
u8 temp=0;

for(i=0;i<8;i++)//循环8次,每次写一位,且先写低位再写高位
{
temp=dat&0x01;//选择低位准备写入,这部分属于采样窗口
dat>>=1;//将次高位移到低位
if(temp)
{
DS18B20_PORT=0;
_nop_();_nop_();
DS18B20_PORT=1;
delay_10us(6);//进行一次读取时隙
}
else
{
DS18B20_PORT=0;
delay_10us(6);
DS18B20_PORT=1;
_nop_();_nop_();
}
}
}

这部分原理是,从最低位开始读取dat(dat是8位数据),若读取到高位,则向DS18中写入1;若读取到低位,则向DS18中写入0。一共循环8次(刚好一个字节),每次读取后,dat就右移一位,原来的次低位就变成低位继续被读取。


开启温转

void ds18b20_start(void)
{
ds18b20_reset();//复位
ds18b20_check();//检查DS18B20
ds18b20_write_byte(0xcc);//同时控制总线所有设备
ds18b20_write_byte(0x44);//转换命令
}

文章开头提过,DS18是数字温度计,因为它能自己进行模数转换,上面代码就是启动模数转换

首先初始化(复位和检测);然后跳过Read ROM指令,也就是不看身份证,直接控制DS18;最后向DS18输入AD转换命令,这样DS18内部就会自动把采集的模拟量温度转换成数字量。


初始化

u8 ds18b20_init(void)
{
ds18b20_reset();
return ds18b20_check();
}

为什么DS18也需要初始化?因为这是1-Wire协议的标准流程

每次新的通信会话都必须以复位和存在检测开始


温度读取

float ds18b20_read_temperture(void)
{
float temp;
u8 dath=0;
u8 datl=0;
u16 value=0;

ds18b20_start();
ds18b20_reset();
ds18b20_check();
ds18b20_write_byte(0xcc);
ds18b20_write_byte(0xbe);

datl=ds18b20_read_byte();
dath=ds18b20_read_byte();
value=(dath<<8)+datl;
if((value&0xf800)==0xf800)
{
value=(~value)+1;
temp=value*(-0.0625);
}
else
{
temp=value*0.0625;
}
return temp;
}

这部分就是从DS18获取数字温度值的函数,我们来看看它是如何运作的

首先定义温度值为浮点数(因为温度有零有整,有正有负);

然后依次开启AD转换、初始化、跳过ROM(不看身份证)。完成准备工作后,可以进行读取了

ds18b20_write_byte(0xbe);

以上命令是Read Scratchpad[0xbe],通过DQ从暂存器字节0的最低有效位开始,直到字节9(即CRC字节)

字节地址名称功能是否可映射到EEPROM
Byte 0 温度LSB 温度值低四位(包含小数部分) 否(运行时数据)
Byte 1 温度MSB 温度值高 8 位(包含符号位) 否(运行时数据)
Byte 2 TH寄存器 高温报警阈值 是(字节 2 的 EEPROM 备份)
Byte 3 TL寄存器 低温报警阈值 是(字节 3 的 EEPROM 备份)
Byte 4 配置寄存器 分辨率设置 是(字节 4 的 EEPROM 备份)
Byte 5 保留
Byte 6 保留
Byte 7 保留
Byte 8 CRC字节 对Byte0~7的循环冗余校验码

从上表也可以看出,温度数据是先传低位再传高位的,所以可以借助时序特点把高位和低位分别存储起来:

datl=ds18b20_read_byte();
dath=ds18b20_read_byte();

然后就是索然无味的移位、合并流程。最后通过(value&0xf800)==0xf800确定温度值是正值还是负值,把转码得到的温度值(DS18的温度值是以16位二进制补码形式存储,所以需要转码)乘以精度(0.0625)得到对应分辨率下的温度值。


至此,我们已经分析完成8个DS18B20函数的功能,这部分实际上已经算是模块化了,也就是说我们已经完成了温度检测部分,剩下的显示,无论是数码管、LCD1602,又或者是LCD12864,都可以完成,但考虑到数码管原理比较简单,所以这里我们选择数码管显示温度。

二、数码管部分

//共阴极数码管显示0~F的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

void smg_display(u8 dat[],u8 pos)
{
u8 i=0;
u8 pos_temp=pos-1;

for(i=pos_temp;i<8;i++)
{
switch(i)//位选
{
case 0: LSC=1;LSB=1;LSA=1;break;
case 1: LSC=1;LSB=1;LSA=0;break;
case 2: LSC=1;LSB=0;LSA=1;break;
case 3: LSC=1;LSB=0;LSA=0;break;
case 4: LSC=0;LSB=1;LSA=1;break;
case 5: LSC=0;LSB=1;LSA=0;break;
case 6: LSC=0;LSB=0;LSA=1;break;
case 7: LSC=0;LSB=0;LSA=0;break;
}
SMG_A_DP_PORT=dat[i-pos_temp];//传送段选数据
delay_10us(100);//延时一段时间,等待显示稳定
SMG_A_DP_PORT=0x00;//消音
}
}

开头的gsmg_code数组实际上存储了共阴极数码管0~F形状的段选数据。然后输入起始位置(pos),8位数码管就会从这一位开始,依次向右移动,移动的同时会填入相应的段选数据

三、主函数部分

#include "public.h"
#include "smg.h"
#include "ds18b20.h"

void main()
{
u8 i=0;
int temp_value;
u8 temp_buf[5];

ds18b20_init();//初始化DS18B20

while(1)
{
i++;
if(i%50==0)//间隔一段时间读取温度值,间隔时间要大于温度传感器转换温度时间
temp_value=ds18b20_read_temperture()*10;//保留温度值小数后一位
if(temp_value<0)//负温度
{
temp_value=-temp_value;
temp_buf[0]=0x40;//显示负号
}
else temp_buf[0]=0x00;//不显示

temp_buf[1]=gsmg_code[temp_value/1000];//百位
temp_buf[2]=gsmg_code[temp_value%1000/100];//十位
temp_buf[3]=gsmg_code[temp_value%1000%100/10]|0x80;//个位+小数点
temp_buf[4]=gsmg_code[temp_value%1000%100%10];//小数点后一位
smg_display(temp_buf,4);
}
}

相较于DS18,主函数部分就很好解释了,可以按作用分成几个部分:显示位数定义、读取温度值、进位显示

显示位数定义

u8 temp_buf[5];

定义一个5位数组,包括符号位、百位、十位、个位(小数点与个位共用一位)、小数点后一位。

读取温度值

if(i%50==0)//间隔一段时间读取温度值,间隔时间要大于温度传感器转换温度时间
temp_value=ds18b20_read_temperture()*10;//保留温度值小数后一位
if(temp_value<0)//负温度
{
temp_value=-temp_value;
temp_buf[0]=0x40;//显示负号
}
else temp_buf[0]=0x00;//不显示

读取温度间隙不能过短,所以设置i作为计时器、每50次循环读取一次温度;然后检测温度正负值:若为正,直接显示;若为负,翻转后加个负号再显示。

进位显示

temp_buf[1]=gsmg_code[temp_value/1000];//百位
temp_buf[2]=gsmg_code[temp_value%1000/100];//十位
temp_buf[3]=gsmg_code[temp_value%1000%100/10]|0x80;//个位+小数点
temp_buf[4]=gsmg_code[temp_value%1000%100%10];//小数点后一位
smg_display(temp_buf,4);

因为前面在读取温度时乘了10,相当于只保留一位小数,所以这里的百位就从原来的value/100变成value/1000,后面同理。在将温度值四位数据都写进数组tem_buf[]后,数码管显示函数从8位数码管的第四位开始依次显示。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 分析一段DS18B20温度显示代码
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!