


当温度转换命令(44H)发布后,经转换所得的温度值以二字节补码形式存 放在高速暂存存储器的第 0 和第 1 个字节。存储的两个字节,高字节的前 5 位 是符号位 S,单片机可通过单线接口读到该数据,读取时低位在前,高位在后, 数据格式如下:

比如我们要计算+85 度,数据输出十六进制是 0X0550,因为高字节的高 5 位为 0,表明检测的温度是正温度,0X0550 对应的十进制为 1360,将这个值乘 以 12 位精度 0.0625,所以可以得到+85 度。
计算-0.5,数据输出十六进制是0XFFF8,因为高字节的高 5 位为 1,表明检测的温度是负温度,那么0XFFF8对应的二进制(1111111111111000)就要取反,取反后变成0X0007,0X0007对应的十进制为7,在将7+1在乘以12 位精度 0.0625是0.5,因为高 5 位为 1,表明检测的温度是负温度,所以是 -0.5显示。
知道了怎么计算温度,接下来我们就来看看如何读取温度数据,由于 DS18B20 是单总线器件,所有的单总线器件都要求采用严格的信号时序,以保证 数据的 完整性。DS18B20 时序包括如下几种:初始化时序、写(0 和 1)时序、 读(0 和 1)时序。 DS18B20 发送所有的命令和数据都是字节的低位在前。这里我们 简单介绍这几个信号的时序:
初始化时序
单总线上的所有通信都是以初始化序列开始。主机输出低电平,保持低电平 时间至少 480us(该时间的时间范围可以从 480 到 960 微妙),以产生复位脉 冲。接着主机释放总线,外部的上拉电阻将单总线拉高,延时 15~60 us,并进入接收模式。接着 DS18B20 拉低总线 60~240 us,以产生低电平应答脉冲,若为低电平,还要做延时,其延时的时间从外部上拉电阻将单总线拉高算起最少要 480 微妙。初始化时序图如下:
写时序
写时序包括写 0 时序和写 1 时序。所有写时序至少需要 60us,且在 2 次 独立的写时序之间至少需要 1us 的恢复时间,两种写时序均起始于主机拉低总 线。写 1 时序:主机输出低电平,延时 2us,然后释放总线,延时 60us。写 0 时序:主机输出低电平,延时 60us,然后释放总线,延时 2us。写时序图如下:

在了解了单总线时序之后,我们来看看 DS18B20 的典型温度读取过程, DS18B20 的典型温度读取过程为:复位→发 SKIP ROM 命令(0XCC)→发开始转 换命令(0X44)→延时→复位→发送 SKIP ROM 命令(0XCC)→发读存储器命令(0XBE)→连续读出两个字节数据(即温度)→结束。
硬件设计
本实验使用到硬件资源如下:
(1)动态数码管 (2)DS18B20 动态数码管电路在前面章节已介绍,这里就不再重复。下面来看下开发板上 DS18B20 模块电路,如下图所示:
/******************************************************************************** 函 数 名: smg_display* 函数功能: 动态数码管显示* 输入: dat :要显示的数据pos :从左开始第几个位置开始显示,范围 1-8* 输出: 无*******************************************************************************/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 ; // 消音}}
#include "ds18b20.h"
#include "intrins.h"
/********************************************************************
***********
* 函 数 名 : ds18b20_init
* 函数功能 : 初始化 DS18B20 的 IO 口 DQ 同时检测 DS 的存在
* 输 入 : 无
* 输 出 : 1:不存在,0:存在
*********************************************************************
**********/
u8 ds18b20_init(void)
{
ds18b20_reset(); // 复位 DS18B20
return ds18b20_check(); // 检测 DS18B20 是否存在
}
/********************************************************************
***********
* 函 数 名 : ds18b20_reset
* 函数功能 : 复位 DS18B20
* 输 入 : 无
* 输 出 : 无
*********************************************************************
**********/
void ds18b20_reset(void)
{
DS18B20_PORT=0; //首先拉低总线,给这个总线,DQ输出个低电平
delay_10us(75); //输出这个总线有个要求,要持续480-960us之间,这里要有个延时函数
DS18B20_PORT=1;//延时过后要释放总线,拉高总线,给这个总线,DQ输出个高电平
delay_10us(2); // 释放总线有个要求,要持续15-60us之间,这里要有个延时函数
}
/********************************************************************
***********
* 函 数 名 : ds18b20_check
* 函数功能 : 检测 DS18B20 是否存在
* 输 入 : 无
* 输 出 : 1:未检测到 DS18B20 的存在,0:存在
*********************************************************************
**********/
u8 ds18b20_check(void)
{
u8 time_temp=0;
//while(DS18B20_PORT&&time_temp<20) ,等待 DQ 为低电平,,根据与运算,任何一个为假则退出,第一种检测到DS18B20_PORT低电平,
//则直接退出。第二种检测DS18B20_PORT高电平,则进入while循环,持续等待,每循环一次time_temp+1,当time_temp>=20,
//则还没有检测到低电平,就退出循环。
while(DS18B20_PORT&&time_temp<20)
{
time_temp++;
delay_10us(1);
}
if(time_temp>=20)return 1; //如果超时则强制返回 1,等待20次没有检测到低电平就返回1,表示未检测到 DS18B20 的存在
else time_temp=0; //检测到低电平,则直接进else 语句,不会执行上面if语句,time_temp归零等待下次检测
//while((!DS18B20_PORT)&&time_temp<20),等待 DQ 为高电平,,根据与运算,任何一个为假则退出,第一种检测到DS18B20_PORT高电平,
//这里(!DS18B20_PORT)非一下,就变成低电平,条件为假,则直接退出。第二种检测DS18B20_PORT低电平,这里(!DS18B20_PORT)非一下,
//就变成高电平,则进入while循环,持续等待,每循环一次time_temp+1,当time_temp>=20,则还没有检测到低高平,就退出循环。
while((!DS18B20_PORT)&&time_temp<20) //等待 DQ 为高电平
{
time_temp++;
delay_10us(1);
}
if(time_temp>=20)return 1; //如果超时则强制返回 1 ,等待20次没有检测到高电平就返回1,表示未检测到 DS18B20 的存在
return 0; //如果
}
/*******************************************************************************
* 函 数 名 : ds18b20_read_bit
* 函数功能 : 从DS18B20读取一个位
* 输 入 : 无
* 输 出 : 1/0
*******************************************************************************/
u8 ds18b20_read_bit(void) //读是没有入口参数的是void类型
{
u8 dat=0;//定义一个变量保存读取到的值,初值为0
DS18B20_PORT=0;//根据时序图,我们要先输出一个低电平
_nop_();_nop_(); //延时2个us
DS18B20_PORT=1; //释放总线,因为IO口有上拉电阻,置1才视为释放总线
//当发送完0后,DS18B20将会释放总线,则通过上拉电阻该总线将会恢复到高电平的闲置状态。
//从DS18B20中输出的数据在初始化读时序后仅有15us的有效时间。因此,主设备在开始改读时
//段后的15us之内必须释放总线,并且对总线进行采样
_nop_();_nop_(); //该段时间不能过长,必须在15us内读取数据
if(DS18B20_PORT)dat=1; //如果总线上为1则数据dat为1,否则为0
else dat=0;
delay_10us(5);
return dat;//返回读取到的值
}
/*******************************************************************************
* 函 数 名 : ds18b20_read_byte
* 函数功能 : 从DS18B20读取一个字节
* 输 入 : 无
* 输 出 : 一个字节数据
*******************************************************************************
举例:ds18b20_read_bit=10010001,循环8次,每次读取一位,且先读低位再读高位,temp8次读取到的位依次是如下
temp<<7 dat>>1 dat=(temp<<7)|(dat>>1);
temp=1 10000000 00000000 10000000
temp=0 00000000 01000000 01000000
temp=0 00000000 00100000 00100000
temp=0 00000000 00010000 00010000
temp=1 10000000 00001000 10001000
temp=0 00000000 01000100 01000100
temp=0 00000000 00100010 00100010
temp=1 10000000 00010001 10010001
*******************************************************************************/
u8 ds18b20_read_byte(void)//读是没有入口参数的是void类型
{
u8 i=0;//一个字节8位,要循环8次,定义一个变量
u8 temp=0;//保存读取到的位
u8 dat=0; //保存读取到的字节
for(i=0;i<8;i++)
{
temp=ds18b20_read_bit();//循环8次,每次读取一位,且先读低位再读高位
dat=(temp<<7)|(dat>>1); //现将读取的位,左移7位到最高位,因为是先读低位再读高位,在右移一位,循环8次,移到最低位
//为了之前读取到的数值,在下次循环的时候不丢失,这里用 | 或运算(参加运算的两个数只要两个数中的一个为1,结果就为1。),
//把(temp<<7)和(dat>>1) 两个相加在赋予dat
}
return dat;
}
/*******************************************************************************
* 函 数 名 : ds18b20_write_byte
* 函数功能 : 写一个字节到DS18B20
* 输 入 : dat:要写入的字节
* 输 出 : 无
*******************************************************************************/
void ds18b20_write_byte(u8 dat)
{
u8 i=0;
u8 temp=0; //把要写入的字节保存在temp里面
for (i=0;i<8;i++) //循环8次,每次写一位,且先写低位再写高位
{
temp=dat&0x01; //选择低位准备写入,根据与运算方法,前后条件同时满足表达式为真,是从低位开始写,dat最低位是1,和0x01比较,temp=1,如果
//dat最低位是0,和0x01比较,temp=0,
dat>>=1;//因为这里dat&0x01比较的是最低位,所以要将次高位移到低位
if(temp)
{
DS18B20_PORT=0;//主机DS18B20_PORT输出低电平,延时 2us,然后释放总线,延时 60us。
//* 函 数 名 : _nop_()
//* 函数功能 : 指令的延迟时间为 1us
//51单片机中,1个机械周期 = 12个时钟周期 = 12 * ( 1 / f)。(f 为晶振频率)。
// 如果只用的是12MHZ的晶振,那么 一个机械周期就是1us;也就是说:
// _nop_(); 指令的延迟时间为 1us。可以较为精确得控制延迟时间
_nop_();_nop_();//延时2us ,一个_nop_()是1us
DS18B20_PORT=1; //然后释放总线, DS18B20_PORT输出高电平
delay_10us(6); //延时 60us
}
else
DS18B20_PORT=0; //写 0时序:主机DS18B20_PORT输出低电平,延时 60us,然后释放总线,延时 2us。
delay_10us(6); //延时 60us
DS18B20_PORT=1; //然后释放总线, DS18B20_PORT输出高电平
_nop_();_nop_();//延时2us ,一个_nop_()是1us
}
}
/********************************************************************
***********
* 函 数 名 : ds18b20_start
* 函数功能 : 开始温度转换
* 输 入 : 无
* 输 出 : 无
*********************************************************************
**********/
void ds18b20_start(void)
{
ds18b20_reset(); //复位
ds18b20_check(); //检查DS18B20
ds18b20_write_byte(0XCC);//发 SKIP ROM 命令 ROM
ds18b20_write_byte(0X44);//发开始转换命令(0X44)
}
/********************************************************************
***********
* 函 数 名 : ds18b20_read_temperture
* 函数功能 : 从 ds18b20 得到温度值
* 输 入 : 无
* 输 出 : 温度数据
*********************************************************************
**********/
float ds18b20_read_temperture(void)//因为温度有可能是小数,负数等,所以定义一个float类型的返回值
{
u8 dath=0;//保存读取的高字节数据
u8 datl=0;//保存读取的低字节数据
u16 value=0;//保存dath和datl合并后的数据
float temp=0;//保存读取到的实际温度
ds18b20_start();//开始转换
//delay_ms(1);//此处也可以不用延时,在检测ds18b20_check()里面已经包含 ,这里可以省略
ds18b20_reset(); //复位
ds18b20_check(); //检查DS18B20
ds18b20_write_byte(0XCC);//发 SKIP ROM 命令 ROM
ds18b20_write_byte(0XBE);//发读存储器命令
datl=ds18b20_read_byte();//读取的低字节
dath=ds18b20_read_byte();//读取的高字节
value=(dath<<8)+datl; //dath和datl两个8字节的数据,合并为16个字节的数据
if((value&0xf800)==0xf800)//通过&运算来判断前5位是否位1
{
value=(~value)+1;//如果为1,就是负温度,读到的数值需要取反加 1 再乘以 0.0625 即可得到实际温度。
temp=value*(-0.0625);//将读到的实际温度保存在temp里面,因为是负温度,所以这里乘以-0.0625
}
else //如果不等于1,那就是0 ,就是正温度,正温度直接乘以0.0625 即可得到实际温度
{
temp=value*0.0625;//将读到的实际温度保存在temp里面
}
return temp; //将得到的温度返回出去
}
#ifndef _ds18b20_H
#define _ds18b20_H
#include "public.h"
//管脚定义
sbit DS18B20_PORT=P3^7;
//函数声明
u8 ds18b20_init(void);//初始化 DS18B20 的 IO 口 DQ 同时检测 DS 的存在
void ds18b20_reset(void);//复位 DS18B20
u8 ds18b20_check(void);//检测 DS18B20 是否存在
u8 ds18b20_read_bit(void);//从DS18B20读取一个位
u8 ds18b20_read_byte(void);//从DS18B20读取一个字节
void ds18b20_write_byte(u8 dat);//写一个字节到DS18B20
void ds18b20_start(void);//开始温度转换
float ds18b20_read_temperture(void);//从 ds18b20 得到温度值
#endif
/********************************************************************
****************** 实验名称:I2C-EEPROM 实验
接线说明:
实验现象:下载程序后,数码管右 4 位显示 0,按 K1 键将数据写入到 EEPROM 内保存,
按 K2 键读取 EEPROM 内保存的数据,按 K3 键显示数据加 1,按 K4 键显示数据清
零,
最大能写入的数据是 255。
注意事项:
*********************************************************************
******************/
#include "public.h"
#include "smg.h"
#include "ds18b20.h"
void main()
{
u8 i=0;
int temp_value;//保存放大10倍后的温度值
u8 temp_buf[5];//定义一个数组,显示5个数码管,保存段码数据
ds18b20_init();//初始化 DS18B20
while(1)
{
i++;
if(i%50==0)//间隔一段时间读取温度值,间隔时间要大于温度传感器转换温度时间
temp_value=ds18b20_read_temperture()*10;//保留温度值小数后一位,这里虽然取得的是整数,
//但实际是保留小数点后1位,加入获取的到的温度是12.36,乘以10是123.6,取整数就是123,已经包含了小数点后一位地3.
if(temp_value<0)//temp_value小于0,温度就是负数
{
temp_value=-temp_value;//根据负负得正,这里把负数转换为正数,保存在temp_value里面
temp_buf[0]=0x40;//显示负号
}
else //大于0就是正数,就不显示负号
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);
}
}