技术论坛

 200Smart子程序增强背景数据块的实现说明

返回主题列表
作者 主题
xiatianyun
奇侠

经验值:5423
发帖数:850
精华帖:10
楼主    2023-02-27 10:02:19
主题:200Smart子程序增强背景数据块的实现说明 精华帖 

总算静下来总结下了。以下内容慢慢整理,原来是md文档,拷贝过来分享。

2023年02月27日

by:azjiao@163.com

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

200Smart 增强背景数据块的实现

200Smart子程序的特点

200Smart是具有很古老的日式风格的小型PLC,其函数用子程序来实现,叫做SBR。SBR类似于TIA平台1200的FC,没有自己的私有数据。

SBR的用来实现一些不需要保留私有数据的一次调用函数功能,比如一次性的数学计算,如果需要使用前几次的计算结果,通常可以使用几种方式传入SRB,一种是使用接口的InOut,一种是不使用接口而是直

接使用全局变量。但直接使用全局变量的缺点是显而易见的,抛开不能模块化的问题,使用全局变量要想实现多个实例也是很有难度的,这里的难度指的是使用和管理变量的难度。

常规意义上来讲SBR多次调用需要注意很多问题。接口变量的分配其实还是次要问题,毕竟1200也有这样的问题。但1200有DB,1200的FC可以使用DB来区分不同的实例接口。而200Smart的子程序就只能靠设计者自己来规划不同的V区来区分不同的实例。

多次调用还有很多和语法特点有关的注意事项,比如讨论很热门的沿指令,在SBR多次调用中就不能使用。其他更难为人的指令是定时器,当然定时器不论是SBR中还是在主程序里都不能多次调用,这个不是SBR的问题,但通常也会伴随子程序多次调用出现更多问题。

归根到底,200Smart子程序的问题就是V区变量规划问题,它给设计者带来了更多和编程无关的管理工作。通常你需要准备几张纸来手动规划V区。无论是采用电子表格还是纸,随着设计的推进,修改V区修改已经调试好的模块是初学者面临的棘手而繁琐的工作。

解决问题的引入

在论坛上有篇帖子是关于集中报警如何实现的,我试着实现了这个程序。实现的方案给我带来了一些解决200Smart如何才能让SBR具有类似FB背景数据块的实现方案思路。

具体来说就是分配出不同尺寸的连续的V区域,每个区域由一个索引来唯一定位,这样只要知道了索引就能定位这个区域。每个区域就是一个背景数据块,可以被SBR分配使用。

这些DB最好位于V区的最后部分,以我实验的ST20来说,V区共计8190个字节,如果背景DB需要使用3000个字节,那么开始地址大约是从VB5000开始的。这里面包括了之后要设计的管理段,实际分配给SBR使用的背景DB容量小于这个数量。这些关于DB开始位置、容量、以及适配不同型号PLC的V区最后地址的参数可以被修改而无需修改管理程序。之所以把V区不同的最后地址也作为参数,是因为要判断设置的其他几个参数是否合法。

其实能够想到上面的内容只是个开端,那如何知道子程序要使用哪个DB呢?

论坛上有一些讨论如何实现背景DB的内容,大多数需要在每次扫描过程中保证每个子程序按照既定的顺序来执行,不能出现条件调用子程序的情况存在。说白了就是执行顺序和DB放置的顺序是隐式对应的,执行完上一个子程序,接下来的子程序使用的DB也就是刚才调用子程序所使用的DB的下面一个DB。这种分配DB的缺陷是很明显的,SBR的执行顺序被强制要求,抛弃了一些PLC程序设计的灵活性。当然好处是设计简单,程序量少。

SBR如何知道要使用哪个DB?这个就和开始提到的集中报警程序的实现有联系了。只需要在SBR接口的InOut处设计一个索引参数(DB_Index),这个索引定位了DB。如果该子程序是第一次被执行,拖油瓶的索引接口变量就从索引资源变量(IndexSource)处取得当前可以使用的索引,索引资源自加一,以便下个SBR分配索引。如果不是第一次被调用,则不会再分配索引,而是使用原来的索引值,也就定位了同一个DB。

SBR内已经定位了DB,要怎么使用该DB呢?可以有两种使用方式:

一种是复制DB数据到SBR临时变量区,在Temp区使用的好处是可以用符号变量操作。使用完毕在退出子程序前把同一个Temp内容复制回DB区就实现了犹如在DB区中使用数据一样的效果。

