基于visual c#.net的虚拟PLC仿真软件开发

多年前勇哥的一篇论文,关于plc仿真器的开发。有需要的朋友可以拿来参考一下。


                中国石油大学(华东)现代远程教育

毕业设计(论文)

 


 

    目:基于visual c#.net的虚拟PLC仿真软件开发

学生姓名:   刘小勇      号: 15316508001


   

 

为了能够不依赖实际的硬件可编程控制器来实现基于可编程控制器的控制,研究和利用C#.net编程语言开发的可编程控制器仿真软件.研究了基于面向对象软件设计方法的梯形图绘制技术,以及基于二叉树数据结构的梯形图向指令表的转换问题.最终的的仿真器软件实现了仿真三菱的FX系列PLC基本指令表,并利用组态组件的概念设计了软件交互功能的UI,可以实时呈现指令运行的效果. 利用该虚拟可编程控制器仿真软件可以用于可编程控制器的实训和实验教学.

 

关键词:可编程控制器,PLC仿真,梯形图,Visual c#

 

   

   


1.    前言……………………………………………………………………………   5

2.    系统的总体框架………………………………………………………………   5

3.    梯形图的编辑…………………………………………………………………   6

4.    梯形图向指令表的转换………………………………………………………   12

5.    组态元件………………………………………………………………………   14

6.    虚拟仿真实验…………………………………………………………………   17

7.    结论……………………………………………………………………………   20

8.    参考文献………………………………………………………………………   21


前   言

 

可编程逻辑控制器(PLC)与传统的继电器控制相比,具有可靠性高、抗干扰能力强、操作简单、扩展功能强等特点。PLC在工业控制领域得到了广泛的应用,是自动技术的3大支柱之一。

可编程控制器应用技术实践性非常强,实践环节至关重要,只有通过实际编程操作,才能使PLC的操作人员真正掌握可编程控制器技术。但由于实践所需要的设备具有价格昂贵、外围元件连接复杂、维护困难等特点,这就为PLC技术的培训带来很大的瓶颈。

本文利用Visual C#开发平台,研究和开发PLC仿真软件,从而在没有任何外部硬件电路的情况下,使用一台运行windows操作系统的PC机,即可实现一台实际硬件的PLC的控制功能。

 

1系统的总体框架

 

一台实际的硬件PLC,具有与计算机类似的硬件,如中央处理器(CPU)、存储器、输入输出部件等。PLC的标准编程语言由IEC61131-3标准提出,共有5种,分别是:梯形图、功能块图、顺序功能图、指令表和结构化文本。其中梯形图直观明了,为大多数工程人员所喜用,是编写PLC程序的首先语言。

本文开发的PLC模拟器支持梯形图和指令表两种方式。实际上,由梯形图编制的PLC程序最终都是先经过编译并转换为指令表程序后,才可以加载到软PLC执行系统上运行。所以如何把梯形图转为指令表是一个关键技术问题。

不同公司的PLC的指令集是不同的,我从流行度方向考虑,选择三菱PLC的指令集做为本文PLC模拟器的指令集,这个指令集只包括一些最常见的基本指令集,差不多可以满足一般PLC实训或者实验。

传统的自动化设备,往往是PLC搭配组态软件与设备,以实现人机交互。如触摸屏幕及其软件就相当于完成了组态软硬件与PLC的交互。本文的PLC模拟器也需要现实类似于组态软件的模块化元件,例如点动按钮、文字输入与文字显示等元件,这些元件可以通过设置参数与PLC的内存单元关联起来。有了这些组态模块,使用可以方便的控制以及观察PLC程序的运行状态及效果。

图1给出了PLC仿真系统的总体框架。

image.png

2 梯形图的编辑

 

    梯形图是一种图形语言,它与传统的继电器电路非常相似,它沿用继电器的触点、线圈,与指令表一道,构成了2种常用的PLC编程表达方式。对于一般的实验来说,所用到的编程元件主要是输入继电器X、输出继电器Y、辅助继电器M、定时器T;对于三菱系统的PLC来说这些元件主要用于基本顺序控制指令、移位指令。

    整个梯形图可以看作主要由常开触点、常闭触点和线圈构成的一个个梯级。因此,在开发过程中,可以抽象为C#的一个类。这个类描述了梯形图元件这个对象以及这个对象应具有的属性。它可以方便的实现各种编程元件的绘制。

 

    梯形图的数据结构如下:

public struct LADDER  //梯形图数据结构

{

        public partTypeEnum ladderType; //梯型图类型

        public string ladderParam;      //指令参数

