MPR121 I2C Capacitive Touch Sensor

Touch sensors are used in many embedded devices. The Freescale MPR121 is a second generation capacitive touch sensor controller with 12 capacitive sensing inputs. It has an I2C interface. Sparkfun recently introduced two breakout boards that use the MPR121.

MPR121 touchpad
Sparkfun MPR121 Capacitive Touch Keypad

The Sparkfun Capacitive Touch Keypad breakout includes 12 keys and a MPR121 mounted on the back of the PCB. Header pins can be soldered to the board for use on a breadboard.

MPR121 breakout
MPR121 Capacitive Touch Sensor Breakout Board

With the breakout board you can make slider wheels and slide bars with a custom designed electrode pattern or have up to 20 touch buttons in a matrix pattern.

whell
A typical slide wheel and slide bar electrode pattern using 10 channels

More information can be found in the data sheet and application notes from Freescale.
MPR121 Datasheet
Application Notes



Here is a short video showing the touch keypad running on mbed. As each key is hit, note that the binary number for the key (1..12) appears in the LEDs. As each key is released the LEDs return to a value of 0 (i.e., no key hit).

The demo code seen in the touch keypad video is based on C++ code posted earlier in the forum at http://mbed.org/forum/electronics/topic/1873/ by Anthony Buckton that can be found at http://mbed.org/users/abuckton/programs/MPR121/lnag0q.

A minor change was made to his demo code to output the key code to the LEDs for use in the video. After a somewhat involved initial power-on configuration sequence, each key hit or release generates an interrupt. By reading two 8-bit I2C registers, you can obtain the status of the 12-keys. Each key is a bit in a register. The interrupt routine reads the registers and outputs the keycode (1..12) to the LEDs in binary.

Import programMPR121_Demo

MPR121 demo code for 12 key Sparkfun touch keypad - sends key number to LEDs. Based on Sparkfun MPR121 code ported to mbed by Anthony Buckton

The existing demo code is based on a Sparkfun example and it prints some test data to the PC's USB virtual com port (not seen in video). Once you have the touch pad working, you will probably want to delete that portion of the demo code. Two additional files are used in the project, mpr121.cpp and mpr121.h. Here is the minimal demo code with the debug printfs removed:

Touch_keypad_demo_code

#include <mbed.h>
#include <mpr121.h>
DigitalOut led1(LED1);
DigitalOut led2(LED2);
DigitalOut led3(LED3);
DigitalOut led4(LED4);
// Create the interrupt receiver object on pin 26
InterruptIn interrupt(p26);
// Setup the i2c bus on pins 9 and 10
I2C i2c(p9, p10);
// Setup the Mpr121:
// constructor(i2c object, i2c address of the mpr121)
Mpr121 mpr121(&i2c, Mpr121::ADD_VSS);

// Key hit/release interrupt routine
void fallInterrupt() {
  int key_code=0;
  int i=0;
  int value=mpr121.read(0x00);
  value +=mpr121.read(0x01)<<8;
  // LED demo mod
  i=0;
  // puts key number out to LEDs for demo
  for (i=0; i<12; i++) {
  if (((value>>i)&0x01)==1) key_code=i+1;
  }
  led4=key_code & 0x01;
  led3=(key_code>>1) & 0x01;
  led2=(key_code>>2) & 0x01;
  led1=(key_code>>3) & 0x01;
}

int main() {
  interrupt.fall(&fallInterrupt);
  interrupt.mode(PullUp);
  while (1) {}
}

After soldering header pins to the touch keypad, some small plastic or felt sticky pads on the back side of the PCB about the same thickness as the black plastic on the header pins helps quite a bit to stabilize the touch keypad on top of a breadboard. It can also be attached using the screw holes provided.

/media/uploads/4180_1/_scaled_touchpad_bottom.jpg
Bottom of Touch Keypad

Wiring

Here are the connections for the demo code:

mbedTouch Keypad
GndGnd
p9SDA
p10SCL
p26IRQ
Vout(3.3V)Vcc

The I2C pullups on SDA and SCL are not provided on the touch keypad board, so add two 4.7K ohm pullups to 3.3V to the circuit. The other breakout board with the MPR121 only, has the pullups on the board.


Report

10 comments on MPR121 I2C Capacitive Touch Sensor:

27 Jun 2013

Hello there

I have modified your code a bit and in the fall_Interrupt I am lighting up a strip of LEDs ( therefore it takes about 2-3 seconds for the completion of the task). If u just tap, or release the electrode before completion, interrupts stop working whatsoever. I think it has to do with the fact that releasing your finger causes another interrupt and since the old interrupt is already running, it blocks any futher ones until restarting the whole MBED.

I am looking for a solution to the problem, and the only one I can think of is making a small ( wait(2) ) delay b/n the possibility of an interrupt being called. This should be done somewhere in the MPR121 files, I think, but I am not sure where at all..

27 Jun 2013

An interrupt service routine should not take 2-3 seconds to complete. That is not a good way to handle the interrupt. Even if you can fix this particular problem the code would be hard to reuse when there are other sources that could also generate interrupts that need immediate attention. In specific cases you can temporarily disable the interrupt to avoid problems. However, in general it is better to let the interrupt respond quickly, get the key that was pressed and return to the main loop. The interrupt handler needs to store the pressed key in some global variable and let ''main'' take action on the key. That could be done in the ''while(1)'' loop by calling a timer to activate and switch off your strip of LEDs.

28 Jun 2013

Thanks for the fast response!! I hear you absolutely that a 2-3sec interrupt is a bad idea. I tried following the GLOBAL VAR + BIG IF in the while loop, I've attached the main.cpp as a file; /media/uploads/daskalov/main.txt sorry couldnt get this importing stuff working.

It still blocks once you release and basically triggering the interrupt for the 2nd time..

28 Jun 2013

I dont have the sensor or strip, so I cant test. The interrupt should not reset the stateTrigger until main has processed the previous one and finished. You should also probably define the variables in the interrupt routine as 'volatile'. I assume the I2C reads have been tested and are OK. Note that you need pull-up Rs on SDA and SCl or I2C communication will hang mbed.

Try this

snip

volatile bool stateTrigger;       // true if hit , false if released
volatile int buttonValue;         // value of the pin touched.


// Key hit/release interrupt routine for MPR121
void fallInterrupt() {

//Only read the device when the previous interrupt has been handled by main

  if (stateTrigger == false) {   // test global variable

    buttonValue = mpr121.read(0x00);    
    buttonValue += mpr121.read(0x01)<<8;

    stateTrigger = true;   // set global variable
  }
}

int main() {
    stateTrigger = false;   
    buttonValue = 0;             

    interrupt.mode(PullUp);              
    interrupt.fall(&fallInterrupt);            // Attach interrupt and its mode respectively.
     
    while (1) {
        
//Test for state change
        if (stateTrigger == true) {

            switch (buttonValue) {                 //r,g,b
                case 1 : colorChase(strip.Color(127,0,0), 10); break;   // red (instantaneously!!! , delay = 20ms)

                snip 

               default : colorChase(strip.Color(127,127,0), 100);    // orange   (2buttons)
            }

            stateTrigger = false; //main is done, release interrupt handler for next button
        }
        else {
          snip
        }                  // big if (buttonPressed)
    }              // while

}          // main

28 Jun 2013

Dear Wim So each piece of the code and hardware for each device has been tested seperately, works correctly. I have been trying to collect it all together now, and here comes the problem.

Unfortunately, I left the project at work, and I will be able to test your solution on Monday.

I am just not sure by introducing the: 41 " stateTrigger = false main is done, release interrupt handler for next button " will the problem of releasing the button( which will call the int. routine fallinterrupt again ) be fixed? (sorry if this sounds stupid in some way, I just don't see it)

28 Jun 2013

The interrupt handler will still be called every time a button is pressed or released. It just wont do anything while main is still busy showing the LED chase. I am not sure why the mpr121.read() may be causing problems. The only thing is that it uses separate i2c start, stop and byte send operations. It would be faster to use the blockreads or writes. Anyhow, I cant test it because I dont have that device nor the LED strips.

A quick test of the basic functionality of my example code above with some printf() statements and a PCF8574 I2C portexpander worked just fine: interrupts were detected multiple times even when main was stuck in a long wait..

Are you sure the problem is not in your switch statement: the mpr121 will return a '0' when the key is released. That means your switch calls the 'default' case which is a very slow LED chase (100ms delays). Better use a special case for '0' that does not do anything.

Are you using any RTOS libs. That may cause some unexpected issues.

29 Jun 2013

I did not have a case '0' because the previous code returned 0 only when the boolean global variable was false and therefore it wouldn't enter to execute the switch statements ( never giving me the slow yellow 2button default case ).

I haven't used, like ever, RTOS libs, I will look into that.

Thanks again, I really hope it just fires off on Monday.

01 Jul 2013

Hi Wim

So I did implement the code modifications that you suggested and it all works fine as long as you wait for the colorChase function in the switch statement to be executed completely. BIG IF refers to the 1st if condition to be true and to enter switch st-s.

This is what I get from debugging when the function colorChase is being completely executed:

  • Entered fallInterrupt (hitting)
  • Entered upgradeValue in fallInterupt
  • Entered BIG IF
  • Left BIG IF
  • Entered fallInterrupt (releasing)
  • Entered upgradeValue in fallInterupt
  • Entered BIG IF
  • Left BIG IF

This is what I get when it is not waited and after that "interrupts block.." :

  • Entered fallInterrupt
  • Entered upgradeValue in fallInterupt
  • Entered BIG IF
  • Entered fallInterrupt
  • Left BIG IF

So my question is how to avoid this? It is unacceptable to have to reset the MCU when people are interacting with it..

01 Jul 2013

Minko Daskalov wrote:

This is what I get from debugging when the function colorChase is being completely executed:

  • Entered fallInterrupt (hitting)
  • Entered upgradeValue in fallInterupt

So my question is how to avoid this? It is unacceptable to have to reset the MCU when people are interacting with it..

You should not use printf inside the interrupt function and inside main at same time. The printf is non-reentrant and this may lead to problems. See the warning on the mbed/handbook/interruptin page. Please try without printf inside the interrupt.

01 Jul 2013

Hi again I have tried without the printf-s it's the same story but I did make it work today!! Clumpsy but working. It works every time you reset the MCU, therefore I just copied the initialization code in the BIG IF ( after switch statements ) and it works just perfectly now. Thanks !

Please login to post comments.