Click here to Skip to main content
15,868,016 members
Articles / Internet of Things / Arduino

Arduino, C#, and Serial Interface

Rate me:
Please Sign up or sign in to vote.
4.97/5 (64 votes)
10 Mar 2016CPOL11 min read 307.6K   22.6K   196   38
Serial communication with an Arduino Board via C# and the SerialPort class from the System.IO.Ports namespace

Content

Introduction

The Arduino boards provide an easy-to-use environment to control electrical components such as LEDs and motors. I have done this project with an Arduino Mega 2560 microcontroller board, but you can use nearly any official Arduino board.

To download and execute the source files attached to this article, you will need to have the following tools installed:

  • An Arduino board, connected to your computer by USB
  • The official Arduino IDE (download from here)
  • The Arduino board driver must be installed as described here
  • Microsoft Visual Studio Express 2010, 2012 or 2013

Background

I completely rewrote this article in fall 2013 and added several new examples, a better explanation and more background information.

What is a Serial Interface?

A serial interface is used for information exchange between computers and peripheral devices. When using a serial communication, the information is sent bit by bit (serial) over a cable. Modern serial interfaces are Ethernet, Firewire, USB, CAN-Bus or RS-485 even though they are not always called "a serial interface". The principle stays the same: Information is bit-by-bit transformed over short distances. Glenn Patton published a great article about the design patterns used by serial communication Ports - It is available on CodeProject.

About the Board

I use an Arduino Mega 2560 Board to test and execute my code, even though the code shall work with most of the available Arduino devices - The code is written using the Official Arduino IDE, which can be downloaded from the Arduino Project page for free, as mentioned at the start of the article.

Working Theory

To explain how a C# program and an Arduino Board may communicate, we are going to assume that the Arduino Board has a temperature sensor, collecting the environment temperatures three times a day: Once in the morning, once at noon and once in the evening. All these values are stored to the EEPROM on the Arduino board. The data is stored in the following format:

11 06 2013 06:00=6.6
11 06 2013 12:00=11.78
11 06 2013 23:00=8.9
11 07 2013 06:00=4.54
11 07 2013 12:00=15.3
11 07 2013 23:00=8.6
11 21 2013 06:00=-2.33  

The raw data is formatted as [DATE TIME]=[temperature], and the temperature value is intended to be measured and stored in Celsius (C). The single sets of temperature data are separated by line breaks.

