DS18B20是单总线的温度传感器,在使用MCU对其进行操作的时候,往往使用模拟单总线时序来进行读写,下面是驱动。
1.复位
时序图
static HRINT8U DS18B20_Reset(void)
{
HRINT8U status;
DS18B20_SetDataOut();//数据线配成输出
DS18B20_DATA_HIGH(); //拉高总线
DS18B20_Delayus(2);//延时2us
DS18B20_DATA_LOW();//拉低总线
DS18B20_Delayus(500);//延时500us,时序图规定最小480us
DS18B20_SetDataIn();//总线配置成输入
DS18B20_Delayus(70);//延时70us,时序图上规定等待时间15-60us
if(DS18B20_ReadDataBit() == 0)//读总线上电平,判断DS18B20是否有回复
{
status = 1;//有回复
}
else
{
status = 0;//无回复
}
DS18B20_Delayus(410);//延时410us
return(status);//返回复位的状态
}
2.主机发送一个字节
时序图
static void DS18B20_WriteDataByte(HRINT8U pWrite)
{
HRINT8U i,WriteData;
WriteData = pWrite;
DS18B20_SetDataOut();//配置成输出
for(i=0;i<8;i++)//循环发送8位
{
DS18B20_DATA_HIGH();//拉高总线
DS18B20_Delayus(2);
DS18B20_DATA_LOW();//拉低总线
DS18B20_Delayus(2);
if(WriteData&0x01)//先发低位,为1
{
DS18B20_DATA_HIGH();//拉高总线
}
else//为0
{
DS18B20_DATA_LOW();//拉低总线
}
DS18B20_Delayus(62);//延时62us,数据稳定时间设定为62us
WriteData >>= 1;//字节左移一位
}
DS18B20_SetDataIn();//配置成输入
}
3.主机读一字节
时序图
static HRINT8U DS18B20_ReadDataByte(void)
{
HRINT8U i,ReadData = 0;
for(i=0;i<8;i++)//循环读取8位
{
ReadData = ReadData >> 1; //左移一位
DS18B20_SetDataOut();//配成输出
DS18B20_DATA_HIGH();//先置高总线
DS18B20_Delayus(2); //延时
DS18B20_DATA_LOW();//置低总线,告诉DS18B20主机要操作总线了
DS18B20_Delayus(2);//延时2us
DS18B20_SetDataIn();//配置成输入
DS18B20_Delayus(8);//延时8us,加上之前的2us大概10us开始采集,数据手册要求在15us前采集
if(DS18B20_ReadDataBit())//读总线电平,为高
{
ReadData = ReadData | 0x80;//置1
}
else//为0,ReadData 原本就初始化为0,不用动作
{
}
DS18B20_Delayus(45);//延时45us,芯片手册规定1个为60us
}
return(ReadData);//返回数据
}
4.DS18B20采集模式配置
DS18B20的操作顺序是:
第一:复位指令 第二:ROM指令 第三:DS18B20功能指令
下面是DS18B20的暂存器,总共9字节。0字节为温度低8位,1字节为温度高8位,2字节是高报警阈值,3字节是低报警阈值,4字节是配置寄存器(用于配置精度),5-7字节保留,8是CRC校验。
static void DS18B20_ModeConfiguration(void)
{
HRINT8U i;
DS18B20_Reset();//复位指令
DS18B20_WriteDataByte(0xCC);//忽略ROM操作,总线只挂一个传感器时用(ROM指令)
DS18B20_WriteDataByte(0x4E);//写暂存器操作(DS18B20功能指令)
DS18B20_WriteDataByte(0x25);//高报警
DS18B20_WriteDataByte(0x21);//低报警
#if (DS18B20_RESOLUTION_MODE == DS18B20_RESOLUTION_9BIT)//根据不用的转换精度配置,编译不同的代码,条件编译
DS18B20_WriteDataByte(DS18B20_RESOLUTION_9BIT);//精度9位,这是一个宏定义,值是0x1F
#elif (DS18B20_RESOLUTION_MODE == DS18B20_RESOLUTION_10BIT)
DS18B20_WriteDataByte(DS18B20_RESOLUTION_10BIT);//精度10位,这是一个宏定义,值是0x3F
#elif (DS18B20_RESOLUTION_MODE == DS18B20_RESOLUTION_11BIT)
DS18B20_WriteDataByte(DS18B20_RESOLUTION_11BIT);//精度11位,这是一个宏定义,值是0x5F
#else
DS18B20_WriteDataByte(DS18B20_RESOLUTION_12BIT);//精度12位,这是一个宏定义,值是0x7F
#endif
DS18B20_Reset();//复位
DS18B20_WriteDataByte(0xCC);//忽略ROM
DS18B20_WriteDataByte(0xBE);//读暂存其指令,可将刚才写入的值读取出来,看看是否正确写入
for(i=0;i<9;i++)
{
DS18B20_RevBuff[i] = DS18B20_ReadDataByte();
}
DS18B20_Reset();//复位
DS18B20_WriteDataByte(0xCC);//忽略ROM
DS18B20_WriteDataByte(0x48); //将刚才写入的3字节暂存器的值搬到E2ROM,防止掉电丢失。
DS18B20_Delayms(15);//延时15ms
DS18B20_Reset();//复位
}
5.采集函数
HRFLOAT DS18B20_Sample(void)
{
HRINT8U i;
static HRFLOAT Temp;
OS_ERR err;
HRDisableInterrupts(); //禁止全局中断,这里关中断时为了在循环时频率有中断,导致延时不准确而通讯失败
DS18B20_Reset();//复位
DS18B20_WriteDataByte(0xCC); //忽略ROM指令
DS18B20_WriteDataByte(0x44);//开始转换
HREnableInterrupts(); //开全局中断
#if (DS18B20_RESOLUTION_MODE == DS18B20_RESOLUTION_9BIT)//根据精度不同,延时时间不一样
// DS18B20_Delayms(100);//for延时100ms
OSTimeDly ( 100, OS_OPT_TIME_DLY, & err );//ucosIII延时100ms
#elif (DS18B20_RESOLUTION_MODE == DS18B20_RESOLUTION_10BIT)
//DS18B20_Delayms(200);
OSTimeDly ( 200, OS_OPT_TIME_DLY, & err );
#elif (DS18B20_RESOLUTION_MODE == DS18B20_RESOLUTION_11BIT)
//DS18B20_Delayms(400);
OSTimeDly ( 400, OS_OPT_TIME_DLY, & err );
#else
OSTimeDly ( 800, OS_OPT_TIME_DLY, & err );//由于转换时间较长,有操作系统时建议采用操作系统来延时,提高程序效率
// DS18B20_Delayms(800);//
#endif
HRDisableInterrupts(); //禁止全局中断
DS18B20_Reset(); //复位
DS18B20_WriteDataByte(0xCC); //忽略ROM
DS18B20_WriteDataByte(0xBE);//读暂存器指令
for(i=0;i<9;i++)
{
DS18B20_RevBuff[i] = DS18B20_ReadDataByte();//读回9个字节
}
HREnableInterrupts(); //开启中断
if(DS18B20_CRC8(DS18B20_RevBuff,8) == DS18B20_RevBuff[8])//判断CRC是否正确
{
Temp = DS18B20_16sToFloat((HRINT16S)((DS18B20_RevBuff[1]<<8) | DS18B20_RevBuff[0]));//将温度转换成小数
}
else
{
Temp = Temp;//the last time
}
OS_TaskSemPost ((OS_TCB *)&AppTaskBuzzerTCB,
(OS_OPT )0,
(CPU_TS )0,
(OS_ERR *)&err); //uscosIII操作系统函数,用于通知其他任务温度采集完毕,可去掉
return(Temp);//返回温度值
}
下面是CRC校验和温度转换成小数的代码
static HRINT8U DS18B20_CRC8(HRINT8U *RomCode,HRINT8U Length)
{
HRINT8U i,x,crc,crcbuff;
crc=0;
for(x = 0; x < Length; x++)
{
crcbuff=RomCode[x];
for(i = 0; i < 8; i++)
{
if(((crc ^ crcbuff)&0x01)==0)
{
crc >>= 1;
}
else
{
crc ^= 0x18; //CRC=X8+X5+X4+1
crc >>= 1;
crc |= 0x80;
}
crcbuff >>= 1;
}
}
return crc;
}
static HRFLOAT DS18B20_16sToFloat(HRINT16S pSource)
{
HRFLOAT pDest;
pDest = (HRFLOAT)(pSource * 0.0625);
return(pDest);
}
6.调试过程中遇到的问题
6.1.调试遇到for语句延时的问题,for代码如下,示波器卡好DS18B20_Delayus(1)延时1.04us,调用DS18B20_Delayus
(480)时,延时时间不够480。
static void DS18B20_Delayus(HRINT32U nTime)
{
HRINT32U i,j;
for(j=0;j< nTime;j++)
{
for(i=0;i<9;i++);
}
}
原因:调用DS18B20_Delayus(1)进出函数一次,DS18B20_Delayus(480)也是进入函数一次,在DS18B20_Delayus(1)的时候进出函数的时间使得DS18B20_Delayus(1)里面的for时间不到1us,所以调用480时时间不够。正确做法是DS18B20_Delayus(1000)卡一个1ms时间,然后再推出1us的时间,尽量让总的延时时间能够忽略掉进出函数的时间。
6.2.调试遇到向DS18B20写入数据,DS18B20无回复现象。查看发送波形是正常的。
原因:写完一个字节以后,没有释放总线,写完后将设置成输入即可。
6.3采集温度时偶尔出现CRC校验不通过。
原因:在读数据时没有关掉系统的中断,导致中断打乱了延时,造成数据读取失败。