Reading 8 Rotary Encoders with MEGA 2560 and Multiplexer

Hello All,

Sorry if this narrows in on other posted topics, but I truly am having trouble with finding an answer for my particular situation.

I am simply looking to read 8 rotary encoders and have each output different midi messages thru serial. Since it looks like the 2560 can only handle 6 (from reading), what other methods can be used to make something like this happen? Is a multiplexer a possibility?

The 2560 is flashed with Hiduino so sending and receiving midi is already possible via USB, and working with other functions. Just looking to add the encoders now. I don't have encoders to test (yet...) but I am dying to figure this out!

Thanks in advance for any suggestions, help and hints!

A multiplexer will tend to miss pulses, unless it consumes all processing time.

I'd go for bus extenders, which will give an interrupt whenever a pin changes state, or with PCINT (pin change interrupt), where a single ISR can serve 1..4 encoders connected to the same port.

I would worry that the Hiduino software would take up too much processing power to allow you to use that many encoders. I would recommend a processor that can handle USB directly like the Arduno Micro.

The other thing is that as MIDI has a fixed range of values the continuous nature of a rotary encoder is not a good design match, what is wrong with a conventional pot?

The Hiduino firmware is flashed to the 16U2 micro and not the main microprocessor. It handles lots of information in real time now and working great. It is controlling 8 slide pots and 38 buttons (with accompanying LEDs) now, and 192 LED's using shift registers, acting as VU Meters. All this is sending and receiving midi over USB with very little delay.

The reason I need encoders and not potentiometers is because they will be controlling virtual pots that have changing values based on the file that is open. That one encoder out of eight will also be responsible for adjusting multiple virtual pots that have two different values.

There are lots of control surfaces out there with 8 or more encoders, and I would be willing to add any type of IC to the Arduino circuit to get these 8 encoders working (like I did adding shift registers). I just don't really know what direction to start. Is it even possible?

I can post the code I have now to describe how it's all working at this point if anyone is interested in seeing how it works. The encoders are pretty much my missing element. I appreciate any and all help!

I like the PCINT option, but would this introduce latency? I guess that's my biggest concern. I'm still a novice and this is all a bit over my head.

I found this IC that might be a work around for the lack of interrupt pins on the 2560... am I correct in thinking that something like this will work to output data to the 2560 without the use of INT pins?

That encoder chip is useful with mechanical switches (debounce...), but it does not reduce the number of required controller pins and interrupts.

Latency may be an issue with motor speed feedback, but not with manually operated encoders. For manual input the sensitivity (impulse rate) must be low, so that a human can adjust the count to a desired value. And some (visual/audible) feedback will tell the user when the intended value is reached - just like with moving a mouse until the mouse pointer reaches the button to press. That's why interrupts are not required in your project, but their use will simplify the code. What kind of user feedback do you have in mind?

Port extenders are shift registers, possibly with useful ads. Some have I2C interface instead of SPI, others signal changes on any pin over a single interrupt line. That interrupt line does what a PCINT does without external hardware. You can start with a decoder function which you call it in every iteration of loop(). If it turns out to miss too many pulses, the same function can be used as a PCINT handler. See the Encoder tutorial how simple such a function will be.

Latency will be higher with external components, because reading (shift in) the external pins requires many processor clocks in the ISR. A PCINT ISR instead can read the signal states directly from the associated controller port.

The feedback I am looking for is really not critical. Simply a turn of the encoder in either direction will transmit a specific 3 byte message that will control the on-screen control. In most cases, an increase in speed while twisting the encoder should change the 3rd byte (velocity) and translating such on-screen.

My concern with latency is that using interrupts, from my understanding, interrupts the processor which is currently driving my LEDs. So would a turn of an encoder freeze my LEDs until I am finished, or would this happen so quickly I wouldn't notice? I'm not sure...

... but I am slightly confused because you mentioned that "interrupts are not required for my project". I understand the coding would be difficult most likely for me, but if using 8 rotary encoders with any available digital pins I have on the 2560 is a possibility that is great news. Again, the encoders aren't required to be extremely accurate, they just need to get the message across in a reasonable amount of time (a few ms?)

I hope this is making sense, is I'm trying my best to understand my options. Thanks for the help!

BTW, here is the code for what the 2560 is working on right now for these LED VU meters... Strictly midi library stuff, so I need to figure out how to take rotary encoder data and convert that back to my program.

#include <MIDI.h>
#include <midi_Defs.h>
#include <midi_Message.h>
#include <midi_Namespace.h>
#include <midi_Settings.h>

MIDI_CREATE_INSTANCE(HardwareSerial, Serial3, controller);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial, pt);

byte lastChannel = 0;
byte pp[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

// Meter pins per channel:
//   - Latch pin connected to ST_CP of 74HC595
//   - Clock pin connected to SH_CP of 74HC595
//   - Data pin connected to DS of 74HC595

#define CHANNELS 8
typedef enum { LATCH = 0, CLOCK = 1, DATA = 2, METER = 3, PEAK = 4, CLIP = 5 } channel_index_t;

// LEFT:
//{ LATCH, CLOCK, DATA,         METER, PEAK, CLIP } ***DO NOT USE 0,1,13,14,15***`
byte meter_left[CHANNELS][6] = { 
  {  2,  3, 4,                 0,  0,  0 }, 
  {  6,  3, 8,                 0,  0,  0 },   
  {  9, 3, 12,                 0,  0,  0 },   
  { 11, 3, 19,                 0,  0,  0 },
  { 17, 3, 23,                 0,  0,  0 },
  { 20, 3, 27,                 0,  0,  0 },
  { 22, 3, 28,                 0,  0,  0 },
  { 25, 3, 29,                 0,  0,  0 }
};

// RIGHT:
//{ LATCH, CLOCK, DATA,         METER, PEAK, CLIP } ***DO NOT USE 0,1,13,14,15***
byte meter_right[CHANNELS][6] = { 
  {  5, 3, 4,                  0,  0,  0 },
  {  7, 3, 8,                  0,  0,  0 },   
  { 10, 3, 12,                 0,  0,  0 },    
  { 16, 3, 19,                 0,  0,  0 },
  { 18, 3, 23,                 0,  0,  0 },
  { 21, 3, 27,                 0,  0,  0 },
  { 24, 3, 28,                 0,  0,  0 },
  { 26, 3, 29,                 0,  0,  0 }
};

void PTHandleAfterTouchPoly(byte channel, byte note, byte pressure)
{ 
  /*
  0xA00ysv
  
  [0xA0]
      0xA_ : AfterTouchPoly
      0x_0 : channel #1
  
  [0x0y] -> note
      y = channel (0..7)

  [0xsv] -> pressure
  
  s : side (left/right)
      s = 0 : side = left; meter
      s = 1 : side = right; meter
      s = 2 : side = left; peak hold
      s = 3 : side = right; peak hold
      s = 4 : side = left; meter & clip
      s = 5 : side = right; meter & clip
      s = 6 : side = left; peak hold & clip
      s = 7 : side = right; peak hold & clip
      
  v : value (0..b)
      v = b : signal >= -2dB; yellow
      v = a : signal >= -4dB; yellow
      v = 9 : signal >= -6dB; yellow
      v = 8 : signal >= -8dB; green
      v = 7 : signal >= -10dB; green
      v = 6 : signal >= -14dB; green
      v = 5 : signal >= -20dB; green
      v = 4 : signal >= -30dB; green
      v = 3 : signal >= -40dB; green
      v = 2 : signal >= -50dB; green
      v = 1 : signal >= -60dB; green
      v = 0 : signal < -60dB; all leds off
  */
  
  // 0xA0
  if (channel == 1) {
    int huv_channel = (note & 0x0f);
    int huv_code = (pressure & 0xf0) >> 4;
    int huv_value = (pressure & 0x0f);
    
    if (0 <= huv_channel && huv_channel < CHANNELS) {
    
      // Right or left channel
      byte *meter = (huv_code & 0x1) ? meter_right[huv_channel] : meter_left[huv_channel];
      switch (huv_code) {
  
        case 0x0:         // Left channel metering
        case 0x1:         // Right channel metering
          meter[METER] = huv_value;
          if (huv_value == 0x00) meter[CLIP] = 0x00;
          break;

        case 0x2:         // Left channel peak hold
        case 0x3:         // Right channel peak hold
          meter[PEAK] = huv_value;
          break;

        case 0x4:         // Left channel metering with Clip LED #12
        case 0x5:         // Right channel metering with Clip LED #12        
          meter[METER] = huv_value;        
          meter[CLIP] = 0x0c;
          break;

        case 0x6:         // Left channel peak hold with Clip LED #12
        case 0x7:         // Right channel peak hold with Clip LED #12
          meter[PEAK] = huv_value;        
          meter[CLIP] = 0x0c;
          break;

        default:
          // we do not support any other commands
          return;
      }
      
      shiftMeter(meter[LATCH], meter[DATA], meter[CLOCK], meter[METER], meter[PEAK], meter[CLIP]);
    }
    
    return;
  }
  
  controller.sendPolyPressure(note, pressure, channel);
}

void PTHandleAfterTouchChannel(byte channel, byte pressure)
{
  controller.sendAfterTouch(pressure, channel);
}


void ControllerHandleAfterTouchPoly(byte channel, byte note, byte pressure)
{
  pt.sendPolyPressure(note, pressure, channel);
}

void shiftMeter(byte latchPin, byte myDataPin, byte myClockPin, byte meter, byte peak, byte clip) {
  int pinState;
  pinMode(latchPin, OUTPUT);
  pinMode(myClockPin, OUTPUT);
  pinMode(myDataPin, OUTPUT);

  //ground latchPin and hold low for as long as you are transmitting
  digitalWrite(latchPin, 0);

  //clear everything out just in case to
  //prepare shift register for bit shifting
  digitalWrite(myDataPin, 0);
  digitalWrite(myClockPin, 0);

  int i;
  for (i = 15; i >= 0; i--)  {
    digitalWrite(myClockPin, 0);
    pinState = ((i < meter) || 
                (peak != 0 && i == (peak - 1)) || 
                (clip != 0 && i == (clip - 1))) ? 1 : 0;

    //Sets the pin to HIGH or LOW depending on pinState
    digitalWrite(myDataPin, pinState);
    //register shifts bits on upstroke of clock pin  
    digitalWrite(myClockPin, 1);
    //zero the data pin after shift to prevent bleed through
    digitalWrite(myDataPin, 0);
  }

  //stop shifting
  digitalWrite(myClockPin, 0);
  
  //return the latch pin high to signal chip that it 
  //no longer needs to listen for information
  digitalWrite(latchPin, 1);
}


void setup()
{
    // Initiate MIDI communications, listen to all channels
    controller.begin(MIDI_CHANNEL_OMNI);
    pt.begin(MIDI_CHANNEL_OMNI);
    
    Serial3.begin(38400);
    
    controller.setThruFilterMode(midi::Off);
    pt.setThruFilterMode(midi::Off);
    pt.setHandleAfterTouchPoly(PTHandleAfterTouchPoly);
    controller.setHandleAfterTouchPoly(ControllerHandleAfterTouchPoly);
}

void loop()
{
  controller.read();
  pt.read();
}

IMO you worry too much about speed. The controller includes hardware for serial connections, SPI, PWM and other features, which work in parallel and don't require much processing in code.

How many steps per revolution do your encoders output? I'd try to add one encoder to your sketch, and watch its output on the Serial Monitor. If this works at all, add more encoders. In case of problems (lost steps) you can start using interrupts at any time.

You would not get an LED freeze or anything like it using interrupts. From my experience you have to look at ( pole ) every 1mS so as not to miss a pulse from a hand turned rotary encoder. Remember you have only got two hands so there will only be two going at any one time,
If there is feedback then missing the odd pulse doesn't matter. Think what a mouse does, rapid movement back and forward has the pointer moving off screen so you pick up the mouse and move it. In other words the feedback you get allows you to compensate. It will be the same with your project. If yo do want to use interrupts, which is not necessary, then you can use the pin change interrupt and have as many pins as you want generate interrupts. If you only monitor one pin of the encoder for change by pole or interrupt you half the processing required but you also half the resolution.

ibanman555:
My concern with latency is that using interrupts, from my understanding, interrupts the processor which is currently driving my LEDs. So would a turn of an encoder freeze my LEDs until I am finished, or would this happen so quickly I wouldn't notice? I'm not sure...

So initially you started with a MEGA and 8 rotary encoders and MIDI, and now there also seems to be LEDs and huge amounts of interrupts already active?!

What about a full hardware list and wiring diagram and code you have until now?

jurs:
So initially you started with a MEGA and 8 rotary encoders and MIDI, and now there also seems to be LEDs and huge amounts of interrupts already active?!
What about a full hardware list and wiring diagram and code you have until now?

Wait.... I'm using a huge amount of interrupts already based on what you see in my sketch???

I'll explain again for anyone who may have missed something. The LED's and accompanying sketch are what the Mega are working on right now. The wiring diagram and hardware list for that can be found Here (x24 meters) This is an important hardware function and I didn't want to screw it up with encoders. All I want to do is add 8 encoders and create 3 byte hex messsages from the encoder data back to USB. My original question was can I use rotary encoders on any digital pin. The answer appears to be yes, i guess. Interrupts aren't needed for my project.

DrDiettrich:
How many steps per revolution do your encoders output? I'd try to add one encoder to your sketch, and watch its output on the Serial Monitor.

As also previously mentioned I don't have any encoders to test with yet. So once I get some I will test out. The reason I was looking for knowledgeable opinions is because you will not find anyone attaching multiple rotary encoders to an Arduino directly, at least that I could find. And I kept reading about the use of interrupts... it appeared that it was nessesary.

DrDiettrich:
That encoder chip is useful with mechanical switches (debounce...), but it does not reduce the number of required controller pins and interrupts.

Latency may be an issue with motor speed feedback, but not with manually operated encoders. For manual input the sensitivity (impulse rate) must be low, so that a human can adjust the count to a desired value. And some (visual/audible) feedback will tell the user when the intended value is reached - just like with moving a mouse until the mouse pointer reaches the button to press. That's why interrupts are not required in your project, but their use will simplify the code. What kind of user feedback do you have in mind?

Port extenders are shift registers, possibly with useful ads. Some have I2C interface instead of SPI, others signal changes on any pin over a single interrupt line. That interrupt line does what a PCINT does without external hardware. You can start with a decoder function which you call it in every iteration of loop(). If it turns out to miss too many pulses, the same function can be used as a PCINT handler. See the Encoder tutorial how simple such a function will be.

Latency will be higher with external components, because reading (shift in) the external pins requires many processor clocks in the ISR. A PCINT ISR instead can read the signal states directly from the associated controller port.

Still trying to wrap my head around this.

Side note: I did find this link and it seems to be focused around what the Dr was talking about with PCINT ISR.... thoughts?

http://openhardware.ro/rotary-encoders-experiments-part-2/

I'm using a huge amount of interrupts already based on what you see in my sketch???

Can't see any extra interrupt processing here apart from the MIDI library.

The wiring diagram and hardware list for that can be found Here (x24 meters)

That is a rough tutorial, it is wrong. The capacitor should not be on the latch pin. This is an ongoing problem with these official tutorials. Many people have pointed out the problem but nothing gets done. Each chip should also have a 0.1Uf ceramic capacitor placed between the power pin and ground, as close to the chip as possible and with as short a lead length on the capacitor as possible.

What does the (x24 meters) bit mean?

I didn't see that fine page before, but it demonstrates the use of PCINT with many encoders. You'll have to exclude the code for unused pins, if you want to use them for other purposes.

Grumpy_Mike:
Can't see any extra interrupt processing here apart from the MIDI library.
That is a rough tutorial, it is wrong. The capacitor should not be on the latch pin. This is an ongoing problem with these official tutorials. Many people have pointed out the problem but nothing gets done. Each chip should also have a 0.1Uf ceramic capacitor placed between the power pin and ground, as close to the chip as possible and with as short a lead length on the capacitor as possible.

What does the (x24 meters) bit mean?

I didn't know this... Note taken. x24 means I took this shift out circuit and made 24 of them.

Thanks DrDiettrich... So this is a good starting point?

x24 means I took this shift out circuit and made 24 of them.

So the meters bit is a typo then.

with 24 shift registers you have 192 outputs, is that correct? Do you need that many? Using that many shift registers I would buffer the clock and latch pins into two groups using some 74HCT04 inverts or similar.
Also the shift out function will be slow for this many registers, I would use the SPI interface to drive this at the maximum speed.

192 is correct. Each LED needs to respond to a specific 3 byte message. I'll look at the T04. Not worried about it much though. It's actually working great. My only concern now are the rotary encoders and how to apply them to my existing sketch and circuit.

Howdy,
I'm working on a similar project. I was wondering what your conclusion was for the eight encoders on the mega board.

In thinking through this problem I had an idea about using capacitive sensitive knobs on the encoders. This way you will know which one (or two) encoders are being touched and then you can switch the address lines of one (or two) multiplexers to select the touched encoders. You'll need two multiplexers per encoder (A & B lines) for a total of four multiplexers. But then you can route the output of the multiplexers to four interrupt lines on the mega to guarantee pulse counting. To conserve I/O you can even use a 5th multiplexer, in a "standard mux input configuration", for the capacitive touch sensors on the eight encoders.

But I'm hoping you tell me that you stuck all eight encoders (16 wires) on non-interrupt digital pins and everything worked fine. :slight_smile:

Steve

If you do not want to miss pulses on your eight rotary encoders you will need eight interrups - 8 different pins.

Interrupt sharing does not work with rotary encoders. They do not store if the just sent an interrupt or not.