重点是协议的方式:单总线协议
重点是:协议中的时间间隔问题
外部线路
初始化步骤:
mintime-----maxtime(480-960)低电平时间
之后进入等待时间(15us-60us)将单总线拉高,看从设备是否将总线从新拉低
/*********************************************************************
* 函 数 名 : Ds18b20Init
* 函数功能 : 按照DS18B20底层时序要求进行传感器初始化
* 参数列表 : 无
* 函数输出 : 若初始化成功则返回0,否则返回1
*********************************************************************/
static u8 Ds18b20Init(void)
{
u8 i = 0;
gIO = 0; // 时序要求将总线拉低480us~960us
delay750us(); // 实际延时750us,符合480-960之间的条件
gIO = 1; // 然后拉高总线,如果DS18B20做出反应会将在15us~60us后总线被拉低
i = 0;
while (gIO) // 等待DS18B20拉低总线
{
i++;
if(i>5) // 等待 15*5=75us,如果还没拉低则可以认为初始化失败了
{
return 1; // 初始化失败
}
delay15us(); // 隔15us查看一下是否收到DS18B20的回应
}
return 0; //初始化成功
}
我们先看写字节
先15us内将要写入的数据拉低或拉高
然后通过从设备的pin来采集。最后采集完之后要释放总线等待下一个bit的写操作(这里显示的一个写周期在60us)
/*********************************************************************
* 函 数 名 : Ds18b20WriteByte
* 函数功能 : 按照DS18B20底层时序要求向DS18B20写入1字节数据
* 参数列表 : dat - 待写入的1字节数据
* 函数输出 : 无
*********************************************************************/
static void Ds18b20WriteByte(u8 dat)
{
u16 i = 0, j = 0;
for (j=0; j<8; j++)
{
gIO = 0; // 每写入一位数据之前先把总线拉低1us
i++;
gIO = dat & 0x01; // 然后写入一个数据,从最低位开始
delay70us(); // 时序要求最少60us
gIO = 1; // 然后释放总线,至少1us给总线恢复时间才能接着写入第二个数值
dat >>= 1;
}
}
/*********************************************************************
* 函 数 名 : Ds18b20ReadByte
* 函数功能 : 按照DS18B20底层时序要求从DS18B20中读取1字节数据
* 参数列表 : 无
* 函数输出 : 返回读取到的1字节数据
*********************************************************************/
u8 Ds18b20ReadByte(void)
{
u8 byte = 0, bi = 0;
u16 i = 0, j = 0;
for (j=8; j>0; j--)
{
gIO = 0; // 先将总线拉低1us
i++;
gIO = 1; // 然后释放总线
i++;
i++; // 延时6us等待数据稳定
bi = gIO; // 读取数据,从最低位开始读取
/*将byte左移一位,然后与上右移7位后的bi,注意移动之后移掉那位补0。*/
byte = (byte >> 1) | (bi << 7);
//byte |= (bi << (8-j));
delay45us();
}
return byte;
}
/*************** 高层时序 *************************************/
void Ds18b20TempConvertCmd(void)
{
Ds18b20Init();
delay1ms();
Ds18b20WriteByte(0xcc); // 跳过ROM操作命令
Ds18b20WriteByte(0x44); // 温度转换命令
delay750ms(); // 等待转换成功,750ms肯定够了
}
void Ds18b20TempReadCmd(void)
{
Ds18b20Init();
delay1ms();
Ds18b20WriteByte(0xcc); // 跳过ROM操作命令
Ds18b20WriteByte(0xbe); // 发送读取温度命令
}
//应用
void TempDisplayTest(void)
{
u16 temp = 0; // 用来暂存12位的AD值
u8 tmh = 0, tml = 0; // 用来暂存2个8位的AD值
u16 tDisp = 0; // 用来存储乘以100倍后的温度值
double t = 0; // 用来存储转换后以摄氏度为单位的温度值
Ds18b20TempConvertCmd(); // 先写入转换命令
Ds18b20TempReadCmd(); // 然后等待转换完后发送读取温度命令
tml = Ds18b20ReadByte(); // 读取温度值共16位,先读低字节
tmh = Ds18b20ReadByte(); // 再读高字节
temp = tml | (tmh << 8); // 默认是12位分辨率,前面4个S位是符号位
// 正温度时符号位为0,下面代码计算没有考虑负温度情况,因为我们实验是在
// 室温下做的,如果要考虑到负温度的情况,代码中要先判断S位,若S位为1则
// 必须点去掉S的1再计算,计算后的值加负号即可。
t = temp * 0.0625;
tDisp = (u16)(t * 100); // 为方便显示将温度值乘以100后强转为u16
// 调用LCD1602的显示函数来显示乘以100倍后的温度值
Lcd1602ShowTempU16(0, 0, tDisp);
}
/*********************************************************************
* 函 数 名 : Lcd1602ShowTempU16
* 函数功能 : 从坐标(x,y)开始显示被乘以100倍之后的U16类型的温度值
注意中间加入了小数点的显示。
* 参数列表 : x - 横向坐标,范围是0-15
* y - 纵向坐标,0表示上面一行,1表示下面一行
* temp - 待显示的u16类型的温度值
* 函数输出 : 无
*********************************************************************/
void Lcd1602ShowTempU16(u8 x, u8 y, u16 temp)
{
u8 d = 0;
Lcd1602SetCursor(x, y); // 当前字符的坐标
d = temp / 1000; // temp开始为4位数,取出最高位
temp %= 1000; // temp中剩下后3位
Lcd1602WriteData(d + '0'); // 显示最高位数字
d = temp / 100; // temp开始为3位数,取出最高位
temp %= 100; // temp中剩下后2位
Lcd1602WriteData(d + '0'); // 显示最高位数字
Lcd1602WriteData('.'); // 显示小数点
d = temp / 10; // temp开始为2位数,取出最高位
temp %= 10; // temp中剩下后1位
Lcd1602WriteData(d + '0'); // 显示最高位数字
d = temp; // temp剩余1位数字,直接显示即可
Lcd1602WriteData(d + '0'); // 显示最高位数字
Lcd1602WriteData(0xdf); // 显示摄氏度的小圆圈
Lcd1602WriteData('C'); // 显示摄氏度的大写C
}