Now, in order to focus on the serial communication, I decided to leave out the EEPROM reading/writing and just define a set of dummy values which are sent whenever a request for the temperature data is coming to the Arduino board. I did a quick example of how a single set of data will look in the Arduino C++ code (I use Arduino's <a href="http://arduino.cc/de/Reference/StringObject" title="Arduino Reference - The String class">String</a> class):

C++
String SingleSetOfData = "11 21 2013 06:00=-2.33"//Contains '11 21 2013 06:00=-2.33\0' 

Given the fact that a date-time (for example "11 21 2013 06:00", which stands for November 21st 2013 at 06:00 AM) can be at most 15 characters long, the temperature will cover everything from -275.00 to 1999.999 degrees and the separator ('=') is another character we come to a total length of 23 characters the singleSetOfData needs to take - Why is it in fact 24 characters long then?
Easy question, easy answer: The compiler adds a terminating character '\0' at the end which's space used need to be taken care of, too - 23 + 1 = 24.


I will not go into detail about the thermometer implementation since there are plenty of instruction sets on how to build one available on the internet - We are going to send dummy values anyways
However, the Arduino board may receive a serial command telling it to read the collected temperature data. I assigned every command a single command code, and defined it to be terminated by a '#' character. So for example, the command to tell the Arduino board has to send the stored temperature data would come like this over the serial interface of the Arduino board, issued by the .NET application which is going to display the temperature data:

1#

Simples. That's it. After the Arduino Board receives this command, it is going to send our previously defined dummy data up to the .NET application, using the serial port, too (Of course, this requires the .NET application to stop sending any command down to the Arduino board):

[STX]
11 06 2013 12:00=11.78$
11 06 2013 23:00=8.9$
11 07 2013 06:00=4.54$
11 07 2013 12:00=15.3$
11 07 2013 23:00=8.6$
11 21 2013 06:00=-2.33[ETX]

Every transmission starts with the code [STX] which is the official US ASCII code for the start of a transmission text (Code "2"). The transmission ends after sending the code [ETX] which is the official US ASCII code for the end of a transmission text (Code "3"). The single datasets are separated by a dollar ($) sign.

Using the Code

The code presented in this chapter was completely rewritten during fall 2013 to provide a more valuable learning resource, and match a less theoretical working theory.

The Arduino Source Code

Error, Warning and Status codes

I use various Error, Warning and Status codes within the Arduino source code to indicate whether method calls were successful or ended up causing a warning or an error.

C++
 /*   WARNING, ERROR AND STATUS CODES                              */
//STATUS
#define MSG_METHOD_SUCCESS 0                      //Code which is used when an operation terminated  successfully
//WARNINGS
#define WRG_NO_SERIAL_DATA_AVAILABLE 250            //Code indicates that no new data is available at the serial input buffer
//ERRORS
#define ERR_SERIAL_IN_COMMAND_NOT_TERMINATED -1   //Code is used when a serial input commands' last char is not a '#' 

Read a command from the Serial Input

I created a method which reads a command from the Arduino Board's serial input buffer and writes it into the pointer which points to an Arduino-String - returned is an integer value indicating an operation success or an operation fail / warning. It returns either one of the three following codes:

  • MSG_METHOD_SUCCESS
    • This code is returned if everything went fine and the command was written into the command pointer.
  • WRG_NO_SERIAL_DATA_AVAILABLE
    • This code is returned if the serial input buffer doesn't contain any data at the time the method is executed.
  • ERR_SERIAL_IN_COMMAND_NOT_TERMINATED
    • This code is returned if there is no data available in the serial input buffer before the end of a command was reached (which means it was not terminated by the '#' character)

Actually, this is the only method I left untouched in comparison to the article and source code in previous versions before I did the complete rewrite.

The code of this method is built up as follow:

The default return code operationStatus is MSG_METHOD_SUCCESS. If no data is available in the serial input buffer (checked by calling Serial.Available() which returns true if there is data in the serial input buffer), the said operationStatus has the code WRG_NO_SERIAL_DATA_AVAILABLE assigned.

C++
int operationStatus = MSG_METHOD_SUCCESS;//Default return is MSG_METHOD_SUCCESS reading data from com buffer.
if (Serial.available()) {
     
}
else{//If not serial input buffer data is available, operationStatus becomes WRG_NO_SERIAL_DATA_AVAIBLE (= No data in the serial input buffer available)
    operationStatus = WRG_NO_SERIAL_DATA_AVAILABLE;
}   

In the likely case that there is data in the serial input buffer, the program executes the following code block:

C++
char serialInByte;//temporary variable to hold the last serial input buffer character
     
do{//Read serial input buffer data byte by byte 
    serialInByte = Serial.read();
    *command = *command + serialInByte;//Add last read serial input buffer byte to *command pointer
}while(serialInByte != '#' && Serial.available());//until '#' comes up or no serial data is available anymore
     
if(serialInByte != '#') {
    operationStatus = ERR_SERIAL_IN_COMMAND_NOT_TERMINATED;
} 

What now happens is not a big deal and easy to explain. I declared the variable serialInByte in order to store a single byte which was read by the Serial.Read() method (which reads exactly one byte from the serial input buffer). I call the said Serial.Read() method, store the byte returned into serialInByte and append serialInByte to the command pointer - This goes on and on until no data is available in the serial input buffer, or the character "#" is showing up, indicating the end of a command.

The last if after the do-while-Loop checks whether the last read character was a "#" and sets the operationStatus to ERR_SERIAL_IN_COMMAND_NOT_TERMINATED if it wasn't because this would mean that a transmission error occurred.

Answer a Serial Command

As you may know, the Arduino source code has a loop() method which is executing the source code in a forever-loop. I placed the code to handle a command directly in this method:

C++
String command = "";  //Used to store the latest received command
int serialResult = 0; //return value for reading operation method on serial in put buffer

serialResult = readSerialInputCommand(&command);

As you can see, I call readSerialInputCommand to read the command string from the serial input buffer. The return code of the method call is stored in the integer variable serialResult, which is afterwards used to detect wether there was a command available to read:

C++
if(serialResult == MSG_METHOD_SUCCESS){
    if(command == "1#"){//Request for sending weather data via Serial Interface
                        //For demonstration purposes this only writes dummy data
        WriteDummyWeatherData();
    }
} 

To keep this example easy and focused on serial communication, I left out any data storing/loading routines and just write dummy data back to the serial output by calling WriteDummyWeatherData():

C++
void WriteDummyWeatherData(){
  Serial.print(STX);
  Serial.print("11 06 2013 12:00=11.78");
  Serial.print(RS);
  Serial.print("11 06 2013 23:00=8.9");
  Serial.print(RS);
  Serial.print("11 07 2013 06:00=4.54");
  Serial.print(RS);
  Serial.print("11 07 2013 12:00=15.3");
  Serial.print(RS);
  Serial.print("11 07 2013 23:00=10.3");
  Serial.print(RS);
  Serial.print("11 21 2013 06:00=-2.33");
  Serial.print(ETX);
} 

Sure that this method call would need to be replaced in case you really want to send actual weather / any other data to the serial output, but it's fair enough for demonstration purposes.

The .NET Source Code

I implemented a small demo WPF application which shows you how serial data can be handled using the .NET Framework.

The WeatherDataContainer class

I have two classes which help me to organize the communication with the Arduino Board: WeatherDataItem and WeatherDataContainer. While the WeatherDataItem is a sole data-holding class (consisting of a property for the date and one for the temperature) WeatherDataContainer is (amongst a List of WeatherDataItems) encapsulating the communication with the Arduino Board, too.

Since the GUI is only calling methods which can be found in the WeatherDataContainer class, I'll focus on it rather than unrolling the boring GUI code.

The WeatherDataContainer has two field variables:

C#
// Interface for the Serial Port at which an Arduino Board is connected.
SerialPort arduinoBoard = new SerialPort();

//Holds a List of WeatherData Items in order to store weather data received from an Arduino Board.
List<WeatherDataItem> weatherDataItems = new List<WeatherDataItem>(); 

It is important for you to keep these variables and the event in mind, since they'll be mentioned throughout the following code snippets.

Connect to an Arduino Board

The Serial communication capabilities for .NET are provided by the SerialPort class from the System.IO.Ports namespace. Before you can send or receive data with a SerialPort instance, you need to open the Port:

C#
public void OpenArduinoConnection()
{
    if(!arduinoBoard.IsOpen)
    {
        arduinoBoard.DataReceived += arduinoBoard_DataReceived;
        arduinoBoard.PortName =  ConfigurationSettings.AppSettings["ArduinoPort"];
        arduinoBoard.Open();
     }
     else
     {
        throw new InvalidOperationException("The Serial Port is already open!");
     }
}

As you can see, we add a new Event handler which handles the DataReceived event of the Serial port - This event is fired as soon as there is data available in the input buffer of the Serial Port.

Getting weather data

You can easily send a command to the Arduino board (I encapsulated the command to make the code better readable):

C#
public void GetWeatherDataFromArduinoBoard()
{
   if (arduinoBoard.IsOpen)
   {
      arduinoBoard.Write("1#");
   }
   else
   {
      throw new InvalidOperationException("Can't get weather data if the serial Port is closed!");
   }
} 

After this command is received by the Arduino Board, it will send back the weather data and trigger the DataReceived event of the Serial Port.

Receive and parse serial data

As soon as there is data available at the input buffer of the Serial Port, it will fire the DataReceived Event which is handled by the following event handler in the WeatherDataContainer class:

C#
void arduinoBoard_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    string data = arduinoBoard.ReadTo("\x03");//Read until the EOT code
    //Split into 'date=temperature' formatted text
    string[] dataArray = data.Split(new string[] 
    {"\x02", "$" }, StringSplitOptions.RemoveEmptyEntries); 
    //Iterate through the split data and parse it into weather data items
    //and add them to the list of received weather data.
    foreach (string dataItem in dataArray.ToList())
    {
       WeatherDataItem weatherDataItem = new WeatherDataItem();
       weatherDataItem.FromString(dataItem);
       weatherDataItems.Add(weatherDataItem);
     }
     
     if(NewWeatherDataReceived != null)//If there is someone waiting for this event to be fired
     {
       NewWeatherDataReceived(this, new EventArgs()); //Fire the event, 
               // indicating that new WeatherData was added to the list.
     }
} 
Closing a SerialPort

