最近遇到一个有意思的编程业务,项目不大,但却有些东西值得说道说道。项目客户开发了一个实验室使用的设备,限于一些客观条件,客户选择了28个步进电机的协作方案,用一台1211的PLC和一块RS485通信板控制它们的动作和顺序(当我接手时硬件部分已经完成了,所以只能在此基础上继续完成控制部分了)。这28个步进电机都是采用Modbus RTU的总线控制,而这28个电机中又分为3个不同厂家的产品,各自有各自的通讯数据表,此外还有若干个温度传感器和压力传感器,也都是通过Modbus RTU和PLC通讯来传递数据。
由于执行一个Modbus RTU的通讯任务,需要经过几个扫描周期,而且不能有两条及以上Modbus的通讯任务同时执行(除非安装多个通讯模块,但是出于硬件成本和设备体积的考虑,客户并不太愿意增加),一个现实的问题就摆在眼前了。一个电机完成一个动作,需要PLC发送一条执行动作的报文给电机,然后发送若干个监测的报文来判断动作是否完成(或发生故障)。如果等一个电机完成动作之后再操作另一个电机,则设备的效率就太差了,因为有很多可以多个电机并行的动作。其次,为每种电机及其每个动作都编写一条Modbus通讯语句,程序的复杂度太高,增加调试和维护的难度。
由此而知,挑战就在于如何在只有一个通讯模块的条件下,实现多个Modbus RTU 电机的并行操作,并且整个程序需要尽可能的简洁且易读易维护。在此分享一下我如何满足客户既要有要的。
首先,由于存在28个电机,而这28个电机的动作基本是一致的(虽然具体实现有所差别),不外乎回原点、绝对定位、相对定位、速度运行、停止运动等这几个动作。自然而然的就想到了面向对象(OOP)的编程方法。为这些电机建立一个类,即一个功能块(FB),FB里面那些变量就是这个类的属性(如图1所示)。其中有一个特别的属性:Command。该属性用于触发这个类的方法,也就是电机需要执行的动作。然后就是编写这些动作的具体程序,用Case Command of…End_Case实现对方法的调用(图2)。最后,再建立这个FB的数组,用For语句循环调用。这样就等于是把28台电机当作一台电机来控制了,程序简明而有效。
其次,只有一条Modbus的调用实现多个电机的并行工作,就需要将每个电机的执行报文和监测报文独立开来。先将所有可以并行的执行报文依次发送到相应的电机,然后轮询地发送监测报文,直到电机的动作完成。那么一个循环队列就是一个非常合适的数据结构。
队列采用数组存放,队列里的每个元素就是一条Modbus的报文信息,包括从站站号、读写模式、数据地址、数据长度等等信息。用首尾两个指针(即数组的索引)来控制队列的进出。当有新的报文需要发送时,就把该报文的数据放入尾指针所指向的元素内,尾指针自增一,若超过数组上限则回到0,这样所有需要发送的报文就在队列中排队等待被执行。而Modbus执行语句则从队列首指针处调出需要发送的报文,每一条报文被调出后,首指针自增一,若超过数组上限则回到0开始,和尾指针一样。当首指针和尾指针相同时,则说明队列里面已没有任何待执行的通讯命令了。
电机类的FB在执行动作时,核心和最终输出就是把相应的Modbus执行参数发送到队列中排队等待执行。而每一条执行报文后又自动触发监测报文的发送。这样做又一个明显的好处,就是电机的动作和Modbus 的通讯完全分离开来,这个循环队列成为两者之间的纽带。在修改电机这部分的程序时,无需考虑和担心Modbus的调用部分,反之亦然。程序的控制逻辑简单明了,清晰易懂。在调试时,监视这个队列的内容就能分析程序的执行情况。
最后,对于不同设备、不同品牌电机,能否采用一种统一的方式来实现Modbus通讯呢?答案是肯定的,就是用数据库。建立一个DB块,存放如图3所示的两个数组就形成了一个简单的关系型数据库,两个数组就是两张表,之间依靠MessageID建立起一对多的关系。在此基础上,所有不同设备和品牌的产品在执行不同动作时的程序结构就可以统一起来,也就是选择数据库中的一条记录。调试程序的时候,只需要看数据库里的数据是否正确,选择的记录是否正确就可以了。哪怕客户将来更换设备或替代产品,也只需要改这个DB块内存放的数据,不会影响到FB中的内容。
总结来说,SCL的长处在于灵活、高效,容易实现较为复杂的控制逻辑。一个好的SCL程序不但要实现客户的需求,更需要一个好的颜值:简洁、易读、易维护、可复用,这才能突出SCL的优越性。而掌握OOP、数据结构和数据库等编程技巧,则是增加SCL程序颜值的有效手段。
(这个项目使用PLC为6ES7-1211-1AE40-0XB0 V4.6,通信板 6ES7 241-1CH30-1XB0 V1.0,博途版本为V18。)


