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;
}

MMA7455L 3-axis accelerometer notes

The Safecast X Kickstarter Geiger counter which I wrote the firmware for contained a MMA7455L 3-axis accelerometer. The MMA7455L is an I2C device and it’s fairly easy to work with.

We didn’t end up doing much with the accelerometer but the readings were stored in the log files. I figured it might be interesting to filter the readings based on device orientation at some point.

Here’s the relevant portion of the code, it was written for the STM32 (and libmaple) but should be easily adaptable for other platforms, accel_read_state wakeups up the accelerometer and grabs a reading, before sending it back to sleep, this fit the use-case in the geiger counter where we were only taking periodic readings.

#include "i2c.h"
#include <stdint.h>
#define ACCEL_I2C I2C1
#define ACCEL_ADDR 0x1D

static struct i2c_dev *i2c = ACCEL_I2C;

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

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

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

    result = i2c_master_xfer(i2c, &msg, 1, 1);

    return result;
}

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

    byte = addr;
    msgs[0].addr   = msgs[1].addr   = ACCEL_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;
    i2c_master_xfer(i2c, msgs, 2, 1);
    return byte;
}


static void accel_wakeup(void) {
    /* Set the mode to "measurement", measuring 2g */
    accel_write(0x16, 0x04 | 0x01);
}

static int accel_ready(void) {
    return (accel_read(0x09) & 1);
}

static int accel_sleep(void) {
    return accel_write(0x16, 0);
}


uint8 accel_read_state(int16 *x, int16 *y, int16 *z) {
  struct i2c_msg msgs[2];
  signed char values[6];
  uint8 addr = 0x00; /* 10-bits read value */
  int32 result = 0;

  accel_wakeup();
  for(int n=0;(!accel_ready()) && (n < 10);n++) delay_us(1000);
  if(!accel_ready()) return 200;

  msgs[0].addr   = ACCEL_ADDR;
  msgs[0].length = sizeof(uint8_t);
  msgs[0].data   = &addr;
  msgs[0].flags  = 0;

  msgs[1].addr   = ACCEL_ADDR;
  msgs[1].length = sizeof(values);
  msgs[1].data   = (uint8 *)values;
  msgs[1].flags  = I2C_MSG_READ;

  result = i2c_master_xfer(i2c, msgs, 2, 1);

  if(result == I2C_STATE_ERROR  ) return 100;
  if(result == I2C_ERROR_TIMEOUT) return 10;

  if (x)
      *x = (values[1]<<2) | (values[0]);
  if (y)
      *y = (values[3]<<2) | (values[2]);
  if (z)
      *z = (values[5]<<2) | (values[4]);

  accel_sleep();

  return 0;
}

int accel_init(void) {
  return 0;
}

int accel_deinit() {
    // suspends accel
    accel_write(0x16, 0);
    return 0;
}

hp t5325 thin client information and manuals

I’ve previously written about hacking the t5325. But I’ve been interesting in buying one again for another project. In preparation I’ve collected some data on them here.

Manuals

Troubleshooting manual
Getting started (not very useful)

Specs

Feature
Description

Processor

  • Marvell ARM 88F6281 1.2 GHz

Chipset

  • XGI VOLARI-Z11-A2CB-HF with 64MB dedicated memory

Memory

  • 512MB DDR2 RAM + 64MB dedicated video RAM

Flash memory

  • 512MB

Graphics

  • XGI VOLARI with 64MB dedicated video RAM

Audio

Output:

  • Internal amplified speaker

  • 1/8-inch mini-jack

  • 24-bit stereo

  • 192-kHz sample rate

Input:

  • 1/8-inch microphone mini-jack

  • 20-bit stereo

  • 96-kHz sample rate

Networking

  • 10/100 Ethernet (RJ-45)

  • DHCP or Static IP support

  • Wake on LAN (WOL)

Input/output/peripheral Support

  • Keyboard: USB keyboard

  • Mouse: USB mouse

  • Printer: Local and/or network printers (RDP, ICA, LPD)

  • Video: DVI-I digital video output (DVI to VGA adapter included)

HY-DIV268N-5A Stepper driver with Arduino

I’ve previously posted about the HY-DIV268N-5A and shown how it can be driven with a function generator and how I used to in my Proxxon CNC Mill conversion.

Using the HY-DIV268N-5A with an Arduino is equally simple. You can use the HY-DIV268N-5A directly, or with the often supplied parallel driver board.

The image below show how the HY-DIV268N-5A should be connected, make sure all the black wires are connected to ground and that the stepper is connected correctly.

Make sure the lights on the HY-DIV268N-5A are correctly illuminated. They wont be if you don’t supply enough voltage/current. Refer to my previous post for specifications.

Once the stepper is correctly connected you should be able to drive the stepper with pulses from the Arduino. Make sure the Enable pin is grounded. And the grounds are common between the driver and the Arduino. All grounds in the system should be connected. Then simply send pulses from the Arduino to the PUL+ pin, pulling DIR+ high/low to change the direction. Use the normal digitalWrite function to do this.