技术论坛

 一种通用化的Modbus站点任务队列执行的模块实现

返回主题列表
作者 主题
宝冬
奇侠

经验值:9836
发帖数:1491
精华帖:29
楼主    2019-11-06 06:52:06
主题:一种通用化的Modbus站点任务队列执行的模块实现 精华帖  精编帖 

首先上两幅HMI的动态GIF截图,看一下这种通用化解决方案,在界面上看起来是什么样子的。它们分别描述了站点通信良好,和站点掉线通信故障的情形。



------------------------------------------------------------------------------------------------------------------------------------


PLC与任意一个Modbus从站进行通信,都会有一个通信队列。这个队列由多个通信任务组成,它们一个接一个的执行,读或写整数或二进制。

所以设计思路就是:不管和什么样的站点通信,一个给定通信队列的执行过程的处理逻辑是完全一样,可以通用的。这个通信队列从哪里来呢?就需要事先给每一类别的站点,预备一个队列加载的FC模块,把这个队列所需的通信信息和任务配置,定制加载到一个标准队列中。这个标准队列在通信执行完毕后,还需要有这同一个类别站点的数据解析FC模块,把队列中的所有的读取数据都解析到它们该去的地方。这就是一个站点的通信过程。这个过程暗示了一个通信队列UDT的需求,而这个UDT一定是由通信任务UDT做为基本单元组成的。

多个站点就需要轮询调度,一个站点执行完毕就执行下一个。这下一个站点有可能和上一个站点是同类的,也可能是不同类的。每一类不同的站点都要有自己专用的通信前加载和通信后解析模块。这暗示着一种通用的站点调度UDT的需求。

站点轮询中,通信质量不好的,或者不在线的,需要从轮询调度中淘汰出去。这只是临时的,以观后效,表现好了可以召回。 

多个站点之间的轮询,如何切换调度等等,或者每一个具体站点在通信前后的数据如何处理,那些很多是个性化的东西。

这里要介绍的是这个通用的负责单个站点的通信队列执行的模块。


这个模块有两个FB:一个是站点FB;一个是Modbus任务执行FB,后者嵌入在前者内部做为子模块被调用执行。站点FB的功能是把任意从站的通信队列,在其内部调度分派给Modbus任务FB。任务FB每次执行一个任务,依次把整个队列执行完毕。同时顺便采集一些必要的监控信息。 

1、站点FB

下面的图片是站点FB被调用的样子。这个FB的设计是基于假设通常大多数情况下,多个从站会采用相同的通信参数连接到一个485端口,所以没有为每一个从站的执行单独进行不同的端口初始化设置。如果情况需要,比如每个从站的协议有所不同,可以把端口初始化的子模块嵌入到站点FB内部,这样可以为每个不同的从站配置不同的端口参数。

 

下图是站点FB内部的结构,分为5个部分。

 


下面的图片是5个部分分别的细节展示。

(1)485端口除了上电初始化,运行中随时可以初始化。如果需要,每个从站单独的端口初始化的可以在此处进行。


(2)来自人机界面操作者的命令对从站的功能模式随时进行改变,所导致的从站任务队列执行需求的动态变化,在此处实施。


(3)调用通用化的Modbus读写执行模块


(4)整个任务队列执行时间在HMI上的动态显示。要求不高,基本上通信质量所导致的时间延长或缩短可以在HMI上供操作者判断。


(5)


2、Modbus任务执行FB

下图是任务执行FB内部的结构,分为4个部分。

 


下面的图片是4个部分分别的细节展示。

(1)and(2):整数和二进制的执行是经过判断分开的。


(3)成功计数供HMI监控通信质量。写操作的记录是为了成功之后不再重复写,除非有变化。


(4)错误计数和错误代码也是供HMI监控通信质量。同时支撑通信质量差的站点的淘汰策略


这两个FB,以及调用站点FB的上层调度模块,都采用兼容存储模式。

上述模块是在下面图片所展示的调度环境下被运用的。


------------------------------------------------------------------------------------------------------------------


上述的站点通信模块的设计思路是比较清晰简单的,但是大家一定会看出,这种效果是与模块中用到的几个UDT的结构设计密切相关的。 

1、通信数据。这个是为了通用化的Modbus任务执行FB而设计的。事实上这个不是必要的。 


2、通信任务。通信数据是做为通信任务的元素存在的,其实这个早期的做法增加了逻辑冗余,使结构不够紧凑,只是在这里懒得改了。直接集成就可以。 


值得一提的是通信任务中有一个执行的控制位。站点FB从通信队列中一个接一个派发通信任务的时候,首先要检查这个控制位是否为真。如果为真就执行通信,否则进行下一个。这个位的作用就是实现了,同一个站点在不同工作模式切换的情况下,通信队列内容发生变化的实现。它事实上是更改了通信队列模板塑造出来的标准队列配置。

