- 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.
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 4O 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 12Caso 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; iAs 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.{ // 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; }
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!
Onde ta o codigo?
ResponderExcluir