Jump to content

Programming Auduino/Teensy for HomePits.


Raisuli

Recommended Posts

I've started work on the next iteration of the homepit, which should have some extra bells and whistles.  Much to my horror I realized all the sketch code for the existing pit got lost, so I've had to start from scratch on a few things.

One of the problems I want to solve is the need for far too many boards.  The obvious solution is to use I2C, which isn't the most intuitive thing in the world, but by using MCP23017 I2C chips and an Arduino/Teensy board (I use Teensy) you can get 128 buttons on 4 wires (2 data, +3.3V, GND).  128 is the limit allowed by DirectX.  Then again, you can also add six axis and two sliders, but that's a different post.  I haven't figured out if DCS can handle the sliders yet; it reads all 6 axis.

Some people have asked for the sketches I use, so I'll go ahead and post the test setups.  Combining multiple components would obviously require merging the test code and setting pins and button numbers correctly.

For the I2C I can map ports on chips to specific button numbers.  Here is the (lightly commented) sketch I put together tonight, and tested with good old fashioned joy.cpl on windows 10:

/*
 * Use the 16 bits of a MCP23017 as digital pins for switch contacts.  This reads the I2C bus and converts the ports on the chip
 * into joystick buttons.  If using multiple chips change deviceCount, add the address to device[], then add the rows to portState 
 * and portButton.  You can address up to 8 chips, but that's 128 buttons, which is the limit for a joystick.  The address pins 
 * are 15, 16, and 17.  The SLC and SDA wires connect to all the chips, as does power, ground, and !RESET
 * 
 * Much of this is from https://teensyhauptwerk.wordpress.com/2014/01/17/more-switches-i2c-example/ with the rest from me
 */

#include <Wire.h>

const byte deviceCount = 1;
byte device[deviceCount]={0x20};

const byte GPIOA=0x12; //IO pins, port A (pin 18)
const byte GPIOB=0x13; //IO pins, port B (Pin 19)

const byte GPPUA=0x0C; //pull up resistors ,port A
const byte GPPUB=0x0D; //pull up resistors ,port B

int portState[deviceCount][16] = {
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} //Set all the ports to defalt high (unpressed)
};

int portButton[deviceCount][16] = {
{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16} //Joystick button numbers associated with each port
};

void setup() { 
  Wire.begin();
  Serial.begin(9600);
  Joystick.useManualSend(true);
  for(int y=0; y < deviceCount; y++) {
    seti2cbyte(device[y],GPPUA,0xFF);  //enable pull-up resistors on port A 
    seti2cbyte(device[y],GPPUB,0xFF);  //enable pull-up resistors on port B
  }
} 

byte geti2cbyte(byte address, byte ptr) {
  Wire.beginTransmission(address);
  Wire.write(ptr);
  Wire.endTransmission();
  Wire.requestFrom(address, 1); // request one byte of data from MCP20317
  byte inputs=Wire.read(); 
  return inputs; 
}

void seti2cbyte(byte address, byte ptr, byte value) {
  Wire.beginTransmission(address);
  Wire.write(ptr); // set MCP23017 memory pointer to GPPUA address
  Wire.write(value); // enable pull up resistors
  Wire.endTransmission();
}

void loop() { 

  for(int i = 0; i < deviceCount; i++) {

    byte port_a = geti2cbyte(device[i],GPIOA); //get status of pins 1-8
    byte port_b = geti2cbyte(device[i],GPIOB); //get status of pins 21-28
  
    int tester=1;
  
    //test each binary bit of "port_a" to see if pin is LOW, so 0
    // use binary AND to do this with a mask 1,2,4,8,16,32,64,128
    for (int x=0; x<8; x++) {
      if ((port_a & tester) == 0) {
        //the BIT is LOW, so check the current stored state for that bit.  If the current state is different, send the appropriate joystick command
        if (portState[i][x] == 1) {
          Serial.print(x);   
          Serial.println(" is pressed!");  
          Joystick.button(portButton[i][x],1);
          portState[i][x] = 0;
        } 
      } else if (portState[i][x] == 0) {
          //The bit is high, or unpressed
          Serial.print(x);
          Serial.println(" has been released");
          Joystick.button(portButton[i][x],0);
          portState[i][x] = 1;
      }
      //then do it all over again for the other 8 bits on the chip
      if ((port_b & tester) == 0) {
        //the BIT is LOW
        if (portState[i][x+8] == 1) {
          Serial.print(x+8);   
          Serial.println(" is pressed!");  
          Joystick.button(portButton[i][x+8],1);
          portState[i][x+8] = 0;
        } 
      } else if (portState[i][x+8] == 0) {
          Serial.print(x+8);
          Serial.println(" has been released");
          Joystick.button(portButton[i][x+8],0);
          portState[i][x+8] = 1;
      }
      tester=tester*2; 
    }
  }
  Joystick.send_now();  //Send control states
  delay(5);

}

My first recreation was a way to use a five pin rotary encoder with a push button.  I use several on my current pit and they're a pain the way I set them up; the indents are not 'single press', so you turn one indent and the 'button' is pushed several times.  Think 'Channel select' on the F/A-18's radios, and then try to settle the encoder to the right channel in between the indents.  The push button bounces, as well.  Also if you turn them too fast they can appear to go backwards.

As long as I had to re-invent that wheel I made each indent one push of the button and got rid of the bounce on the push.  The backwards trick is still there, but you have to work harder for it.

This is the sketch I used to accomplish all that:

/* 
 *  Addicore encoder turned into basically three buttons, one for a click to the right, one for a click to the left, and one for push
 *  Also, the push button bounces BADLY, so special handling
 *  
 *  Type: Serial, Keyboard, Mouse, Joystick
 */

#include <Encoder.h>
#include <Bounce.h>

const int encoderButtonPin = 0; // Pin number for the SW contact
const int clickRightButton = 10; // Button number for a clockwise turn
const int clickLeftButton = 11; // Button number for a counterclockwise turn
const int clickPushButton = 12; // Button number for a push

// Change these pin numbers to the pins connected to your encoder.
Encoder encoder(2, 1); // Pin numbers for CLK, DT; in this order clockwise is positive and counterclockwise negative. The encoders I have count 4 between indents
Bounce encoderButton = Bounce(encoderButtonPin, 10); // 10 ms delay


void setup() {
  pinMode(encoderButtonPin, INPUT_PULLUP);

  Joystick.useManualSend(true);
 
  Serial.begin(9600);
  Serial.println("Addicore Encoder Test:");
}

long encoderPosition  = 0;

void loop() {
  // Let's look at the encoder first
  long newPos;
  newPos = encoder.read();
  if (newPos != encoderPosition) {
    if ( (newPos - encoderPosition) >= 4)
    {
      Serial.println("Click Right!");
      Joystick.button(clickRightButton, 1);
      encoderPosition = newPos;
    } else if ( (newPos - encoderPosition) <= -4)
    {
      Serial.println("Click Left!");
      Joystick.button(clickLeftButton, 1);
      encoderPosition = newPos;
    } 
  } else
  {
    Joystick.button(clickRightButton, 0);
    Joystick.button(clickLeftButton, 0);
  }

  //Then the button
  if (encoderButton.update()) {
    if (encoderButton.fallingEdge()) {
      Serial.println("Click Button");
      Joystick.button(clickPushButton, 1);
    } else
    {
      Joystick.button(clickPushButton, 0);
    }
  }  

  Joystick.send_now(); // Send control states
  delay(25); // This will also keep buttons pushed.  You can be too quick on a button when you're sending them programmatically
}

I'll add posts as I solve some of the other Arduino/Teensy problems.  There is some coverage for those, but it seems not that much.

Link to comment
Share on other sites

2 minutes ago, No1sonuk said:

IIRC, Arduino megas have something like 50 IO.

 

Teensy 3.6 has 57 digital and 6 analog pins that cannot also be used as digital.  If you're willing to sacrifice digital pins there are three I2C channels, 26 analog, a bunch of SPI, and a choir singing Christmas carols.  The only advantage I saw over Arduino when I first started was the outrageous number of digital pins, because I wasn't smart enough to use I2C back then. 

I ran out of pins on more than one panel, and used a couple Teensy LCs when I couldn't pawn off the extra pins on to a different board.  This is why I have a box full of Teensy 3.6 and LC boards, a Teensy 4.1, and an Arduino Mega.  Hence my Teensy bias; absolutely nothing against Arduino.

