17.2.5 C51模拟单总线操作子函数

对于DS18S20的操作需要严格遵守单总线协议,一般采用单总线主控制器或者带有单总线主机的微处理器来实现。但是很多时候,所选用的单片机不具备单总线接口。此时便需要在软件中模拟单总线操作。首先需要定义单总线所使用的引脚,示例如下。


sbit ONEWIRE_DQ=P2^0;//定义P2^0引脚为DS18S20的DQ引脚


这里以数字温度传感器DS18S20为例,介绍单总线器件所支持的ROM操作命令、存储器操作命令等的C51软件实现。

1.延时子函数

延时子函数用于延时指定的时间,用来构成1-Wire总线协议所需要的时序以及15kbps标准速率和111kbps高速速率的要求。在程序中,可以通过双重空循环语句实现延时操作。延时子函数的程序代码示例如下。


void Delay(int useconds)

{

int s,t;

for(s=0;s<useconds;s++)//双重循环

{

for(t=0;t<10;t++);//空循环语句实现延时

}

}


提示以上函数通过空语句实现延时,这导致延时的长短与80C51的晶振频率相关,具体延时时长可通过C51中的调试功能进行跟踪。

2.复位子函数

复位子函数用于完成单总线上从器件的复位操作。在程序中,可用首先将数据线DQ拉低并保持一段时间来实现1-Wire总线上所有器件的复位。接着主机等待DS18S20返回的存在脉冲,并根据存在脉冲返回存在信号。如果返回0则表示器件存在,如果返回1则表示器件不存在。复位子函数的程序代码示例如下。


uchar Reset(void)

{

uchar PresencePalse;

ONEWIRE_DQ=0;//拉低数据线DQ

Delay(3);//延时

ONEWIRE_DQ=1;//置数据线DQ为高电平

Delay(1);//延时

PresencePalse=ONEWIRE_DQ;//读取存在信号

Delay(3);//延时,等待时间隙结束

if(PresencePalse)//返回存在信号

return 1;

else

return 0;

}


3.位写入子函数

位写入子函数用于向单总线上的从器件写入一个比特的数据。在程序中,首先拉低数据线DQ开始写时间隙,然后向数据线DQ写入位数据。如果需要写入1,则数据线DQ置1即可;如果需要写入0,则数据线DQ置0即可。位写入子函数的程序代码示例如下。


void WriteBit(char val)

{

ONEWIRE_DQ=0;//写时间隙

if(val==1)

ONEWIRE_DQ=1;//数据线DQ置1,写1

else

ONEWIRE_DQ=0;//数据线DQ置0,写0

Delay(1);//延时,在时间隙内保持电平值

ONEWIRE_DQ=1;//拉高数据线DQ

}


4.字节写入子函数

字节写入子函数用于向单总线上的从器件写入一个字节的数据。在程序中,采用循环移位的方式,每次调用位写入子函数WriteBit写入一位。字节写入子函数的程序代码示例如下。


void WriteByte(char val)

{

uchar i;

uchar temp;

for(i=0;i<8;i++)//循环写入字节,每次写入一位

{

temp=val>>i;//移位

temp&=0x01;

WriteBit(temp);//逐位写

}

}


5.位读取子函数

位读取子函数用于从单总线上读取从器件返回的一个比特的数据。在程序中,首先拉低数据线DQ开始读时间隙,然后将DQ置1。接着延时一段时间,读取并返回数据总线DQ上的位数据。位读取子函数的程序代码示例如下。


uchar ReadBit(void)

{

ONEWIRE_DQ=0;//读时间隙

ONEWIRE_DQ=1;//DQ置1

Delay(1);//延时

if(ONEWIRE_DQ)//返回数据总线DQ上的位数据

return 1;

else

return 0;

}


6.字节读取子函数

字节读取子函数用于从单总线上读取从器件返回的一个字节的数据。在程序中,采用循环移位的方式,每次调用位读取子函数ReadBit读取一位。字节读取子函数的程序代码示例如下。


uchar ReadByte(void)

{

uchar i;

uchar value=0;

for(i=0;i<8;i++)//读取字节,每次读取一位

{

if(ReadBit())

value|=0x01<<i;//循环左移

else

;//空语句

Delay(1);

}

return(value);//返回字节数据

}


7.读取ROM代码子函数

读取ROM代码子函数用于读取单总线上单个DS18S20从器件的ROM代码。在程序中,首先使用复位子函数Reset复位单总线上的所有从器件,然后调用字节写入子函数WriteByte来执行读出ROM序列号指令,指令码为33H。接着,循环调用字节读取子函数ReadByte来读取DS18S20返回的8个字节的ROM序列号。最后,通过串口打印输出获得的ROM序列号。读取ROM代码子函数的程序代码示例如下。


void ReadROMNumber(void)

{

int n;

char dat[9];

printf(“\nReading DS18S20 ROM Number\n”);//输出信息

Reset();//复位子函数

WriteByte(0x33);//读出ROM序列号命令

for(n=0;n<8;n++)

{

dat[n]=ReadByte();//循环读ROM序列号

}

printf(“\nDS18S20 ROMNumber=”);//输出ROM序列号

printf(“%X%X%X%X%X%X%X%X\n”,//输出ROM序列号

dat[7],dat[6],dat[5],dat[4],dat[3],dat[2],dat[1],dat[0]);

}


8.CRC校验子函数

CRC校验子函数用于完成一次循环冗余校验。一般来说,在进行单总线ROM搜索时需要CRC校验。其中,经常采用查表法来实现CRC校验。在程序设计时,需要预先定义CRC校验表,详细介绍可参阅DS18S20的参考应用笔记。CRC校验子函数的程序代码示例如下。


uchar CRCCheck(uchar x)

{

uchar code dsc[]=//CRC校验表

{

0,94,188,226,97,63,221,131,194,156,126,32,163,253,31,65,

157,195,33,127,252,162,64,30,95,1,227,189,62,96,130,220,

35,125,159,193,66,28,254,160,225,191,93,3,128,222,60,98,

190,224,2,92,223,129,99,61,124,34,192,158,29,67,161,255,

70,24,250,164,39,121,155,197,132,218,56,102,229,187,89,7,

219,133,103,57,186,228,6,88,25,71,165,251,120,38,196,154,

101,59,217,135,4,90,184,230,167,249,27,69,198,152,122,36,

248,166,68,26,153,199,37,123,58,100,134,216,91,5,231,185,

140,210,48,110,237,179,81,15,78,16,242,172,47,113,147,205,

17,79,173,243,112,46,204,146,211,141,111,49,178,236,14,80,

175,241,19,77,206,144,114,44,109,51,209,143,12,82,176,238,

50,108,142,208,83,13,239,177,240,174,76,18,145,207,45,115,

202,148,118,40,171,245,23,73,8,86,180,234,105,55,213,139,

87,9,235,181,54,104,138,212,149,203,41,119,244,170,72,22,

233,183,85,11,136,214,52,106,43,117,151,201,74,20,246,168,

116,42,200,150,21,75,169,247,182,232,10,84,215,137,107,53

};

CRCdsc=dsc[CRCdsc^x];//查表校验

return CRCdsc;//返回

}


9.搜索器件子函数

搜索器件子函数用于搜索单总线上的下一个DS18S20从器件。在程序中,单总线主机采用循环搜索,直到全部ROM字节0~7都完成。如果单总线上没有其他DS18S20器件则返回0。在ROM搜索时,单总线主机写入搜索ROM序列号指令,指令码为F0H,并采用CRC校验子函数执行CRC校验。搜索器件子函数的程序代码示例如下。


uchar SearchDevice(uchar EndFlag,uchar LastData)

{

uchar m=1;//DS18S20 ROM位索引

uchar n=0;//DS18S20 ROM字节索引

uchar k=1;

uchar x=0;

uchar disMarker=0;

uchar g;

uchar nxt;

int flag;

nxt=0;//初始化

CRCdsc=0;

flag=Reset();//复位函数

if(flag||EndFlag)//如果没有其他器件

{

LastData=0;

return 0;//返回0

}

WriteByte(0xF0);//搜索ROM命令

do//循环

{

x=0;

if(ReadBit()==1)

x=2;

Delay(1);

if(ReadBit()==1)

x|=1;

if(x==3)

break;

else

{

if(x>0)

g=x>>1;

else

{

if(m<LastData)

g=((DS18S20ROM[n]&k)>0);

else

g=(m==LastData);

if(g==0)

disMarker=m;

}

if(g==1)

DS18S20ROM[n]|=k;

else

DS18S20ROM[n]&=~k;

WriteBit(g);//位写入函数

m++;

k=k<<1;

if(k==0)

{

CRCCheck(DS18S20ROM[n]);//CRC校验

n++;

k++;

}

}

}while(n<8);//直到全部ROM字节0~7都完成

if(m<65||CRCdsc)

LastData=0;

else//搜索成功

{

LastData=disMarker;//置位LastData,lastOne和nxt

EndFlag=(LastData==0);

nxt=1;//表示总线上还有其他器件,搜索未结束

}

return nxt;

}


提示以上程序中的uchar在主程序中通过宏定义进行定义。

10.搜索第一个器件子函数

搜索第一个器件子函数用于搜索单总线上的第一个DS18S20从器件。在程序中,主要调用搜索器件函数SearchDevice来完成一次搜索。搜索第一个器件函数FindFirstDevice的程序代码示例如下。


uchar FindFirstDevice(void)

{

uchar LastData=0;

uchar EndFlag=0;

return SearchDevice(EndFlag,LastData);//搜索器件函数SearchDevice

}


11.读取暂存器子函数

读取暂存器子函数用于读取DS18S20内部高速暂存器中的数据。在程序中,首先执行读暂存器指令,指令码为BEH。然后通过循环调用字节读取子函数ReadByte来读取暂存器中9个字节的数据。读取暂存器子函数的程序代码示例如下。


void ReadData(void)

{

int j;

char pad[10];

printf(“\nReading DS18S20 ScratchPad Data\n”);

WriteByte(0xBE);//读暂存器命令(代码为BEH)

for(j=0;j<9;j++)//循环读取暂存器中9个字节的数据

{

pad[j]=ReadByte();//字节读取函数

}

printf(“\n ScratchPAD DATA=”);

printf(“\n%X%X%X%X%X%X%X%X%X\n”,//输出结果

pad[8],pad[7],pad[6],pad[5],pad[4],pad[3],pad[2],pad[1],pad[0]);

}


12.查找器件子函数

查找器件子函数用于查找单总线上的所有DS18S20从器件。在程序中,首先复位单总线,通过FindFirstDevice的返回值确定是否存在从器件。如果总线上存在DS18S20,则将其唤醒并调用。程序中,使用SearchDevice函数来识别单总线上每个DS18S20从器件唯一的ROM序列号。查找器件函数的程序代码示例如下。


void FindDevices(void)

{

uchar numROMs;

uchar m;

uchar LastData=0;

uchar EndFlag=0;

numROMs=0;

if(!Reset())

//复位总线

{//如果存在器件则开始处理

if(FindFirstDevice())//调用FindFirstDevice函数

{

do//循环

{

numROMs++;

for(m=0;m<8;m++)

{//识别ROM代码

ROMFound[numROMs][m]=DS18S20ROM[m];

}

printf(“\nDS18S20 ROM CODE=”);

printf(“\n%02X%02X%02X%02X%02X%02X%02X%02X\n”,

ROMFound[5][7],ROMFound[5][6],ROMFound[5][5],ROMFound[5][4],

ROMFound[5][3],ROMFound[5][2],ROMFound[5][1],ROMFound[5][0]);

}while(SearchDevice(EndFlag,LastData)&&(numROMs<10));

//直到没有发现其他从器件

}

}

}


13.读取温度子函数

读取温度子函数用于读取单总线上DS18S20测量的温度值。如果单总线上只有一个DS18S20,则可以使用该函数来获取温度测量值。在程序中,首先复位单总线,然后执行温度转换指令,指令码为44H。接着执行读暂存器指令读取温度数据,指令码为BEH。最后通过打印输出对应的摄氏温度值以及华氏温度值。读取温度子函数的程序代码示例如下。


void ReadTemperature(void){

char get[10];

char temp_lsb,temp_msb;

int k;

char Ftemperature,Ctemperature;

Reset();

//复位

WriteByte(0xCC);

//跳过ROM序列号命令

WriteByte(0x44);

//启动温度转换命令

Delay(1);

Reset();

WriteByte(0xCC);

//跳过ROM序列号命令

WriteByte(0xBE);

//读暂存器指令

for(k=0;k<9;k++)

{

get[k]=ReadByte();

//循环读取

}

printf(“\n Scratch DATA=%X%X%X%X%X%X%X%X%X\n”,

get[8],get[7],get[6],get[5],get[4],get[3],get[2],get[1],get[0]);

temp_msb=get[1];

temp_lsb=get[0];

if(temp_msb<=0x80)

{

temp_lsb=(temp_lsb/2);//移位,得到完整的温度值

}

temp_msb=temp_msb&0x80;//屏蔽符号位之外的所有数据位

if(temp_msb>=0x80)

{

temp_lsb=(~temp_lsb)+1;//temp_lsb取补,再加1

}

if(temp_msb>=0x80)

{

temp_lsb=(temp_lsb/2);//移位,得到完整的温度值

}

if(temp_msb>=0x80)

{

temp_lsb=((-1)*temp_lsb);//符号位

}

printf(“\nTempC=%d degrees C\n”,(int)temp_lsb);//摄氏温度值输出

Ctemperature=temp_lsb;

Ftemperature=(((int)Ctemperature)*9)/5+32;

printf(“\nTempF=%d degrees F\n”,(int)Ftemperature);//华氏温度值输出

}