skip to Main Content

Interfacing a touch keypad with an AVR

I’ve always admired touchpad technology and I recently got a chance to make my own 12-key touchpad using Freescale’s MPR121Q. This nifty little chip supports upto 12 debounced pads, configurable registers and works on the I2C protocol. All the electrodes can be combined into one electrode that acts as a proximity sensor. Also of the 12 electrodes, 8 can also be used as GPIO pins that can drive LEDs.

For a rundown on how to create your own touch keypad using this chip see this post. Gerbers, schematics are all included in the download here.

The basic task to get the MPR121Q working is by setting up a bunch of registers. These essentially control a variety of things including touch and release thresholds, baseline levels, touch filter levels (for debounced outputs) etc. More details on that are in the datasheet. For a quick guide to knowing what these registers do and how to set them up read the AN3944 application note from Freescale.

The Schematic

My test setup included interfacing the MPR121Q with an Atmega8A. I also included an LCD display that shows which of the keypads were touched. The code includes the I2C and MPR121 read, write and configuration routines from Sparkfun’s code. The LCD code is based on Peter Fluery’s excellent LCD tutorial. Please note that the code is in C. I compiled it using Atmel Studio 6.1. If you are using any Arduino flavors, check out Sparkfun’s code which gives the code in Arduino syntax.

The MPR121Q is a 3.3V device and connecting it to a 5V device may cause it to burn out.

Though folks at Sparkfun say that this can be hooked up to an Uno or Duemilanove or other 5V Arduino’s directly (only the SDA, SCL and IRQ pins. The VCC still needs to be 3.3V) I’ve not tried it. My entire setup is based on 3.3V (except for the LCD which is on 5V).

The Code

Before you upload the code, you may want to set the Atmega8A fuse bits to use an external oscillator. Also since the whole setup is on 3.3V the maximum oscillator frequency is 8MHz. The code also supports sending data through an UART. Hence to keep error rates to 0.00% a 7.3728MHz crystal is chosen.

lfuse: 0xFF
hfuse: 0xC9

Here is my code. Its quite long and you may want to break it up into libraries. The code is appropriately commented. If you have queries give us a shout in the comments.

/*
 This code demonstrates the functionality of a 12-key touchpad 
 based on Freescale's MPR121Q. This code uses parts of Sparkfun's 
 code (the I2C and MPR121 config routines) for their MPR121Q 
 breakout board and their help is much appreciated. This code 
 was compiled using Atmel Studio 6.1. 

 The touchpad checks are based on the electrode wiring configuration 
 mentioned below. If any other configuration is used, appropriate
 changes need to be made in the #defines in the code.

 This code also includes UART routines which can be used in tandem
 with mpr121ReadConfig() and mpr121ReadRegStatus() functions to check
 if the MPR121 is configured properly.

 In its unaltered form this code writes data read form the touchpad 
 to an LCD module. Peter Fleury's LCD library is used here and his 
 help is much appreciated. Connections to the LCD are also listed here. 
 The LCD is a widely available 16x2 character HD44780-based module.

 Hardware:
 ---------
 # An ATmega8A powered with 3.3V (since the MPR121Q runs on 3.3V)
   and 5V (only for the LCD module).
 # 100n capacitors on every VCC and GND pair on the ATmega8A.
 # 100n and 10u capacitors on the main VCC and GND on the ATmega8A.
 # 10K resistor between RESET and VCC.
 # 27pF capacitors between the XTAL pins and GND.

 MPR121 connections
 ------------------

 IRQ is PD2
 SDA is PC4
 SCL is PC5

 Touchpad	MPR121Q electrodes
 --------	------------------
	1		ELE 03 (pin 11)
	2		ELE 07 (pin 15)
	3		ELE 11 (pin 19)
	4		ELE 02 (pin 10)
	5		ELE 06 (pin 14)
	6		ELE 10 (pin 18)
	7		ELE 01 (pin 09)
	8		ELE 05 (pin 13)
	9		ELE 09 (pin 17)
	0		ELE 04 (pin 12)
	*		ELE 00 (pin 08)
	#		ELE 08 (pin 16)

 LCD connections
 ---------------
 Be sure to define XTAL in lcd.h to the 
 same F_CPU as this file.

 RS - PD5
 RS - PD6
 EN - PD7

 Data lines:
 D4 - PB4
 D5 - PB3
 D6 - PB2
 D7 - PB1

 */ 