For my next trick the plan is to leverage DCS-Bios and do the simply impossible, which is have a mater caution button that actually LIGHTS UP when there is a Master Caution.  Ok, I'm starting small, but I have some big ideas.  I've never used BCS-Bios before. 

What is really going to kill me is the labels for my illuminated panels.  All my work has been done in Visio, because I have it and know how to use it, and that ain't gonna cut it.  The only tool I have is a pretty generic Chinese CNC, and making the base panels won't be hard.  It's the engraving that scares me, so I'm putting it off and doing the electronics.

Link to comment
Share on other sites

Hi.

I don't want to be disgusting, but check out this thread...😱

https://forum.dcs.world/topic/97434-an-arduino-usb-hid-controller-composite-usb-controller

I managed to follow the instructions and made 3 Arduino Mega joystick board. with two joysticks per board, and a total of 256 buttons plus 8 axis (!) per board, 32 of those buttons are auto on/off, so they can be used with rotary encoders or levers (on/off, on/off/on). A dream come true.

I wpuldn't like to discourage you nor to be mean, but it works almost perfect. Te main flow with more than one Arduino is the name, as every Arduino will have the same name and Windows will shuffle them every so often, so you will have to uninstall anr re install them in a certain order so they are recognised and the profiles you made can be used.

Check it and if in doubt ask me, as I really progressed on it and have quite some experience.

Saludos.

Saca111


Edited by Sacarino111
Link to comment
Share on other sites

12 minutes ago, Sacarino111 said:

Hi.

I don't want to be disgusting, but check out this thread...😱

https://forum.dcs.world/topic/97434-an-arduino-usb-hid-controller-composite-usb-controller

I managed to follow the instructions and made 3 Arduino Mega joystick board. with two joysticks per board, and a total of 256 buttons plus 8 axis (!) per board, 32 of those buttons are auto on/off, so they can be used with rotary encoders or levers (on/off, on/off/on). A dream come true.

I wpuldn't like to discourage you nor to be mean, but it works almost perfect. Te main flow with more than one Arduino is the name, as every Arduino will have the same name and Windows will shuffle them every so often, so you will have to uninstall anr re install them in a certain order so they are recognised and the profiles you made can be used.

Check it and if in doubt ask me, as I really progressed on it and have quite some experience.

Saludos.

Saca111

 

I use matrix setups quite a bit, but only for momentary switches.  In fact, all of my momentary switches are put into a matrix; the current UFC has 40 buttons on 13 pins.  It works out that the majority of the switches I use are toggles, and you can't put toggles in a matrix due to the multi-press limitations.  There's a little bit of voodoo to do a matrix right, but I use bounceless NKK switches and just know not to engage more than one at a time, so I don't bother with it. 

I also don't use any third party drivers or software, simply because every layer increases the failure cross-section and muddies up the underlying OS.  Windows is fragile enough without help.  That, of course, changes when I use DCS-Bios, but it will mostly be used for feedback rather than input.  My current rig only depends on the standard Windows USB HID drivers and the controls are mapped directly in DCS.

I haven't read all the details of this post, but will; I'm always in the market for better ideas and will absolutely go through it.  Thanks for the link!

One of the other nice things about Teensy is you can (and I do) name them.  DCS doesn't see the name, but I can edit device names in DCS.  Windows does see the name, so they're easy to sort in the OS.  That might be possible with Arduino, but I haven't bothered to look yet.

Link to comment
Share on other sites

34 minutes ago, Raisuli said:

For my next trick the plan is to leverage DCS-Bios and do the simply impossible, which is have a mater caution button that actually LIGHTS UP when there is a Master Caution.  Ok, I'm starting small, but I have some big ideas.  I've never used BCS-Bios before. 

That sooooo last year.  My latest DCS-BIOS "messing about" was Master Caution light/button on my Stream Deck, and a fuel duration/range estimator ON a Stream Deck button. 😉

BTW, I'd advise you to use the "Flightpanels Fork" of DCS-BIOS - it's actively updated and bug-fixed.  The Hub version isn't as far as I'm aware.
https://github.com/DCSFlightpanels

Link to comment
Share on other sites

6 minutes ago, Raisuli said:

