Quando estamos aprendendo a programar, um dos primeiros exercícios que fazemos é a criação de uma calculadora simples que é operada através de linha de comando. Devido à natureza física do Arduino, a realização deste tipo de projeto não é tão comum, mas também é possível através do monitor serial.
Nesta postagem vamos mostrar como criar uma interface de comunicação com o usuário através do monitor serial do Arduino para que seja possível enviar comandos à placa através de um computador. Os princípios que mostraremos aqui podem ser utilizados não apenas para controlar seu Arduino através do computador, mas podem ser adaptados também para controla-lo através de outras placas e outros softwares que utilizem comunicação serial.
O usuário deverá informar qual operação deseja realizar em nossa calculadora e depois inserir dois números que serão os operandos desta operação. As operações disponíveis serão: adição, subtração, multiplicação e divisão.
Componentes necessários
Para seguir este tutorial, você precisará dos seguintes itens:
Esta prática também deve funcionar com qualquer outra placa da família Arduino (ou compatível).
Variáveis
Começaremos nosso código criando seis variáveis globais. A primeira delas será um inteiro (int) que receberá qual o tipo de operação foi selecionada pelo usuário. Vamos inicia-la com valor 0:
1 | int opcao = 0; |
Em seguida, criaremos duas Strings que receberão os operandos da operação:
1 2 | String operando1 = ""; String operando2 = ""; |
Este tipo foi escolhido porque os dados digitados no monitor serial são recebidos por padrão como bytes e precisaremos verificar se o usuário digitou apenas números ou se existe alguma letra ou caractere especial na entrada.
Teremos também três variáveis de controle do tipo booleana que vão auxiliar na navegação pelos diálogos do monitor serial. Estas variáveis lógicas permitem apenas dois possíveis valores: 1 (true) e 0 (false). O Arduino permite que este tipo de variável seja declarada tanto como bool quanto boolean, sendo bool o padrão recomendado pela documentação de referência do Arduino.
A nossa primeira variável booleana vai informar para o programa se o menu inicial deve ou não ser exibido ao usuário. Por isso, vamos inicia-la como “verdadeiro” (true):
1 | bool menu = true; |
Nossa segunda variável booleana indicará se o sistema está ou não ocupado. Ela será verdadeira apenas quando nosso Arduino estiver esperando por uma entrada do usuário e vai impedir que o menu seja escrito repetidas vezes dentro do loop do programa. Como precisamos que o menu seja mostrado pelo menos uma vez ao ligar a placa, vamos inicia-la como falsa (false):
1 | bool busy = false; |
A última variável vai indicar ao programa se ele está esperando pelo primeiro ou pelo segundo operando de nossa calculadora. Ela se tornará verdadeira apenas após o usuário inserir o primeiro operando no monitor serial. Logo, ela será inicializada como falsa:
1 | bool gotOperando1 = false; |
Após a criação das variáveis, precisaremos de uma função que as retorne aos seus valores iniciais ao final de cada operação:
1 2 3 4 5 6 7 8 9 10 | void reset() { menu = true; busy = false; gotOperando1 = false; opcao = 0; operando1 = ""; operando2 = ""; cleanSerialBuffer(); } |
Esta função também será chamada caso seja detectada alguma entrada inválida por parte do usuário.
Menu Inicial
Nossa interface de comunicação com o usuário nada mais será do que uma sucessão de chamadas ao método Serial.println().
Imagem 1 – Menu no Monitor Serial
Dentro do setup() teremos apenas a inicialização da comunicação serial com uma taxa de transferência em bits por segundo (baud rate) de 9600:
1 2 3 | void setup() { Serial.begin(9600); } |
E o loop() terá apenas uma chamada à função printMenu(), que criaremos logo em seguida.
1 2 3 | void loop() { printMenu(); } |
Na função printMenu() precisamos garantir que o menu seja escrito uma única vez no monitor serial. Para isso, vamos verificar os valores de duas variáveis booleanas que criamos anteriormente: menu e busy.
Caso o valor de menu seja true e busy seja false, vamos escrever o menu no monitor serial e imediatamente mudar o valor de busy para true, indicando que o programa está agora esperando uma resposta do usuário. Esta resposta será lida por outra função que chamaremos de lerOpcao().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void printMenu() { if(menu) {// Se o menu deve ser mostrado. if(!busy) {// Mostra o menu caso não esteja esperando input do usuário. Serial.println("Escolha uma opção:\n"); Serial.println("1. Adição.\n"); Serial.println("2. Subtração.\n"); Serial.println("3. Multiplicação.\n"); Serial.println("4. Divisão.\n"); busy = true; } lerOpcao(); // Recebe a escolha do usuário. } else { recebeOperandos(); } } |
Alternativamente, o menu pode ser escrito com um único Serial.println() caso queira economizar linhas de código:
1 | Serial.println("Escolha uma opção:\n\n1. Adição.\n\n2. Subtração.\n\n3. Multiplicação.\n\n4. Divisão.\n"); |
Caso uma opção válida seja digitada, a função lerOpcao() vai mudar os valores de menu e busy para false e o programa prosseguirá para a função recebeOperandos(), que pedirá que o usuário insira os operandos para a realização da operação escolhida.
Tratamento de Entrada
Dentro da função lerOpcao(), vamos usar o método Serial.available() como condição para fazer leitura dos dados. Quando chamado, esse método retorna a quantidade de bytes disponíveis para leitura no buffer serial. Sendo assim, só vamos iniciar a leitura se a quantidade de bytes disponíveis for maior que zero:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | void lerOpcao() { if (Serial.available() > 0) { opcao = verificaOpcao(); if(!((opcao >= 1) && (opcao <= 4))) { Serial.println("Escolha uma opção válida.\n"); reset(); } else { menu = false; busy = false; } } } |
Quando o usuário digita qualquer coisa no monitor serial e pressiona a tecla ENTER para enviar, ele também está intencionalmente enviando uma quebra de linha (caractere ‘\n’) para o Arduino e ler esse caractere pode causar erros na execução do programa, pois ele ficará armazenado no buffer serial. Para evitar problemas, vamos botar todos os dados dentro de uma String e dizer que a leitura deve ser encerrada ao alcançar o caractere ‘\n’:
1 | String op = Serial.readStringUntil('\n'); |
Essa leitura dos dados é feita dentro da função verificaOpcao(), que também vai fazer uma análise dos dados recebidos. Ela retornará 0 (o valor inicial da variável opcao) caso o usuário digite algo que não seja um número ou que possua mais de 1 caractere (tendo em vista que o menu só possui 4 opções).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | int verificaOpcao() { String op = Serial.readStringUntil('\n'); if(op.length() != 1) {// Se o tamanho da String for diferente de 1. Serial.println("Erro: a opção deve ser um número de 1 a 4."); return 0; } else { if(isAlpha(op.charAt(0))) {// Se o caractere for uma letra. Serial.println("Erro: a opção deve ser um número de 1 a 4."); return 0; } else { if(isDigit(op.charAt(0))) {// Se for um dígito numérico. return op.toInt(); } else { return 0; } } } } |
Ao final da operação, precisamos converter a String para int, pois este é o tipo da variável opcao à qual o retorno da função será atribuída. Isso é feito com o método toInt() da classe String.
Recebendo os Operandos
Com o tipo de operação selecionado, a função printMenu() entrará na função recebeOperandos(), dando continuidade à nossa interface de comunicação com o usuário. Será pedido que o usuário digite o primeiro operando e logo em seguida, o segundo.
Imagem 2 – Digitando os Operandos
Para que o programa saiba em que passo desse processo ele se encontra, vamos criar uma condição com a variável gotOperando1 que criamos no início do projeto. Caso seu valor seja false, o programa vai pedir o primeiro operando e mudar o valor de busy para true para que a instrução seja mostrada apenas uma vez no monitor serial.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | if(!gotOperando1) {// Se o usuário ainda não digitou o primeiro operando. if(!busy) { Serial.println("Insira o primeiro operando.\n"); busy = true; } if (Serial.available() > 0) { operando1 = Serial.readStringUntil('\n'); // Ler uma string. if((operando1.length() > 0) && verificaNumero(operando1)) {// Verifica se um número com pelo menos 1 caractere foi digitado. gotOperando1 = true; // Usuário inseriu o primeiro operando. busy = false; } else {// Se o usuário digitou algo que não seja um número. Serial.println("Valor inválido.\n"); reset(); // Todas as variáveis retornam aos valores iniciais. } } } |
Após a leitura dos dados, precisamos verificar duas coisas: se a mensagem lida possui pelo menos 1 caractere (tamanho maior que 0) e se o que foi recebido foi um número. Esta última acontecerá dentro da função verificaNumero() que percorrerá a String para garantir que todos os caracteres que a compõe são dígitos numéricos ou um ponto decimal:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | bool verificaNumero(String operando) { for(int i = 0; i < operando.length(); i++) { if(!isDigit(operando.charAt(i))) {// Verifica se o caractere atual é um dígito (número). if(operando.charAt(i) != '.') {// Verifica se o caractere atual é um ponto decimal. return false; } } } return true; } |
Depois de receber o primeiro operando, o mesmo processo é repetido para o segundo, mas dentro de uma estrutura else.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | else {// Se o usuário adicionou o primeiro operando. if(!busy) { Serial.println("Insira o segundo operando.\n"); busy = true; } if (Serial.available() > 0) { operando2 = Serial.readStringUntil('\n'); if((operando2.length() > 0) && verificaNumero(operando2)) {// Verifica se um número foi digitado. busy = false; realizaOperacao(operando1.toFloat(), operando2.toFloat()); } else {// Se o usuário digitou algo que não seja um número. Serial.println("Valor inválido.\n"); } reset(); // Todas as variáveis retornam aos valores iniciais. } } |
Se tudo ocorrer de acordo com o esperado, é chamada a função realizaOperacao() para imprimir o resultado da operação no monitor serial. Assim como o menu, ela é apenas uma sucessão de chamadas a Serial.print().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | void realizaOperacao(float num1, float num2) { switch(opcao) { case 1: Serial.print(num1); Serial.print(" + "); Serial.print(num2); Serial.print(" = "); Serial.println(num1+num2); Serial.println(""); break; case 2: Serial.print(num1); Serial.print(" - "); Serial.print(num2); Serial.print(" = "); Serial.println(num1-num2); Serial.println(""); break; case 3: Serial.print(num1); Serial.print(" x "); Serial.print(num2); Serial.print(" = "); Serial.println(num1*num2); Serial.println(""); break; case 4: Serial.print(num1); Serial.print(" : "); Serial.print(num2); Serial.print(" = "); Serial.println(num1/num2); Serial.println(""); break; } } |
Ao final, nossa interface completa ficará da seguinte maneira:
Imagem 3 – Resultado
Código Completo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | int opcao = 0; // Variável de seleção de operação. String operando1 = ""; String operando2 = ""; bool menu = true; // Indica que o menu inicial deve ser mostrado. bool busy = false; // Indica que o programa está esperando input do usuário. bool gotOperando1 = false; // Indica que o primeiro operando foi recebido. void setup() { Serial.begin(9600); } void loop() { printMenu(); } void reset() {// Retorna variáveis para seus valores iniciais. menu = true; busy = false; gotOperando1 = false; opcao = 0; operando1 = ""; operando2 = ""; cleanSerialBuffer(); } void cleanSerialBuffer() {// Limpa o buffer serial. while(Serial.available() > 0) { Serial.read(); } } void printMenu() { if(menu) {// Se o menu deve ser mostrado. if(!busy) {// Mostra o menu caso não esteja esperando input do usuário. Serial.println("Escolha uma opção:\n"); Serial.println("1. Adição.\n"); Serial.println("2. Subtração.\n"); Serial.println("3. Multiplicação.\n"); Serial.println("4. Divisão.\n"); busy = true; } lerOpcao(); // Recebe a escolha do usuário. } else { recebeOperandos(); } } void lerOpcao() { if (Serial.available() > 0) {// Ler apenas se existir dados disponíveis. opcao = verificaOpcao(); // Lê a mensagem. if(!((opcao >= 1) && (opcao <= 4))) {// Se a opção não for um valor de 1 a 4. Serial.println("Escolha uma opção válida.\n"); reset(); } else { menu = false; busy = false; } } } int verificaOpcao() {// Verifica se o usuário digitou uma opção válida. String op = Serial.readStringUntil('\n'); if(op.length() != 1) {// Se o tamanho da String for diferente de 1. Serial.println("Erro: a opção deve ser um número de 1 a 4."); return 0; } else { if(isAlpha(op.charAt(0))) {// Se o caractere for uma letra. Serial.println("Erro: a opção deve ser um número de 1 a 4."); return 0; } else { if(isDigit(op.charAt(0))) {// Se for um dígito numérico. return op.toInt(); } else { return 0; } } } } void recebeOperandos() { if(!gotOperando1) {// Se o usuário ainda não digitou o primeiro operando. if(!busy) { Serial.println("Insira o primeiro operando.\n"); busy = true; } if (Serial.available() > 0) { operando1 = Serial.readStringUntil('\n'); // Ler uma string. if((operando1.length() > 0) && verificaNumero(operando1)) {// Verifica se um número com pelo menos 1 caractere foi digitado. // Serial.print("Operando 1: "); // Serial.println(operando1); gotOperando1 = true; // Usuário inseriu o primeiro operando. busy = false; } else {// Se o usuário digitou algo que não seja um número. Serial.println("Valor inválido.\n"); reset(); // Todas as variáveis retornam aos valores iniciais. } } } else {// Se o usuário adicionou o primeiro operando. if(!busy) { Serial.println("Insira o segundo operando.\n"); busy = true; } if (Serial.available() > 0) { operando2 = Serial.readStringUntil('\n'); // Ler uma string até a quebra de linha. if((operando2.length() > 0) && verificaNumero(operando2)) {// Verifica se um número foi digitado. // Serial.print("Operando 2: "); // Serial.println(operando2); busy = false; realizaOperacao(operando1.toFloat(), operando2.toFloat()); } else {// Se o usuário digitou algo que não seja um número. Serial.println("Valor inválido.\n"); } reset(); // Todas as variáveis retornam aos valores iniciais. } } } bool verificaNumero(String operando) { for(int i = 0; i < operando.length(); i++) { if(!isDigit(operando.charAt(i))) {// Verifica se o caractere atual é um dígito (número). if(operando.charAt(i) != '.') {// Verifica se o caractere atual é um ponto decimal. return false; } } } return true; } void realizaOperacao(float num1, float num2) { switch(opcao) { case 1: // Adição. Serial.print(num1); Serial.print(" + "); Serial.print(num2); Serial.print(" = "); Serial.println(num1+num2); Serial.println(""); break; case 2: // Subtração. Serial.print(num1); Serial.print(" - "); Serial.print(num2); Serial.print(" = "); Serial.println(num1-num2); Serial.println(""); break; case 3: // Multiplicação. Serial.print(num1); Serial.print(" x "); Serial.print(num2); Serial.print(" = "); Serial.println(num1*num2); Serial.println(""); break; case 4: // Divisão. Serial.print(num1); Serial.print(" : "); Serial.print(num2); Serial.print(" = "); Serial.println(num1/num2); Serial.println(""); break; } } |
Gostou? Deixe seu comentário logo abaixo, não deixe de conferir outras postagens do nosso blog. Confira também a nossa loja virtual e encontre todos os componentes utilizados no projeto no post.
Deixe um Comentário