Teensy with Encoders and MCP23017

Status
Not open for further replies.

tenorjazz

Active member
Any help or ideas are greatly appreciated!

I am working on a project that has me reading 25 rotary encoders with buttons, controlling 32 LEDs, reading 16 "stand alone" buttons and reading 2 potentiometers. That makes a total of 127 inputs. I thought about using multiple Teensy's but that would be a lot of Teensy's so think I can make it work with 8 MCP23017's.

Bread boarding a prototype to test how the MCP23017's would work out I have run into a strange problem that has to do with addressing the Multiplexers.

When I set the MCP to address 27 (both on the bread board, 3.3v to pins 15,16,17 and in the code) everything seems to work OK. When I set the address to any other address (example: 20, GND pins 15,16,17) , it doesn't.

I only have 2 encoders attached at this time so wonder if this may be the problem ( I seem to remember you might need to set the unused data pins to Gnd or 3.3v, can't remember which, so they aren't floating)??? But it still doesn't make sense that it works on address 27 and not on address 20.

I have tested this on both and LC and a 3.6 with the same results. I have also swapped out the MCP, just in case that was the problem.

Pictures for the bread board wiring and a picture of the prototype panel.
IMG-1751.jpgIMG-1752.jpgIMG-1753.jpgIMG-1754.jpgIMG-1755.jpgIMG-1750.jpg

Here is the code. I know it could be written better, but it works, at least on address 27. I also know I probably should be using interrupts, but I can't figure out how to code them.

#include <Wire.h>
#include "Adafruit_MCP23017.h"


const int v1Clk=0;
const int v1Dta=1;
const int v2Clk=2;
const int v2Dta=3;
const int v3Clk=4;
const int v3Dta=5;
const int v4Clk=6;
const int v4Dta=7;
const int v5Clk=8;
const int v5Dta=9;
const int v6Clk=10;
const int v6Dta=11;
const int v7Clk=12;
const int v7Dta=13;
const int v8Clk=14;
const int v8Dta=15;

int v1Amt = 0;
int v2Amt = 0;
int v3Amt = 0;
int v4Amt = 0;
int v5Amt = 0;
int v6Amt = 0;
int v7Amt = 0;
int v8Amt = 0;


int resolution = 2;

Adafruit_MCP23017 mcpVol;


void setup() {
mcpVol.begin(27); // use default address 0

mcpVol.pinMode(v1Clk, INPUT);
mcpVol.pinMode(v1Dta, INPUT);
mcpVol.pinMode(v2Clk, INPUT);
mcpVol.pinMode(v2Dta, INPUT);
mcpVol.pinMode(v3Clk, INPUT);
mcpVol.pinMode(v3Dta, INPUT);
mcpVol.pinMode(v4Clk, INPUT);
mcpVol.pinMode(v4Dta, INPUT);
mcpVol.pinMode(v5Clk, INPUT);
mcpVol.pinMode(v5Dta, INPUT);
mcpVol.pinMode(v6Clk, INPUT);
mcpVol.pinMode(v6Dta, INPUT);
mcpVol.pinMode(v7Clk, INPUT);
mcpVol.pinMode(v7Dta, INPUT);
mcpVol.pinMode(v8Clk, INPUT);
mcpVol.pinMode(v8Dta, INPUT);

Serial.begin(9600);
}

void loop() {
ckVol1();
ckVol2();
ckVol3();
ckVol4();
ckVol5();
ckVol6();
ckVol7();
ckVol8();
}

void ckVol1(){
static int oldA = HIGH;
static int oldB = HIGH;
int result=0;
int newA=mcpVol.digitalRead(v1Clk);
int newB=mcpVol.digitalRead(v1Dta);
if (newA!=oldA || newB != oldB){
//something has changed
if(oldA == HIGH && newA==LOW){
result = (oldB + 2 -1);
}
}
oldA=newA;
oldB=newB;
int change = result;
if(change==2){
v1Amt=v1Amt+resolution;
if (v1Amt>127)v1Amt=127;
Serial.print("Vol 1: ");
Serial.println(v1Amt);
}else if(change==1){
v1Amt=v1Amt-resolution;
if (v1Amt<0) v1Amt=0;
Serial.print("Vol 1: ");
Serial.println(v1Amt);
}
}

