Neste post, irei falar sobre como iniciar o desenvolvimento de objetos interativos com Arduino. Agora, vamos ver como adicionar dispositivos de entrada e saída, sejam eles analógicos ou digitais. A plataforma Arduino possui
14 entradas e saídas digitais, das quais
6 podem ser usadas para
saída analógica emulada via PWM, e
6 entradas (entradas apenas) analógicas. As funções para entrada digital e para entrada e saída analógica são:
- digitalRead(num_da_porta) — função que lê e retorna o valor digital (0 ou 1) da porta
- analogWrite(num_da_porta, valor) — função que escreve um valor analógico na porta. Este valor pode variar de 0 a 255 e representa uma tensão média de saída entre 0V (0) e 5V (255). Para maiores informações, ver a explicação da modulação PWM no site do Arduino.
- analogRead(num_da_porta) — função que lê e retorna um valor de tensão analógico presente na porta especificada, convertido para um número entre 0 e 1023, sendo o valor 0 correspondendo a 0V e 1023 correspondendo a 5V.
Agora iremos usar as entradas e saídas do arduino para fazer um jogo no estilo do
Genius (
Simon). O jogo consiste em quatro botões grandes que acendem em sequência e o jogador precisa memorizar esta sequência e repetí-la. A cada rodada a sequência aumenta em 1 elemento. O jogo termina quando o jogador erra a sequência apresentada ou, no nosso caso, quando a sequência chega a 100 elementos.
Montando os circuitos
Para implementar este jogo, utilizaremos 4
sensores de pressão para representar os botões, quatro
LEDs coloridos para indicar qual botão da sequência deve-se pressionar, um LED para indicar que o jogador deve começar a pressionar os botões, e um auto-falante para emitir o som de cada botão.
Primeiro vamos conectar os LEDs às saídas digitais do Arduino. Escolhemos conectá-los às saídas digitais PWM, pois desta forma poderemos incrementar o nosso jogo posteriormente, adicionando efeitos nos LEDs, com escrita analógica nestas portas. Assim, os LEDs vermelho, verde, azul e amarelo serão conectados às portas 5, 6, 9 e 10, respectivamente. O LED que indica a vez do jogador repetir a sequência será conectado à porta 2. Adicionamos as seguintes diretivas de pré-processamento para facilitar a leitura do código:
// LEDs em portas de saída PWM digital
#define LED_RED 5
#define LED_GREEN 6
#define LED_BLUE 9
#define LED_YELLOW 10O circuito para ligar os LEDs às portas é composto do LED propriamente dito e um resistor de 220Ω em série para limitar a corrente no LED, como pode ser visto na figura abaixo:
Os
sensores de pressão que utilizamos são resistores que variam sua resistência de acordo com a força aplicada sobre ele. Sem nenhuma força exercida sobre ele, sua resistência é acima de 1MΩ, e com 10Kg sua resistência é próxima de 0Ω. Assim, ligaremos o sensor de pressão com um resistor de 10kΩ para formar um
divisor de tensão, que vai variar a tensão de entrada na porta analógica de acordo com a força aplicada ao sensor de pressão. Ligaremos cada sensor de pressão (que chamaremos de PAD no código) a uma das portas analógicas de acordo com a definição abaixo:
// PADs nas portas de entrada analógica
#define PAD_RED 0
#define PAD_GREEN 1
#define PAD_BLUE 2
#define PAD_YELLOW 3
#define MAX_PADS 4
O circuito para os sensores de pressão está mostrado na figura abaixo:
Para emitir os sons do jogo, utilizaremos um pequeno auto-falante, destes encontrados em
headphones. Este auto-falante será conectado à porta 12 do Arduino. Abaixo segue a diretiva de pré-processamento com a definição desta porta:
// Speaker on digital out port 12
#define SPEAKER 12
Caso seja necessário, podemos adicionar um resistor de baixo valor (100Ω ou menos) para reduzir o volume do auto-falante. O Genius utiliza notas musicais para indicar o acionamento de cada botão. Sabemos que o som de uma nota musical é a variação do deslocamento do ar em uma única frequência. Assim, se ligarmos e desligarmos a porta em que o auto-falante está conectado, na frequência desejada, o auto-falante emitira o som da nota correspondente. As notas que utilizaremos para cada um dos botões são: Lá para o vermelho, Lá, uma oitava acima (o dobro da frequência) para o verde, Ré para o azul e Sol para o amarelo. Para ligar e desligar o auto-falante na frequência desejada, precisamos calcular o período, que é o inverso da frequência. O auto-falante deverá permanecer meio período ligado e meio período desligado, tantas vezes quanto for a duração da nota. Assim, temos as seguintes definições para o meio período de cada nota a ser tocada:
// Notas desempenhado por cada PAD
// O valor de cada constante representa o período de cada
// Note que será tocado quando um bloco é pressionado.
// A relação de cada almofada com uma nota é o seguinte:
#define NOTE_HPERIOD_RED 1136 // Uma nota - 440Hz
#define NOTE_HPERIOD_GREEN 568 // Uma nota e uma oitava acima do RED - 880Hz
#define NOTE_HPERIOD_BLUE 1700 // D-note - 294Hz
#define NOTE_HPERIOD_YELLOW 1275 // G-note - 392Hz
#define NOTE_HPERIOD_ERROR 5000 // 100Hz
Abaixo segue a função para tocar uma nota, bem como o circuito para ligar o auto-falante à porta.
// Plays a note according to its half periodvoid playNote(long hperiod, long duration)
{
long elapsed_time = 0;
// Jogou menos tempo do que 'duração'
while (elapsed_time < duration)
{
// turno para a metade do período
digitalWrite(SPEAKER, HIGH);
delayMicroseconds(hperiod);
// turno fora por metade do período
digitalWrite(SPEAKER, LOW);
delayMicroseconds(hperiod);
// Adiciona um período de tempo decorrido
elapsed_time += (hperiod*2);
}
}
Ao juntar todos estes circuitos e conectá-los ao Arduino, temos a nossa configuração de hardware pronta. A figura abaixo mostra como ficou este circuito montado em uma protoboard.
Funções do Jogo
A logica do jogo é bastante simples. Primeiro temos que sortear um elemento (pad) e adicioná-lo à sequência de pads. Depois executamos toda a sequência para que o jogador a memorize, e depois esperamos pelo pressionamento dos pads e comparamos os pads pressionado com a sequência salva. Caso o jogador acerte a sequência, começamos este ciclo novamente. Se não, sinalizamos o erro e o jogo recomeça.
Para o nosso jogo, definimos um conjunto de constantes, que representam o tempo que cada LED permanece aceso, o intervalo entre o acendimento dos LEDs, a sensibilidade dos PADs, etc. Além disto, definimos duas tabelas para converter um valor de PAD para a porta do respectivo LED e o valor do PAD em um periodo da nota a ser tocada. Também temos variáveis para representar o fim do jogo e para armazenar a sequência de pads a serem pressionados. Abaixo seguem estas definições e as funções setup() e loop() do nosso jogo:
////////////////////////////////////////////////////////////
// Definição de constantes
////////////////////////////////////////////////////////////
// Notas desempenhado por cada PAD
// O valor de cada constante representa o período de cada
// Note que será tocado quando um bloco é pressionado.
// A relação de cada almofada com uma nota é o seguinte:
#define NOTE_HPERIOD_RED 1136 // Uma nota - 440Hz
#define NOTE_HPERIOD_GREEN 568 // Uma nota e uma oitava acima do RED - 880Hz
#define NOTE_HPERIOD_BLUE 1700 // D-note - 294Hz
#define NOTE_HPERIOD_YELLOW 1275 // G-note - 392Hz
#define NOTE_HPERIOD_ERROR 5000 // 100Hz
// Número máximo de toques na PAD, para terminar o jogo
#define MAX_NUM_TOUCHES 100
// Duração máxima de cada toque em milisegundos
#define MAX_TOUCH_DURATION 500
// Intervalo máximo de cada toque em milisegundos
#define MAX_TOUCH_INTERVAL 250
// Sensibilidade do sensor de toque
#define TOUCH_SENSIBILITY 1000
// Porta analógica para a leitura do inicializador randomSeed
#define RANDOM_SEED_PORT 5
////////////////////////////////////////////////////////////
// Definição de variáveis globais
////////////////////////////////////////////////////////////
// Tabela de pesquisa para converter PAD para um LED
unsigned char PAD_TO_LED[MAX_PADS];
// Tabela de pesquisa para converter PAD para um período NOTA
unsigned long PAD_TO_NOTE_HPERIOD[MAX_PADS];
// Jogo mais variável
boolean gameOver;
// Seqüência de pastilhas de ser tocado
unsigned char padSequence[MAX_NUM_TOUCHES];
// O número atual de usuários toca deve executar
int currentNumTouches;
// Duração de cada toque (irá diminuir ao longo do tempo)
long currentTouchDuration;
// Intervalo de tempo entre os toques (vai diminuir ao longo do tempo)
int currentTouchInterval;
////////////////////////////////////////////////////////////
// Declarações de função
////////////////////////////////////////////////////////////
// Função para repor o estado do jogo
void restartGame();
// Função para preencher nex aleatório para ser pressionado e inseri-lo em seqüência pad
void nextRound();
// Função que reproduz a seqüência de teclado atual
void playSequence();
// Função que verifica a entrada do jogador contra a seqüência salvo
void verifySequence();
// Função para aguardar uma entrada de teclado
int readPads();
// Função para transformar o bloco em correspondig levou,
// E jogar bloco de notas correspondentes
void turnPadOn(int pad);
// Função que reproduz o som da vitória
void playVictorySound();
// Função que reproduz o som de erro
void playErrorSound();
// Função para tocar uma nota com base no seu período de meia
void playNote(long period, long duration);
// Coloque todos os leds no teclado
void turnAllLedsOn();
// Coloque todos os leds apagados pad
void turnAllLedsOff();
////////////////////////////////////////////////////////////
// Configuração do Arduino () função
////////////////////////////////////////////////////////////
void setup()
{
// Definindo portas LED como SAÍDAS
pinMode(LED_PLAYING, OUTPUT);
pinMode(LED_BLUE, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
pinMode(LED_RED, OUTPUT);
// Definindo como porta SPEAKER OUTPUT
pinMode(SPEAKER, OUTPUT);
// Não há necessidade de usar pinMode para definir as entradas PADs
// Porque eles estão ligados as entradas analógicas
// (Na verdade você não pode fazer isso porque pinMode
// Afecta apenas as portas digitais) RS Rs se liga mané!
// Inicializando a PAD para LED tabela de pesquisa
PAD_TO_LED[PAD_RED] = LED_RED;
PAD_TO_LED[PAD_GREEN] = LED_GREEN;
PAD_TO_LED[PAD_BLUE] = LED_BLUE;
PAD_TO_LED[PAD_YELLOW] = LED_YELLOW;
// Inicializando a almofada NOTA tabela de pesquisa
PAD_TO_NOTE_HPERIOD[PAD_RED] = NOTE_HPERIOD_RED;
PAD_TO_NOTE_HPERIOD[PAD_GREEN] = NOTE_HPERIOD_GREEN;
PAD_TO_NOTE_HPERIOD[PAD_BLUE] = NOTE_HPERIOD_BLUE;
PAD_TO_NOTE_HPERIOD[PAD_YELLOW] = NOTE_HPERIOD_YELLOW;
restartGame();
}
////////////////////////////////////////////////////////////
// Loop Arduino () função
////////////////////////////////////////////////////////////
void loop()
{
// Gera almofada próxima aleatória e acrescenta que a seqüência pad
nextRound();
// Executar a seqüência de teclado atual
playSequence();
delay(currentTouchInterval);
// Aguarda pela entrada do jogador e verificar se a entrada do jogador
// Corresponde a seqüência de teclado
verifySequence();
// Se o jogo tiver terminado, reiniciar o jogo
if(gameOver)
{
restartGame();
}
}
Para a geração de números aleatórios, utilizamos as seguintes funções do Arduino: randomSeed(), que inicializa o gerador de números aleatórios; e random() que retorna um numero aleatório dentro de um intervalo. Para inicializar a função randomSeed() usamos o valor lido de uma porta analógica não utilizada (conectada), que vai flutuar (variar) de acordo com as ondas eletromagnéticas do ambiente. Esta inicialização e a inicialização as variáveis do jogo é feita na função restartGame(), apresentada abaixo:
void restartGame()
{
// Reset global variables
gameOver = false;
currentNumTouches = 0;
currentTouchDuration = MAX_TOUCH_DURATION;
currentTouchInterval = MAX_TOUCH_INTERVAL;
// turn off all leds
turnAllLedsOff();
// Restart random number generation
randomSeed(analogRead(RANDOM_SEED_PORT));
}
Abaixo seguem as funções para calcular o próximo pad a ser pressionado, executar a sequência de pads para que o usuário memorize e verificar se os pads pressionados pelo usuário estão na sequência correta.
////////////////////////////////////////////////////////////
// Game functions
////////////////////////////////////////////////////////////
void nextRound()
{
// Increments the number of pad touches for this round
currentNumTouches++;
// If pad touches reaches the maximum, the player has finished the game
// So, play the victory sounds
if (currentNumTouches >= MAX_NUM_TOUCHES)
{
playVictorySound();
}
// Else, adds a new random pad to the sequence
else
{
padSequence[currentNumTouches - 1] = random(0, MAX_PADS);
}
}
void playSequence()
{
// go through the stored PAD sequence turn on corresponding LED and
// playing corresponding note
for (int i=0; i < currentNumTouches; i++)
{
turnPadOn(padSequence[i]);
}
}
void verifySequence()
{
int pad = 0;
// Turn the playing LED on
// This notifies the player that it is time to press the pads
digitalWrite(LED_PLAYING, HIGH);
// Run though pad sequence waiting for player input and
// verifying the input against the sequence
for(int i=0; i< currentNumTouches; i++)
{
// Waits for pad input
pad = readPads();
// Verify if the correct pad was pressed
if (pad == padSequence[i])
{
// If the pad is correct, turn the corresponding LED
// on and play its note
turnPadOn(pad);
}
else
{
// if pressed pad is a wrong pad, the player looses the game
// so, play the Error sound
playErrorSound();
gameOver = true;
break;
}
}
// Turn the playing LED off
digitalWrite(LED_PLAYING, LOW);
}
Quando o jogador pressiona um pad, sua resistência diminui, levando o valor de tensão na porta correspondente do arduino para baixo também. Assim, para verificar os pads pressionados, devemos ler cada uma das portas dos pads em sequência. Caso o valor lido seja menor que o nosso valor de sensibilidade (no nosso caso 1000, para uma sensibilidade bem alta), consideramos este pad pressionado. A função que faz esta leitura está descrita abaixo:
int readPads()
{
boolean padPressed = false;
int pad = 0;
do{
// Verifica a porta de cada bloco, verificando se
// Foi pressionado
for(int i=0; i
{
// Lê a porta de almofada
int pressure = analogRead(i);
// Se a pressão atravessa o threashold, bloco atual é pressionado
if(pressure < TOUCH_SENSIBILITY)
{
padPressed = true;
pad = i;
break;
}
}
}while (!padPressed);
return pad;
}
As outras funções do código são funções auxiliares bastante simples, por isso não serão discutidas aqui. Você pode fazer o download do código completo para execução no seu arduino neste link: CESAR_simon_game.zip.
Abaixo segue um vídeo do protótipo do jogo funcionando. Nos próximos posts da serie abordaremos outras funcionalidades do arduino mais avançadas, fiquem ligados!