Tool to connect Serial USB devices to MSFS2020 via SimConnect/WASM using VS2022

In the beginning of 2022, I wrote a tutorial on how to develop your own implementation to connect with SimConnect and WASM using Visual Studio 2019. I wrote this tutorial because I had to spend a lot of time to find decent documentation about the topic, so why not share my findings with the community. For people that have no experience with SimConnect and WASM, I strongly recommend to read this post first.

I have now used the basics of that tutorial to develop a complete new tool that allows to easily connect hardware that supports Serial USB (example: Arduino) with MSFS2020. In the meantime, I’m using Visual Studio 2022 as well. You can find the tool on GitHub here. In the following weeks, I will explain all the details in several chapters as I did in the previous post.

EDIT 21/Jan/2024:
In the meantime, a new and better version CockpitHardwareHUB_v2 is released on GitHub. This version is a complete rewrite of the first version, and a lot more rubust. Devices that are working with the first version will work with this new version, as the protocol and concepts haven’t changed. This new version uses a different WASM module (WASimCommander developped by mpaperno). A folder with all executables can be found in the repository. And this time, the GitHub repository also contains a full user manual that explains the protocol.

I decided to put this post in the “Home Cockpit Builders” section of this forum, because that is definitely the target audience. My own goal is to build my own A320 cockpit, and I will use this tool to connect with MSFS2000, so I think that this section is the best fit.

The tool is also not ready. Currently it supports A-vars, L-vars and K-vars, but will be expanded in the future for other types of variables.

Some people might wonder: “Why do we need another tool? There are already many (free) tools such as MobiFlight.” Well, there are a few reasons why I developed my own tool. First of all, because writing software is part of my hobby. Secondly, tools like MobiFlight are great because you don’t have to know anything about writing code yourself, but that is also the limitation. Although these tools have a massive amount of possibilities, you always will have to stick to the concepts provided in such tool. My tool just provides some basic communication protocol between a Serial USB device and MSFS2020, and some methods to define the variables you want to control. But you have to write your own code to use it. It means that you are also not limited to Arduino - in fact, I’m using PIC Microcontrollers to develop my own hardware. So basically, my tool is meant for a different public, but at the end it’s all with the same goal in connecting buttons, rotary encoders, LED’s, LCD displays, etc… to MSFS2020.

On GitHub, I have provided 2 extra folders in the repository.

  • Executables: In my previous post I had the question to provide at least one version of executables that can be used immediatly, without having to setup the whole enviornment in Visual Studio. The Executables contain both the WASM-module and the tool itself. The folder WASM_HABI needs to be copied in the MSFS Community folder before starting MSFS2020. In my previous tutorial, I explain how you can find this Community folder. Look here, and search for “Use our first WASM module”. Do not create the folder, but copy the folder “WASM_HABI” from the Executables in the Community folder. The other files “CockpitHardwareHUB.exe, Microsoft.FlightSimulator.SimConnect.dll and SimConnect.dll” need to be copied in a folder on your computer. The tool can then be launched by starting “CockpitHardwareHUB.exe”.
  • ArduinoExample: This is some example code that can be used on an Arduino Mega 2560. I might provide the schematic of this example later in this post. It’s uses the FlyByWire A32NX (my favorite!), and allows controlling the EFIS Left CSTR and WPT (PushButton and LED) and shows the HDG on an 7-Segment Display together with a Rotary Encoder to change the value.

Finally, please be aware that I’m not a professional Software Developer. I’m sure that the coding style can be improved, and that I might not use all the wistles and bells that the C# and C++ language provide. Do not hesitate to suggest improvements, give comments or make recommendations in this post. Although, be aware that this is not a C# or C++ forum.

8 Likes

Chapter 1: Introduction to the Cockpit Hardware HUB (in short “HUB”) and how to use it

Features:

  • Connecting with 1 or more Serial USB devices. The tool supports “hot connect/disconnect”.
  • Real time monitoring of Serial USB device communication (CmdRxCnt, CmdTxCnt, NackCnt)
  • Test function giving an indication of communication speed with the Serial USB devices
  • List of all variables that a Serial USB device has registered
  • Use of “execute_calculator_code” on the fly. Very handy to test out commands before implementing them.
  • Send Commands to Serial USB devices and/or MSFS2020. This includes registering variables (more details further below) and setting their values.
  • List of all registered variables with their real time values. The list can be filtered.
  • Logging with 3 levels (High, Medium, Low). The log can be filtered, cleared and disabled.

Concept of the tool:

The principle is that if a Serial USB device connects, it tells which variables it is interested in. These variables are then validated and registered with MSFS2020. This concept allows each hardware module individually to decide which variables it needs.

Each variable gets a unique 3 digit ID in the tool. This means that currently a maximum of 999 variables can be registered (000 is not supported).

Once the unique ID is known, it can be used to control the variable. In the above screenshot, the command “003=1” will set the value of the L-var A32NX_EFIS_L_OPTION to the value 1 (CSTR). The advantage of using a unique ID is that the command becomes a lot shorter (less communication and easier to use).

The core functionality of the Cockpit Hardware HUB is basically parsing the variable string that the Serial USB device wants to register. This means checking the syntax, and validate the registration of the variable. This makes use of all the technologies as described in my earlier tutorial, which means making use of SimConnect and my own WASM module. The GitHub repository contains the source code of both the SimConnect interface, the WASM Module and the module that controls the connection and communication with the Serial USB devices.

Format of a variable

To register a variable, the below structure has to be used.

image

  • The first part is the type used to send and/or receive values. VOID is used for variables that do not send nor receive any values, such as Events.
  • A variable can be Read only, Write only or Read/Write. This will restrict the usage of setting/getting values. You can not set the value of a Read Only variable, and can not get the value of a Write Only variable. The ability to define Write Only variable also avoids that the SimConnect module is continuously listening to the variable value, which would be a waste of resources. In case of a VOID variable, the R/W/RW has to be omitted, because you can’t get or set values anyway.
  • Currently, only A, L and K type variables are supported. X-type variables are my own invention, and are variables that are based on “execute_calculator_code” (more details below).
  • The variable body can contain any valid variable name that is used in MSFS2020 and/or the add-ons.
  • The unit is only needed if required

Here are some examples of variables that can be used with the FlyByWire A32NX:

image

Example 1: Registering and controlling an A-var

My examples always use the free FlyByWire A32NX add-on which you can find here. But in case of A-Vars the default A320 could be used as well. Make sure that MSFS2020 is started, and that the A32NX is at the start of the runway with engines running. You can use the tool without connecting it to Serial USB hardware.

Enable the logging. You can leave it on Log Level “Low”.

Connect the tool with MSFS2020 by pressing the Connect button:

image

A successful connection will be indicated by the tool. The Connect button will change into a Disconnect button.

image

The log will show some information about the connection.

Don’t worry about the “ALREADY_CREATED” messages - this is because the tool tries to recreate some Client Areas that the WASM module has already created.

Now enter the below string in the Command line and press “>MSFS”.

This will tell the tool to register the variable with MSFS.

  • Variable type is FLOAT64 (you could also have used INT64 or INT32, which can hold the altitude value)
  • Variable will be Read/Write, which means we will receive real time values from MSFS, but we will also be able to set the value of the variable
  • Variable is of type A, and has the name AUTOPILOT ALTITUDE LOCK VAR:3
  • The unit used is feet

If all goes well, you should see the below in the Variables list:

It means that the variable has got the unique ID “001”, which can now be used to set the value. The Value is also updated in real time. You can experiment with this by changing the Altitude in the airplane by turning the knob. You will see the value changing immediately. If you have many variables, you can enter some text in “Filter” (example, the variable name).

When you want to change the Altitude, you enter the below in Command and press “>MSFS”.

Now a few things will happen. The tool will send the new value to MSFS through SimConnect. The value will change in MSFS2020, and because of that, SimConnect will also receive the new value. Because we are listening to that variable (RW). The tool will update it’s value in real time.

Goto Chapter 2: Some more examples

3 Likes

Hi @HBilliet - I appreciate your effort and enthusiasm and I am happy you know there is MobiFlight which is open source. In the beginning I wasn’t sure why you’re re-doing something similar but I missed your explanation about your motivation - ooops, my bad!

One additional point:
MobiFlight, despite its flexibility, as you said will have some limits. However actually one thing you can do in such case is that you can grab the source code and extend it with something that’s missing. You can then either use it for yourself or even share it with the community or contribute back to the MobiFlight project.

In any case, Good luck with your learning and sharing your insights.

Feel free to join the community and contribute your passion to the project. I feel it’s kind of a waste of energy to recreate the wheel.

@MobiFlight I think I clearly explained this in the beginning of my post?

I know MobiFlight, and I worked with it myself, and I have learned a lot with analyzing your source code as well (I even mentioned this very clearly in my previous post as well - all credits to you guys!). My tool is meant for a different public, that 1) wants to write the software in the devices themselves and 2) wants to use other devices than Arduino. Although, if you think that I have wrongly stated that this is not possible with MobiFlight, then please let me know, and I will obviously correct/remove this statement. And for all clarity, reason I mentioned MobiFlight is that this is the only other tool I really used my self, and I can only say that it is a great tool to work with!

1 Like

Hi @HBilliet - I don’t know how i missed that paragraph where you’re mentioning Mobiflight. Sorry for any confusion. Offer still stands - feel invited to contribute to the MF project with your ideas.

Chapter 2: Some more examples

Example 2: Registering and controlling an L-var

In the UI, registering an L-var is the same as registering an A-Var. But behind the scenes, L-vars are using the WASM module (for more details, have a look at my previous post here).

The WASM module will take care of the registration, and if successful it will send back an acknowledge package that contains all the necessary information such as the offset where in the Client Data Area the variable is stored. In case you would launch the Cockpit Hardware HUB a second time and connect it with MSFS2020 (example from a different PC), and register for exactly the same variable, then the WASM module will send back the same information. This avoids that the same variables are being registered multiple times, which consumes Client Data Area space and which also makes the WASM module spend time to monitor the same variable multiple times, which is not effecient.

