未庆超 蔡启仲 李克俭 谢从涩
(广西工学院电气与信息工程学院)
近几年,随着计算机、网络、微处理器等技术的迅速发展,PLC编程器所用硬件平台的核心由8位微处理器转变为32位微处理器,软件平台所用操作系统由DOS、Windows系统转变为Linux嵌入式系统或者uC/OS-II实时操作系统。目前编程器的编译系统主要有翻译性和解释性两种。其中翻译型的编译过程是将源代码程序翻译为目标机器可识别的语言(通常为二进制机器码),并由硬件执行的过程[1]。其优点是生成目标代码后,目标机器执行效率高,占用资源小。解释型的编译过程是将源代码程序在目标机器上直接逐条解释执行,无需先将其翻译为目标机器代码。该型编译需要将编译器直接植入硬件,其缺点是源代码程序运行的每一步都要对其进行解释,使得程序运行效率相对低下,占用资源高,无法满足工业控制过程的高效性、高实时性要求。
针对自主研制的基于ARM+FPGA组成的小型可编程控制器,设计一种PLC手持编程器的翻译型编译系统,选用32位ARM微控制器LPC2478和uC/OS-II实时操作系统作为设计平台的核心。
图1 手持编程器总体框架
手持编程器由硬件和软件两部分组成。硬件设计以32位微控制器LPC2478为核心。LPC2478微控制器内部集成多种资源,包括 CAN控制器、SDRAM控制器、液晶显示屏控制器、UART控制器、外部存储控制器等,减少了系统外围元件数量,使系统的硬件最小化。软件设计选择uC/OS-II作为系统的核心。uC/OS-II实时操作系统是一个完整、可移植、可固化、可裁剪、可剥夺、抢占式实时多任务内核的嵌入式操作系统,可以管理64个任务,具有信号量、事件标志组、消息邮箱、任务管理和内存块管理等系统功能。手持编程器利用CAN总线与PLC主机通信;用LCD屏显示编译系统的相关信息;用存储器存储 PLC程序及相关数据。手持编程器的总体框架如图1所示。
PLC手持编程器的编译系统由源代码编制、编译、存储、显示、通讯五大模块组成,总框架如图2所示,执行流程如图3所示。
图2 编译系统的总框架图
图3 编译系统的执行流程图
IEC61131-3是PLC编程语言的国际标准[2],它定义了梯形图、顺序功能图、功能模块、结构化文本和指令表5种PLC编程语言规范。其中,指令表是一种较“低级”的编程语言,它类似于计算机的汇编语言,其代码由一系列的指令行组成,常作为其它文本化语言和图形语言转译过程的中间语言。因此,选用指令表作为程序的编程语言,比较直接,不用其它语言转换。
PLC源代码编制模块用来完成对PLC源代码指令表程序的新建、插入、修改、添加、删除、查找、替换、复制、粘贴、保存等功能。
存储模块主要将编译生成的PLC主机能识别的二进制目标代码及 PLC的源代码指令表等数据存储起来,以便查看使用。设计中采用数组的形式存放编译生成的二进制目标代码,采用链表的形式存放PLC源代码指令表程序。在链表处理过程中每条指令用一个结点的数据结构来表示,该结点的结构带有两个指针,分别指向左右两个有相邻逻辑关系的其它结点,其结构如图4所示。
图4 指令结点存储结构
程序设计中采用结构体来表示结点数据结构,其定义如下:
struct node{ char data;
struct node *left;
struct node *right;
int flag;
};
其中,data存放该结点的关键值,可以是操作数的编号或操作码的代码号;flag存放结点数据结构的标志位,表示该结点存放的是操作数或操作码;结点指针分别指向该结点的上层和下层结点,这样结点之间可通过指针连接,进行扫描和结点元素的插入、删除、修改等操作。在扫描PLC源程序指令表过程中,以空格作为分隔界限,将各独立字符取出,再将该独立字符与系统关键字对照来识别该字符。对已识别的关键字字符,系统申请结点struct node数据结构空间,将该关键字存放入data。根据关键字为指令操作符或者操作软元件,分别将flag标志位置0或1;同时设置结点的指针指向。
在 PLC指令表程序中用二叉树的结构[3]表示指令的逻辑关系,其结构如图5所示。每个二叉树的根结点为系统的指令操作符,叶子结点表示系统的操作软元件。对该二叉树的逻辑结构进行后序遍历后可以得到对应的指令表程序。
图5 结点相互连接方式
PLC指令主要分为基本指令和应用指令。基本指令是使用较多的指令,完成位逻辑运算功能;而应用指令完成特定运算的功能。以字为单位对 PLC指令进行编码,每条 PLC指令包含操作码和操作数两部分,其中操作码有1个,操作数有1个、2个、3个或者更多,例如应用指令的源操作数和目的操作数。所以,根据操作数的个数不同,将 PLC指令分为三类:第一类是LD、LDR、OR、AND;第二类是基本指令中无操作数指令、单操作数指令和步进指令;第三类是应用指令。用 32位中的最高四位 D31~D28区别这三类指令。其中,第二类指令对应的是0110;第三类指令对应的是 0111;第一类指令对应的是 4位二进制数的其它组合。
位单元软元件PLC指令共有8种,分为X、Y、T、C和M1、M2、M3、S两类。对每种软元件进行编码,从0开始编号。M是各类软元件中编号最多的软元件,故将 M 以 1024个软元件为一个单位分为M1、M2和M3三种类型。其中,X、Y、T、C用2位二进制数编码(称为软元件的基地址)区分,4种软元件各自最多有256个,所以用8位二进制数(称为软元件的位地址)进行编码,共用10位二进制数判断4种软元件及其编号;对M1、M2、M3、S用2位二进制数区分,分别用10位二进制数进行编号,即共用12位二进制数判断4种软元件及其个数;系统中有常开、常闭、上升沿微分、下降沿微分四种接点类型,用2位二进制数进行编码区分。
对第一类指令进行编码时又将LD、LDR、OR、AND分为LD、LDR和OR、AND两类。D31~D28从0000~0101表示LD、LDR,若D27是0,则表示LD;若D27是1,则表示LDR。D31~D28从1000~1101表示AND、OR,若D27是0,则表示AND;若D27是1,则表示OR。若它们只有1个操作数,则 D26~D17依次是软元件的基地址和位地址,D16~D15表示接点类型,D14~D13表示指令结束标志,其它为无关项置1;若它们有2个操作数,则第 1个操作数和第 2个操作数分别占 D26~D15、D14~D1,最后1位表示指令结束。标志位,0表示未结束,1表示结束。
对第二类指令进行编码时比较简单,8种软元件编码,详见表1、表2。
对第三类指令进行编码时仅涉及 48条应用指令,分别给其编号,编号的范围是0~47,采用7位二进制代码对D27~D21编码,编码值等于各自的编号。用D20区分是否为脉冲执行方式,若是,则D20为1;否则,D20为0。而其它应用指令的操作数有源操作数和目的操作数,有些应用指令没有操作数。对有操作数的应用指令,用2个或3个32位二进制数表示,将应用指令的编号、脉冲执行方式位和源操作数用一个32位编码,目的操作数用另外一个32位编码。将操作数分为软元件的编号和转移地址,转移地址就是软元件的位地址,源操作数占20位,目的操作数占32位,无关项都置为1。源操作数有T、C、D、K(十进制整数)、H(十六进制整数)、V、Z、KnX、KnY、KnS、KnM1、KnM2、KnM3共 13种软元件,用4位二进制数进行编码区分,从0000到1100;目的操作数有T、C、D、V、Z、KnY、KnS、KnM1、KnM2、KnM3共10种软元件,用4位二进制数进行编码区分,从0000到1001。对无操作数的应用指令(如CJ、CALL、SRET等)用一个32位二进制数表示,D27~D21用来指令编码,从0000000开始,需要脉冲执行方式位的指令仍用D20位表示,需要转移地址的指令用D19至后面的位表示,无关项都置1。
表1 第二类指令编码表
表2 第二类指令编码表
编译模块的功能是对 PLC源代码指令表进行词法分析、语法分析、语义分析、代码优化[4],若出现错误则进行错误处理;反之,则生成 PLC主机能识别的二进制目标代码。词法分析和语法分析的实质是检查源程序的整个输入是否构成一个完整的 PLC指令程序。
词法分析采用有限自动机的原理来实现扫描功能,从 PLC源文件中逐个读取字符,再对各个字符进行识别,分离出关键字、变量、数字、常量等。将软元件(如X、Y、S)和指令符(如基本指令)设置为关键字。同时检查源程序指令中操作符和操作数的关系是否正确。例如,对系统的位单元元件S不能使用ORB、ANB等块操作符,对数据寄存器软元件D、V、Z使用SET等位操作指令等类似的错误。当对应的操作符与操作软元件不相符时系统提示错误,停止相应程序的处理。
语法分析则是针对词法分析阶段中产生的单词序列进行检查,判断是否符合指令表语言的语法规则,确定整个输入字符串是否构成一个在语法上正确的程序等等。将词法分析阶段识别的关键字分类为表达式、语句等语法单元。再根据关键字的意义将该源程序的二叉树的逻辑结构转换为布尔表达式。根据语法规则的描述,确定该布尔表达式的逻辑结构是否与PLC程序的语法规则相符。
语义分析则分析整个句子是否符合 PLC指令编程规则、数据的类型是否匹配、程序在逻辑上是否有错误等。
代码优化就是在 PLC编程规则下将多余的空格符、制表符、注释等字符过滤掉,减少代码的存储空间。错误处理则是在编译过程中自动加入冗余容错逻辑,提高容错能力,增强系统的自适应能力。
经过以上阶段,可生成结构和逻辑上正确的PLC源指令代码。对 PLC源指令代码进行遍历,根据树状结构对其存储。对PLC源指令代码通过后序遍历,从后序遍历根结点的各个子树,然后访问根结点。将操作码的编译代码和操作软元件的编译代码移位相加就构成了最终的二进制目标代码。
编译一段 PLC源程序指令,进行测试,以验证编译系统编译PLC指令的正确性。
LD X001
OR X003
OUT Y002
LDI X001
OUT Y003
LDI X001
ORI X003
OUT Y004
END
输入PLC指令代码时规定:指令符和软元件之间只有一个空格符,若多个或者没有空格符,则会出现输入代码错误;各类软元件与其编号之间不能有任何符号,否则提示错误。
将这段 PLC代码通过外围按键输入,通过代码扫描函数 Code_Scan(),返回该段代码的总行数。由编译函数Compile Code()编译该段PLC源代码,将编译结果显示在液晶显示屏上,如表3所示。第1列是PLC源程序指令,第2列是以十六进制形式表示的目标代码。
表3 PLC源代码及编译结果显示
编译每行代码时,分辨出指令符和软元件之间的空格符;根据空格符的位置找出软元件编号的起始位置;利用截取子字符串函数substr()分别读取指令符、软元件和软元件的编号,利用字符串处理函数strcmp()判断指令符与可编程控制器的指令符是否相同。若相同,则判断软元件具体类别。结合软元件编号,整行PLC指令代码被编译成十六进制代码,再通过十六进制数转变为二进制数函数 Sieteen To Bin(),生成相应的二进制代码;若没有一个相同,则提示错误。
读取指令符和软元件之间空格符的位置函数如下:
int Read_Space (char *tempstr,char c)
{
int i=0;
for(i=0;i { if (tempstr[i]==c) return i; } } 截取子字符串函数substr()如下: char * substr(char *s,int p,int size) { int i; char *temp=(char *)malloc(size+1); for(i=0;i temp[i]=s[p-1+i]; temp[i]=' '; return temp; } 以LDI为例,编译PLC源代码函数Compile Code()如下: char* Compile Code(char *str) { int pos=0,i=0,j,k, Bit_A=0; char C_C,Inf Str[60]=" ",code[9]=""; char *C_Head,*temp; pos = Read_Space(str,' '); if (pos!=-1) { C_Head = substr(str,1,pos); C_C = str[pos+1]; temp = substr(str,pos+3,5); Bit_A = atoi(temp); } else C_Head=str; if (strcmp(C_Head,"LDI")==0) { pos = 0+( Bit_A -0)*4 ; sprintf(Inf Str,"%x", pos); switch(C_C) { case 'X': code[0]='4';code[1]='4';break; case 'Y': code[0]='4';code[1]='C';break; case 'T': code[0]='5';code[1]='4';break; case 'C': code[0]='5';code[1]='C';break; } j=strlen(Inf Str); switch(j) { case 1: code[2]='0';code[3]=Inf Str[0];break; case 2: code[2]=Inf Str[0]; code[3]=Inf Str[1]; break; } for(j=4;j<8;j++) code[j]='F'; code[8]=' '; } …… } 本文介绍了一种 PLC手持编程器的编译系统。该编译系统在ADS1.2开发软件上实现,在人机界面输入 PLC指令,经编译后生成以十六进制形式显示的目标代码。通过实例测试,证明该系统能正确完成将PLC源代码指令表编译成PLC主机能识别的二进制机器代码。 [1] Andrew W Appel. 现代编译原理C语言描述[M].北京:人民邮电出版社,2006. [2] 高金刚,陈建春,刘雄伟.数控系统的软PLC系统开发[J].计算机测量与控制,2010,12(3):254-256. [3] 姚远,丑武胜,陈友东,等.软PLC编程开发系统的设计和实现[J].组合机床与自动化加工技术,2006(6):14-18. [4] 陆林,白瑞林.一类PLC的编译器的设计与实现[J].微计算机信息,2008,24(12):17-19. [5] 方承远,张振国.工厂电气控制技术[M].北京:机械工业出版社,2006,7.4 结束语