It is highly recommended to close a SerialPort after it isn't in use anymore. This happens by calling the Close() method on a SerialPort object:

C#
arduinoBoard.Close(); 

The GUI

I chose two ways of displaying the weather data: As DataGrid, and as 3D Cylinder Chart. Either one of them has its advantages, even though I prefer the Chart since it looks a bit fancier.

Displaying the Data in a Data Grid

Displaying the weather data in a data grid has an advantage if you want to display a lot of data in a reasonable way - If you only have ten or less data items displaying the weather data as a Cylinder graph is rather reasonable.

Image 1

The XAML code of the above grid looks somewhat like that:

XML
<DataGrid Name="weatherDataGrid" AutoGenerateColumns="False" Grid.Column="1">
  <DataGrid.Columns>
     <DataGridTextColumn Header="Date" 
     Width="1*" Binding="{Binding Date}">
     </DataGridTextColumn>
     <DataGridTextColumn Header="temperature (Celsius)" 
     Width="1*" Binding="{Binding temperatureCelsius}">
     </DataGridTextColumn>
  </DataGrid.Columns>
</DataGrid> 

The Grid has two columns from which one is bound to the Date Property and one is bound to the temperatureCelsius Property of the WeatherDataItem class.

The Grid is filled as soon as the Event handler for the WeatherDataContainer's NewWeatherDataReceived is triggered, and is executed with Thread safety provided by the dispatcher:

C#
void weatherData_NewWeatherDataReceived(object sender, EventArgs e)
{
   Dispatcher.BeginInvoke(new ThreadStart(() => 
                          weatherDataGrid.ItemsSource = weatherData.WeatherDataItems)); 
Displaying the data in a 3D Chart

The 3D Chart is placed on a different tab on the application window and directly drawn on a WPF Canvas by the DrawChart method:

C#
private void DrawChart()
{
  List<double> temperatures = new List<double>();
  List<string> captions = new List<string>();
  foreach (WeatherDataItem item in weatherData.WeatherDataItems)
  {
    temperatures.Add(item.temperatureCelsius);
    captions.Add(string.Format("{0}\n{1} C", item.Date.ToShortDateString(), 
                 item.temperatureCelsius.ToString()));
  }
  ChartDataContainer container = new ChartDataContainer();
  container.Data = temperatures;
  container.DataCaptions = captions;
  container.MaxSize = new Point(800, 300);
  container.XAxisText = "Date / temperature (Celsius)";
  container.YAxisText = "";
  container.Offset = new Point(20, 20);
  container.ChartElementColor = Colors.BurlyWood;
  CylinderChartPainter.DrawChart(container, weatherChartCanvas);
} 

The Chart is drawn as soon as the Event handler for the WeatherDataContainer's NewWeatherDataReceived is triggered, and is executed with Thread safety provided by the dispatcher:

C#
void weatherData_NewWeatherDataReceived(object sender, EventArgs e)
{
    Dispatcher.BeginInvoke(new ThreadStart(DrawChart));

The XAML for the Canvas looks pretty ordinary, it is basically just a canvas filling a TabItem:

XML
<TabItem Name="weatherChartTab" Header="3D Chart">
  <TabItem> 
    <Canvas Name="weatherChartCanvas">
                    
    </Canvas>
  </TabItem>
</TabControl> 

In the End, a Chart looking somewhat like the Chart shown in the picture below is drawn onto the Canvas:

Image 2

Points of Interest

Arduino provides a huge amount of possibilities on working with hardware and connection to a computer. Last but not least, at the official Arduino homepage is a large community support available. It is very interesting which possibilities are available when you use a serial interface: You can send a pretty huge amount of data and it is not even really complicated.

The best part (for me) was that I was able to do a basic bit of WPF. I played around with various methods of databinding and invoking before I got the .NET app to run.

History

  • 10-October-2012: Created the article
  • 10-October-2012: Updated
  • 12-October-2012: Added the functionality to read different commands
  • 11-September-2013: Changed License to LGPL 3
  • 23-November-2013: Complete rewrite - New, structured approach to solve an example problem
  • 26-November-2013: Updated to use the 3D Cylinder Chart to display weather data

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
BugResolved InvalidOperationException when switching tab back and forth Pin
Chris Basak23-Feb-21 11:56
Chris Basak23-Feb-21 11:56 
Newscool, but... another library Pin
Da_Hero29-Sep-19 9:47
professionalDa_Hero29-Sep-19 9:47 
BugEOT is wrong Pin
nexus998-Apr-17 8:20
nexus998-Apr-17 8:20 
QuestionGood Work. Pin
ArchieF16-Mar-16 22:49
ArchieF16-Mar-16 22:49 
AnswerRe: Good Work. Pin
Marco Bertschi17-Mar-16 3:20
protectorMarco Bertschi17-Mar-16 3:20 
GeneralMy vote of 5 Pin
Member 1102292519-Aug-14 12:00
Member 1102292519-Aug-14 12:00 
GeneralRe: My vote of 5 Pin
Marco Bertschi24-Aug-14 6:52
protectorMarco Bertschi24-Aug-14 6:52 
Question3DChart missing? Pin
N.Enguehard2-Jun-14 13:39
N.Enguehard2-Jun-14 13:39 
AnswerRe: 3DChart missing? Pin
Marco Bertschi3-Jun-14 1:03
protectorMarco Bertschi3-Jun-14 1:03 
GeneralRe: 3DChart missing? Pin
N.Enguehard3-Jun-14 6:33
N.Enguehard3-Jun-14 6:33 
GeneralRe: 3DChart missing? Pin
Marco Bertschi3-Jun-14 7:37
protectorMarco Bertschi3-Jun-14 7:37 
QuestionEOT... standard? Pin
Member 1084688827-May-14 7:50
Member 1084688827-May-14 7:50 
AnswerRe: EOT... standard? Pin
Marco Bertschi27-May-14 8:02
protectorMarco Bertschi27-May-14 8:02 
GeneralMy vote of 5 Pin
Volynsky Alex21-Apr-14 21:49
professionalVolynsky Alex21-Apr-14 21:49 
GeneralTEMPARATURE TEMPARATURE TEMPARATURE Pin
André Pereira25-Jan-14 2:38
André Pereira25-Jan-14 2:38 
GeneralRe: TEMPARATURE TEMPARATURE TEMPARATURE Pin
Marco Bertschi25-Jan-14 3:28
protectorMarco Bertschi25-Jan-14 3:28 
Newsexactly what i was looking for... Pin
Dana_G4-Dec-13 18:11
professionalDana_G4-Dec-13 18:11 
GeneralRe: exactly what i was looking for... Pin
Marco Bertschi4-Dec-13 18:47
protectorMarco Bertschi4-Dec-13 18:47 
QuestionArduino hardware Pin
Plamen Kovandjiev28-Nov-13 1:43
professionalPlamen Kovandjiev28-Nov-13 1:43 
AnswerRe: Arduino hardware Pin
Marco Bertschi28-Nov-13 3:32
protectorMarco Bertschi28-Nov-13 3:32 
QuestionYou mention my Article!! Pin
glennPattonWork326-Nov-13 22:58
professionalglennPattonWork326-Nov-13 22:58 
AnswerRe: You mention my Article!! Pin
Marco Bertschi27-Nov-13 1:58
protectorMarco Bertschi27-Nov-13 1:58 
GeneralMy vote of 5 Pin
Abinash Bishoyi9-Apr-13 6:01
Abinash Bishoyi9-Apr-13 6:01 
QuestionCommands? Pin
kovacsgellert6-Jan-13 4:14
kovacsgellert6-Jan-13 4:14 
AnswerRe: Commands? Pin
glennPattonWork327-Nov-13 3:00
professionalglennPattonWork327-Nov-13 3:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.