We know that the maximum Client Data Area size can only be 8192 bytes. The tool will automatically create additional Client Data Areas if it needs more space. A maximum of 10 Client Data Areas are supported, which means a total of 81920 bytes for the L-vars. There is currently no protection if you exceed that limit, except that you will get an Exception in the tool. I might improve this in the future.

Remark: At the time of writing of this post, I haven’t fully tested the multiple Client Data Area feature (I just forgot :flushed:). This means that exceeding 8192 bytes might not work yet. Once I have tested it, I will remove this remark.

For this example, I want to register a variable that shows the HDG of the FlyByWire A32NX.

Let’s assume that we don’t have a clue what the name of the variable is. We can use several resource to find out. One obvious source is the FlyByWire website, which has a section that shows all the panels used in the cockpit. The HDG is on the FCU Panel that you can find here. There we see that the variable name we need is A32NX_AUTOPILOT_HEADING_SELECTED. This is an L-var, and it is Read Only.

If we want to know the unit, we can use a FlyByWire GitHub repository that contains more details about the variable. There we find out that the unit is “Degrees”, and in case the value is -1, we should show the managed mode which is “—”.

image

Before we register a variable, we can make use of the “execute_calculator_code” feature to check if the above is correct. Let’s tune the HDG to a certain value (example 100), and then check the value:

Looks like our variable is working. Now we can register for it. We can use an INT32, and the variable is Read Only.

After pressing “>MSFS”, the variable is registered, and we can now monitor it in real time.

Because it is a Read Only variable, we can’t change it with the command “001=[value]”. If you put the Log Level on “High”, you can see an error message if you try to send for example “001=250” to MSFS.

Remark: I might still tune the logging levels a bit. I think that errors like “Variable can not be set” should be visible if the Log Level is “Low”. So it might be that when you download the tool from GitHub, this has already been adapted.

Example 3: Registering and controlling a K-var without value

Let’s register a K-var this time. A K-var is an Event. In a lot of cases, an Event doesn’t need a value. That’s why we can register them as VOID which means we can also omit the Read and/or Write indications.

The knob to Increase or Decrease the HDG value on the FCU Panel can be controlled via a “Custom Event”. We see in the FlyByWire documentation that the event name is A32NX.FCU_HDG_INC and A32NX.FCU_HDG_DEC. The fact that the Event starts with the name of the addon followed by a dot makes it a Custom Event. Native Sim Events do not have a dot in the name (example: FUELSYSTEM_PUMP_TOGGLE).

We can also check Events with the “execute_calculator_code”. Although we don’t have a value, we can “set” the value of the K-var, which is done by adding the “>” in front of the variable.

We won’t receive any value, which means that the Float, Int and String values are all showing 0.

Now we know that the variables are working, we can register them.

Because there is no value, the Variables screen will show “N/A”.

Now we can use the variables to Increase and Decrease the HDG value. We do that by using the command “002” and “003”. We can also use “002=” and “003=”, but strictly spoken we don’t need the “=” because there is no value to add.

Example 4: Registering and controlling a K-var with value

Some K-vars can take a value. The earlier mentioned K-var FUELSYSTEM_PUMP_TOGGLE is a native Sim-Event that takes the Fuel Pump number as parameter. We can also start with the FlyByWire documentation here to find more info.

Because this is an MSFS EVENT, the SimConnect SDK Documentation also contains some information.

An Event can only take a “DWORD” as parameter, which in our tool translates in an INT32. The tool is protecting against registering with wrong variable types, which means that for K-vars it will only accept VOID or INT32.

We can again use the “execute_calculator_code” to test the variable before we register it. As we could see in the documentation, the fuel pump value can be a value from 1 to 6. The below command toggles the first Fuel Pump. We can see the result in the OVHD panel, where Fuel Pump 1 is switched off.

image

We can now register the variable. Because this is an Event, which doesn’t return any values, we register it as Write Only, and we will see “N/A” as returned value.

We can now toggle the 6 fuel pumps with the command “004=1”, “004=2”, …

Remark: Be careful not to switch off all Fuel Pumps if you have started the plane at the beginning of the runway with engines running. While I was testing, I did this as well. But once the last fuel pump is switched off, you cut off the power of the plane, with the effect that the cockpit goes dark. Took me a few minutes to realize that this was not a bug or a malfunction :rofl:

Goto Chapter 3: Customized variables using X-var

Chapter 3: Customized variables using X-var

I thought that X-vars don’t exist. Later I realized that I’m wrong. If discovered in the documentation about the RPN notation that X is an existing Variable prefix.

I didn’t change my implementation (yet). In my tool, X-vars are variables that are executed with “execute_calculator_code” in the WASM module. This allows to make a kind of “macro’s” that even can combine more variables or create complex structures using RPN notation.

An example of an X-var is:

1 (>L:A32NX_EFIS_L_OPTION,enum) 1 (>L:A32NX_EFIS_R_OPTION,enum)

This sequence is setting both the left as right EFIS on CSTR.

In the meantime, you know the drill. We can first test the RPN string with the “execute_calculator_code” to see if it works. If it does, we can register the variable. Because we don’t need a value, we can use a VOID.

