Arduino 10: funções, listas, bibliotecas

Com o que vimos de programação do Arduino até aqui, que foi basicamente como usar suas entradas analógicas e digitais, já podemos construir todos os circuitos que vimos nas seções anteriores usando transistores e circuitos integrados 555, de forma muito mais simples e sem precisar calcular circuitos RC (resistor-capacitor), e nem mesmo medir tensões e correntes. Não será sempre assim. O conhecimento de eletrônica básica ainda será importante para fazer projetos mais interessantes, porém o Arduino de fato simplifica o uso da eletrônica.

Esta é uma seção opcional. Aqui exploramos alguns tópicos mais avançados de programação com o Arduino que permitirão que você use programas escritos por outras pessoas e faça alterações neles sem necessariamente entender tudo o que fazem. Se você nunca programou antes os conceitos podem parecer complexos. Mas não é preciso entender tudo para fazer os experimentos. Monte os circuitos (que são muito simples) e copie os códigos. Você pode deixar para ler a teoria depois, quando já tiver mais familiaridade com programas em Arduino.

Declarando funções

Os comandos digitalWrite, analogRead, etc. que chamamos dentro dos blocos setup() e loop() são chamadas de funções. Elas foram definidas nas bibliotecas do Arduino. Essas bibliotecas são automaticamente incluídas em todos os programas.

Uma função, portanto, para que possa ser chamada, precisa ser definida em algum lugar. A chamada poderá ser feita dentro do bloco loop() ou setup(). A definição de novas funções poderá ser feita no próprio sketch, ao lado dos blocos loop() e setup() que são definições de funções (chamadas automaticamente pelo Arduino).

Portanto, para definir uma função, escolha qualquer lugar antes ou depois dos blocos loop() e setup(), e crie um novo bloco com a estrutura abaixo:

void nomeDaFuncao() {
    // coloque aqui as instruções que sua função irá executar quando chamada
}

Vejamos um exemplo. Abaixo definimos uma função chamada piscar():

void piscar() {
    digitalWrite(8, HIGH);
    delay(500);
    digitalWrite(8, LOW);
    delay(500);
}

Ela tem o mesmo código dentro de loop() do circuito que pisca o LED. Agora podemos chamar a função que acabamos de definir como se fosse um comando (terminando em ponto-e-vírgula):

void loop() {
    piscar();   // chama a função piscar()
}

O código acima funciona igual ao código original que pisca o LED. A vantagem de definir funções é que podemos chamá-las várias vezes, sem precisar repetir o código que ela contém. Isto é mais fácil de perceber se definirmos funções com parâmetros.

Quando definimos uma função, os parâmetros são declarados como variáveis. Como toda variável, o cada parâmetro tem um tipo de dados que faz parte da declaração. Essas variáveis declaradas irão receber valores quando a função for chamada, e podem usar esses valores dentro da definição da função. No exemplo abaixo definimos uma função piscar() contendo dois parâmetros inteiros:

void piscar(int pino, int tempo) {
    digitalWrite(pino, HIGH);
    delay(tempo);
    digitalWrite(pino, LOW);
    delay(tempo);
}

Observe que as variáveis são usadas pelos comandos que estão dentro da função. Para chamar a função acima precisamos passar para ela dois parâmetros inteiros. O valor do primeiro parâmetro será copiado (atribuído) à variável pino, e o valor do segundo será copiado à variável tempo. Por exemplo, veja a chamada abaixo dentro de loop():

void loop() {
    piscar(8, 500);
}

Isto irá copiar o valor 8 para pino, e o valor 500 para tempo. E dentro da função, esses valores serão novamente copiados para funções do Arduino (digitalWrite e delay). Qual a vantagem disso? Agora podemos chamar a mesma função várias vezes, alterando os parâmetros que passamos para ela. Por exemplo, podemos fazer um loop() que pisca LEDs em pinos diferentes e em tempos diferentes sem precisar escrever 12 linhas de código:

void loop() {
    piscar(8, 500);
    piscar(9, 250);
    piscar(8, 1000);
}

Experimento 31 (extra) – Definindo funções para controlar um LED RGB

Material necessário:

  • Arduino Nano + cabo USB + computador
  • LED RGB de anodo comum (ou três LEDs: um vermelho, um verde e um azul)
  • Resistor de 220 ohms
  • Protoboard, fios e jumpers

O programa abaixo define uma função cor(r,g,b) que permite declarar cores usando comandos RGB que são traduzidos em cores de um LED RGB cujos terminais estão conectados a saídas PWM do Arduino. O circuito pode ser usado com 3 LEDs ou um LED RGB. Como o Led RGB usado tem o anodo comum, ele foi ligado em 5V e analogWrite(pino, 0) provocará o brilho máximo, já que a diferença de potencial nesse valor é máxima. Para compensar isto sem modificar o circuito podemos subtrair 255 do valor de cada componente de cor. Isto é feito dentro da função cor();

