segunda-feira, 24 de janeiro de 2011

SIMONDUINO - game touchscreen com Arduino Brasileirino




Neste post demonstrarei como utilizar uma tela touchscreen resistiva implementando o jogo SIMON (Genius da Estrela) no Arduino.

Primeiro vamos ver como é o funcionamento da tela touchscreen resistiva.

Basicamente essas telas são compostas de dois filmes resistivos "resistive Coating". Entre eles existem espaçadores "spacers" que evitam falsos toques. Existe também uma base de vidro "Bottom Glass" que dá resistência mecânica e mantém firme o resistive Coating da parte inferior.



Esse tipo de tela possui 4 pontos de conexão, sendo dois deles associados aos extremos do resistive Coating (on top film) e os outros dois associados aos extremos do resistive Coating (on bottom glass).



Ao pressionarmos um ponto da tela, o Resistive Coating superior entra em contato com o inferior criando uma resistência entre os pontos de conexão do resistive Coating superior e do resistive Coating inferior.

Na prática o que temos é que a tela touchscreen resistiva se comporta como dois potênciometros, um associado ao eixo X e outro ao eixo Y.



Obtendo as coordenadas do ponto pressionado




Para obtermos a cordenada X, aplicamos (GND) em um dos terminais associados ao barramento lateral da tela e 5 volts no terminal associado ao outro barramento lateral da tela, então efetuamos uma leitura analógica no terminal associado ao barramento superior ou inferior da tela.

A figura ao lado mostra como efetuar a leitura da cordenada X:














Para obtermos a cordenada Y, aplicamos (GND) no terminal associado ao barramento inferior da tela e 5 volts no terminal associado ao barramento superior da tela, então efetuamos uma leitura analógica no terminal associado a qualquer um dos barramentos laterais da tela.

A figura ao lado mostra como efetuar a leitura da cordenada Y:











Com relação a calibragem da tela, foi necessário apenas ajustar no código o posicionamento na tela de acordo com os valores analógicos máximos e mínimos obtidos das cordenadas X e Y.

As linhas principais do código para obter o ponto pressionado na tela ficaram da seguinte forma:

int x=analogRead(yLow);
x=map(x,90,850,0,127);

onde valores mínimo e máximo de X (90 e 850) foram mapeados para os pixels do display (0 ~ 127)


int y=analogRead(xLow);
y=map(y,80,820,0,63);

onde valores mínimo e máximo de Y (80 e 820) foram mapeados para os pixels do display (0 ~ 63)



Bem agora que já sabemos o funcionamento básico do touchscreen resistivo, vamos ao projeto.

Para implementar o SIMON touchscreen com Arduino, utilizei uma placa Brasileirino da Globalcode, um display LCD gráfico ITM-12864 (128x64 pixels), uma tela touchscreen resistiva de Nintendo DS e um Nintendo DS Touch Screen Connector Breakout (este para conexão do flat cable da tela touch).
Obs: Recomendo a utilização do Nintendo DS Touch Screen Connector Breakout, pois a conexão da tela sem ele se torna muito delicada.



A tela foi dividida em 4 quadrantes, onde cada um representa um botão do jogo. Vide foto abaixo:





O display ITM-12864 é baseado no controlador KS0108 e portanto é compatível com a "KS0108 Graphics LCD library"

Essa biblioteca facilita bastante a interface com o display LCD, visto que podemos carregar imagens bimap no display com apenas um comando.
Mais detalhes sobre a utilização dessa biblioteca podem ser vistos no meu post sobre bússola digital ou na página oficial da biblioteca.
Um detalhe importante na utilização dessa biblioteca é configurar corretamente o arquivo ks0108_Arduino.h.
Nesse arquivo indicamos onde está conectado cada terminal do display no Arduino.

Para este projeto o trecho inicial do arquivo ficou da seguinte forma:

/*

/*********************************************************/
/* Configuration for assigning LCD bits to Arduino Pins */
/*********************************************************/
/* Arduino pins used for Commands
* default assignment uses the first five analog pins
*/

#define CSEL1 2 // CS1 Bit // swap pin assignments with CSEL2 if left/right image is reversed
#define CSEL2 3 // CS2 Bit

#define R_W 13 // R/W Bit
#define D_I 12 // D/I Bit
#define EN 19 // EN Bit
//#define RES 19 // Reset Bit // uncomment this to contol LCD reset on this pin

/* option: uncomment the next line if all command pins are on the same port for slight speed & code size improvement */
#define LCD_CMD_PORT PORTB // Command Output Register for pins 14-19

/* Arduino pins used for LCD Data
* un-comment ONE of the following pin options that corresponds to the wiring of data bits 0-3
*/
#define dataPins8to11 // bits 0-3 assigned to arduino pins 8-11, bits 4-7 assigned to arduino pins 4-7
//#define dataPins14to17 //bits 0-3 assigned to arduino pins 14-17, bits 4-7 assigned to arduino pins 4-7. (note command pins must be changed)
//#define dataPins0to3 // bits 0-3 assigned to arduino pins 0-3 , bits 4-7 assigned to arduino pins 4-7, this is marginally the fastest option but its only available on runtime board without hardware rs232.

/* NOTE: all above options assume LCD data bits 4-7 are connected to arduino pins 4-7 */

/*******************************************************/
/* end of Arduino configuration */
/*******************************************************/



A maioria dos displays LCD necessitam de uma tensão negativa para acionamento do display, muitos deles possuem um circuito interno para gerar essa tensão. Como o ITM-12864 não possui esse circuito e afim de evitar a utilização de duas fontes de alimentação externas, montei no protoboard localizado em baixo do display, um conversor de +12volts para -9 volts utilizando um CI 555.

Nas figuras abaixo temos o diagrama elétrico do conversor e a montagem dele no protoboard. Na protoboard foram adicionados também o speaker, o potenciômetro de controle de contraste do display e os resistores que ligam os terminais da tela touch ao terra.




Nas imagens abaixo temos o detalhe da conexão do flat cable da tela touch utilizando a Nintendo DS touch Screen Connector Breakout. Também é possível ver a colagem da tela touch no display onde utilizei tiras de fita dupla face.





Além de ser conectada nas portas analógicas 0 a 3, a tela touch necessita resistores para o terra (GND) conforme diagrama abaixo:
Esses resistores foram montados na protoboard em baixo do display e eles tornam o processo de calibração da tela desnecessário.




O programa deste projeto foi baseado no código apresentado pelo Spock em seu post sobre "DOJO de programação com Program-Me", sendo que aproveitei toda a parte funcional do código original e fiz apenas as adaptações necessárias para substituir os botões e Leds pela touchscreen e pelo display, além de implementar sons diferentes para cada botão.

Segue abaixo o código final do projeto.



#include <ks0108.h> // inclui biblioteca KS0108
#include "bloco1_ON.h" // bitmap botao 1 ativado
#include "bloco2_ON.h" // bitmap botao 2 ativado
#include "bloco3_ON.h" // bitmap botao 3 ativado
#include "bloco4_ON.h" // bitmap botao 4 ativado
#include "blocos_OFF.h" // bitmap todos os botoes desativados
#include "principal.h" // bitmap logo SIMONDUINO
#include "logo_Globalcode.h" // bitmap logo Globalcode
#include "level1.h" // bitmap nivel 1
#include "level2.h" // bitmap nivel 2
#include "level3.h" // bitmap nivel 3
#include "level4.h" // bitmap nivel 4
#include "gameOver.h" // bitmap game over
#include "parabens.h" // parabens voce venceu

#define xLow 15 //define porta associada ao xLow (X2)
#define xHigh 17 //define porta associada ao xHigh (X1)
#define yLow 16 //define porta associada ao yLow (Y2)
#define yHigh 14 //define porta associada ao yHigh (Y1)

int bip = 18;
char state = 'S'; // S = Start, P = Play, B = Read button, ...
int sequencia[] = {0,0,0,0,0,0,0,0,0};
int currentRound = 0; // armazena round atual
int level = 1; //define nivel 1 como inicial
int tamanhoSequencia = 8; //define o tamnanho de cada sequencia
boolean fimJogo = true; // indica fim de jogo

void setup() {
pinMode(bip, OUTPUT); // configura porta speaker como saida
Serial.begin(9600);
GLCD.Init(NON_INVERTED); // inicializa a biblioteca GLCD
GLCD.ClearScreen(); // limpa tela do display
GLCD.DrawBitmap(principal,0,0,BLACK); //desenha na tela o bitmap do SIMONDUINO
delay(3000);
GLCD.DrawBitmap(logo_Globalcode,0,0,BLACK); //desenha na tela o logo da Globalcode
delay(3000);
GLCD.DrawBitmap(level1,0,0,BLACK); //desenha na tela o bitmap do nivel 1
delay(2000);
}

void loop() {
if (state == 'S') {
inicio(); //inicializa jogo
fimJogo = false;
currentRound = 0;
state = 'P';
}
if (state == 'P') {
acendeluzesdarodada(); //reproduz seguencia da rodada no display e no speaker
state = 'B';
}
if (state == 'B') {
for (int i = 0; i <= currentRound; i++){
int botao = leBotao(); //chama funcao que le os botoes (touchscreen)
if (sequencia[i] != botao || botao==-1) {
state = 'S';
fimJogo = true;
GLCD.DrawBitmap(gameOver,0,0,BLACK); //desenha na tela o bitmap de gameover
beep();
delay(2000);
GLCD.DrawBitmap(level1,0,0,BLACK); //draw the bitmap at the given x,y position
level = 1;
break;
}
}
if (!fimJogo) {
currentRound++;
if (currentRound == tamanhoSequencia) { //verifica se round foi concluido
level++ ;
switch (level) { //caso concluido desenha bitmap do proximo nivel
case 2:
GLCD.DrawBitmap(level2,0,0,BLACK); //draw the bitmap at the given x,y position
break;
case 3:
GLCD.DrawBitmap(level3,0,0,BLACK); //draw the bitmap at the given x,y position
break;
case 4:
GLCD.DrawBitmap(level4,0,0,BLACK); //draw the bitmap at the given x,y position
break;
case 5:
GLCD.DrawBitmap(parabens,0,0,BLACK); //draw the bitmap at the given x,y position
delay(5000);
GLCD.DrawBitmap(level1,0,0,BLACK); //desenha na tela o bitmap do nivel 1
delay(2000);
state = 'S';
level = 1 ;
break;
}
state = 'S';}else{
state = 'P';
}
}
}
delay(2000);
}

void acendeluzesdarodada() {
for (int i = 0; i <= currentRound;i++){
alteraBotoes(sequencia[i]);
ton(sequencia[i]);
delay(500 - (level*50));
noTone(bip);
alteraBotoes(-1);
delay(250 - (level*25));
}
}

int leBotao() {
int timeout = 0;
int botao = -1;
while (botao == -1 && timeout <= (35 - (level*5))) {
botao = leScreen(); //le touchscreen
if(botao != -1) {
break;
}
delay(100);
timeout++;
}
alteraBotoes (botao);
while (leScreen( ) != -1){
ton(botao);
}
noTone(bip);
GLCD.DrawBitmap(blocos_OFF,0,0, BLACK); //desenha todos os botoes desativados
return botao;
}

void beep() { // emite som de fim de jogo
tone(bip,100);
delay(2000);
noTone(bip);
}

void ton(int freq) { //emite sond de cada botao
tone(bip,freq*1000);
}