Now we can enable both EFIS_L and EFIS_R to CSTR (1) with one simple command “001”. Because we use VOID, we even don’t need to add the “=”-sign.

Using a value with X-var

X-vars support VOID (as in the example above) and INT32. Reason that other variable types are not supported is that an X-var is also using an Event, which only supports DWORD as parameter. The below example registers an X-var with an INT32 as a Write Only variable which allows you to set the EFIS_L. Using the command “003=5” sets it to ARPT (5).

Just be aware that the value is just been put in front of the X-var. This means that in the WASM module, the “execute_calculator_code” is executing the command:

5 (>L:A32NX_EFIS_L_OPTION,enum).

Remark 1: Even if we use VOID, the X-var will always be preceded with the value 0. That means that in the first example, the command sent with “execute_calculator_code” is:

0 1 (>L:A32NX_EFIS_L_OPTION,enum) 1 (>L:A32NX_EFIS_R_OPTION,enum)

The reason is that we use an Event to trigger the X-var, and the Event has always a DWORD parameter, which in case of VOID is just set to 0. But there is no way on the receiving side in the WASM module to know that this 0 should be ignored or not. I might improve this in the future, but the above 0 doesn’t really harm.

EDIT 05MAY2022: Adding 0 parameter in case of VOID has been solved. Actually, the WASM Module does know whether the parameter should be used or not, because it knows that it is a VOID variable. So in case of VOID, we just ignore the parameter. New source files pushed on GitHub.

Remark 2: My tool is converting to uppercase, but this is a bad idea, so I will need to change that. Reason is that RPN is case sensitive, and some operators are small cases. I tried for example to create the command:

INT32_W_X:d (>L:A32NX_EFIS_L_OPTION,enum) (>L:A32NX_EFIS_R_OPTION,enum)

The intention is to use a command like “004=3” which would then set both EFIS_L and EFIS_R to the value 3 (cfr. RPN notation documentation - “d” duplicates the stack). But when executed in the WASM module, the command is converted to uppercase, and “D” is not recognized as RPN stack operator.

EDIT 05MAY2022: Case sensitivity issue has been solved. New source files pushed on GitHub.

Avoid using “R” with X-vars that also write values

If you use “R” when registering an X-var, then the WASM Module will continue poll for this variable using “execute_calculator_code”. If in the same RPN code you also set a value, then this is also continuously executed.

Example: INT32_R_X:3 (>L:A32NX_EFIS_L_OPTION,enum) (L:A32NX_EFIS_L_OPTION,enum).
This will first set the value 3 to EFIS_L, and then read EFIS_L. Because of the Read, the variable is continuously executed with “execute_calculator_code”, but that also means that the EFIS_L is also continuously set to 3. This means that if in the below example, you use command “003=5”, this will set the EFIS_L to ARPT (5), but immediately after that it will jump back to WPT (3).

This is also the reason why you can not use “RW” with an X-var. It does not make sense to combine both a read and write action in the same X-var.

Goto Chapter 4: Connecting with Arduino

Chapter 4: Connecting with Arduino

On GitHub, you will find an example Arduino sketch. This sketch is based on the below connection diagram which is meant for a MEGA 2560 R3:

The example is meant for the FlyByWire A32NX. As the labels on the drawing already indicate, the demo controls the following:

  • EFIS_L CSTR (button to control + LED to show if active)
  • EFIS_L WPT (button to control + LED to show if active)
  • 3 digits of 7-Segment display (based on MAX7219) to show the HDG
  • Rotary Encoder to INC/DEC the HDG

Below I will explain step by step how to run the example, and what is happening behind the scenes.

1. Start the sim

Launch MSFS2020. Make sure that “Developer mode” is enabled (From main menu, select Options/General Options/Developers and set Developer Mode ON). Select a flight with the FlyByWire A32NX (this is a free add-on that needs to be installed separately) and select the start of a runway as Departure. Once the plane is started with the engines running, press “CTRL-2” to show the Glareshield/FCU. You should get a view like below.

Use menu “Window/Console” to show the console window that will give some more info about the WASM module. Best is to filter “habi_wasm”, which will only show the messages generated by my own WASM module. If you have copied the WASM folder from GitHub in the MSFS Community folder, then the WASM module should start automatically, which will be shown in the Console window.

Remark: Version in screenshot could differ from the pre-built one on GitHub. If you rebuild the source code of the complete VS2022 solution, you should have the latest version.

2. Connect with Arduino

First power the Arduino and connect it with USB. When powering up, the 7-segment displays will probably flash on for a fraction of a second, and then dim completely.

Launch the Cockpit Hardware HUB and enable the logging. Then press the Connect button. A lot of things are happening at once now.

  • The tool will first connect with MSFS2020 and start some processes to interact with SimConnect and WASM
  • Next the USB modules that are already connected will be detected, and the tool will attempt to communicate with them. It’s very common that Try 0 will fail with a TimeoutException, but Try 1 should be successful. The tool will do a maximum of 20 trials, which in case of multiple USB devices will all run asynchronously.
  • If a connection is established, the first command sent to the device is “IDENT”. The device will respond with a “DeviceName” and a “ProcessorType”. This is only informative, and is displayed in the “USB Device” field of the UI. In the screenshot this is “FCU A32NX” and “ARDUINO”.
  • The second command that is sent to the device is “REGISTER”. That tells the device to send all the “Variables” it requires to the tool, terminated by an empty string.
    In the Arduino sketch, I store the variables in a const char* array, which makes it easy to keep track of the number to be used later on - see below.
// variables (variables start at 001
const char *Variables[] = {
  "INT32_RW_L:A32NX_EFIS_L_OPTION,enum",                // 001
  "VOID_K:A32NX.FCU_HDG_INC",                           // 002
  "VOID_K:A32NX.FCU_HDG_DEC",                           // 003
  };
  • Once the variables are received by the tool, it will register them one by one. This is exactly the same as if you would do it manually by entering them one by one in the Command line, and press the button “>MSFS”.
    The result can be seen in the “Variables” window. You can also see the list of registered commands per Serial USB device on the left side of the UI.
  • During registration, the tool is going to send the current value of the variable back to the device. It does that by issuing the commands “[UniqueID]=[value]”. This is again the same as if you would enter this command manually in the Command line, and press the button “>Device”.
    The UniqueID is not the same as the number used in the device, which we call DeviceID. When a value from MSFS is sent to the device, the command “[UniqueID]=[value]” is translated in “[DeviceID]=[value]”. The opposite is also true, when the device sents a command “[DeviceID]=[value]”, the tool will translate it into “[UniqueID]=[value]” before processing towards MSFS.
    The “DeviceID” is the order in which the variables are sent for registration, starting from 1. This is the same order as the array in the Arduino sketch. Once the variable is registered, it gets a UniqueID that is used by the tool. Both are stored together with the registered variable.

3. Using the hardware

Now we can use the hardware. If you click on the CSTR pushbutton, and you would connect a serial port monitor on your Serial USB port, you would see the below:

image

First, the command “001=1” is sent from the device to the Cockpit Hardware HUB. 001 is the ID of the first variable in the Arduino. In the Cockpit Hardware HUB, this is translated in the “Unique ID”, which is 003. That’s why if you put the LogLevel on High, you will see the line:

image

This command is then processed by the tool and is being sent by SimConnect via the Client Data Area to the WASM module (because it’s an L-var) who will then translate it in a command being sent to the FlyByWire A32NX using a set_named_variable_value from the Gauge API.

If all goes well, the result should be that the CSTR indicator of the EFIS_L on the FCU should lit up. Because of the change in indicator status, the value change will be sent through the Client Data Area back to SimConnect and will trigger the UI to show the value 1 for that variable (because it is RW). This value change of the variable with UniqueID 003 will also be sent to the Device with command “003=1”. The tool is going to translate the UniqueID in the DeviceID, which means that the command “001=1” is sent on the Serial USB (which can be seen in the screenshot).

The Arduino will receive this command and the CSTR LED will lit up. The Arduino will also send back an Acknowledge to the tool, which is simply the character “A” followed by a newline. This to indicate that the command has successfully be received.

Remark: All commands sent from the tool to the Arduino are being acknowledged by the Arduino by sending back the sequence “A\n”. If an Acknowledge is not received within 150 msec, the command is sent a second time. If that fails again, the tool gives up and goes to the next command. The next command will only be sent to the device after an acknowledge is received. This way of working avoids that the commands from the tool are sent too fast.
There is no acknowledge protocol when commands are sent from the Arduino back to the tool. I have not implemented this, but this is not that dramatic. First, the Arduino will only send commands if you take some action (push a button, dial an encoder, etc…) which is not 100 times per second, so the tool will definitely be capable of catching up. Second, if it really would happen, then in this very exceptional case, you will notice that your action has no effect, and you will try again.

We can now also use the other button, and the Rotary Encoder. Below is a picture of the example at work.

image

4. Debugging your hardware

You can also debug your hardware without using the Cockpit Hardware HUB. You can use the “Serial Monitor” of the Arduino IDE, and enter the commands yourself to see the effect.

If you type “IDENT” followed by enter, you will see the below:
image

If you then type “REGISTER” followed by enter, you will see the list of variables that need to be registered:
image

If you now turn the Rotary Encoder UP or DOWN, you will see the commands “002=” or “003=” (These are UP and DOWN events, and don’t need a value).
image

If you type the command “001=1”, you will see the CSTR LED lit up, and you will also see that the device returns the sequence “A/n” which is the Acknowledge being sent back.

Goto Chapter 5: Example of using X-var

This is really cool. I use Mobiflight for all my stuff but would really like to learn coding. I made some modifications to some of the Mobiflight files in order to make a DIY FFB rudder pedal. The pedals work well, but I really don’t know what I’m doing. I’ve downloaded several FFB libraries and Simconnect tools, yet still don’t have clue.
I’m hoping to read through your posts and learn more about programming and using Simconnect. Thanks for all the time and effort you’ve put into this. I will be starting with your original tutorial before moving on to this one.

@JCSLOVE If you want to learn more about SimConnect, then I suggest that you first read my other post SimConnect + WASM combined project using VS2019 - SDK Discussion / Tutorials - Microsoft Flight Simulator Forums. There I try to explain all the basics first. The tool and source code provided here makes use of these basics.

I’m on chapter 2 and have just opened Visual Studio 2019 for the first time. So exciting…

Are VS2019 and VS Code interchangeable?

Chapter 5: Example of using X-var

This is an example of when you can make good use of the X-var in my tool. Let’s examine the below example variable definition to set the EFIS_L_OPTION:

INT32_W_X:s0 (L:A32NX_EFIS_L_OPTION, enum) == if{ 0 (>L:A32NX_EFIS_L_OPTION, enum) } els{ l0 (>L:A32NX_EFIS_L_OPTION, enum) }

You can use the same syntax for the EFIS_R_OPTION:

INT32_W_X:s0 (L:A32NX_EFIS_R_OPTION, enum) == if{ 0 (>L:A32NX_EFIS_R_OPTION, enum) } els{ l0 (>L:A32NX_EFIS_R_OPTION, enum) }

This will create variables which you can then use to set the EFIS_L_OPTION or EFIS_R_OPTION. Below screenshot shows both registered X-vars as variables 004 and 005.

But why using this complex X-var, and not simply use the L-var that was already created in our previous example (see variable number 003):

INT32_RW_L:A32NX_EFIS_L_OPTION, enum

The reason is that for using command “003=[param]”, we first need to determine the parameter based on the current status of the EFIS_L_OPTION. This will require some coding in our device, and the result will be sending the command “003=1” or “003=0” (if we want to set/reset CSTR).

The advantage of using the X-var is that we I can now use the same command “004=1” in all cases. This will set or reset CSTR depending on the current value of CSTR. This makes it extremely easy to connect this with a simple button in your Hardware, which always will send “004=1” - no coding required. The same applies to setting/resetting the other EFIS_L_OPTION values WPT (004=3), VOR D (004=2), NDP (004=4) and ARPT (004=5).

X-vars make use of execute_calculator_code in the WASM module, and this makes use of strings written in RPN notation. Some basic knowledge of RPN is required to make use of the full potential that X-vars offer. Let’s dissect the command in more detail.

Let’s use the parameter 1 to set CSTR of EFIS_L_OPTION. In below explanation, “L-var” is just a shortcut for “L:A32NX_EFIS_L_OPTION, enum”. The string that is executed could look like below:

1 (L-var) == if{ 0 (>L-var) } els{ 1 (>L-var) }

  • 1 (L-var) ==
    This compares the value 1 with the value of the L-var. The result can be TRUE or FALSE. You can even try this in the “execute_calculator_code”, and depending on the value of your L-var, you will see 0 (false) or 1 (true) as return values.
  • if{ ... } els{ ... }
    If the previous statements resulted in TRUE, then the first { … } is executed. If FALSE, the second { … } is executed.
  • 0 (>L-var)
    This sets the L-var to 0 - this means that if CSTR was set, it will now be reset
  • 1 (>L-var)
    This sets the L-var to 1 - this means that if CSTR was not set, it will now be set

So basically, I’m checking if the current value of the L-var is equal to the required value (1). If it is, I reset it to 0. If it is not, then I force the value to the required value (1).

But the above only works for the value 1. If I want to use the value 2 (VOR D), I should use:

2 (L-var) == if{ 0 (>L-var) } els{ 2 (>L-var) }.

A more common approach would be to write this as follows:

param (L-var) == if{ 0 (>L-var) } els{ param (>L-var) }

We now have to find a way when I use the command “NNN=X”, the 2 occurrences of param in the string are replaced with X. But the problem is that when I give a parameter to our command, this is only going to be added in FRONT of the variable. We should find a way to store the param somewhere, and then re-use it later in the string.

This can be done by using the RPN commands sN and lN. (be aware that the second command is the small caps “L”). Explanation of these commands can be found in the RPN documentation.

Now we can fully understand the string:

s0 (L:A32NX_EFIS_L_OPTION, enum) == if{ 0 (>L:A32NX_EFIS_L_OPTION, enum) } els{ l0 (>L:A32NX_EFIS_L_OPTION, enum) }

If we use the above with the command “NNN=4”, then in the WASM module, the following string is used with “execute_calculator_code” - the parameter 4 is added in front of the string:

4 s0 (L:A32NX_EFIS_L_OPTION, enum) == if{ 0 (>L:A32NX_EFIS_L_OPTION, enum) } els{ l0 (>L:A32NX_EFIS_L_OPTION, enum) }

First the 4 is stored in the internal register 0 using command s0. But the 4 stays on top of the stack, and is then used to be compared with the current value of EFIS_L_OPTION. If this is TRUE, then EFIS_L_OPTION is set to 0 (switch off). If this is FALSE, then first the stored param value is loaded from register 0 using command l0, which gives us the value 4, and then EFIS_L_OPTION is set to that value.

You can use the above string in the “execute_calculator_code” of the tool, and then change the 4 by a value between 0 and 5 and experiment with it. Below uses the value 2.

By the way, I haven’t invented this string myself. In my earlier post (see here), I showed a way how you use the “Behaviors” window to get some good examples of RPN strings. These are used by the developers of FlyByWire themselves. The above is exactly how they implemented the push-buttons of the EFIS_L_OPTION and EFIS_R_OPTION.

