MPR121 Captouch sensor notes

The MPR121 is a capacitive sensor that was used in the Safecast Kickstarter Geiger counter (Onyx). I figured that the code could be of use to someone else adding MPR121 support to a STM32 (libmaple) project. The code could of course be adapted to other platforms with I2C support fairly easily. The complete codebase is of course of github here

From memory the fiddly bits were finding the right parameters to get the sensor working through the plastic enclosure (parameters hardcoded in cap_init). And working round issues were I’d randomly see all the sensors firing. There was a lot of fiddling to get the captouch to be responsive, but not to misfire…

#include "mpr121.h"
#include "i2c.h"

#define CAPTOUCH_ADDR 0x5A
#define CAPTOUCH_I2C I2C1
#define CAPTOUCH_GPIO 30

GUI *system_gui;
static struct i2c_dev *i2c;
static uint16 touchList =  1 << 8 | 1 << 6 | 1 << 4 | 1 << 3 | 1 << 2 | 1 << 0;

int last_key_state=0;
bool captouch_disable_messages=false;

void cap_set_disable_messages(bool b) {
 captouch_disable_messages=b;
}

static void mpr121Write(uint8 addr, uint8 value) {
  struct i2c_msg msg;
  uint8 bytes[2];
  int result;

  bytes[0] = addr;
  bytes[1] = value;

  msg.addr = CAPTOUCH_ADDR;
  msg.flags = 0;
  msg.length = sizeof(bytes);
  msg.xferred = 0;
  msg.data = bytes;

  result = i2c_master_xfer(i2c, &msg, 1, 100);
  return;
}

static uint8 mpr121Read(uint8 addr) {
  struct i2c_msg msgs[2];
  uint8 byte;

  byte = addr;
  msgs[0].addr   = msgs[1].addr   = CAPTOUCH_ADDR;
  msgs[0].length = msgs[1].length = sizeof(byte);
  msgs[0].data   = msgs[1].data   = &byte;
  msgs[0].flags = 0;
  msgs[1].flags = I2C_MSG_READ;
  int result = i2c_master_xfer(i2c, msgs, 2, 100);

  // retry reads
  for(int n=0;(result == -1) && (n<5);n++) {
    result = i2c_master_xfer(i2c, msgs, 2, 100);
  }
  if(result == -1) return 255;

  return byte;
}

char c[100];
char *cap_diagdata(int e) {

  int elech=0;
  int elecl=0;
  if(e==0)  elecl = mpr121Read(ELE0_DATAL);
  if(e==0)  elech = mpr121Read(ELE0_DATAH);
  if(e==1)  elecl = mpr121Read(ELE1_DATAL);
  if(e==1)  elech = mpr121Read(ELE1_DATAH);
  if(e==2)  elecl = mpr121Read(ELE2_DATAL);
  if(e==2)  elech = mpr121Read(ELE2_DATAH);
  if(e==3)  elecl = mpr121Read(ELE3_DATAL);
  if(e==3)  elech = mpr121Read(ELE3_DATAH);
  if(e==4)  elecl = mpr121Read(ELE4_DATAL);
  if(e==4)  elech = mpr121Read(ELE4_DATAH);
  if(e==5)  elecl = mpr121Read(ELE5_DATAL);
  if(e==5)  elech = mpr121Read(ELE5_DATAH);
  if(e==6)  elecl = mpr121Read(ELE6_DATAL);
  if(e==6)  elech = mpr121Read(ELE6_DATAH);
  if(e==7)  elecl = mpr121Read(ELE7_DATAL);
  if(e==7)  elech = mpr121Read(ELE7_DATAH);
  if(e==8)  elecl = mpr121Read(ELE8_DATAL);
  if(e==8)  elech = mpr121Read(ELE8_DATAH);
  if(e==9)  elecl = mpr121Read(ELE9_DATAL);
  if(e==9)  elech = mpr121Read(ELE9_DATAH);
  if(e==10) elecl = mpr121Read(ELE10_DATAL);
  if(e==10) elech = mpr121Read(ELE10_DATAH);

  elech = elech & 0x3;
  uint32_t elecv = ((uint32_t)elech << 8) | (uint32_t)elecl;

  uint32_t elec_base=0;
  if(e==0)  elec_base = ((uint32_t)((uint8_t)mpr121Read(ELE0_BASE)))  << 2;
  if(e==1)  elec_base = ((uint32_t)((uint8_t)mpr121Read(ELE1_BASE)))  << 2;
  if(e==2)  elec_base = ((int)mpr121Read(ELE2_BASE))  << 2;
  if(e==3)  elec_base = ((int)mpr121Read(ELE3_BASE))  << 2;
  if(e==4)  elec_base = ((int)mpr121Read(ELE4_BASE))  << 2;
  if(e==5)  elec_base = ((int)mpr121Read(ELE5_BASE))  << 2;
  if(e==6)  elec_base = ((int)mpr121Read(ELE6_BASE))  << 2;
  if(e==7)  elec_base = ((int)mpr121Read(ELE7_BASE))  << 2;
  if(e==8)  elec_base = ((int)mpr121Read(ELE8_BASE))  << 2;
  if(e==9)  elec_base = ((int)mpr121Read(ELE9_BASE))  << 2;
  if(e==10) elec_base = ((int)mpr121Read(ELE10_BASE)) << 2;

  uint32_t bs = mpr121Read(TCH_STATL);
           bs = bs | ((0x1F & mpr121Read(TCH_STATH)) << 8);

  uint32_t is_pressed=0;
  if(bs & (1 << e)) is_pressed=1; else is_pressed=0;

  sprintf(c,"%"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"",elecv,elec_base,bs,is_pressed);

  return c;
}

bool cap_check() {
  uint8 d = mpr121Read(ELE0_DATAL);
  if(d == 255) return false;
  return true;
}

uint32_t press_time[16];
uint32_t release_time[16];
uint32_t press_time_any;
uint32_t release_time_any;

uint32_t cap_last_press(int key) {
  return press_time[key];
}

uint32_t cap_last_release(int key) {
  return release_time[key];
}

uint32_t cap_last_press_any() {
  return press_time_any;
}

uint32_t cap_last_release_any() {
  return release_time_any;
}

void cap_clear_press() {
  for(int n=0;n<16;n++) press_time[n] = 0;
  for(int n=0;n<16;n++) release_time[n] = 0;
  press_time_any=0;
  release_time_any=0;
}

/**
 * Interrupt handler for capacitive keyboard events
 */
static void cap_change(void) {

  // first read
  int key_state=0;
  key_state  = mpr121Read(TCH_STATL);
  key_state |= mpr121Read(TCH_STATH) << 8;


  // there appears to be a bug where it suddenly outputs a lot of bits set.
  unsigned int v=key_state; // count the number of bits set in v
  unsigned int c; // c accumulates the total bits set in v
  for (c = 0; v; c++) {
    v &= v - 1; // clear the least significant bit set
  }
  if(c > 3) return;


  // clear unconnected electrodes
  key_state &= touchList;

  // detect keys pressed
  int keys_pressed = key_state & (~last_key_state); //TODO: ! bitwise NOT

  // detect keys released
  int keys_released  = (~key_state) & last_key_state; //TODO: ! bitwise NOT

   bool readok=true;

  if((!captouch_disable_messages) && (readok == true)) {
    for (int key=0; key<16; key++) {
      if (keys_pressed &(1<<key)) { system_gui->receive_key(key,KEY_PRESSED );   press_time[key] = realtime_get_unixtime(); press_time_any=realtime_get_unixtime(); }
      if (keys_released&(1<<key)) { system_gui->receive_key(key,KEY_RELEASED); release_time[key] = realtime_get_unixtime(); release_time_any = realtime_get_unixtime(); }
    }
    last_key_state = key_state;
  }

}

bool cap_ispressed(int key) {
  if (last_key_state & (1<<key)) { return true; }
                            else { return false;}
}

int cap_lastkey() {
  return last_key_state;
}

uint8_t cap_mhd_r = 0x01;
uint8_t cap_nhd_r = 0x01;
uint8_t cap_ncl_r = 0xFF;
uint8_t cap_fdl_r = 0x02;

uint8_t cap_mhd_f = 0x01;
uint8_t cap_nhd_f = 0x01;
uint8_t cap_ncl_f = 0xFF;
uint8_t cap_fdl_f = 0x02;

uint8_t cap_dbr   = 0x77;

uint8_t cap_touch_threshold   = 0x10;
uint8_t cap_release_threshold = 0x0B;

void cap_set_mhd_r(uint8_t v) { cap_mhd_r = v; }
void cap_set_nhd_r(uint8_t v) { cap_nhd_r = v; }
void cap_set_ncl_r(uint8_t v) { cap_ncl_r = v; }
void cap_set_fdl_r(uint8_t v) { cap_fdl_r = v; }

void cap_set_mhd_f(uint8_t v) { cap_mhd_f = v; }
void cap_set_nhd_f(uint8_t v) { cap_nhd_f = v; }
void cap_set_ncl_f(uint8_t v) { cap_ncl_f = v; }
void cap_set_fdl_f(uint8_t v) { cap_fdl_f = v; }

void cap_set_dbr  (uint8_t v) { cap_dbr = v; }
void cap_set_touch_threshold  (uint8_t v) { cap_touch_threshold   = v; }
void cap_set_release_threshold(uint8_t v) { cap_release_threshold = v; }

void cap_init(void) {

    // 63 2 4 1 63 2 4 1 0 8 4
    cap_set_mhd_r(63);
    cap_set_nhd_r(2);
    cap_set_ncl_r(4);
    cap_set_fdl_r(1);
    cap_set_mhd_f(63);
    cap_set_nhd_f(2);
    cap_set_ncl_r(4);
    cap_set_fdl_f(1);
    cap_set_dbr(0);
    cap_set_touch_threshold(8);
    cap_set_release_threshold(4);

    gpio_set_mode(PIN_MAP[9].gpio_device,PIN_MAP[9].gpio_bit,GPIO_OUTPUT_PP);
    gpio_set_mode(PIN_MAP[5].gpio_device,PIN_MAP[5].gpio_bit,GPIO_OUTPUT_PP);
    gpio_write_bit(PIN_MAP[9].gpio_device,PIN_MAP[9].gpio_bit,1);
    gpio_write_bit(PIN_MAP[5].gpio_device,PIN_MAP[5].gpio_bit,1);
    delay_us(1000);
    gpio_set_mode(PIN_MAP[9].gpio_device,PIN_MAP[9].gpio_bit,GPIO_INPUT_PD); // Can also be floating, but PD is safer if components misplaced.
    gpio_set_mode(PIN_MAP[5].gpio_device,PIN_MAP[5].gpio_bit,GPIO_INPUT_PD);

    i2c = CAPTOUCH_I2C;
    i2c_init(i2c);
    i2c_master_enable(i2c, 0);

    mpr121Write(0x80,0x63); // soft reset
    delay_us(1000);
    mpr121Write(ELE_CFG, 0x00);   // disable electrodes for config
    delay_us(100);

    // Section A and B - R (rise) F (fall) T (touch)
    mpr121Write(MHD_R, cap_mhd_r); // (1 to 63)
    mpr121Write(NHD_R, cap_nhd_r); // (1 to 63)
    mpr121Write(NCL_R, cap_ncl_r); // (0 to 255)
    mpr121Write(FDL_R, cap_fdl_r); // (0 to 255)

    mpr121Write(MHD_F, cap_mhd_f); // (1 to 63) largest value to pass through filer
    mpr121Write(NHD_F, cap_nhd_f); // (1 to 63) maximum change allowed
    mpr121Write(NCL_F, cap_ncl_f); // (0 to 255) number of samples required to determine non-noise
    mpr121Write(FDL_F, cap_fdl_f); // (0 to 255) rate of filter operation, larger = slower.

    // Section D
    // Set the Filter Configuration
    // Set ESI2

    // was 0x01, 0x25
    mpr121Write(AFE_CONF, 0x01); //AFE_CONF  0x5C
    mpr121Write(FIL_CFG , 0x04); //FIL_CFG   0x5D

    // Section F
    mpr121Write(ATO_CFG0, 0x0B); // ATO_CFG0 0x7B

    // limits
    // was0xFF,0x00,0x0E
    mpr121Write(ATO_CFGU, 0x9C); // ATO_CFGU 0x7D
    mpr121Write(ATO_CFGL, 0x65); // ATO_CFGL 0x7E
    mpr121Write(ATO_CFGT, 0x8C); // ATO_CFGT 0x7F

    // enable debouncing
    mpr121Write(DBR     , cap_dbr); // set debouncing, in this case 7 for both touch and release.

    // Section C
    // This group sets touch and release thresholds for each electrode
    mpr121Write(ELE0_T , cap_touch_threshold);
    mpr121Write(ELE0_R , cap_release_threshold);
    mpr121Write(ELE1_T , cap_touch_threshold);
    mpr121Write(ELE1_R , cap_release_threshold);
    mpr121Write(ELE2_T , cap_touch_threshold);
    mpr121Write(ELE2_R , cap_release_threshold);
    mpr121Write(ELE3_T , cap_touch_threshold);
    mpr121Write(ELE3_R , cap_release_threshold);
    mpr121Write(ELE4_T , cap_touch_threshold);
    mpr121Write(ELE4_R , cap_release_threshold);
    mpr121Write(ELE5_T , cap_touch_threshold);
    mpr121Write(ELE5_R , cap_release_threshold);
    mpr121Write(ELE6_T , cap_touch_threshold);
    mpr121Write(ELE6_R , cap_release_threshold);
    mpr121Write(ELE7_T , cap_touch_threshold);
    mpr121Write(ELE7_R , cap_release_threshold);
    mpr121Write(ELE8_T , cap_touch_threshold);
    mpr121Write(ELE8_R , cap_release_threshold);
    mpr121Write(ELE9_T , cap_touch_threshold);
    mpr121Write(ELE9_R , cap_release_threshold);
    mpr121Write(ELE10_T, cap_touch_threshold);
    mpr121Write(ELE10_R, cap_release_threshold);
    mpr121Write(ELE11_T, cap_touch_threshold);
    mpr121Write(ELE11_R, cap_release_threshold);

    delay_us(100);

    // Section E
    // Electrode Configuration
    // Enable 6 Electrodes and set to run mode
    // Set ELE_CFG to 0x00 to return to standby mode
    mpr121Write(ELE_CFG, 0x0C);   // Enables all 12 Electrodes
    delay_us(100);

    // This can also be FLOATING, but PU is safer if components misplaced.
    gpio_set_mode(PIN_MAP[CAPTOUCH_GPIO].gpio_device,PIN_MAP[CAPTOUCH_GPIO].gpio_bit,GPIO_INPUT_PU);
    exti_attach_interrupt((afio_exti_num)(PIN_MAP[CAPTOUCH_GPIO].gpio_bit), gpio_exti_port(PIN_MAP[CAPTOUCH_GPIO].gpio_device), cap_change, EXTI_FALLING);

    // Clears the first interrupt
    for(int n=0;n<16;n++) press_time[n]   = realtime_get_unixtime();
    for(int n=0;n<16;n++) release_time[n] = realtime_get_unixtime();
    press_time_any   = realtime_get_unixtime();
    release_time_any = realtime_get_unixtime();

    return;
}

void cap_deinit(void) {
  exti_detach_interrupt((afio_exti_num)(PIN_MAP[CAPTOUCH_GPIO].gpio_bit));

  // Disable MPR121 scanning, in case the chip is on
  mpr121Write(ELE_CFG, 0x00);

  return;
}