void inicio() {
GLCD.ClearScreen();
GLCD.DrawBitmap(blocos_OFF,0,0,BLACK); //desenha todos os botoes desativados
randomSeed(analogRead(4)); // inicializa numeros randomicos
for (int i = 0; i < tamanhoSequencia; i++) {
sequencia[i] = (int) random(1, 5); // gera sequencia aleatoria do round
}
}

int leScreen() {
pinMode(xLow,OUTPUT); // prepara sinais da tela touch para leitura da coordenada X
pinMode(xHigh,OUTPUT);
digitalWrite(xLow,LOW);
digitalWrite(xHigh,HIGH);
digitalWrite(yLow,LOW);
digitalWrite(yHigh,LOW);
pinMode(yLow,INPUT);
pinMode(yHigh,INPUT);
delay(10);
//xLow has analog port -14 !!
int x=analogRead(yLow -14); // le coordenada X
x=map(x,90,850,0,127); // ajusta coordenada X de acordo com resolucao do display

pinMode(yLow,OUTPUT); // prepara sinais da tela touch para leitura da coordenada Y
pinMode(yHigh,OUTPUT);
digitalWrite(yLow,LOW);
digitalWrite(yHigh,HIGH);
digitalWrite(xLow,LOW);
digitalWrite(xHigh,LOW);
pinMode(xLow,INPUT);
pinMode(xHigh,INPUT);
delay(10);
//xLow has analog port -14 !!
int y=analogRead(xLow - 14); // le coordenada Y
y=map(y,80,820,0,63); // ajusta coordenada Y de acordo com resolucao do display

// Serial.print(x,DEC); //imprime cordenadas no serial monitor (descomentar para debug)
// Serial.print(",");
// Serial.println(y,DEC);

delay(200);

if ((x >= 0 && x < 64) && y > 32) { // verifica em qual quadrante da tela foi efetuado o toque de acordo com as coordenadas X e Y
return 1;
}
if ((x >= 64 && x < 128) && y > 32) {
return 2;
}

if ((x >= 0 && x < 64) && (y >= 0 && y < 32)) {
return 3;
}
if ((x >= 64 && x < 128) && (y >= 0 && y < 32)) {
return 4;
}
return -1;
}

void alteraBotoes (int botao) {
switch (botao) {
case 1:
GLCD.DrawBitmap(bloco1_ON, 0, 0, BLACK); //desenha botao 1 ativado
break;
case 2:
GLCD.DrawBitmap(bloco2_ON, 64, 0, BLACK); //desenha botao 2 ativado
break;
case 3:
GLCD.DrawBitmap(bloco3_ON, 0, 32, BLACK); //desenha botao 3 ativado
break;
case 4:
GLCD.DrawBitmap(bloco4_ON, 64, 32, BLACK); //desenha botao 4 ativado
break;
default:
// se nenhum botao estiver pressionado, desenha botoes desativados
GLCD.DrawBitmap(blocos_OFF, 0, 0, BLACK); //desenha botoes desativados
}
}



Abaixo temos um video demonstrando o funcionamento do SIMONDUINO Touchscreen, sendo que apenas reduzi o tamanho das combinações de cada nível de 8 para 3 afim de encurtar o vídeo. A cada nível que se avança as sequências são apresentadas com maior velocidade e o jogador tem menos tempo para reproduzi-las.



Custo dos componentes principais:

Display LCD gráfico ITM-12864 R$ 50,00
Tela touchscreen Nintendo DS US$ 9,95
Breakout board para conexão da tela US$ 3,95


Bem é isso pessoal, espero que gostem do projeto. Me diverti bastante fazendo ele.

[ ]s

José Luiz Sanchez Lorenzo
Twitter: @jllorenzo

Um comentário:

  1. Olá amigo, onde posso encontrar esses componentes? Sabe se a touch é facil de acoplar ao arduino mega? Grato

    ResponderExcluir