        public bool isHaveVline;        //有竖线 (所有元件都允许有竖线)

 

        public override bool Equals(object obj)

        {

            if (obj == null) return false;

            if (obj is LADDER)

            {

                var b = (LADDER)obj;

                return this.isHaveVline == b.isHaveVline &&

                    this.ladderParam == b.ladderParam &&

                    this.ladderType == b.ladderType;

            }

            return base.Equals(obj);

        }

};

 

其中,梯形图类型是个枚举变量,包括下面的类型:

public enum partTypeEnum

    {

        //nopPart空元件, lineNumPart行号元件,logicSymbol逻辑符号元件(虚拟不可见,

//由梯型图编译器使用)       

anyPart = 0, nopenPart, closePart, orOpenPart, orClosePart,

        coilPart, applicationPart, pPart, fPart, orpPart,

        orfPart, notPart, hlinePart, vlinePart, nopPart,

        lineNumPart, logicSymbol, leftParenthesis, rightParenthesis, nullSymbol, comma

    }

其中 nopenPat为常开触点,closePart为常闭触点,pPart为上升沿检测触点,fPaft为下降沿检测触点,coilPart为线圈。其它一些类型为编辑器与指令编译器用的特殊符号。

梯形图绘制只需要实例化这些元件,就可以绘制出常开触点、常闭触点、线圈等元件。所有绘制通过函数drawPart()实现,在它的参数中,需要传入绘制元件的X,Y坐标,和元件类型的实例。

 

private void drawPart(Graphics g, int startX, int startY, LADDER ladderNode)

{

            Pen p = new Pen(Color.Black, 1);

            switch (ladderNode.ladderType)

            {

                case partTypeEnum.nopenPart:  //-||-

                    drawNopenPart(g, p, startX, startY, ladderNode);

                    break;

                case partTypeEnum.closePart:  //-|/|-

                    drawClosePart(g, p, startX, startY, ladderNode);

                    break;

                case partTypeEnum.orOpenPart: //+-|   |-+

                    darwOrOpenPart(g, p, startX, startY, ladderNode);

                    break;

                case partTypeEnum.orClosePart:  //+-| / |-+

                    drawOrClosePart(g, p, startX, startY, ladderNode);

                    break;

                case partTypeEnum.coilPart:  //— (   )—

                    drawCoilPart(g, p, startX, startY, ladderNode);

                    break;

                case partTypeEnum.applicationPart:  //-[ ]-

                    drawApplicationPart(g, p, startX, startY, ladderNode);

                    break;

                case partTypeEnum.pPart:  //—| P |—

                    drawP(g, p, startX, startY, ladderNode);

                    break;

                case partTypeEnum.fPart: //—| F |—

                    drawF(g, p, startX, startY, ladderNode);

                    break;

                case partTypeEnum.orpPart: //+-| P |-+

                    drawOrp(g, p, startX, startY, ladderNode);

                    break;

                case partTypeEnum.orfPart:  //+-| F |-+

                    drawOrf(g, p, startX, startY, ladderNode);

                    break;

                case partTypeEnum.notPart:  //——/——

                    g.DrawLine(p, new Point(startX + 26, startY + 12), new Point(startX + 18, startY + 28));

                    break;

                case partTypeEnum.hlinePart:  //————

                    g.DrawLine(p, new Point(startX, startY + 20), new Point(startX + 44, startY + 20));

                    break;

                case partTypeEnum.vlinePart: //  |

                    break;

                case partTypeEnum.nopPart: //空元件

                    break;

                case partTypeEnum.lineNumPart:

                    g.DrawString(ladderNode.ladderParam, new Font("宋体", 10), new SolidBrush(Color.Black), startX + 10, startY + 15);

                    break;

            }

            if (ladderNode.isHaveVline)

                g.DrawLine(p, new Point(startX, startY + 20), new Point(startX, startY + 60));

            vline(g);

}

 

图2是梯形图中的常开触点。

image.png

在C#中绘制这个常开触点是采用画线的方式,下面给出代码,其画线的方式和图2所示意的一致。

private void drawNopenPart(Graphics g, Pen p, int startX, int startY, LADDER ladderNode)

{

    //1,2

g.DrawLine(p, new Point(startX, startY + 20), new Point(startX + 18, startY + 20));

//3,4

g.DrawLine(p, new Point(startX + 18, startY + 12), new Point(startX + 18, startY + 28));

//5,6

g.DrawLine(p, new Point(startX + 26, startY + 12), new Point(startX + 26, startY + 28));

//7,8

    g.DrawLine(p, new Point(startX + 26, startY + 20), new Point(startX + 44, startY + 20));

    g.DrawString(ladderNode.ladderParam.LadderGetParam()[0], new Font("宋体", 10), new SolidBrush(Color.Black), startX - 2, startY - 1);

}

