Wednesday, February 1, 2017

Arduino with 8 KY040 Rotary Encoders

Adding 8 Rotary Encoders to an Arduino Mega 2560

A rotary encoder requires two pins to be connected to two digital input pins on an Arduino so that the software can determine the direction of turn of the encoder and increment or decrement a running count. A Mega 2560 has two 8-pin ports (B and K) that can be programmed as pin-change-interrupts for use with rotary encoders. The sketch provided below will accommodate 8 rotary encoders on an Arduino Mega 2560.

An earlier blog, "Arduino and KY040 Rotary Encoder (January 10, 2017)" provided a sketch which provided a report of microseconds between A-B switch changes as an encoder was rotated. That sketch provided a good understanding of what was happening inside the KY040, as far as which pin (A or B) changed state first (which determines the direction of turn) and how many extraneous toggles of the A or B switches were being made during a one-detent rotation.

A subsequent blog, "Arduino and FSX (Part 6) (January 23, 2017)" provided a sketch that added 32 pins for buttons or switches to an Arduino using MCP23S17 Port Expanders. That sketch was modified in "Arduino and FSX (Part 7)" to use interrupts that were mirrored and open-drain to provide 32 pins for buttons or switches. The next blog in that series "Arduino and FSX (Part 8)" provided a sketch for adding 16 rotary encoders to an Arduino with two 16-pin MCP23S17 Port Expanders. The sketch incorporated pin-change-interrupts on both the MCP23S17 Port Expanders and the Arduino.

The sketch provided below eliminates the use of the Port Expanders by using the Mega 2560 which has two 8 pin-change-interrupt ports. While each port expander provides 16 pins for 8 rotary encoders, the Mega 2560 maxes out with 8 encoders.

If you need more than 8 encoders, build a board with multiple MCP23S17 Port Expanders. Eight Port Expanders can be used together using just 4 SPI address pins to provide 64 rotary encoders.

Hardware --

The hardware used with this sketch is straightforward: Arduino Mega 2560, 8 (KY040) rotary encoders, wiring from ground and +5V, and encoder pins A (CLK) and B (DT) to Arduino pins {53, A8}, {52, A9}, {51, A10}, {50, A11}, {10, A12}, {11, A13}, {12, A14}, and {13, A15}.

Sketch Software --

The sketch senses a HIGH-LOW pin change on either or both pin A and B of each rotary encoder. Each input pin is programmed as pin-change-interrupt, so when the pin state changes, a port interrupt signal triggers an ISR (interrupt service routine). While the interrupt is held, port pin values are read (saved in memory) and flags are set to indicate what caused the interrupt. Then the interrupts are cleared. The CPU can then go back to doing loop() processing or handle another interrupt.

One detent turn clockwise of the encoder causes the A pin to change from HIGH-LOW, then the B pin changes from HIGH-LOW, then the A pin changes LOW-HIGH, and finally the B pin changes LOW-HIGH, to complete the turn. A counter-clockwise turn causes the B pin to change first, then the A pin, and so on.

Within loop(), between interrupts, the previously set flags are used to determine how to process the pin state data that was captured during the port reads. The data from successive interrupts are accumulated to determine the direction of turn of the encoder and to increment or decrement a running sum for each encoder.

Sketch --



/*

   Sketch - Arduino_ISR_KY040_Mega2560_02_8X40Joystick_Oleg

   Lowell Bahner
   February, 2017

   This code sends joyStick data when rotary encoders change values.

   This code uses 16 pins on 2 ports on a Mega2560 for 8 KY040 rotary encoders.
   Other encoders may also work but have not been tested with this code.
   Each encoder pin is on a separate port (Port B and Port K)
   Ref: www_atmel.com/Images/Atmel-2549-8-bit-AVR-Microcontroller-ATmega640-
               1280-1281-2560-2561_datasheet.pdf

   8 Rotary Encoders are connected to the 16 Arduino pins. Each encoder CLK "A" pin
   is connected to a successive Mega Port B pin [0,1,...7] and the encoder DT "B" pin
   is connected to the Mega Port K pin [A8,A9,...A15].

   The function "processRE()" code is adapted from Oleg Mazurov (2011 Mar 30)
   Ref: www_circuitsathome.com/mcu/rotary-encoder-
                interrupt-service-routine-for-avr-micros/

   This code uses 2 ISR's (one per port) to sense port interrupts on the two Mega ports.

   A one detent turn of an encoder takes either the "A" or "B" pin LOW (ground)
   which triggers the respective Arduino port interrupt. That interrupt triggers
   the Arduino ISR code which reads the port pins and sets flags for further 
   processing to determine the encoder direction of turn and the number of turns. 
   Each port interrupt is processed independently of other interrupts.

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

   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 Mega 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

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

   Hardware Wiring Example: KY040 Rotary Encoder #0, pin CLK "A" wired
      to Ard pin 53 (on port B), and Rotary Encoder #0, pin DT "B" wired
      to Ard pin A8 (on port K).

   Arduino digital pins are set as pin-change-interrupt INPUT_PULLUP
      which trigger the port interrupt when state changes HIGH-LOW or LOW-HIGH.

   A clockwise (CW) turn of a Rotary Encoder evaluates to -1.
   A counter-clockwise (CCW) turn of a Rotary Encoder evaluates to 1.

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

*/

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

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

const byte ENCODERS = 8;        // 8 rotary encoders on 2 pciPorts
const byte NUM_BUTTONS = 40;    // do not change this value.
const byte NUM_AXES = 8;        // 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 joyReport data change

volatile uint8_t reFlag = 0;          // only send RE data when joyReport RE data change
volatile uint8_t pciPortReadFlag [3]; // flag if pciPort was read
volatile uint8_t pciPortRead [3];     // save the port interrupt pin value byte

// create the ENCODER struct
typedef struct
{
  int reAPin;     // which RE pin for the "A" side interrupt
  int reBPin;     // which RE pin for the "B" side interrupt

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

  volatile uint8_t reAB;       // retained value
  volatile int8_t reValue;       // current RE value -1, 1
  volatile int32_t reCounter;   // running count for this re[0,1]
  volatile uint8_t reAPinGPIO;   // current value reAPin
  volatile uint8_t reBPinGPIO;   // current value reBPin
  //volatile uint8_t reAPinPrev;   // previous value reAPin
  //volatile uint8_t reBPinPrev;   // previous value reBPin
  volatile uint8_t reAFlag;      // reAFlag
  volatile uint8_t reBFlag;      // reBFlag

} RE;

// Use these arrays for encoder A pins on Port B and B pins on Port K interrupts
volatile RE re[ENCODERS] = {
  {53, A8},
  {52, A9},
  {51, A10},
  {50, A11},
  {10, A12},
  {11, A13},
  {12, A14},
  {13, A15},
}; // end of encoders


// Mega 2560 External Interrupt pins, 2, 3, 18, 19, 20, & 21 (not used in this code)

// Mega 2560 Pin-Change-Interupt Ports and pins
// PB7...PB4 DPins 13...10 and PB3...PB0 DPins 50...53 (PCINT7...0)
// PK7...PK0 APins A15...A8 (PCINT23...16)

// 8 Rotary Encoders are possible with 16 PCI pins across two 8-pin ports PB and PK
// Mega 2560 PCI pins: PB7...PB0 D13...D10 & D50...D53 (no SPI possible)
// Mega 2560 PCI pins: PK7...PK0 A15...A8

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


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

// pin change interrupts
void checkForPinChange (const byte pciPort);
ISR (PCINT0_vect);
//ISR (PCINT1_vect);
ISR (PCINT2_vect);
void setup();
void sendJoyReport(struct joyReport_t *report);
void processRE(uint8_t pciPort);
void loop ();
void print8Bits(uint8_t myByte);
void print16Bits(uint16_t myWord);
void crPrintHEX(unsigned long DATA, unsigned char numChars);

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


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

