O uso de teclados touch capacitivos é uma tendência nos dias atuais, já que a vida útil de uma tecla desse tipo é muito superior a de uma tecla mecânica. Outro fator interessante para o uso desse tipo de teclado é a possibilidade de um design mais moderno. Cada vez mais estão surgindo interfaces desse tipo, seja em eletrodomésticos, aparelhos eletrônicos ou até mesmo em interfaces industriais.
Neste artigo iremos explorar a solução proposta pelo Touchpad Shield para teclado touch capacitivo. Esse shield foi adquirido em parceria com a loja FILIPEFLOP para nossa avaliação.
Hardware
O Touchpad Shield é baseado no circuito integrado MPR121, um controlador para sensores touch capacitivos da Freescale. Possui 9 teclas capacitivas e 3 pinos para conexão de novos eletrodos externos. Além disso possui o conversor de nível de 5V para 3,3V. A figura 1 exibe o shield com as respectivas teclas no layout da placa.

Figura 1 – Touchpad Shield frente e verso.
O esquema elétrico desse shield é relativamente simples visto que possui pouco componentes. A figura 2 exibe seu esquema elétrico, note que possui apenas o MPR121 e alguns componentes:

Figura 2 – Esquemático Touchpad Shield.
Note na figura 2 que o MPR121 é alimentado com 3,3V proveniente do pino 3,3V do Arduino. Para interface com Arduino alimentado com 5V foi montado um conversor de nível para os pinos SDA e SCL da comunicação I2C. Você pode aproveitar este circuito conversor de nível para aplicações de interfaces digital entre diferentes níveis de tensões, como por exemplo interface de circuitos com a placa Arduino Due.
O MPR121 é um controlador para sensor touch capacitivo com interface I2C. Ele pode controlar até 12 eletrodos capacitivos. Outra característica interessante é que este chip pode ser utilizado como drive de até 8 LEDs, quando os pinos não estão configurados como entrada para eletrodos.
Note que o pino ADDR está conectado ao GND, dessa forma o endereço da I2C é 0X5A. Não há a possibilidade de mudança do endereço nesse hardware já que os desenvolvedores não previram jumpers de configuração na placa. O MPR121 permite até 4 endereços dependendo de onde está conectado o pino ADDR. Este pino pode ser conectado ao VSS, VDD, SDA ou SCL resultando nos endereços 0x5A, 0x5B, 0x5C e 0x5D respectivamente, porém como visto nessa placa o ADDR está conectado ao GND resultando no endereço 0x5A.
Note que além dos pinos SDA e SCL da I2C, também está ligado ao Arduino o pino IRQ. Este pino é utilizado para gerar uma interrupção quando for detectado alguma tecla pressionada ou solta. Este pino é uma saída em coletor aberto sendo ativa em nível zero. Dessa forma pode-se utilizar recurso de interrupção para leitura das teclas, conforme será abordado no código exemplo. Nesse momento o microcontrolador, que atua como Master no barramento I2C, requisita as informações para o CI, que funciona como Slave nesse mesmo barramento. Para mais informações veja o datasheet do componente.
Software
Na página do produto no site da FILIPEFLOP está disponível um código exemplo para teste do shield. Além do código para o Arduino estão disponíveis alguns arquivos que deverão ser inseridos na biblioteca do Arduino para correta compilação. O desenvolvedor optou por não utilizar a biblioteca Wire do Arduino. Vamos analisar este exemplo iniciando pelo arquivo mpr121.h:
/*
MPR121.h
April 8, 2010
by: Jim Lindblom
*/
// MPR121 Register Defines
#define MHD_R 0x2B
#define NHD_R 0x2C
#define NCL_R 0x2D
#define FDL_R 0x2E
#define MHD_F 0x2F
#define NHD_F 0x30
#define NCL_F 0x31
#define FDL_F 0x32
#define ELE0_T 0x41
#define ELE0_R 0x42
#define ELE1_T 0x43
#define ELE1_R 0x44
#define ELE2_T 0x45
#define ELE2_R 0x46
#define ELE3_T 0x47
#define ELE3_R 0x48
#define ELE4_T 0x49
#define ELE4_R 0x4A
#define ELE5_T 0x4B
#define ELE5_R 0x4C
#define ELE6_T 0x4D
#define ELE6_R 0x4E
#define ELE7_T 0x4F
#define ELE7_R 0x50
#define ELE8_T 0x51
#define ELE8_R 0x52
#define ELE9_T 0x53
#define ELE9_R 0x54
#define ELE10_T 0x55
#define ELE10_R 0x56
#define ELE11_T 0x57
#define ELE11_R 0x58
#define FIL_CFG 0x5D
#define ELE_CFG 0x5E
#define GPIO_CTRL0 0x73
#define GPIO_CTRL1 0x74
#define GPIO_DATA 0x75
#define GPIO_DIR 0x76
#define GPIO_EN 0x77
#define GPIO_SET 0x78
#define GPIO_CLEAR 0x79
#define GPIO_TOGGLE 0x7A
#define ATO_CFG0 0x7B
#define ATO_CFGU 0x7D
#define ATO_CFGL 0x7E
#define ATO_CFGT 0x7F
// Global Constants
#define TOU_THRESH 0x0F
#define REL_THRESH 0x0A
Nesse arquivo foram definidos os registradores do MPR121 para manipulação via I2C. Para mais detalhes de cada registrador consulte datasheet do MPR121.
Toda a implementação da comunicação I2C com as funções para interface com o MPR121 é feita no arquivo I2C.h. Confira o código desse arquivo a seguir:
// This library provides the high-level functions needed to use the I2C
// serial interface supported by the hardware of several AVR processors.
#include <avr/io.h>
#include <avr/interrupt.h>
#include "types.h"
#include "defs.h"
// TWSR values (not bits)
// (taken from avr-libc twi.h - thank you Marek Michalkiewicz)
// Master
#define TW_START 0x08
#define TW_REP_START 0x10
// Master Transmitter
#define TW_MT_SLA_ACK 0x18
#define TW_MT_SLA_NACK 0x20
#define TW_MT_DATA_ACK 0x28
#define TW_MT_DATA_NACK 0x30
#define TW_MT_ARB_LOST 0x38
// Master Receiver
#define TW_MR_ARB_LOST 0x38
#define TW_MR_SLA_ACK 0x40
#define TW_MR_SLA_NACK 0x48
#define TW_MR_DATA_ACK 0x50
#define TW_MR_DATA_NACK 0x58
// Slave Transmitter
#define TW_ST_SLA_ACK 0xA8
#define TW_ST_ARB_LOST_SLA_ACK 0xB0
#define TW_ST_DATA_ACK 0xB8
#define TW_ST_DATA_NACK 0xC0
#define TW_ST_LAST_DATA 0xC8
// Slave Receiver
#define TW_SR_SLA_ACK 0x60
#define TW_SR_ARB_LOST_SLA_ACK 0x68
#define TW_SR_GCALL_ACK 0x70
#define TW_SR_ARB_LOST_GCALL_ACK 0x78
#define TW_SR_DATA_ACK 0x80
#define TW_SR_DATA_NACK 0x88
#define TW_SR_GCALL_DATA_ACK 0x90
#define TW_SR_GCALL_DATA_NACK 0x98
#define TW_SR_STOP 0xA0
// Misc
#define TW_NO_INFO 0xF8
#define TW_BUS_ERROR 0x00
// defines and constants
#define TWCR_CMD_MASK 0x0F
#define TWSR_STATUS_MASK 0xF8
// return values
#define I2C_OK 0x00
#define I2C_ERROR_NODEV 0x01
#define sbi(var, mask) ((var) |= (uint8_t)(1 << mask))
#define cbi(var, mask) ((var) &= (uint8_t)~(1 << mask))
#define WRITE_sda() DDRC = DDRC | 0b00010000 //SDA must be output when writing
#define READ_sda() DDRC = DDRC & 0b11101111 //SDA must be input when reading - don't forget the resistor on SDA!!
//MPR121 defines
#define MPR121_R 0xB5 // ADD pin is grounded
#define MPR121_W 0xB4 // So address is 0x5A
// functions
//I2C fxns for mpr121
unsigned int mpr121Read(uint8_t address);
void mpr121Write(unsigned char address, unsigned char data);
void mpr121QuickConfig(void);
//! Initialize I2C (TWI) interface
void i2cInit(void);
//! Set the I2C transaction bitrate (in KHz)
void i2cSetBitrate(unsigned short bitrateKHz);
// Low-level I2C transaction commands
//! Send an I2C start condition in Master mode
void i2cSendStart(void);
//! Send an I2C stop condition in Master mode
void i2cSendStop(void);
//! Wait for current I2C operation to complete
void i2cWaitForComplete(void);
//! Send an (address|R/W) combination or a data byte over I2C
void i2cSendByte(unsigned char data);
//! Receive a data byte over I2C
// ackFlag = TRUE if recevied data should be ACK'ed
// ackFlag = FALSE if recevied data should be NACK'ed
void i2cReceiveByte(unsigned char ackFlag);
//! Pick up the data that was received with i2cReceiveByte()
unsigned char i2cGetReceivedByte(void);
//! Get current I2c bus status from TWSR
unsigned char i2cGetStatus(void);
void delay_ms(uint16_t x);
// high-level I2C transaction commands
//! send I2C data to a device on the bus (non-interrupt based)
unsigned char i2cMasterSendNI(unsigned char deviceAddr, unsigned char length, unsigned char* data);
//! receive I2C data from a device on the bus (non-interrupt based)
unsigned char i2cMasterReceiveNI(unsigned char deviceAddr, unsigned char length, unsigned char *data);
/*********************
****I2C Functions****
*********************/
void i2cInit(void)
{
// set i2c bit rate to 40KHz
i2cSetBitrate(100);
// enable TWI (two-wire interface)
sbi(TWCR, TWEN); // Enable TWI
}
void i2cSetBitrate(unsigned short bitrateKHz)
{
unsigned char bitrate_div;
// set i2c bitrate
// SCL freq = F_CPU/(16+2*TWBR))
cbi(TWSR, TWPS0);
cbi(TWSR, TWPS1);
//calculate bitrate division
bitrate_div = ((F_CPU/4000l)/bitrateKHz);
if(bitrate_div >= 16)
bitrate_div = (bitrate_div-16)/2;
outb(TWBR, bitrate_div);
}
void i2cSendStart(void)
{
WRITE_sda();
// send start condition
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
}
void i2cSendStop(void)
{
// transmit stop condition
TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
}
void i2cWaitForComplete(void)
{
int i = 0; //time out variable
// wait for i2c interface to complete operation
while ((!(TWCR & (1<<TWINT))) && (i < 90))
i++;
}
void i2cSendByte(unsigned char data)
{
delay_ms(1);
//printf("sending 0x%x\n", data);
WRITE_sda();
// save data to the TWDR
TWDR = data;
// begin send
TWCR = (1<<TWINT)|(1<<TWEN);
}
void i2cReceiveByte(unsigned char ackFlag)
{
// begin receive over i2c
if( ackFlag )
{
// ackFlag = TRUE: ACK the recevied data
outb(TWCR, (inb(TWCR)&TWCR_CMD_MASK)|BV(TWINT)|BV(TWEA));
}
else
{
// ackFlag = FALSE: NACK the recevied data
outb(TWCR, (inb(TWCR)&TWCR_CMD_MASK)|BV(TWINT));
}
}
unsigned char i2cGetReceivedByte(void)
{
// retieve received data byte from i2c TWDR
return( inb(TWDR) );
}
unsigned char i2cGetStatus(void)
{
// retieve current i2c status from i2c TWSR
return( inb(TWSR) );
}
void delay_ms(uint16_t x)
{
uint8_t y, z;
for ( ; x > 0 ; x--){
for ( y = 0 ; y < 90 ; y++){
for ( z = 0 ; z < 6 ; z++){
asm volatile ("nop");
}
}
}
}
/////////////////////////////////////////////////////////
unsigned int mpr121Read(uint8_t address)
{
unsigned int 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();
}
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, 0x0C); // Enables all 12 Electrodes
//mpr121Write(ELE_CFG, 0x06); // Enable first 6 electrodes
// Section F
// Enable Auto Config and auto Reconfig
/*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
}
Note que este arquivo está bem denso, já que o desenvolvedor não se preocupou com a separação de código e juntou toda a implementação em um único arquivo.
Como dito, neste arquivo estão as funções de configuração, escrita e leitura do MPR121 que serão utilizadas no código do Arduino.
Exemplo
Por fim vamos analisar o código exemplo para teste no Arduino. Com toda a abstração realizada nos arquivos apresentados a codificação no Arduino fica relativamente simples. Neste exemplo vamos atentar para a utilização do recurso de interrupção para leitura das teclas. Como apresentado anteriormente o MPR121 possui um pino de interrupção que “avisa” quando tem tecla para ser lida. Vejamos o código com os comentários em português:
//inclusão de arquivos externos para abstração de interface com MPR121
#include "mpr121.h"
#include "i2c.h"
// Quantidade máxima de digitos utilizados
#define DIGITS 11
// endereçamento de tecla com o eletrodo conectado ao MPR121
#define ONE 8
#define TWO 5
#define THREE 2
#define FOUR 7
#define FIVE 4
#define SIX 1
#define SEVEN 6
#define EIGHT 3
#define NINE 0
//pinos extras (não utilizado neste código)
#define ELE9 9
#define ELE10 10
#define ELE11 11
//pino de interrupção
int irqpin = 2; // D2 no esquemático
void setup()
{
//configura pino de interrupçãop como entrada
//e habilita pull-up(saida IRQ -> coletor aberto)
pinMode(irqpin, INPUT);
digitalWrite(irqpin, HIGH);
//configura comunicação serial para exibição dos valores de teclas
Serial.begin(9600);
//coloca ADC4 como saída (PC4, SDA)
DDRC |= 0b00010011;
// liga Pull-ups nos pinos da I2C
PORTC = 0b00110000;
// inicica comunicação I2C utilizando biblioteca apresentada
i2cInit();
delay(100);
// inicia mpr121
mpr121QuickConfig();
// Cria uma interrupçãp para ocorrer quando um botão for acionado
//o pino IRQ vai para nivel zero e a função getNumber é chamada.
attachInterrupt(0,getNumber,LOW);
// escreve 'Ready...' para indicar que iniciou o sistema
Serial.println("Ready...");
}
void loop()
{
//a leitura das teclas e feita na interrupção
}
void getNumber()
{
int i = 0;
int touchNumber = 0;
uint16_t touchstatus;
char digits[DIGITS];
touchstatus = mpr121Read(0x01) << 8; //le ELE8 - ELE11, ELEPROX Touch Status e armazena na parte alta da variável
touchstatus |= mpr121Read(0x00); // le eletrodos de 0 a 7
for (int j=0; j<12; j++) // VERIFICA QUANTO ELETRODOS FORAM PRESSIONADOS
{
if ((touchstatus & (1<<j)))
touchNumber++;
}
if (touchNumber == 1) //se foi pressionado 1
{
//verifica qual foi
if (touchstatus & (1<<SEVEN))
digits[i] = '7';
else if (touchstatus & (1<<FOUR))
digits[i] = '4';
else if (touchstatus & (1<<ONE))
digits[i] = '1';
else if (touchstatus & (1<<EIGHT))
digits[i] = '8';
else if (touchstatus & (1<<FIVE))
digits[i] = '5';
else if (touchstatus & (1<<TWO))
digits[i] = '2';
else if (touchstatus & (1<<NINE))
digits[i] = '9';
else if (touchstatus & (1<<SIX))
digits[i] = '6';
else if (touchstatus & (1<<THREE))
digits[i] = '3';
Serial.print(digits[i]); //imprime valor da tecla
i++;
}
//se mais de uma tecla for pressionada não faz nada
else if (touchNumber == 0)
;
else
;
}
O tratamento de leitura de teclas é feito dentro da função getNumber que é chamada quando o pino IRQ vai para zero, gerando uma interrupção externa configurada no pino 2 do Arduino. Para verificação de qual tecla (eletrodo) foi acionado, são lidos os registradores de status (0x00 e 0x01). No código apresentado apenas uma tecla é tratada.
A figura 3 exibe a saída no terminal serial para o tratamento do evento de tecla:

Figura 3 – Teclas pressionadas exibidas no terminal serial
Conclusão
O Touchpad shield se apresentou uma placa bem interessante para interface touch capacitiva utilizando a plataforma Arduino. Utilizando os recursos do MPR121 fica bem fácil esse tipo de aplicação. Com o uso da comunicação I2C e utilizando o recurso de interrupção, a interface com o microcontrolador é feita com poucos pinos e não há a necessidade de ficar varrendo as teclas já que o MPR121 se encarrega de “avisar” quando há tecla para leitura. Vale a pena acessar a página sobre o MPR121 no site da Freescale [5] e estudar os Application Notes para obter mais informações.





Não consigo compilar este codigo exemplo, esta dando erro quando inicia acomunicação I2C utilizando biblioteca apresentada. Poderia tirar minha duvida?
Olá Túlio, você incluiu todas as bibliotecas? Qual o erro apresentado?