void ckVol2(){
static int oldA = HIGH;
static int oldB = HIGH;
int result=0;
int newA=mcpVol.digitalRead(v2Clk);
int newB=mcpVol.digitalRead(v2Dta);
if (newA!=oldA || newB != oldB){
//something has changed
if(oldA == HIGH && newA==LOW){
result = (oldB + 2 -1);
}
}
oldA=newA;
oldB=newB;
int change = result;
if(change==2){
v2Amt=v2Amt+resolution;
if (v2Amt>127)v2Amt=127;
Serial.print("Vol 2: ");
Serial.println(v2Amt);
}else if(change==1){
v2Amt=v2Amt-resolution;
if (v2Amt<0) v2Amt=0;
Serial.print("Vol 2: ");
Serial.println(v2Amt);
}
}

void ckVol3(){
static int oldA = HIGH;
static int oldB = HIGH;
int result=0;
int newA=mcpVol.digitalRead(v3Clk);
int newB=mcpVol.digitalRead(v3Dta);
if (newA!=oldA || newB != oldB){
//something has changed
if(oldA == HIGH && newA==LOW){
result = (oldB + 2 -1);
}
}
oldA=newA;
oldB=newB;
int change = result;
if(change==2){
v3Amt=v3Amt+resolution;
if (v3Amt>127)v3Amt=127;
Serial.print("Vol 3: ");
Serial.println(v3Amt);
}else if(change==1){
v3Amt=v3Amt-resolution;
if (v3Amt<0) v3Amt=0;
Serial.print("Vol 3: ");
Serial.println(v3Amt);
}
}

void ckVol4(){
static int oldA = HIGH;
static int oldB = HIGH;
int result=0;
int newA=mcpVol.digitalRead(v4Clk);
int newB=mcpVol.digitalRead(v4Dta);
if (newA!=oldA || newB != oldB){
//something has changed
if(oldA == HIGH && newA==LOW){
result = (oldB + 2 -1);
}
}
oldA=newA;
oldB=newB;
int change = result;
if(change==2){
v4Amt=v4Amt+resolution;
if (v4Amt>127)v4Amt=127;
Serial.print("Vol 4: ");
Serial.println(v4Amt);
}else if(change==1){
v4Amt=v4Amt-resolution;
if (v4Amt<0) v4Amt=0;
Serial.print("Vol 4: ");
Serial.println(v4Amt);
}
}

void ckVol5(){
static int oldA = HIGH;
static int oldB = HIGH;
int result=0;
int newA=mcpVol.digitalRead(v5Clk);
int newB=mcpVol.digitalRead(v5Dta);
if (newA!=oldA || newB != oldB){
//something has changed
if(oldA == HIGH && newA==LOW){
result = (oldB + 2 -1);
}
}
oldA=newA;
oldB=newB;
int change = result;
if(change==2){
v5Amt=v5Amt+resolution;
if (v5Amt>127)v5Amt=127;
Serial.print("Vol 5: ");
Serial.println(v5Amt);
}else if(change==1){
v5Amt=v5Amt-resolution;
if (v5Amt<0) v5Amt=0;
Serial.print("Vol 5: ");
Serial.println(v5Amt);
}
}

void ckVol6(){
static int oldA = HIGH;
static int oldB = HIGH;
int result=0;
int newA=mcpVol.digitalRead(v6Clk);
int newB=mcpVol.digitalRead(v6Dta);
if (newA!=oldA || newB != oldB){
//something has changed
if(oldA == HIGH && newA==LOW){
result = (oldB + 2 -1);
}
}
oldA=newA;
oldB=newB;
int change = result;
if(change==2){
v6Amt=v6Amt+resolution;
if (v6Amt>127)v6Amt=127;
Serial.print("Vol 6: ");
Serial.println(v6Amt);
}else if(change==1){
v6Amt=v6Amt-resolution;
if (v6Amt<0) v6Amt=0;
Serial.print("Vol 6: ");
Serial.println(v6Amt);
}
}