One of the other nice things about Teensy is you can (and I do) name them.  DCS doesn't see the name, but I can edit device names in DCS.  Windows does see the name, so they're easy to sort in the OS.  That might be possible with Arduino, but I haven't bothered to look yet.

HI.

Thanks for the answer...

To me , the most limiting thing of the link I posted is the "name gate" I did try every single trick I know (wich indeed is to aks for help to any one who kows about programming, as myself  I am absolute ignorant on it), so if you mange to find the way to change the windows name it would be great, I even found the "descriptor.h" file where you can change the name, and I know how change it, but couldn't compile it, always the same errors.

But, besides that, al working great.

I neither use any external software for it, just the DCS setup, that recognises up to 128 buttons per joy and as every arduino is set up as two joysticks, you have up to 256 buttons and 8 axis.

Saludos.

Saca111

And recently started fiddeling with DCS-BIOS; Master Caution light (F/A-18 and A10C), Landing Gear lever light and AoA indexer.... drooling! 

Link to comment
Share on other sites

36 minutes ago, No1sonuk said:

That sooooo last year.  My latest DCS-BIOS "messing about" was Master Caution light/button on my Stream Deck, and a fuel duration/range estimator ON a Stream Deck button. 😉

BTW, I'd advise you to use the "Flightpanels Fork" of DCS-BIOS - it's actively updated and bug-fixed.  The Hub version isn't as far as I'm aware.
https://github.com/DCSFlightpanels

Thanks for the advice!

I'm curious what happens when you push the fuel duration/range estimate button? 

I do, however, confess to being SO the year before the year before last.  I used to be able to read punch cards by looking at them.  Never got that good with paper tape, though...  🤪

 

32 minutes ago, Sacarino111 said:

HI.

Thanks for the answer...

To me , the most limiting thing of the link I posted is the "name gate" I did try every single trick I know (wich indeed is to aks for help to any one who kows about programming, as myself  I am absolute ignorant on it), so if you mange to find the way to change the windows name it would be great, I even found the "descriptor.h" file where you can change the name, and I know how change it, but couldn't compile it, always the same errors.

But, besides that, al working great.

I neither use any external software for it, just the DCS setup, that recognises up to 128 buttons per joy and as every arduino is set up as two joysticks, you have up to 256 buttons and 8 axis.

Saludos.

Saca111

And recently started fiddeling with DCS-BIOS; Master Caution light (F/A-18 and A10C), Landing Gear lever light and AoA indexer.... drooling! 

Renaming is done in a .c file with the same name as the sketch in the same folder.  Maybe Arduino works differently, but they seem to program the same.  Heck, you have to have Arduino loaded to program a Teensy.

This is from one of the Rev1 attempts; really just a button box I used before I bought the dedicated game machine.  In this case RMon meant the box lived under the right monitor.  IIRC errors compiling with the added .c were nagging struct issues, but I always got it to work.  Now I don't remember the nags, though.  I'm sure I'll have to solve this again, too.

#include <usb_names.h>

#define MANUFACTURER_NAME  {'P','i','c','c','o','l', 'o'}
#define MANUFACTURER_NAME_LEN 7
#define PRODUCT_NAME    {'R','M','o','n'}
#define PRODUCT_NAME_LEN  4

struct usb_string_descriptor_struct usb_string_manufacturer_name = {
  2 + MANUFACTURER_NAME_LEN * 2,
  3,
  MANUFACTURER_NAME
};
struct usb_string_descriptor_struct usb_string_product_name = {
  2 + PRODUCT_NAME_LEN * 2,
  3,
  PRODUCT_NAME
};

 

Link to comment
Share on other sites

10 minutes ago, Raisuli said:

Thanks for the advice!

I'm curious what happens when you push the fuel duration/range estimate button? 

I do, however, confess to being SO the year before the year before last.  I used to be able to read punch cards by looking at them.  Never got that good with paper tape, though...  🤪

 

Nothing happens when the button is pressed.  It was a test - I'm going to put the display on an arduino-driven LCD, as the update rate plays havoc with the stream deck display. 🤣
Also, as I don't know if there's global variable support, the calculations involved have to be repeated for each displayed number.

As for reading punch cards, etc.  I knew a guy about 20 years ago who COULD read paper tape like it was normal print on a page.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...