PLC(ProgrammableLogicController可编程逻辑控制器)是在工业环境下使用的数字逻辑操作系统。其编程语言有我们最为熟悉的梯形图,本篇文章将从梯形图的原理、编译、运行,实现一个软PLC。
一、基本原理梯形图可以理解为一个电路图,通过梯形图上的元件的通断来控制整个程序逻辑。元件的状态只有“通”和“断”;元件的关系可以分为“与”、“或”、“非”。是不是似曾相识?这里我们把含有逻辑关系的元件当做一个逻辑块;通过这些逻辑块最终输出到元件——输出块。
如下图:

图1简单梯形图
从母线出发,连接逻辑块到输出块的通路称为输出图。如上图为一个输出图。
只有一个逻辑块和输出块的图称为最简图,如下图所示:

图2最简图
有了上面的基本原理,接下来就可以抽象梯形图,从而实现梯形图的显示和编译。
二、算法原理2.1算法思路通过自上而下,从左到右遍历梯形图,找到每一个块,根据块与块间的逻辑关系——与或非,只要确定了两个块的关系,就可以将这两个块合并变成一个块,以此的处理方式一直重复,直至剩下一个逻辑块和一个输出块,那么算法就结束了。
当然还需要异常处理,这个不在这里讨论。
2.2算法的伪代码初始状态将每个元件当做一个逻辑块或者输出块。
取出一个输出图。
合并相邻输出块、输入块
如果合并后为最简图则返回第2步取下一个输出图,不是则继续3步
伪代码如下:
initGraph();//步骤1while(getNextOutputGraph())//步骤2{while(!isTheSimpleGraph())//步骤4{shiftGraph();//步骤3}outputPLCInstructions();//输出转换的指令}2.3算法示意首先取出一个输出图,刚好图一就是一个完整的输出图,标记X000、M001、M000为3个逻辑块,Y001、Y002为2个输出块
判断是否为最简图,有5个块,所以不是最简图
合并X000、M000为一个逻辑块,标记为Block0,同时记录OR关系
判断是否为最简图,有4个块,所以不是最简图
合并Block0、M001为一个逻辑块,标记为Block1,同时记录ANDP关系
判断是否为最简图,有3个块,所以不是最简图
合并Y001、Y002为一个输出块,标记为Block2,同时记录输出关系(合并只能合并同种属性的块,所以Block1块没有逻辑块可以合并了)
判断是否为最简图,一个逻辑看,一个输出块,所以化简结束
输出每个块:
图1的梯形图处理结果如下,输出PLC指令:

PLC指令
2.4扩展思考该算法的思路只针对简单的情况,实际还有嵌套的情况,比如在输出块里面又有一个逻辑块连接输出块。算法需要做一下处理,按照上面的算法先处理里面的逻辑块连接输出块即可。
三、运行将上面编译出来的PLC指令,输入到PLCCore,使用解析运行的方式来模拟PLC运行。图中灰色表示元件导通,双击元件可以改变元件状态,如果双击M001导通,则Y001和Y002导通一次。

