Arduino · material didático
Os comandos de controle de fluxo para o Arduíno são aqueles que permitem ao programador alterar a sequência de execução das instruções do programa, similares à linguagem C/C++.
Podem ser divididos em dois grupos:
As condições que determinam o fluxo de execução das instruções geralmente são definidas pelo resultado de uma expressão lógica.
As expressões aritméticas resultam em números, por exemplo ponto flutuante (tipo float) ou inteiros (tipo int).
As expressões lógicas são aquelas cujo resultado é um valor lógico. Na linguagem do Arduíno (como em C/C++), o valor lógico retornado por uma expressão lógica verdadeira é 1; e o valor lógico falso é 0.
Nesse ponto cabe uma observação:
Assim como os operadores aritméticos especificam à máquina uma operação aritmética, os operadores lógicos especificam operações lógicas, que são de dois tipos: relacionais ou de comparação e operações lógicas (próprias da lógica matemática).
Os operadores lógicos de comparação possibilitam criar proposições simples estabelecendo a relação comparativa entre dois dados do mesmo tipo, por exemplo, duas variáveis, uma variável e uma expressão aritmética, duas constantes etc.
A tabela especifica os operadores e a respectiva operação lógica correspondente.
| Operador | Operação |
|---|---|
| > | maior |
| < | menor |
| >= | maior ou igual |
| <= | menor ou igual |
| == | igual |
| != | diferente |
Os operadores relacionais são aplicados a dois operandos. Por exemplo, 3 > 2, que compara o número 3 com o número 2.
Os operandos podem conter números; variáveis, por exemplo, x > 3; e expressões aritméticas, por exemplo, x + 3 <= 4 + y. A máquina resolve primeiro as expressões aritméticas (os operadores aritméticos têm precedência sobre os lógicos).
Expressões lógicas podem ser operandos de outras expressões lógicas. Isso é possível porque as expressões lógicas retornam valores inteiros (int) que são interpretados como 0 (falso) ou 1 (verdadeiro). Pode-se usar esses valores como operandos em outras operações lógicas para construir expressões mais complexas.
Variáveis, expressões ou constante lógicas podem ser combinadas com operadores lógicos para formar proposições compostas.
As operações lógicas básicas são conjunção, disjunção e negação (inversão lógica).
| Operador | Operação |
|---|---|
| && | conjunção (and) |
| || | disjunção (or) |
| ! | negação |
Existem operações que podem ser aplicadas bit a bit.
As operações lógicas também podem ser utilizadas bit a bit. Nesse caso, os operandos devem ser inteiros. Os operadores são os seguintes:
| Operador | Operação |
|---|---|
| & | conjunção bit a bit (and) |
| \ | disjunção bit a bit (or) |
| ^ | ou exclusivo bit a bit (xor) |
| ! | negação |
| << | deslocamento à esquerda |
| >> | deslocamento à direita |
As operações lógicas bit a bit (ou bitwise) são usadas para manipular bits individuais de um número. Na linguagem do Arduíno (assim como em C/C++), essas operações são realizadas usando operadores específicos que operam diretamente nos bits de um operando. Essas operações são úteis em muitas aplicações de baixo nível, como manipulação de registros, criptografia, e controle de hardware.
As operações de conjunção, disjunção e negação são aplicadas a cada bit dos operandos. Os operadores bit a bit operam diretamente sobre os bits individuais dos operandos.
As operações de deslocamento requerem como operando o inteiro cuja representação em bita será aplicado o deslocamento, bem como o número de bits de posições de deslocamento. Por exemplo,
int a = 3; // 11 em binário
int res = a << 2; // Desloca os bits de 'a' duas posições para a esquerda
Serial.begin(9600);
Serial.println(res); // Imprime o valor de 'res' no monitor serialirá apresentar na console do monitor serial o valor 12 (1100 em binário).
Operações lógicas bit a bit são projetadas para serem usadas com tipos de dados inteiros (como int, unsigned int, char, short, long). Aplicar esses operadores a tipos de dados de ponto flutuante (como float e double) não é apropriado e geralmente resulta em comportamento indefinido ou em um erro de compilação, dependendo do compilador e das configurações.
Os caracteres podem ser usados com operadores bit a bit porque cada caractere é internamente representado por um valor inteiro correspondente ao seu código ASCII.
Na condicional simples, a execução de um bloco de instruções interno ao comando é condicionada ao resultado da expressão lógica da sentença.
Sua sintaxe é
if (<condição>) {
<bloco de instruções>
}Processo de execução pela máquina:
A instrução (ou bloco de instruções) interna é executada somente se a condição ou o cálculo da expressão lógica resultar diferente de 0 e a execução do programa prossegue após a estrutura if.
Em caso contrário, se a condição for falsa (valor 0), a máquina executa a instrução seguinte ao if do programa, sem executar a instrução (ou bloco de instruções).
Observações gerais:
Um exemplo é o seguinte:
void setup() {
Serial.begin(9600); // Inicializa a comunicação serial a 9600 bps
}
void loop() {
int num; // Declaração da variável num
// Solicitar ao usuário que insira um número
Serial.println("Digite um numero: ");
// Esperar até que o usuário insira um número no monitor serial
while (Serial.available() == 0) {
// Aguarda a entrada do usuário
}
// Lê o número inserido
num = Serial.parseInt();
// Limpa o buffer serial para evitar caracteres residuais
while (Serial.available() > 0) {
Serial.read();
}
// Verificar o valor de 'num' e enviar a resposta correspondente
if (num > 10) {
Serial.println("\n\nO numero e maior do que 10");
}
if (num == 10) {
Serial.println("\n\nVoce acertou!\n");
Serial.println("\nO numero e igual a 10");
}
if (num < 10) {
Serial.println("\n\nO numero e menor do que 10");
}
// Adiciona um pequeno atraso antes de reiniciar o loop
delay(1000);
}Nesse exemplo, acho importante destacar:
Serial.available(): é um método da classe que o objeto Serial representa. Verifica quantos bytes estão disponíveis para leitura na porta serial e retorna o número de bytes disponíveis para leitura na porta serial. Em outras palavras, indica quantos caracteres (ou bytes) foram recebidos pela porta serial e estão prontos para serem lidos.while (Serial.available() == 0):: é uma estrutura de repetição - como trataremos mais adiante, que continua a rodar em loop enquanto Serial.available() retorna 0, ou seja, mantém o Arduino preso dentro do loop até que pelo menos um byte de dados esteja disponível para leitura. Quando o usuário digita um número e pressiona "Enter", os bytes correspondentes ao número (e possivelmente um caractere de nova linha \n ou \r, Serial.available() retorna um valor maior que 0, significando que há pelo menos um byte de dados na fila de recepção serial, e o loop while termina, permitindo que o código continue sua execução.while (Serial.available() > 0): é um loop que continua a rodar enquanto Serial.available() retorna um valor maior que 0, indicando que há dados disponíveis residuais no buffer serial. Serial.read() é um método lê um byte de dados do buffer serial, logo, nesse caso, limpando o buffer.Serial.parseInt():: é um método que lê a entrada serial até encontrar um número válido e busca convertê-lo em um inteiro (int). Ignora automaticamente qualquer caractere não numérico (exceto sinais de negativo) até encontrar o início de um número. Assim que encontra um dígito numérico, começa a construir o número até que ele encontre um número completo ou até que o tempo limite (timeout, 1 segundo, que pode ser ajustado com o método Serial.setTimeout()) seja alcançado. O método retorna o número inteiro que foi lido e convertido. Esse valor pode ser armazenado em uma variável, como no exemplo. continua lendo caracteres do buffer serial. Observação: caracteres como espaços, quebras de linha (\n), ou qualquer caractere não numérico (exceto sinais de negativo) são ignorados até que o método encontre um número válido.Na condicional composta, a execução de um bloco de instruções interno ao comando if é condicionada ao valor da condição ou resultado da expressão lógica da sentença ser verdadeiro (diferente de 0), enquanto a execução de outro bloco de instruções, interno ao else do comando if, é condicionada ao resultado ser falso (valor lógico igual a 0).
A sintaxe é
if (<condição>) {
<bloco 1 de instruções>
}
else {
<bloco 2 de instruções>
}Processo de execução pela máquina:
Observações gerais:
Um exemplo é o seguinte:
void setup() {
Serial.begin(9600); // Inicializa a comunicação serial a 9600 bps
}
void loop() {
int num; // Declaração da variável num
// Solicitar ao usuário que insira um número
Serial.println("Digite um numero: ");
// Esperar até que o usuário insira um número no monitor serial
while (Serial.available() == 0) {
// Aguarda a entrada do usuário
}
// Lê o número inserido
num = Serial.parseInt();
// Limpa o buffer serial para evitar caracteres residuais
while (Serial.available() > 0) {
Serial.read();
}
// Verificar o valor de 'num' e enviar a resposta correspondente
if (num > 10) {
Serial.println("\n\nO numero e maior do que 10");
} else {
Serial.println("\n\nO numero e menor ou igual a 10");
if (num < 5) {
Serial.println("\n\nO numero e menor do que 5\n");
Serial.print("\nO numero e ");
Serial.println(num);
}
}
// Adiciona um pequeno atraso antes de reiniciar o loop
delay(1000);
}switch...caseSeja o seguinte uso de condicional composta
void setup() {
Serial.begin(9600); // Inicializa a comunicação serial a 9600 bps
}
void loop() {
int a; // Declaração da variável a
// Solicitar ao usuário que insira um número
Serial.println("Digite um numero: ");
// Esperar até que o usuário insira um número no monitor serial
while (Serial.available() == 0) {
// Aguarda a entrada do usuário
}
// Lê o número inserido
a = Serial.parseInt();
// Limpa o buffer serial para evitar caracteres residuais
while (Serial.available() > 0) {
Serial.read();
}
// Verificar o valor de 'a' e enviar a resposta correspondente
if (a > 100) {
Serial.println("a maior do que 100");
} else if (a > 50) {
Serial.println("a maior do que 50");
} else {
Serial.println("a menor ou igual a 50");
}
// Adiciona um pequeno atraso antes de reiniciar o loop
delay(1000);
}O código possui dois comandos condicionais compostos if...else. Diz-se que os ifs estão aninhados.
A linguagem do Arduíno possui uma instrução composta (switch...case) de condicional composta múltipla cuja sintaxe é a seguinte:
switch (<expressão integral>) {
case <dado inteiro, caractere ou enumerador>:
<bloco 1 de instruções>
break;
case <dado inteiro, caractere ou enumerador>:
<bloco 2 de instruções>
break;
...
default:
<bloco n de instruções>
}O argumento do switch é a expressão que você deseja avaliar. Esta expressão pode ser de qualquer tipo de dado integral, ou seja, que representa inteiros, como int, char, enum, etc. O valor desta expressão é comparado com os valores especificados em cada case.
O argumento do case pode ser:
enum definidos pelo identificador seguidos de valores entre {} separados por vírgulas. Por exemplo, enum Dias {SEG, TER, QUA, QUI, SEX, SAB, DOM};. Em C, os elementos de uma enumeração (enum) devem ser valores inteiros constantes.O funcionamento do switch...case é o seguinte:
switch comparando-o com o argumento do primeiro case.switch, comparando-o com o argumento do próximo case.case.default, caso o argumento do switch não seja igual aos argumentos dos case.default e segue à execução da próxima instrução do código.Adaptando o exemplo anterior para ilustrar o uso do switch...case, temos:
void setup() {
Serial.begin(9600); // Inicializa a comunicação serial a 9600 bps
}
void loop() {
int a; // Declaração da variável a
// Solicitar ao usuário que insira um número
Serial.println("Digite um numero: ");
// Esperar até que o usuário insira um número no monitor serial
while (Serial.available() == 0) {
// Aguarda a entrada do usuário
}
// Lê o número inserido
a = Serial.parseInt();
// Limpa o buffer serial para evitar caracteres residuais
while (Serial.available() > 0) {
Serial.read();
}
// Usando switch para verificar o valor de 'a'
switch (a) {
case 100:
Serial.println("Voce digitou 100");
break;
case 50:
Serial.println("Voce digitou 50");
break;
default:
Serial.println("Voce digitou numero diferente de 50 e de 100");
break;
}
// Adiciona um pequeno atraso antes de reiniciar o loop
delay(1000);
}Observação. Você pode usar o mesmo bloco de instruções subordinado a vários case diferentes. Isso é feito simplesmente omitindo as instruções de break entre os case, permitindo que a execução "caia" (fall through) para o próximo case. Por exemplo,
switch (<expressão integral>) {
case <dado inteiro, caractere ou enumerador>:
case <dado inteiro, caractere ou enumerador>:
<bloco 2 de instruções>
break;
...
default:
<bloco n de instruções>
}? :Na linguagem do Arduíno, o operador ternário ? : é uma forma concisa de expressão condicional que pode substituir a estrutura if-else para operações simples. Este operador é também conhecido como operador condicional.
A sintaxe é
<condição> ? <instrução ou bloco de instruções 1> : <instrução ou bloco de instruções 2>;, onde
gotoNa linguagem do Arduíno dispõe-se de um comando de desvio incondicional, goto. Precisa ser utilizado com muito cuidado, pois é próprio de uma linguagem não estruturada.
O comando goto é frequentemente associado a linguagens de programação não estruturadas, onde o fluxo de controle pode ser alterado arbitrariamente, levando a um código difícil de seguir e manter. No entanto, é importante entender que o C, embora seja uma linguagem de programação estruturada, inclui o comando goto por razões históricas e por permitir certos usos específicos onde pode ser conveniente.
As linguagens não estruturadas são caracterizadas pela ausência de estruturas claras para o controle de fluxo de execução das instruções, como loops e condicionais, o que frequentemente leva ao uso excessivo de comandos goto, resultando em código espaguete (spaghetti code). Exemplos incluem linguagens de primeira geração como Assembly.
O formato geral do goto é
<nome do rotulo>: <instrução>
...
goto <nome do rótulo>;O rótulo (label) pode ser um nome qualquer, contanto que siga as regras de nomenclatura dos identificadores. Um rótulo é definido como um identificador seguido de dois pontos (:), e pode ser colocado antes de qualquer instrução em uma função.
Os caracteres são tipo char. Os caracteres válidos na linguagem do Arduíno são baseados no conjunto de caracteres ASCII. O ASCII (American Standard Code for Information Interchange) é um padrão de codificação de caracteres que representa texto em computadores e outros dispositivos que utilizam texto. O ASCII usa valores numéricos para representar caracteres, incluindo letras, dígitos, sinais de pontuação e caracteres de controle.
Caracteres ASCII no Arduíno Letras: A-Z (maiúsculas), a-z (minúsculas) Dígitos: 0-9 Sinais de pontuação: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ { | } ~ ` Caracteres de controle: como \n (nova linha), \t (tabulação), \0 (nulo), entre outros.
Os caracteres são representados internamente por seus valores ASCII, que são inteiros decimais correspondentes a cada caractere. A tabela ASCII (American Standard Code for Information Interchange) define esses valores para um conjunto padrão de caracteres.
A tabela ASCII mapeia caracteres para números inteiros. Por exemplo,
Os caracteres individuais precisam ser delimitados por aspas simples (' '). Alguns caracteres de controle comuns também são representados usando aspas simples, mas com sequências de escape (\).
A declaração de variáveis do tipo char possui a seguinte sintaxe:
char x;e a declaração com inicialização
char x = '<caractere>';As atribuições podem ser realizadas para caracteres da mesma forma que para variáveis int e float, <nome> = '<caractere>'.
Além disso, pode-se comparar caracteres diretamente usando os operadores de comparação (==, !=, <, >, <=, >=), pois os caracteres são representados internamente por seus valores ASCII. Similarmente, entre eles podem ser aplicados operadores lógicos bit a bit.
As cadeias de caracteres podem ser definidas de duas formas no Arduino: usando vetores de caracteres (char) ou objetos String. Para a linguagem Arduino, um conjunto de caracteres entre aspas duplas é considerado uma string.
No caso de variáveis, as strings baseadas em char precisam ser declaradas sem inicialização ou com inicialização da seguinte forma:
| Tipo | Descrição |
|---|---|
char <nome>[<tamanho>]; |
Declaração sem inicialização |
char <nome>[] = "<cadeia de caracteres>" |
Declaração com inicialização |
Onde
Observações:
char da mesma forma que nas variáveis int, float e char; Para manipulação de strings no Arduino, você pode usar o objeto String que permite atribuições diretas, como String nome = "Maria";.char não podem ser usadas diretamente em operações lógicas da mesma forma que variáveis inteiras ou caracteres. **Strings em C (e Arduino) são arrays de caracteres terminados com o caractere nulo.Toda string baseada em char possui um caractere 'terminador' definido no ASCII pelo caractere de escape /0. Por exemplo, a palavra "Maria" é uma string que na linguagem Arduino é definida em memória por:
| 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| 'M' | 'a' | 'r' | 'i' | 'a' | '/0' |
Como uma string baseada em char é um vetor de caracteres, o nome do vetor define um ponteiro do vetor, ou seja, a variável que aponta para o endereço onde o primeiro caractere do vetor está armazenado em memória. Em razão disso, os elementos da string (os caracteres) podem ser identificados pela sua posição relativa. A sintaxe para a identificação é, como mostrado no exemplo:
char nome[] = "Maria";
Serial.print("A primeira letra do nome é ");
Serial.println(nome[0]);É bom destacar que a posição sempre é iniciada a partir de 0.
Na linguagem Arduino uma cadeia de caracteres também pode ser declarada com um objeto tipo String. Nesse caso, deve ser declarado da seguinte forma:
| Tipo | Descrição |
|---|---|
String <nome> = "<cadeia de caracteres>"; |
Declaração com inicialização |
String <nome>; |
Declaração sem inicialização |
Serial.print() e Serial.println()Como já utilizado nos exemplos anteriores, no Arduino as funções Serial.print() e Serial.println() são usadas para enviar dados do Arduino para o Monitor Serial no computador.
Serial.print(): Imprime os dados na janela do Monitor Serial sem adicionar uma nova linha ao final.Serial.println(): Imprime os dados na janela do Monitor Serial e move o cursor para a próxima linha.Exemplo básico:
void setup() {
Serial.begin(9600); // Inicializa a comunicação serial a 9600 bps
}
void loop() {
// Limpa o buffer serial
while (Serial.available() > 0) {
Serial.read(); // Lê e descarta qualquer dado restante no buffer
}
Serial.print("Digite um caractere: ");
while (Serial.available() == 0) {
// Aguarda até que um caractere seja digitado no Monitor Serial
}
char c = Serial.read(); // Lê o caractere digitado
Serial.print("\nVocê digitou: ");
Serial.println(c);
delay(1000); // Pequeno atraso para dar tempo ao usuário
}Serial.read()A função Serial.read() lê o primeiro byte de dados disponíveis na entrada serial e o retorna como um valor inteiro. Esse valor pode ser convertido em um caractere se necessário.
Exemplo:
void setup() {
Serial.begin(9600);
}
void loop() {
delay(1000);
if (Serial.available() > 0) {
char c = Serial.read(); // Lê o caractere digitado
Serial.print("Você digitou: ");
Serial.println(c);
while (Serial.available()>0)
Serial.read();
}
}Serial.readString()A função Serial.readString() lê a entrada serial e retorna uma String contendo os caracteres recebidos até um delimitador ou timeout. Por padrão, o delimitador é o caractere de nova linha (\n).
Exemplo:
void setup() {
Serial.begin(9600);
}
void loop() {
if (Serial.available() > 0) {
String texto = Serial.readString();
Serial.print("Você digitou: ");
Serial.println(texto);
}
}Serial.parseInt()A função Serial.parseInt() lê a entrada serial e converte os números recebidos em um valor inteiro (int).
Exemplo:
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println("Digite um número:");
while (Serial.available() == 0) {
// Aguarda a entrada do usuário
}
int num = Serial.parseInt();
while (Serial.available() > 0) {
// Limpa o buffer
Serial.read();
}
Serial.print("Você digitou: ");
Serial.println(num);
}As String são objetos utilizados no Arduino para manipulação de texto. As String podem ser manipuladas de diversas maneiras, utilizando métodos específicos.
Exemplo de criação e manipulação de uma String:
void setup() {
Serial.begin(9600);
String nome = "Arduino"; // Cria uma String
Serial.print("O nome é: ");
Serial.println(nome);
nome += " Mega"; // Concatena " Mega" à String
Serial.print("O nome completo é: ");
Serial.println(nome);
}
void loop() {
// O loop fica vazio neste exemplo
}A tabela abaixo apresenta um resumo dos principais métodos para trabalhar com o monitor serial.
| Método | Descrição | Entrada | Saída |
|---|---|---|---|
Serial.begin(baudrate) |
Inicializa a comunicação serial com a taxa de baud especificada. | baudrate (taxa de transmissão em bps) |
Nenhuma |
Serial.available() |
Retorna o número de bytes disponíveis para leitura na porta serial. | Nenhuma | Número de bytes disponíveis |
Serial.read() |
Lê o próximo byte de dados recebidos no buffer serial. | Nenhuma | Byte lido (int, -1 se falha) |
Serial.peek() |
Olha o próximo byte de dados recebidos sem removê-lo do buffer. | Nenhuma | Byte lido (int, -1 se falha) |
Serial.flush() |
Aguarda até que a transmissão de dados em andamento seja concluída. | Nenhuma | Nenhuma |
Serial.print(data) |
Envia dados para o monitor serial em formato texto. | data (int, float, string, etc.) |
Nenhuma |
Serial.println(data) |
Envia dados para o monitor serial em formato texto, seguido por uma nova linha. | data (int, float, string, etc.) |
Nenhuma |
Serial.write(val) |
Envia um byte ou uma sequência de bytes para o monitor serial. | val (byte, char, array, string) |
Número de bytes escritos |
Serial.end() |
Finaliza a comunicação serial, liberando os pinos digitais usados. | Nenhuma | Nenhuma |
Serial.parseInt() |
Lê os dados da porta serial como um valor inteiro. | Nenhuma | Valor inteiro lido |
Serial.parseFloat() |
Lê os dados da porta serial como um valor flutuante (float). | Nenhuma | Valor float lido |
Serial.find(target) |
Lê os dados recebidos até encontrar uma correspondência com o valor de destino especificado. | target (string ou char) |
true se encontrado, false se não |
Serial.readString() |
Lê todos os caracteres recebidos como uma string até que um tempo limite expire. | Nenhuma | String lida |
Serial.readBytes(buffer, length) |
Lê uma quantidade específica de bytes no buffer fornecido. | buffer (char array), length (int) |
Número de bytes lidos |
Serial.findUntil(target, terminator) |
Lê os dados até encontrar um alvo ou terminador, o que ocorrer primeiro. | target, terminator (string ou char) |
true se encontrado, false se não |
Serial.setTimeout(timeout) |
Define o tempo limite para operações de leitura. | timeout (milissegundos) |
Nenhuma |
Os métodos descritos na tabela são aplicáveis a qualquer objeto da classe HardwareSerial (da qual Serial é uma instância) no Arduino, e não apenas ao monitor serial. A classe HardwareSerial é usada para se comunicar com dispositivos seriais em geral, o que inclui:
Esses métodos permitem que você envie e receba dados através de qualquer interface serial disponível na placa, seja ela conectada ao monitor serial no computador ou a outro dispositivo serial externo.