Mpr121 capacitive touch sensor driver

Discussing the implementation of the Mpr121-hal crate and its usage.

Siebencorgie published on
3 min, 532 words

Categories: Embedded

Writing a driver (again)

The last driver for the FS1027-Dg flow sensor was motivated by my MIDI-Sax build. My first prototype used this sensor and a couple of buttons to assemble the sax. The result works surprisingly well. But normal buttons tampered with the playing experience since it is not always easy to press such small buttons. My next approach was using MX-Switches (known from mechanical keyboards) to get a better key press experience. However the travel was way too long in practice. Which brings me to touch based keys.

Researching the topic brought me to Adafruit's Mpr121 capacitive touch sensor. Its exactly what I need (I²C based and >= 10 channels). So I decided to buy the board.

While waiting for the board I had searched for Rust HAL integration. I found two related crates mpr121 and adafruit-mpr121. Both didn't fit my needs. The first one targets embedded programming, but doesn't seem to be fully implemented at the moment. The second one targets RaspberryPi + Linux. It doesn't use embedded-hal but the Linux I²C driver.

So I decided to write my own embedded-hal implementation.

While implementing the driver I had two main references, the C++ implementation from Adafruit, and the data sheet. My goal was to mirror the C++ implementation as close as possible, since this is probably the way most people are used to interact with the board. After all its sold mostly by Adafruit. I also wanted to introduce the rusty way of handling errors and data. The result is a similar API with better error reporting and some convenience functions.


The crate is hosted on and comes with documentation. However, a quick start would be to setup a I²C connection between the sensor and your dev-board like this:

Connecting MPR121 module to esp32

In code you have to create a valid I²C instance using embedded-hal and create a mpr121 instance using that bus. This look similar to the pseudocode below.

use your_board_hal::i2c::I2C;
use mpr121-hal::Mpr121;

fn main(){

    //... gpio setup code for your device

    let i2c = I2C::new(gpio.sdl, gpio.sdc);
    let mut mpr = Mpr121::new_default(i2c);
        for channel in 0..12{
            println("[{}]: {}", channel, mpr.get_sensor_touch(channel));
    //if you need back the I²C bus, or the gpio pins, use free
    let i2c =;


The board can be configured to respond to different addresses on the I²C line. This is helpfull if another device already uses the address on the same line, or if you use multiple mpr121 boards on the same line.

By default the address is 0x5a. This is the address used by Mpr121::new_default as well. If you connect the addr pin of the mpr121 board to Vdd, Sdc or Sdl you change the address the board listens on. To set the address use Mpr121::new() where the address mirrors your physical addr pin connection.