网上DS18B20的驱动实现代码一大堆,简简单单的就能够移植成功,获得温度值,但是为什么代码这么写?为什么要延时那么长的时间?不对照手册仔细分析时序图,还真是不明白为什么。
下面我们就来详细剖析一下DS18B20的驱动函数的实现。
DS18B20 简介
DS18B20数字温度传感器是美国DALLAS公司生产的单总线数字温度传感器。其测温范围为-55℃~+125℃(-67℉~+257℉),64位只读存储器的片序列号。从而允许多只DS18B20同时并联在一根单线总线上;
华氏度和摄氏度换算关系:
(华氏度-32)×5÷9=摄氏度
DS18B20可以用一个微控制器的GPIO引脚去控制。器件内部高速暂存器区含有两个字节的温度寄存器,用来存储温度传感器输出的数据。
此外,高速暂存器区还有上下温度报警寄存器(TH和TL),和一个字节的配置寄存器。
配置寄存器允许用户将温度的精度设置为9~12位对应的分辨率为0.5℃、0.25℃、0.125℃、0.0625℃。上电默认为12位转换精度。
- DS18B20存储器图
实际使用中,当只是为了测温,只需用到字节1,字节2和字节5。应用环境中如果没有强干扰,不是十分严格的话,不做校验也可以。
其中,暂存寄存器中的字节5包含着配置寄存器,配置寄存器内容如下图所示:
用户通过改变上表中R0和R1的值来配置DS18B20的分辨率。上电默认为R0=1及R1=1(12位分辨率)。
- 温度/数据对应表:
温度转换后,温度转换的值将会保存在暂存存储器的温度寄存器中,并且DS18B20将会恢复到闲置状态。
TH,TL和配置寄存器是EEPROM,存储的数据在器件掉电时不会消失。
DS18B20的另一个功能是可以在没有外部电源供电的情况下工作。当总线处于高电平状态,DQ与上拉电阻连接通过单总线对器件供电。
同时处于高电平状态的总线信号对内部电容(Cpp)充电,在总线处于低电平状态时,该电容提供能量给器件。
这种提供能量的形式被称为“寄生电源”;
寄生电源模式时,VDD引脚必须接地。
- “寄生电源”供电方式
- 外部电源供电方式
原理图
外观及封装
- TO-92封装
- 防水型不锈钢封装
采用导热性高的密封胶灌封,保证了温度传感器的高灵敏性,极小的温度延迟。
芯片每个引脚均用热缩管隔开,防止短路,内部封胶,防水防潮。
引脚说明红线:VCC绿线:GND黄线:DQ,传感器数据总线
驱动实现
INITIALIZATION TIMING
在初始化序列期间,总线控制器拉低总线并保持至少480us以发送一个复位脉冲,返回释放总线,进入接收状态(等待DS18B20应答)。
总线释放后,单总线由上拉电阻拉到高电平。
当DS18B20探测到I/O引脚上的上升沿后,等待15-60us,然后其以拉低总线60-240us的方式发出存在脉冲。至此,初始化时序完毕。
所以,初始化成功的标志就是能否读到DS18B20这个先低后高的脉冲时序,并且拉低的时间要满足60-240us。
复位DS18B20的代码如下:
//复位DS18B20
void DS18B20_Rst(void)
{
DS18B20_IO_OUT(); //总线设置为输出模式
DS18B20_DQ_OUT=0; //主机拉低总线
delay_us(750);
DS18B20_DQ_OUT=1; //释放总线,产生的上升沿能被DS18B20检测到
delay_us(15); //延时15us之后,等待DS18B20发送的低电平信号到达。
}
//等待DS18B20的回应
//返回1:未检测到DS18B20的存在
//返回0:存在
u8 DS18B20_Check(void)
{
u8 retry=0;
DS18B20_IO_IN(); //总线设置为输入模式
while (DS18B20_DQ_IN&&retry<200) //等待拉低总线60-240us的低电平
{
retry++;
delay_us(1);
};
if(retry>=200)return 1;
else retry=0;
while (!DS18B20_DQ_IN&&retry<240)
{
retry++;
delay_us(1);
};
if(retry>=240)
return 1;
return 0;
}
READ/WRITE TIME SLOT TIMING DIAGRAM
DS18B20的写时序(见下图):
主机在写时隙向DS18B20写入数据,其中分为写”0”时隙,和写”1”时隙。总线主机使用写“1”时间隙向DS18B20写入逻辑1,使用写“0”时间隙向DS18B20写入逻辑0。
所有的写时隙必须有最少60us的持续时间,相邻两个写时隙必须要有最少1us的恢复时间。两种写时隙都通过主机拉低总线产生(见下图)。
为了产生写1时隙,在拉低总线后主机必须在15μs内释放总线。在总线被释放后,由于上拉电阻将总线恢复为高电平。
为了产生写”0”时隙,在拉低总线后主机必须继续拉低总线以满足时隙持续时间的要求(至少60μs)。
在主机产生写时隙后,DS18B20会在其后的15~60us的一个时间段内采样单总线(DQ)。在采样的时间窗口内,如果总线为高电平,主机会向DS18B20写入1;如果总线为低电平,主机会向DS18B20写入0。
综上所述,所有的写时隙必须至少有60us的持续时间。相邻两个写时隙必须要有最少1us的恢复时间。所有的写时隙(写0和写1)都由拉低总线产生。
//写一个字节到DS18B20
//dat:要写入的字节
void DS18B20_Write_Byte(u8 dat)
{
u8 j;
u8 testb;
DS18B20_IO_OUT(); //设置DQ为输出模式
for (j=1;j<=8;j++)
{
testb=dat&0x01;
dat=dat>>1;
if (testb)
{
DS18B20_DQ_OUT=0;// Write 1
delay_us(2);
DS18B20_DQ_OUT=1;
delay_us(60);
}
else
{
DS18B20_DQ_OUT=0;// Write 0
delay_us(60); // 等待DS18B20来采集信号
DS18B20_DQ_OUT=1;
delay_us(2);
}
}
}
DS18B20的读时序(见下图):
主机发起读时序时,DS18B20仅被用来传输数据给控制器。因此,总线控制器在发出读指令后必须立刻开始读时序。
所有读时序必须最少60us,包括两个读周期间至少1us的恢复时间。
当总线控制器把数据线从高电平拉到低电平时,读时序开始,数据线必须至少保持1us,然后总线被释放。
DS18B20 通过拉高或拉低总线来传输”1”或”0”。
当传输逻辑”0”结束后,总线将被释放,通过上拉电阻回到上升沿状态。
从DS18B20输出的数据在读时序的下降沿出现后15us 内有效。因此,总线控制器在读时序开始后必须把I/O口设置为输入模式,以读取I/O口状态。
阴影部分为DS18B20释放总线的时刻,总线为空闲状态。
//从DS18B20读取一个位
//返回值:1/0
u8 DS18B20_Read_Bit(void) // read one bit
{
u8 data;
DS18B20_IO_OUT(); //设置总线为输出模式
DS18B20_DQ_OUT=0;
delay_us(2); //拉低最少1us
DS18B20_DQ_OUT=1; //拉低再升高,产生读时序
DS18B20_IO_IN(); //设置总线为输入模式
delay_us(12);
if(DS18B20_DQ_IN)data=1;
else data=0;
delay_us(50);
return data;
}
//从DS18B20读取一个字节
//返回值:读到的数据
u8 DS18B20_Read_Byte(void) // read one byte
{
u8 i,j,dat;
dat=0;
for (i=1;i<=8;i++)
{
j=DS18B20_Read_Bit();
dat=(j<<7)|(dat>>1);
}
return dat;
}
- DS18B20的功能命令
- 获取温度值
要获取温度值,我们需要发送上面功能命令0x44,然后发送读取暂存寄存器命令0xBE,然后我们只需要获得暂存器中的9个字节的前两个字节即可。
要获得DS18B20的温度值,需要按照下表中的顺序依次发送功能命令。
获取温度的具体代码实现如下:
//开始温度转换
void DS18B20_Start(void)// ds18b20 start convert
{
DS18B20_Rst();
DS18B20_Check();
DS18B20_Write_Byte(0xcc);// skip rom
DS18B20_Write_Byte(0x44);// convert
}
//从ds18b20得到温度值
//精度:0.1C
//返回值:温度值 (-550~1250)
short DS18B20_Get_Temp(void)
{
u8 temp;
u8 TL,TH;
short tem;
DS18B20_Start (); // ds18b20 start convert
DS18B20_Rst();
DS18B20_Check();
DS18B20_Write_Byte(0xcc);// skip rom
DS18B20_Write_Byte(0xbe);// convert
TL=DS18B20_Read_Byte(); // LSB
TH=DS18B20_Read_Byte(); // MSB
if(TH>7)
{
TH=~TH;
TL=~TL;
temp=0;//温度为负
}else temp=1;//温度为正
tem=TH; //获得高八位
tem<<=8;
tem+=TL;//获得底八位
tem=(float)tem*0.625;//转换
if(temp)return tem; //返回温度值
else return -tem;
}
跳过ROM序列号检测命令(0xCCH),对于单片DS18B20在线的系统,该命令允许主机跳过ROM序列号检测而直接对寄存器操作,从而节省时间,对于多片DS18B20在线系统,该命令将引起数据冲突。
如果主机只是对一个DS18B20进行操作,进而不需要读取ROM编码了,只要发送跳过ROM(0xCCH)命令,就可以进行温度转换和读取操作了。
- 获取DS18B20内部ID序列号
因为咱们总线上只有一个DS18B20设备,所以直接发送0x33指令即可READ ROM。
DS18B20中有一个64位光刻ROM,按说明书说法,开始(最低)8位是产品类型标号,对于DS18B20来说都是(28H),接着的48位是该DS18B20自身的序列号,最后8位是前面56位的循环冗余校验码(CRC=X8+X5+X4+1)。
光刻ROM的作用是使每一个DS18B20都各不相同,这样就可以实现一根总线上挂接多个DS18B20的目的。
读取ROM方法:先复位DS18B20,成功后执行读取ROM命令(33H),然后将这64位以8个字节的方式存入数组。
获取DS18B20内部ID序列号的具体代码实现如下:
DS18B20_Rst();
DS18B20_Check();
DS18B20_Write_Byte(0x33);
for(i = 0; i < 8;i++)
{
arrDS18B20ID[i] = DS18B20_Read_Byte();
}
sprintf((char *)dtbuf,"DS18B20 ID is %02X %02X %02X %02X %02X %02X %02X %02X\r\n", arrDS18B20ID[0], arrDS18B20ID[1], arrDS18B20ID[2], arrDS18B20ID[3], arrDS18B20ID[4], arrDS18B20ID[5], arrDS18B20ID[6], arrDS18B20ID[7]);
printf((u8 *)dtbuf,strlen((char *)dtbuf));
我手里的两个DS18B20得到的结果如下所示:
由上可以看出,首字节都是0x28,即产品类型都是一样的。