Skip to content

Storing dataref identifiers for Teensy 2.0

14 November, 2012

There’s a problem I have with standard Teensyduino flight sim code. Here’s a slightly modified version of the GearPanel tutorial code:

#include <Blah.h>

////////////////////////////////////////
// Blah blah blah blah
//
const int blah = 45;
Bounce blahblah = Bounce (blah, 5);

////////////////////////////////////////
// Blah blah blah
//
const int blahblah = 10;
const int blahblah = 11;
const int blahblah = 12;

const int blahblah = 20;
const int blahblah = 19;
const int blahblah = 18;

////////////////////////////////////////
// Blah blah blah blah blah
//
// Blah blah blah
FlightSimCommand blahblah;
FlightSimCommand blahblah;

// Landing gear actual position
FlightSimFloat blahblahblah;
FlightSimFloat gearDeployNose;
FlightSimFloat blahblahblah;

// Blahblah blah blah
FlightSimInteger blahblahblah;

// Blah blah blahblah blah
FlightSimFloat blahblah;

void setup() {
  
  //////////////////
  // Blah
  //
  pinMode (blahblahPin, INPUT_PULLUP);
  
  //////////////////
  // Blah
  //
  pinMode(blahblah, OUTPUT);
  pinMode(blahblah, OUTPUT);
  pinMode(blahblah, OUTPUT);
  
  pinMode(blahblah, OUTPUT);
  pinMode(blahblah, OUTPUT);
  pinMode(blahblah, OUTPUT);
  
  //////////////////
  // Blah
  //
  blahblah       = XPlaneRef("blah/blah/blah/blah");
  blahblah       = XPlaneRef("blah/blah/blah/blah");
  
  blahblahblah   = XPlaneRef("blah/blah/blah/blah");
  gearDeployNose = XPlaneRef("sim/flightmodel2/gear/deploy_ratio[0]");
  blahblahblah   = XPlaneRef("blah/blah/blah/blah");
  
  blahblahblah   = XPlaneRef("blah/blah/blah/blah");
  
  blahblah       = XPlaneRef("blah/blah/blah/blah");
}

void loop() {
  // blah blah blah etc
}

The problem is writing the DataRef identifier (for example sim/cockpit/radios/transponder_light) such a long way away from the FlightSimWotsit it will be assigned to. It gets really difficult to keep track of code like this.

The FlightSimWotsit must be declared at global scope (ie, not inside a function), but the assignment must take place within a function, which is why we can’t write

FlightSimFloat xpdrLight;
xpdrLight = XPlaneRef("sim/cockpit/radios/transponder_light");

void setup() {
  ...

The next best thing is to store the identifier in a string or char array, created alongside the FlightSimClass at the top of the file.

FlightSimFloat xpdrLight;
const char xpdrLightIdent[] = "sim/cockpit/radios/transponder_light";

void setup() {
  xpdrLight = XPlaneRef(xpdrLightIdent);
  pinMode(12, OUTPUT);
}
void loop() {
  FlightSim.update();
  digitalWrite(12, xpdrLight);
}

But if you try this, you’ll get a mysterious error: initializer fails to determine size of '__c'.

This is because the Teensy 2.0 (and Teensy++ 2.0) processors have a ‘Harvard’ memory architecture, unlike nearly every other processor, which has ‘Von Neumann’ memory architecture. I don’t understand what that means, but I don’t need to: all I know is it’s possible to store constants in program memory, instead of in RAM, to save precious bytes. Paul’s done this with Teensyduino’s X-Plane identifiers, so the ident assignment operator (myFlightSimWotsit = XPlaneRef("some/x_plane/ident");) and the .assign() member function take pointers to program-memory chars, not ordinary ones. Such data is declared using the PROGMEM preprocessor macro.

After a lot of head-scratching, help from Paul, and experimentation, I found the working pattern:

FlightSimFloat xpdrLight;
PROGMEM const char xpdrLightIdent[] = "sim/cockpit/radios/transponder_light";

void setup() {
  xpdrLight.assign((const _XpRefStr_ *) xpdrLightIdent);
  pinMode(12,OUTPUT);
}

void loop() {
  FlightSim.update();
  digitalWrite(12,xpdrLight);
}

The PROGMEM statement stores the identifier in program memory, and the cast to _XpRefStr_ * allows us to give it to the .assign() member function later.

(I haven’t figured out how to make this work with = XPlaneRef() syntax, but .assign() works well enough.)

It’s possible to make the code easier to write using preprocessor #defines:

#define DataRefIdent PROGMEM const char
#define IdentRef (const _XpRefStr_ *)

FlightSimFloat xpdrLight;
DataRefIdent xpdrLightIdent[] = "sim/cockpit/radios/transponder_light";

void setup() {
  xpdrLight.assign(IdentRef xpdrLightIdent);
  pinMode(12,OUTPUT);
}

void loop() {
  FlightSim.update();
  digitalWrite(12,xpdrLight);
}

And also we can now make classes to apply all the boilerplate for us, ‘hiding’ a FlightSimClass privately inside the class.

#define DataRefIdent PROGMEM const char
#define IdentRef (const _XpRefStr_ *)

class SimpleLED {
public:
  SimpleLED(const int & pin, const char * ident) {
    _pin = pin;
    _ident = ident;
  }

  void setup() {
    _dr.assign(IdentRef _ident);
    pinMode(_pin, OUTPUT);
  }

  void update() {
    digitalWrite(_pin, _dr);
  }

private:
  FlightSimInteger _dr;
  int _pin;
  const char * _ident;
};

// ^^^ this code is written once and then 'hidden' in a header file
////////
// vvv this code is what we write for our exciting new TransponderLight project

DataRefIdent xpdrLightIdent[] = "sim/cockpit/radios/transponder_light";
SimpleLED xpdrLight (12, xpdrLightIdent);

void setup() {
  xpdrLight.setup();
}

void loop() {
  FlightSim.update();
  xpdrLight.update();
}

Look at that – no more calls to pinMode or digitalWrite or XPlaneRef when we want to use an LED with a nice boolean-style dataref! Two lines, one for the ident and another to create a SimpleLED, and that’s it! We still need to call .setup() and .update() for each LED, but we can eliminate all bar one of those using a linked list.

We still need to write the ident on its own line, because the ident is a PROGMEM constant, and the class cannot be PROGMEM. (If I’ve missed something fundamental about C++ syntax, please let me know!)

Then you can do all kinds of wonderful labour-saving things, like putting your power-supply simulation into the class, along with the ‘Is X-Plane enabled?’ checking, a generic bulb-test feature, and – most important – that linked list for setting up or updating all the instances of the class with one instruction, instead of writing foo.setup(); bar.setup(); baz.setup(); over and over again.

I’ve been doing exactly that with my SimObjects library. So far it covers dataref-driven LEDs, program logic-driven LEDs, and dataref-driven servos. I’ll write more about it when it’s more polished and tested, but an up-to-date development version is available on GitHub here.

As a final example, here’s the GearPanel project again, using the 15 Nov 2012 version of SimObjects.

#include "Bounce.h"
#include "SimLEDDev.h"


//// Gear control elements
const int gearSwitchPin = 45;
Bounce gearSwitch = Bounce (gearSwitchPin, 5);
FlightSimCommand gearUp;
FlightSimCommand gearDown;


//// Simulated power supply elements
FlightSimFloat supplyVolts;
const float voltsNeeded = 10.0;


//// Gear position lights elements
// Gear position dataref idents
DataRefIdent gearPosID[3][64] = {
  "sim/flightmodel2/gear/deploy_ratio[1]",
  "sim/flightmodel2/gear/deploy_ratio[0]",
  "sim/flightmodel2/gear/deploy_ratio[2]" };

// Gear is down when deploy-ratios are equal to 1.
// These are the green lights. They light when their dataref is
//  between 1.0 and 999.
SimLEDFloatDR gearDownLeft  (15, gearPosID[0], 1.0, 999.0);
SimLEDFloatDR gearDownNose  (16, gearPosID[1], 1.0, 999.0);
SimLEDFloatDR gearDownRight (17, gearPosID[2], 1.0, 999.0);
// and that's the green lights done. We don't need to think about
// them specifically anywhere below this point!

// Gear is wrong when gear deploy position doesn't match the gear
// handle position. These are the red lights.
SimLEDLocal gearWrongLeft  (12);
SimLEDLocal gearWrongNose  (13);
SimLEDLocal gearWrongRight (14);
// The 'local' means 'Teensy program'. The signal to drive the LED
// comes from our code, not automatically from an dataref from the
// other end of the USB cable.

// We need these for the red light driving logic.
FlightSimFloat gearPos[3];
FlightSimInteger gearHandleDown;



void setup() {
  // Gear control
  pinMode (gearSwitchPin, INPUT_PULLUP);
  
  gearUp = XPlaneRef("sim/flight_controls/landing_gear_up");
  gearDown = XPlaneRef("sim/flight_controls/landing_gear_down");
  
  // Simulated power supply
  supplyVolts =     XPlaneRef("sim/cockpit2/electrical/bus_volts[0]");
  
  // Gear position light logic
  for (int i = 0; i < 3; ++i) {
    gearPos[i].assign(IdentRef gearPosID[i]);
  }
  gearHandleDown =  XPlaneRef("sim/cockpit2/controls/gear_handle_down");
  
  // Set up every SimLED
  SimObject::setup();
}



void loop() {
  FlightSim.update();
  
  // Gear control
  gearSwitch.update();
  if(gearSwitch.read() == LOW) { // if the switch is closed
    if (gearHandleDown == 0) { // if gear handle is up
      gearDown.once(); // move it down
    }
  } else {
    if (gearHandleDown == 1) { // if gear handle is down
      gearUp.once(); // move it up
    }
  }
  
  // Power supply
  SimObject::hasPower = (supplyVolts > voltsNeeded);
  
  // Gear position light logic
  gearWrongLeft.setActive(gearHandleDown != gearPos[0]);
  gearWrongNose.setActive(gearHandleDown != gearPos[1]);
  gearWrongRight.setActive(gearHandleDown != gearPos[2]);
  
  // Update every SimLED
  SimObject::update();
}

The classes are still in development, so function names and interfaces will change. (I welcome suggestions and feedback on this.) Looking at this code, I see opportunities to write more classes to manage switches, commands, and to wrap around datarefs to reduce the boilerplate even further.

I understand Teensy 3.0 has a conventional memory architecture and will ignore the PROGMEM statement entirely. Then we can just store identifiers as regular old char arrays. But until Teensy 3.0 gains X-Plane support, and while users still have Teensy 2.0 projects to build, this is the way to make our lives easier.

I hope this might be useful!
– Jack

Advertisements

From → Guides

2 Comments
  1. In the line : gearPos[i].assign(IdentRef gearPosID[i]);
    The IdentRef is supposed to be DataRefIdent right?

    When I use the expression: switchPos.assign(DatarefIdent myswitchDataRef[0])
    I get an error saying “error: expected primary-expression before ‘__attribute__'”
    What could be wrong in my statement above?

  2. Hi Anthony,

    I think the #define IdentRef (const _XpRefStr_ *) statement has gone missing. Put that back in somewhere (ideally next to wherever DataRefIdent is declared) and it should behave.

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s