1. 程式人生 > 實用技巧 >基於西門子PLC和觸控式螢幕以及C#開發的通用IO監控系統

基於西門子PLC和觸控式螢幕以及C#開發的通用IO監控系統

前言: 在專案開發的過程之中,經常需要做IO監控畫面.當IO監控點多的時候,往往需要做很多的畫面,並且浪費了HMI的很多IO點.做起畫面來也很麻煩和繁瑣.從而,讓我思考著如何做一個工具.可以在一個畫面上監控所有的變數.

要實現這個,其實也不難.

1,需要將IO變數泛型的通過指標的方式,反饋到一個變數上,並且使用這個變數的位來指示每個IO的狀態.

2,需要將IO的文字解釋生成一系列的IO圖片,然後在HMI上建立IO列表.關聯起來.

一,使用C#開發圖片生成工具:

在左側輸入,I的文字解析,在右側輸入O的文字解析.在IO地址處放入起始地址.然後點選生成圖片,就會生成一系列的圖片了.

300表示從I300.0和Q300.0開始的IO變數.點選生成圖片,開始生成:

開啟一張圖片,看下效果:名字叫 IO_0.PNG

最後一張 名字叫 IO_15.PNG

建立完之後,把所有圖片拷貝專案資料夾裡面,並且建立圖形列表,名字叫IOtable

再建立一個IO點滅和亮通過不同形式表示的圖形列表,IO顯示(反正大家可以自己去設)

做完之後,再新建一個畫面

這是模擬的,實際畫面裡面是 一個圖形IO,繫結,剛才建立的IOTable圖形列表.以及32個圖形IO,繫結剛才建立的IO顯示圖形列表

外加兩個翻頁按鈕,當按左邊時候,迴圈向下翻頁,按右邊時迴圈向上翻頁(翻到底時就重複到起始地址).

這是翻到第一頁.可以看到,第一個I300.0沒有被點亮,而第二頁,則被點亮了.

實際上是連線了一個變數:

二,我們在PLC裡面來實現這個功能:新建一個FB塊,名字叫IO_Monitor

輸入介面說明:

  • 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為當前索引.

實現:


通過 除以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].

這是翻頁的實現:




這是CircleMode,其用處是將任意值取模到指定範圍內.比如 –1 ,取模後變成了15.16,取模後變成了0.

二,下面講一下利用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>

結果:

其次:建立介面效果圖:

由於在一個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();函式非常的關建,只有重新整理後,才能正確的顯示實際的圖片.