仿真运行截图
下面是已经支持的指令表,PLCCore是在三菱PLC下参考制作,可以直接模拟三菱PLC的程序
staticInsItemg_insItems[]={{{dinsDEBUG},sinsNOP-1,"DEBUG",1,0,ARG_COIL_G},{{dinsLD},sinsLD,"LD",1,0,ARG_COIL_BIT},{{dinsLDI},sinsLDI,"LDI",1,0,ARG_COIL_BIT},{{dinsLDP},sinsLDP,"LDP",1,0,ARG_COIL_BIT},{{dinsLDF},sinsLDF,"LDF",1,0,ARG_COIL_BIT},{{dinsAND},sinsAND,"AND",1,0,ARG_COIL_BIT},{{dinsANI},sinsANI,"ANI",1,0,ARG_COIL_BIT},{{dinsANDP},sinsANDP,"ANDP",1,0,ARG_COIL_BIT},{{dinsANDF},sinsANDF,"ANDF",1,0,ARG_COIL_BIT},{{dinsOR},sinsOR,"OR",1,0,ARG_COIL_BIT},{{dinsORI},sinsORI,"ORI",1,0,ARG_COIL_BIT},{{dinsORP},sinsORP,"ORP",1,0,ARG_COIL_BIT},{{dinsORF},sinsORF,"ORF",1,0,ARG_COIL_BIT},{{dinsANB},sinsANB,"ANB",0,0,0},{{dinsORB},sinsORB,"ORB",0,0,0},{{dinsMPS},sinsMPS,"MPS",0,0,0},{{dinsMRD},sinsMRD,"MRD",0,0,0},{{dinsMPP},sinsMPP,"MPP",0,0,0},{{dinsINV},sinsINV,"INV",0,0,0},{{dinsMEP},sinsMEP,"MEP",0,0,0},{{dinsMEF},sinsMEF,"MEF",0,0,0},{{dinsOUT},sinsOUT,"OUT",1,0,ARG_COIL_X|ARG_COIL_Y|ARG_COIL_M},{{dinsOUTT},sinsOUTT,"OUT",2,0,ARG_COIL_T,ARG_COIL_DATA},{{dinsOUTC},sinsOUTC,"OUT",2,0,ARG_COIL_C,ARG_COIL_DATA},{{dinsOUT},sinsOUTMS,"OUT",1,0,ARG_COIL_M},{{dinsOUTS},sinsOUTMS,"OUT",1,0,ARG_COIL_S},{{dinsSET},sinsSET,"SET",1,0,ARG_COIL_X|ARG_COIL_Y|ARG_COIL_M},{{dinsSETS},sinsSETMS,"SET",1,0,ARG_COIL_S},{{dinsSET},sinsSETMS,"SET",1,0,ARG_COIL_M},{{dinsRST},sinsRST,"RST",1,0,ARG_COIL_X|ARG_COIL_Y|ARG_COIL_M},{{dinsRSTTC},sinsRSTMSTC,"RST",1,0,ARG_COIL_T|ARG_COIL_C},{{dinsRSTS},sinsRSTMSTC,"RST",1,0,ARG_COIL_S},{{dinsRST},sinsRSTMSTC,"RST",1,0,ARG_COIL_M},{{dinsPLS},sinsPLS,"PLS",1,0,ARG_COIL_X|ARG_COIL_Y|ARG_COIL_M|ARG_COIL_S},{{dinsPLF},sinsPLF,"PLF",1,0,ARG_COIL_X|ARG_COIL_Y|ARG_COIL_M|ARG_COIL_S},{{dinsSTL},sinsSTL,"STL",1,0,ARG_COIL_S},{{dinsRET},sinsRET,"RET",0,0,0},{{dinsMC},sinsMC,"MC",1,0,ARG_COIL_N},{{dinsMCR},sinsMCR,"MCR",1,0,ARG_COIL_N},{{dinsNOP},sinsNOP,"NOP",0,0,0},{{dinsEND},sinsEND,"END",0,0,0},{{dinsADD},sinsADD,"ADD",3,0,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_D|ARG_COIL_V},{{dinsSUB},sinsSUB,"SUB",3,0,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_D|ARG_COIL_V},{{dinsMUL},sinsMUL,"MUL",3,0,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_D|ARG_COIL_V},{{dinsDIV},sinsDIV,"DIV",3,0,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_D|ARG_COIL_V},{{dinsINC},sinsINC,"INC",1,0,ARG_COIL_D|ARG_COIL_V},{{dinsDEC},sinsDEC,"DEC",1,0,ARG_COIL_D|ARG_COIL_V},{{dinsMOV},sinsMOV,"MOV",2,0,ARG_COIL_DATA,ARG_COIL_D|ARG_COIL_V},{{dinsCMP},sinsCMP,"CMP",3,0,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_M},{{dinsCJ},sinsCJ,"CJ",1,0,ARG_COIL_P},{{dinsCJP},sinsCJP,"CJP",1,0,ARG_COIL_P},{{dinsSRET},sinsSRET,"SRET",0,0,0},{{dinsFEND},sinsFEND,"FEND",0,0,0},{{dinsCALL},sinsCALL,"CALL",1,0,ARG_COIL_P},{{dinsP},sinsP,"P",1,0,ARG_COIL_P},{{dinsDADD},sinsDADD,"DADD",3,1,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_D|ARG_COIL_V},{{dinsDSUB},sinsDSUB,"DSUB",3,1,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_D|ARG_COIL_V},{{dinsDMUL},sinsDMUL,"DMUL",3,1,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_D|ARG_COIL_V},{{dinsDDIV},sinsDDIV,"DDIV",3,1,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_D|ARG_COIL_V},{{dinsDINC},sinsDINC,"DINC",1,1,ARG_COIL_D|ARG_COIL_V},{{dinsDDEC},sinsDDEC,"DDEC",1,1,ARG_COIL_D|ARG_COIL_V},{{dinsDMOV},sinsDMOV,"DMOV",2,1,ARG_COIL_DATA,ARG_COIL_D|ARG_COIL_V},{{dinsDCMP},sinsDCMP,"DCMP",3,1,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_M},{{dinsLD_E},sinsLD_E,"LD=",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsAND_E},sinsAND_E,"AND=",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsOR_E},sinsOR_E,"OR=",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsLD_NE},sinsLD_NE,"LD",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsAND_NE},sinsAND_NE,"AND",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsOR_NE},sinsOR_NE,"OR",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsLD_B},sinsLD_B,"LD",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsAND_B},sinsAND_B,"AND",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsOR_B},sinsOR_B,"OR",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsLD_EB},sinsLD_EB,"LD=",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsAND_EB},sinsAND_EB,"AND=",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsOR_EB},sinsOR_EB,"OR=",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsLD_S},sinsLD_S,"LD",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsAND_S},sinsAND_S,"AND",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsOR_S},sinsOR_S,"OR",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsLD_ES},sinsLD_ES,"LD=",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsAND_ES},sinsAND_ES,"AND=",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsOR_ES},sinsOR_ES,"OR=",2,0,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsLDD_E},sinsLDD_E,"LDD=",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsANDD_E},sinsANDD_E,"ANDD=",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsORD_E},sinsORD_E,"ORD=",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsLDD_NE},sinsLDD_NE,"LDD",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsANDD_NE},sinsANDD_NE,"ANDD",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsORD_NE},sinsORD_NE,"ORD",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsLDD_B},sinsLDD_B,"LDD",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsANDD_B},sinsANDD_B,"ANDD",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsORD_B},sinsORD_B,"ORD",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsLDD_EB},sinsLDD_EB,"LDD=",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsANDD_EB},sinsANDD_EB,"ANDD=",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsORD_EB},sinsORD_EB,"ORD=",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsLDD_S},sinsLDD_S,"LDD",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsANDD_S},sinsANDD_S,"ANDD",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsORD_S},sinsORD_S,"ORD",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsLDD_ES},sinsLDD_ES,"LDD=",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsANDD_ES},sinsANDD_ES,"ANDD=",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsORD_ES},sinsORD_ES,"ORD=",2,1,ARG_COIL_DATA,ARG_COIL_DATA},{{dinsDDRVA},sinsDDRVA,"DDRVA",4,1,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_Y,ARG_COIL_Y},{{dinsDDRVI},sinsDDRVI,"DDRVI",4,1,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_Y,ARG_COIL_Y},{{dinsDRVA},sinsDRVA,"DRVA",4,0,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_Y,ARG_COIL_Y},{{dinsDRVI},sinsDRVI,"DRVI",4,0,ARG_COIL_DATA,ARG_COIL_DATA,ARG_COIL_Y,ARG_COIL_Y},};PLCCore使用c语言编写,已经成功跑在Stm32上,windows模拟器也是同个PLCCore。但界面使用MFC,后面整理完会将项目开源。敬请期待。
四、写在最后由于这个是半成品,也没有空维护,目前已经带领团队完成了IEC的编译型的PLC编程软件和runtime,引入了语法树和梯形图的逻辑树来翻译梯形图。本文写到的算法也算是入门,让更多感兴趣的朋友来了解PLC梯形图的翻译方法,还有感兴趣的可以留言或者私信。





