继续努力鸭!!!!
今天想再复习一遍DS18B20的知识,把底层再弄一遍,,,不然该忘了,,鹅鹅鹅,大家也要勤加复习呀!
写在前面:
/*延时函数*/
void Delayus(u8 us)
{
do{
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
}while(--us);
}
bit Get18B20ACK()//检测是否存在DS18B20这个器件
{
bit ack;
EA = 0;//关闭总中断
IO_18B20 = 0;//先拉低IO口
DelayUs(250);//延时500us
DelayUs(250);
IO_18B20 = 1;//拉高电平,单片机释放总线,准备读取脉冲数据
DelayUs(60);//等待60us
ack = IO_18B20;//读取存在脉冲(注意,存在脉冲是一个低电平脉冲)
while(!IO_18B20);//等待存在脉冲结束
EA = 1;//重新使能中断
return ~ack;
}
做几点解释:1. 和I2C寻址类似,1-Wire总线开始也要检测是否存在相应的器件,若存在相应器件,会返回一个低电平脉冲;
2. 只有在单片机释放总线(拉高电平)的情况下,单片机才能从从机上读取数据,这点和其它两个通信协议是一样的;
3. 关于下面这行代码:从时序图上可以看出,DS18B20的存在脉冲会持续60-240us,所以我们需要等待这个存在脉冲结束,那什么时候结束呢?当然是这个脉冲变成高电平了呀,所以就有了这行代码,while循环,非0即为真,如果存在脉冲没有结束(一直为0),就会一直执行while循环
while(!IO_18B20);//等待存在脉冲结束
void Write18B20(u8 dat)
{
u8 mask;
EA = 0;//要在18B20内部执行读或者写操作时要关闭总中断!!!!
for(mask = 0x01; mask != 0; mask <<= 1)
{
IO_18B20 = 0;//产生2us的低电平脉冲
DelayUs(2);//为了确保IO-18B20口确实处于拉低状态
if((mask & dat) == 0)
IO_18B20 = 0;
else
IO_18B20 = 1;
DelayUs(60);
IO_18B20 = 1;//最后要把IO_18B20拉高,释放总线!!!!
//DelayUs(2);//这里不需要再延时了
}
EA = 1;//上面关闭了,最后要记得开启!!!!!
}
注意!!! 写入数据时,要先产生一个2us的低电平脉冲
读操作:
注意!!! 读数据是单片机读,那么就必须是在释放总线的情况下读取数据的,同样的,读数据前也要先产生一个2us的低电平脉冲,然后再迅速拉高(因为必须在15us的时间内读取数据)
u8 Read18B20()
{
u8 mask;
u8 dat = 0;//定义一个变量保存读到的数据
EA = 0;//关闭总中断
for(mask = 0x01; mask != 0; mask <<= 1)
{
IO_18B20 = 0;//先产生一个2us的低电平脉冲
DelayUs(2);
IO_18B20 = 1;//单片机释放总线,准备开始读取数据
DelayUs(2);
if(IO_18B20 == 1)
dat |= mask;
else
dat &= ~mask;
DelayUs(60);//延时60us
}
EA = 1;//重新使能
return dat;
}
/*返回值表示是否操作成功,ack= 0表示成功*/
bit Start18B20()//只是调用了write函数,不必关闭总中断
{
bit ack;
ack = Get18B20ACK();//判断是否存在DS18B20这个器件
if(ack == 1)//存在器件,因为Get18B20的函数返回值是~ack
{
Write18B20(0xCC);//跳过ROM操作,我们的开发板上1-Wire总线上只挂了一个器件
Write18B20(0x44);//开始温度转换
}
return ~ack;//ack = 0表示操作成功
}
bit Get18B20Temp(int *temp)
{
bit ack;
u8 MSB,LSB;
EA = 0;
ack = Get18B20ACK();
if(ack == 1)
{
Write18B20(0xCC);
Write18B20(0xBE);//发送读命令
LSB = Read18B20();//读取DS18B20的低字节
MSB = Read18B20();//读取DS18B20的高字节
MSB &= 0x0F;//清除符号位,符号位是高四位
*temp = ((int)MSB << 8) | LSB;//注意这里是左移八位,将MSB高八位的0移走!!!!!!!!!!
*temp = *temp * 6.25;//将读取到的16进制数转换为十进制的温度
}
EA = 1;
return ~ack;
}
说明几点: 1. 注意下面这行代码:(int)是将8位的MSB强制转换为16位(高八位补0),我们要得到完整的16位温度数据,还需要把MSB和LSB合在一起;
*temp = ((int)MSB << 8) | LSB;
2.这行代码是如何实现数据转换的呢?还记得我们上面说过,二进制改变1位,温度改变0.0625°C,但是这里的温度是扩大100后的温度,所以0.0625°C也要扩大100倍,可以这么理解,*temp代表n个刻度,而一个刻度代表的温度是6.25°C,二者想乘就得到温度的数值了。
*temp = *temp * 6.25;//将读取到的16进制数转换为十进制的温度
底层部分结束,我们还要在主函数里写一个刷新温度(显示温度)的函数:
void RefreshTemp()//显示小数部分
{
int temp;
u8 i;
Get18B20Temp(&temp);//主函数中已经开始了温度转换,故这里先获取温度
Start18B20();//温度转换需要一定的时间,故需要再次开始温度转换,为下一次获取温度做准备
//temp >>= 4;//去掉温度上的小数部分,temp低四位是小数部分
for(i = 7; i>2; i--)
{
LedBuff[i] = 0xFF;//关闭不用的数码管,记住如果要关闭数码管,必须对LedBuff进行操作
}
for(i = 1; i<5; i++)
{
LedBuff[i] = LedChar[temp % 10];
temp /= 10;
}
/*i循环赋值的函数还可以像下面这样写*/
// LedBuff[4] = LedChar[(temp / 1000) % 10];
// LedBuff[3] = LedChar[(temp / 100) % 10];
// LedBuff[2] = LedChar[(temp / 10) % 10];
// LedBuff[1] = LedChar[temp % 10];
LedBuff[0] = LedChar[12];
LedBuff[3] &= 0x7F;//0x7F=01111111,该行代码相当于把第四个数码管的第七位置0,即点亮dp,这样就实现了加上小数点的操作
}
刷新温度有两种写法,一种是上面的显示小数部分的写法,另一种是不显示小数的写法,不显示小数部分比较好写,只要加上我注释掉的这行代码“ temp >>= 4;”就好,然后数码管只用三个就可以,小数点也不需要加,读者可以自己写一下。
这里提一下数码管扫描的函数叭:
u8 LedChar[] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E};
u8 LedBuff[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
void LedScan()
{
static u8 index = 0;
P2 = (P2 & 0x1F) | 0xE0;
P0 = 0xFF;
P2 &= 0x1F;
P2 = (P2 & 0x1F) | 0xC0;
P0 = 0x80 >> index;
P2 &= 0x1F;
P2 = (P2 & 0x1F) | 0xE0;
P0 = LedBuff[index];
P2 &= 0x1F;
index++;
index &= 7;
}
emmm,会再详细写一篇关于数码管的博客的。