很多人用51第一次驱动DS18B20或多或少都会遇到一些问题。可能读出来是85.0,可能是e7.5(字符液晶显示),就是得不到正确值。而出现这些情况,基本上是时序错误导致(当然DS18B20损坏也会导致读出85)
一般人喜欢直接把网上的驱动直接复制过来用,这也是导致时序出错的重要原因,因为对于DS18B20这种单总线操作的器件,其时序间隙是以微秒(us)计算的,而大部分都是使用一个While(x--)的方式来得到延时时间,这样对于不同系列的
单片机,不同晶振都会导致延时时间发生变化,从而导致时序出错。使用STC系列单片机的,即使是同一个晶振下,不同系列(如12系列和89系列)也会导致该延时函数执行的时间不同,通常情况下该演示程序的执行时间会有几倍等级的变化。
如何解决这个问题呢?以下是我的个人经验了。只提供大概思想,具体还是留给各位发挥吧
首先,假定我有一个延时程序,延时时间就为1us*倍数(void Delay_us(unsigned char i)),最大为255 us。为什么不直接定义一个iunsigned nt型变量呢?这样不就可以延时65535us吗?实际上,DS18B20的时序间隙最大也就650us左右,如果使用unsigned int型变量一方面浪费内存,一方面增加延时时间确定的难易程度。
按DS18B20的说明手册编写DS18B20的读写和复位时序。时序间隙时间都取要求时间间隙的中间值偏小一点。很多人认为自己写时序很困难,实际上,只有写过的人才清楚,DS18B20的时序真的很简单。
最后写1us产生时间。我一般是这样确定的:写一个while(x--)的延时函数,如
void Delay_Temp() //?个机器周期*a (标示执行一次的时钟周期方便以后不同频率时计算)
{
unsigned chat a=1;
while(a--);
}
将a赋值为1,在main函数中调用该函数,编译后进入调试,单步执行,查看该子程序的汇编语句,查看单片机手册各汇编指令执行周期,将执行一次循环体的总时间周期标志在该函数名后注释。根据晶振计算一下执行一次的时间,用1us除去即得到a值,把a重新赋值,那么这个函数就是近似1us的程序了(对于1T单片机并且时钟大于10MHz,若是12T或6T时钟频率又较低的最好考虑进Call和RET指令的时间)。
最后再编写一个
Void Delay_us(unsigned char Set_us)
{
while(Set_us--)
Delay_Temp();
}
以后换单片机(仅限于51)只要计算一下Delay_Temp的a值使该函数为1us即可。
我使用以上的办法,在STC不同系列不同晶振的硬件中都完美驱动DS18B20,而且改动程序不超过5秒(只是计算一下a值而已)
最后说一下为什么把DS18B20时间间隙设置为中间值偏小一点?很明显在之前延时函数时间的确定中,尤其是Delay_us中也是不断地循环调用Delay_Temp,会有额外的LCALL等指令时间。产生额外延时时间。若要计算得非常准确,可以在Delay_Temp的计算中加入进入该函数的LACLL等指令的值,以提高准确度。
另外再提一下,有些人得到温度是使用浮点数计算,这是非常浪费资源的做法,通常我们都是使用乘法加移位的办法获得最终温度数据(定点小数,即获取的温度为整型值,显示时认为加小数点),这样代码量起码减少1KB。此外,温度是有符号数,这点很多人经常忽略了。
还有,有人担心DS18B20自身发热会不会影响测量结果。经过实验对比,没什么影响
DS18B20上电后第一次可能读出来是85,这是正常的,可通过软件方法屏蔽。
使读出数据波动不大的办法,我采用的办法是连续读两次,若两次读取结果相同,则更新温度,而不是采用均值的办法。
以上仅是个人一点经验,欢迎大家指正
5