3、通信队列。它就是通信任务的数组,它的长度是由一个常量决定的。通信队列的另一个用途是为每一类站点生成自己特定的基本任务队列模板,用于初始化自己的通信队列。而来自HMI的操作命令变化和数值更改就是在基本队列配置的基础上,来动态修改通信队列的内容的,这个工作是由站点装载FC来做的。 


4、站点记录。这里包含了多种记录,所有记录的长度都是由通信队列中用到的同一个常量决定的,这也是采用这个常量的作用。站点淘汰机制的相关结构包含在这里,这是因为站点记录除了单独实用,更主要是做为每类特定站点UDT的元素存在的。 


特定类别站点UDT定义的实例。此处以一个温控器的UDT抽象为例,这个UDT定义如果全部展开有200多行,放不下也太凌乱,所以此处仅展示站点记录是做为特定站点UDT的元素存在的这一点。各种设备的功能是完全不同的,但它们可以采用相同的通信控制元素。


站点记录这个UDT的作用有如下两个:

● 可以以此为依据看出线路通信质量的好坏,比如存在干扰的情况。通过硬件手段的改变,在HMI上直接就可以看出来通信质量是否有改善或者恶化,便于现场的判断。

● 可以做为淘汰或召回站点通信的依据。我通常根据:站点通信队列中的每一个通信任务的记录中,成功次数小于失败次数且失败次数大于3,就把该站点淘汰。重新尝试的时候,就是把先前的记录清空,如果后来质量改善了,就不会被淘汰了。或者干脆强制继续通信,用于诊断和维护。


5、站点轮询单元。这个UDT是为了在顶层整体的通信调度中,能够采用通用方法去调度不同种类,不同数量的站点而设计的。 


下面通过两个图片来演示这种UDT中的数据信息,如何用来进行顶层站点调度。

(1)下图是以上面提到的温控器为例,在通用化的站点FB执行之前,首先要按照项目需求和来自HMI指令和参数的随时变化,对通信队列进行动态调整的站点装载FC。这个FC是为这类温控器量身定制的。每一种不同类别的从站需要有自己独特的站点装载FC。


(2)下图还是以上面提到的温控器为例,在通用化的站点FB执行之后,负责对通信中获得的信息进行处理的站点后期处理FC。这个FC也是为这类温控器量身定制的。每一种不同类别的从站也是需要有自己独特的站点后期处理FC。


6、通信端口。为了可能的端口配置的参数化和标准化而设计了这个UDT。但在大多数场景下这个其实不是必要的。


------------------------------------------------------------------------------------------------------------------------


通过上面两篇展示的FB和UDT的设计,可以看出如下的道理: 

如果某一个功能的结构设计,是采用面向对象的策略来对待,那么它就会通过几个FB或FC的组合运用来实现,而这些FB或FC都是同步运转的,也就是这些块之间是存在紧密联系的。 

UDT就是把各个FB或FC之间的共性关联因素聚合在一起形成的结构。 

间接寻址是某一个FB或FC内部的空间遍历行为。通过UDT的结构关联,各个独立块的内部行为就可以动态协同起来。

建立在UDT基础上的参数化接口,使得模块化分割能够实现。结合在一起,这就是面向对象模式的功能实现。


---------------------------------------------------------------------------------------------------


那个Modbus任务执行FB是没必要单独存在的。

这个任务FB只内嵌在站点FB之内,其它地方并没有用到,所以不应该单独抽取出来做成模块,没有通用性,徒增冗余。正确做法应该是:把这个FB的内部逻辑散开,直接写到站点FB内部,这样就只有一个站点FB了,更紧凑简单,管理方便,还能减少一个UDT。

根据上面的说法,动手改一下代码,很快完成,如下图。


只需要把原来站点FB中第三部分(调用任务FB的代码),用任务FB内部的代码直接替换(见红色方框内部),相关变量复制粘贴和改一下名称以匹配,就测试OK了。

模块化风格可以大幅度提升调试效率,尤其是复杂点的任务,因为任务都被分割局部化了,特别容易排错和随时更改。很省事,相同的因素不用来回牵连折腾。


------------------------------------------------------------------------------------------------


关于站点的淘汰和召回



----------------------------------------------------------------------------------------------------


Modbus本身不复杂,但如果考虑到各种可能的场景,就会意识到问题的关键不在于Modbus本身,而是如何封装需求。

越是底层,距离硬件越近的东西,编程越容易。因为它离需求很远,变化就少,所以就非常固定。而任何固定的知识点,并没有本质上的难度,只是做功课的问题。

越是趋近表层,距离用户越近的空间里,问题越复杂,因为它距离“人”非常近,而人的需求是充满变数的东西。

