domenica 23 ottobre 2016

Writing your first HMI project in C# – WPF

Writing an Hmi with C# and WPF is quite easy, once that you know how to structure your project.
In this post i will make an example on how to write a simple Hmi (2 pages) that can access to a variable that get updated from another thread.

Structure of the project

The structure of the Hmi will be the following:
– Graphical User Interface: written in WPF using the PageSwitcher class.
– Plc variables: global static variables that can be read(-only) from all pages.
– Plc driver: multithreaded communication to avoid to block the graphic user interface thread.
Once you created your pages, you need to develop the communication driver for your plc, or to use an existing driver.
The communication driver is usually synchronized with the plc, this means that the driver sends a request to the plc and waits until the plc answers; the waiting time can be short or long, depending on the protocol and cable.
A synchronized communication can be a pain for your graphic user interface, because it will lock the pages for the most of the time; that’s why the communication has to be hosted on a secondary thread and plc-variables needs to be synchronized with the GUI thread.

Plc variables and communication thread

To write an Hmi and access to plc values i often use global static variables, that contains the values that i read from the plc.
Usually i declare a plc variable in this way:
1
public static int Count { get; private set; }
The “private set” means that the variable is read-only outside the class, and this is logic because you can’t write a plc-variable without write it in the plc before.
To contain the variables and the communication thread you should use a static class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static class Plc
{
    static Thread t;
     
    static Plc()
    {
        // this is an example of static constructor, that i don't need in this example and i leave it empty.
    }
    public static void StartCommunication()
    {
        t = new Thread(new ThreadStart(CommunicationThread));
        t.Name = "PlcCommunication";
        t.Start();
    }
}
The communication thread is:
1
2
3
4
5
6
7
8
9
10
11
12
static readonly object _locker = new object();
private static void CommunicationThread()
{
    while (!StopCommunication)
    {
        lock (_locker)
        {
             Count++;
             Thread.Sleep(500); //long elaboration
        }
    }
}

Access to plc-variables inside the pages

To access to plc-variables inside the hmi pages, just declare a timer for each page, with a callback that updates the GUI objects with the plc-variables.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DispatcherTimer timer = new DispatcherTimer();
public Page1()
{
    InitializeComponent();
    timer.Interval = TimeSpan.FromMilliseconds(100);
    timer.Tick += new EventHandler(timer_Tick);
    timer.Start();
    timer_Tick(null, null); //this will refresh the textboxes before you show the page
}
void timer_Tick(object sender, EventArgs e)
{
    txtCount.Text = Plc.Count.ToString();
}

Memory issues

There is a known memory leak with DispatcherTimer inside UserControls, because if it’s not stopped it will not release the UserControl and you will keep in RAM all the page that you visit.
The reference is here: http://geekswithblogs.net/dotnetrodent/archive/2009/11/05/136015.aspx
To avoid the memory leak remember to subscribe to the event UserControl_Unloaded:
1
2
3
<UserControl x:Class="HmiExample.Pages.Page1"
             ...
              Unloaded="UserControl_Unloaded">
and to stop the timer in the event handler:
1
2
3
4
private void UserControl_Unloaded(object sender, System.Windows.RoutedEventArgs e)
{
    timer.Stop();
}

Example for VS2010

This is a quick and dirty example, but it will get you started until you will master WPF and Datacontext – Databinding mechanism.
Download the example for Visual Studio 2010.

Nessun commento:

Posta un commento