#define LED_VERMELHO 9
#define LED_VERDE 10
#define LED_AZUL 11

void setup() {}

void cor(int r, int g, int b) {
    analogWrite(LED_VERMELHO, 255 - r);
    analogWrite(LED_VERDE, 255 - g);
    analogWrite(LED_AZUL, 255 - b);
}

void acender(int r, int g, int b, int intervalo) {
    cor(r, g, b);
    delay(intervalo);
}

void loop() {
    acender(255,0,0,1000);   // vermelho
    acender(0,255,0,1000);    // verde
    acender(0,0,255,1000);    // azul
    acender(190,255,0,1000);  // amarelo
    acender(190,0,255,1000);  // magenta

    acender(0,255,255,1000);  // ciano
    acender(255,255,255,1000); // branco
    acender(0,0,0,1000);      // apagado

    acender(63,0,0,1000); // vermelho com 1/4 da intensidade
    acender(0,63,0,1000); // verde com 1/4 da intensidade
    acender(0,0,63,1000); // azul com 1/4 da intensidade
}

Além da função cor(), o programa acima também define uma função acender(r,g,b,duracao), que estabelece a cor (chamando a função cor()) e o tempo em que ela ficará acesa. O loop() chama várias vezes a função acender() com diferentes parâmetros, para que o circuito exiba uma sequência de cores diferentes, mantendo cada uma acesa por um segundo.

Bibliotecas e arquivos .h (arquivos de cabeçalho)

Suponha que você pretenda usar LEDs RGB em vários circuitos diferentes e queira reusar as funções criadas no experimento anterior. Uma maneira de fazer isto é recortar e colar o texto no outro programa. Mas há alternativas melhores. Uma delas é armazená-las em um arquivo separado que possa ser incluído em outros programas: um arquivo de cabeçalho, que tem a extensão .h

Para criar um arquivo de cabeçalho no IDE do Arduino, clique no menu que aparece logo abaixo do ícone do monitor serial, e depois selecione “New Tab”:

Depois escolha um nome para o arquivo. Por exemplo, “ledsrgb.h”, e grave-o.

Agora vamos transferir as funções cor() e acender() para o arquivo ledsrgb.h. Recorte-as do sketch, e cole as duas funções abaixo no arquivo .h:

void cor(int r, int g, int b) {
    analogWrite(LED_VERMELHO, 255 - r);
    analogWrite(LED_VERDE, 255 - g);
    analogWrite(LED_AZUL, 255 - b);
}

void acender(int r, int g, int b, int intervalo) {
    cor(r, g, b);
    delay(intervalo);
}

Como a função cor() depende de variáveis que ainda estão no arquivo original, precisamos redefini-las localmente (no arquivo .h) e depois copiar os valores. Acrescente o seguinte código:

int pino_R; // foram declaradas sem valor inicial
int pino_G;
int pino_B;

void configurarRGB(int pr, int pg, int pb) {
    pino_R = pr; // o valor inicial passado quando esta função for chamada
    pino_G = pg;
    pino_B = pb;
}

E altere a função cor() para que ela use os novos valores:

void cor(int r, int g, int b) {
    analogWrite(pino_R, 255 - r);
    analogWrite(pino_G, 255 - g);
    analogWrite(pino_B, 255 - b);
}

A função configurarRGB() será chamada a partir do sketch original, para passar os valores dos pinos ao arquivo .h. Para incluir o arquivo .h no seu sketch, use uma expressão #include:

#include "piscaled.h"

(observe que ela não tem ponto e vírgula no final – é uma diretiva como #define).

Dentro do setup() do seu sketch, chame a função configurarRGB() passando as variáveis que contém os números dos pinos. Desta forma, os valores serão copiados para as variáveis do arquivo .h:

void setup() {
    configurarRGB(LED_VERMELHO, LED_VERDE, LED_AZUL);
}

Agora é possível rodar o programa alterado, que consiste de dois arquivos, e ele funcionará igual, mesmo não contendo a definição de acender(). Se você quiser usar piscaled.h em outro sketch, precisa apenas copiá-lo para a pasta onde está o arquivo .ino do sketch, declarar o #include e chamar o configurarRGB(). Depois disso você pode chamar as funções cor() e acender() como se elas tivessem sido definidas localmente.

Se você achar mais fácil, pode baixar o projeto contendo os dois arquivos usados neste exemplo a partir do repositório GitHub disponível na Internet. Veja o link na introdução da apostila.