void ckVol7(){
static int oldA = HIGH;
static int oldB = HIGH;
int result=0;
int newA=mcpVol.digitalRead(v7Clk);
int newB=mcpVol.digitalRead(v7Dta);
if (newA!=oldA || newB != oldB){
//something has changed
if(oldA == HIGH && newA==LOW){
result = (oldB + 2 -1);
}
}
oldA=newA;
oldB=newB;
int change = result;
if(change==2){
v7Amt=v7Amt+resolution;
if (v7Amt>127)v7Amt=127;
Serial.print("Vol 7: ");
Serial.println(v7Amt);
}else if(change==1){
v7Amt=v7Amt-resolution;
if (v7Amt<0) v7Amt=0;
Serial.print("Vol 7: ");
Serial.println(v7Amt);
}
}

void ckVol8(){
static int oldA = HIGH;
static int oldB = HIGH;
int result=0;
int newA=mcpVol.digitalRead(v8Clk);
int newB=mcpVol.digitalRead(v8Dta);
if (newA!=oldA || newB != oldB){
//something has changed
if(oldA == HIGH && newA==LOW){
result = (oldB + 2 -1);
}
}
oldA=newA;
oldB=newB;
int change = result;
if(change==2){
v8Amt=v8Amt+resolution;
if (v8Amt>127)v8Amt=127;
Serial.print("Vol 8: ");
Serial.println(v8Amt);
}else if(change==1){
v8Amt=v8Amt-resolution;
if (v8Amt<0) v8Amt=0;
Serial.print("Vol 8: ");
Serial.println(v8Amt);
}
}
 
Here is a picture of the MCP address matrix. The MCP base address is 20 and goes to 27.

MCP23017-addresspins1.jpg

Yes I am running I2C, I need every available pin I can manage to find.

I was reading an article that calculated the performance of a similar setup and he suggested that there would plenty of "headroom" for this to work. Even if I miss a few clicks of a rotary encoder, that won't be a problem.
 
The reset pin should be pin 18, just above the address pins. I have it connected to 3.3v of the Teensy, which is also tied to VCC (Pin 9 or VDD on the MCP data sheet). See the attached photos.
 
I don’t see a common ground. pulling up or down unused pins not necessary as theyre digital only, not analog, you can if you want turn on the built in pullups but not necessary
0x20 should work but you need common ground

some breadboard power rail dont cross other half of board, verify this as well
 
by the way, 27 works because 28 will too, 29 also, etc.

the way adafruit library is address is done in begin() values 0-7, anything higher is, well, set to 7, and thats why 0x27 worked, because 27 was set to max (7)
So if you want address 0x20, do begin(0) :)

when you did begin(20) before, it really just set it to 7 instead, which is why only address 0x27 worked

so just use begin(0) and the chip with address 0x20 will work
 
More Info:

I grounded all the unused data pins on the MCP, just to make sure that wasn't the problem, it didn't help.
 
Thanks a ton!! tonton81, that works. That explains a lot on why some of my other projects gave me troubles.

While I have this thread going might as well ask if anybody can help me code this using interrupts? I think that with polling this many pins I might miss more than a few inputs, even if the article I mentioned above suggested I wouldn't.

I have read a lot of articles on programming encoders with interrupts and I'm struggling to understand how it works, but I am really confused when the MCP is introduced into the mix.
 
if you access the chip directly via Wire without using the library, you could read in the 2 bytes which contain all 16 bits,

if you want it interrupt capable you’d need to enable the interrupt in the chip’s register and use attachInterrupt on teensy to capture on RISING/FALLING edge. Polling should be fine tho but using the library your reading each pin is a separate I2C sequence, I usually poll the MCP23017 by reading the both banks (2 bytes) and checking which bit positions are high or low

