Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 5 of 5

Thread: Using MCP23017 with an Audio Shield

Hybrid View

  1. #1

    Using MCP23017 with an Audio Shield


    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 !

  2. #2
    Senior Member BriComp's Avatar
    Join Date
    Apr 2014
    Cheltenham, UK
    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.

  3. #3

    My code uses 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 :
    #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() {
      delay1.delay(0, 400);
      mixer1.gain(0, 1.0);
      mixer1.gain(1, 1.0);
      encPlex.attachCallback(onChange); // attach a common callback for all encoders
    void loop() {
    EncPlex23017.h :
     * 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
            inline EncPlex23S17(unsigned EncoderCount);
            inline void begin(CountMode mode);
            inline void tick(); // call as often as possible
            //Adafruit_MCP23X17 mcp23017;
            ::MCP23017 mcp23017;
            //MCP23017 mcp23017 = MCP23017(0x20);
            bool isSetup = false;
        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
            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 :
    #ifndef _MCP23017_H_
    #define _MCP23017_H_
    //keep the Arduino board compatibility
        #include <Wire.h>
        #include <TinyWireM.h>
        #define Wire TinyWireM
    class MCP23017 {
        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();
        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
    #ifdef __AVR
        #include <avr/pgmspace.h>
    #elif defined(ESP8266)
        #include <pgmspace.h>
    #include "MCP23017.h"
    #if ARDUINO >= 100
        #include "Arduino.h"
        #include "WProgram.h"
    //keep the Arduino backward compatibility
    static inline void wiresend(uint8_t x) {
    #if ARDUINO >= 100
        Wire1.write((uint8_t) x);
    static inline uint8_t wirerecv(void) {
    #if ARDUINO >= 100
        return Wire1.receive();
     * 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);
    	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);
     * 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
     * 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;
    	// set defaults!
    	// all inputs on port A and B
     * Initializes the default MCP23017, with 000 for the configurable part of the address
    void MCP23017::begin(void) {
     * Sets the pin mode to either INPUT or OUTPUT
    void MCP23017::pinMode(uint8_t p, uint8_t d) {
     * 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);
    	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)
    	else {
    	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(ba & 0xFF);
    	wiresend(ba >> 8);
    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
    	// write the new GPIO
    void MCP23017::pullUp(uint8_t p, uint8_t d) {
    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);
    	// Configure the port B
     * 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);
    	// 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.
    	// enable the pin for interrupt
    uint8_t MCP23017::getLastInterruptPin(){
    	uint8_t intf;
    	// try port A
    	for(int i=0;i<8;i++) if (bitRead(intf,i)) return i;
    	// try port B
    	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();
    		uint8_t intcapreg=regForPin(intPin,MCP23017_INTCAPA,MCP23017_INTCAPB);
    		uint8_t bit=bitForPin(intPin);
    		return (readRegister(intcapreg)>>bit) & (0x01);
    	return MCP23017_INT_ERR;

  4. #4
    Here is how I plugged things :
    Click image for larger version. 

Name:	IMG_20221124_010951_HDR.jpg 
Views:	25 
Size:	154.8 KB 
ID:	29796

  5. #5
    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 !

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts