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);//华氏温度值输出
}