// Rotary encoder pin change triggers "pciPort" Arduino ISR.
// pciPort==0 is Ard Port B, pciPort==1 is not used, pciPort==2 is Ard Port K.
// Once the ISR finishes, interrupts are cleared, then loop() processes the pins.
// The pin data are written to the joyReport data structure for sending out USB.

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

  if (pciPort == 0) {
    pciPortRead [pciPort] = ~PINB;
    pciPortReadFlag [pciPort] = 1;
  } else if (pciPort == 1) {
    pciPortRead [pciPort] = 0b0;
    pciPortReadFlag [pciPort] = 1;
  } else if (pciPort == 2) {
    pciPortRead [pciPort] = ~PINK;
    pciPortReadFlag [pciPort] = 1;
  }

  for (uint8_t reID = 0; reID < ENCODERS; reID++) {
    if (re[reID].pciPortIntA == pciPort) {
      if ((digitalRead(re[reID].reAPin) == LOW)) {
        re[reID].reAFlag = 1;
#ifdef DEBUG
        /*
        // print what pins are changing and in what order of interrupt
        Serial.print ("\n, reAFlag: "); Serial.print (re[reID].reAFlag);
        Serial.print (", pciPortRead [pciPort]: "); print8Bits (pciPortRead [pciPort]);
        */
#endif
      }
    }
    if (re[reID].pciPortIntB == pciPort) {
      if ((digitalRead(re[reID].reBPin) == LOW)) {
        re[reID].reBFlag = 1;
#ifdef DEBUG
        /*
        // print what pins are changing and in what order of interrupt
        Serial.print ("\n, reBFlag: "); Serial.print (re[reID].reBFlag);
        Serial.print (", pciPortRead [pciPort]: "); print8Bits (pciPortRead [pciPort]);
        */
#endif
      }
    }
  }     // end of for each encoder
} // end of checkForPinChange

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

// handle pin change interrupt for Mega 2560 pins 53-50 & 10-13
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 not used
//ISR (PCINT1_vect)
//{
//  cli(); //stop interrupts happening before we read pin values
//  checkForPinChange (PCIE1);
//  sei(); //restart interrupts
//}  // end of PCINT1_vect