WARNING: Make sure you don’t exceed 256 bytes for the strings used to register variables.

Goto Chapter 6: Connecting with PIC Microcontroller

I have switched from VS2019 to VS2022 without any problem. If I remember well, you might just need to make sure that your target platform is set to x64, and you might have to enable both projects correctly (application and WASM), but for the rest, it seemed perfectly compatible. The complete transition, including downloading VS2022 and installing it, and then rebuilding my code after setting above correctly took me less than an hour.

I really suggest to switch to VS2022 as VS2019 is not supported any longer.

VS 2019 (and VS 2022) are the programs used to create and develop programs. VS Code is a text editor that is integrated to Microsoft programming languages and is mostly used to edit text files, if you don’t used the IDE (integrated Development Environment) GUI to edit your code in a project. ie. super developers that use the command line to compile. That is a simplistic explanation. So it’s up to you how you use or not use both.

Creating WASM projects is not really available for VS2022, there is no MSFS platform toolset available. Yes you can fudge it, but VS 2022 is 64 bit now (if you downloaded that version), so the 2019 toolset may or may not work correctly.

Thanks for your explanation. I was confused then - never used “VS Code” myself. I was only referring to VS2019 and VS2022.

All I can say is that I used my original VS2019 solution, and used in VS2022 without any issue. If I’m not mistaken, the new MSFS2020 SimConnect requires x64 target and doesn’t work for x32 anyway. Although, I do agree with you that different from VS2019, there seems not to be a specific “Microsoft Flight Simulator” platform when choosing C++ target code. By copying my earlier VS2019 code, all project settings seemed to be set correctly.

The SDK Documentation found here does indeed only mention VS2019. This means that if you ever going to build a WASM module from scratch, that some research might be required. Although, I have the feeling that the “standard C++ files” provided with the “Microsoft Flight Simulator” platform doesn’t give much specifics. I started them from scratch anyway. It might just be some specific settings for the compiler and linker that need some attention?

I’m not experienced enough to give correct advice here. So if other people can jump in, please do.

1 Like

Yes, that’s true for the WASM “server”. As far as I understand it, when you create a WASM project in VS2019, the template automatically creates links and references to the 64 bit SimConnect so you don’t even see it referenced specifically in the project.

However, you do not have to use a 64 bit SimConnect for the client to communicate with the WASM. In fact, Mobiflight uses a 32 bit SimConnect for its client and so do I in my own communicator, because I wrote it years ago, and didn’t want to go through all the hassle of rewriting all the other projects I have that depended on it, to x64

Chapter 6: Connecting with PIC Microcontroller

I like Arduino to make proto-types and do quick tryouts. But for the real work, my choice is the PIC microcontroller family from Microchip. There are a huge amount of different types, all having some specific hardware on board.

The one I’m using is the PIC18F45K50. One of the advantages of this type is that it has Crystal-less Full Speed (12 Mb/s) and Low-Speed Operation (1.5 Mb/s) USB on board. For prototyping, I’m using the 40-PIN PDIP package which can be easily put on a breadboard.

Software is developed using the MPLAB X IDE. You can download a free version, which doesn’t optimize the code, but which in my opinion is good enough for my cockpit hardware. I have invested in the MPLAB® PICkit™ 4 In-Circuit Debugger, which allows me to easily upload the code directly to the controller through an onboard programming interface (which implies that you give up 3 pins). But the additional advantage is that you also can do on chip debugging.

The MPLAB X IDE also offers a very powerful Microchip Code Configurator (MCC), which allows you to configure several features of the PIC microcontroller. This MCC generates the necessary code to use the USB, timers, interrupts, etc…

One of the main reasons I’m using the PIC microcontroller is that I only need 1 single chip with a few capacitors and I have a basic system. It’s as simple as that!

I quickly built the same configuration as I did with the Arduino in chapter 4. The difference is that in Arduino you can use C++, but in the free MPLAB X IDE I have to use C-language. But I don’t see this as a disadvantage, in the contrary. It gives me the feeling that I’m “closer to the hardware”, and have better control of the speed of my code.

The variables are stored in an array, and are sent to the Cockpit Hardware HUB when the “REGISTER” command is received.

const char *Variables[] = {
  "INT32_RW_L:A32NX_EFIS_L_OPTION,enum",                // 001
  "VOID_K:A32NX.FCU_HDG_INC",                           // 002
  "VOID_K:A32NX.FCU_HDG_DEC",                           // 003
  "INT32_W_X:s0 (L:A32NX_EFIS_L_OPTION, enum) == if{ 0 (>L:A32NX_EFIS_L_OPTION, enum) } els{ l0 (>L:A32NX_EFIS_L_OPTION, enum) }", // 004
  "INT32_W_X:s0 (L:A32NX_EFIS_R_OPTION, enum) == if{ 0 (>L:A32NX_EFIS_R_OPTION, enum) } els{ l0 (>L:A32NX_EFIS_R_OPTION, enum) }", // 005
  "INT32_R_L:A32NX_AUTOPILOT_HEADING_SELECTED,degrees", // 006
  };

The result can be seen in the below screen.

Be aware that the order of the variables in the device is not the same as the order in the Cockpit Hardware HUB. Example, the HDG_INC and HDG_DEC in the device are “002” and “003” (the DeviceID), but in the Cockpit Hardware HUB this is “001” and “002” (the MSFSID). The Cockpit Hardware HUB is doing the translation between DeviceID to MSFSID. Example, the command sent by the device to increase the heading is “001=”. The Cockpit Hardware HUB will translate this in “002=” which is sent to the SimConnect module. When the EFIS_L_CSTR is switched on, the SimConnect module in the Cockpit Hardware HUB will use the command “003=1”. This is translated in “001=1” when it is send to the device.

I connected the following hardware to the microcontroller (be aware that below commands are based on the DeviceID’s):

  • Rotary Encoder that will send the commands “002=” (CW) and “003=” (CCW)
  • 2 LED’s to show the EFIS_L_CSTR (“001=1”) and EFIS_L_WPT (“001=3”)
  • 2 push buttons to use the X-variables for the EFIS_L_CSTR (“004=1”) and EFIS_L_WPT (“004=3”)
  • MAX7219 based 7-segment display to show the HDG (“006=…”)

Although it is also included in the variable list, I’m not using the EFIS_R_OPTION in this demo.

I have built some definitions in the Microcontroller software that allows easy configuration of inputs (push buttons, encoders, …) and outputs (LEDs, 7-segment displays, …).

Example, for the encoders, I use the below structure:

typedef struct EncDef_t
{
//--To be initialized-----------------------------------------------------------
    uint8_t pinA;               // input pin connected with pin A
    uint8_t pinB;               // input pin connected with pin B
    const char* pCmdCW;         // command executed for CW
    const char* pCmdCCW;        // command executed for CCW
//--Internal variables----------------------------------------------------------
    uint8_t eState;             // current state of encoder
}; 

struct EncDef_t EncDefs[] =
{
//    pinA     PinB     pCmdCW                  pCmdCCW
    { pin_RB0, pin_RB1, CMD_HDG_INC,            CMD_HDG_DEC,             },
};
size_t nEncDefs = sizeof(EncDefs) / sizeof(struct EncDef_t);

Adding an encoder is simply done by adding a line in the array EncDefs[], which defines to which pins the encoder A and B pins are connected, and which commands are executed when the encoder turns CW or CCW.

When the controller software starts, an Initialize_EncCmds() is called that, based on the above definitions, configures the pins as inputs. In the main loop Process_EncCmds() is debouncing the pinA and pinB, and based on their states sends the CW or CCW command over USB.

The same principles are used for the buttons. A structure defines the pins and the commands executed (a command is defined when the button is pressed - another command can be defined when the button is released). The functions Initialize_KeyCmds() and Process_KeyCmds() do the rest.

I created some functions to put values on the 7-segment displays, where you can define the position and length. The implementation even supports chaining MAX7219’s. For the LED-outputs, I support direct LED’s but also using shift registers that can also be chained. The latter means that you only need a few pins to connect a large number of LEDs.

The result is below.

image

As you can see, the hardware is really minimal. Don’t be fooled by the add-on board on top, because this is only used to put power on the breadboard. I only use 3 small capacitors and one resistor to drive this microcontroller, including it’s USB interface that has a connector below at the right.

Currently the system is very responsive. Turning the encoder has immediate effect. I’m curious to see how the system will behave if I connect all the hardware of the A320 FCU to a single PIC microcontroller. I’ve done some experiments several years ago when I was still using the FMGS A320 on FSX. There I connected 4 encoders and a 2 x 8 digit display, including a lot of LEDs and switches, and the system still behaved very well.

I guess that this completes my software, which means that I now can continue on building my first A320 module which will be the FCU. The design is ready as you can see below. Wish me good luck! :slight_smile:

2 Likes

I noticed that the last post here was May 26th. Is this project still active?

I am a home sim builder currently using X-Plane 11. I have serious doubts about the future of X-Plane so I am hedging my bets.

My current sim is modeled after a Beechcraft 1900D and uses roughly 20 micro-controllers (Teensy 3.2 specifically, all driven by the TeensyDuino plugin for X-Plane)

I love this architecture because there’s no middleware. Just the plugin and the Teensys. I have looked at Mobiflight and while it is great for discrete I/O and steppers, it’s utility pretty much stops there.

The microcontrollers handle a myriad of functions. Mostly Discrete I/O but also driving real-world avionics. I have a pair of Garmin GMX-200 units that are driven by 1 teeny (Essentially just a moving map device tied to a Reality-XP Garmin 430 unit.

I have looked at a few MSFS2020 to Arduino programs but so far they are far from mature. I understand that the home cockpit world will require a few more years to catch up but I’m hoping to speed up that process. I’m a developer (SQL, C#, C++, some Python) and am about to retire at the ripe old age of 60.

…and I’m looking for a solid architecture to convert to from X-Plane without having to rewire/re-program my entire sim. I just want to upload some code for 2020 and go…

So, to that end…

  1. Is this project still active?
  2. Do you need/want help? :slight_smile: