Friday, January 27, 2017

Arduino and FSX (Part 7)

Arduino Joystick with 32 Pins Using Pin-Change-Interrupts

The 8-axis by 40-pin Joystick discussed in the previous "Arduino and FSX (Part 6)" is modified in this Part 7 to use "pin change interrupts". Pin-Change-Interrupts are a technique to implement an increased number of interrupts if required. In a future blog, pin-change-interrupts will be used for adding rotary encoders that are connected to the MCP23S17 Port Expanders. Each 16-pin Port Expander can connect 8 rotary encoders, and each Port Expander will require two interrupt pins on the Arduino.

In Part 6, MCP23S17 (MCP) 16-pin Port Expanders had the port interrupt pins "mirrored" together and those were then wired together and configured as "open-drain". By mirroring and bridging with open-drain, only one Arduino interrupt pin was used to sense when any of the 32 MCP pins changed state. When that interrupt was sensed, the joyReport data was transmitted through USB to FSX. If only buttons or switches are to be used, this approach is ideal.

In this Part 7, "pin-change-interrupts" are used in place of the mirrored and open-drain interrupt. Each of the Port A and Port B interrupts on each MCP will be wired to an Arduino digital pin that is configured as a pin-change-interrupt. The Arduino Uno has two pins assigned as hardware interrupts, INT0, and INT1, both of which are on the Arduino Port D. Port D is comprised of the digital pins D0-D7, Port B includes digital pins D8-13, and Port C includes analog pins A0-A5. Rather than being limited to two hardware interrupt pins (INT0 and INT1), each Arduino pin can be configured as an interrupt pin. Interrupt pins on each of these 3 ports work just like those on the MCP chips. Each port pin can serve as an interrupt pin, and each can trigger a port interrupt--PCIE0 (Port B), PCIE1 (Port C), or PCIE2 (Port D).

In the sketch code below, pin D2 is wired to MCP Bank0 Port INTA, and D3 is wired to MCP Bank0 Port INTB. Pin D4 is wired to MCP Bank1 Port INTA, and D5 is wired to MCP Bank1 Port INTB. Four MCP Port interrupts are connected to four Arduino pin-change-interrupts.

When an MCP pin is grounded through a switch, that pin transitions from HIGH to LOW. When that happens, the corresponding MCP Port interrupt transitions HIGH to LOW which takes the Arduino interrupt pin HIGH to LOW. That transition triggers an Interrupt Service Routine (ISR) for the Arduino port. The ISR calls function "checkForPinChange (port)" which reads the MCP pin data and sets flags to indicate to function "loop()" that the joyReport needs to be filled with data and transmitted through USB to FSX. When the switch is released, the same process happens, but the pin value returns to 0.

The "Arduino_ISR_MCP23S17_8X40Joystick" sketch below is a near replica of the sketch used in Part 6, with added changes necessary to implement pin-change-interrupts. The hardware does not change from those used in Part 6. Be sure to comment out DEBUG so that the joyReport is not corrupted with Serial.print output.


Software and Firmware --
The Arduino sketch is presented below. Once the Arduino is programmed with the sketch and tested with your hardware, the Arduino needs to be converted from a USB-Serial device to a USB-HID joystick device (Arduino-big-joystick.hex) with 8-axes and 40-buttons/switches. That device can then provide an additional 32 buttons/switches to be used by FSX. Those buttons/switches can be programmed in FSUIPC to activate the desired function in FSX. Also modify the sketch to incorporate the axes you require. See the comments in the sketch as a guide.
Sketch -

