Hi all,
After much thinking and testing i finally managed to work out how to read rotary encoders with the MCP23S17.
I haven't found any libraries for this purpose, neither a working solution(without adding extra hardware) so I post here the code I have come up with:
#include <SPI.h>
// MCP23S17 registers
const byte IODIRA = 0x00; // IO direction (0 = output, 1 = input (Default))
const byte IODIRB = 0x01;
const byte IOPOLA = 0x02; // IO polarity (0 = normal, 1 = inverse)
const byte IOPOLB = 0x03;
const byte GPINTENA = 0x04; // Interrupt on change (0 = disable, 1 = enable)
const byte GPINTENB = 0x05;
const byte DEFVALA = 0x06; // Default comparison for interrupt on change (interrupts on opposite)
const byte DEFVALB = 0x07;
const byte INTCONA = 0x08; // Interrupt control (0 = interrupt on change from previous, 1 = interrupt on change from DEFVAL)
const byte INTCONB = 0x09;
const byte IOCON = 0x0A; // IO Configuration: bank/mirror/seqop/disslw/haen/odr/intpol/notimp
const byte GPPUA = 0x0C; // Pull-up resistor (0 = disabled, 1 = enabled)
const byte GPPUB = 0x0D;
const byte INFTFA = 0x0E; // Interrupt flag (read only) : (0 = no interrupt, 1 = pin caused interrupt)
const byte INFTFB = 0x0F;
const byte INTCAPA = 0x10; // Interrupt capture (read only) : value of GPIO at time of last interrupt
const byte INTCAPB = 0x11;
const byte GPIOA = 0x12; // Port value. Write to change, read to obtain value
const byte GPIOB = 0x13;
const byte OLLATA = 0x14; // Output latch. Write to latch output.
const byte OLLATB = 0x15;
const byte ssPin = 38;
const byte numOfMCPs = 2;
const byte ports[numOfMCPs] = {0x20, 0x21};
const byte intPins[numOfMCPs] = {18,19};
bool interrupted[numOfMCPs] = {false,false};
static byte counters[numOfMCPs][8] = {{0,0,0,0,0,0,0,0 }, {0,0,0,0,0,0,0,0 }};
byte portALast[numOfMCPs] = {0b00000000, 0b00000000};
static byte counters[numOfMCPs][8] = {{0,0,0,0,0,0,0,0 }, {0,0,0,0,0,0,0,0 }};
//ISR functions
void handleInterrupt() { interrupted[0] = true; }
void handleInterrupt2(){ interrupted[1] = true; }
//Write to expander
void expanderWrite (const byte reg, const byte data, const byte port)
{
digitalWrite (ssPin, LOW);
SPI.transfer (port << 1); // note this is write mode
SPI.transfer (reg);
SPI.transfer (data);
digitalWrite (ssPin, HIGH);
}
//Read from expander
byte expanderRead(const byte reg, const byte port)
{
byte data = 0;
digitalWrite (ssPin, LOW);
SPI.transfer ((port << 1) | 1); // note this is read mode
SPI.transfer (reg);
data = SPI.transfer (0);
digitalWrite (ssPin, HIGH);
return data;
}
void setup()
{
SPI.begin();
pinMode (ssPin, OUTPUT);
digitalWrite (ssPin, HIGH);
pinMode(intPins[0], INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(intPins[0]), handleInterrupt, FALLING);
pinMode(intPins[1], INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(intPins[1]), handleInterrupt2, FALLING);
for(int i = 0; i<numOfMCPs; i++)
{
//Config MCP
expanderWrite (IOCON, 0b01101000, ports[i]); //Mirror interrupts, disable sequential mode, enable hardware adressing
//Set PORT A registers
expanderWrite (IODIRA, 0b11111111, ports[i]); //1 is input
expanderWrite (GPPUA, 0b00000000, ports[i]);
expanderWrite (IOPOLA, 0b11111111, ports[i]);
expanderWrite (INTCONA, 0b00000000, ports[i]);
expanderWrite (GPINTENA, 0b11111111, ports[i]);
//Set PORT B registers
expanderWrite (IODIRB, 0b11111111, ports[i]); //1 is input
expanderWrite (GPPUB, 0b00000000, ports[i]);
expanderWrite (IOPOLB, 0b11111111, ports[i]);
expanderWrite (GPINTENB, 0b00000000, ports[i]);
}
}
void loop()
{
for(int j = 0; j < numOfMCPs; j++)
{
//Check if the encoder has caused an ineterrupt
if(interrupted[j])
{
interrupted[j] = false;
byte portA = expanderRead(GPIOA, ports[j]);
byte portB = expanderRead(GPIOB, ports[j]);
//Check which pin of the encoder caused interrupt and and check the value of pin A
byte mask = 0;
byte i = 0;
byte aLast;
byte aCurrent;
for(i = 0; i < 8; i++)
{
mask = 1 << i;
aLast = portALast[j] & mask;
aCurrent = portA & mask;
if(aLast ^ aCurrent)
{
break;
}
}
//Then determine the direction of rotation if its the raising edge of the square wave
if(aCurrent)
{
if(!aLast)
{
portALast[j] = portA;
byte bState = mask & portB;
if(bState) //Clockwise
{
counters[j][i]++;
}else{ //Counter-Clockwise
counters[j][i]--;;
}
Serial.print("Chip [");
Serial.print(j);
Serial.print("] Var (");
Serial.print(i);
Serial.print(") : ");
Serial.println(counters[0][i]);
}
}else{
if(aLast)
{
portALast[j] = portA;
}
}
}
}
}
Pin A of the first encoder connects to A0 on the MCP and pin B to port B0 and so on.
This way you can connect 8 encoders to an MCP and you can connect 8 MCP's the same SS pin, if you have that much interrupt pins. (I am using the MEGA which has only six interrupts.)
The code should also work with the I2C version with modified expanderWrite() and expanderRead() functions.
I use the debounce circuit with each encoders I mention in my previous post.
For me this code works quite well with two chips connected to SPI.
I hope this sketch will once help someone.
If there is something I should do different or any question about the code, please write me ![]()
Best regards,
Agoston