10. Serial I/O - Part 3

10. Serial I/O - Part 3
Photo by Sahand Babali / Unsplash

Let's think about what data types. Remember the SimVar structure we created in Part 1 of the series, we created a struct with two floats that represent the Altitude and Barometric Setting of an altimeter.

private struct SimVars
{
  public float Altitude;            // INDICATED ALTITUDE (feet)
  public float KohlsmanSettingHg;   // KOHLSMAN SETTING HG (inHG)
}

In memory, Arduino's float and double are both 4-bytes objects, but our PC is 64-bit and C#'s double is a 8-bytes object. Fortunately, float is 32-bits so we can register our SimVars as SIMCONNECT_DATATYPE.FLOAT32 type to get the values as floats and send them directly to Arduino.

Changing the C# Code to send the data in bytes

Let's change the private void Simconnect_OnRecvSimobjectData(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA data) method. Instead of calling serialPort.WriteLine(...) we will be converting the values to byte arrays and sending them using serialPort.Write(byte[] buffer, int offset, int count).

// comment out so we don't send it as a string anymore
//serialPort.WriteLine($"{simvars.Altitude},{simvars.KohlsmanSettingHg}");

// first, create a buffer to put the data
List<byte> buffer = new();

// then lets add the two simvars. We will use BitConverter to get the floats as a byte array
buffer.AddRange(BitConverter.GetBytes(simvars.Altitude));
buffer.AddRange(BitConverter.GetBytes(simvars.KohlsmanSettingHg));

// now, lets add our newline to specify the end of our message
buffer.Add((byte)'\n');

// finally, lets write the message to the serial port
serialPort.Write(buffer.ToArray(), 0, buffer.Count);

That's it. We created a byte buffer, we converted both SimVars to byte arrays using the utility class BitConverter, while adding them to our buffer. We added our newline to specify the end of our message and we wrote the bytes to the serial port. Our message size should always be 9 bytes, that is:

  • 4 bytes - for the Altitude
  • 4 bytes - for the Barometer Setting
  • 1 byte - for the Newline Character ('\n')

Changing the Arduino Sketch to read and convert Serial bytes

To read bytes from the serial port, we will be using the Serial.readBytes(char* buffer, int length). We will then use memcpy(float* value, char* buffer, int size) to literally copy the 4 bytes we have in the buffer into the object's memory. This will reconstruct the float value we sent from the C# app. Finally we will send a message back to see if all of this worked:

void setup() {
  // initialize the port and set the baud rate to 115200, change to 9600 if you are having communication issues, but make sure it's the same rate as in the C# code
  Serial.begin(115200);
}

float altimeter;
float barometer;

void loop() {
  // check if we have at least 8 bytes. our full message should be 9 bytes,
  if (Serial.available() > 8) {
    altimeter = readFloat();
    barometer = readFloat();

    Serial.readStringUntil('\n');

    // send the data back through the serial connection
    Serial.print("Altitude: ");
    Serial.print(altimeter);
    Serial.print(", Barometer: ");
    Serial.println(barometer);
  }
}

// util method to read 4 bytes from the serial port and return them as a float
float readFloat() {
  char buffer[4];
  Serial.readBytes(buffer, 4);

  float value;
  memcpy(&value, buffer, sizeof(float));

  return value;
}

Lets upload the Sketch to the Arduino, and press on the Connect button in the Hello MSFS app. We can see that the data is being sent correctly and the new message format is being displayed with the correct Altimeter and Barometer Setting values:

Wrapping things up

In Part 1 we learned about the Serial Port, how to send and receive data from and to the PC using C# code and an Arduino Sketch in C++. We learned how to identify our Arduino devices using PID/VID, which can be used to maintain a list of connected devices and send the SimVar data to them, but for sake of simplicity, just hardcoded my COM port in the examples to give the reader the option of adding that logic with the WMI query snippet provided.

In Part 2 we extended the code we wrote that uses SimConnect SDK to subscribe to SimVars and send them to an Arduino device using the Serial Port object as a string, and we verified that the data was received correctly by sending the same string back and printing it in the Debug output window.

In Part 3 we modified the code again to send the data in their own native type by using BitConverter to convert the floats to byte arrays, build a byte buffer with both Altitude and Barometer Setting SimVars byte arrays and our newline terminator, and modified the Arduino Sketch to read the data from the Serial connection as bytes and convert them back to float types and rebuilt our message and send it back to the Hello MSFS app to visually verify that the data was received and converted correctly.

Now you have the building blocks on how to build your own instruments based on the SimVar data from Microsoft Flight Simulator 2020, and hopefully you can start building some kickass instruments and build up your sim cockpit!