• 基于西门子PLC和触摸屏以及C#开发的通用IO监控系统


    前言: 在项目开发的过程之中,经常需要做IO监控画面.当IO监控点多的时候,往往需要做很多的画面,并且浪费了HMI的很多IO点.做起画面来也很麻烦和繁琐.从而,让我思考着如何做一个工具.可以在一个画面上监控所有的变量.

    要实现这个,其实也不难.

    1,需要将IO变量泛型的通过指针的方式,反馈到一个变量上,并且使用这个变量的位来指示每个IO的状态.

    2,需要将IO的文本解释生成一系列的IO图片,然后在HMI上建立IO列表.关联起来.

    一,使用C#开发图片生成工具:

    image

    在左侧输入,I的文本解析,在右侧输入O的文本解析.在IO地址处放入起始地址.然后点击生成图片,就会生成一系列的图片了.

    image

    300表示从I300.0和Q300.0开始的IO变量.点击生成图片,开始生成:

    image

    打开一张图片,看下效果:名字叫 IO_0.PNG

    image

    最后一张 名字叫 IO_15.PNG

    image

    建立完之后,把所有图片拷贝项目文件夹里面,并且建立图形列表,名字叫IOtable

    image

    再建立一个IO点灭和亮通过不同形式表示的图形列表,IO显示(反正大家可以自己去设)

    image

    做完之后,再新建一个画面

    image

    这是仿真的,实际画面里面是 一个图形IO,绑定,刚才建立的IOTable图形列表.以及32个图形IO,绑定刚才建立的IO显示图形列表

    外加两个翻页按钮,当按左边时候,循环向下翻页,按右边时循环向上翻页(翻到底时就重复到起始地址).

    这是翻到第一页.可以看到,第一个I300.0没有被点亮,而第二页,则被点亮了.

    image

    实际上是连接了一个变量:

    image

    二,我们在PLC里面来实现这个功能:新建一个FB块,名字叫IO_Monitor

    image

    输入接口说明:

    •                Random:如果为true,则表示是无序IO,需要人工设定IOTable表,以绑定每个Index对应的IO地址.

                                如果为False,则自动建立IO地址和Index之间的关系.默认是自动建立:

    IF NOT #Random AND "FirstScan" THEN
        FOR #i := 0 TO #LastIndex DO
            #IOTable[#i] := #StartAddr + #i * 2;//因为地址是字节为单位,Index是以字为单位.IOTable[i],表示当前索引对应的IO地址.
        END_FOR; //StartAddr ---还记得吗?就是上面的开始地址.也就是支持偏置.
    END_IF;
    •              LastIndex:表明最终索引号,本列是15.表示有0-15,16个图片,共16*16,200多I点,和200多O点
    •              StartAddr:已经说明.根据实际IO地址填入.
    •              IOInterface.Left_Sw: 左翻页,实际就是把Index-1
    •              IOInterface.Right_Sw:右翻页,实际就是把Index+1
    •             为防止Index<0或>LastIndex,我做了个处理,当其为0-1时则其=最大值,反之LastIndex+1时,为最小值.    
    •             IOStatus,为当前索引的IO状态.
    •             IOIndex为当前索引.

    实现:


    image

    通过 除以2 来找到在Word数组中的位置. 通过Move_BLK_VAriant函数,来将本来不序列化的Any类型转为了一个WOrd数组.

    也就是 P#I0.0 Word 1000,实际上变成了一个  数组 tmp[0..999] of Word -----其中,tmp[0]=IW0,tmp[1]=IW2,tmp[999]=Iw1998.---------从而,也理解了刚才除以2的意义.因为,当我寻址I300时候,实际上在数组中,是tmp[150].

    这是翻页的实现:




    image

    这是CircleMode,其用处是将任意值取模到指定范围内.比如 –1 ,取模后变成了15.16,取模后变成了0.

    image

    二,下面讲一下利用C#实现自动生成图片的工具制作方法.

          1,首先建立操作界面:

    <Window x:Class="EsayTools.IoTable"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:EsayTools"
            mc:Ignorable="d"
            Title="IO图片生成器" Height="493.31" Width="1293.16" FontSize="20" WindowStyle="ToolWindow">
        <Grid >
            <Grid.RowDefinitions>
                <RowDefinition Height="40"></RowDefinition>
                <RowDefinition Height="*"></RowDefinition>
                <RowDefinition Height="40"></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <TextBlock Text="输入点文本" VerticalAlignment="Center" Margin="20 0 0 0" FontSize="20" FontStyle="Italic" Foreground="Red"></TextBlock>
            <TextBlock Grid.Column="1" Text="输出点文本" VerticalAlignment="Center" Margin="20 0 0 0" FontSize="20" FontStyle="Italic" Foreground="Red"></TextBlock>
            <TextBox x:Name="Txt1" Grid.Row="1" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" Background="AliceBlue" BorderThickness="3" BorderBrush="Black" Margin="2"></TextBox>
            <TextBox x:Name="Txt2" Grid.Row="1" Grid.Column="1" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" Background="AliceBlue" BorderThickness="3" BorderBrush="Black" Margin="2"></TextBox>
            <TextBlock Text="IO起始地址:" Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="20 0 20 0" FontSize="20" FontStyle="Italic" Foreground="Red"></TextBlock>
            <Grid  Grid.Column="3" Grid.Row="3">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <TextBox x:Name="IOAddr" BorderBrush="Black" BorderThickness="3" Background="AliceBlue" ></TextBox>
                <Button  Click="bt1_Click" Grid.Column="2" Margin="10 2 10 2" FontSize="20">生成图片</Button>
    
            </Grid>
        </Grid>
    </Window>

    结果:

    image

    其次:建立界面效果图:

    image

    由于在一个TextBlock中同时显示IO,存在一个设计数据对齐的问题.也就是说,比如一个汉字对应2个字母,对应3个空格等等.

    最后解决办法,将字体敢为SimSun---凭借我不怎么好的英语瞎拆下是不是"仿宋"的意思??

    对齐的算法:

     private string FormatStringToChina(string str, int len)
            {
                return str + new string(' ', len - Encoding.GetEncoding("gb2312").GetBytes(str).Length);
    
            }

    len:为要对齐的最大长度.(在这里,有些字体是不行的...)

    首先将写入操作界面的字符串转为字符串数组:

     string[] strIs = ParentTxt1.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                string[] strOs = ParentTxt2.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                var Len = ((strIs.Length+15) / 16)> ((strOs.Length + 15 )/ 16)?((strIs.Length + 15 )/ 16): ((strOs.Length + 15 )/ 16);
                int StartAddr = ParentAddr; ;
                //检查文本框
                if (strIs.Length == 0 && strOs.Length == 0)
                {
                    System.Windows.Forms.MessageBox.Show("请填入正确的数据,IO文本数量不匹配");
                    return;
                }
    
                if(ParentOutputFile==null)
                {
    
                        System.Windows.Forms.MessageBox.Show("未输入正确文件名");
                        return;
    
    
                }

    其次,生成IO字符串数组

     private void generateStrings(int addr, out string[] iaddrs, out string[] oaddrs)
            {
                iaddrs = new String[16];
                oaddrs = new string[16];
    
                for (int i = 0; i < 16; i++)
                {
                    iaddrs[i] = "I" + (addr + i / 8) + "." + (i % 8);
                    oaddrs[i] = "Q" + (addr + i / 8) + "." + (i % 8);
                }
            }

    然后迭代  刷新TextBlock并且生成图片:

    刷新TextBlock代码:

    public void GenerateItem(TextBlock textBlock, string Iaddr, string Itxt, string Oaddr, string Otxt)
            {
                if(Itxt==null || Itxt==String.Empty)
                {
                    Itxt ="备用";
                }
                if (Otxt == null || Itxt == String.Empty)
                {
                    Otxt = "备用";
                }
    
                textBlock.Text = "     " + FormatStringToChina(Iaddr, 10) + FormatStringToChina(Itxt, 40) + FormatStringToChina(Oaddr, 10) + FormatStringToChina(Otxt, 40);
                if(IsBlue)
                {
                    textBlock.Background = Brushes.Orange;
                    IsBlue = false;
                }
                else
    
                {
                    textBlock.Background = Brushes.Brown;
                    IsBlue = true;
                }
    
            }
    
            public void GenerateItems(String[] Inops, String[] Onops, string[] Itxts, string[] Otxts)
            {
                int Index = 0;
                foreach (var Item in grid1.Children)
                {
                    TextBlock textBlock = (TextBlock)Item;
                    GenerateItem(textBlock, Inops[Index], Itxts[Index], Onops[Index], Otxts[Index]);
                    Index++;
                }
            }

    生成图片代码(网上找的):用于将Visual控件的内容转为图片,我这边是grid1,也就是包含了16个block的一个布局面板.

     private void GetPicFromControl(FrameworkElement element, String type, String outputPath)
            {
                //96为显示器DPI
                var bitmapRender = new RenderTargetBitmap((int)element.ActualWidth, (int)element.ActualHeight + 100, 96, 96, PixelFormats.Pbgra32);//位图 宽度  高度   水平DPI  垂直DPI  位图的格式    高度+100保证整个图都能截取
                //控件内容渲染RenderTargetBitmap
                bitmapRender.Render(element);
                BitmapEncoder encoder = null;
                //选取编码器
                switch (type.ToUpper())
                {
                    case "BMP":
                        encoder = new BmpBitmapEncoder();
                        break;
                    case "GIF":
                        encoder = new GifBitmapEncoder();
                        break;
                    case "JPEG":
                        encoder = new JpegBitmapEncoder();
                        break;
                    case "PNG":
                        encoder = new PngBitmapEncoder();
                        break;
                    case "TIFF":
                        encoder = new TiffBitmapEncoder();
                        break;
                    default:
                        break;
                }
                //对于一般的图片,只有一帧,动态图片是有多帧的。
                encoder.Frames.Add(BitmapFrame.Create(bitmapRender));//添加图
                if (!Directory.Exists(System.IO.Path.GetDirectoryName(outputPath)))
                    Directory.CreateDirectory(System.IO.Path.GetDirectoryName(outputPath));
                using (var file = File.Create(outputPath))//存储文件
                    encoder.Save(file);
    
    
    
            }


    这里的一个关键问题在于如何循环生成多张图片.

       private void GenerateOneBitMap(IOTableForClip Clip, int StartAddr, int CurAddr, string[] Itxtss, string[] Qtxtss)
            {
                string[] Itxts = new string[16];
                string[] Qtxts = new string[16];
                for (int i = 0; i < 16; i++)
                {
                    var Offset = (CurAddr - StartAddr) * 8;
                    if (Itxtss.Length >= Offset + i + 1)
                    {
                        Itxts[i] = Itxtss[Offset + i];
                    }
                    if (Qtxtss.Length >= Offset + i + 1)
                    {
                        Qtxts[i] = Qtxtss[Offset + i];
                    }
    
    
                }
    
                generateStrings(CurAddr, out string[] Iaddrs, out string[] Oaddrs);
                GenerateItems(Iaddrs, Oaddrs, Itxts, Qtxts);
                String[] PathName = ParentOutputFile.Split('.');
                this.UpdateLayout();
                GetPicFromControl(grid1, "PNG", PathName[0]+"_"+((CurAddr-StartAddr)/2)+".PNG");
    
            }

    UpdateLayout();函数非常的关建,只有刷新后,才能正确的显示实际的图片.

    附录,git地值https://gitee.com/mao_qin_bin/easy-tools.git.

  • 相关阅读:
    虚函数表
    写出float x 与“零值”比较的if语句
    系统表的构成
    UEFI的inf文件构成
    最短路径算法
    EDK2与EDK2工具链关系图
    GIT提交本地文件
    docker学习笔记-04:docker容器数据卷
    docker学习笔记-03:docker的镜像原理
    docker学习笔记-02:docker常用命令
  • 原文地址:https://www.cnblogs.com/frogkiller/p/14206073.html
Copyright © 2020-2023  润新知