/*

   Sketch - Arduino_ISR_MCP23S17_8X40Joystick

   Lowell Bahner
   January, 2017

   This code sends joyStick data when axis or button/switches change values.

   This code uses two MCP23S17 16-pin Port Expanders to add 32 pins to an Uno or Mega2560.

   This code uses 4 ISR's (one per MCP port) to sense port interrupts on the two MCP's.

   A push button or switch takes one or more MCP23S17 input pins LOW (to ground)
   to trigger interrupts which are processed to read the button/switch data.
   Multiple switches can change state at the same interrupt.

   The MCP23S17 has an interrupt on each digital input pin which can set
   a PORT interrupt pin. The Port A interrupt (INTA) is chip pin 20, and Port B
   interrupt (INTB) is chip pin 19. Each INTA and INTB then connects to an
   Arduino ISR interrupt pin.

   Bank   Port   MCP_INT   Ard_INT Pin
   0      A      A         D2
   0      B      B         D3
   1      A      A         D4
   1      B      B         D5

   Each Arduino interrupt triggers the appropriate
   "pin-change-interrupt ISR" (interrupt service routine) in the sketch.
   On an Uno, there are two external interrupts Int0, Int1) but 24 Pin-Change-Interrupts.
   Pin-Change-Interrupts share an ISR between all the pins on a port (port B, C, and D).

   Ref: thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/
   Ref: www_gammon.com.au/forum/?id=11130

   The interrupts on the MCP Port Expanders are NOT bridged together using
   MIRROR or OPEN DRAIN. Both the MCP and Arduino pins must be set as INPUT_PULLUP.

   UNO/MEGA hardware interrupt pins (D2, D3, D4, D5...)

   For use as USB-HID joyStick:
   1) connect potentiometer wiper pins to analog pins A0, A1, A2
   2) In loop(), comment out "sendFlag = 0;" line to use axes
   3) connect rotary encoders to MCP23S17 digital pins
   4) test with DEBUG to check joyReport data are correct
   5) #undef DEBUG so that joyReport data are not corrupted with Serial.print
   6) program USB ATmega16U2 as USB-HID

   ======================================================

   Sketch Output to Serial Monitor Example


   ======================================================

  Starting MCP23S17 Pin-Change-Interrupt Joystick


  Bank[chip]: 0
  ...mcp [chip].aPin: 2, mcp [chip].bPin: 3, mcp [chip].aBitMask: 00000100, mcp [chip].bBitMask: 00001000
  ...mcp [chip].whichAInterrupt: 2, mcp [chip].whichBInterrupt: 2
  ... *ICRmaskPort: 00001100, PCICR: 00000100, PCMSK0: 00000000, PCMSK1: 00000000, PCMSK2: 00001100

  Bank[chip]: 1
  ...mcp [chip].aPin: 4, mcp [chip].bPin: 5, mcp [chip].aBitMask: 00010000, mcp [chip].bBitMask: 00100000
  ...mcp [chip].whichAInterrupt: 2, mcp [chip].whichBInterrupt: 2
  ... *ICRmaskPort: 00111100, PCICR: 00000100, PCMSK0: 00000000, PCMSK1: 00000000, PCMSK2: 00111100

  (Button down, Bank0 Pin 1 & Bank1 Pin 4)
  Bank[chip]: 0, flagMCPA: 1, PortGPIOVal: 00000010, mcpFlag 1

  Bank[chip]: 1, flagMCPA: 1, PortGPIOVal: 00010000, mcpFlag 1

  joyReport btnArray: 00000010, 00000000, 00010000, 00000000

  axis[0]= -21045 axis[1]= -20853 axis[2]= -21173 axis[3]= 0 axis[4]= 0 axis[5]= 0 axis[6]= 0 axis[7]= 0
  btnArray[0]= 00000010 btnArray[1]= 00000000 btnArray[2]= 00010000 btnArray[3]= 00000000 btnArray[4]= 00000000

  (Button up)
  Bank[chip]: 0, flagMCPA: 1, PortGPIOVal: 00000000, mcpFlag 1

  Bank[chip]: 1, flagMCPA: 1, PortGPIOVal: 00000000, mcpFlag 1

  joyReport btnArray: 00000000, 00000000, 00000000, 00000000

  axis[0]= -20341 axis[1]= -20276 axis[2]= -20533 axis[3]= 0 axis[4]= 0 axis[5]= 0 axis[6]= 0 axis[7]= 0
  btnArray[0]= 00000000 btnArray[1]= 00000000 btnArray[2]= 00000000 btnArray[3]= 00000000 btnArray[4]= 00000000

  (Button down, Port A: Bank0 Pin 1, Bank1 Pin 4)
  Bank[chip]: 0, flagMCPA: 1, PortGPIOVal: 00000010, mcpFlag 1

  Bank[chip]: 1, flagMCPA: 1, PortGPIOVal: 00010000, mcpFlag 1

  joyReport btnArray: 00000010, 00000000, 00010000, 00000000

  axis[0]= -20597 axis[1]= -20533 axis[2]= -20917 axis[3]= 0 axis[4]= 0 axis[5]= 0 axis[6]= 0 axis[7]= 0
  btnArray[0]= 00000010 btnArray[1]= 00000000 btnArray[2]= 00010000 btnArray[3]= 00000000 btnArray[4]= 00000000

  (and, Button down, Port B: Bank0 Pin 14, Bank1 Pin 12)
  Bank[chip]: 0, flagMCPB: 1, PortGPIOVal: 01000000, mcpFlag 2

  Bank[chip]: 1, flagMCPB: 1, PortGPIOVal: 00010000, mcpFlag 2

  joyReport btnArray: 00000010, 01000000, 00010000, 00010000

  axis[0]= -21494 axis[1]= -21430 axis[2]= -21750 axis[3]= 0 axis[4]= 0 axis[5]= 0 axis[6]= 0 axis[7]= 0
  btnArray[0]= 00000010 btnArray[1]= 01000000 btnArray[2]= 00010000 btnArray[3]= 00010000 btnArray[4]= 00000000

*/

// to turn on DEBUG, define DEBUG. Make sure to undef DEBUG for joystick use
//#undef DEBUG
#define DEBUG

// ======================================================
// Sketch Code

// Majenko MCP23S17 Library updated 2017-01-23 which fixed interrupts bug
#include <MCP23S17.h>

// Arduino Library SPI.h
#include <SPI.h>

const byte EXPANDERS = 2;       // the number of MCP23S17 chips to use
const byte NUM_BUTTONS = 40;    // do not change this value
const byte NUM_AXES = 8;        // 6 axes to UNO, and 8 to MEGA. do not change this value.

typedef struct joyReport_t {
  int16_t axis[NUM_AXES];
  uint8_t btnArray[(NUM_BUTTONS + 7) / 8]; // 8 buttons per byte
} joyReport_t;
joyReport_t joyReport;
joyReport_t prevjoyReport;
uint8_t sendFlag = 0; // only send data when data change

// SPI CS/SS chipselect pin can be changed by user as desired
const uint8_t chipSelect = 10;

volatile uint16_t mcpReading = 0; // 16-bit interrupt reading
volatile uint8_t mcpFlag = 0;     // only send data when data change

typedef struct
{
  int deviceID; // MCP chip 0,1,...7
  int aPin;     // which Arduino pin for the "A" side interrupt
  int bPin;     // which Arduino pin for the "B" side interrupt

  // values below are calculated at run-time
  volatile uint16_t mcpGPIO; // chip word value
  volatile uint8_t mcpAGPIO; // Port A byte value
  volatile uint8_t mcpBGPIO; // Port B byte value

  volatile uint8_t flagMCPA;
  volatile uint8_t flagMCPB;

  volatile uint8_t flagGPIO;
  volatile uint8_t flagAGPIO;
  volatile uint8_t flagBGPIO;

  byte aBitMask;   // which interrupt bit in the A port
  byte bBitMask;   // which interrupt bit in the B port
  byte whichAInterrupt;  // which pin-change interrupt port (0, 1, 2)
  byte whichBInterrupt;  // which pin-change interrupt port (0, 1, 2)

} MCP;

// MCP device=devID{0,1,...EXPANDERS-1}, where Bank0=0, Bank1=1...Bank7=7
// MCP port=portID{0,1}, where PORTA=0 PORTB=1
// Each MCP devID:portID = 8 pins
// Bank0 connects to Ard pins D2,D3; Bank1 connects to Ard pins D4,D5;, etc
volatile MCP mcp [EXPANDERS] = {
  {0, 2, 3},
  {1, 4, 5},
  // {2, 6, 7},
  // {3, 8, 9},
}; // end of expanders

// Create an object for each chip
// Bank0 is address 0. Pins A0,A1,A2 grounded.
// Bank1 is address 1. Pin A0=+5V, A1,A2 grounded.

MCP23S17 Bank0(&SPI, chipSelect, 0);
MCP23S17 Bank1(&SPI, chipSelect, 1);

// ++++++++++++++++++++++++++++++++++++++++++++++++++++


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
//   function prototypes - not necessary but show the
//         functions used in this sketch
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// pin change interrupts
void checkForPinChange (const byte which);
ISR (PCINT0_vect);
ISR (PCINT1_vect);
ISR (PCINT2_vect);
void setup();
void setChipPins();
void setPin(MCP23S17 &bank, uint8_t chip);
void readBankPortGPIOVal(MCP23S17 &bank, uint8_t chip, uint8_t port);
void interruptMCP();
void sendJoyReport(struct joyReport_t *report);
void loop ();
void print8Bits(uint8_t myByte);
void print16Bits(uint16_t myWord);
void crPrintHEX(unsigned long DATA, unsigned char numChars);

// ++++++++++++++++++++++++++++++++++++++++++++++++++++


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
//   function checkForPinChange()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// MCP pin change triggers MCP INTA or INTB that triggers "which" Arduino ISR.
// which==0 is Ard Port B, which==1 is Ard Port C, which==2 is Ard Port D.
// A switch ON = pin change HIGH-LOW or, a switch OFF = pin change LOW-HIGH.
// if MCP chip INTA fired, set flagMCPA and read the Port A pins.
// if MCP chip INTB fired, set flagMCPB and read the Port B pins.
// Set mcpFlag>0 means one or more pins/interrupts changed state.
// ISR finishes, interrupts are cleared, then process the flags in loop().
// Using mcp.portRead() clears the mcp interrupt during the Ard interrupt.

