1-Wire搜索算法详解(2)


4 实例及算法分析

要理解算法,或制定算法,我们需要通过一个实例来解释:



       ROM示例(仅列出前4位)

ROM编号

1234……

ROM1

0011……

ROM2

1010……

ROM3

1111……

ROM4

0001……

  ​

DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法详解(2)

​                                                       84ROM的实例


假设1-Wire挂有4个ROM如上表,这里ROM码仅设4位,编号从1到4,而实际器件ROM位序号从1到64。右图则是按“先0后1”遍历顺序所构造的二叉树,可见第一次遍历得到ROM4:0001,第二次遍历得到ROM1:0011,依次类推。图中打有⊕的位置就是分叉点,即差异位(或称为混码位)。


现在根据上例观察各次遍历,提炼出算法及实现细节:

1.第一次遍历时,第1位Bit1时遇到第一个差异位,按左序遍历即先0后1的顺序,我们选择0(称为方向0);Bit2不是差异位,算法根据“二读”后方便地选0;Bit3又是差异位,再选择方向0,Bit4也不是差异位。遍历结果得到ROM4。这里可得出结论1:凡遇到新的差异位,选择0


2.第二次遍历,按实例分析,Bit1处再选择方向0;Bit2取0;Bit3不能再选0了,改选1;Bit4取1;得到ROM1。问题随即产生:问题1、Bit1为何走0?此处确为差异位,但已不是首次经过,不能套用结论1。问题2、Bit3处为何改走1?简单分析问题2可得出结论2:凡上次遍历时最后一个走0的差异位本次应走1。很明显,“上次遍历时最后一个差异位”下的左分支已经走过,这次应该往右走了。再回答问题1可以得到结论3:凡上次遍历时最后一个走0的差异位之前的差异位仍按上次遍历的老路走。这个结论比较拗口,观察实例来解释:Bit3是差异位,右分支还没走过,现在我要走Bit3下边的右分支,Bit3之前当然是按上次路线来走了。


3.然后是否就可以进行第三次遍历了呢?且慢,有一个重要的问题还没处理。第二次遍历前,那个“上次遍历时最后一个走0的差异位”应该Bit3,我们先设置一个变量LastDiscripancy来记住这个值,即LastDiscripancy=3;从前面的分析我们知道,这个变量的值是决定本次遍历时算法在哪个位置改道的依据,而且这个变量在本次遍历全过程中应该保持不变的,如果在该节点前或后有任意多少的差异位,均按结论1(新差异位)和结论3(老差异位)来处理。也可以理解成结论2在一次遍历中必须适用且仅适用一次,这也就是每次遍历都能找出一个新的ROM的核心所在。那么第二次遍历后,变量LastDiscripancy应该指向哪个节点呢?直接观察就可发现应该指向Bit1。


4.那么算法中如何实现每次遍历后变量LastDiscripancy的更新呢?要知道这个指针随着每次遍历在或上或下地移动,如第一次遍历后指向Bit3,第二次后指向Bit1,第三次后指向Bit2。还有,第一次开始遍历前、第四次遍历后该变量又指向哪里呢?由于实际ROM多达64位,从机如果数据多的话,差异位也会随之增加,所以算法再引入一个变量Last_Zero,在每次遍历前设为0,即指向起始位置前,然后遍历ROM码的1-64位时,该变量始终指向最后一个走0的差异位,等到遍历完成后,将该变量的值赋给LastDiscripancy即可。这样我们再观察实例的第二次遍历,Last_Zero遍历前为0,Bit1时,符合“走0的差异位”,于是Last_Zero=1,后续Bit3尽管是差异位,但不走0,所以遍历完成后,该值还是1,最后交给LastDiscripancy,自己又指向0,为第三次遍历作好准备。


5.第三次遍历就变得顺理成章:LastDiscripancy=1,Bit1处需运用结论2,算法改走1。而且第三次遍历让Bit2成为了“最后一个走0的差异位”,第四次遍历就在此处改走1了。第四次遍历搜索到最后一个rom,但还得告诉程序结束信号,从而退出搜索。结束信号用什么判定?4个ROM循环4次?当然不是,上述表图只是举例,实际总路线上挂接多少从机是未知的。答案还是利用变量LastDiscripancy来判定,开始搜索前自然有LastDiscripancy=0,但一旦进入搜索程式,每次遍历后该变量总是指向ROM码中间可能的某一位(1-64),直至遍历到最后一个从机,这时必定是所有差异位均走1,如果Last_Zero保持为初始值0,遍历过程中未作修改,遍历完成后LastDiscripancy=Last_Zero=0。以此为条件可判定搜索全部完成。


