1 概述
梯形图转换为指令表之后,下面一步当然就是编译了,用户指令经过编译,才能转换为
PLC 系统可识别和执行的指令格式。
当初设计编译器的时候,主要考虑两点,一个是尽量降低与主程序的耦合度,方便以后
的升级和维护;二是接口统一且灵活,可以很容易的支持新产品和新特性。否则随着产品系
列越来越多,上位机软件的维护会成为噩梦。
基于这两点,当时选择了 XML 文件来定义指令格式,每个系列的 PLC 都有一份独立的
XML 文件,它们具有统一的格式和对外接口,内部则详尽描述了各个 PLC 的特性。这样,
在主程序代码里,不用出现任何跟机型名称或代号相关的处理,只需要创建一个对应型号的
XML 查询器,就可以知道每个 PLC 指令系统的所有细节。
试想,如果把机型全部定义在主程序里,那么代码中肯定会频繁出现类似下面的代码,
在这种情况下,当你要更改某个机型的某条指令的时候,就只能查找查找再查找。。。
而在 XML 文件里,由于格式统一,结构清晰,定位和修改都极其方便。并且更改之后,
不会对主程序造成任何影响,只需要重新编译相应的资源 DLL 就可以发布使用(所谓升级补
丁大概就是这么来的)。当然,要做到这一点,必须对 XML 的结构和内容进行仔细、严密、
完整地设计,确保他能够容纳指令系统的所有变化。
现在回头来看,当时这个选择还是挺具有前瞻性的,随着后来各个系列的 PLC 逐渐推出,
XML 接口的优越性也充分体现。因为每新增一个机型,单板软件几乎都要重做一遍(虽然大
部分代码可以参考),但是上位机软件只需要复制一份 XML 文档,修修补补,一天之内基本
上就搞定了。事实上,XML 文件在我们的上位机软件上大量应用,不只指令系统,像硬件配
置、系统错误等都是用它来实现的。
基于 XML 接口的整个程序结构如下图
用户编程的时候,只要选定型号,那么相应的 XML 查询器就生成了,主程序此后不再关
心型号名称,因为后面对用户程序的校验和编译等,都是通过查询器接口来做。
大家可以看看三菱或西门子的编程软件,新建工程的第一步,都是选择系列型号,异曲
同工。
2 指令码的格式
现在三菱的程序格式已经被大家琢磨透了,但是当时我们在做第一款 PLC 的时候,并没
有参照三菱的格式,而是完全自己设计的一套。
首先指令系统是解释型的,因为这样简单灵活,虽然性能相比编译型的稍差,但是在用
户层面区别并不明显。指令格式应该算是 CISC 结构的,各条指令的指令长度和参数长度都不
尽相同,这样做的目的主要是加大代码密度,减少占用空间,因为当时 ARM 芯片的片内
FLASH 一般都比较小,而用户程序动辄十几 K 步,还是比较占空间的(一步=两个字节)。
在这里不一一列出各个指令的编码格式,有兴趣的可以去看看三菱的。总体原则是:
u 简单指令(比如 LD AND OR OUT)单步完成
u 复杂指令(简单指令之外的各种计算指令和应用指令),指令码一步,参数类型一步,
参数值一步或两步。
3 XML文件结构
XML 文件里包括两个大的子结构,一个是对指令的描述,一个是对元件的描述。指令的
描述部分,说明了该指令的指令码、类型、参数个数及每个参数的要求,而元件的描述,则
说明了该元件的数据类型、编码、寻址方式和读写属性。
对一个指令进行解析时,先查询它的参数要求,然后再根据用户输入的参数,逐个查询
参数的信息,并与指令要求进行比对,以确认指令和参数都输入无误。以上都确认完毕后,
就将指令编码和元件编码写入到最终的执行代码。
4 XML查询器
实现 XML 查询接口的库有很多,有微软官方的,也有开源的,可以根据自己的需要选择,
反正按我的经验,对于这种简单应用,任何一种库的查询性能都是足够的。
具体实现的时候,可以将指令查询器抽象出一个接口类,然后具体的查询器继承自接口
类,并包含一个库中的实现类,比如选择微软的 DOM 模型的话:
class CInstructionQueryer : public IInstructionQueryer
{
public:
//此处实现 IInstructionQueryer 定义的查询接口
HRESULT AAAAA();
HRESULT BBBBB();
private:
//IXMLDOMDocument2 是 DOM 模型中具体实现查询操作的类
IXMLDOMDocument2* m_pXMLDoc;
5 编译器执行过程
6 实例解析
按照惯例,最后以一个例子说明用户指令编译为指令码的过程。
6.1 待编译的指令
LD X4
PLSY 10000 D100 Y1
6.2 相关XML文件内容
//LD 指令的定义
<LD>
<ins_type>1</ins_type>
<ins_code>16</ins_code>
<operand_num>1</operand_num>
<operand id="0">
<data_type>1</data_type>
<io_type>0</io_type>
<elem_mask>517632</elem_mask>
</operand>
</LD>
//PLSY 指令的定义
<PLSY>
<ins_type>2</ins_type>
<code>61668</code>
<operand_num>3</operand_num>
<operand id="0">
<data_type>16</data_type>
<io_type>0</io_type>
<elem_mask>3071</elem_mask>
</operand>
<operand id="1">
<data_type>16</data_type>
<io_type>0</io_type>
<elem_mask>3071</elem_mask>
</operand>
<operand id="2">
<data_type>1</data_type>
<io_type>1</io_type>
<elem_mask>16384</elem_mask>
</operand>
</PLSY>
//常数类型的定义
<CONST>
<elem_type>1</elem_type>
<data_type>62</data_type>
<addr_type>1</addr_type>
<addr_subtype>0</addr_subtype>
<elem_rwattr>0</elem_rwattr>
</CONST>
//X 元件的定义
<X>
<elem_type>8192</elem_type>
<data_type>1</data_type>
<addr_type>3</addr_type>
<addr_subtype>1</addr_subtype>
<elem_rwattr>0</elem_rwattr>
</X>
//Y 元件的定义
<Y>
<elem_type>16384</elem_type>
<data_type>1</data_type>
<addr_type>3</addr_type>
<addr_subtype>2</addr_subtype>
<elem_rwattr>2</elem_rwattr>
</Y>
//D 元件的定义
<D>
<elem_type>128</elem_type>
<data_type>62</data_type>
<addr_type>3</addr_type>
<addr_subtype>17</addr_subtype>
<elem_rwattr>2</elem_rwattr>
</D>
6.3 编译后的指令码(16进制,共10步,20个字节)
10 04
F0 E4 00 02 00 00 27 10 11 00 00 64 00 00 02 00 00 01

