Skip to content

File mpr121.h

File List > dev > mpr121.h

Go to the documentation of this file

Source Code

#pragma once
#ifndef DSY_MPR121_H
#define DSY_MPR121_H

// The default I2C address
#define MPR121_I2CADDR_DEFAULT 0x5A        
#define MPR121_TOUCH_THRESHOLD_DEFAULT 12  
#define MPR121_RELEASE_THRESHOLD_DEFAULT 6 

namespace daisy
{
class Mpr121I2CTransport
{
  public:
    Mpr121I2CTransport() {}
    ~Mpr121I2CTransport() {}

    struct Config
    {
        I2CHandle::Config::Peripheral periph;
        I2CHandle::Config::Speed      speed;
        Pin                           scl;
        Pin                           sda;

        I2CHandle::Config::Mode mode;

        uint8_t dev_addr;

        Config()
        {
            periph = I2CHandle::Config::Peripheral::I2C_1;
            speed  = I2CHandle::Config::Speed::I2C_400KHZ;

            scl = Pin(PORTB, 8);
            sda = Pin(PORTB, 9);

            dev_addr = MPR121_I2CADDR_DEFAULT;
        }
    };

    inline bool Init(Config config)
    {
        config_ = config;

        I2CHandle::Config i2c_conf;
        i2c_conf.mode   = I2CHandle::Config::Mode::I2C_MASTER;
        i2c_conf.periph = config.periph;
        i2c_conf.speed  = config.speed;

        i2c_conf.pin_config.scl = config.scl;
        i2c_conf.pin_config.sda = config.sda;

        return I2CHandle::Result::OK != i2c_.Init(i2c_conf);
    }

    bool Write(uint8_t *data, uint16_t size)
    {
        return I2CHandle::Result::OK
               != i2c_.TransmitBlocking(config_.dev_addr, data, size, 10);
    }

    bool Read(uint8_t *data, uint16_t size)
    {
        return I2CHandle::Result::OK
               != i2c_.ReceiveBlocking(config_.dev_addr, data, size, 10);
    }

  private:
    I2CHandle i2c_;
    Config    config_;
};


template <typename Transport>
class Mpr121
{
  public:
    Mpr121() {}
    ~Mpr121() {}

    struct Config
    {
        typename Transport::Config transport_config;
        uint8_t                    touch_threshold;
        uint8_t                    release_threshold;

        Config()
        {
            touch_threshold   = MPR121_TOUCH_THRESHOLD_DEFAULT;
            release_threshold = MPR121_RELEASE_THRESHOLD_DEFAULT;
        }
    };

    enum Result
    {
        OK = 0,
        ERR
    };

    Result Init(Config config)
    {
        config_ = config;

        SetTransportErr(transport_.Init(config_.transport_config));

        // soft reset
        WriteRegister(MPR121_SOFTRESET, 0x63);
        System::Delay(1);

        WriteRegister(MPR121_ECR, 0x0);

        // this doesn't work for some reason...
        // uint8_t c = ReadRegister8(MPR121_CONFIG2);

        // if(c != 0x24)
        //     return ERR;

        SetThresholds(config_.touch_threshold, config_.release_threshold);
        WriteRegister(MPR121_MHDR, 0x01);
        WriteRegister(MPR121_NHDR, 0x01);
        WriteRegister(MPR121_NCLR, 0x0E);
        WriteRegister(MPR121_FDLR, 0x00);

        WriteRegister(MPR121_MHDF, 0x01);
        WriteRegister(MPR121_NHDF, 0x05);
        WriteRegister(MPR121_NCLF, 0x01);
        WriteRegister(MPR121_FDLF, 0x00);

        WriteRegister(MPR121_NHDT, 0x00);
        WriteRegister(MPR121_NCLT, 0x00);
        WriteRegister(MPR121_FDLT, 0x00);

        WriteRegister(MPR121_DEBOUNCE, 0);
        WriteRegister(MPR121_CONFIG1, 0x10); // default, 16uA charge current
        WriteRegister(MPR121_CONFIG2, 0x20); // 0.5uS encoding, 1ms period

        // autoconfig
        // WriteRegister(MPR121_AUTOCONFIG0, 0x0B);

        // correct values for Vdd = 3.3V
        // WriteRegister(MPR121_UPLIMIT, 200);     // ((Vdd - 0.7)/Vdd) * 256
        // WriteRegister(MPR121_TARGETLIMIT, 180); // UPLIMIT * 0.9
        // WriteRegister(MPR121_LOWLIMIT, 130);    // UPLIMIT * 0.65
        // autoconfig

        // enable X electrodes and start MPR121
        uint8_t ECR_SETTING
            = 0x80
              + 12; // 5 bits for baseline tracking & proximity disabled + X
                    // amount of electrodes running (12)
        WriteRegister(MPR121_ECR,
                      ECR_SETTING); // start with above ECR setting

        return GetTransportErr();
    }

    void SetThresholds(uint8_t touch, uint8_t release)
    {
        // set all thresholds (the same)
        for(uint8_t i = 0; i < 12; i++)
        {
            WriteRegister(MPR121_TOUCHTH_0 + 2 * i, touch);
            WriteRegister(MPR121_RELEASETH_0 + 2 * i, release);
        }
    }


    uint16_t FilteredData(uint8_t t)
    {
        if(t > 12)
            return 0;
        return ReadRegister16(MPR121_FILTDATA_0L + t * 2);
    }

    uint16_t BaselineData(uint8_t t)
    {
        if(t > 12)
            return 0;
        uint16_t bl = ReadRegister8(MPR121_BASELINE_0 + t);
        return (bl << 2);
    }

    uint16_t Touched()
    {
        uint16_t t = ReadRegister16(MPR121_TOUCHSTATUS_L);
        return t & 0x0FFF;
    }

    uint8_t ReadRegister8(uint8_t reg)
    {
        uint8_t buff;
        SetTransportErr(transport_.Write(&reg, 1));
        SetTransportErr(transport_.Read(&buff, 1));

        return buff;
    }

    uint16_t ReadRegister16(uint8_t reg)
    {
        uint16_t buff;
        SetTransportErr(transport_.Write(&reg, 1));
        SetTransportErr(transport_.Read((uint8_t *)&buff, 2));

        return buff;
    }

    void WriteRegister(uint8_t reg, uint8_t value)
    {
        // MPR121 must be put in Stop Mode to write to most registers
        bool stop_required = true;

        // first get the current set value of the MPR121_ECR register
        uint8_t ecr_reg = MPR121_ECR;
        uint8_t buff[2] = {ecr_reg, 0x00};

        SetTransportErr(transport_.Write(buff, 1));

        uint8_t ecr_backup;
        SetTransportErr(transport_.Read(&ecr_backup, 1));
        if((reg == MPR121_ECR) || ((0x73 <= reg) && (reg <= 0x7A)))
        {
            stop_required = false;
        }

        if(stop_required)
        {
            // clear this register to set stop mode
            SetTransportErr(transport_.Write(buff, 2));
        }

        buff[0] = reg;
        buff[1] = value;
        SetTransportErr(transport_.Write(buff, 2));

        if(stop_required)
        {
            // write back the previous set ECR settings
            buff[0] = ecr_reg;
            buff[1] = ecr_backup;
            SetTransportErr(transport_.Write(buff, 2));
        }
    }

    enum RegMap
    {
        MPR121_TOUCHSTATUS_L = 0x00,
        MPR121_TOUCHSTATUS_H = 0x01,
        MPR121_FILTDATA_0L   = 0x04,
        MPR121_FILTDATA_0H   = 0x05,
        MPR121_BASELINE_0    = 0x1E,
        MPR121_MHDR          = 0x2B,
        MPR121_NHDR          = 0x2C,
        MPR121_NCLR          = 0x2D,
        MPR121_FDLR          = 0x2E,
        MPR121_MHDF          = 0x2F,
        MPR121_NHDF          = 0x30,
        MPR121_NCLF          = 0x31,
        MPR121_FDLF          = 0x32,
        MPR121_NHDT          = 0x33,
        MPR121_NCLT          = 0x34,
        MPR121_FDLT          = 0x35,

        MPR121_TOUCHTH_0    = 0x41,
        MPR121_RELEASETH_0  = 0x42,
        MPR121_DEBOUNCE     = 0x5B,
        MPR121_CONFIG1      = 0x5C,
        MPR121_CONFIG2      = 0x5D,
        MPR121_CHARGECURR_0 = 0x5F,
        MPR121_CHARGETIME_1 = 0x6C,
        MPR121_ECR          = 0x5E,
        MPR121_AUTOCONFIG0  = 0x7B,
        MPR121_AUTOCONFIG1  = 0x7C,
        MPR121_UPLIMIT      = 0x7D,
        MPR121_LOWLIMIT     = 0x7E,
        MPR121_TARGETLIMIT  = 0x7F,

        MPR121_GPIODIR    = 0x76,
        MPR121_GPIOEN     = 0x77,
        MPR121_GPIOSET    = 0x78,
        MPR121_GPIOCLR    = 0x79,
        MPR121_GPIOTOGGLE = 0x7A,

        MPR121_SOFTRESET = 0x80,
    };

  private:
    Config    config_;
    Transport transport_;
    bool      transport_error_;

    void SetTransportErr(bool err) { transport_error_ |= err; }

    Result GetTransportErr()
    {
        Result ret       = transport_error_ ? ERR : OK;
        transport_error_ = false;
        return ret;
    }

}; // class

using Mpr121I2C = Mpr121<Mpr121I2CTransport>;

} // namespace daisy
#endif