1、DS18B20
DS18B20本身就是温度传感器,只需要DS18B20数据引脚和单片机I/O单片机通过片机通过1-Wire协议与DS18B通信,读出温度。 相关模块电路图:
2.温度转换规则
DS18B20可以直接读取数字的温度值。温度传感器的精度为用户可编程的9、10、11或12位,分别为0.5℃,0.25℃,0.125℃和0.0625℃增量增加。默认上电精度为12位。也就是说温度每变化0.0625度,二进制数字变化1。转换精度由配置寄存器决定,如下:(R1R0出厂默认11) DS18B当需要进行温度测量和AD转换时,必须发出总线控制器[44h]命令,启动温度转换,即Write18b20(0x44)。转换后,以两个字节的形式存储产生的温度数据 高速暂存器 在温度寄存器中(先低后高),DS18b20继续等待。
DS18B20温度数据格式如下,转换后获得12位数据,存储在DS18B20的两个8位的RAM中。MSB高字节存储在里面
,LSB低字节存储在里面
。 高字节的前五位是符号位。如果测量温度大于0,则为0,只要测量值乘以0.0625可获得实际温度;若温度小于0,则为1,测得的值需要先减1,然后乘以0.0625
实际温度际温度。
3、ROM&RAM操作指令
(1)ROM指令集
指令 | 约定代码 | 功能 |
---|---|---|
Read ROM | 33H | 读DS18B20温度传感器ROM编码,即64位地址 |
Match ROM | 55H | 后跟64位ROM让总线控制器在多点总线上匹配特定的序列DS18B20.匹配的从机器可以响应后续命令,其不匹配的从机可以等待复位脉冲。当总线上有单个或多个设备时,可以使用该命令。 |
Skip ROM |
CCH |
|
Search ROM | F0H | 用于确定挂在同一总线上的用途DS18B64位ROM地址,准备操作各种设备。 |
Alarm ROM | ECH | 执行后,温度超过上限或下限的电影响应。 |
当我们只挂一个DS18B20点,只需要写一篇关于ROM的指令,即Write18b20(0xcc);。
(2) RAM指令集
指令 | 约定代码 | 功能 |
---|---|---|
启动温度转换(Convert T) | 44H |
启动DS18B20进行温度转换,从转换到获取温度的时间取决于DS18B20的精度,12位转换最长750ms,结果存入9字节RAM。 |
读暂存器 | BEH |
读9字节RAM的内容 |
写暂存器 | 4EH |
|
复制暂存器 | 48H | 将RAM复制第三、四字节的内容EEPROM中 |
重调E2PROM | B8H | 将EEPROM内容恢复到RAM中的3、4字节 |
读供电方式 | B4H | 读DS18B20供电模式,寄生供电时DS18B20发送0DS18B20发送“1”。 |
4.通过单线总线端口访问DS18B20流程
类似于I2C的寻址,1-Wire在总线开始时,有必要检查这条总线是否存在DS18B20.如果存在,总线将按时间顺序返回低电平;如果没有,则不返回,即总线将保持高电平。该过程称为脉冲检测。
获取存在脉冲有两个作用:①检测是否存在DS18B20 ②通过此过程通知DS18B准备20,单片机要操作。
脉冲检测的时序图如下: 整个过程描述如下:
①单片机拉下引脚,持续480~960us (以持续500us为例) ②单片机释放总线,即提高电平。 ③15 ~60us后,如果DS18B当这个装置存在时,它会主动降低引脚,返回低脉冲(为了确保读到这个脉冲,选择延迟60us,但不能超过75us) ④持续60 ~240us后,DS18B20释放总线,I0端口被上拉电阻拉高。
对应程序如下:
void Delayus(uint us) //@11.0592MHz {
do{
_nop_();
_nop_();
_nop_();
}while(us--);
}
bit Init_18b20()
{
bit initflag;
EA = 0; //关闭总中断
DQ = 0; //读写之前都要将该引脚拉低
Delayus(500); //延时500us
DQ = 1;
Delayus(60); //延时60us
initflag = DQ; //读取存在脉冲,0存在,1不存在
while(!DQ); //等待存在脉冲结束,即DQ = 1
EA = 1; //打开总中断
return initflag;
}
注:需要说明的是,DS18B20对时序的要求非常严格,所以在开始对某一位操作前要先关闭中断,防止中途受到干扰。但是位与位之间的间隔是可以无穷大的,完全可以在完成一位的操作之后,去干别的事情,结束之后再回来操作下一位。
一条总线上只接一个器件的情况,此时只需要直接跳过ROM,不进行ROM检测。用到的语句如下:
Write18b20(0xcc);//跳过ROM操作
常用到的两条如下:
Write18b20(0x44);//启动一次温度转换
Write18b20(0xbe);//发送读指令
写数据
过程描述如下:在给DS18b20写数据之前,单片机要先把引脚拉低,持续一段时间(2us),而后DS18b20会在60us之内读完这位数据。然后释放总线(拉高引脚) 代码如下:
//向DS18B20写入一个字节,dat为带写入字节
void Write_18b20(u8 dat)
{
u8 i;
EA = 0;
for(i=0;i<8;i++)
{
DQ = 0;
Delayus(2);//产生2us的低电平脉冲
DQ = dat & 0x01; //先读取低位
Delayus(60);
dat >>= 1; //右移一位,准备写入下一位
DQ = 1;
}
EA = 1;
}
读数据
过程描述如下:在读取DS18B20数据之前,单片机首先要拉低这个引脚,并且至少保持1us。而后释放这个引脚(拉高电平),尽快读取。从拉低这个引脚到读取不能超过15us。再延时60us,确保读取完毕。
//从DS18B20读取一个字节,返回值为读到的字节
u8 Read_18b20()
{
u8 i,dat;
EA = 0;
for(i=0;i<8;i++)
{
DQ = 0;
Delayus(2);
DQ = 1;
Delayus(2);
dat >>= 1;
if(DQ)
dat |= 0x80;//先读的是低位,依次右移
Delayus(60);
}
EA = 1;
return dat;
}
实时测量室内温度并通过数码管显示完整程序:
#include "sys.h"
#include "18b20.h"
bit GET_TEMP_FLAG;
void main()
{
int Temp = 0; //读取当前的温度值
//int Temp_int = 999, Temp_dec = 999; //温度值的整数和小数部分
ALL_Init();
Timer0Init();
while(1)
{
if(GET_TEMP_FLAG)
{
GET_TEMP_FLAG = 0;
if(Start_18b20())
{
//放大10倍 让数码管显示小数点后一位
Temp = Temp_Get()*0.0625*10+0.5; //+0.5 --> 4舍5入
// Temp_int = Temp >> 4; //分离出温度值整数部分
// Temp_dec = Temp & 0xF; //分离出温度值小数部分
// Temp_dec = Temp_dec * (10000 / 16); //二进制小数部分转换为4位十进制
}
}
Nixie_Drive(Temp);
}
}
#include "sys.h"
#include "18b20.h"
extern uchar smg1;
void Delayus(uint us) //@11.0592MHz
{
do{
_nop_();
_nop_();
_nop_();
}while(us--);
}
bit Init_18b20()
{
bit initflag;
EA = 0;//关闭总中断
DQ = 0;//读写之前都要将该引脚拉低
Delayus(500);//延时500us
DQ = 1;
Delayus(60);//延时60us
initflag = DQ; //读取存在脉冲,0存在,1不存在
while(!DQ);//等待存在脉冲结束,即DQ = 1
EA = 1;//打开总中断
return initflag;
}
//向DS18B20写入一个字节,dat为带写入字节
void Write_18b20(u8 dat)
{
u8 i;
EA = 0;
for(i=0;i<8;i++)
{
DQ = 0;
Delayus(2);//产生2us的低电平脉冲
DQ = dat & 0x01; //先读取低位
Delayus(60);
dat >>= 1; //右移一位,准备写入下一位
DQ = 1;
}
EA = 1;
}
//从DS18B20读取一个字节,返回值为读到的字节
u8 Read_18b20()
{
u8 i,dat;
EA = 0;
for(i=0;i<8;i++)
{
DQ = 0;
Delayus(2);
DQ = 1;
Delayus(2);
dat >>= 1;
if(DQ)
dat |= 0x80;//先读的是低位,依次右移
Delayus(60);
}
EA = 1;
return dat;
}
bit Start_18b20()
{
bit ack;
ack = Init_18b20();//执行总线复位,获取18b20的应答
if(ack == 0)//如果18b20应答,启动一次转换
{
Write_18b20(0xcc);//跳过RAM操作
Write_18b20(0x44);//启动一次温度转换
}
return ~ack; //ack == 0 表示操作成功,返回值对其取反
}
int Temp_Get()
{
bit ack;
u8 LSB,MSB;
u16 temp;
ack = Init_18b20();//执行总线复位,获取18b20的应答
if(ack == 0)
{
uchar check;
Write_18b20(0xcc);
Write_18b20(0xbe);
LSB = Read_18b20();
//MSB的3~7这5位为符号位,当5位都为0时为正温度 都为1时为负温度
MSB = Read_18b20();
//MSB = 0xfc; // -55℃ 负温度检验代码
//LSB = 0x90;
//检测正负温度
check = MSB;
temp = ((int) MSB << 8) + LSB; //合成16bit的整数
//当check大于等于0x08时,说明采集到的数据为负温度值
//负数是以补码的形式存在内存中,要求:原码 = 补码取反 + 1
if(check >= 0x08)
{
temp = ~temp + 1;
smg1 = 1; //数码管1显示 - 符号
}else
smg1= 0; //数码管1不显示任何
}
return temp;
}
#include "sys.h"
// 0 1 2 3 4 5 6 7
uchar code nixie[] = {
0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
// 8 9 a b c d e f u
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xc1}; //共阳数码管码字
uchar NixieBuff[] = {
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};
uchar smg1,smg2,smg3,smg4,smg5,smg6,smg7,smg8;
uchar code Symbol[] = {
0xff,0xbf}; //全灭,-
void Nixie_Scan()
{
static uchar index;
HC138_Set(7);
P0 = 0xff;//消影
HC138_Set(6);
P0 = 0x01<<index;
HC138_Set(7);
P0 = NixieBuff[index];
HC138_Set(0);
index++;
index &= 0x07;
}
void Nixie_Show()
{
NixieBuff[0] = Symbol[smg1];
NixieBuff[1] = nixie[smg2];
NixieBuff[2] = nixie[smg3]&0x7f;
NixieBuff[3] = nixie[smg4];
NixieBuff[4] = nixie[smg5];
NixieBuff[5] = Symbol[smg6];
NixieBuff[6] = Symbol[smg7];
NixieBuff[7] = Symbol[smg8];
}
void Nixie_Drive(u16 dat)
{
smg2 = dat/100;
smg3 = dat%100/10;
smg4 = dat%10;
smg5 = 12;
smg6 = smg7 = smg8 = 0;
}
#include "sys.h" extern bit GET_TEMP_FLAG; /** *@brief 外设初始化 *@param[in] none *@return none **/ void ALL_Init() { P2 = (P2&0x1f)|0xa0; //打开Y5C P0 = 0x00; //关闭
蜂鸣器&继电器 P2 = (P2&0x1f)|0xe0; //打开Y7C P0 = 0xff; //关闭数码管 P2 = (P2&0x1f)|0x80; //打开Y4C P0 = 0xff; //关闭LED P2 = P2&0x1f; //关闭所用使能 } /** *@brief 延时函数 *@param[in] 延时多少ms(0~65535) *@return none **/ void Operate_Delay(u16 ms) { u16 i; for(ms;ms>0;ms--) for(i=921;i>0;i--); } /** *@brief 74HC138译码器通道选择 *@param[in] channel (通道) *@return none **/ void HC138_Set(u8 channel) { switch(channel) { case 0: P2 = P2 & 0x1f; break; //关闭所有通道 case 4: P2 = (P2&0x1f)|0x80; break; //选择Y4对应的模块 (LED), 运算结果为P2高三位 100 case 5: P2 = (P2&0x1f)|0xa0; break; //选择Y5对应的模块 (蜂鸣器), 运算结果为P2高三位 101 case 6: P2 = (P2&0x1f)|0xc0; break; //选择Y6对应的模块 (数码管位选), 运算结果为P2高三位 110 case 7: P2 = (P2&0x1f)|0xe0; break; //选择Y7对应的模块 (数码管段选), 运算结果为P2高三位 111 } } void Timer0Init(void) //1毫秒@11.0592MHz { AUXR |= 0x80; //定时器时钟1T模式 TMOD &= 0xF0; //设置定时器模式 TL0 = 0xCD; //设置定时初值 TH0 = 0xD4; //设置定时初值 TF0 = 0; //清除TF0标志 TR0 = 1; //定时器0开始计时 ET0 = 1; EA = 1; } void Timer0() interrupt 1 { static uint i; i++; if(i==1200) { i = 0; GET_TEMP_FLAG = 1; } Nixie_Show(); Nixie_Scan(); }