📜 ⬆️ ⬇️

Working with Arduino from a C # application


In this article, I would like to talk about how you can read data and control an Arduino card connected via a USB port, from a .Net application, and from a UWP application.

You can do this without using third-party libraries. In fact, using only virtual COM port.

Let's write a sketch for Arduino first. We will send to the port a line with text containing the value of a variable, which we will have will constantly change in the loop (thus simulating data taken from the sensor). We will also read data from the port and if we receive the text “1”, then we will turn on the built-in LED. It is located on the 13th pin and is marked on the board with the Latin letter L. And if we get “0”, then turn it off.

int i = 0; //       int led = 13; void setup() { Serial.begin(9600); //     pinMode(led, OUTPUT); //    13-      } void loop() { i = i + 1; //        String stringOne = "Info from Arduino "; stringOne += i; //  Serial.println(stringOne); //     char incomingChar; if (Serial.available() > 0) { //        incomingChar = Serial.read(); //         LED switch (incomingChar) { case '1': digitalWrite(led, HIGH); break; case '0': digitalWrite(led, LOW); break; } } delay(300); } 

WPF application


Now create a WPF application. The markup is pretty simple. 2 buttons and a label to display the text received from the port is all that is needed:
')
  <StackPanel Orientation="Vertical"> <Label x:Name="lblPortData" FontSize="48" HorizontalAlignment="Center" Margin="0,20,0,0"> </Label> <Button x:Name="btnOne" Click="btnOne_Click" Width="100" Height="30" Margin="0,10,0,0"> 1</Button> <Button x:Name="btnZero" Click="btnZero_Click" Width="100" Height="30" Margin="0,10,0,0"> 0</Button> </StackPanel> 

Add 2 namespaces:

 using System.Timers; using System.IO.Ports; 

And in the scope of class 2 variables with a delegate:

 System.Timers.Timer aTimer; SerialPort currentPort; private delegate void updateDelegate(string txt); 

Implement the Window_Loaded event. In it, we will go through all available ports, listen to them and check whether the port displays a message with the text “Info from Arduino”. If we find the port sending such a message, it means we have found the Arduino port. In this case, you can set its parameters, open the port and start the timer.

  bool ArduinoPortFound = false; try { string[] ports = SerialPort.GetPortNames(); foreach (string port in ports) { currentPort = new SerialPort(port, 9600); if (ArduinoDetected()) { ArduinoPortFound = true; break; } else { ArduinoPortFound = false; } } } catch { } if (ArduinoPortFound == false) return; System.Threading.Thread.Sleep(500); //   currentPort.BaudRate = 9600; currentPort.DtrEnable = true; currentPort.ReadTimeout= 1000; try { currentPort.Open(); } catch { } aTimer = new System.Timers.Timer(1000); aTimer.Elapsed += OnTimedEvent; aTimer.AutoReset = true; aTimer.Enabled = true; 

