翻查DS18B20的DataSheet编写操作函数,其过程遇到了不少坎,记下来备查。
- 对于单总线的DS18B20芯片,首先严格按照时序图写出正确的“写0”、“写1”和“读0、1”的基础函数,再以此写出其他基础操作的命令。
我在编制“写0”函数的时候,省却了最后拉高总线的一句bus=1,结果后续操作过程中大部分功能都正确,只有写精度的部分总是出错,写进去9位的“0x1f”,总是得到10位的“0x3f”,翻论坛、看datasheet,反复调试,浪费的大量时间,最后才发现症结所在,并经过测试该错误得以重现。
sbit bus = P2^; //天祥板DS18B20的DQ数据脚直连P2^2 //写0
void DS18B20_WriteZero(void)
{
bus = ;
bus = ;
DelaySpecial(); //保持低电平时间60~120us,实际约71
bus = ; //就是这里!开始没有该语句
}
//写1
1void DS18B20_WriteOne()
{
bus = ;
bus = ;
_nop_(); //保持低电平时间大于1us
bus = ;
DelaySpecial();//在主设备初始化写后,DS18B20读的时间要持续15~60us,实际71us
}
/**********************************************************************************
*函数名称: unsigned char DS18B20_Init(void)
*函数描述: 每次执行ROM command之前,必须进行DS18B20初始化
*入口参数: 无
*出口参数: 1/0。1:失败;0:成功
*备 注: 严格执行时序图时间要求,若晶振不是11.0592MHz,需要从新设定各个等待时间
**********************************************************************************/
bit DS18B20_Init(void)
{
bus = ; //主设备发送复位脉冲(Tx)拉低单总线
DelaySpecial(); //最小480us,实际约500us
bus = ; //主设备放开总线进入接收模式(Rx)
//DS18B20检测到上升沿信号,等待15~60um后,拉低单总线60~240um,作为应答脉冲
DelaySpecial(); //此处取71us后
if(bus == ) //取样检测是否为低电平
{
bus = ; //释放总线
DelaySpecial(); //要求整个Master Rx周期的时间最小480us,此处补足所缺时间
return ;
}
else //此处可根据需要修改错误处理
{
DS18B20_ShowErrorCode(ERROR_MESSAGE_INIT_FAILURE); Beep();
return ;
}
}
/***************************************************************************
*函数名称: void DelaySpecial(unsigned char num)
*函数描述: 精确延时函数
*入口参数: 序号(unsigned char,<256)
*出口参数: 无
*备 注: 晶振11.0592,num为0时为13us,之后每加一,延长约10us
*num 延时 num 延时 num 延时 num 延时 num 延时
*0 13 10 109 20 208 30 305 40 401
*1 22 11 119 21 217 31 315 41 411
*2 31 12 129 22 227 32 325 42 421
*3 41 13 139 23 237 33 335 43 431
*4 51 14 148 24 247 34 344 44 441
*5 61 15 158 25 256 35 353 45 451
*6 71 16 168 26 266 36 363 46 461
*7 80 17 178 27 276 37 373 47 470
*8 90 18 187 28 286 38 383 48 480
*9 100 19 197 29 295 39 393 49 490
***************************************************************************/
void DelaySpecial(unsigned char num)
{
while(num--)_nop_();
}
/**********************************************************************************
*函数名称: bit DS18B20_ReadBit(void)
*函数描述: 主设备从DS18B20读数据,读出“1”或者“0”
*入口参数: 无
*出口参数: 读出的位
*备 注: 操作DS18B20的基础方法,每次读出一位
**********************************************************************************/
bit DS18B20_ReadBit(void)
{
bit result;
bus = ;
_nop_();//要不要均可
bus = ;
_nop_();//置低电平后至少保持1us
bus = ;
_nop_();//时序图推荐读总线的时间放在15us的最后,因此多加了几个_nop_()
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
result = bus;
DelaySpecial();//DS18B20提供的数据在主设备信号下降沿15us后可用,实际31us
return result;
}
/**********************************************************************************
*函数名称: unsigned char DS18B20_ReadByte(void)
*函数描述: 主设备从DS18B20读数据,读出一个字节
*入口参数: 无
*出口参数: 读出的字节
*备 注: 操作DS18B20的基础方法,每次读出一位
**********************************************************************************/
unsigned char DS18B20_ReadByte(void)
{
unsigned char byteResult, i;
bit result;
for(i=; i<; i++)
{
byteResult >>= ;
result = DS18B20_ReadBit();
byteResult |= (((unsigned char)result)<<);
}
return byteResult;
}
/**********************************************************************************
*函数名称: void DS18B20_WriteCommandOrByte(unsigned char data_)
*函数描述: 向DS18B20写一个字节数据
*入口参数: 要写入的字节数据
*出口参数: 无
*备 注: 操作DS18B20的基础方法,每次写入一个字节
**********************************************************************************/
void DS18B20_WriteCommandOrByte(unsigned char data_)
{
unsigned char i = ;
while(i--)
{
if(data_ & 0x01)
{
DS18B20_WriteOne();
}
else
{
DS18B20_WriteZero();
}
data_ >>= ;
}
}
DS18B20_WriteCommandOrByte()中使用到的预定义命令如下:
#define ROM_COMMAND_SEARCH_ROM 0xF0
#define ROM_COMMAND_READ_ROM 0x33
#define ROM_COMMAND_MATCH_ROM 0x55
#define ROM_COMMAND_SKIP_ROM 0xCC
#define ROM_COMMAND_ALARM_SEARCH 0xEC #define FUNCTION_COMMAND_CONVERT_T 0x44
#define FUNCTION_COMMAND_WRTIE_SCRATCHPAD 0x4E
#define FUNCTION_COMMAND_READ_SCRATCHPAD 0xBE
#define FUNCTION_COMMAND_COPY_SCRATCHPAD 0x48
#define FUNCTION_COMMAND_RECALL_EEPROM 0xB8
#define FUNCTION_COMMAND_READ_POWER_SUPPLY 0xB4
2.本来想封装大部分功能,方便使用时调用,但是该芯片的操作非常复杂,整来整去代码的体积太大。看来该芯片还是不太适合过多的封装,因此总结了该芯片使用方法的规律。
整个使用方法都集中在datasheet的“ROM Commands Flowchart”和“DS18B20 Function Commands Flowchart”两张图中,这是使用的核心所在。
我只调试了单个DS18B20采用单独供电的情况,这里有几点需要说明,备忘。
2.1 每次操作的顺序如下,使用哪个Rom Command,跟哪几个Function Command,顺序是什么,均根据功能需要,查阅这两个Flow Chart。
1) 初始化(DS18B20_Init())
2) 一个Rom Command(DS18B20_WriteCommandOrByte (**))
3) 连续多个Function Command(DS18B20_WriteCommandOrByte(**))
2.2 Rom Command共有5个。
DS18B20芯片接收到初始化命令(DS18B20_Init())之后,对再次接到信号逐次判断是33H、55H、F0H、ECH还是CCH,如果对上了哪一个,就往哪个分支上走去。如果对不上,该芯片会退回到初始状态,如果还想操作它,必须从初始化命令开始从新再来。
这5个Rom操作命令分别是:
a) 33H,Read Rom Command:该命令只能用在总线上只有一个从设备的情况,使主设备不经过Search Rom过程,直接读取从设备的64位Rom编码(从低到高分别是:类别信息,ID,CRC信息)
b) 55H,Match Rom Command:该命令后跟着发送64位Rom代码,表示要操作的对象
c) F0H,Search Rom Command:通过该命令,主设备获得总线上连接的所有从设备的信息
d) ECH,Alarm Search Command:搜寻温度报警命令
e) CCH,Skip Rom Command:跳过Rom操作命令,对于系统上只有一只单总线芯片的情况,发送该命令后,即可发送Function Command了。
2.3 Function Command共有六个。
如同Rom Command一样,芯片也是逐个比对功能命令,对上哪一个就往那里走去。
a) 44H,Convert Temperature:转化温度命令,DS18B20接到后开始进行温度转化,注意从9位精度一直到12位精度,转化所需的时间越来越长。这里有两种办法处理等待的时间:一个是,需根据所设的精度调整等待时间,见下图。第二个是,转化未完成前DS18B20一直将总线置低电平,可以用DS18B20_ReadBit()进行判断,如果读到高电平则说明转化已完成,否则返回继续等待;需要注意的是,为了防止出现意外情况需要设置一个等待时间限度,防止程序死锁,下面程序中给出循环次数15000次,大概2秒的时间。
/**********************************************************************************
*函数名称:float DS18B20_SingleAndExternalPowerGetT(void)
*函数描述:对于仅挂有1个DS18B20且使用外部电源供电的系统,取得测量的温度
*入口参数:无
*出口参数:是否成功,如果返回-100表示转化失败
*备 注:还有很大优化空间
*温度配置寄存器结构----------------------------------------------------------------
*bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
*0 R1 R0 1 1 1 1 1
*R1 R0 Resolution Max Conversion Time
*0 0 9-bit 93.75 ms (tCONV/8)
*0 1 10-bit 187.5 ms (tCONV/4)
*1 0 11-bit 375 ms (tCONV/2)
*1 1 12-bit 750 ms (tCONV)
*Th、Tl结构------------------------------------------------------------------------
* bit15 b14 b13 b12 b11 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b0
* S S S S S 2^6 2^5 2^4 2^3 2^2 2^1 2^0 2^-1 2^-2 2^-3 2^-4
* 9位 有效
* 10位 有效 有效
* 11位 有效 有效 有效
* 12位 有效 有效 有效 有效
* --------------------------------- -----------------------------------
* MS Byte(thTlCr[0]) LS Byte(thTlCr[1])
*S(signal):1=负,0=正
*bit10~bit4,共7位,代表整数部分
*bit3~bit0,共4位,代表小数部分
**********************************************************************************/
float DS18B20_SingleAndExternalPowerGetT()
{
unsigned int i = ; //见上面文字说明部分
unsigned char tem = ;
unsigned char tlTmCr[] = {};
float t;
DS18B20_Init();
DS18B20_WriteCommandOrByte(ROM_COMMAND_SKIP_ROM); //仅有一个测温器件的,先执行一下本命令
DS18B20_WriteCommandOrByte(FUNCTION_COMMAND_CONVERT_T);//发起温度转化命令
while(DS18B20_ReadBit()== || i--==); //见上面文字说明部分
if (DS18B20_GetTlTMCr(tlTmCr))
{
t = ((tlTmCr[] & 0x07) << ) | ((tlTmCr[] & 0xf0) >> );//得到温度的整数部分
if (tlTmCr[] & 0xf8)//前5位是1,代表温度为负
{
switch (tlTmCr[])//根据温度配置寄存器R1、R0的值确定转化的温度是9、10、11还是12的
{
case 0x1f:
t += (~((tlTmCr[] & 0x08) >> ) +) * 0.5;
break;
case 0x3f:
t += (~((tlTmCr[] & 0x0c) >> ) +) * 0.25;
break;
case 0x5f:
t += (~((tlTmCr[] & 0x0e) >> ) +) * 0.125;
break;
case 0x7f:
t += (~(tlTmCr[] & 0x0c) +) * 0.0625;
break;
}
t = -t;
}
else
{
switch (tlTmCr[])//根据温度配置寄存器R1、R0的值确定转化的温度是9、10、11还是12的
{
case 0x1f:
t += ((tlTmCr[] & 0x08)>>) * 0.5;
break;
case 0x3f:
t += ((tlTmCr[] & 0x0c)>>) * 0.25;
break;
case 0x5f:
t += ((tlTmCr[] & 0x0e)>>) * 0.125;
break;
case 0x7f:
t += (tlTmCr[] & 0x0f) * 0.0625;
break;
}
}
return t;
}
return -;
}
/**********************************************************************************
*函数名称: bit DS18B20_GetTlTMCr(unsigned char * thTlCr)
*函数描述: 取得Th和Tl的值
*入口参数: 指向16位温度寄存器低8位、高8位和位数配置寄存器数组的指针
*出口参数: 无
*备 注: 入口参数的指针指向连续三个字节,分别是Tlsb、Tmsb、ConfigurationRegister
**********************************************************************************/
bit DS18B20_GetTlTMCr(unsigned char *tlTmCr)
{
unsigned char scratchPad[] = {};
if(DS18B20_GetScrtchPad(scratchPad)) //最高位为0,代表crc验证错误,Th Tl均直接返回错误代码0
{
*tlTmCr = scratchPad[];
*(tlTmCr+) = scratchPad[];
*(tlTmCr+) = scratchPad[];
return ;
}
else
{
return ;
}
}
b) 48H,Copy ScratchPad:将温度上下限和配置寄存器的内容拷贝到EEPROM
c) 4EH,Write ScratchPad:主设备依次写入TH(Byte2,温度报警高限)、TL(Byte3,温度报警低限)和温度配置寄存器(Byte4)。
/**********************************************************************************
*函数名称: void DS18B20_Config(char tHighAlarm, char tLowAlarm, unsigned char bits)
*函数描述: 写高低报警温度和温度配置寄存器
*入口参数: tHighAlarm、tLowAlarm带符号数(只能设置成整温度报警值),bits=9、10、11、12位
*出口参数: 无
*备 注:
*THAlarm and TLAlarm Register
*BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0
*Sign 2^6 2^5 2^4 2^3 2^2 2^1 2^0
**********************************************************************************/
void DS18B20_Config(char tLowAlarm, char tHighAlarm, unsigned char bits)
{
unsigned char code arrayBit[] = { 0x1f, 0x3f, 0x5f, 0x7f };
DS18B20_Init();
DS18B20_WriteCommandOrByte(ROM_COMMAND_SKIP_ROM);//cc
DS18B20_WriteCommandOrByte(FUNCTION_COMMAND_WRTIE_SCRATCHPAD);//4e
DS18B20_WriteCommandOrByte(tHighAlarm);
DS18B20_WriteCommandOrByte(tLowAlarm);
DS18B20_WriteCommandOrByte(arrayBit[bits - ]);
}
d) BEH,Read ScratchPad:依次读取ScratchPad内容,掌握转化的温度、上下限温度报警值以及精度配置寄存器的内容都需要执行该操作。
/**********************************************************************************
*函数名称: bit DS18B20_ReadScrtchPad(*scrtchPad)
*函数描述: 读取ScrtchPad的内容
*入口参数: 指向ScrtchPad的指针,ScrtchPad本身长9字节
*出口参数: 读Scratchp是否成功,1=成功,0=失败
*备 注: 无
**********************************************************************************/
bit DS18B20_GetScrtchPad(unsigned char *scrtchPad)
{
unsigned char i;
if (!DS18B20_Init()) return ;
DS18B20_WriteCommandOrByte(ROM_COMMAND_SKIP_ROM);
DS18B20_WriteCommandOrByte(FUNCTION_COMMAND_READ_SCRATCHPAD);
for(i = ; i<; i++)
{
*(scrtchPad + i) = DS18B20_ReadByte();
}
if(calcrc_bytes(scrtchPad, ))
return ;
return ;
}
/********************************************************/
/*DS18B20的CRC8校验程序,抄来的,仅根据习惯修改了返回值1代表成功,0代表失败
/********************************************************/
unsigned char calcrc_1byte(unsigned char abyte)
{
unsigned char i,crc_1byte;
crc_1byte=;
//设定crc_1byte初值为0
for(i = ; i < ; i++)
{
if(((crc_1byte^abyte)&0x01))
{
crc_1byte^=0x18;
crc_1byte>>=;
crc_1byte|=0x80;
}
else
crc_1byte>>=;
abyte>>=;
}
return crc_1byte;
}
/***************************************************************************
*函数名称: bit calcrc_bytes(unsigned char *p,unsigned char len)
*函数描述: CRC校验
*入口参数: 指向最末一字节是CRC信息的指针;长度
*出口参数: 是否成功
*备 注: 1=校验成功;0=校验失败
***************************************************************************/
bit calcrc_bytes(unsigned char *p,unsigned char len)
{
unsigned char crc=;
while(len--) //len为总共要校验的字节数
{
crc=calcrc_1byte(crc^*p++);
}
if(crc)//如果crc不等于0,数据传输错误
{
return ;
}
else
{
return ; //若最终返回的crc为0,则数据传输正确
}
}
ScratchPad从0~8共9个字节,其结构见下图,最后一个字节是前8个字节的CRC信息。如果不做CRC校验的话,读到所需的字节后,可以发送一个初始化信号DS18B20_Init()后,终止后面的读取动作。
温度转化完成后就存储在ScrathPad的第0、1字节中。第1个字节是温度的高8位,第0个字节是温度的低8位。
- 高8位的高5位代表正负号(Bit15~Bit11),正温度时均为0,负温度时均为1;
- 高8位的低3位(Bit10~Bit8)和低8位的高4位(Bit7~Bit4)组合成温度的整数部分(共7位),无论温度为正、为负,这7位直接就是温度的整数部分;
- 低8位的低4位(Bit3~Bit0)是温度的小数部分,注意:温度为正时直接换算即可,当温度为负时,小数部分要取反、加一后再换算。
- 9位精度时 :只用Bit3,1代表0.5℃
- 10位精度时:使用Bit3、Bit2,1代表0.25℃,如该两位为10B时,温度为2*0.25=0.50℃
- 11位精度时:使用Bit3、Bit2和Bit1,1代表0.125℃,如该三位为101B时,温度为5*0.125=0.625℃
- 12位精度时:Bit3~Bit0都使用,1代表0.0625℃
- 如该四位为1011B时,温度为11*0.0625=0.6875℃
- 在温度为负时,同样的1011B,先取反得到0100B,再加一得到0101B,温度为-5*0.0625 = -0.3125℃
e) B8H,Recall EEPROM:将存储到EEPROM中的上下限报警温度和温度精度配置寄存器的内容,写回到ScratchPad中,相当于48H号命令Copy ScratchPad的逆操作。
f) B4H,Read Power Supply:检查从设备是独立供电模式,还是寄生电源模式。
ROM Commands Flowchart
DS18B20 Function Commands Flowchart