void checkForPinChange (const byte which)
{
  //Serial.print ("\n\n =============== which: "); Serial.print (which);
  //Serial.print ("\n checkForPinChange ");

  for (uint8_t chip = 0; chip < EXPANDERS; chip++) {
    if (mcp [chip].whichAInterrupt == which) {
      if ((digitalRead(mcp [chip].aPin) == LOW)) {
        mcp[chip].flagMCPA = 1;
        mcpFlag = 1;
        switch (chip) {
          case 0:
            readBankPortGPIOVal(Bank0, chip, 0);
            break;
          case 1:
            readBankPortGPIOVal(Bank1, chip, 0);
            break;
          default:
            break;
        }
#ifdef DEBUG
        /*
          Serial.print ("\n\nBank[chip]: "); Serial.print (chip);
          Serial.print (", flagMCPA: "); Serial.print (mcp[chip].flagMCPA);
          Serial.print (", PortGPIOVal: "); print8Bits (mcp[chip].mcpAGPIO);
          Serial.print (", mcpFlag "); Serial.print (mcpFlag);
        */
#endif
      }
    }
    if (mcp [chip].whichBInterrupt == which) {
      if ((digitalRead(mcp [chip].bPin) == LOW)) {
        mcp[chip].flagMCPB = 1;
        mcpFlag = 2;
        switch (chip) {
          case 0:
            readBankPortGPIOVal(Bank0, chip, 1);
            break;
          case 1:
            readBankPortGPIOVal(Bank1, chip, 1);
            break;
          default:
            break;
        }
#ifdef DEBUG
        /*
          Serial.print ("\n\nBank[chip]: "); Serial.print (chip);
          Serial.print (", flagMCPB: "); Serial.print (mcp[chip].flagMCPB);
          Serial.print (", PortGPIOVal: "); print8Bits (mcp[chip].mcpBGPIO);
          Serial.print (", mcpFlag "); Serial.print (mcpFlag);
        */
#endif
      }
    }
  }     // end of for each expander
} // end of checkForPinChange

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
//   ISR (PCINT0_vect)
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// handle pin change interrupt for D8 to D13 here
ISR (PCINT0_vect)
{
  cli(); //stop interrupts happening before we read pin values
  checkForPinChange (PCIE0);
  sei(); //restart interrupts
}  // end of PCINT0_vect


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
//   ISR (PCINT1_vect)
// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// handle pin change interrupt for A0 to A5 here
ISR (PCINT1_vect)
{
  cli(); //stop interrupts happening before we read pin values
  checkForPinChange (PCIE1);
  sei(); //restart interrupts
}  // end of PCINT1_vect

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
//   ISR (PCINT2_vect)
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// MCP23S17 INTA and INTB wired to Arduino pin D2-D7
//    triggers this ISR (PCINT2_vect).
// handle pin change interrupt for D0 to D7 here
ISR (PCINT2_vect)
{
  //Serial.println ("ISR (PCINT2_vect) ...");
  cli(); //stop interrupts happening before we read pin values
  checkForPinChange (PCIE2);
  sei(); //restart interrupts
}  // end of PCINT2_vect

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function setup
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void setup() {

  Bank0.begin();
  Bank1.begin();

  Serial.begin (115200);

#ifdef DEBUG
  Serial.println(F("\n Starting MCP23S17 Pin-Change-Interrupt Joystick"));
#endif

  //----------------------------------------------------------
  // Port Expander pin and interrupts configuration
  //----------------------------------------------------------
  //
  // Input port code
  // pins 0-15 are on device:port
  // device Bank0, Bank1
  // port 0=Port A, 1=Port B
  //
  // Set MCP23S17 pin modes and Interrupt configurations
  // example set one pin: chip.pinMode(0, INPUT_PULLUP);
  //
  setChipPins(); // use loop to set individual pins

  // clear the MCP interrupt values variables
  mcpReading = 0;

  //----------------------------------------------------------
  // Arduino pin-change-interrupts configuration
  //----------------------------------------------------------
  //
  // clear any outstanding Arduino pin-change-interrupts
  PCIFR  |= bit (PCIF0) | bit (PCIF1) | bit (PCIF2);

  for (uint8_t chip = 0; chip < EXPANDERS; chip++)
  {
    // set interrupt pins to INPUT_PULLUP
    pinMode (mcp [chip].aPin, INPUT_PULLUP);
    pinMode (mcp [chip].bPin, INPUT_PULLUP);

    // Create a Mask with the pin bit = 1
    mcp [chip].aBitMask = digitalPinToBitMask (mcp [chip].aPin);
    mcp [chip].bBitMask = digitalPinToBitMask (mcp [chip].bPin);

    // Which ISR for each interrupt pin (0=PCIE0, 1=PCIE1, 2=PCIE2)
    mcp [chip].whichAInterrupt = digitalPinToPCICRbit (mcp [chip].aPin);
    mcp [chip].whichBInterrupt = digitalPinToPCICRbit (mcp [chip].bPin);

    // Activate this pin-change interrupt bit (eg. PCMSK0, PCMSK1, PCMSK2)
    /*   Ref: thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/
      // PCMSK definitions:
      PCMSK0 |= 0b00000011;    // turn on pins PB0 & PB1, PCINT0 & PCINT1, pins D8, D9
      PCMSK1 |= 0b00010000;    // turn on pin PC4, which is PCINT12, pin A4
      PCMSK2 |= 0b00001100;    // turn on pins PD2 & PD3, PCINT18 & PCINT19, pins D2, D3
    */
    volatile byte * ICRmaskPort = digitalPinToPCMSK (mcp [chip].aPin);
    *ICRmaskPort  |= bit (digitalPinToPCMSKbit (mcp [chip].aPin));
    //Serial.print ("\n*ICRmaskPort A: "); print8Bits (*ICRmaskPort);
    *ICRmaskPort  |= bit (digitalPinToPCMSKbit (mcp [chip].bPin));
    //Serial.print (", *ICRmaskPort B: "); print8Bits (*ICRmaskPort);

    // Enable this pin-change interrupt
    /*   Ref: thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/
      // PCICR definitions:
      PCICR |= 0b00000001;    // turn on port b
      PCICR |= 0b00000010;    // turn on port c
      PCICR |= 0b00000100;    // turn on port d
      PCICR |= 0b00000111;    // turn on all ports
    */
    PCICR |= bit (digitalPinToPCICRbit (mcp [chip].aPin));
    //Serial.print ("\nPCICR A: "); print8Bits (PCICR);
    PCICR |= bit (digitalPinToPCICRbit (mcp [chip].bPin));
    //Serial.print (", PCICR B: "); print8Bits (PCICR);

#ifdef DEBUG
    Serial.print ("\n\nBank[chip]: "); Serial.print (chip);
    Serial.print ("\n...mcp [chip].aPin: "); Serial.print (mcp [chip].aPin);
    Serial.print (", mcp [chip].bPin: "); Serial.print (mcp [chip].bPin);
    Serial.print (", mcp [chip].aBitMask: "); print8Bits(mcp [chip].aBitMask);
    Serial.print (", mcp [chip].bBitMask: "); print8Bits(mcp [chip].bBitMask);
    Serial.print ("\n...mcp [chip].whichAInterrupt: "); Serial.print (mcp [chip].whichAInterrupt);
    Serial.print (", mcp [chip].whichBInterrupt: "); Serial.print (mcp [chip].whichBInterrupt);
    Serial.print ("\n... *ICRmaskPort: "); print8Bits (*ICRmaskPort);
    Serial.print (", PCICR: "); print8Bits (PCICR);
    Serial.print (", PCMSK0: "); print8Bits (PCMSK0);
    Serial.print (", PCMSK1: "); print8Bits (PCMSK1);
    Serial.print (", PCMSK2: "); print8Bits (PCMSK2);
#endif

  } // end of Arduino interrupts for each expander

}  // end of setup


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function setChipPins()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// Set all MCP Chip pins to desired mode
void setChipPins() {
  // Example: pass the Bank object to the setPin function

  for (uint8_t chip = 0; chip < EXPANDERS; chip++) {
    switch (chip) {
      case 0:
        setPin(Bank0, chip);
        break;
      case 1:
        setPin(Bank1, chip);
        break;
      case 2:
        //setPin(Bank2, chip);
        break;
      case 3:
        //setPin(Bank3, chip);
        break;
      default:
        break;
    }
  }
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function setPin()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// Set all Port Expander pins to desired mode INPUT_PULLUP
// Set all Port Expander input pins as interrupts CHANGE
// CHANGE allows buttons to return to 0 (open)
void setPin(MCP23S17 &bank, uint8_t chip) {
  for (uint8_t ind = 0; ind <= 15; ind++) {
    bank.pinMode(ind, INPUT_PULLUP);
    bank.enableInterrupt(ind, CHANGE);
  }

  // set Port Expander Interrupt configuratons
  bank.setMirror(false);
  bank.setInterruptOD(false);
  bank.setInterruptLevel(LOW); // not used with OD

  // clear all interrupts on this Port Expander
  mcpReading = bank.getInterruptValue();
  //Serial.print ("\nBank"); Serial.print (chip);
  //Serial.print (".getInterruptValue() = "); print8Bits (mcpReading);

}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function readBankPortGPIOVal()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void readBankPortGPIOVal(MCP23S17 &bank, uint8_t chip, uint8_t port) {
  if (port < 1) {
    mcp[chip].mcpAGPIO = ~bank.readPort(port);
    mcp[chip].flagAGPIO = 1;
  } else {
    mcp[chip].mcpBGPIO = ~bank.readPort(port);
    mcp[chip].flagBGPIO = 1;
  }
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function interruptMCP()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void interruptMCP() {
  // Write the previously read .mcpGPIO values into joyReport btnArray
  //Serial.println ("\n");
  for (uint8_t chip = 0; chip < EXPANDERS; chip++) {
    delay (10);
    switch (chip) {
      case 0:
        joyReport.btnArray[0] = mcp[chip].mcpAGPIO;
        joyReport.btnArray[1] = mcp[chip].mcpBGPIO;
        break;
      case 1:
        joyReport.btnArray[2] = mcp[chip].mcpAGPIO;
        joyReport.btnArray[3] = mcp[chip].mcpBGPIO;
        break;
      default:
        break;
    }
  }

#ifdef DEBUG
  /*
    Serial.print ("\n\njoyReport btnArray: "); print8Bits (joyReport.btnArray[0]);
    Serial.print (", "); print8Bits (joyReport.btnArray[1]);
    Serial.print (", "); print8Bits (joyReport.btnArray[2]);
    Serial.print (", "); print8Bits (joyReport.btnArray[3]);
  */
#endif

}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function sendJoyReport()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

// Send an HID report to the USB interface
void sendJoyReport(struct joyReport_t *report)
{
#ifndef DEBUG
  //Serial.write((uint8_t *)report, sizeof(joyReport_t));
  // do not send duplicate values
  //
  if (memcmp( report, &prevjoyReport, sizeof( joyReport_t ) ) != 0)
  {
    Serial.write((uint8_t *)report, sizeof(joyReport_t));
    memcpy ( &prevjoyReport, report, sizeof( joyReport_t ) );
  }
  //
  // end do not send duplicate values
#else
  // dump human readable output for debugging
  Serial.println("\n");
  for (uint8_t ind = 0; ind < NUM_AXES; ind++) {
    Serial.print("axis[");
    Serial.print(ind);
    Serial.print("]= ");
    Serial.print(report->axis[ind]);
    Serial.print(" ");
  }
  Serial.println();
  for (uint8_t ind = 0; ind < NUM_BUTTONS / 8; ind++) {
    Serial.print("btnArray[");
    Serial.print(ind);
    Serial.print("]= ");
    print8Bits(report->btnArray[ind]);
    //Serial.print(report->btnArray[ind], HEX);
    Serial.print(" ");
  }

#endif
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// function loop
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void loop ()
{

  // This code runs the MCP Interrupt processing from the Arduino ISR
  //    and then takes action (checkForPinChange()) for the MCP pins that
  //    went LOW or HIGH.

  // checkForPinChange() flags that an interrupt (mcpFlag>0) has occurred
  //    and each mcp[chip].flagMCPA or mcp[chip].flagMCPB is set if a port pin
  //    changed state.

  // checkForPinChange() reads the MCP pins (readPort(0 or 1)).
  // interruptMCP() writes the MCP pin values into joyReport.

  // A pin setting in subsequent joyReports will remain ON as long as
  //    the switch is ON, and each joyReport that is sent (due to a different
  //    pin or axis change) will have that pin ON until the switch is OFF.
  //    When a switch turns off, that pin is cleared and a joyReport is sent.

  // process the digital input pins on MCP23S17's
  if (mcpFlag > 0) {
    interruptMCP(); // write the interrupt pins to joyReport
  }

  /* Axes connect to Analog pins A0, A1, A2...A7 */
  /* Arduino UNO has 6 analog pins of 8 possible. Set pin to 0 if not used */
  /* Ground any analog ports that are not connected to potentiometers to reduce noise */
  uint8_t axisCount = 3; // set the number of axes you want to use, 3=[0,1,2]
  for (uint8_t axis = 0; axis < axisCount; axis++) {
    int tmp = joyReport.axis[axis]; // copy previous axis value
    // Average 5 readings of port to get better values from noisy potentiometers
    // Use >5 to average more readings per pot
    long sumAxis = 0;
    int avg = 0;
    int count = 5;
    for (int i = 0; i < count; i++) {
      sumAxis = sumAxis + analogRead(axis);
    }
    avg = sumAxis / count;
    joyReport.axis[axis] = map(avg, 0, 1023, -32768, 32767 );

    // flag change in axis if avg reading changes by > 100
    if (abs(joyReport.axis[axis] - tmp) > 100) sendFlag = 1;
  }

  //Set un-used analog pins to 0 to reduce spurious values in joyReport.
  for (uint8_t i = axisCount; i < 8; i++) {
    joyReport.axis[i] = 0;
  }

  // for now, turn off axis data for testing digital inputs
  // comment out "sendFlag = 0;" line to use axes
  sendFlag = 0;

  if ((mcpFlag > 0) || (sendFlag > 0)) {
    //Send Data to HID
    sendJoyReport(&joyReport);
    mcpFlag = 0;
    sendFlag = 0;

    // Clear the MCP23S17 interrupt flags to allow new interrupts
    for (uint8_t chip = 0; chip < EXPANDERS; chip++) {
      switch (chip) {
        case 0:
          //mcpReading = Bank0.getInterruptValue(); // this is not necessary
          mcp[chip].flagMCPA = 0;
          mcp[chip].flagMCPB = 0;
          break;
        case 1:
          //mcpReading = Bank1.getInterruptValue(); // this is not necessary
          mcp[chip].flagMCPA = 0;
          mcp[chip].flagMCPB = 0;
          break;
        case 2:
          // mcpReading = Bank2.getInterruptValue(); // this is not necessary
          mcp[chip].flagMCPA = 0;
          mcp[chip].flagMCPB = 0;
          break;
        case 3:
          // mcpReading = Bank3.getInterruptValue(); // this is not necessary
          mcp[chip].flagMCPA = 0;
          mcp[chip].flagMCPB = 0;
          break;
        default:
          break;
      }
    }
  }

  delay (10); // give loop something to do while idle

}

#ifdef DEBUG
// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// print binary8 binary16 and hex functions
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

//---------------------------------------------------------------------------------
// print 8-bit byte as 8 bit binary string
//---------------------------------------------------------------------------------

void print8Bits(uint8_t myByte) {
  for (uint8_t mask = 0x80; mask; mask >>= 1) {
    if (mask  & myByte)
      Serial.print('1');
    else
      Serial.print('0');
  }
}

//---------------------------------------------------------------------------------
// print 16-bit word as 2 8-bit bit binary strings with space
//---------------------------------------------------------------------------------

void print16Bits(uint16_t myWord) {
  print8Bits(highByte(myWord));
  Serial.print (" ");
  print8Bits(lowByte(myWord));
  /*
    for (uint16_t mask = 0x8000; mask; mask >>= 1) {
    if (mask  & myWord)
      Serial.print('1');
    else
      Serial.print('0');
    }
  */
}

//---------------------------------------------------------------------------------
// crPrintHEX print value as hex with specified number of digits
//---------------------------------------------------------------------------------

void crPrintHEX(unsigned long DATA, unsigned char numChars) {
  unsigned long mask  = 0x0000000F;
  mask = mask << 4 * (numChars - 1);
  Serial.print("0x");
  for (unsigned int eID = numChars; eID > 0;  --eID) {
    Serial.print(((DATA & mask) >> (eID - 1) * 4), HEX);
    mask = mask >> 4;
  }
  Serial.print("  ");
}

#endif


Useful references -
(Jan 27, 2017)


No comments:

Post a Comment