Skip to content

File mcp23x17.h

File List > dev > mcp23x17.h

Go to the documentation of this file

Source Code

#pragma once

#include "per/gpio.h"
#include "per/i2c.h"

// This get defined in a public (ST) header file
#undef SetBit

namespace daisy
{
// Adapted from https://github.com/blemasle/arduino-mcp23017

enum class MCPRegister : uint8_t
{
    IODIR_A = 0x00, // Controls the direction of the data I/O for port A.
    IODIR_B = 0x01, // Controls the direction of the data I/O for port B.
    IPOL_A
    = 0x02, // Configures the polarity on the corresponding GPIO_ port bits for port A.
    IPOL_B
    = 0x03, // Configures the polarity on the corresponding GPIO_ port bits for port B.
    GPINTEN_A
    = 0x04, // Controls the interrupt-on-change for each pin of port A.
    GPINTEN_B
    = 0x05, // Controls the interrupt-on-change for each pin of port B.
    DEFVAL_A
    = 0x06, // Controls the default comparaison value for interrupt-on-change for port A.
    DEFVAL_B
    = 0x07, // Controls the default comparaison value for interrupt-on-change for port B.
    INTCON_A
    = 0x08, // Controls how the associated pin value is compared for the interrupt-on-change for port A.
    INTCON_B
    = 0x09, // Controls how the associated pin value is compared for the interrupt-on-change for port B.
    IOCON  = 0x0A, // Controls the device.
    GPPU_A = 0x0C, // Controls the pull-up resistors for the port A pins.
    GPPU_B = 0x0D, // Controls the pull-up resistors for the port B pins.
    INTF_A = 0x0E, // Reflects the interrupt condition on the port A pins.
    INTF_B = 0x0F, // Reflects the interrupt condition on the port B pins.
    INTCAP_A
    = 0x10, // Captures the port A value at the time the interrupt occured.
    INTCAP_B
    = 0x11, // Captures the port B value at the time the interrupt occured.
    GPIO_A = 0x12, // Reflects the value on the port A.
    GPIO_B = 0x13, // Reflects the value on the port B.
    OLAT_A = 0x14, // Provides access to the port A output latches.
    OLAT_B = 0x15, // Provides access to the port B output latches.
};

enum class MCPPort : uint8_t
{
    A = 0,
    B = 1
};

inline MCPRegister operator+(MCPRegister a, MCPPort b)
{
    return static_cast<MCPRegister>(static_cast<uint8_t>(a)
                                    + static_cast<uint8_t>(b));
}

enum class MCPMode : uint8_t
{
    INPUT,
    INPUT_PULLUP,
    OUTPUT,
};

class Mcp23017Transport
{
  public:
    struct Config
    {
        I2CHandle::Config i2c_config;
        uint8_t           i2c_address;
        void              Defaults()
        {
            i2c_config.periph         = I2CHandle::Config::Peripheral::I2C_1;
            i2c_config.speed          = I2CHandle::Config::Speed::I2C_1MHZ;
            i2c_config.mode           = I2CHandle::Config::Mode::I2C_MASTER;
            i2c_config.pin_config.scl = Pin(PORTB, 8);
            i2c_config.pin_config.sda = Pin(PORTB, 9);
            i2c_address               = 0x27;
        }
    };

    void Init()
    {
        Config config;
        config.Defaults();
        Init(config);
    };

    void Init(const Config& config)
    {
        i2c_address_ = config.i2c_address;
        i2c_.Init(config.i2c_config);
    };

    I2CHandle::Result WriteReg(MCPRegister reg, uint8_t val)
    {
        uint8_t data[1] = {val};
        return i2c_.WriteDataAtAddress(
            i2c_address_, static_cast<uint8_t>(reg), 1, data, 1, timeout);
    }

    I2CHandle::Result WriteReg(MCPRegister reg, uint8_t portA, uint8_t portB)
    {
        uint8_t data[2] = {portA, portB};
        return i2c_.WriteDataAtAddress(
            i2c_address_, static_cast<uint8_t>(reg), 1, data, 2, timeout);
    }

    uint8_t ReadReg(MCPRegister reg)
    {
        uint8_t data[1] = {0x00};
        i2c_.ReadDataAtAddress(
            i2c_address_, static_cast<uint8_t>(reg), 1, data, 1, timeout);
        return data[0];
    }

    void ReadReg(MCPRegister reg, uint8_t& portA, uint8_t& portB)
    {
        uint8_t data[2];
        i2c_.ReadDataAtAddress(
            i2c_address_, static_cast<uint8_t>(reg), 1, data, 2, timeout);

        portA = data[0];
        portB = data[1];
    }

    daisy::I2CHandle i2c_;
    uint8_t          i2c_address_;
    uint8_t          timeout{10};
};

template <typename Transport>
class Mcp23X17
{
  public:
    struct Config
    {
        typename Transport::Config transport_config;
    };

    void Init()
    {
        Config config;
        config.transport_config.Defaults();
        Init(config);
    };

    void Init(const Config& config)
    {
        transport.Init(config.transport_config);

        //BANK =     0 : sequential register addresses
        //MIRROR =     0 : use configureInterrupt
        //SEQOP =     1 : sequential operation disabled, address pointer does not increment
        //DISSLW =     0 : slew rate enabled
        //HAEN =     0 : hardware address pin is always enabled on 23017
        //ODR =     0 : open drain output
        //INTPOL =     0 : interrupt active low
        transport.WriteReg(MCPRegister::IOCON, 0b00100000);

        //enable all pull up resistors (will be effective for input pins only)
        transport.WriteReg(MCPRegister::GPPU_A, 0xFF, 0xFF);
    };

    void PortMode(MCPPort port,
                  uint8_t directions,
                  uint8_t pullups  = 0xFF,
                  uint8_t inverted = 0x00)
    {
        transport.WriteReg(MCPRegister::IODIR_A + port, directions);
        transport.WriteReg(MCPRegister::GPPU_A + port, pullups);
        transport.WriteReg(MCPRegister::IPOL_A + port, inverted);
    }

    void PinMode(uint8_t pin, MCPMode mode, bool inverted)
    {
        MCPRegister iodirreg  = MCPRegister::IODIR_A;
        MCPRegister pullupreg = MCPRegister::GPPU_A;
        MCPRegister polreg    = MCPRegister::IPOL_A;
        uint8_t     iodir, pol, pull;
        if(pin > 7)
        {
            iodirreg  = MCPRegister::IODIR_B;
            pullupreg = MCPRegister::GPPU_B;
            polreg    = MCPRegister::IPOL_B;
            pin -= 8;
        }
        iodir = transport.ReadReg(iodirreg);
        if(mode == MCPMode::INPUT || mode == MCPMode::INPUT_PULLUP)
            SetBit(iodir, pin);
        else
            ClearBit(iodir, pin);
        pull = transport.ReadReg(pullupreg);
        if(mode == MCPMode::INPUT_PULLUP)
            SetBit(pull, pin);
        else
            ClearBit(pull, pin);
        pol = transport.ReadReg(polreg);
        if(inverted)
            SetBit(pol, pin);
        else
            ClearBit(pol, pin);
        transport.WriteReg(iodirreg, iodir);
        transport.WriteReg(pullupreg, pull);
        transport.WriteReg(polreg, pol);
    }

    void WritePin(uint8_t pin, uint8_t state)
    {
        MCPRegister gpioreg = MCPRegister::GPIO_A;
        uint8_t     gpio;
        if(pin > 7)
        {
            gpioreg = MCPRegister::GPIO_B;
            pin -= 8;
        }

        gpio = transport.ReadReg(gpioreg);
        if(state > 0)
        {
            gpio = SetBit(gpio, pin);
        }
        else
        {
            gpio = ClearBit(gpio, pin);
        }

        transport.WriteReg(gpioreg, gpio);
    }

    uint8_t ReadPin(uint8_t pin)
    {
        MCPRegister gpioreg = MCPRegister::GPIO_A;
        uint8_t     gpio;
        if(pin > 7)
        {
            gpioreg = MCPRegister::GPIO_B;
            pin -= 8;
        }

        gpio = transport.ReadReg(gpioreg);
        if(ReadBit(gpio, pin))
        {
            return 1;
        }

        return 0;
    }

    void WritePort(MCPPort port, uint8_t value)
    {
        transport.WriteReg(MCPRegister::GPIO_A + port, value);
    }

    uint8_t ReadPort(MCPPort port)
    {
        return transport.ReadReg(MCPRegister::GPIO_A + port);
    }

    void Write(uint16_t value)
    {
        transport.WriteReg(
            MCPRegister::GPIO_A, LowByte(value), HighByte(value));
    }

    uint16_t Read()
    {
        uint8_t a = ReadPort(MCPPort::A);
        uint8_t b = ReadPort(MCPPort::B);

        pin_data = a | b << 8;
        return pin_data;
    }

    uint8_t GetPin(uint8_t id) { return ReadBit(pin_data, id); }

  private:
    uint8_t GetBit(uint8_t data, uint8_t id)
    {
        uint8_t mask     = 1 << id;
        uint8_t masked_n = data & mask;
        return masked_n >> id;
    }

    uint8_t SetBit(uint8_t data, uint8_t pos)
    {
        data = (data | (1 << pos));
        return data;
    }

    uint8_t ClearBit(uint8_t data, uint8_t pos)
    {
        data = (data & (~(1 << pos)));
        return data;
    }

    uint8_t ReadBit(uint16_t data, uint8_t pos)
    {
        return data & (1 << pos) ? 0xff : 0x00;
    }

    uint8_t LowByte(uint16_t val) { return val & 0xFF; }
    uint8_t HighByte(uint16_t val) { return (val >> 8) & 0xff; }

    uint16_t  pin_data;
    Transport transport;
};

using Mcp23017 = Mcp23X17<Mcp23017Transport>;
} // namespace daisy