for the interrupt route its better to use MIRROR in IOCON register and tie both interrupt pins together before running a line to teensy
 
it’s very simple and more efficient to access these chips directly without library overhead
example

Wire.beginTransmission(0x20); // start a new session to chip
Wire.write(0x12); //set the address pointer
Wire.endTransmission;

Here we set the address for the first bank of 8 pins, now lets get the value.
Wire.requestFrom(0x20,1); //request one byte from the pointed location from above
byte bank1 = Wire.read();

now “bank1” contains all your first 8 pins.
if your good with bitshifting you can request both banks same time:

Wire.beginTransmission(0x20); // start a new session to chip
Wire.write(0x12); //set the address pointer
Wire.endTransmission;

Wire.requestFrom(0x20,2); //request TWO bytes from the pointed location from above
uint16_t allPins = (uint16_t)(Wire.read() << 8) | Wire.read();

“allPins” now has all pin states from both banks, from pin 0 to pin 15 :)

I typed this from my phone :)
 
Most of what you said about interrupts went over my head, maybe something to learn about in the future, but in a bit of a time crunch right now. I will look at the Wire library and see if I figure out how to read both banks, like you suggest, that sounds like it might work best for me.

You wouldn't by any chance have some simple code I could look at to help me understand how you "poll the MCP23017 by reading both banks (2 bytes) and checking which bit positions are high or low" works? I will try to figure it out but I do learn better by example, so anything would be greatly appreciated!!

I am going to use 8 MCP's in this project so it sounds like just reading 2 bytes on each and processing that data would make it go really fast.

Got to go off line for a little bit so will check back later.
 
once you have allPins, you can read HIGH(1)/LOW(0) from the variable using:

bitRead(allPins,0); // read pin 0
bitRead(allPins,15); // read pin 15

:)
 
Well to ditch the library totally, you should know this
By default, the chip is already set as input with sequencial addressing, so in setup, you can tell the chip to enable pullups for all it's inputs like so:

Code:
void setup() {
  Serial.begin(115200);
  delay(1000); // some startup time
  Wire.begin(); // enable Wire bus (mandatory)
  Wire.beginTransmission(0x20); // start a new session to chip address
  Wire.write(0x0C); //set the register pointer (pullups on bankA);
  Wire.write(0xFF); // set pins 0-7 pullups enabled
  Wire.write(0xFF); // set pins 8-15 pullups enabled
  Wire.endTransmission; // setup complete
}

Setup was simple enough if your using the chip for inputs only, with all pins now set as INPUT_PULLUP basically.

Then in your loop, you can poll this way:
Code:
void loop() {
  Wire.beginTransmission(0x20); // start a new session to chip
  Wire.write(0x12); //set the register pointer
  Wire.endTransmission;

  Wire.requestFrom(0x20,2); //request TWO bytes from the pointed location from above
  uint16_t allPins = (uint16_t)(Wire.read() << 8) | Wire.read();

  if ( bitRead(allPins,4) == 0 ) { // if pin 4 is LOW
    Serial.println("Pin 4 is LOW");
  }

}

Every time your loop is ran,whenever pin 4 goes LOW, it will be printed in Serial monitor :)

You can still poll using adafruit library still in your code. Doing "mcpVol.digitalRead" in the loop is called polling, as its accessing the chip in every loop. So technically, your already polling...

if I check your current loop:
Code:
void loop() {
ckVol1();
ckVol2();
ckVol3();
ckVol4();
ckVol5();
ckVol6();
ckVol7();
ckVol8();
}

each of those is running 2 digitalReads. That means, your doing 16 separate talks to the chip, whereas my way you only do ONE Then you can check all the pins you want from the single 16bit variable without checking the chip 16 times over :)
 
Thanks so much for all the information, it certainly is making things clearer. I think I understand how to get the state of the pins on each poll and will try to code some things this afternoon to see if I do.

However, I have Rotary encoders attached to the pins on the MCP, in pairs, 0-1,2-3, etc. I was able to find some simple code and was able to tweak it that let me poll the MCP's directly and I was able to tweak it a little so that it works pretty good (that's the code I posted). It is my understanding that when you are coding for Rotary Encoders there is a time factor that you need to deal with as the pins go high and low, so you can tell which way the Encoder is being turned. If you do a two byte read isn't that just a snapshot of the moment? So how can you tell which way the Encoder is turning.

Not sure if this makes sense, because I am not sure I really understand how encoders are programmed.
 
By the way, you are right on how I was doing the looping and once I had all the Encoders and buttons and things loaded there would 9-10 times that many calls to do the job. It would certainly be better if I could do it the way you are describing.
 
you would actually read it even faster than before, your loop will become faster as it doesnt need to start and stop multiple communication sessions to the chip for the same pins. either way you do it will be the same result, but you’ll get faster readings with the register data and more live results as the loop is faster you capture more results faster

the interrupt way would be the same, youd still be running the same code when the interrupt occurs, so if your encoder is moving back and fourth, the interrupt would be firing, but adafruit or the direct accessing will be the same regardless, they just wouldnt be in the loop, rather they’d be in the interrupt handler which is called when the interrupt is triggered
 
If you do a two byte read isn't that just a snapshot of the moment? So how can you tell which way the Encoder is turning.

some bits will be 0, some will be 1, each bit in the uint16_t (16 bit variable) is a pin, so your snapshot of all the pins will show pin 0 as high and pin 1 as low for example
 
If you do a two byte read isn't that just a snapshot of the moment? So how can you tell which way the Encoder is turning.

some bits will be 0, some will be 1, each bit in the uint16_t (16 bit variable) is a pin, so your snapshot of all the pins will show pin 0 as high and pin 1 as low for example

so when you do this:
Code:
int newA=mcpVol.digitalRead(v4Clk);
int newB=mcpVol.digitalRead(v4Dta);

it can be the same as:
Code:
int newA=bitRead(allPins,v4Clk);
int newB=bitRead(allPins,v4Dta);

if you put allPins as global variable and poll in the loop, with the bitRead adjustments it would work as it is now but faster, and you can keep those functions in your loop since they would be feeding off the global variable set by the loop pin collector :)

think of it this way, by the time your 16th access to the chip is done you probably would have missed encoder events from the first few runs, direct access would allow faster updates with less loss
 
Wow, I think I am beginning to understand. Thanks again.

I should be able to start coding again later tonight and try all this stuff out. Maybe by tomorrow I will have a whole new and much better system!!
 
What I am building is a "performance" controller for Ableton Live. If all goes as planned it should be able control 32 channels of clips(audio or midi) plus 8 extra hardware/software synths (selecting them and controlling the volume). I will go into more detail and try to do a video when I am done, if anybody is interested.

Here is another picture of the control surface (still needs the LEDs).

IMG-1749.jpg

I have been able to get the 8 volume Rotary Encoders working. Just copy and tweak a few more times and that should get everything else going. I still have to wire the beast and build a case, but will post some pictures and more details when I get it done, if anyone is interested. I have spent the last 3-4 days trying to understand how to do this, I figure there should be others out there trying to do something similar.

Thanks so much to tonton81, I could not have done it without your help!!!!!

Code:
#include <Wire.h>

void setup() {
  Serial.begin(115200);
  delay(3000); // some startup time
  Wire.begin(); // enable Wire bus (mandatory)
  Wire.beginTransmission(0x20); // start a new session to chip address
  Wire.write(0x0C); //set the register pointer (pullups on bankA);
  Wire.write(0xFF); // set pins 0-7 pullups enabled
  Wire.write(0xFF); // set pins 8-15 pullups enabled
  Wire.endTransmission(); // setup complete

}
int v1Amt = 0;
int v1OldA = HIGH;
int v1OldB = HIGH;
const int v1CC=1;
const int v1Clk=8;
const int v1Dta=9;

int v2Amt = 0;
int v2OldA = HIGH;
int v2OldB = HIGH;
const int v2CC=2;
const int v2Clk=10;
const int v2Dta=11;

