Using MCP23017 with an Audio Shield

peo

New member
Hello,

I've succesfully ran some code with a teensy 4.1 and an audio shield that takes input sound and outputs it with a delay.
Then, I added MCP23017 expander to my projet and a rotary encoder connected through this expander.
My audio effect still works and the rotary encoder movements are detected but I now hear a continuous low volume and high pitched sound in the background (even if I have no signal on my input). Also, when I turn the encoder, I hear some crakling sound.

I tried to separate the audio shield on SCL/SDA and MCP23017 on SCL1/SDA1 and I still have the same issue.

What can I try to solve that ?
Thank you in advance !
 
Forum Rule: Always post complete source code & details to reproduce any issue!
Can you post your code (Use CODE tags with the # button) and a picture of your wiring.
 
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;
}
 
Here is how I plugged things :
IMG_20221124_010951_HDR.jpg
 
I solved the crackling noise when rotating the encoder by putting the ground pin of MCP23017 on another ground pin than the audio shield. I have to search for some explanation to understand why it works better that way.

Concerning the "high pitched sound in the background", it disappears if I stop polling on the encoder... I have to understand how to work with interruption !
 
Back
Top