会独立看时序图非常重要,这是很多前辈们反复强调的,这样你可以在工作中独立查手册,看时序图,写代码。毕竟那么多传感器,就是高手也不可能完全记得住它们的时序图和功能命令,用得着时,还得去查手册,一段时间不用,又忘了。不会读时序图,写代码就抓瞎,无从下手。 在学习DS18B20的时序图时,我一直有疑问,在它的相关读写函数里,都是DQ数据反复变化,主控与DS18B20到底是怎么判断是写还是读的,为何写1与读1时先有DQ=1的变化,而写0与读0在时序图上却看不到DQ=1的变化,但在代码上又与读0,写0具有相同的前置条件?DQ=1就是为了写1吗?尤其是读0与读1的那一部分,程序里都是先有DQ=1这一句,然后让主机从DS18B20读取数据,可是我在读0的时序图上却没有看到上拉电阻反映DQ=1的动作。这些问题一直影响我对DS18B20时序图的判断与理解。
在金沙滩kingst51开发板教程与普中开发板教程中,我依然没有看明白,解开我的疑惑。
后来咨询伟大的AI老师,才略有开悟,现总结如下,希望能为后来者撑伞铺路。
总的来说,DS18B20本身无法主动判断主机接下来是要进行读还是写操作。通信的方向和时机完全由主机(单片机) 按照固定的通信协议来控制。整个流程可以理解为一个严谨的对话,主机是主导者,DS18B20只是被动响应。要理解这个机制,可以从“命令决定方向”和“时序执行操作”两个层面来看。还有一个写的时序优先级是高于读的时序优先级的(我的总结)。
就像你给别人打电话,你需要先拔对方号码,按地址找到对方(是写),对方回应一声“喂”,你就告诉对方接下来的操作是什么(还是写),如果是“写”操作,就发出写的时序变化(还是写),只有是“读”操作,才会按要求发出读的时序变化。DS18B20这时才认可DQ数据线上的变化时序是读。这就是先有“命令决定方向”,然后给的时序又必须符合读写的要求,即“时序执行操作”,这一套流程下来,DS18B20才能分清当前到底是要干什么。当然我们也分清了。
一个典型的温度读取流程,就展示了这种“命令-操作”的交替过程:
主机发命令(写):主机发送 0xCC(跳过ROM命令)和 0x44(启动温度转换命令)。 等待转换:主机等待DS18B20完成温度转换。 主机发命令(写):主机再次发送 0xCC(跳过ROM命令)和 0xBE(读暂存器命令)。 主机读数据(读):此时主机才启动读时序,从总线上读取DS18B20发来的温度数据。
可以看到,是“读暂存器命令”(0xBE)这个指令,决定了接下来的操作是“读”。如果没有发送这个命令,而直接去执行读时序,DS18B20是不会回应有效数据的。 最不好理解的就是“读”时序图,请看下图:
目的:主机从DS18B20接收数据(一个bit)。 发起动作:主机将总线拉低(DQ=0),并持续至少1us。 这时你可以在程序中写上DQ=0;_nop_();_nop()_; (一个空操作耗时1us,整两个空操作函数,以保证延时大于1us, 但别太多,因为时序图要求在15us内完成采样。) 关键区别:主机拉低总线后,必须立即释放总线(DQ=1),进入输入模式,然后在15us内对总线电平进行采样。这时你在程序中写上DQ=1,释放总线,上拉电阻将总线电平拉到1。不必再延时了。因为马上总线的电平就由DS18B20控制(拉低表示'0',不动作表示'1',这些动作不需要主动写DQ=0或1)。DS18B20立即按输出要求输出数据,如果输出的是0,DS18B20就将总线拉低,抑制上拉电阻的行动,所以你在读0的时序图中看不到采样前上拉电阻的动作,就是因为DS18B20的输出把它抑制了。如果输出是1,DS18B20就没有动作,任由上拉电阻把总线电平扯到1。这时你的代码就是if(DQ) dat |=0x80; else啥操作都没有,向右移位后,相应位自然置0。当然后面还跟着dat的循环向右移位。与写操作不同,这里不再有重复的DQ=1或0的代码与操作。15us后,DS18B20按要求放弃对总线操作,读0的时序图显示上拉电阻自行把总线电平拉到1,读1的时序图显示,反正已经是1了,还是同前。过了至少45us后,主机就开始主动控制总线。这个45us, 你写个代码表示延时,如delay(50us)。至少写的数字要超过45us。
再看写时序图: 45us延时完毕后,与读操作不同的是,读操作里采样完成后,比如说读0,是DS18B20拉低的总线,15us后DS18B20放弃操作,上拉电阻可自行工作,将总线置1。 写0操作是主机主动把总线拉低的,上拉电阻不能自行工作,所以还需要你再手写一遍DQ=1,让上拉电阻把总线置1。
为什么最后都要 DQ=1?至于初始化DS18B20的时序图,我就不再分析了,看懂了上两个时序图,这个就很简单了。
附读,写操作函数
unsigned char Read(void)
{
unsigned char i =0;
unsigned char dat =0;
for(i=8;i>0;i -- )
{
DQ=0;//给脉冲信号
_nop_(); _nop_();
DQ=1;//给脉冲信号,主机主动干的。
if(DQ) //该DS18B20干活了,若拉低电平,就会把DQ变为0,若输出1,DQ就还是1
dat =0x80;
delay_18B20(5);//15us后,上拉电阻主动拉高电平。延时50us,总线交由主机控制
dat>>=1;
}
return dat;
}
void Write(unsigned char dat)
{
unsigned char i =0;
for(i=8;i>0;i -- )
{
DQ=0;
_nop_(); _nop_();
DQ=dat&0x01; //这里就相当于写0时,重复写了遍DQ=0; 写1时又与后面的DQ=1重复。
delay_18B20(5);//延时50us,这个时间是DS18B20采样时间
DQ=1;
dat>>=1;
}
|