Hello,
My code uses https://github.com/luni64/EncoderTool that facillitates use of an encoder over an expander. It is a linked library that I installed in my platformio project.
This my main.c :
Code:
#include <Arduino.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <EncPlex23017.h>
// GUItool: begin automatically generated code
AudioInputI2S i2sInput; //xy=213.75,469.25
AudioEffectDelay delay1; //xy=489,335
AudioMixer4 mixer1; //xy=692,482
AudioOutputI2S i2sOutput; //xy=940.75,470.25
AudioConnection patchCord1(i2sInput, 0, delay1, 0);
AudioConnection patchCord2(i2sInput, 0, mixer1, 1);
AudioConnection patchCord3(delay1, 0, mixer1, 0);
AudioConnection patchCord4(mixer1, 0, i2sOutput, 0);
AudioConnection patchCord5(mixer1, 0, i2sOutput, 1);
AudioControlSGTL5000 sgtl5000_1; //xy=225.75,608.25
// GUItool: end automatically generated code
EncoderTool::EncPlex23S17 encPlex(1);
// this will be called whenever one of the connected encoders changes
void onChange(byte ch, int value, int delta)
{
Serial.printf("Encoder #: %d, value: %3d, delta: %2d\n", ch, value, delta);
}
void setup() {
Serial.begin(9600);
AudioMemory(150);
sgtl5000_1.enable();
sgtl5000_1.volume(0.9);
delay1.delay(0, 400);
mixer1.gain(0, 1.0);
mixer1.gain(1, 1.0);
encPlex.begin();
encPlex.attachCallback(onChange); // attach a common callback for all encoders
}
void loop() {
//encPlex.tick();
}
EncPlex23017.h :
Code:
/************************************************************
*
* Use a MCP23S17 multiplexer to read out up to 8 attached encoders
* The A/B pins of the encoders go to the A/B inputs of the MCP
*
************************************************************/
#pragma once
//#include <Adafruit_MCP23X17.h>
//#include <MCP23017.h>
#include <MCP23017.h>
#include <EncoderTool.h>
namespace EncoderTool
{
class EncPlex23S17 : public EncPlexBase
{
public:
inline EncPlex23S17(unsigned EncoderCount);
inline void begin(CountMode mode);
inline void tick(); // call as often as possible
protected:
//Adafruit_MCP23X17 mcp23017;
::MCP23017 mcp23017;
//MCP23017 mcp23017 = MCP23017(0x20);
bool isSetup = false;
};
//================================================================================
// INLINE IMPLEMENTATION
EncPlex23S17::EncPlex23S17(unsigned encoderCount) // nothing to do but telling the base class the number of encoders
: EncPlexBase(encoderCount)
{}
void EncPlex23S17::begin(CountMode mode = CountMode::quarter)
{
EncPlexBase::begin(mode); // setup the base class
mcp23017.begin(7);
for (unsigned i = 0; i < encoderCount; i++) // configure all needed pins as input. We start with A_0/B_0 up to the required number of pin pairs
{
mcp23017.pinMode(i, INPUT_PULLUP);
mcp23017.pinMode(i + 8, INPUT_PULLUP);
}
isSetup = true;
}
void EncPlex23S17::tick() // call this as often as possible
{
if (isSetup) // tick might be called from a timer or yield before it is setup
{
uint16_t data = mcp23017.readGPIOAB(); // read the data from the 23S17 multiplexer
for (unsigned i = 0; i < encoderCount; i++) // for all configured encoders
{ // extract the A/B
unsigned A = (data & 1 << i) != 0; //
unsigned B = (data & 1 << (i + 8)) != 0; //
int delta = encoders[i].update(A, B); // the base class will take care of the decoding
if (delta != 0 && callback != nullptr) callback(i, encoders[i].getValue(), delta); // if something changed, invoke the callback
}
}
}
} // namespace EncoderTool
MCP23017.h :
Code:
#ifndef _MCP23017_H_
#define _MCP23017_H_
//keep the Arduino board compatibility
#ifndef ARDUINO_AVR_GEMMA
#include <Wire.h>
#else
#include <TinyWireM.h>
#define Wire TinyWireM
#endif
class MCP23017 {
public:
void begin(uint8_t addr);
void begin(void);
void pinMode(uint8_t p, uint8_t d);
void digitalWrite(uint8_t p, uint8_t d);
void pullUp(uint8_t p, uint8_t d);
uint8_t digitalRead(uint8_t p);
void writeGPIOAB(uint16_t);
uint16_t readGPIOAB();
uint8_t readGPIO(uint8_t b);
void setupInterrupts(uint8_t mirroring, uint8_t open, uint8_t polarity);
void setupInterruptPin(uint8_t p, uint8_t mode);
uint8_t getLastInterruptPin();
uint8_t getLastInterruptPinValue();
private:
uint8_t i2caddr;
uint8_t bitForPin(uint8_t pin);
uint8_t regForPin(uint8_t pin, uint8_t portAaddr, uint8_t portBaddr);
uint8_t readRegister(uint8_t addr);
void writeRegister(uint8_t addr, uint8_t value);
void updateRegisterBit(uint8_t p, uint8_t pValue, uint8_t portAaddr, uint8_t portBaddr);
};
//default device address
#define MCP23017_ADDRESS 0x20
//registers (default IOCON.BANK = 0)
#define MCP23017_IODIRA 0x00
#define MCP23017_IPOLA 0x02
#define MCP23017_GPINTENA 0x04
#define MCP23017_DEFVALA 0x06
#define MCP23017_INTCONA 0x08
#define MCP23017_IOCONA 0x0A
#define MCP23017_GPPUA 0x0C
#define MCP23017_INTFA 0x0E
#define MCP23017_INTCAPA 0x10
#define MCP23017_GPIOA 0x12
#define MCP23017_OLATA 0x14
#define MCP23017_IODIRB 0x01
#define MCP23017_IPOLB 0x03
#define MCP23017_GPINTENB 0x05
#define MCP23017_DEFVALB 0x07
#define MCP23017_INTCONB 0x09
#define MCP23017_IOCONB 0x0B
#define MCP23017_GPPUB 0x0D
#define MCP23017_INTFB 0x0F
#define MCP23017_INTCAPB 0x11
#define MCP23017_GPIOB 0x13
#define MCP23017_OLATB 0x15
#define MCP23017_INT_ERR 255
#endif
MCP23017.cpp
Code:
#ifdef __AVR
#include <avr/pgmspace.h>
#elif defined(ESP8266)
#include <pgmspace.h>
#endif
#include "MCP23017.h"
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
//keep the Arduino backward compatibility
static inline void wiresend(uint8_t x) {
#if ARDUINO >= 100
Wire1.write((uint8_t) x);
#else
Wire1.send(x);
#endif
}
static inline uint8_t wirerecv(void) {
#if ARDUINO >= 100
return Wire1.read();
#else
return Wire1.receive();
#endif
}
/**
* Bit number associated to a give Pin
*/
uint8_t MCP23017::bitForPin(uint8_t pin){
return pin%8;
}
/**
* Register address, port dependent, for a given PIN
*/
uint8_t MCP23017::regForPin(uint8_t pin, uint8_t portAaddr, uint8_t portBaddr){
return(pin<8) ?portAaddr:portBaddr;
}
/**
* Reads a given register
*/
uint8_t MCP23017::readRegister(uint8_t addr){
// read the current GPINTEN
Wire1.beginTransmission(MCP23017_ADDRESS | i2caddr);
wiresend(addr);
Wire1.endTransmission();
Wire1.requestFrom(MCP23017_ADDRESS | i2caddr, 1);
return wirerecv();
}
/**
* Writes a given register
*/
void MCP23017::writeRegister(uint8_t regAddr, uint8_t regValue){
// Write the register
Wire1.beginTransmission(MCP23017_ADDRESS | i2caddr);
wiresend(regAddr);
wiresend(regValue);
Wire1.endTransmission();
}
/**
* Helper to update a single bit of an A/B register.
* - Reads the current register value
* - Writes the new register value
*/
void MCP23017::updateRegisterBit(uint8_t pin, uint8_t pValue, uint8_t portAaddr, uint8_t portBaddr) {
uint8_t regValue;
uint8_t regAddr=regForPin(pin,portAaddr,portBaddr);
uint8_t bit=bitForPin(pin);
regValue = readRegister(regAddr);
// set the value for the particular bit
bitWrite(regValue,bit,pValue);
writeRegister(regAddr,regValue);
}
////////////////////////////////////////////////////////////////////////////////
/**
* Initializes the MCP23017 given its HW selected address, see datasheet for Address selection.
* the addr is from 0 to 7
* soldering the A0 to A2 to change the addr
*/
void MCP23017::begin(uint8_t addr) {
if (addr > 7) {
addr = 7;
}
i2caddr = addr;
Wire1.begin();
// set defaults!
// all inputs on port A and B
writeRegister(MCP23017_IODIRA,0xff);
writeRegister(MCP23017_IODIRB,0xff);
}
/**
* Initializes the default MCP23017, with 000 for the configurable part of the address
*/
void MCP23017::begin(void) {
begin(0);
}
/**
* Sets the pin mode to either INPUT or OUTPUT
*/
void MCP23017::pinMode(uint8_t p, uint8_t d) {
updateRegisterBit(p,(d==INPUT),MCP23017_IODIRA,MCP23017_IODIRB);
}
/**
* Reads all 16 pins (port A and B) into a single 16 bits variable.
*/
uint16_t MCP23017::readGPIOAB() {
uint16_t ba = 0;
uint8_t a;
// read the current GPIO output latches
Wire1.beginTransmission(MCP23017_ADDRESS | i2caddr);
wiresend(MCP23017_GPIOA);
Wire1.endTransmission();
Wire1.requestFrom(MCP23017_ADDRESS | i2caddr, 2);
a = wirerecv();
ba = wirerecv();
ba <<= 8;
ba |= a;
return ba;
}
/**
* Read a single port, A or B, and return its current 8 bit value.
* Parameter b should be 0 for GPIOA, and 1 for GPIOB.
*/
uint8_t MCP23017::readGPIO(uint8_t b) {
// read the current GPIO output latches
Wire1.beginTransmission(MCP23017_ADDRESS | i2caddr);
if (b == 0)
wiresend(MCP23017_GPIOA);
else {
wiresend(MCP23017_GPIOB);
}
Wire1.endTransmission();
Wire1.requestFrom(MCP23017_ADDRESS | i2caddr, 1);
return wirerecv();
}
/**
* Writes all the pins in one go. This method is very useful if you are implementing a multiplexed matrix and want to get a decent refresh rate.
*/
void MCP23017::writeGPIOAB(uint16_t ba) {
Wire1.beginTransmission(MCP23017_ADDRESS | i2caddr);
wiresend(MCP23017_GPIOA);
wiresend(ba & 0xFF);
wiresend(ba >> 8);
Wire1.endTransmission();
}
void MCP23017::digitalWrite(uint8_t pin, uint8_t d) {
uint8_t gpio;
uint8_t bit=bitForPin(pin);
// read the current GPIO output latches
uint8_t regAddr=regForPin(pin,MCP23017_OLATA,MCP23017_OLATB);
gpio = readRegister(regAddr);
// set the pin and direction
bitWrite(gpio,bit,d);
// write the new GPIO
regAddr=regForPin(pin,MCP23017_GPIOA,MCP23017_GPIOB);
writeRegister(regAddr,gpio);
}
void MCP23017::pullUp(uint8_t p, uint8_t d) {
updateRegisterBit(p,d,MCP23017_GPPUA,MCP23017_GPPUB);
}
uint8_t MCP23017::digitalRead(uint8_t pin) {
uint8_t bit=bitForPin(pin);
uint8_t regAddr=regForPin(pin,MCP23017_GPIOA,MCP23017_GPIOB);
return (readRegister(regAddr) >> bit) & 0x1;
}
/**
* Configures the interrupt system. both port A and B are assigned the same configuration.
* Mirroring will OR both INTA and INTB pins.
* Opendrain will set the INT pin to value or open drain.
* polarity will set LOW or HIGH on interrupt.
* Default values after Power On Reset are: (false, false, LOW)
* If you are connecting the INTA/B pin to arduino 2/3, you should configure the interupt handling as FALLING with
* the default configuration.
*/
void MCP23017::setupInterrupts(uint8_t mirroring, uint8_t openDrain, uint8_t polarity){
// configure the port A
uint8_t ioconfValue=readRegister(MCP23017_IOCONA);
bitWrite(ioconfValue,6,mirroring);
bitWrite(ioconfValue,2,openDrain);
bitWrite(ioconfValue,1,polarity);
writeRegister(MCP23017_IOCONA,ioconfValue);
// Configure the port B
ioconfValue=readRegister(MCP23017_IOCONB);
bitWrite(ioconfValue,6,mirroring);
bitWrite(ioconfValue,2,openDrain);
bitWrite(ioconfValue,1,polarity);
writeRegister(MCP23017_IOCONB,ioconfValue);
}
/**
* Set's up a pin for interrupt. uses arduino MODEs: CHANGE, FALLING, RISING.
*
* Note that the interrupt condition finishes when you read the information about the port / value
* that caused the interrupt or you read the port itself. Check the datasheet can be confusing.
*
*/
void MCP23017::setupInterruptPin(uint8_t pin, uint8_t mode) {
// set the pin interrupt control (0 means change, 1 means compare against given value);
updateRegisterBit(pin,(mode!=CHANGE),MCP23017_INTCONA,MCP23017_INTCONB);
// if the mode is not CHANGE, we need to set up a default value, different value triggers interrupt
// In a RISING interrupt the default value is 0, interrupt is triggered when the pin goes to 1.
// In a FALLING interrupt the default value is 1, interrupt is triggered when pin goes to 0.
updateRegisterBit(pin,(mode==FALLING),MCP23017_DEFVALA,MCP23017_DEFVALB);
// enable the pin for interrupt
updateRegisterBit(pin,HIGH,MCP23017_GPINTENA,MCP23017_GPINTENB);
}
uint8_t MCP23017::getLastInterruptPin(){
uint8_t intf;
// try port A
intf=readRegister(MCP23017_INTFA);
for(int i=0;i<8;i++) if (bitRead(intf,i)) return i;
// try port B
intf=readRegister(MCP23017_INTFB);
for(int i=0;i<8;i++) if (bitRead(intf,i)) return i+8;
return MCP23017_INT_ERR;
}
uint8_t MCP23017::getLastInterruptPinValue(){
uint8_t intPin=getLastInterruptPin();
if(intPin!=MCP23017_INT_ERR){
uint8_t intcapreg=regForPin(intPin,MCP23017_INTCAPA,MCP23017_INTCAPB);
uint8_t bit=bitForPin(intPin);
return (readRegister(intcapreg)>>bit) & (0x01);
}
return MCP23017_INT_ERR;
}