6.综上,当算法执行到一个差异位时,需判断区分为三种情况:首次遇到型、上次最后走0型、上次非最后走0型。可以通过二个变量的比较来表征这三种情况,即比较当前搜索的位id_bit_number和上次最后走0的位LastDiscrepancy的大小来判别,如下表:


表3.搜索路径方向的确定

三种情况

当前搜索位 vs 最后走0差异位

路径(变量:search_direction)

结论2:上轮最后差异位

Id_bit_number=LastDiscripancy

选1

结论3:非最后差异位

Id_bit_number < LastDiscripancy

同上次 (来自存储的最近ROM码)

结论1:新遇差异位

Id_bit_number > LastDiscripancy

选0

其中变量Id_bit_number表示当前搜索位,每一次遍历时从1至64步进;

变量search_direction表示当前节点经过二读及判断后确定的搜索方向,是0还是1;

其他变量说明请参见流程图中附文。



5 流程图:

图9列出了对一个从器件进行搜索的流程图;注意:流程图附文中列出了涉及到的一些关键变量,并进行了说明,在本文描述内容、流程图及源代码中也将用到这些名称的变量。

其他说明:

1、该流程最终被代码表达成一个函数,可以执行对一个器件的搜索;

2、当主机复位后未收到从机的存在脉冲、最后设备变量LastDeviceFlat=1、二读后均为1这三种情况,函数执行初始化变量后返回False;

3、红色虚线框中流程,是针对差异位的处理,分3种情况走不同分支,该部分是本算法的精华;

4、红色虚线框中最下面的二个绿色背景框,是对家族码的处理,适用于多类型器件混合网络的指定处理,如单类器件组网,可以跳过此部分;

5、图中各指令框边上列出了部分变量、变量比较、变量赋值表达式,以方便后续理解代码;

程序每步搜索确定的ROM码将存入数组变量ROM_no[]中,流程图中“同上次”框中将调用此数组变量的值,获取上轮遍历时该位值。

DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法详解(2)


DS18B20 <wbr>1-WIRE <wbr>ROM搜索算法详解(2)


 

6 实现

上述流程图算法实际上是一个通用的函数,可实现一次从机ROM码的搜索,在附录代码中即为OWSearch()函数,执行一次该函数,可以有以下几种结果:

1.复位信号发出后,未收到任何应答信号,表明无器件挂接或硬件电路故障,函数返回False退出;

2.上轮搜索中程序判断为最后一个器件,本次执行函数也是返回False退出;

3.在某位“二读”时出现“11”的信号,这是执行中出现的异常,函数也是返回False退出;

4.首次或再次进入此函数,信号也正常,函数将搜索到总线上的第一个器件ROM码;

在上述4种结果中,第1和第3是异常后退出,第2是器件搜索完毕后结束退出。第4是首次执行搜索和再次执行搜索这二种情况,可以表述后下面二个函数:First和Next,二个函数通过对LastDiscrepancy、LastFamilyDiscrepancy、LastDeviceFlag和ROM_NO值的处理,利用同一流程实现了两个不同类型的搜索操作;这两个操作是搜索1-Wire器件ROM码的基础,本文附后列出了全部代码,可供测试。

First  

FIRST’操作是搜索1-Wire总线上的第一个从机器件。该操作是通过将LastDiscrepancy、LastFamilyDiscrepancy和LastDeviceFlag置零,然后进行搜索完成的。最后ROM码从ROM_NO寄存器中读出。若1-Wire总线上没有器件,复位序列就检测不到应答脉冲,搜索过程中止。

Next

‘NEXT’操作是搜索1-Wire总线上的下一个从机器件;一般情况下,此搜索操作是在‘FIRST’操作之后或上一次‘NEXT’操作之后进行;保持上次搜索后这些值的状态不变、执行又一次搜索即可实现‘NEXT’操作。之后从ROM_NO寄存器中来读出新一个ROM码。若前一次搜索到的是1-Wire上的最后一个器件,则返回一个无效标记FALSE,并且把状态设置成下一次调用搜索算法时将是‘FIRST’操作的状态。

      Maxim的AN187应用笔记图3(a, b, c)例举了三个器件的搜索过程,为简单起见该示例中的ROM码只有2位。具体过程有二个图和一个表。与本文中的示例也大同小异,所以本文不再列出。有兴趣的可直接阅读AN187应用笔记。


7 高级变量搜索

有3种利用同一组状态变量LastDiscrepancy、LastFamilyDiscrepancy、LastDeviceFlag、ROM_NO实现的高级变量搜索算法,这几种高级搜索算法允许来指定作为搜索目标或需要跳过搜索的器件的类型(家族码)以及验证某类型的器件是否在线(参见表4)。

如果理解了算法原理及各个变量的作用,不难理解“高级变量搜索”的三种功能。本文附带代码为简便起见,删除了这些内容,读者如感兴趣可直接访问AN187应用笔记。


Verify

‘VERIFY’操作用来检验已知ROM码的器件是否连接在1-Wire总线上,通过提供ROM码并对该码进行目标搜索就可确定此器件是否在线。首先,将ROM_NO寄存器值设置为已知的ROM码值,然后将LastDiscrepancy和LastDeviceFlag标志位分别设置为64(40h)和0;进行搜索操作,然后读ROM_NO的输出结果;如果搜索成功并且ROM_NO中存储的仍是要搜索器件的ROM码值,那么此器件就在1-Wire总线上。


Target Setup

‘TARGET SETUP’操作就是用预置搜索状态的方式首先查找一个特殊的家族类型,每个1-Wire器件都有一个字节的家族码内嵌在ROM码中(参见图1),主机可以通过家族码来识别器件所具有的特性和功能。若1-Wire总线上有多片器件时,通常是将搜索目标首先定位在需注意的器件类型上。为了将一个特殊的家族作为搜索目标,需要将所希望的家族码字节放到ROM_NO寄存器的第一个字节中,并且将ROM_NO寄存器的复位状态置零,然后将LastDiscrepancy设置为64(40h);把LastDeviceFlag和LastFamilyDiscrepancy设置为0。在执行下一次搜索算法时就能找出所期望的产品类型的第一个器件;并将此值存入ROM_NO寄存器。需要注意的是如果1-Wire总线上没有挂接所期望的产品类型的器件,就会找出另一类型的器件,所以每次搜索完成后,都要对ROM_NO寄存器中存储的结果进行校验。


Family Skip Setup

‘FAMILY SKIP SETUP’操作用来设置搜索状态以便跳过搜索到的指定家族中的所有器件,此操作只有在一个搜索过程结束后才能使用。通过把LastFamilyDiscrepancy复制到LastDiscrepancy,并清除LastDeviceFlag即可实现该操作;在下一搜索过程就会找到指定家族中的下一个器件。如果当前家族码分组是搜索过程中的最后一组,那么搜索过程结束并将LastDeviceFlag置位。


要注意的是,FamilySkipSetup和TargetSetup函数实际上并没有进行搜索操作,它们只不过是用来设置搜索寄存器,以便在下一次执行‘NEXT’操作时能跳过或找到所期望的类型。

表4.搜索变量状态的设置

功能函数

LastDiscrepancy

LastFamily- Discrepancy

LastDeviceFlag

ROM_NO

FIRST

0

0

0

搜索结果

NEXT

保持原值

保持原值

保持原值

保持搜索结果

VERIFY

64

0

0

验证与预设相同

TARGET SETUP

64

0

0

仅设家族码,其他为0

FAMILY SKIP SETUP

复制于LastFamilyDiscrepancy

0

0

保持原值


8 结论

本文提供的搜索算法可以找出任意给定的1-Wire器件组中独一无二的ROM码,这是保证多点1-Wire总线应用的关键,已知ROM码后就可以对逐一选定的某个1-Wire器件来进行操作。本文还对一些变量搜索算法做了详细论述,这些变量搜索算法能够查找或跳过特定类型的1-Wire器件。

或许Maxim觉得1-Wire器件的软件开销让人望而生畏,因此设计了专用芯片DS2480B系列,可以实现串口到1-Wire线路的驱动,包括与本文档中相同的搜索算法,详细资料请参阅DS2480B数据资料和应用笔记192;以及DS2490芯片,实现USB口到1-Wire桥接,也可实现了整个搜索过程;还有一款I2C桥接1-Wire的芯片。使用这些芯片,无须编写复杂算法,只需向芯片发送命令即可实现多种控制,其最大优点是芯片中集成了1-Wire操作控制的硬件,省却了1-Wire复杂的时序控制,因而其他编程可使用高级语言来编写,甚至直接使用Maxim提供的平台软件在PC上开发1-Wire应用系统。

9 附录

《详解3》中将给出了实现搜索过程的例程,并给出了‘C’程序代码,需要注意的是,Maxim并没有给出“复位、读写总线”等低级1-Wire函数的C代码,而是提示我们可以调用TMEXAPI实现,考虑到本程序在51系列平台上独立调试,代码中提供了这些函数。关于TMEXAPI和其它一些1-WireAPI的详细资料请参考​应用笔记155​。

另须注意:低级1-Wire函数对时序控制有极高要求,其延时代码必须确保符合读写时序,本文程序在1T单片机MPC82G516调试通过,如改用其他1T单片机或12T单片机,必须对延时函数重新调试参数,以获得1-Wire规定时序。另:代码在实测中也适用于“寄生供电”,即不给从机连接VCC电源,代码也可正常工作。关于“寄生供电”的详细信息,请参见Maxim其他应用笔记。