内容
通过DS18B20温度传感器,在数码管显示检测到的温度值;
DS18B20介绍
简介
DS18B20是由DALLAS半导体公司推出的一种的“一线总线(单总线)”接口的温度传感器;
与传统的热敏电阻等测温元件相比,它是一种新型的体积小、适用电压宽、与微处理器接口简单的数字化温度传感器;
特点
DS18B20温度传感器具有如下特点:
- 适应电压范围更宽,电压范围:3.0~5.5V,在寄生电源方式下可由数据线供电;
- 温范围-55℃~+125℃,在-10~+85℃时精度为±0.5℃;
- 可编程的分辨率为9~12位,对应的可分辨温度分别为0.5℃、0.25℃、0.125℃和 0.0625℃,可实现高精度测温;
- 在9位分辨率时最多在93.75ms内把温度转换为数字,12位分辨率时最多在750ms内把温度值转换为数字,速度更慢;
- 测量结果直接输出数字温度信号,以"一根总线"串行传送给CPU,同时可传送CRC校验码,具有极强的抗干扰纠错能力;
- 负压特性:电源极性接反时,芯片不会因发热而烧毁,但不能正常工作;
结构
DS18B20一共有三个管脚,当我们正对传感器切面(传感器型号字符那一面)时,传感器的管脚顺序是从左到右排列;
管脚1为GND,管脚2为数据DQ,管脚3为VDD;
如果把传感器插反,那么电源将短路,传感器就会发烫,很容易损坏,所以一定要注意传感器方向;
通常在开发板上都会标出传感器的凸起出,所以只需要把传感器凸起的方向对着开发板凸起方向插入即可;
DS18B20温度传感器的内部存储器包括一个高速的暂存器RAM和一个非易失性的可电擦除的EEPROM,后者存放高温度和低温度触发器TH、TL和配置寄存器;
配置寄存器是配置不同的位数来确定温度和数字的转化,配置寄存器结构如下:
TM | R1 | R0 | 1 | 1 | 1 | 1 | 1 |
---|
低五位一直都是"1",TM是测试模式位,用于设置DS18B20在工作模式还是在测试模式,在DS18B20出厂时该位被设置为0,用户不需要去改动;
R1和R0用来设置DS18B20的精度(分辨率),可设置为9,10,11或12位,对应的分辨率温度是0.5℃,0.25℃,0.125℃和0.0625℃;
R0和R1配置如下图:
在初始状态下默认的精度是12位,即R0=1、R1=1;
高速暂存存储器由9个字节组成,其分配如下:
当温度转换命令(44H)发布后,经转换所得的温度值以二字节补码形式存放在高速暂存存储器的第0和第1个字节;
存储由两个字节组成,高字节的前5位是符号位S,单片机可通过单线接口读到该数据,读取时低位在前,高位在后,数据格式如下:
如果测得的温度大于0,这5位为‘0’,只要将测到的数值乘以0.0625(默认精度是12位)即可得到实际温度;
如果温度小于0,这5位为‘1’,测到的数值需要取反加1再乘以0.0625即可得到实际温度;
温度计算
温度与数据对应关系如下:
比如我们要计算+85度,数据输出十六进制是0X0550,因为高字节的高5位为0,表明检测的温度是正温度,0X0550对应的十进制为1360,将这个值乘以12位精度0.0625,所以可以得到+85度;
DS18B20使用
知道了怎么计算温度,接下来我们就来看看如何读取温度数据;
由于DS18B20是单总线器件,所有的单总线器件都要求采用严格的信号时序,以保证数据的完整性;
DS18B20时序包括如下几种:初始化时序、写(0和1)时序、 读(0和1)时序;
DS18B20发送所有的命令和数据都是字节的低位在前;
这里我们简单介绍这几个信号的时序:
初始化时序
初始化时序图如下:
单总线上的所有通信都是以初始化序列开始;
主机输出低电平,保持低电平时间至少480us(该时间的时间范围可以从480到960us),以产生复位脉冲;
接着主机释放总线,外部的上拉电阻将单总线拉高,延时15~60us,并进入接收模式;
接着DS18B20拉低总线60~240us,以产生低电平应答脉冲,若为低电平,还要做延时,其延时的时间从外部上拉电阻将单总线拉高算起最少要480us;
写时序
写时序图如下:
写时序包括写0时序和写1时序;
所有写时序至少需要60us,且在2次独立的写时序之间至少需要1us的恢复时间,两种写时序均起始于主机拉低总线;
写1时序:主机输出低电平,延时2us,然后释放总线,延时60us;
写0时序:主机输出低电平,延时60us,然后释放总线,延时2us;
读时序
读时序图如下:
单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据;
所有读时序至少需要60us,且在2次独立的读时序之间至少需要1us的恢复时间;
每个读时序都由主机发起,至少拉低总线1us;
主机在读时序期间必须释放总线,并且在时序起始后的15us之内采样总线状态;
一般的读时序过程为:主机输出低电平延时2us,然后主机转入输入模式延时12us,然后读取单总线当前的电平,然后延时50us;
完整过程
了解了单总线时序之后,我们来看看DS18B20的温度读取过程:
DS18B20的温度读取过程为:复位→发SKIP ROM命令(0XCC)→发开始转换命令(0X44)→延时→复位→发送SKIP ROM命令(0XCC)→发读存储器命令(0XBE)→连续读出两个字节数据(即温度)→结束;
原理图
由图可知,总线连接p37口,所以我们通过控制该io口的电位变化即可实现初始化以及读写时序;
思路
根据时序图编写初始化、读、写程序;(初始化包括复位和检测DS18B20是否存在)
编写检测DS18B20是否存在的程序(如果信号口一直为低电位,即判定为不存在);
按使用步骤,读取温度值,转换为十进制后,使其在数码管上显示;
编码
User
main.c
/*
* @Description: 通过DS18B20温度传感器,在数码管显示检测到的温度值
*/
#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) // 间隔一段时间读取温度值,间隔时间要大于温度传感器转换温度时间(12位分辨率时转换时间为750ms)
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);
}
}
Public
public.h
#ifndef _public_H
#define _public_H
#include "reg52.h"
typedef unsigned int u16; // 对系统默认数据类型进行重定义
typedef unsigned char u8;
void delay_10us(u16 ten_us);
void delay_ms(u16 ms);
#endif
public.c
#include "public.h"
/**
* @description: 延时函数,ten_us=1时,大约延时10us
* @param {u16} ten_us 延时倍数
* @return {*}
*/
void delay_10us(u16 ten_us)
{
while (ten_us--)
;
}
/**ms延时函数,ms=1时,大约延时1ms***
* @param {u16} ms 延时倍数
* @return {*}
*/
void delay_ms(u16 ms)
{
u16 i, j;
for (i = ms; i > 0; i--)
for (j = 110; j > 0; j--)
;
}
App/ds18b20
ds18b20.h
#ifndef _ds18b20_H
#define _ds18b20_H
#include "public.h"
// 管脚定义
sbit DS18B20_PORT = P3 ^ 7; // DS18B20数据口定义
// 函数声明
u8 ds18b20_init(void);
float ds18b20_read_temperture(void);
#endif
ds18b20.c
#include "ds18b20.h"
#include "intrins.h"
/**
* @description: 复位DS18B20
* @return {*}
*/
void ds18b20_reset(void)
{
DS18B20_PORT = 0; // 拉低DQ
delay_10us(75); // 拉低750us
DS18B20_PORT = 1; // DQ=1
delay_10us(2); // 20US
}
/**
* @description: 检测DS18B20是否存在
* @return {u8} 1:未检测到DS18B20的存在,0:存在
*/
u8 ds18b20_check(void)
{
u8 time_temp = 0;
while (DS18B20_PORT && time_temp < 20) // 等待DQ为低电平
{
time_temp++;
delay_10us(1);
}
if (time_temp >= 20)
return 1; // 如果超时则强制返回1
else
time_temp = 0;
while ((!DS18B20_PORT) && time_temp < 20) // 等待DQ为高电平
{
time_temp++;
delay_10us(1);
}
if (time_temp >= 20)
return 1; // 如果超时则强制返回1
return 0;
}
/**
* @description: 从DS18B20读取一个位
* @return {u8} 1/0
*/
u8 ds18b20_read_bit(void)
{
u8 dat = 0;
DS18B20_PORT = 0;
_nop_();
_nop_();
DS18B20_PORT = 1;
_nop_();
_nop_(); // 该段时间不能过长,必须在15us内读取数据
if (DS18B20_PORT)
dat = 1; // 如果总线上为1则数据dat为1,否则为0
else
dat = 0;
delay_10us(5);
return dat;
}
/**
* @description: 从DS18B20读取一个字节
* @return {u8} 一个字节数据
*/
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);
}
return dat;
}
/**
* @description: 写一个字节到DS18B20
* @param {u8} dat 要写入的字节
* @return {*}
*/
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_();
}
}
}
/**
* @description: 开始温度转换
* @return {*}
*/
void ds18b20_start(void)
{
ds18b20_reset(); // 复位
ds18b20_check(); // 检查DS18B20
ds18b20_write_byte(0xcc); // SKIP ROM
ds18b20_write_byte(0x44); // 转换命令
}
/**
* @description: 初始化DS18B20的IO口DQ,同时检测DS的存在
* @return {u8} 1:不存在,0:存在
*/
u8 ds18b20_init(void)
{
ds18b20_reset();
return ds18b20_check();
}
/**
* @description: 从ds18b20得到温度值
* @return {float} 温度数据
*/
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); // SKIP ROM
ds18b20_write_byte(0xbe); // 读存储器
datl = ds18b20_read_byte(); // 低字节
dath = ds18b20_read_byte(); // 高字节
value = (dath << 8) + datl; // 合并为16位数据
if ((value & 0xf800) == 0xf800) // 判断符号位,负温度
{
value = (~value) + 1; // 数据取反再加1
temp = value * (-0.0625); // 乘以精度
}
else // 正温度
{
temp = value * 0.0625;
}
return temp;
}
App/smg
smg.h
#ifndef _smg_H
#define _smg_H
#include "public.h"
#define SMG_A_DP_PORT P0 // 使用宏定义数码管段码口
// 定义数码管位选信号控制脚
sbit LSA = P2 ^ 2;
sbit LSB = P2 ^ 3;
sbit LSC = P2 ^ 4;
extern u8 gsmg_code[17]; // 使“共阴极数码管显示0~F的段码数据”这个变量定义为外部可用
void smg_display(u8 dat[], u8 pos);
#endif
smg.c
#include "smg.h"
// 共阴极数码管显示0~F的段码数据
u8 gsmg_code[17] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
/**
* @description: 动态数码管显示函数
* @param {u8} dat 要显示的数据
* @param {u8} pos 从左开始第几个位置开始显示,范围1-8
* @return {*}
*/
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; // 消影
}
}
编译和结果
按F7编译,无错误,生成.hex文件,使用pz-isp将hex文件下载到单片机
结果:显示检测到的温度