第二种使用方式是直接在DB上操作数据,由于并不知道该SBR使用的DB地址,也就不能开始就设计好符号变量(事实上可以开始就设计好符号变量,后面有叙述。),所以直接使用就是用指针操作数据。由于指针操作存在很多不便性,所以这种方式主要用于使用指针显得很方便的场合,比如数组操作之类。

随着背景DB实现程序的设计推进,我使用DB的方式这两种都有采用,针对不同的应用场景有不同的使用方式。比如,如果子程序的Temp区足以容纳需要的数据,就使用Temp操作;如果SBR的Temp容量小需要更多的存储区,就把多余的部分使用直接在DB中操作。如果接口比较多,需要使用更多的接口数量,就使用事先设计好DB中的符号变量,直接操作符号变量的方式,在遵循一定的设计顺序后就可以在该方式下也实现多个实例化。

实现的代价

要想实现上述功能,牺牲V区的一些空间和增加程序代码的数量、增加扫描时间是必然的代价,其中,牺牲V区空间其实是谈不上的,V区通常足够大,一般来说不用白不用,用了不白用。最主要的增加了代码体积和增加了扫描时间。

在不增加其他功能,单纯一个做好的具有背景DB功能的空白模板,编译后块大小是2657字节。初始化程序也只在First_Scan时调用,由于空白模板没有其他子程序,也看不出扫描周期增加多少。我实验过用自定义定时器使用DB,50个定时器一起执行扫描周期大约17ms-18ms的样子。50个自定义定时器可以简单理解为50台使用DB的简单设备。也许确实程序占用太大了,ST20的程序存储器总共只有12k,这几乎占用了1/5左右,但何不换个CPU呢?18k、24k。还是先做起来再说吧。

增强背景数据块的功能

所谓增强,无非是在基本功能的基础上增加了其他一点点功能。200Smart的接口数量是有限的,只有16个IO接口,超出限制将不能正常添加形参。有人说可以把多个bit形参组合成Byte、Word、DWord之类,确实有效,但这个不是讨论的重点,所谓曲线救国毕竟是曲线呀。要怎么做呢?当然是使用指针了,也就是全局V区作为SBR的形参,可这还是需要人工规划不同的V区来适用不同的实例,没有解决根本问题。

既然已经有了可以自动分配和定位的DB,那么何不规划出几处特殊的DB来作为扩展接口的数据块呢?同一种类型的实例使用同一个DB,在DB中事先设计适用于同一个类型的接口变量形参,这样SBR内部直接使用DB内的变量形参,不用更改。那如何才能适用于同一类型的多个实例呢?我想到了子程序内部使用的Temp变量运行机制。200Smart子程序的临时变量区其实只有一个,是公共使用的。也有资料表明200Smart的Temp区一共14个,这涉及到200Smart的嵌套层级,这个和我想到的实现无关,只要想到这些就足够了。

我的实现方案是设计14个特殊用途的DB,用来作为Temp区使用,命名和子程序内的Temp冲突,但只要知道这个和子程序Temp是不同的概念就可以了。为了区别,以下暂且称为DBTemp吧。DBTemp的用途一方面是由于SBR内部还是有需要使用全局变量的情况出现无可避免,比如......(想不起来了,大概跟指针反引用有关),如果需要,则把这些数据用某个DBTemp内的预先定义的变量替代,只要使用前后让这个DBTemp进行FIFO操作,这样无论SBR何时执行都能够正确操作。这样就需要设计DBTemp的FIFO算法。设计14个DBTemp也和之前提到的嵌套有关,其实加入背景数据块后会降低嵌套深度,因为背景数据块管理程序需要调用一级子程序,如果需要使用背景DB则留给用户的嵌套深度是7级主循环嵌套调用。

考虑到其实实际项目中很少有需要使用全部14层级的情况,所以我安排用最后4个DBTemp作为SBR扩展IO接口。4个也就是只有4类子程序需要扩展接口。之所以是4个,主要是考虑到可能需要扩展的类型有限,使用原来的16个接口就可解决大多数类型的设计,这也是实验阶段目标所导致的,如果需要可以修改少量代码就可适配。

使用时首先按照FIFO原则先指定需要使用的DBTemp编号入队,以保护前面使用者的数据不丢失,然后给DBTemp内的SBR形参赋实际参数值,然后调用SBR。调用完毕处理DBTemp内的需要输出的参数,最后出队,恢复前面使用者的数据。这里涉及到接口类型,作为扩展接口的参数全部类同于InOut类型,Out和InOut需要调用完毕及时转存,而In需要设计者自己保证不会破坏原有数据。

