DS18B20的介绍
DS18B20是一种常用的数字温度传感器;
下面介绍它的特点和功能:
- 封装和引脚定义:DS18B20常见的封装为TO-92,也有其他封装形式可选。其针脚定义包括供电(VCC)、地(GND)以及数据线(DQ)。
2.数字输出:DS18B20采集到的温度数据以数字信号形式输出,可直接连接到微处理器等设备进行数据处理和控制。
3.单线接口方式:DS18B20与微处理器之间仅需要一条通信线,大大简化了连接和使用的复杂性。
4.适应电压范围广:DS18B20可以在3.0~5.5V的电压范围内工作,在寄生电源方式下甚至可以通过数据线供电。
5.高精度测温:DS18B20具有高精度的温度测量能力,支持的测量分辨率可通过程序设定为9~12位。
6.抗干扰能力强:DS18B20采用了数字信号传输,具备较好的抗干扰能力,适用于各种非极限温度场合。
7.应用广泛:DS18B20的外观可以根据应用场合的不同进行调整,例如管道式、螺纹式、磁铁吸附式和不锈钢包裹式等,适用于电缆沟测温、锅炉测温、机房测温、农业大棚测温等领域。
内部结构框图
主要由4部分组成:温度传感器、温度报警触发器TH和TL、配置寄存器(EEPROM)、64位ROM;
64位ROM:作为器件地址,用于总线通信的寻址,对于每个DS18B20来说,它们的64位系列号均不相同;这样的作用能使一根总线链接多个DS18B20;
SCRATCHPAD(暂存器):用于总线的数据交互;一般位于CPU内部中,用于快速访问和暂存计算过程的临时结果或变量;在这里是用来暂存温度的读数;并且链接着多个器件;
温度传感器:用于测量周围温度的设备,将温度转换为电信号来实现测量;
温度报警触发器TH和TL:这是一种电路或者是设备,用于监测周围温度并在温度超过设定阈值时触发报警。
配置寄存器(EEPROM):可电擦写可编程只读存储器;用于保存温度触发阈值和配置参数;
CRC GENNERATOR:循环冗余校验,用于检测和纠正数据或存储过程中的错误。
DS18B20存储器
上图为DS18B20的存储器结构,存储器由一个暂存SRAM和一个存储高低报警值TH和TL以及非易失性电可擦除EEPROM组成。
注意当报警功能不使用时,TH和 TL 寄存器可以被当作普通寄存器使用;
位 0 和位 1 为测得温度信息的 LSB 和 MSB。这两个字节是只读的。第 2 和第 3 字节是 TH 和 TL 的拷贝。位 4 包含配置寄存器数据;位5,6 和 7 被器件保留,禁止写入;这些数据在读回时全部表现为逻辑 1。高速暂存器的位 8 是只读的,包含以上八个字节的 CRC 码;
单总线的介绍
单总线是一种用于在电子系统中传输信息的通信协议。它通过在系统中使用单根导线来连接多个设备,并且每个设备都可以发送和接收数据。
单总线协议通常由一个主设备和多个从设备组成。主设备负责控制通信的发起和结束,而从设备则相应地执行主设备的指令。单总线上的通信是通过发送特定格式的数据包来实现的。
在单总线中,数据以位的形式进行传输。每个设备通过读取或写入单根导线上的数据位来接收或发送数据。为了实现多个设备之间的通信,每个设备通过独特的地址标识来进行识别。
单总线的优点之一是减少了系统中需要的导线数量。由于只有一根导线用于数据传输,这使得系统设计更加简单。此外,由于通信是在一个主设备和多个从设备之间进行的,因此主设备可以轻松地控制和管理整个系统。
然而,单总线也存在一些限制。由于所有设备共享同一个导线,因此通信可能会受到干扰或冲突的影响。此外,在大型系统中,单总线可能会受到传输速率的限制。
硬件结构
设备的DQ均要配置成开漏输出模式
DQ添加一个上拉电阻,阻值一般为4.7KΩ左右
若此总线的从机采取寄生供电,则主机还应配一个强上拉输出电路
开漏输出模式(Open-Drain Output Mode)是一种电路输出结构,常见于数字电路中。它可以实现多个设备共享同一根数据线的连接,并且能够提供电平控制和信号共享功能。
在开漏输出模式下,输出引脚通常由一个晶体管和一个外部上拉电阻(Pull-up Resistor)组成。晶体管充当开关,控制电路是否将该引脚连接到地(GND)或断开连接。
当晶体管导通时,输出引脚连接到地,形成低电平(逻辑0)。而当晶体管关闭时,外部上拉电阻将引脚拉高至正电压,形成高电平(逻辑1)。
单总线的时序结构
在单总线的时序结构里,设备之间通过时序协议进行通信;一般分为初始化、发送位、接收位、发送一个字节、接收一个字节;
初始化:主机将总线拉低至少480us,然后释放总线,等待15-60us后,存在的从机会拉低总线60~240us以响应主机,之后从机将释放总线;
代码:
sbit OneWire_DQ=P3^7;//DQ在单片机中对应的寄存器 unsigned char OneWire_Init() { unsigned char i; unsigned char AckBit; OneWire_DQ=1;//确保在单总线拉低之前为高电平 OneWire_DQ=0;//单总线被拉低 _nop_(); //延迟500us i = 227; while (--i); OneWire_DQ=1; //释放总线 _nop_(); //延迟70us i = 31; while (--i); AckBit=OneWire_DQ; //从设备取到信号响应主设备 _nop_(); //延迟500us i = 227; while (--i); return AckBit; }
发送一位:主机将总线拉低60-120us,然后释放总线,表示发送0;主机将总线拉低1~15us,然后释放总线,表示发送1。从机将在总线拉低30us后(典型值)读取电平,整个时间片应大于60us;
代码:
void OneWire_SendBit(unsigned char Bit) { unsigned char i; OneWire_DQ=0;//总线拉低 _nop_(); //延迟10us i = 3; while (--i); OneWire_DQ=Bit;//总线发送信号(1或0) i = 22; //延迟50us while (--i); OneWire_DQ=1;//释放总线 }
这里将两种可能结果通过赋值的方式来发送一个位;先将总线拉低至10us,然后对总线收到主设备的信号,如果为1,那么延迟这50us总是为高电平,为0,那么延迟这50us总是为低电平;
接收一位:主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us的末尾),读取为低电平则为接收0,读取为高电平则为接收1 ,整个时间片应大于60us;
代码:
unsigned char OneWire_ReceiveBit() { unsigned char i; unsigned char Bit; OneWire_DQ=0;//将总线拉低 _nop_(); //延迟5us i = 1; while (--i); OneWire_DQ=1;//释放总线 _nop_(); //延迟5us i = 1; while (--i); Bit=OneWire_DQ;//读取总线的电平信号 i = 22; //延迟50us while (--i); return Bit; }
通过在拉低5us后释放总线,如果读取总线的电平信号为1,那么后一直保持高电平状态;如果读取总线的电平信号为0,那么会有50us一直为低电平;
发送接收一个字节:连续调用8次发送一位的时序,依次发送/接收一个字节的8位(低位在前);
代码:
void OneWire_SendByte(unsigned char Byte) { unsigned char i; for(i=0;i<8;i++) { OneWire_SendBit(Byte&(0x01<<i)); } } unsigned char OneWire_ReceiveByte() { unsigned char i; unsigned char Byte=0x00; for(i=0;i<8;i++) { if(OneWire_ReceiveBit()) { Byte|=(0x01<<i); } } return Byte; }
温度存储格式
DS18B20温度感应器能精确到小数点后4位;LS字节存储的是较低位的,而MS存储的是较高位的;
图为温度十进制转换为二进制输出的形式,再由二进制转换为十六进制;
要注意的是存储是以补码形式进行存储,所以输出也同样以补码形式输出;
DS18B20操作流程
一般通过初始化+ ROM指令 + 功能指令来实现;
初始化:从机复位,主机判断从机是否响应
ROM操作:ROM指令+本指令需要的读写操作
功能操作:功能指令+本指令需要的读写操作
这里只是介绍了对应操作的指令;
SKIP ROM [CCh] (忽略 ROM 指令)
这条指令允许总线控制器不用提供 64 位 ROM 编码就使用功能指令。例如,总线控制器可以先发出一条忽略 ROM 指令,然后发出温度转换指令[44h],从而完成温度转换操作。注意:当只有一只从机在总线上时,无论如何,忽略 ROM 指令之后只能跟着发出一条读取暂存器指令[BEh]。在单点总线情况下使用该命令,器件无需发回 64 位 ROM 编码,从而节省了时间。如果总线上有不止一只从机,若发出忽略 ROM 指令,由于多只从机同时传送信号,总线上就会发生数据冲突。
CONVERT T [44h] (温度转换指令)
这条命令用以启动一次温度转换。温度转换指令被执行,产生的温度转换结果数据以 2 个字节的形式被存储在高速暂存器中,而后 DS18B20 保持等待状态。
READ SCRATCHPAD [BEh] (读暂存器指令)
这条命令读取暂存器的内容。读取将从字节 0 开始,一只进行下去,知道第 9 字节(字节 8,CRC)读完,如果不想读完所有字节,控制器可以在任何时间发出复位命令来中止读取。
我们只实现温度转换和读温度的操作;
代码:
//DS18B20指令 #define DS18B20_SKIP_ROM 0xCC #define DS18B20_CONVERT_T 0x44 #define DS18B20_READ_SCRATCHPAD 0xBE void DS18B20_Convert() { OneWire_Init(); //初始化,使温度感应器应答 OneWire_SendByte(DS18B20_SKIP_ROM); //主机发送跳过ROM指令给DS18B20 OneWire_SendByte(DS18B20_CONVERT_T); //让DS18B20进行温度转换 } float DS18B20_ReadT() { unsigned char TLSB,TMSB; short Temp; float T; OneWire_Init();//初始化 OneWire_SendByte(DS18B20_SKIP_ROM);//跳过ROM指令 OneWire_SendByte(DS18B20_READ_SCRATCHPAD);//发送读操作指令 TLSB=OneWire_ReceiveByte();//接收总线返回的字节 TMSB=OneWire_ReceiveByte(); Temp=(TMSB<<8)|TLSB;//TSMB进行左移,与TSMB按位或合成一个16位的 T=Temp/16.0;//需要将温度精确到小数后4位(2^4) return T; }
在LCD屏上显示温度实例
OneWire.h
#ifndef __ONEWIRE_H__ #define __ONEWIRE_H__ //对总线初始化 unsigned char OneWire_Init(); //主机发送一个位 void OneWire_SendBit(unsigned char Bit); //主机接收一个位 unsigned char OneWire_ReceiveBit(); //主机发送一个字节 void OneWire_SendByte(unsigned char Byte); //主机接收一个字节 unsigned char OneWire_ReceiveByte(); #endif
OneWire.c
include <REGX52.H> #include<INTRINS.H> sbit OneWire_DQ=P3^7; unsigned char OneWire_Init() { unsigned char i; unsigned #char AckBit; OneWire_DQ=1; OneWire_DQ=0; _nop_(); //延迟500us i = 227; while (--i); OneWire_DQ=1; _nop_(); //延迟70us i = 31; while (--i); AckBit=OneWire_DQ; _nop_(); //延迟500us i = 227; while (--i); return AckBit; } void OneWire_SendBit(unsigned char Bit) { unsigned char i; OneWire_DQ=0; _nop_(); //延迟10us i = 3; while (--i); OneWire_DQ=Bit; i = 22; //延迟50us while (--i); OneWire_DQ=1; } unsigned char OneWire_ReceiveBit() { unsigned char i; unsigned char Bit; OneWire_DQ=0; _nop_(); //延迟5us i = 1; while (--i); OneWire_DQ=1; _nop_(); //延迟5us i = 1; while (--i); Bit=OneWire_DQ; i = 22; //延迟50us while (--i); return Bit; } void OneWire_SendByte(unsigned char Byte) { unsigned char i; for(i=0;i<8;i++) { OneWire_SendBit(Byte&(0x01<<i)); } } unsigned char OneWire_ReceiveByte() { unsigned char i; unsigned char Byte=0x00; for(i=0;i<8;i++) { if(OneWire_ReceiveBit()) { Byte|=(0x01<<i); } } return Byte; }
DS18B20.h
#ifndef __DS18B20_H__ #define __DS18B20_H__ //DS18B20开始温度变换 void DS18B20_Convert(); //DS18B20读取温度 float DS18B20_ReadT(); #endif
DS28B20.c
#include <REGX52.H> #include"OneWire.h" //DS18B20指令 #define DS18B20_SKIP_ROM 0xCC #define DS18B20_CONVERT_T 0x44 #define DS18B20_READ_SCRATCHPAD 0xBE void DS18B20_Convert() { OneWire_Init(); //初始化 OneWire_SendByte(DS18B20_SKIP_ROM); //发送跳过ROM OneWire_SendByte(DS18B20_CONVERT_T); //发送温度转换指令 } float DS18B20_ReadT() { unsigned char TLSB,TMSB; short Temp; float T; OneWire_Init();//初始化 OneWire_SendByte(DS18B20_SKIP_ROM);//发送跳筊ROM OneWire_SendByte(DS18B20_READ_SCRATCHPAD);//发送温度的暂存器 TLSB=OneWire_ReceiveByte();//读取温度 TMSB=OneWire_ReceiveByte(); Temp=(TMSB<<8)|TLSB; T=Temp/16.0; return T; }
Delay.h
#ifndef __DELAY_H__ #define __DELAY_H__ void Delayms(unsigned int x); #endif
Delay.c
void Delayms(unsigned int x) //@11.0592MHz { unsigned char i, j; while(x--) { i = 2; j = 199; do { while (--j); } while (--i); } }
LCD1602.h
#ifndef __LCD1602_H__ #define __LCD1602_H__ //用户调用函数: void LCD_Init(); void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char); void LCD_ShowString(unsigned char Line,unsigned char Column,char *String); void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length); void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); #endif
LCD1602.c
#include <REGX52.H> //引脚配置: sbit LCD_RS=P2^6; sbit LCD_RW=P2^5; sbit LCD_EN=P2^7; #define LCD_DataPort P0 //函数定义: /** * @brief LCD1602延时函数,12MHz调用可延时1ms * @param 无 * @retval 无 */ void LCD_Delay() { unsigned char i, j; i = 2; j = 239; do { while (--j); } while (--i); } /** * @brief LCD1602写命令 * @param Command 要写入的命令 * @retval 无 */ void LCD_WriteCommand(unsigned char Command) { LCD_RS=0; LCD_RW=0; LCD_DataPort=Command; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602写数据 * @param Data 要写入的数据 * @retval 无 */ void LCD_WriteData(unsigned char Data) { LCD_RS=1; LCD_RW=0; LCD_DataPort=Data; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602设置光标位置 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @retval 无 */ void LCD_SetCursor(unsigned char Line,unsigned char Column) { if(Line==1) { LCD_WriteCommand(0x80|(Column-1)); } else if(Line==2) { LCD_WriteCommand(0x80|(Column-1+0x40)); } } /** * @brief LCD1602初始化函数 * @param 无 * @retval 无 */ void LCD_Init() { LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵 LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关 LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动 LCD_WriteCommand(0x01);//光标复位,清屏 } /** * @brief 在LCD1602指定位置上显示一个字符 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @param Char 要显示的字符 * @retval 无 */ void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char) { LCD_SetCursor(Line,Column); LCD_WriteData(Char); } /** * @brief 在LCD1602指定位置开始显示所给字符串 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串 * @retval 无 */ void LCD_ShowString(unsigned char Line,unsigned char Column,char *String) { unsigned char i; LCD_SetCursor(Line,Column); for(i=0;String[i]!='\0';i++) { LCD_WriteData(String[i]); } } /** * @brief 返回值=X的Y次方 */ int LCD_Pow(int X,int Y) { unsigned char i; int Result=1; for(i=0;i<Y;i++) { Result*=X; } return Result; } /** * @brief 在LCD1602指定位置开始显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~65535 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以有符号十进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:-32768~32767 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length) { unsigned char i; unsigned int Number1; LCD_SetCursor(Line,Column); if(Number>=0) { LCD_WriteData('+'); Number1=Number; } else { LCD_WriteData('-'); Number1=-Number; } for(i=Length;i>0;i--) { LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以十六进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~0xFFFF * @param Length 要显示数字的长度,范围:1~4 * @retval 无 */ void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i,SingleNumber; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { SingleNumber=Number/LCD_Pow(16,i-1)%16; if(SingleNumber<10) { LCD_WriteData(SingleNumber+'0'); } else { LCD_WriteData(SingleNumber-10+'A'); } } } /** * @brief 在LCD1602指定位置开始以二进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~1111 1111 1111 1111 * @param Length 要显示数字的长度,范围:1~16 * @retval 无 */ void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0'); } }
main.c
#include <REGX52.H> #include"Delay.h" #include"LCD1602.h" #include"DS18B20.h" float T; void main() { DS18B20_Convert();//上电先转换一次温度,防止第一次数据报错 Delayms(1000); //等待转换完成 LCD_Init(); LCD_ShowString(1,1,"Temperature:"); while(1) { DS18B20_Convert(); //转换温度 T=DS18B20_ReadT(); //读取温度 if(T<0) { LCD_ShowChar(2,1,'-'); T=-T; } else { LCD_ShowChar(2,1,'+'); } LCD_ShowNum(2,2,T,3); //显示整数部分 LCD_ShowChar(2,5,'.'); LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//显示小数部分 } }
由于上电复位后温度会有一个初始值,所以需要在上电时转换温度,并且延迟一秒钟转换,让我们屏蔽它的初始值状况;然后就是在循环中转换温度,可以达到实时进行温度感应,并且对温度进行读取,对于小数部分的数字,要在屏幕上以整数形式显示,就将它转换为整数再取余即可;