在编辑过程中,整个梯形图被分割为若干个行和列。相当于元件只能绘制在各个方块中。由于梯形图的梯级数、行数、每行元件数、类别都是未知的,整个编辑过程是一个动态存储过程。因此,以动态列表方式来记录梯形图的所有信息。这种数据结构也便于梯形图向指令表转换以及指令的编译运行。

图3是仿真软件的梯形编辑器全貌。

image.png

编辑器左边是常用编程元件和常用功能,包括保存载入梯形图,梯形图转为指令表等;空白区域是编辑区,蓝色框是光标。基本编辑规则与三菱PLC编程软件GX Developer类似。

整个编辑器利用C#的面向对象编程方法进行了封装。编辑器myPanel是一个继承于画板Panel自定义控件;这种方式可以促进组件重用,例如可以把这个编辑器用于其它的软件,只需要做为控件拖入到界面上即可。

image.png

3 梯形图向指令表的转换

 

   梯形图是由很多个梯级构成的。在梯形图的人工转换过程中,遵循着自上而下、从左往右的原则,逐个梯级进行转换。因此,梯形图本质上就是一个有向图。梯形图在绘制过程中,通过梯形图的存储数据结构,表明了各个元件所在的行与列。梯形图的转换过程就是把梯形图先转换为一棵二叉树,然后根据二叉树来识别相应的指令。

一幅梯形图可以看成是触点或者电路块之间的串联与并联。而电路块实质上就是2个或者2个以上的触点构成的。所以本质上琮是触点的串联与并联。串联用“*”表示,并联用“+”表示。如图5所示是一个梯形图。

image.png

按照自上而下、从左往右的原则,该梯形图可转换为图6所示的二叉树。

image.png


二叉树是梯形图向指令表转换的关键,每个梯级均对应一棵二叉树。二叉树清晰地表达了触点之间的关系,通过对二叉树进行遍历,结合对应的触点类型,进行指令表的书写。转换过程如下:

 

Step1:第1个看到的是1个部分,为常开触点X0,故为LD X0;

Step2:第二部分对应的是常开触点X1和常开触点X3构成的电路块。X1为电路块开始的第一个常开触点,故为 LD X1, X3与X1的关系为逻辑“与”,故为 AND X3

    Step3:看到的是“+”号(注意图上+号后面的数字为序号,只是为了方便查看,无其它意义),两者的结果与X0进行“或”,电路块的或为ORB。

    Step4:看到的是第3部分,为常开触点X2,与前者关系为+,故为OR X2。

    Step5:看到的是第4部分,为辅助寄存器M0,与前者的关系为“*”,故为AND M0。

    Step6:最后看到的是第5部分,为线圈Y0,关系为*,故直接输出OUT Y0。

 

    图7给出了梯形图与其转换的代码。

image.png

梯形图向指令表转换完毕,即可获得了1个逻辑表达式。利用该逻辑表达式,实现了指令表的编译。针对图4所示的梯形图及其转换过程,对应的逻辑表达式为:

image.png

4 组态元件

 

组态元件用于构建用户与PLC进行交互的UI界面。常见的有输入元件,例如按钮或者输入框,还有显示元件,用于显示PLC某些编程元件的值。

图7是仿真软件的组态输入与输出元件,其中“外接触点开关”、“外接自锁开关”,“输出点”是输入元件,用于显示PLC的输入继电器的状态和输出继电器的状态。

最下面是两个连接到输入继电器X5,X6的自锁按钮, 和连接到输入继电器X0,X1的点动按钮。

图中还可以看到,自锁开关按下去后,输出继电器X5保持导通;而点动开关X1按下去后X1导通,但松手后X1不导通。

在C#中,组态元件被定义成一组自定义控件,它编制完成后做为一组控件可以被放置于Form窗体中,并且设置其特定的属性。例如上面的按钮组态元件,可以设定关联到那个输入继电器。

 

所有的组态组件拥有共同的基类ScadaClass, 它们有公用属性:图片地址,组件名称,通讯用的命名管道。

    class ScadaClass : Panel

    {

        public readonly string partImagePath = "c:\\partImg";

        public string partName { get; set; }

        public static NamedPipeClientStream pipeClient =

         new NamedPipeClientStream("127.0.0.1", "scadaPipe",

             PipeDirection.InOut, PipeOptions.Asynchronous,

             TokenImpersonationLevel.None);

 

    }

 