#define F_CPU 7372800

#include <stdlib.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "types.h"
#include "defs.h"
#include "i2c.h"
#include "mpr121.h"
#include "lcd.h"

//Define UART settings
#define BAUD_RATE 9600
#define MYUBRR (((F_CPU / (BAUD_RATE * 16UL))) - 1)

// Mapping the keypad with MPR121Q electrodes
#define ONE 3
#define TWO 7
#define THREE 11
#define FOUR 2
#define FIVE 6
#define SIX 10
#define SEVEN 1
#define EIGHT 5
#define NINE 9
#define ZERO 4
#define STAR 0
#define HASH 8

unsigned int readData; //variable to read data from the MPR121's config 
					   //registers. Should be read in HEX or binary format.

#define MPR121_R 0xB5	// ADD pin is grounded
#define MPR121_W 0xB4	// So address is 0x5A

#define sbi(var, mask)   ((var) |= (uint8_t)(1 << mask))
#define cbi(var, mask)   ((var) &= (uint8_t)~(1 << mask))

///============Function Prototypes=========///

char mpr121Read(unsigned char address);
void mpr121Write(unsigned char address, unsigned char data);
void mpr121QuickConfig(void);
void mpr121ReadConfig(void);
void mpr121ReadRegStatus(void);
unsigned char whichPad(void);
void printPadLCD(void);

void USART_Init(unsigned int ubrr);
void USART_Transmit(unsigned char data);
unsigned char USART_Receive(void);

int checkInterrupt(void);

void ioinit(void);

int main (void)
{
	USART_Init(MYUBRR);
	lcd_init(LCD_DISP_ON);

	ioinit();
	i2cInit();

	mpr121QuickConfig();

	lcd_clrscr();
	lcd_puts("System ON");

	while(1)
	{
		while(checkInterrupt()) //do nothing till the interrupt is triggered
			;
		printPadLCD();
	}
}

char mpr121Read(unsigned char address)
{
	char data;

	i2cSendStart();
	i2cWaitForComplete();

	i2cSendByte(MPR121_W);	//write 0xB4
	i2cWaitForComplete();

	i2cSendByte(address);	//write register address
	i2cWaitForComplete();

	i2cSendStart();

	i2cSendByte(MPR121_R);	//write 0xB5
	i2cWaitForComplete();
	i2cReceiveByte(TRUE);
	i2cWaitForComplete();

	data = i2cGetReceivedByte();	//get MSB result
	i2cWaitForComplete();
	i2cSendStop();

	cbi(TWCR, TWEN);	// Disable TWI
	sbi(TWCR, TWEN);	// Enable TWI

	return data;
}

void mpr121Write(unsigned char address, unsigned char data)
{
	i2cSendStart();
	i2cWaitForComplete();

	i2cSendByte(MPR121_W);	// write 0xB4
	i2cWaitForComplete();

	i2cSendByte(address);	// write register address
	i2cWaitForComplete();

	i2cSendByte(data);
	i2cWaitForComplete();

	i2cSendStop();
}

// MPR121 Quick Config
// This will configure all registers as described in AN3944

void mpr121QuickConfig(void)
{
	// Section A
	// This group controls filtering when data is > baseline.
	mpr121Write(MHD_R, 0x01);
	mpr121Write(NHD_R, 0x01);
	mpr121Write(NCL_R, 0x00);
	mpr121Write(FDL_R, 0x00);

	// Section B
	// This group controls filtering when data is < baseline.
	mpr121Write(MHD_F, 0x01);
	mpr121Write(NHD_F, 0x01);
	mpr121Write(NCL_F, 0xFF);
	mpr121Write(FDL_F, 0x02);

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

	// Section D
	// Set the Filter Configuration
	// Set ESI2
	mpr121Write(FIL_CFG, 0x04);

	// 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, 0x06);	// Enable first 6 electrodes
	mpr121Write(ELE_CFG, 0x0C);		// Enables all 12 Electrodes

	// Section F
	// Enable Auto Config and auto Re-config
	//mpr121Write(ATO_CFG0, 0x0B);
	//mpr121Write(ATO_CFGU, 0xC9);	// USL = (Vdd-0.7)/vdd*256 = 0xC9 @3.3V
	//mpr121Write(ATO_CFGL, 0x82);	// LSL = 0.65*USL = 0x82 @3.3V
	//mpr121Write(ATO_CFGT, 0xB5);	// Target = 0.9*USL = 0xB5 @3.3V
}

int checkInterrupt(void)
{
	if ((PIND & (1<<2)) == 0)
		return 0;
	else
		return 1;
}

void ioinit (void)
{
    //1 = output, 0 = input
	DDRB = 0b00111111; //PORTB1, B1 output
    DDRC = 0b00010111; //PORTC4 (SDA), PORTC5 (SCL), PORTC all others are inputs
    DDRD = 0b11111010; //PORTD (RX on PD0), IRQ on PD2
	PORTC = 0b00110000; //pullups on the I2C bus
}

unsigned char whichPad(void)
{
	unsigned char touchedPad;

	//Read all pads based on the electrode connections listed above
	// HEX values returned by mpr121Read() show which bits are set
	// and hence which pads are touched.

	if (mpr121Read(0x00) == 0x08 && mpr121Read(0x01) == 0x00)
		touchedPad = '1';
	else if (mpr121Read(0x00) == 0x80 && mpr121Read(0x01) == 0x00)
		touchedPad = '2';
	else if (mpr121Read(0x00) == 0x00 && mpr121Read(0x01) == 0x08)
		touchedPad = '3';
	else if (mpr121Read(0x00) == 0x04 && mpr121Read(0x01) == 0x00)
		touchedPad = '4';
	else if (mpr121Read(0x00) == 0x40 && mpr121Read(0x01) == 0x00)
		touchedPad = '5';
	else if (mpr121Read(0x00) == 0x00 && mpr121Read(0x01) == 0x04)
		touchedPad = '6';
	else if (mpr121Read(0x00) == 0x02 && mpr121Read(0x01) == 0x00)
		touchedPad = '7';
	else if (mpr121Read(0x00) == 0x20 && mpr121Read(0x01) == 0x00)
		touchedPad = '8';
	else if (mpr121Read(0x00) == 0x00 && mpr121Read(0x01) == 0x02)
		touchedPad = '9';
	else if (mpr121Read(0x00) == 0x10 && mpr121Read(0x01) == 0x00)
		touchedPad = '0';
	else if (mpr121Read(0x00) == 0x01 && mpr121Read(0x01) == 0x00)
		touchedPad = '*';
	else if (mpr121Read(0x00) == 0x00 && mpr121Read(0x01) == 0x01)
		touchedPad = '#';

	return touchedPad;
}

/* UART functions */

void USART_Init(unsigned int ubrr)
{
	/* Set baud rate*/
	UBRRH = (ubrr>>8);
	UBRRL = ubrr;

	/* Enable receiver and transmitter */
	UCSRB = (1<<RXEN) | (1<<TXEN);

	/* Set frame format: 8data, 1 stop bit */
	UCSRC = 0b10000110; //URSEL=1, UMSEL=0, UPM1=0, UPM0=0, UCSZ1=1, UCSZ0=1, UCPOL=0.
}

void USART_Transmit(unsigned char data)
{
	/* Wait for empty transmit buffer */
	while(!(UCSRA & (1<<UDRE)))
		;
	/* Put data into buffer, sends the data */
	UDR = data;
}

unsigned char USART_Receive(void)
{
	/* Wait for data to be received */
	while(!(UCSRA & (1<<RXC)))
		;
	/* Get and return received data from buffer */
	return UDR;
}

/* Output MPR121Q configuration registers contents on the UART */

void mpr121ReadConfig(void)
{
	readData = mpr121Read(0x2B); //MHD rising
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x2C); //NHD rising
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x2D); //NCL rising
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x2E); //FDL rising
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x2F); //MHD falling
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x30); //NHD falling
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x31); //NCL falling
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x32); //FDL falling
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x41); //ELE0 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x42); //ELE0 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x43); //ELE1 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x44); //ELE1 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x45); //ELE2 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x46); //ELE2 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x47); //ELE3 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x48); //ELE3 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x49); //ELE4 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x4A); //ELE4 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x4B); //ELE5 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x4C); //ELE5 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x4D); //ELE6 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x4E); //ELE6 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x4F); //ELE7 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x50); //ELE7 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x51); //ELE8 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x52); //ELE8 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x53); //ELE9 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x54); //ELE9 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x55); //ELE10 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x56); //ELE10 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x57); //ELE11 touch threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x58); //ELE11 release threshold
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x5D); //Filter configuration
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x5E); //Number of electrodes enabled
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x7B); //AUTO-CONFIG control register 0
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x7D); //AUTO-CONFIG USL register
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x7E); //AUTO-CONFIG LSL register
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x7F); //AUTO-CONFIG Target Level register
	USART_Transmit(readData);
	_delay_ms(1000);
}

/* Output MPR121Q touch status register contents on the UART */

void mpr121ReadRegStatus(void)
{
	readData = mpr121Read(0x00); //ELE0-7 read status
	USART_Transmit(readData);
	_delay_ms(1000);

	readData = mpr121Read(0x01); //ELE8-11 read status
	USART_Transmit(readData);
	_delay_ms(1000);
}

void printPadLCD(void)
{
	if (whichPad() == '1')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('1');
	}
	else if (whichPad() == '2')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('2');
	}
	else if (whichPad() == '3')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('3');
	}
	else if (whichPad() == '4')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('4');
	}
	else if (whichPad() == '5')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('5');
	}
	else if (whichPad() == '6')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('6');
	}
	else if (whichPad() == '7')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('7');
	}
	else if (whichPad() == '8')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('8');
	}
	else if (whichPad() == '9')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('9');
	}
	else if (whichPad() == '0')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('0');
	}
	else if (whichPad() == '*')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('*');
	}
	else if (whichPad() == '#')
	{
		lcd_clrscr();
		lcd_puts("Pad Touched: ");
		lcd_putc('#');
	}
}

This Post Has 6 Comments

  1. So, if the ‘#’ is pressed, you end up reading either register 00 or 01  a total of 288 times before you determine what key was pressed ?

    1. To be honest, this was a very long time ago and I don’t remember much of it. In the ‘printPadLCD’ function the two registers seems to be read once each for 11 times before it reaches the ‘#’ key (through the ‘whichPad’ function). It obviously could’ve been written to just read the register once and store its value in a variable and then compare against a known set of values for printing on the LCD. The ‘whichPad’ function, each register gets read once and that value is then compared against the set values of the keypad.

    1. I’ve never used CVAVR and I do not have the hex file with me as of now. I worked on this project a long time ago… Sorry!

  2. hello dear friend
    I was tormented for a long time to launch your project in Avr Studio 6.1 but I always get error.
    could you comile your project in avr studio 6.1 and send me a hex file?
    I am ready to accept your commercial conditions for receiving a working hex file.
    david

Leave a Reply

Your email address will not be published.

Back To Top