To remove data from the port and compare it with the data I was looking for, I used the ArduinoDetected function:

  private bool ArduinoDetected() { try { currentPort.Open(); System.Threading.Thread.Sleep(1000); //  ,  SerialPort    string returnMessage = currentPort.ReadLine(); currentPort.Close(); //   void loop()     Serial.println("Info from Arduino"); if (returnMessage.Contains("Info from Arduino")) { return true; } else { return false; } } catch (Exception e) { return false; } } 

Now it remains to implement the event handling timer. The OnTimedEvent method can be generated using Intellisense. Its contents will be as follows:

  private void OnTimedEvent(object sender, ElapsedEventArgs e) { if (!currentPort.IsOpen) return; try //                { //     currentPort.DiscardInBuffer(); //    string strFromPort = currentPort.ReadLine(); lblPortData.Dispatcher.BeginInvoke(new updateDelegate(updateTextBox), strFromPort); } catch { } } private void updateTextBox(string txt) { lblPortData.Content = txt; } 

We read the value from the port and output it as label text. But since the timer works in a thread other than the UI thread, we need to use Dispatcher.BeginInvoke. This is where the delegate declared at the beginning of the code came in handy.

After finishing work with the port it is very desirable to close it. But since we work with it constantly, while the application is open, it is logical to close it when closing the application. Add a Closing event to our window:

  private void Window_Closing(object sender, EventArgs e) { aTimer.Enabled = false; currentPort.Close(); } 

Is done. Now it remains to send a message to the port with the text “1” or “0”, depending on the button click and you can test the operation of the application. It's simple:

  private void btnOne_Click(object sender, RoutedEventArgs e) { if (!currentPort.IsOpen) return; currentPort.Write("1"); } private void btnZero_Click(object sender, RoutedEventArgs e) { if (!currentPort.IsOpen) return; currentPort.Write("0"); } 

The resulting example is available on GitHub .

By the way, WinForms application is created even faster and easier. It is enough to drag the SerialPort element onto the form from the toolbar (if you wish, you can drag the Timer element from the toolbar). After that, in the right place of the code, you can open the port, read data from it and write to it in approximately the same way as in a WPF application.

UWP application


In order to understand how to work with a serial port, I looked at the following example: SerialSample
To allow working with a COM port in the application manifest there should be the following declaration:

  <Capabilities> <Capability Name="internetClient" /> <DeviceCapability Name="serialcommunication"> <Device Id="any"> <Function Type="name:serialPort"/> </Device> </DeviceCapability> </Capabilities> 

In C # code, we will need 4 namespaces:

 using Windows.Devices.SerialCommunication; using Windows.Devices.Enumeration; using Windows.Storage.Streams; using System.Threading.Tasks; 

And one variable in the class scope:

  string deviceId; 

When loading, we consider in it the port id value to which the Arduino board is connected:

  private async void Page_Loaded(object sender, RoutedEventArgs e) { string filt = SerialDevice.GetDeviceSelector("COM3"); DeviceInformationCollection devices = await DeviceInformation.FindAllAsync(filt); if (devices.Any()) { deviceId = devices.First().Id; } } 

The following Task counts 64 bytes from the port and displays the text in the field named txtPortData

 private async Task Listen() { using (SerialDevice serialPort = await SerialDevice.FromIdAsync(deviceId)) { if (serialPort != null) { serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000); serialPort.BaudRate = 9600; serialPort.Parity = SerialParity.None; serialPort.StopBits = SerialStopBitCount.One; serialPort.DataBits = 8; serialPort.Handshake = SerialHandshake.None; try { using (DataReader dataReaderObject = new DataReader(serialPort.InputStream)) { Task<UInt32> loadAsyncTask; uint ReadBufferLength = 64; dataReaderObject.InputStreamOptions = InputStreamOptions.Partial; loadAsyncTask = dataReaderObject.LoadAsync(ReadBufferLength).AsTask(); UInt32 bytesRead = await loadAsyncTask; if (bytesRead > 0) { txtPortData.Text = dataReaderObject.ReadString(bytesRead); txtStatus.Text = "Read operation completed"; } } } catch (Exception ex) { txtStatus.Text = ex.Message; } } } } 

In UWP applications in C #, there is no SerialPort.DiscardInBuffer method. Therefore, one of the options is to read the data each time opening the port again, which was demonstrated in this case. If you try, you can see that the countdown comes from one unit each time. Approximately the same thing happens in the Arduino IDE when you open the Serial Monitor. Option, of course, so-so. To open a port every time is not the case, but if data is rarely to be read, this method will come down. In addition, the recorded example is thus shorter and clearer.

The recommended option is not to announce the port every time, but to announce it once, for example, when loading. But in this case, it will be necessary to regularly read the data from the port so that it does not fill up with the old one and the data would be relevant. See how this is done in my example UWP application . I suppose that the concept of the inability to clear the buffer is that constantly asynchronously taking off data does not really load the system. As soon as the required number of bytes is read into the buffer, the code written next is executed. There is a plus, that with such constant monitoring you will not miss anything, but some (including me) lack the usual opportunity to “read” the data once.

You can use similar code to write data to the port:

  private async Task sendToPort(string sometext) { using (SerialDevice serialPort = await SerialDevice.FromIdAsync(deviceId)) { Task.Delay(1000).Wait(); if ((serialPort != null) && (sometext.Length != 0)) { serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000); serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000); serialPort.BaudRate = 9600; serialPort.Parity = SerialParity.None; serialPort.StopBits = SerialStopBitCount.One; serialPort.DataBits = 8; serialPort.Handshake = SerialHandshake.None; Task.Delay(1000).Wait(); try { using (DataWriter dataWriteObject = new DataWriter(serialPort.OutputStream)) { Task<UInt32> storeAsyncTask; dataWriteObject.WriteString(sometext); storeAsyncTask = dataWriteObject.StoreAsync().AsTask(); UInt32 bytesWritten = await storeAsyncTask; if (bytesWritten > 0) { txtStatus.Text = bytesWritten + " bytes written"; } } } catch (Exception ex) { txtStatus.Text = ex.Message; } } } } 

You may notice that after the initialization of the port and the installation of parameters, pauses were added for 1 second. Without these pauses, it was impossible to force Arduino to react. It seems that the serial port really does not tolerate fuss. Again, I remind you that it is better to open the port once, and not to open / close it constantly. In this case, no pause is needed.
A simplified version of the UWP application, which is similar to the WPF .Net application discussed above, is available on GitHub

As a result, I came to the conclusion that working in UWP with Arduino directly through a virtual COM port, although unusual, is quite possible without connecting third-party libraries.
I note that Microsoft is working quite closely with Arduino, so there are many different libraries and communication technologies that simplify development. The most popular, of course, is the Windows Remote Arduino library that works with the Firmata protocol.

Source: https://habr.com/ru/post/275439/


All Articles