1.Abstract
如前篇随笔所写,将以前遇到最难懂的两个部分重拾一下。前一篇写的是I2C协议(这一篇就来写基于DS18B20的1-wire协议。以前用到它的时候是借助别人写的文章和配套程序,整了一个一知半解;现在重新学习一遍,我想参考的资料就只有唯一一份了——芯片手册,最为权威和齐全的。在平静下来写这个的时候,着实花了很大精力将手册从头看到尾,部分的时序图在草稿纸上画了画,理解也更加深刻了一些。
总线的结构都是差不多的,相对于上篇中的I2C总线,1-wire总线结构从逻辑上来说还简单一点,要说复杂的地方,那就是比较严格的时序了——规定的时间内必须完成某事,否则容易出岔子。关于1-wire总线的优劣势,在仔细分析完一个具体的器件以后,再来做结论比较适合,下面开始正文。
2.Content
挂在1-wire总线上的,一般都只有一个主器件,一个或者多个从器件;而且通常情况下单个器件的情况居多。构成主器件的,都是微控制器或者可编程逻辑器件(CPLD/FPGA),从器件就是各个厂商生产的具体功能芯片了(尤其是DALLAS Semiconductor 公司)。下面以一个具体的实例为例,分析下它是如何工作的。器件使用典型的DS18B20温度传感器,在1-wire总线结构中,它只能作从器件。
2.1 协议分析
和分析I2C的协议一样,把挂在总线上的器件分为两种;一种是主器件,它具有控制总线的权利,主动与从器件打交道;另外一种是从器件,它受主器件的指挥;一般都是站在主器件的角度去看总线(因为从器件是受控对象,它的功能已经实现好了,只需要给它特定的指令,它就会自动地去做某件事儿)。
和其他总线通信一样,主器件需要知道从器件的一些基本信息——器件地址和器件内部的控制信息。//代码效果参考:http://www.zidongmutanji.com/bxxx/576062.html
对于DS18B20来说,解决它的器件地址还真不是一件容易的事儿,看看它的地址信息是怎么分配的。FIG2.1 DS18B20内部ROM信息
DS18B20的内部ROM有64位,分为8个字节(8 * 8 = 64bit);第一个字节是器件家族的固定编码,DS18B20为28H;中间的6个字节48位为器件的序列号,也就是器件地址(最为关注的就是它了);最后一个字节是前面 8 + 48 = 56 位的循环冗余校验码(CRC CODE),若是主器件想读取该器件的ROM信息,可以将前56位编码做一个循环冗余校验,生成一个8bit的循环冗余校验码,然后和最后的这一个字节进行对比,若相同,则说明读取信息是正确的,否则,主器件的读取是有误的。
从上面的分析中,找到了关键的地址信息——中间的这个48bit序列号,按照数学的逻辑组合,它可以分配2的48次方281,474,976,710,656个地址,以亿为单位算,有281474亿个地址!实在是太多了,所以器件生产商将每一个器件都做了一个固定的序列号;不过这样有一个麻烦的是,拿到手中的器件的地址信息最开始是不知道的,要想获得某个器件的地址,必须得用主器件将它的这个序列号读取出来;然而实际上,在1-wire总线上挂了多个DS18B20的器件,要将它们的地址都获取出来,是非常麻烦的;芯片手册里给出了一种算法,将不同的器件识别出来,这里的识别指的不是准确的获取它们的序列信息。所以通常情况下,在未知器件序列号的情况下,都是采用单个从器件跟主器件连接的。要想主器件上一根I/O口线连接多个从器件,那么就得想想其他办法了。
这里提出一种简单的方法供参考。鉴于DS18B20的数据输出口传输的是数//代码效果参考:http://www.zidongmutanji.com/bxxx/276042.html
字信号,故MCU的数据端口也应该配置的数据输入输出端口;将它们抽象起来看,就是一个数字端口能分时的与多个数字端口连接,常采用方法就是用选择器,n个地址的选择器就可以分2的n次方个连接端口,下图是以8选1的集成芯片74151做例子解释。FIG2.2 74151多路选择器
本图的重要目的是为了解释接口的关系,图纸并不完全。左边是DS18B20的插座,数据接入选择器的输入端,输出端和输入端口选择的地址接在MCU输入输出端子上。如果还需要接更多的DS18B20器件,可以采用选择器路数更多的功能芯片,如16选1,32选1等;如果还需要接有更多的从器件,按照这种逻辑可以使用CPLD/FPGA器件,它们的引脚数比较众多,是做数字设计的好帮手。这样用多路选择器的方法可以解决多个DS18B20分时共用一个数据端口的问题。
由上述可知,1-wire总线上在同一个时间点上主器件只和一个从器件相连,构成一对一的关系,所以主器件知道从器件的地址信息就不再有意义了。在1-wire通信中,主器件需要了解从器件的控制信息显得更为重要一些了。
对应于主器件,和从器件通信只有两件事儿,其一是向从器件写数据,另外一个是从从器件读数据。基于DS18B20的1-wire通信协议将器件的控制信息全部列举出来,主器件只需要发送这些控制命令,就可以执行相应的操作了。
FIG2.3 DS18B20的指令对照表
由于DS18B20内部的寄存器不多(只有七个,其中有三个作为保留),所以对器件进行读写的控制就比较简单,发送相应的读写命令指令以后,器件就会按照协议进行数据交换。整体的读写操作流程如下图所示。
FIG2.4 主器件向从器件写数据
FIG2.5 主器件从从器件读数据
和其他的通信协议一样,主器件的写操作分为五大步骤,建立通信、写ROM操作指令、写控制操作指令、数据交换、结束通信。
1-wire协议对它们进行了详细地规定;建立通信是一段480us~960us的低电平复位,如下图所示。
FIG2.5 DS18B20的初始化
操作首先是主控给一段480-960us的低电平,然后释放总线(将总线拉高);紧接着DS18B20等待15-60us以后,输出一个时常为60-240us的低电平,表示收到主器件发来的初始化信息;之后将总线释放掉。主器件可以通过从器件返回的这个低电平判断器件是否正常连接(相当于一个应答)。
写ROM操作指令。具体主器件是如何将数据一位一位地通过1-wire传送到从器件,这个在后边讲述;这里将它抽象出来,作为一个指令来叙述。 写ROM的操作指令有5个,如下表所示。
表2.1 ROM指令对照表
ROM指令 指令功能 33H READ ROM 55H MATCH ROM F0H SERACH ROM ECH ALARM SERACH ROM CCH SKIP ROM
虽然器件给出了五种ROM的操作指令,但是在实际应用中,主器件与从器件的连接是1对1的,所以一般情况下是不需要对ROM数据进行太多的处理的,所以CCH(SKIP ROM)这条ROM指令用得最多。
写一个控制操作指令。在主器件进行完写ROM操作指令以后,紧接着就是要写一个控制指令了,控制指令表由图FIG2.3给出了,这里只将控制指令部分和指令功能部分列成表给出。
表2.2 控制指令对照表
控制指令 指令功能 44H Convert T BEH Read Scratchpad 4EH Write Scratchpad 48H Copy Scratchpad B8H Recall E2 B4H Read Power Supply
简单的解释一下吧,44H(Convert T)指令是让从器件进行温度转换的指令,在正确读取温度数据之前,需要对当前的温度进行转换;BEH(Read Scratchpad)指令是将从器件中的9个RAM数据读取出来,DS18B20的内部RAM结构图如下图所示。
FIG2.6 DS18B20内部RAM一览表
前两个数据是温度转换数据,紧接着的两个是温度上下限控制数据,第五个是配置寄存器,第6到第8个是保留寄存器,第9个是前边数据的CRC校验数据。与掉电数据丢失的SCRATCHPAD RAM块相比,器件内部设置了一个掉电不丢失的电可擦除E2RAM,用于保存温度上下限控制数据和配置寄存器数据。
这些数据是按BYTE0 到BUTE8的顺序逐步读出的。若需要读取E2RAM块的数据,则需要先执行将E2RAM的数据写入到SCRATCHPAD的控制指令。下面简要的说明一下其他控制指令的功能。
BEH(READ SCRATCHPAD)指令是读取SCRATCHPAD的指令,执行此条指令以后,从器件会将内部SCRAPTCHPAD的数据逐步的发送至1-wire上。当然在读的这个过程中,主器件随时可以中断读操作,只对特定感兴趣的数据读取。
4EH(WRITE SCRATCHPAD)指令是写SCRATCHPAD指令,执行此条指令以后,主器件需要完整地将温控上下限数据(TH 与TL)和配置数据(CONFIG)依次发送到1-wire上。值得注意的是此条指令的写只是将数据存放到SCRATCHPAD中,而不是E2RAM中,它是会掉电丢失的。
48H(COPY SCRACTCHPAD)指令是将SCRATCHPAD中的温控上下限数据(TH与TL)和配置数据(CONFIG)写到E2RAM中,用于掉电数据保持。执行此条指令以后后续不需要再写数据。
B8H(RECALL SCRATCHPAD)指令是将E2RAM的数据读到SCRATCHPAD中,一般是用于读取E2RAM的数据进行校验的场合。执行此条指令以后后续不需要再写数据。
B4H(READ POWER SUPPLY)指令是读取芯片的供电方式。一种是独立的电源供电,返回1;另外一种是用DQ引脚端复合供电,返回0。执行此条指令以后后续不需要再写数据。
了解总体的数据读写操作流程以后,剩下的就是具体的读写操作过程了。因为读写的操作过程数据线上只有0和1的变化,没有时钟的辅助,所以DS18B20对时序的控制非常严格。还是站在主器件的角度去看。
FIG2.7 主器件写一位0或者1数据时序操作
主器件的写是一个特定时间内完成特定数据的变化的过程。主器件首先要发送一个大于1us的低电平,后续就发送相应的数据位,整个1位写的时常至少为60us。如上图左边写0的过程,主器件首先发送一个大于1us的低电平电平,由于数据是0,所以后续的电平为低电平,直至整个位长时间结束(至少60 - 1us)。写完1位数据以后,需要释放数据线,否则数据线低电平时间超过480us,则导致器件开始初始化操作。右边写1的过程也是一样,主器件首先发送一个大于1us的低电平,由于数据是1,所以发送完低电平以后就需要释放数据线,数据线高电平时间一直保持到整个位长时间结束(至少60-1us);与发送1相比,最后可以不需要释放总线(因为数据线本就是高电平)。图中灰色的方框内给出了典型的操作时间;在前15个us以内,写操作主控需要将数据线拉低至少1us,然后将要发送的数据电平发送到数据上并保持稳定,这整段的时间最好在15us左右,余下的15+30 = 45us是从器件DS18B20读取数据(电平采样)的过程;采样完成以后,需要释放数据线,两位数据读取的时间要大于1us。
FIG2.8 主器件读取一位0或者1数据的时序操作
理解了主器件写一位数据的操作流程,那么来理解主器件读一位数据的操作流程就比较简单了,它们的格式都是一样的。主器件读取一位数据之前,也需要发送一个大于1us的低电平,然后等待从器件将相应的数据放到数据线上直到稳定;主器件等待一段时间以后对数据线进行采样,如果数据线为高电平,表示从器件发送了一个1;相反,如果数据线为低电平,则表示从器件发送一个0数据。整个读取一位数据的时长至少为60us,1位数据读取完毕以后,从器件会自动的将数据线释放掉,做好发送下一位数据的准备。
插一段理解。纵观通信特点,读和写都的过程都是差不多的;每一次读或写的开始,主器件都需要发送一个至少1us时常的低电平,可以认为是数据传输的开始,紧接着是将数据放到数据线并稳定下来,这整个的时常大约为15us,余下的时间就是等待主器件或者从器件的采样操作,最后就是将数据线释放掉,准备下一次数据的传输操作。
2.2 基于MCU的协议实现
用MCS-51来实现一下,采用的是传统51单片机,12M晶振,故指令时间最短为1us。
2.2.1 DS18B20 复位
bit Init_DS18B20(void)
{
bit dat=0; // DQ复位
DQ = 1;
DelayUs2x(5); // 稍做延时,高电平维持一小段时间
DQ = 0; //单片机将DQ拉低
DelayUs2x(200); //精确延时 大于 480us 小于960us
DelayUs2x(200);
DQ = 1; // 拉高总线
DelayUs2x(50); //15~60us 后 接收60-240us的存在脉冲
dat=DQ; //如果x=0则初始化成功, x=1则初始化失败
DelayUs2x(25); //稍作延时返回
return dat;
}
2.2.2 MCU读取一个字节
unsigned char ReadOneChar(void)
{
unsigned char i=0;
unsigned char dat = 0;
for (i=8;i>0;i--)
{
DQ = 0; // 给低电平信号
dat]=1;
_Nop();
_Nop(); // 维持一段时间
DQ = 1; // 给脉冲信号
if(DQ)
dat|=0x80;
DelayUs2x(25); // MCU采样
}
return(dat);
}
2.2.3 MCU写一个字节
void WriteOneChar(unsigned char dat)
{
unsigned char i=0;
for (i=8; i>0; i--)
{
DQ = 0; // 先发送一个低电平
_Nop();
_Nop(); // 低电平时常大于1us
DQ = dat0x01; // 将数据发送到总线上
DelayUs2x(25); // 保持数据一段时间
DQ = 1; // 释放数据线
dat]=1;
}
DelayUs2x(25); // 时序缓冲处理,可省略
}
2.2.4 读取温度转换值
unsigned int ReadTemperature(void)
{
unsigned char a=0;
unsigned int b=0;
unsigned int t=0;
Init_DS18B20();
WriteOneChar(0xCC); // 跳过读序号列号的操作
WriteOneChar(0x44); // 启动温度转换
DelayMs(750); // 等待数据转换完毕
Init_DS18B20(); // 初始化DS18B20
WriteOneChar(0xCC); //跳过读序号列号的操作
WriteOneChar(0xBE); //读取温度寄存器等(共可读9个寄存器) 前两个就是温度
a=ReadOneChar(); //低位
b=ReadOneChar(); //高位
b[=8;
t=a+b; // 数据合并
return(t);
}
值得注意的是,主器件每次对从器件的操作,都需要符合固定的通信格式,详细地可以参照2.1节协议分析。
2.3 协议验证
限于手上只有一个DS18B20温度芯片,所以直接将它与MCU相连就可以了,采用默认的12位温度转换方式。不考虑扩展的情况。测试的工具是一个量程为0置100度的液体温度计,温度计的分辨率为0.1度。下面是一个室内室外早晚的测试结果表。
表2.2 温度测试表
测量次数
温度计测量值(度)
DS18B20测量值(度)
1
12.5
12.496
2
16.5
16.496
3
18.7
18.695
4
14.8
14.795
对照上述的结果表,可以看出测量结果还是比较精确的。产生误差的原因有两个方面,一个是传感器量化的误差,另外一个是温度计的测量精度有限,人眼来读取数值时难免会有些误差。
从上述的结果分析中可以看出,主器件MCU与从器件DS18B20之间按照1-wire的通信方式是成功的。
3.Conclusion
总体上来说,1-wire协议还是不复杂的,但是对时序的要求还是比较高;相对其他的两线或者三线协议,1线协议要实现单根线上挂多个器件是非常麻烦,这或许也是1-wire协议用得不是很广泛的原因吧。
4.Reference
1). //代码效果参考:http://www.zidongmutanji.com/zsjx/465687.html
AT24C02 datasheet