那么DBTemp每个的容量选择多少合适呢?我设计了50个字节长度,这个可以在DBInit模块使用参数进行调整。

而背景数据块设计了255个DB可以使用,也是可以在DBInit模块使用参数进行调整。

管理模块的设计

管理堆表头设计

在V中分配的背景DB需要有一些空间来容纳管理变量,我把这些变量放置到了开头,称作管理DB的表头。

这些表头信息是:

pHeapHead 指向自身的堆指针,也是Heap首地址。

HeapStatus 堆状态信息。

DBCap 数据块预置容量

DBLen 数据块实际使用长度

pDBTerminal 数据块终点地址

pDBVector 数据块头指针

IndexSource 索引资源

指定数量的全局数据块索引

表头和DB之间的分隔符"#"

这些变量的作用涉及到许多技术实现细节,以后慢慢添加。

之后是数据块,不过头两个DB块是特殊用途DB的管理块,本质上这两个DB和之后的几个特殊用途DB都是数据块,只不过由管理程序使用,分配用户DB也不会用这几个块空间来分配。

数据块的结构

每个数据块的第一个字是DB容量,这个由DB分配程序来读写,用户不用操心。
之后是一个字节的DB标志符"FF",这个是方便调试而设计的,并无多大作用,也是设计遗留下来的东西,唯一的作用是可以帮助分辨DB开始。

这个标识符唯一不存在于接下来的一个DB。

然后才是真正的DB数据。

Temp块管理DB的设计

Temp块管理DB管理的其实不只Temp块,还硬塞进来了FIFO队列。 FIFO队列后是14个存放DBTemp的指针向量。DBTemp动态分配,这些指针表在DBTemp初始化时自动生成。由于设计也是逐步推进的,这里采用直接存放4字节的指针,而不是像接下来的用户DB指针表存放的是V编号,也就是指针的低字数据。

用户DB指针表(用户DB管理表)的设计

开始的版本其实没有这个DB,用户DB的定位采用的是遍历定位方式,但考虑许久还是专门设计出一个DB来存放用户DB开始的指针数据,这样DBIndex不是直接定位用户DB,而是首先定位这个表的数据,然后提取数据作为指针来直接定位用户DB。好处是代码量少些,也可能执行起来快速些。

用户DB不是一开始就规划好的,而是动态分配的,位置、尺寸都是动态由子程序自己所需DB尺寸自动生成的。

DBTemp块的设计

DBTemp共计14个,每个都具有用户指定的大小,比如50个字节。没有内部特殊结构,和普通DB没有区别。

用户可以使用的普通数据块

共计255个容量,每个的大小不同,由用户子程序决定。

好了,增强背景数据块的基本结构也已经说了,和我有同样需求的朋友相信已经能够知道我要如何做了。这里给出结构的截图方便大家理解:


模块设计

一共有6个模块,分别是DBInit、BaseInit、DBMalloc、Temp单区分配、TempStack、ByteClear。中文变量其实也蛮好的。

DBInit

First_Scan_On时调用,主要作用是判断库分配的开始地址是否合适,初始化DB表头信息。
表头信息里面有个状态字节HeapStatus,设置了几个反应运行中是否发生DB错误的判断信息,给调试带来一些便利。

bit0: 1 初始化时超过了V区容量

bit1: 1 库设置的V区全局变量pHeapHead没有位于开头,主要是反应DBInit的参数pHeapAddr和pHeapHead不一致。

bit3: 1 且bit0=bit1=bit2=0,初始化完成。

bit4: 1 已经使用的容量超过了预置容量

bit5: 1 DB定位失败

bit6: 1 用户DB数量超过DBQTY限制。

BaseInit

这个也是在Fisrt_Scan_On时调用,主要作用是初始化几个特殊用途数据块:DBTemp管理块(FIFO、DBTemp地址栈表)、用户DB地址表、DBTemp。

DBMalloc

这个是公共模块,用于DB分配。如果SBR是首次扫描需要获取可用的V区开始地址作为用户DB地址,并把这个地址存入用户DB地址表中来。如果不是第一次扫描,就直接从用户DB地址表中获取实际用户DB地址并返回该地址。包括一些错误判断和状态信息的给出。

Temp单区分配

这个就简单了,就是直接分配14个DBTemp。只不过没有使用浪费全局索引而是使用temp变量,因为分配完后DBTemp地址会存放到DBTemp管理区,就不需要索引来定位DBTemp了。

