Arduino – Teclado touch capacitivo

teclado touch

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. 

1 - 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:

2 - esquema

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: 

3 - terminal serial

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.

Para aprender mais

Arduino – Primeiros Passos

Arduino UNO

Arduino – Saídas PWM

Referências

LOJA FILIPEFLOP

Datasheet MPR121

Arduino Due

Biblioteca Wire

MPR121 – site Freescale

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
8 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Túlio Veloso
Túlio Veloso
08/11/2015 13:45

Não consigo compilar este codigo exemplo, esta dando erro quando inicia acomunicação I2C utilizando biblioteca apresentada. Poderia tirar minha duvida?

Fabio_Souza_Embarcados
Fabio_Souza_Embarcados
Reply to  Túlio Veloso
08/11/2015 20:44

Olá Túlio, você incluiu todas as bibliotecas? Qual o erro apresentado?

Home » Hardware » Placas de desenvolvimento » Arduino – Teclado touch capacitivo

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: