DCS-BIOS: Multiple modules at the same time

DCS-BIOS vs HID is a topic I already discusses a while ago. Long story short, DCS-BIOS, compared to HID, offers a simpler implementation and the possibility of using data coming from a DCS module directly, such as the status of an gauge (e.g. the VVI of the Ka-50) or LED and lamps (for instance, the AP channels status of the same helicopter). HID instead, allows the peripheral to be seen by the PC as a normal joystick, not very different from the one we can buy from on Amazon or from the shelf of a store. This means that a device can be used not only with any DCS module, but with any other game.

Up to here, nothing new. HID seems a pillar of flexibility whereas DCS-BIOS is more tied and monolithic. This is not entirely true: multiple modules can take advantage of the powerful DCS-BIOS with the only drawback of the memory usage. How? Well, that’s entirely up to the coder.

DCS-BIOS: Flexibility of control

Switches and LEDs can be interfaced almost directly by means of DCS-BIOS. In one of the recent updates about Donkey’s pit, we built a small Controls Box to handle the AP channels of the Ka-50. The LEDs were controlled directly by means of the following simple code:

DcsBios::LED apAltHoldLed(0x1936, 0x8000, LED_ALTHOLD);
DcsBios::LED apBankHoldLed(0x1936, 0x0200, LED_BANKHOLD);
DcsBios::LED apFdLed(0x1938, 0x0200, LED_FLIGHTDIR);
DcsBios::LED apHdgHoldLed(0x1936, 0x0800, LED_HDGHLD);
DcsBios::LED apPitchHoldLed(0x1936, 0x2000, LED_PITCHHLD);

In the same article I mentioned that my Ka-50 TFT doesn’t use this type of direct access because data had to be managed or interpolated first. This is the type of functions I relied on:
void onApPitchHoldBtnChange(unsigned int newValue) {
/* your code here */
}
DcsBios::IntegerBuffer apPitchHoldBtnBuffer(0x1936, 0x1000, 12, onApPitchHoldBtnChange);

The two different functions allow to achieve similar object by means of different approaches. This is one of the great benefits of DCS-BIOS.

Handling multiple modules

There are several ways to solve the issue of supporting multiple modules with DCS-BIOS. The following are some simple suggestions, I focused on the less complex and it doesn’t mean they are the most efficient.

Situation: the following examples consider whereas the DCS module running is the Shark, the Hip or the Tomcat. Such examples can be further expanded to include any number of modules. The only limit is the quantity of memory available.
Let’s also assume that a single LED has to display the status of the Master Arm of the Ka-50 and the Hip and the MSL Prep indicator of the Tomcat.

Solution I: Continuous Update

Since most of the functions trigger when something changes, we can have a global variable working as a flag to identify which module is running.
In the following example I’m using pseudocode for simplicity’s sake; in C you can use an unsigned int or int, booleans and so on and even bit-masks, if you prefer (I used those a lot on PIC16).

You can have, for instance, a simple procedure that updates the current module flag or multiple procedures, one for each module. I’d rather use those than adjusting the global variable directly because it’s safer and more flexible: if at a later time I want to support additional modules, I won’t have to chase the use of such variable all over the code but, instead, I just have to update the procedures and functions that deal with it.

#define MODULE_SHARK 0
#define MODULE_HIP 1
#define MODULE_TOMCAT 2

int moduleID=MODULE_SHARK;

/*the following procedures update the current module*/
void setModuleShark(){moduleID=MODULE_SHARK;}
void setModuleHip(){moduleID=MODULE_HIP;}
void setModuleTomcat(){moduleID=MODULE_TOMCAT;}
int getModule(){return moduleID;) /*if you want to do additional operations on the moduleID*/
/****************************************************/


/*DCS-BIOS functions and procedures. Each handles a specific function of the panel and updates the module in use at the same time. The names of the functions are made up, use the proper name from the documentation of DCS-BIOS.*/
void onMasterArmKa50(unsigned int newValue) {
setModuleShark();
/* additional code that handles the Master arm of the Ka-50*/
}DcsBios::IntegerBuffer onMasterArmKa50(0x1234, 0x0000, 1, onMasterArmKa50);

void onMasterArmHip(unsigned int newValue) {
setModuleHip();
/* additional code that handles the Master arm of the Hip */
}DcsBios::IntegerBuffer onMasterArmHip(0x2345, 0x0001, 2, onMasterArmHip);

void onMSLPrepTomcat(unsigned int newValue) {
setModuleTomcat();
/* additional code that handles the MSL Prep light indicator of the Tomcat */
}DcsBios::IntegerBuffer onMSLPrepTomcat(0x3456, 0x0002, 3, onMSLPrepTomcat);

This pseudocode assigns a new value to the current module variable every time a function triggers. The idea behind it is that, since DCS-BIOS functions are related to a single module, methods and procedures about a module not in use won’t trigger. This solution is updated constantly, which is inefficient performance-wise, but allows to jump between modules any moment and your device will adapt automatically. This may come handy in a multiplayer session with multiple assets available.

Something that should be carefully considered is how to update the device: you can have an updater running in the loop or update when the DCS-BIOS functions trigger, or even something in-between. If your devices is organized homogeneously, a direct access to the LED or area of the TFT is probably faster and more efficient.
As I said already, the goal of this pseudocode is giving you ideas and suggestions. The optimal solution depends on your device and the result you want to obtain.

This example uses the first approach because is simpler to implement.

void updater(){
[..]
updateLED0(){};
}

void updateLED0(){
switch(moduleID){
case MODULE_SHARK:
case MODULE_HIP:
/* handle LED/TFT/etc */
break;
case MODULE_TOMCAT:
/* handle LED/TFT/etc */
break;}
}

Solution II: Set module on boot

You can select the module you want to use right after powering up the device. Simply define a number of buttons of your choice and use them to select the module, first thing after having plugged your device. This solution has the advantage of avoiding the constantly set of variables at the expenses of flexibility.

Solution III: Dedicated switch

A dedicated multiple switch, can be used to select the module in use. De facto it would set only a variable, and then work as the Solution II.

Solution IV: TFT touchscreen

Similar to the Solution III but more efficient is the use of the touchscreen, available on most TFTs. By tapping on the screen you can circle between different modules. I did it for test’s sake for the Ka-50 and the Mi-8. It was quite handy.


I did a brief example of multi-module management by displaying on the TFT a different value read from the VVI of the Ka-50 and the Mi-8. Depending on the module in use, the value would update or not. The following is the full code of the test, as you can see the initialization is actually longer than the functions themselves.


/*
* Karon's TFT Multimodule test
* last upd 14/09/2019
*/

//#define DCSBIOS_IRQ_SERIAL
#define DCSBIOS_DEFAULT_SERIAL

#include "DcsBios.h"

#include // Core graphics library
#include // Hardware-specific library

#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0

#define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define GREY 0x6B4D
Elegoo_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

/**********************/
/*** DCS-BIOS CALLS ***/
/**********************/

/***** Ka-50 *****/
void onVarioSpeedChange(unsigned int newValue) {
shark_VVI(newValue);
}DcsBios::IntegerBuffer varioSpeedBuffer(0x184e, 0xffff, 0, onVarioSpeedChange);
/*****************/

/***** Mi-8 *****/
void onVariometerLChange(unsigned int newValue) {
hip_VVI(newValue);
}DcsBios::IntegerBuffer variometerLBuffer(0x26cc, 0xffff, 0, onVariometerLChange);
/*****************/

/**********************/
/**********************/

void setup() {
DcsBios::setup();
tft.reset();
uint16_t identifier = tft.readID();
identifier=0x9341;
tft.begin(identifier);
tft.setRotation(3);
tft.fillScreen(BLACK);
tft.setTextSize(2);
tft.setTextColor(WHITE);

initDCS();
}

void loop() {
DcsBios::loop();
}

//******************************************************************************************/
//******************************************************************************************/

void initDCS(){
tft.setCursor(0,0);
tft.print("Ka-50");
tft.setCursor(0,100);
tft.print("Mi-8");
}

void shark_VVI(int v){
tft.fillRect(100,0,100,50,BLACK);
tft.setCursor(100,0);
tft.print(v);
}

void hip_VVI(int v){
tft.fillRect(100,100,100,50,BLACK);
tft.setCursor(100,100);
tft.print(v);
}


I hope you have found some interesting ideas from this brief article. If you have suggestions or comments, feel free to share them!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.