Quando a gente começa a fazer coisas com Arduino, tudo é muito simples. É fácil abrir os códigos de exemplo, dentro do programa do Arduino, modificá-los para fazermos o que queremos, mas depois de pouco tempo, surge a vontade de fazer coisas complexas, ou que encontramos na internet. Temos então um novo desafio, entender o código criado por um estranho.
Enquanto os programas de exemplo do Arduino são de excelente qualidade, na internet podemos encontrar código de tudo quanto é jeito, inclusive alguns que nem sequer funcionam. E esta dificuldade, de entender programas criados por outros, não é apenas restrita à iniciantes, pois a capacidade de entender depende menos da nossa experiência, e mais da clareza do código escrito.
Por causa disso, decidi escrever este post. Quero ajudar vocês, fazedores que gostam de brincar com Arduino (mas que não são programadores), com duas coisas: 1. como ler e entender um programa pronto; e 2. como usar funções para simplificá-lo, de tal forma que qualquer pessoa possa entendê-lo no futuro. Então vamos começar, pegando o código do nosso próprio tutorial do jogo Genius, que publicamos na PandoraLab.
O código abaixo é baseado em um jogo de memória chamado Genius, porém modificado. Neste jogo modificado, temos 3 LEDs, 3 botões e um buzzer. Os LEDs irão piscar 6 vezes, em uma sequência aleatória, emitindo sons específicos para cada LED que pisca. Depois que essa sequência for tocada, o jogador deve copiá-la, apertando os botões que representam cada LED, na mesma ordem que eles piscaram originalmente.
Se você acertar a sequência, os LEDs piscarão e o buzzer vai fazer um barulho mostrando que você acertou, e uma nova sequência diferente irá tocar, reiniciando o jogo. Se você errar, você irá ouvir um barulho de erro, a jogada será interrompida e o jogo reiniciará com uma nova sequência.
Nós vamos agora primeiro ler o código, para depois podermos separar ele em blocos, entender cada bloco individualmente e depois simplificar ele. O primeiro passo é ler o código rapidamente, buscando entender a estrutura do código (não se preocupe em saber o que faz cada linha individualmente):
// ----------- Bloco defines ------------------ // #define LED_A 5 #define LED_B 6 #define LED_C 7 #define BUTTON_A 8 #define BUTTON_B 9 #define BUTTON_C 10 #define BUZZER 11 #define TOM_A 1200 #define TOM_B 1600 #define TOM_C 2000 #define TEMPO_PISCA 600 #define TEMPO_ENTRE 150 #define QT_SEQ 6 int sorteados[QT_SEQ]; // QT_SEQ é o tamanho da sequência dos leds // ----------- Declaração da função de cada elemento ---------------- // void setup() { Serial.begin(9600); pinMode(LED_A, OUTPUT); pinMode(LED_B, OUTPUT); pinMode(LED_C, OUTPUT); pinMode(BUTTON_A, INPUT_PULLUP); pinMode(BUTTON_B, INPUT_PULLUP); pinMode(BUTTON_C, INPUT_PULLUP); pinMode(BUZZER, OUTPUT); randomSeed(analogRead(0)); // sorteia uma nova sequencia toda vez que liga o Arduino } // ----------- Loop do sorteio, leitura de botão e comparação --------- // void loop() { // acende tudo digitalWrite(LED_A, HIGH); digitalWrite(LED_B, HIGH); digitalWrite(LED_C, HIGH); le_botao(); // Jogador aperta qlqer botão e já inicia o sorteio // apaga tudo digitalWrite(LED_A, LOW); digitalWrite(LED_B, LOW); digitalWrite(LED_C, LOW); delay(500); // ------ Bloco do sorteio da sequência -------- // for (int jogada = 0; jogada < QT_SEQ; jogada ++) { // QT_SEQ é o tamanho da sequência - quantas vezes os leds piscam sorteados[jogada] = random(3); pisca_led(sorteados[jogada]); delay(TEMPO_ENTRE); }// fim do sorteio // ------ Bloco que compara a sequência com os botoes apertados -------- // int vez; // definido fora do for para poder verificar numero de acertos no final for (vez = 0; vez < QT_SEQ; vez ++) { if (le_botao() == sorteados[vez]) { pisca_led(sorteados[vez]); } else { pisca_led(sorteados[vez]); break; // sai do for } } //fim do for // ------ Bloco que demonstra o resultado -------- // if (vez == QT_SEQ) { // acertou seq. inteira delay(500); for (int i = 0; i < 3; i++) { tone(BUZZER, 2000); delay(200); tone(BUZZER, 4000); delay(200); } noTone(BUZZER); } else { // errou algo delay(500); for (int i = 0; i < 3; i++) { tone(BUZZER, 200); delay(200); tone(BUZZER, 300); delay(200); } noTone(BUZZER); } } // fim do loop // ------- Função piscar Led ------- // void pisca_led(int posicao) { Serial.println(posicao); if (posicao == 0) { digitalWrite(LED_A, HIGH); tone (BUZZER, TOM_A); delay(TEMPO_PISCA); digitalWrite(LED_A, LOW); } else if (posicao == 1) { digitalWrite(LED_B, HIGH); tone (BUZZER, TOM_B); delay(TEMPO_PISCA); digitalWrite(LED_B, LOW); } else if (posicao == 2) { digitalWrite(LED_C, HIGH); tone (BUZZER, TOM_C); delay(TEMPO_PISCA); digitalWrite(LED_C, LOW); } noTone(BUZZER); } // fim do pisca_led // ----------- Função ler os botões -------- // int le_botao () { int result = -1; // -1 indica que nenhum botão foi apertado while (result == -1) { if (digitalRead(BUTTON_A) == LOW) { result = 0; //posição 0 } else if (digitalRead(BUTTON_B) == LOW) { result = 1; } else if (digitalRead(BUTTON_C) == LOW) { result = 2; } } // fim do while return result; // retorna o inteiro int le_botao }
Podemos normalmente dividir um programa de Arduino em quatro grandes partes, ou blocos. Esses blocos são o que formam a estrutura de um programa. O primeiro bloco fica logo no início do programa. Nessa parte é onde chamamos bibliotecas externas, fazemos definições (#define) e declaramos variáveis globais. O segundo é o bloco do setup(). O terceiro, é o do loop() e finalmente o quarto é o conjunto de declarações de outras funções criadas no programa. Vamos então, ler o primeiro bloco, a fim de entendê-lo. Substitui alguns comentários originais, e adicionei outros dado meu entendimento, baseando-me nos nomes das variáveis e funções:
Primeiro bloco:
// ----------- Bloco defines ------------------ // //Portas digitais usadas pelos LEDs #define LED_A 5 #define LED_B 6 #define LED_C 7 //Portas digitais usadas pelos Botões #define BUTTON_A 8 #define BUTTON_B 9 #define BUTTON_C 10 //Porta usada pelo buzzer #define BUZZER 11 //Tons usados quando um botão é pressionado ou quando um LED acende #define TOM_A 1200 #define TOM_B 1600 #define TOM_C 2000 //Não sabemos ao certo ainda para o que servem esses abaixo, mas sabemos //que tem algo a ver com Tempo. Iremos voltar neles mais adiante. #define TEMPO_PISCA 600 #define TEMPO_ENTRE 150 //Tamanho da sequência de LEDs #define QT_SEQ 6 //Variável onde vamos armazenar a sequência int sorteados[QT_SEQ];
Felizmente o código está relativamente bem comentado, e as variáveis/definições tem bons nomes. Nosso grande objetivo aqui é abstrair o código. Isso é importante, porque infelizmente, o nosso cérebro não é bom em manter mais do que 7 ou 8 informações na nossa cabeça ao mesmo tempo, então precisamos simplificar as 160 linhas do programa. Por isso que estamos fazendo esta primeira etapa, dividindo o código em pequenos blocos, lendo eles isoladamente e adicionando os comentários explicando as partes não óbvias.
Vamos continuar com o segundo bloco, o setup():
// ----------- Declaração da função de cada elemento ---------------- // void setup() { //inicializa o monitor serial Serial.begin(9600); //Configura os pinos dos LEDs como Output pinMode(LED_A, OUTPUT); pinMode(LED_B, OUTPUT); pinMode(LED_C, OUTPUT); //Configura os pinos dos botões como Input pinMode(BUTTON_A, INPUT_PULLUP); pinMode(BUTTON_B, INPUT_PULLUP); pinMode(BUTTON_C, INPUT_PULLUP); //Configura o buzzer como Output pinMode(BUZZER, OUTPUT); //Inicializa o gerador de números "aleatórios" ( https://www.arduino.cc/en/Reference/RandomSeed ) randomSeed(analogRead(0)); }
Até aqui, sem grandes surpresas. O setup() basicamente inicializa as configurações necessárias para que o programa funcione. Podemos ler o próximo bloco, mas invés de irmos para o terceiro, iremos pular direto para o quarto, onde as funções são definidas. Isso irá tornar as coisas mais simples quando lermos o bloco do loop(), pois essas funções são usadas lá, e precisamos saber o que elas fazem para podermos entender o loop().
// ------- Função piscar Led ------- // void pisca_led(int posicao) { Serial.println(posicao); if (posicao == 0) { digitalWrite(LED_A, HIGH); tone (BUZZER, TOM_A); delay(TEMPO_PISCA); digitalWrite(LED_A, LOW); } else if (posicao == 1) { digitalWrite(LED_B, HIGH); tone (BUZZER, TOM_B); delay(TEMPO_PISCA); digitalWrite(LED_B, LOW); } else if (posicao == 2) { digitalWrite(LED_C, HIGH); tone (BUZZER, TOM_C); delay(TEMPO_PISCA); digitalWrite(LED_C, LOW); } noTone(BUZZER); } // fim do pisca_led // ----------- Função ler os botões -------- // int le_botao () { int result = -1; // -1 indica que nenhum botão foi apertado while (result == -1) { if (digitalRead(BUTTON_A) == LOW) { result = 0; //posição 0 } else if (digitalRead(BUTTON_B) == LOW) { result = 1; } else if (digitalRead(BUTTON_C) == LOW) { result = 2; } } // fim do while return result; // retorna o inteiro int le_botao }
Temos aqui duas funções, a pisca_led() e a le_botao(). O código está meio confuso, especialmente a pisca_led(). Isso acontece pois há muito código similar e repetido, mas, ainda assim, podemos entender o que elas fazem. Uma função tem normalmente 3 partes: argumentos, um bloco de código que será executado, e um retorno. Nem todas essas partes são obrigatórias, por exemplo, a função pisca_led() não retorna nenhum valor, e a função le_botao() não aceita nenhum argumento. Através desse modelo de como uma função funciona, podemos facilmente entender as funções acima.
A pisca_led() acende um LED e emite um som pelo buzzer, dado o argumento posicao. Já a função le_botao() faz exatamente o que o nome sugere, ela faz a leitura de quando um botão é pressionado e retorna um valor, dependendo de qual botão você pressionou.
Vamos então prosseguir para o bloco loop(), para depois finalmente podermos simplificar o código:
// ----------- Loop do sorteio, leitura de botão e comparação --------- // void loop() { // acende todos os LEDs digitalWrite(LED_A, HIGH); digitalWrite(LED_B, HIGH); digitalWrite(LED_C, HIGH); //Chamamos a função le_botão(), ou seja, não iremos prosseguir para a próxima parte do código //enquanto um botão não for apertado le_botao(); // apaga todos os LEDs digitalWrite(LED_A, LOW); digitalWrite(LED_B, LOW); digitalWrite(LED_C, LOW); delay(500); // ------ Bloco do sorteio da sequência -------- // for (int jogada = 0; jogada < QT_SEQ; jogada ++) { // QT_SEQ é o tamanho da sequência - quantas vezes os leds piscam sorteados[jogada] = random(3); //Chamamos a função pisca_led(), acendendo o LED e emitindo o tom dado o valor de sorteados[jogada] pisca_led(sorteados[jogada]); //Então a definição que vimos no primeiro bloco é usada aqui, TEMPO_ENTRE define o tempo //que iremos aguardar entre a piscada de um LED e o próximo da sequência delay(TEMPO_ENTRE); }// fim do sorteio // ------ Bloco que compara a sequência com os botoes apertados -------- // int vez; // definido fora do for para poder verificar numero de acertos no final for (vez = 0; vez < QT_SEQ; vez ++) { if (le_botao() == sorteados[vez]) { pisca_led(sorteados[vez]); } else { pisca_led(sorteados[vez]); break; // sai do for } } //fim do for // ------ Bloco que demonstra o resultado -------- // if (vez == QT_SEQ) { // acertou seq. inteira delay(500); for (int i = 0; i < 3; i++) { tone(BUZZER, 2000); delay(200); tone(BUZZER, 4000); delay(200); } noTone(BUZZER); } else { // errou algo delay(500); for (int i = 0; i < 3; i++) { tone(BUZZER, 200); delay(200); tone(BUZZER, 300); delay(200); } noTone(BUZZER); } } // fim do loop
Aqui as coisas complicam um pouco. Primeiro, este bloco é composto por diversas partes independentes, que fazem coisas diferentes, porém que estão juntas. Podemos usar funções para separar essas partes em blocos menores. Por exemplo, em vez de incluírmos o código que gera a sequência aleatória no loop(), podemos criar uma função gerar_sequencia(), colocar esse código nessa função e apenas chamar ela de dentro do loop(). Isto que significa abstrair o código, tornando-o mais simples e fácil de ler.
Vamos fazer isso então, primeiro criando funções para as seguintes partes da lógica: 1) função geradora da sequência aleatória; 2) e uma função para comparar os botões apertados pelo jogador com a sequência esperada; 3) função para quando o jogador ganhar o jogo; 4) função para quando ele perder o jogo.
Dessa forma, nosso código ficaria assim:
// ----------- Loop do sorteio, leitura de botão e comparação --------- // void loop() { // acende todos os LEDs digitalWrite(LED_A, HIGH); digitalWrite(LED_B, HIGH); digitalWrite(LED_C, HIGH); //Chamamos a função le_botão(), ou seja, não iremos prosseguir para a próxima parte do código //enquanto um botão não for apertado le_botao(); // apaga todos os LEDs digitalWrite(LED_A, LOW); digitalWrite(LED_B, LOW); digitalWrite(LED_C, LOW); delay(500); // ------ Bloco do sorteio da sequência -------- // sortear_sequencia(); // ------ Bloco que demonstra o resultado -------- // int resultado = checar_jogada(); if (resultado == QT_SEQ) { // acertou seq. inteira ganhou(); } else { // errou algo perdeu(); } } // fim do loop
Ok, mas cadê o código de cada uma dessas novas funções? Basicamente recortei o código anterior e joguei dentro dessas novas funções criadas:
void sortear_sequencia(){ for (int jogada = 0; jogada < QT_SEQ; jogada ++) { sorteados[jogada] = random(3); //Chamamos a função pisca_led(), acendendo o LED e emitindo o tom dado o valor de sorteados[jogada] pisca_led(sorteados[jogada]); delay(TEMPO_ENTRE); }// fim do sorteio } int checar_jogada(){ int vez; // definido fora do for para poder verificar numero de acertos no final for (vez = 0; vez < QT_SEQ; vez ++) { if (le_botao() == sorteados[vez]) { pisca_led(sorteados[vez]); } else { pisca_led(sorteados[vez]); break; // sai do for } } //fim do for return vez; } void ganhou(){ delay(500); for (int i = 0; i < 3; i++) { tone(BUZZER, 2000); delay(200); tone(BUZZER, 4000); delay(200); } noTone(BUZZER); } void perdeu(){ delay(500); for (int i = 0; i < 3; i++) { tone(BUZZER, 200); delay(200); tone(BUZZER, 300); delay(200); } noTone(BUZZER); }
Se você comparar o conteúdo dessas funções com o código loop() original, verá que não há nada de novo, nós apenas separamos o código e o loop() ficou muito mais simples e fácil de entender. Isso que significa abstrair o código. Resumindo, o que fizemos aqui foi muito simples, primeiro dividimos o código em blocos, depois simplificamos os blocos, separando cada parte em pequenas funções separadas.
Agora que temos conhecimento de como o código funciona, podemos modificá-lo para implementar o jogo original do Genius, em vez desta versão mais simples. Iremos fazer isso no nosso próximo post!
Segue então o nosso código final, que apesar de um pouco mais extenso, é mais simples de ler, entender e modificar:
// ----------- Bloco defines ------------------ // //Portas digitais usadas pelos LEDs #define LED_A 5 #define LED_B 6 #define LED_C 7 //Portas digitais usadas pelos Botões #define BUTTON_A 8 #define BUTTON_B 9 #define BUTTON_C 10 //Porta usada pelo buzzer #define BUZZER 11 //Tons usados quando um botão é pressionado ou quando um LED acende #define TOM_A 1200 #define TOM_B 1600 #define TOM_C 2000 //Não sabemos ao certo ainda para o que servem esses abaixo, mas sabemos //que tem algo a ver com Tempo. Iremos voltar neles mais adiante. #define TEMPO_PISCA 600 #define TEMPO_ENTRE 150 //Tamanho da sequência de LEDs #define QT_SEQ 6 //Variável onde vamos armazenar a sequência int sorteados[QT_SEQ]; // ----------- Bloco setup ------------------ // void setup() { //inicializa o monitor serial Serial.begin(9600); //Configura os pinos dos LEDs como Output pinMode(LED_A, OUTPUT); pinMode(LED_B, OUTPUT); pinMode(LED_C, OUTPUT); //Configura os pinos dos botões como Input pinMode(BUTTON_A, INPUT_PULLUP); pinMode(BUTTON_B, INPUT_PULLUP); pinMode(BUTTON_C, INPUT_PULLUP); //Configura o buzzer como Output pinMode(BUZZER, OUTPUT); //Inicializa o gerador de números "aleatórios" ( https://www.arduino.cc/en/Reference/RandomSeed ) randomSeed(analogRead(0)); } // ----------- Bloco loop ------------------ // void loop() { // acende todos os LEDs digitalWrite(LED_A, HIGH); digitalWrite(LED_B, HIGH); digitalWrite(LED_C, HIGH); //Chamamos a função le_botão(), ou seja, não iremos prosseguir para a próxima parte do código //enquanto um botão não for apertado le_botao(); // apaga todos os LEDs digitalWrite(LED_A, LOW); digitalWrite(LED_B, LOW); digitalWrite(LED_C, LOW); delay(500); // ------ Bloco do sorteio da sequência -------- // sortear_sequencia(); // ------ Bloco que demonstra o resultado -------- // int resultado = checar_jogada(); if (resultado == QT_SEQ) { // acertou seq. inteira ganhou(); } else { // errou algo perdeu(); } } // fim do loop // ----------- Bloco funções ------------------ // void pisca_led(int posicao) { Serial.println(posicao); if (posicao == 0) { digitalWrite(LED_A, HIGH); tone (BUZZER, TOM_A); delay(TEMPO_PISCA); digitalWrite(LED_A, LOW); } else if (posicao == 1) { digitalWrite(LED_B, HIGH); tone (BUZZER, TOM_B); delay(TEMPO_PISCA); digitalWrite(LED_B, LOW); } else if (posicao == 2) { digitalWrite(LED_C, HIGH); tone (BUZZER, TOM_C); delay(TEMPO_PISCA); digitalWrite(LED_C, LOW); } noTone(BUZZER); } // fim do pisca_led int le_botao () { int result = -1; // -1 indica que nenhum botão foi apertado while (result == -1) { if (digitalRead(BUTTON_A) == LOW) { result = 0; //posição 0 } else if (digitalRead(BUTTON_B) == LOW) { result = 1; } else if (digitalRead(BUTTON_C) == LOW) { result = 2; } } // fim do while return result; // retorna o inteiro int le_botao } void sortear_sequencia(){ for (int jogada = 0; jogada < QT_SEQ; jogada ++) { // QT_SEQ é o tamanho da sequência - quantas vezes os leds piscam sorteados[jogada] = random(3); //Chamamos a função pisca_led(), acendendo o LED e emitindo o tom dado o valor de sorteados[jogada] pisca_led(sorteados[jogada]); //Então a definição que vimos no primeiro bloco é usada aqui, TEMPO_ENTRE define o tempo //que iremos aguardar entre a piscada de um LED e o próximo da sequência delay(TEMPO_ENTRE); }// fim do sorteio } void checar_jogada(){ int vez; // definido fora do for para poder verificar numero de acertos no final for (vez = 0; vez < QT_SEQ; vez ++) { if (le_botao() == sorteados[vez]) { pisca_led(sorteados[vez]); } else { pisca_led(sorteados[vez]); break; // sai do for } } //fim do for return vez; } void ganhou(){ delay(500); for (int i = 0; i < 3; i++) { tone(BUZZER, 2000); delay(200); tone(BUZZER, 4000); delay(200); } noTone(BUZZER); } void perdeu(){ delay(500); for (int i = 0; i < 3; i++) { tone(BUZZER, 200); delay(200); tone(BUZZER, 300); delay(200); } noTone(BUZZER); }
Agradecimento ao MauMaker por ter lido um rascunho deste post.
peço desculpas pela falta de conhecimento,a credito que esse post foi muito bem explicado, mas eu não entendo nada de arduino mas gostaria muito de entender por isso estou buscando conhecer eu não entendi como juntar dois codigos em um ja fiz as declaração, as definições, ai em void setup declarei os botoes e tal ae peguei um codigo e antes o codigo e depois do void setup criei void 1(){}
depois criei void2(){}
ae no void loop deixei
{coisa 1 e coisa 2}
simplismente carrega mas num apresenta nada no display não postei o codigo por não saber se primeiro pode se eu puder me respondam por favor, desde ja agradeço
Boa Noite Rafael!
Porque você colocou as funções no fim do código? Poderia colocá-las no início?
Você testou esse código? Por que a função void checar_jogada() tem return se é void?
Conteúdo nota mil!! Parabéns pelo artigo super completo.