int v3Amt = 0;
int v3OldA = HIGH;
int v3OldB = HIGH;
const int v3CC=3;
const int v3Clk=12;
const int v3Dta=13;

int v4Amt = 0;
int v4OldA = HIGH;
int v4OldB = HIGH;
const int v4CC=4;
const int v4Clk=14;
const int v4Dta=15;

int v5Amt = 0;
int v5OldA = HIGH;
int v5OldB = HIGH;
const int v5CC=5;
const int v5Clk=0;
const int v5Dta=1;

int v6Amt = 0;
int v6OldA = HIGH;
int v6OldB = HIGH;
const int v6CC=6;
const int v6Clk=2;
const int v6Dta=3;

int v7Amt = 0;
int v7OldA = HIGH;
int v7OldB = HIGH;
const int v7CC=7;
const int v7Clk=4;
const int v7Dta=5;

int v8Amt = 0;
int v8OldA = HIGH;
int v8OldB = HIGH;
const int v8CC=8;
const int v8Clk=6;
const int v8Dta=7;

int resolution = 2;
uint16_t vAllPins;

void loop() {

/*
 * Process Volume encoders
 */
    Wire.beginTransmission(0x20); // start a new session to chip
    Wire.write(0x12); //set the register pointer
    Wire.endTransmission();
  
    Wire.requestFrom(0x20,2); //request TWO bytes from the pointed location from above
    vAllPins = (uint16_t)(Wire.read() << 8) | Wire.read();

    ProcVol(v1OldA,v1OldB,v1Amt,v1CC,v1Clk,v1Dta); // Process Volume 1
    ProcVol(v2OldA,v2OldB,v2Amt,v2CC,v2Clk,v2Dta); // Process Volume 2
    ProcVol(v3OldA,v3OldB,v3Amt,v3CC,v3Clk,v3Dta); // Process Volume 2
    ProcVol(v4OldA,v4OldB,v4Amt,v4CC,v4Clk,v4Dta); // Process Volume 2
    ProcVol(v5OldA,v5OldB,v5Amt,v5CC,v5Clk,v5Dta); // Process Volume 2
    ProcVol(v6OldA,v6OldB,v6Amt,v6CC,v6Clk,v6Dta); // Process Volume 2
    ProcVol(v7OldA,v7OldB,v7Amt,v7CC,v7Clk,v7Dta); // Process Volume 2
    ProcVol(v8OldA,v8OldB,v8Amt,v8CC,v8Clk,v8Dta); // Process Volume 2

    
}

void ProcVol(int &OldA,int &OldB,int &vAmt,int CC,int Clk,int Dta){
    int change=0;
    int newA=(bitRead(vAllPins,Clk));
    int newB=(bitRead(vAllPins,Dta));
    if (newA!=OldA || newB != OldB){
      //something has changed
      if(OldA == HIGH && newA==LOW){
        change = (OldB + 2 -1);
      }
    }
    OldA=newA;
    OldB=newB;
    if(change==2){
      vAmt=vAmt+resolution;
      if (vAmt>127)vAmt=127;
      Serial.print("Vol ");
      Serial.print(CC);
      Serial.print(": ");
      Serial.println(vAmt);
    }else if(change==1){
      vAmt=vAmt-resolution;
      if (vAmt<0) vAmt=0;
      Serial.print("Vol ");
      Serial.print(CC);
      Serial.print(": ");
      Serial.println(vAmt);
    }
  
}
 
I have run into a bit of a problem and was wondering if anybody can shed some light about what's going on. In my project I have banks of 8 rotary encoders where the +v and ground are wired in series.

unnamed.jpg

That would be the 2 black wires in the image. With 8 of them wired together like this I put an ohm meter across the +v and ground wires and get a 5k ohm reading. I was expecting to see no connection. There are no solder bridges that I can see. Is there a resistor or something connecting the +v and ground in the encoder. I don't know how to test for that and I don't see any indication of any connection when I measure just a single encoder.

Can anybody help!
 
Status
Not open for further replies.
Back
Top