.Net is used when programming desktop and web applications, and is it possible to use a framework for managing industrial facilities?
We will understand at the beginning of where it is possible to use such software.
Industrial control systems consist of several levels:
- sensor level
- control level (PLC, computer)
- visualization level / SCADA
Visualization is the most unpretentious level of automated control systems for information processing speed, usually on the operator panels information is displayed about the state of any aggregate of the entire system (some numerical values, status bits (on-off), graphs of change of values). For this level, you can freely use all the features of .NET for displaying graphical information (Win Forms, WPF, SilverLight).
Consider the level of control. Here, the necessary speed of information processing depends on the control object: if you want to just blink the LED or turn on / off the engine without tight timing, then this is possible. If it is necessary to manage objects with a rigidly defined reaction time, then it is necessary to use a real-time system.
Real-time systems require a real-time OS. In such OSs, the context switching time of tasks is strictly defined, i.e. the time slot for processing each thread with the same priority is allocated the same.
And what can happen when using a "desktop" OS?
I want to consider what speed can be achieved using Windows and .Net. In this case, there will be obstacles to high performance:
- parallel running processes
- background services
- garbage collector .NET applications
- checks when executing managed code
We set the task: there is a PC with Windows XP + an USB-COM adapter based on an FTDI controller. How fast is it possible to switch the RTS output of the adapter to get a stable
meander (without changing the pulse repetition period)? Such a task is the simplest version of the implementation of the control of external devices by a program on a PC.
To bring the performance of the program closer to the real-time mode, we use the program priority and workflow increase.
A bit of theory on Windows Task Scheduler
In total there are 32 levels of priorities:
- 0 system level "zero page thread"
- 1-15 adjustable levels
- 16-31 real time levels
In the range of changeable priorities, the following classes are additionally distinguished:
- Idle (2-6)
- Below Normal (4-8)
- Normal (6-10)
- Above Normal (8-12)
- High (11-15)
- Real-time (22-26)
Each process during creation (CreateProcess ()) can be assigned a priority class, if it is not specified, the process receives the priority class Normal (level 8). Each process thread can be individually assigned a priority level. The total, base, thread priority is calculated by the combination of the process priority class and the thread priority level. If the basic priority of a thread belongs to the real-time priority group (level 16-31), then it does not change dynamically, otherwise the scheduler may slightly change the priority of threads depending on the current processor load and events (UI, I / O).
The time slot for each thread is allocated 10ms for a single processor system and 15ms if the system is multiprocessor. Also, the quantum depends on the type of OS (normal or server (the server uses “long quanta”)), as well as on the application state (the priority of UI streams and streams working with I / O devices, temporarily increases when corresponding events occur).
For our example, we use the “toughest” option — set the process priority class in Real-time and the workflow priority in Highest. The basic priority level of a thread in this combination is 24. At this level of priority, the thread scheduler does not have to switch the context to other threads (since, apart from system processes, there are no other threads with such priority).
')
Program
using System;
using System.Collections. Generic ;
using System.Diagnostics;
using System.IO;
using System.IO.Ports;
using System.Threading;
using System.Windows.Forms;
using FTD2XX_NET;
private bool _exitThread;
private Thread _workThreadPort, _workThreadPortUnmanaged, _workThreadDriver;
private List < long > _durationsPort, _durationsPortUnmanaged, _durationsDriver;
public FormMain()
{
InitializeComponent();
//System.Diagnostics.Process.GetCurrentProcess().PriorityClass =
// ProcessPriorityClass.RealTime;
// SerialPort
_workThreadPort= new Thread(()=>
{
SerialPort port = new SerialPort( "COM5" );
port.BaudRate = 115200;
port.StopBits = StopBits.One;
port.Parity = Parity.None;
port.DataBits = 7;
port.Handshake = Handshake.None;
port.Open();
bool flag= false ;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
//Thread.CurrentThread.Priority = ThreadPriority.Highest;
while (!_exitThread)
{
_durationsPort.Add(stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
port.RtsEnable = flag;
flag = !flag;
}
port.Close();
});
// (Win API)
_workThreadPortUnmanaged = new Thread(() =>
{
UnmanagedSerialPort unmanagedSerialPort = new UnmanagedSerialPort( "COM5" );
unmanagedSerialPort.Open();
bool flag= false ;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
//Thread.CurrentThread.Priority = ThreadPriority.Highest;
while (!_exitThread)
{
_durationsPortUnmanaged.Add(stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
if (flag)
{
unmanagedSerialPort.On();
}
else
{
unmanagedSerialPort.Off();
}
flag = !flag;
}
unmanagedSerialPort.Close();
});
// API FTDI
_workThreadDriver= new Thread(()=>
{
FTDI myFtdiDevice = new FTDI();
myFtdiDevice.OpenByIndex(0);
bool flag= false ;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
//Thread.CurrentThread.Priority = ThreadPriority.Highest;
while (!_exitThread)
{
FTDI.FT_STATUS ftStatus = myFtdiDevice.SetRTS(flag);
if (ftStatus == FTDI.FT_STATUS.FT_OK)
{
flag = !flag;
_durationsDriver.Add(stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
}
}
myFtdiDevice.Close();
});
_durationsDriver = new List < long >();
_durationsPort = new List < long >();
_durationsPortUnmanaged = new List < long >();
}
//
private void b_Save_Click( object sender, EventArgs e)
{
StreamWriter sw = new StreamWriter( "Port.csv" );
_durationsPort.ForEach(l => sw.WriteLine(l));
sw.Close();
sw = new StreamWriter( "Driver.csv" );
_durationsDriver.ForEach(l => sw.WriteLine(l));
sw.Close();
sw = new StreamWriter( "PortUnmanaged.csv" );
_durationsPortUnmanaged.ForEach(l => sw.WriteLine(l));
sw.Close();
}
private void b_RunViaPort_Click( object sender, EventArgs e)
{
_exitThread = false ;
_workThreadPort.Start();
}
private void b_Stop_Click( object sender, EventArgs e)
{
_exitThread = true ;
}
private void b_RunViaPortUnmanaged_Click( object sender, EventArgs e)
{
_exitThread = false ;
_workThreadPortUnmanaged.Start();
}
private void b_RunViaDriver_Click( object sender, EventArgs e)
{
_exitThread = false ;
_workThreadDriver.Start();
}
* This source code was highlighted with Source Code Highlighter .
Consider 3 options for working with the port:
- SerialPort class
- unmanaged code (Win API: GetCommState, EscapeCommFunction, SetCommState)
- FTDI library
In each case, we change the state of the RTS pin in the program cycle, marking the time of one cycle. This time corresponds to the switching time of the pin in the desired state.
Test conditions: each of the options is checked for 30 seconds, after 10s the processor for 10s is loaded with an additional application (WinRar - hardware performance test). Each option is checked at the usual (level 8) priority of the stream (blue graph) and Real-time (level 24, red graph).
results
SerialPort class

Unmanaged Code and FTDI Library

The graphs show that when working through the standard class SerialPort, the cycle time is about 5 times longer than the time when working through a “non-standard” solution. Increasing the priority of a stream reduces the time of one cycle to 2ms, such a stream is not interrupted.
findings
When implementing control of external devices from a PC using a managed code .net framework, the response time will be no less than 1-2ms. Partially reducing the effect of parallel processes is possible by increasing the priority of the stream. At the same time, we should not forget about other processes and, if possible, manually switch the context (Thread.Sleep (0)) to other waiting threads. Excessive calls to the garbage collector (GC) should be avoided by rational work with objects, using the correct application architecture, this can be traced by a profiler or system performance counters. Also in a multiprocessor system, you can assign different threads to different processors (see SetThreadAffinityMask ()).
The purpose of the article is not to make another bike, it is clear that you cannot do without a PLC or a microcontroller to control technical processes; I want to show that it is possible for .NET to find application in solving a certain range of tasks, where the required minimum response time of a system to an impact is more than 2-15ms.
I would like to see through N years. NET RT following the example of the Java Real-Time System. As well as the industrial application of the .NET Micro Framework on PLCs of well-known companies (Siemens, Omron).
Update1Links
C # for Real-time - an article that pushed me on this topic
Requirements for a Real-Time .NET Framework - this article outlines the principles necessary for implementing .NET RT based on a comparison with Java Real-time
Real-time GC in Java - Thanks
conscellComparing Thread.Sleep (0) and Thread.Sleep (1):
here and
here - Thanks
Frozik and
tangro