下面是定义输入组件的基类,所有的具体类型的按钮都以此为基类。

 class ScadaButton : ScadaClass

    {

        private string eleName;

        private ushort eleNo;

        private bool onOff;

        private bool isSelfLock;

        private string imageOnFilePath;

        private string imageOffFilePath;

        public TextBox tbMsg = new TextBox();

 

        public ScadaButton()

        {

            this.Controls.Add(tbMsg);

            tbMsg.TextChanged += new EventHandler(tbMsg_TextChanged);

            EleName = "X";

            OnOff = false;

        }

 

        void tbMsg_TextChanged(object sender, EventArgs e)

        {

            var txt = ((TextBox)sender).Text;

            if (txt != null && txt.Length > 1)

            {

                var res = txt.splitNameAndValue();

                EleName = res.Item1;

                EleNo = (ushort)res.Item2;

            }

        }

 

        protected override void OnPaint(PaintEventArgs e)

        {

            tbMsg.Width = 40;

            tbMsg.Text = EleName + EleNo;

            base.OnPaint(e);

        }

 

        public ushort EleNo

        {

            get { return eleNo; }

            set { eleNo = value; }

        }

 

        public string EleName

        {

            get { return eleName; }

            set { eleName = value; }

        }

 

        public bool OnOff

        {

            get { return onOff; }

            set { onOff = value; }

        }

       

        public bool IsSelfLock

        {

            get { return isSelfLock; }

            set { isSelfLock = value; }

        }

      

        public string ImageOnFilePath

        {

            get { return imageOnFilePath; }

            set { imageOnFilePath = value; }

        }

      

        public string ImageOffFilePath

        {

            get { return imageOffFilePath; }

            set { imageOffFilePath = value; }

        }

 

    }

 

下图展示了组态组件中一个触点开关与一个自锁开关按下时的效果。

image.png

5 虚拟仿真实验

 

    本系统利用上述各模块的功能,为PLC的实验教学提供了一个直观、形象的虚拟环境。使用该系统进行实验的步骤如下:

1)  根据给定的控制要求,在该仿真软件平台上绘制梯形图;

2)  生成对就原指令表,然后点击“开始仿真”。

3)  根据屏幕显示的结果进行调试和修改PLC程序,返回1。

 

这里选用交通灯控制实例来进行仿真实验,用于检验虚拟PLC仿真软件的运行效果。

控制要求为:

(1)    按输入信号开始执行程序。

(2)    按每十秒钟切换一组信号灯显示,红(y0),绿(y1),蓝(y2),循环执行。

按照上述要求,编制的梯形图与其生成的指令表以及运行结果如图9所示。

image.png

下图是梯形图生成的指令表。

image.png

图10  指令表

 

下图是本实验的梯形图代码。

image.png

下图是PLC的资源监视功能,从中我们可以看到订时器T0正在计时。

image.png


图12  程序用到资源的实时监控

 

下图是组态元件的显示结果,从中我们看到红灯(Y0)正在被点亮;十秒钟后绿灯(Y1)将被点亮,接下来是蓝灯(Y2)将被点亮;程序会不断循环这个过程。


image.png


6 结论

 

    本文利用C#开发了一个仿三菱系列PLC的虚拟仿真软件,并构建了虚拟实验环境,主要讨论了梯形图绘制和梯形图转换为指令表2个关键的技术问题。针对交通灯控制的仿真实验表明,该仿真软件可以直观的用于PLC的实验与教学。本软件对于想入门PLC的初学者是非常适用的。

    进一步,可以在该平台的基础上,增加3D虚拟实验环境;或者是增加对应的硬件通讯与IO接口,用于控制实际的实验对象。


参考文献

[1]袁云龙,基于组态软件的PLC控制系统仿真实验[J] 自动化仪表,2006,27(5):57-58,61.

[2] 李杰臣,刘琼.PLC软件仿真技术在教学中的应用[J].成都航空职业技术学院学报,2006,22(1);25-27.


--------------------- 

作者:hackpig
来源:
www.skcircle.com
版权声明:本文为博主原创文章,转载请附上博文链接!


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:
本帖最后由 勇哥,很想停止 于 2019-03-05 20:19:18 编辑
  • 评论列表:
  •  访客
     发布于 2020-07-18 19:33:27  回复该评论
  • 能否分享一下源码,不胜感激915685943@qq.com
  •  zxc
     发布于 2021-12-25 17:03:30  回复该评论
  • 您好,我想问一下您能提供一下您编写的这个编程软件的源码吗

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年4月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864