TempStack

TempStack实现DBTemp的FIFO,根据传入功能ii_Function的不同可以选择是入栈还是出栈。
byi_TempNo是DBTemp编号,设计了几个特殊用途的编号:
0#作为普通temp扩展,入口参数byi_TempNo=0.
10#~13#作为通用设备实例的I/O接口参数使用,byi_TempNo=10~13。
其他Temp区作为Temp栈使用,从1#~9#共计9个块,可供总计9层子程序嵌套使用,用户不能指定,由FIFO管理到底使用哪个DBTemp。
主程序外最多可以嵌套到第8层,实际由于需要使用静态DB管理(调用1层子程序),再加上本程序占用一层,如果需要使用Temp扩展(本功能块),只能嵌套到第6层。
如果中断需要使用本功能,则最多只能在中断内使用2层嵌套子程序。

ByteClear

用于大尺寸V区清零复位,也可以是任何数据赋值给V区初始化。


MAIN中的固定程序

主程序开始执行一次DBInit和BaseInit。  


DBInit接口参数:

ii_MainDBQty: 需要在MAIN中使用的DB个数。用于在主程序中调用子程序,自动在DB管理表头空出设置数量的字节空间,用户查看地址后在符号表定义这些主程序中使用的DB索引变量。其实也可以设置为0,主程序中调用子程序时使用的DB索引变量可以是任何可用变量,支持读写即可。这里在DB管理表头留出空间来给主程序使用完全是规划的结果。

ii_HeapCap:DB容量。实际需要的容量比这个要多些。
假设ii_MainDBQty是26,DB管理表头需要46字节,实际需要空间为3046字节。而其他固定空间:假设每个DBTemp容量为50字节,DBTemp共计14个区(14X52=728字节),加上管理Temp所需空间(92+2=94字节)和用户DB地址管理(假设一共255个用户DB数量)(255*2+2=512), 加上DB管理表头46字节,共计需要固定空间为1380字节。
如果ii_HeapCap设置3000字节,用户实际使用空间为3000-1380=1620字节,用于静态DB。

pHeapAddr:DB放置的开始地址,要和库分配的开始地址一致。

pVLimitAddr: 200Smart的V区最后一个单元地址。主要是为了判断是否配置了足够的区域来放置DB区。


BaseInit接口参数:    

1. ii_TempCap: DBTemp每个的容量。

2. ii_DBQty: 用户DB的数量,设置可用的子程序数量。也用于分配用户DB地址管理区。


带DB用户子程序的使用

SBR_Template参数

有几个固定的接口参数和temp数据:

1、byiq_DBIndex: 子程序DB索引变量。

2、byq_ErrInfo

3、bq_Error

4、_pDBAddr_: 由DBMalloc取得的DB开始地址。

5、_BYTELEN_: 用于指定需要复制到temp中的静态DB数据长度。

6、_EXTLEN_: 如果temp不足与容纳静态DB,指定需要扩展的DB长度。需要用指针来操作数据,而不是变量符号,所以扩展DB最好少用。

7、_FLAG_: 静态DB复制到temp的开始标识。其下的temp可以认为是静态DB,需要注意不要超过L60。


几处固定用法:
btFirstRP和bFirstMem用于生成首次使用脉冲。
tempAC0和tempAC1用于累加器保护,仁者见仁智者见智,随意。 DB_Temp0: 用于该子程序调用另外一个子程序的DB索引,符号随意。由于DB索引需要可读可写,不能使用temp变量,所以就顺理成章地用静态数据了。


扩展temp的使用

扩展接口的使用


聿舟工控
侠士

经验值:1456
发帖数:201
精华帖:2
9楼    2023-02-27 17:06:40
精华帖  主题:回复:200Smart子程序增强背景数据块的实现说明

       如果从纯理论上说,楼主的想法值得思考和探讨;但非要在smart200上实现这个功能,我也觉没这个必要;smart200,本来就是为小项目设计的,何必把其功能做的如同1200,甚至1500呢;我想这也就是西门子团队为什么不这样做的原因吧;何况这样也会影响PLC的稳定性,增加CPU的符合,并给新手带了困惑;

      仅是一点不成熟看法,无他而已!

学无止境,永远保持一颗谦虚上进的心。
您收到0封站内信:
×
×
信息提示
很抱歉!您所访问的页面不存在,或网址发生了变化,请稍后再试。