写这个帖子名为谈论Modbus,实际上是在展示如何界定个性与共性,如何最大化提取共性和封装个性。只不过是借着modbus这个应用场景来展示通用策略的一种实现方式。

比如有多种品牌的Modbus站点,每种若干个。同品牌站点中的每一个运用的方式都不同,且同一个站点的工作模式随时动态变化。这些站点都可以分接在不同的通信口上,但要求实现方式标准一致。通信参数可以在现场由操作者重新设定。操作者可以在现场以非编程式的监控和通信故障诊断。而且这个编程任务不能过多的增加程序员的工作时间,也就是能够很快的实现大幅度需求变更,而且要求降低程序调试的错误率。

上面说的这些需求如何同时满足呢?这个帖子就是说这个。

这个整体结构说白了很简单:一个大FB中,内置一个针对多个不同种类的多个站点的通用调度机制。在此基础上,内嵌3层呈流水线式排布的子功能区:通信前,通信,通信后。其中通信FB是完全通用的。通信前后,需要给各个类站点预备事前装载和事后解析的FC集合,但编写方式几乎一样。多重实例运用很少,更多采用参数实例。

如果需要把站点从几个扩展到几十个,且多个品牌多用途多工作模式,这种设计结构下就非常容易实现扩展,且调试排错非常容易。1-2天就可以编程完毕,不用熬夜。这就是设计这种结构的用意。

记得我第一次采用这种结构完成程序的时候,一个错误都没有,直接运行一把OK,自己都有点惊讶,这在面向过程的套路下是不可能的。就是因为这种元素分离的架构下,泡沫非常容易挤出去。


-----------------------------------------------------------------------


最近摆弄C#上位机,突然联想到关于面向对象的原则体现,这个帖子还能写几句。

面向对象六大原则中有几条在PLC是可以用到的,比如:单一职责、开闭原则、迪米特法则。抽象的纯净,耦合就会少。作为结果和效率,对扩展开放,对修改封闭,是一个主要目标。

在本帖通用的完整modbus解决方案中,为了体现开闭原则,特截取几张图片来说明:如果需要新增不同种类和数量的从站设备的时候,究竟哪几个地方需要做怎样的修改,而其它部分是完全不用动的。


1、用户常量扩展


用户常量位于默认的PLC变量表中。

Top_JobsQueue是标准通信队列的长度(数组顶端序号)。这个是用于所有设备的,几乎不用改(除非某个设备的通信任务数量超过设定值,此处为30)

Top_Station_All是所有通信站点的数量(数组顶端序号)。如果有ABC三类设备,A类有5个,B类有3个,C类有4个,那么这个数值就设为11。

Top_Station_West是具体某一类站点的数量(数组顶端序号)。这里我用了5个West温控器,所以数组顶部为4。

如果需要新增某一类不同modbus通信结构的设备,那么就需要增加一个对应的常量(Top_Station_OtherDevice),并给出数组顶部的数值。添加的方式类似很容易。


2、站点数据扩展


需要建立新增类别站点设备的UDT数组,和该类设备的标准通信模板。

UDT抽象要包含该类设备用到的全部功能。通信模板根据设备的通信文档来设置。添加的方式类似很容易。


3、站点调度数据扩展


增加站点轮询调度数组的长度,并把相关的站点调度数据加入进去。

添加的方式类似也很容易。


4、程序扩展


横线以上部分完全通用不用更改。

横线下方的5个部分是个性化的,复制粘贴一下,新增段落。

编写子模块,看起来多,其实编写套路非常相似。而且由于模块化的方式,内容单纯简单,调试排错非常容易。



这样的话按照上述方式,增加10几个完全不同通信方式的设备就相对容易。

重要的是原来的代码不用动,调试纠错非常容易,架构不会崩。

而且新增的内容虽然不同,但结构和方式是一样的,不会乱,管理清晰方便。


Zaxife
至圣

经验值:12573
发帖数:2503
精华帖:31
9楼    2019-11-18 09:58:49
精编帖  主题:回复:一种通用化的Modbus站点任务队列执行的模块实现

PLC的RAM不多,模块化编程虽好,但是PLC吃不消。

模块本身buff起码300个字节,然后每个序列又要独立的寄存器映射RAM,然后每个站点的RAM又是成倍的RAM消耗,如果PLC没有128k的RAM真没必要这样折腾。

嗯,西门子的POL控制器有512kb的RAM,多个相同站点轮询这样做确实非常方便。但事实上是很多站点都是不尽相同的读写指令然后通讯逻辑都不尽相同,到头来还是要再次封装模块,到最后发现还不如用自带的库来得简单。

您收到0封站内信:
×
×
信息提示
很抱歉!您所访问的页面不存在,或网址发生了变化,请稍后再试。