梯形图显示到编译--软PLC的实现
PLC(Programmable Logic Controller 可编程逻辑控制器)是在工业环境下使用的数字逻辑操作系统。其编程语言有我们最为熟悉的梯形图,本篇文章将从梯形图的原理、编译、运行,实现一个软PLC。
一、基本原理
梯形图可以理解为一个电路图,通过梯形图上的元件的通断来控制整个程序逻辑。元件的状态只有“通”和“断”;元件的关系可以分为“与”、“或”、“非”。是不是似曾相识?这里我们把含有逻辑关系的元件当做一个逻辑块;通过这些逻辑块最终输出到元件——输出块。
如下图:
图1 简单梯形图
从母线出发,连接逻辑块到输出块的通路称为输出图。如上图为一个输出图。
只有一个逻辑块和输出块的图称为最简图,如下图所示:
图2 最简图
有了上面的基本原理,接下来就可以抽象梯形图,从而实现梯形图的显示和编译。
二、算法原理
2.1 算法思路
通过自上而下,从左到右遍历梯形图,找到每一个块,根据块与块间的逻辑关系——与或非,只要确定了两个块的关系,就可以将这两个块合并变成一个块,以此的处理方式一直重复,直至剩下一个逻辑块和一个输出块,那么算法就结束了。
当然还需要异常处理,这个不在这里讨论。
2.2 算法的伪代码
- 初始状态将每个元件当做一个逻辑块或者输出块。
- 取出一个输出图。
- 合并相邻输出块、输入块
- 如果合并后为最简图则返回第2步取下一个输出图,不是则继续3步
伪代码如下:
initGraph(); //步骤1
while( 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块没有逻辑块可以合并了)
- 判断是否为最简图,一个逻辑看,一个输出块,所以化简结束
- 输出每个块:
Block0 = LD X000; OR M000
Block1 = Block0 ANDP M001
= LD X000; OR M000; ANDP M001
Block2 = OUT Y001; OUT Y002
Block1 => Block2 = LD X000; OR M000; ANDP M001; OUT Y001; OUT Y002
注: =>表示输出到的意思,处理只要把左右两边的文本连接就可以
图1的梯形图处理结果如下,输出PLC指令:
2.4 扩展思考
该算法的思路只针对简单的情况,实际还有嵌套的情况,比如在输出块里面又有一个逻辑块连接输出块。算法需要做一下处理,按照上面的算法先处理里面的逻辑块连接输出块即可。
三、运行
将上面编译出来的PLC指令,输入到PLCCore,使用解析运行将模拟PLC运行。图中灰色表示元件导通,双击元件可以改变元件状态,如果双击M001导通,则Y001和Y002导通一次。
下面是已经支持的指令表,PLCCore是在三菱PLC下参考制作,可以直接模拟三菱PLC的程序
static InsItem g_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梯形图的翻译方法,还有感兴趣的可以留言或者私信