// ++++++++++++++++++++++++++++++++++++++++++++++++++++
//   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() {

  Serial.begin (115200);

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

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

  for (uint8_t reID = 0; reID < ENCODERS; reID++)
  {

    // set interrupt pins to INPUT_PULLUP
    pinMode (re[reID].reAPin, INPUT_PULLUP);
    pinMode (re[reID].reBPin, INPUT_PULLUP);

    // Create a Mask with the pin bit = 1
    re[reID].aBitMask = digitalPinToBitMask (re[reID].reAPin);
    re[reID].bBitMask = digitalPinToBitMask (re[reID].reBPin);

    // Which ISR for each interrupt pin (0=PCIE0, 1=PCIE1, 2=PCIE2)
    re[reID].pciPortIntA = digitalPinToPCICRbit (re[reID].reAPin);
    re[reID].pciPortIntB = digitalPinToPCICRbit (re[reID].reBPin);

    // Activate this pin-change interrupt bit (eg. PCMSK0, PCMSK1, PCMSK2)
    /*   Ref: thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/
      // Example PCMSK definitions:
      PCMSK0 |= 0b00000011;    // turn on pins PB0 & PB1, PCINT0 & PCINT1, pins D8, D9
      PCMSK1 |= 0b00010000;    // turn on pin PC4, pciPort is PCINT12, pin A4
      PCMSK2 |= 0b00001100;    // turn on pins PD2 & PD3, PCINT18 & PCINT19, pins D2, D3
    */

    PCMSK0 |= 0b11111111;    // turn on port b pins PCINT0 to PCINT7
    PCMSK1 |= 0b00000000;    // not used
    PCMSK2 |= 0b11111111;    // turn on port k pins PCINT16 to PCINT23

    // Enable this pin-change interrupt
    /*   Ref: thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/
      // Example 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 (re[reID].reAPin));
    //Serial.print ("\nPCICR A: "); print8Bits (PCICR);
    PCICR |= bit (digitalPinToPCICRbit (re[reID].reBPin));
    //Serial.print (", PCICR B: "); print8Bits (PCICR);

#ifdef DEBUG
    // examine the Arduino ISR setup
    Serial.print ("\n\nMega 2560: ");
    Serial.print ("\n...re[reID].reAPin: "); Serial.print (re[reID].reAPin);
    Serial.print (", re[reID].reBPin: "); Serial.print (re[reID].reBPin);
    Serial.print (", re[reID].aBitMask: "); print8Bits(re[reID].aBitMask);
    Serial.print (", re[reID].bBitMask: "); print8Bits(re[reID].bBitMask);
    Serial.print ("\n...re[reID].pciPortIntA: "); Serial.print (re[reID].pciPortIntA);
    Serial.print (", re[reID].pciPortIntB: "); Serial.print (re[reID].pciPortIntB);
    Serial.print (", PCICR: "); print8Bits (PCICR);
    Serial.print (", PCMSK0: "); print8Bits (PCMSK0);
    Serial.print (", PCMSK1: "); print8Bits (PCMSK1);
    Serial.print (", PCMSK2: "); print8Bits (PCMSK2);
#endif

    // initalize the 8 encoders on each port
    for (uint8_t reID = 0; reID < ENCODERS; reID++) {
      //re[reID].reAPin = RE pin for the "A" side interrupt
      //re[reID].reBPin = RE pin for the "B" side interrupt
      re[reID].reAB = 3;         // old_AB
      re[reID].reValue = 0;      // current RE value -1, 1
      re[reID].reCounter = 0;    // running count for this re[0,1]
      re[reID].reAPinGPIO = 0;   // current value reAPin
      re[reID].reBPinGPIO = 0;   // current value reBPin
      //re[reID].reAPinPrev = 0;   // previous value reAPin
      //re[reID].reBPinPrev = 0;   // previous value reBPin
      re[reID].reAFlag = 0;      // reAFlag
      re[reID].reBFlag = 0;      // reBFlag
    } // end of setup encoders

  } // end of Arduino interrupts for each expander

}  // end of setup


// ++++++++++++++++++++++++++++++++++++++++++++++++++++
// 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 processRE()
// ++++++++++++++++++++++++++++++++++++++++++++++++++++

void processRE(uint8_t pciPort) {

  /*
   * Process the encoders on each expander
   * 4 ENCODERS per Arduino Port Interrupt
   *
      for (uint8_t reID = 0; reID < ENCODERS; reID++) {
        re[reID].reAPin = read array;    // which RE pin for the "A" side interrupt
        re[reID].reBPin = read array;    // which RE pin for the "B" side interrupt
        re[reID].reValue = 0;      // current RE value -1, 1
        re[reID].reCounter = 0;    // running count for this re[0,1]
        re[reID].reAPinGPIO = 0;   // current value reAPin
        re[reID].reBPinGPIO = 0;   // current value reBPin
        re[reID].reAPinPrev = 0;   // previous value reAPin
        re[reID].reBPinPrev = 0;   // previous value reBPin
        re[reID].reAFlag = 0;      // reAFlag
        re[reID].reBFlag = 0;      // reBFlag
      } // end of setup encoders

  */

  // ++++++++++++++++++++++++++++++++++++++++++++++++++++
  // this code is adapted from Oleg Mazurov (2011 Mar 30)
  // Ref: www_circuitsathome.com/mcu/rotary-encoder-
  //            interrupt-service-routine-for-avr-micros/
  // ++++++++++++++++++++++++++++++++++++++++++++++++++++

  // This code determines the direction of turn and cumulative count
  //     for up to 8 rotary encoders on 1 Arduino Mega 2560 using 2 8-pin ports.
  // A turn of a rotary encoder on a port
  //     causes a series of pin change interrupts
  //     that triggers this code for each interrupt.
  // For the KY040 Rotary Encoder:
  //    pin CLK ==> RE Pin reAPin, pin DT ==> RE Pin reBPin
  // The reAPin goes to port B [0...7] pins (53, 52, 51, 50, 10, 11, 12, 13)
  // The reBPin goes to port K [0...7] pins (A8, A9, A10, A11, A12, A13, A14, A15)
  // This arrangement gives a separate port interrupt for each pin of an encoder

  static const int8_t enc_states [] PROGMEM =
  {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}; //encoder lookup table

  // cycle through each of 8 rotary encoders
  // with pin A on Port B and pin B on Port K
  for (uint8_t reID = 0; reID < ENCODERS; reID++) {

    // recall retained value of old_AB
    uint8_t old_AB = re[reID].reAB;
    uint8_t encport = 0;
    int8_t dir;

    // get the expander:encoder:pinA, pinB values at time of interrupt
    // eg, Port_B [0] & Port_K [0], Port_B [5] & Port_K [5]

    // for encoders with pin A on Port B and pin B on Port K
    re[reID].reAPinGPIO = bitRead(pciPortRead [re[reID].pciPortIntA], reID);
    re[reID].reBPinGPIO = bitRead(pciPortRead [re[reID].pciPortIntB], reID);

    // check if this RE sent a signal, if not, ignore it
    if ((re[reID].reAPinGPIO > 0) || (re[reID].reBPinGPIO > 0)) {
      old_AB <<= 2; //remember previous RE state and shift-left by 2 bits
      // copy encoder pin values to encport bits 1,0
      if (re[reID].reAPinGPIO > 0) bitSet(encport, 0);
      if (re[reID].reBPinGPIO > 0) bitSet(encport, 1);
      //copy bits 1,0 to old_AB
      old_AB |= encport & 0x03;
      // use index to obtain direction and state
      dir = pgm_read_byte(&(enc_states[( old_AB & 0x0f )]));
      //check if at detent and transition is valid
      // dir=1 CCW, dir=-1 CW
      if ( dir && ( encport == 3 )) {
        re[reID].reValue = dir;
        re[reID].reCounter = re[reID].reCounter - dir;

        reFlag = 1; // joyReport data changed flag
        // place the 8-encoder data into
        //       joyReport [ 0 and 1 ]

        if (dir < 0) bitSet(joyReport.btnArray[1], reID); // CW data
        if (dir > 0) bitSet(joyReport.btnArray[0], reID); // CCW data

        // btnArray[2] and btnArray[3] are available for use with 16 switches
        // connected to other Mega 2560 pins (code has to be added)

#ifdef DEBUG

        Serial.print ("\n RE["); Serial.print (reID); Serial.print ("]");
        Serial.print (", encport: "); print8Bits (encport);
        Serial.print (", old_AB: "); print8Bits (old_AB);
        Serial.print (", enc_states["); Serial.print (old_AB & 0x0f); Serial.print ("]: ");
        Serial.print (" dir: "); Serial.print (dir);
        if ( dir == 1 ) {
          Serial.print (", CCW");
        }
        else {
          Serial.print (", CW");
        }
        Serial.print (", Count "); Serial.print ( re[reID].reCounter);
#endif
      } // end if (dir...
    } // end if ((re[reID].reAPinGPIO...

    // retain settings for this encoder for next indent comparison
    re[reID].reAB = old_AB;

  } // end encoder  for (uint8_t reID...

} // end code adapted from Oleg Mazurov (2011 Mar 30)


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

void loop ()
{

  // This code runs Arduino Mega 2560 pin-change-interrupt ISR's
  //    and then takes action (checkForPinChange()) for the Arduino pins that
  //    went LOW or HIGH. Port B and Port K pins are set as pin-change-interrupts.

  // checkForPinChange() flags that a pin interrupt (re[reID].reAFlag>0) has occurred
  //    and each pciPortReadFlag[pciPort] is set if a port pin
  //    changed state.

  // checkForPinChange() reads the inverse value of the port pins
  //   (pciPortRead[pciPort] = ~PINB;) and  (pciPortRead[pciPort] = ~PINK;)
  // processRE(pciPort) writes the encoder values into joyReport[0], [1].

  // 16 switches could be added to this code and their values into joyReport[2], [3].

  // process Rotary Encoders
  for (uint8_t pciPort = 0; pciPort < 3; pciPort++) {
    if (pciPortReadFlag [pciPort] > 0) {
      processRE(pciPort);
    }
  }

  /* 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 = 0; // 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 ((reFlag > 0) || (sendFlag > 0)) {
    //Send Data to HID
    sendJoyReport(&joyReport);

    // clear the joyReport
    sendFlag = 0;
    reFlag = 0;
    joyReport.btnArray[0] = 0b0;
    joyReport.btnArray[1] = 0b0;
    joyReport.btnArray[2] = 0b0;
    joyReport.btnArray[3] = 0b0;
    joyReport.btnArray[4] = 0b0;
  }
  // clear pci port read flags
  for (uint8_t pciPort = 0; pciPort < 3; pciPort++) {
    pciPortReadFlag [pciPort] = 0;
  }

  //}
  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







(February 1, 2017)


1 comment:

  1. This blog post is truly remarkable! The way you’ve presented the topic is both engaging and insightful. It’s evident that a lot of effort and thought went into creating this content. Thank you for sharing such valuable perspectives. I’m excited to explore more of your work in the future!
    Rotary Encoders Enrgtech
    Electronicssecret

    ReplyDelete