翻查DS18B20的DataSheet编写操作函数,其过程遇到了不少坎,记下来备查。
- 对于单总线的DS18B20芯片,首先严格按照时序图写出正确的“写0”、“写1”和“读0、1”的基础函数,再以此写出其他基础操作的命令。
我在编制“写0”函数的时候,省却了最后拉高总线的一句bus=1,结果后续操作过程中大部分功能都正确,只有写精度的部分总是出错,写进去9位的“0x1f”,总是得到10位的“0x3f”,翻论坛、看datasheet,反复调试,浪费的大量时间,最后才发现症结所在,并经过测试该错误得以重现。
1 sbit bus = P2^2; //天祥板DS18B20的DQ数据脚直连P2^2 2 3 //写0 4 void DS18B20_WriteZero(void) 5 { 6 bus = 1; 7 bus = 0; 8 DelaySpecial(6); //保持低电平时间60~120us,实际约71 9 bus = 1; //就是这里!开始没有该语句 10 }
//写1
1void DS18B20_WriteOne() 2 { 3 bus = 1; 4 bus = 0; 5 _nop_(); //保持低电平时间大于1us 6 bus = 1; 7 DelaySpecial(6);//在主设备初始化写后,DS18B20读的时间要持续15~60us,实际71us 8 }
1 /********************************************************************************** 2 *函数名称: unsigned char DS18B20_Init(void) 3 *函数描述: 每次执行ROM command之前,必须进行DS18B20初始化 4 *入口参数: 无 5 *出口参数: 1/0。1:失败;0:成功 6 *备 注: 严格执行时序图时间要求,若晶振不是11.0592MHz,需要从新设定各个等待时间 7 **********************************************************************************/ 8 bit DS18B20_Init(void) 9 { 10 bus = 0; //主设备发送复位脉冲(Tx)拉低单总线 11 DelaySpecial(50); //最小480us,实际约500us 12 bus = 1; //主设备放开总线进入接收模式(Rx) 13 //DS18B20检测到上升沿信号,等待15~60um后,拉低单总线60~240um,作为应答脉冲 14 DelaySpecial(6); //此处取71us后 15 if(bus == 0) //取样检测是否为低电平 16 { 17 bus = 1; //释放总线 18 DelaySpecial(42); //要求整个Master Rx周期的时间最小480us,此处补足所缺时间 19 return 1; 20 } 21 else //此处可根据需要修改错误处理 22 { 23 DS18B20_ShowErrorCode(ERROR_MESSAGE_INIT_FAILURE); 24 Beep(); 25 return 0; 26 } 27 }
1 /*************************************************************************** 2 *函数名称: void DelaySpecial(unsigned char num) 3 *函数描述: 精确延时函数 4 *入口参数: 序号(unsigned char,<256) 5 *出口参数: 无 6 *备 注: 晶振11.0592,num为0时为13us,之后每加一,延长约10us 7 *num 延时 num 延时 num 延时 num 延时 num 延时 8 *0 13 10 109 20 208 30 305 40 401 9 *1 22 11 119 21 217 31 315 41 411 10 *2 31 12 129 22 227 32 325 42 421 11 *3 41 13 139 23 237 33 335 43 431 12 *4 51 14 148 24 247 34 344 44 441 13 *5 61 15 158 25 256 35 353 45 451 14 *6 71 16 168 26 266 36 363 46 461 15 *7 80 17 178 27 276 37 373 47 470 16 *8 90 18 187 28 286 38 383 48 480 17 *9 100 19 197 29 295 39 393 49 490 18 ***************************************************************************/ 19 void DelaySpecial(unsigned char num) 20 { 21 while(num--)_nop_(); 22 }
/********************************************************************************** *函数名称: bit DS18B20_ReadBit(void) *函数描述: 主设备从DS18B20读数据,读出“1”或者“0” *入口参数: 无 *出口参数: 读出的位 *备 注: 操作DS18B20的基础方法,每次读出一位 **********************************************************************************/ bit DS18B20_ReadBit(void) { bit result; bus = 1; _nop_(); bus = 0; _nop_();//置低电平后至少保持1us bus = 1; _nop_();//时序图推荐读总线的时间放在15us的最后,因此多加了几个_nop_() _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); result = bus; DelaySpecial(4);//DS18B20提供的数据在主设备信号下降沿15us后可用,实际31us return result; }
1 /********************************************************************************** 2 *函数名称: unsigned char DS18B20_ReadByte(void) 3 *函数描述: 主设备从DS18B20读数据,读出一个字节 4 *入口参数: 无 5 *出口参数: 读出的字节 6 *备 注: 操作DS18B20的基础方法,每次读出一位 7 **********************************************************************************/ 8 unsigned char DS18B20_ReadByte(void) 9 { 10 unsigned char byteResult, i; 11 bit result; 12 for(i=0; i<8; i++) 13 { 14 byteResult >>= 1; 15 16 result = DS18B20_ReadBit(); 17 byteResult |= (((unsigned char)result)<<7); 18 19 } 20 return byteResult; 21 }
1 /********************************************************************************** 2 *函数名称: void DS18B20_WriteCommandOrByte(unsigned char data_) 3 *函数描述: 向DS18B20写一个字节数据 4 *入口参数: 要写入的字节数据 5 *出口参数: 无 6 *备 注: 操作DS18B20的基础方法,每次写入一个字节 7 **********************************************************************************/ 8 void DS18B20_WriteCommandOrByte(unsigned char data_) 9 { 10 unsigned char i = 8; 11 while(i--) 12 { 13 if(data_ & 0x01) 14 { 15 DS18B20_WriteOne(); 16 } 17 else 18 { 19 DS18B20_WriteZero(); 20 } 21 data_ >>= 1; 22 } 23 }
DS18B20_WriteCommandOrByte()中使用到的预定义命令如下:
1 #define ROM_COMMAND_SEARCH_ROM 0xF0 2 #define ROM_COMMAND_READ_ROM 0x33 3 #define ROM_COMMAND_MATCH_ROM 0x55 4 #define ROM_COMMAND_SKIP_ROM 0xCC 5 #define ROM_COMMAND_ALARM_SEARCH 0xEC 6 7 #define FUNCTION_COMMAND_CONVERT_T 0x44 8 #define FUNCTION_COMMAND_WRTIE_SCRATCHPAD 0x4E 9 #define FUNCTION_COMMAND_READ_SCRATCHPAD 0xBE 10 #define FUNCTION_COMMAND_COPY_SCRATCHPAD 0x48 11 #define FUNCTION_COMMAND_RECALL_EEPROM 0xB8 12 #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 = 15000; //见上面文字说明部分 unsigned char tem = 0; unsigned char tlTmCr[3] = {0}; float t; DS18B20_Init(); DS18B20_WriteCommandOrByte(ROM_COMMAND_SKIP_ROM); //仅有一个测温器件的,先执行一下本命令 DS18B20_WriteCommandOrByte(FUNCTION_COMMAND_CONVERT_T);//发起温度转化命令 while(DS18B20_ReadBit()==0 || i--==0); //见上面文字说明部分 if (DS18B20_GetTlTMCr(tlTmCr)) { t = ((tlTmCr[1] & 0x07) << 4) | ((tlTmCr[0] & 0xf0) >> 4);//得到温度的整数部分 if (tlTmCr[1] & 0xf8)//前5位是1,代表温度为负 { switch (tlTmCr[2])//根据温度配置寄存器R1、R0的值确定转化的温度是9、10、11还是12的 { case 0x1f: t += (~((tlTmCr[0] & 0x08) >> 3) +1) * 0.5; break; case 0x3f: t += (~((tlTmCr[0] & 0x0c) >> 2) +1) * 0.25; break; case 0x5f: t += (~((tlTmCr[0] & 0x0e) >> 1) +1) * 0.125; break; case 0x7f: t += (~(tlTmCr[0] & 0x0c) +1) * 0.0625; break; } t = -t; } else { switch (tlTmCr[2])//根据温度配置寄存器R1、R0的值确定转化的温度是9、10、11还是12的 { case 0x1f: t += ((tlTmCr[0] & 0x08)>>3) * 0.5; break; case 0x3f: t += ((tlTmCr[0] & 0x0c)>>2) * 0.25; break; case 0x5f: t += ((tlTmCr[0] & 0x0e)>>1) * 0.125; break; case 0x7f: t += (tlTmCr[0] & 0x0f) * 0.0625; break; } } return t; } return -100; }
1 /********************************************************************************** 2 *函数名称: bit DS18B20_GetTlTMCr(unsigned char * thTlCr) 3 *函数描述: 取得Th和Tl的值 4 *入口参数: 指向16位温度寄存器低8位、高8位和位数配置寄存器数组的指针 5 *出口参数: 无 6 *备 注: 入口参数的指针指向连续三个字节,分别是Tlsb、Tmsb、ConfigurationRegister 7 **********************************************************************************/ 8 bit DS18B20_GetTlTMCr(unsigned char *tlTmCr) 9 { 10 unsigned char scratchPad[9] = {0}; 11 if(DS18B20_GetScrtchPad(scratchPad)) //最高位为0,代表crc验证错误,Th Tl均直接返回错误代码0 12 { 13 *tlTmCr = scratchPad[0]; 14 *(tlTmCr+1) = scratchPad[1]; 15 *(tlTmCr+2) = scratchPad[4]; 16 return 1; 17 } 18 else 19 { 20 return 0; 21 } 22 }
b) 48H,Copy ScratchPad:将温度上下限和配置寄存器的内容拷贝到EEPROM
c) 4EH,Write ScratchPad:主设备依次写入TH(Byte2,温度报警高限)、TL(Byte3,温度报警低限)和温度配置寄存器(Byte4)。
1 /********************************************************************************** 2 *函数名称: void DS18B20_Config(char tHighAlarm, char tLowAlarm, unsigned char bits) 3 *函数描述: 写高低报警温度和温度配置寄存器 4 *入口参数: tHighAlarm、tLowAlarm带符号数(只能设置成整温度报警值),bits=9、10、11、12位 5 *出口参数: 无 6 *备 注: 7 *THAlarm and TLAlarm Register 8 *BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0 9 *Sign 2^6 2^5 2^4 2^3 2^2 2^1 2^0 10 **********************************************************************************/ 11 void DS18B20_Config(char tLowAlarm, char tHighAlarm, unsigned char bits) 12 { 13 unsigned char code arrayBit[] = { 0x1f, 0x3f, 0x5f, 0x7f }; 14 DS18B20_Init(); 15 DS18B20_WriteCommandOrByte(ROM_COMMAND_SKIP_ROM);//cc 16 DS18B20_WriteCommandOrByte(FUNCTION_COMMAND_WRTIE_SCRATCHPAD);//4e 17 DS18B20_WriteCommandOrByte(tHighAlarm); 18 DS18B20_WriteCommandOrByte(tLowAlarm); 19 DS18B20_WriteCommandOrByte(arrayBit[bits - 9]); 20 }
d) BEH,Read ScratchPad:依次读取ScratchPad内容,掌握转化的温度、上下限温度报警值以及精度配置寄存器的内容都需要执行该操作。
1 /********************************************************************************** 2 *函数名称: bit DS18B20_ReadScrtchPad(*scrtchPad) 3 *函数描述: 读取ScrtchPad的内容 4 *入口参数: 指向ScrtchPad的指针,ScrtchPad本身长9字节 5 *出口参数: 读Scratchp是否成功,1=成功,0=失败 6 *备 注: 无 7 **********************************************************************************/ 8 bit DS18B20_GetScrtchPad(unsigned char *scrtchPad) 9 { 10 unsigned char i; 11 if (!DS18B20_Init()) return 0; 12 DS18B20_WriteCommandOrByte(ROM_COMMAND_SKIP_ROM); 13 DS18B20_WriteCommandOrByte(FUNCTION_COMMAND_READ_SCRATCHPAD); 14 for(i = 0; i<9; i++) 15 { 16 *(scrtchPad + i) = DS18B20_ReadByte(); 17 } 18 if(calcrc_bytes(scrtchPad, 9)) 19 return 1; 20 return 0; 21 }
1 /********************************************************/ 2 /*DS18B20的CRC8校验程序,抄来的,仅根据习惯修改了返回值1代表成功,0代表失败 3 /********************************************************/ 4 unsigned char calcrc_1byte(unsigned char abyte) 5 { 6 unsigned char i,crc_1byte; 7 crc_1byte=0; 8 //设定crc_1byte初值为0 9 for(i = 0; i < 8; i++) 10 { 11 if(((crc_1byte^abyte)&0x01)) 12 { 13 crc_1byte^=0x18; 14 crc_1byte>>=1; 15 crc_1byte|=0x80; 16 } 17 else 18 crc_1byte>>=1; 19 abyte>>=1; 20 } 21 return crc_1byte; 22 } 23 /*************************************************************************** 24 *函数名称: bit calcrc_bytes(unsigned char *p,unsigned char len) 25 *函数描述: CRC校验 26 *入口参数: 指向最末一字节是CRC信息的指针;长度 27 *出口参数: 是否成功 28 *备 注: 1=校验成功;0=校验失败 29 ***************************************************************************/ 30 bit calcrc_bytes(unsigned char *p,unsigned char len) 31 { 32 unsigned char crc=0; 33 while(len--) //len为总共要校验的字节数 34 { 35 crc=calcrc_1byte(crc^*p++); 36 } 37 if(crc)//如果crc不等于0,数据传输错误 38 { 39 return 0; 40 } 41 else 42 { 43 return 1; //若最终返回的crc为0,则数据传输正确 44 } 45 }
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