Criando uma biblioteca para o Arduino para facilitar o reuso de suas funções

Já criamos a função que usa o led interno para comunicar valores e situações ao programador, agora é hora de entender como criar uma biblioteca para facilitar o uso dessa função em múltiplos programas sem ficar copiando o texto dela.

No artigo anterior apresentei a minha função debugLed(), que eu uso para me transmitir informações de estado (tipo OK, FALHA, AGUARDANDO, etc.) e para acompanhar o estado dos sensores analógicos, dispensando para isso o uso de displays LCD, da serial/tela do computador e mesmo a instalação de leds adicionais: a função apenas controla, de modo bem simples, o número de vezes que o led interno do Arduino pisca.

Durante algum tempo eu simplesmente copiava o código da função a cada novo programa mas, tendo sido programador, logo reconheci que esta é a situação ideal para a criação de uma biblioteca. Quando se cria uma biblioteca, as funções (e variáveis, tipos, etc.) exportadas por ela podem ser usadas em vários outros programas, simplesmente declarando, em cada um deles, que eles devem ser compilados em conjunto com a biblioteca.

Acima você vê como fica o programa que faz uso da minha função debugLed(), com destaque para o trecho que faz a referência a ela. Abaixo você o vê (à direita, bem menor porque removi os comentários) lado a lado com a versão anterior (à esquerda), que precisa trazer o código completo da função que controla o led.

O do lado esquerdo define, ao seu final, toda a função, que é chamada (dentro do loop()) pelo nome de debugLed(). Já o da direita inclui, no seu início, a biblioteca DebugLED (que eu criei para compartilhar a função), e aí chama a mesma função pelo nome de debugLed.sinaliza().

No exemplo acima pode parecer que a única vantagem é o código mais curto e fácil de entender, mas pense em como isso ficaria melhor se eu estivesse encapsulando na minha biblioteca uma função maior e mais complexa, ou mesmo uma coleção de funções usadas em conjunto em vários programas diferentes.

Criação de uma biblioteca para o Arduino

Em suma, as vantagens mais visíveis que eu procuro ao colocar em uma biblioteca as funções que eu reuso com frequência1: os programas ficam menores e sem complexidade que não diga respeito ao seu funcionamento direto, eventuais melhorias nas funções compartilhadas ficam imediatamente disponíveis para todos os programas que os incluem, a modularização facilita correções e melhorias, etc.

Já criei e mantive muitas bibliotecas na vida, mas no caso do Arduino eu tinha um problema no meu caminho: nunca programei em C++ antes, e é esta a linguagem que serve como base à estruturação do código para o Arduino.

Felizmente não tive dificuldade em encontrar a solução: um tutorial oficial explicando como criar uma biblioteca para Arduino.

O tutorial mencionado acima é o recurso que eu recomendo que você consulte para conhecer os detalhes a respeito da criação das suas próprias bibliotecas, mas para ajudá-lo, e também para me ajudar a fixar os conceitos, vou registrar a seguir o que eu precisei fazer para criar uma biblioteca exportando a função mencionada no artigo anterior.

A biblioteca é definida em 2 arquivos obrigatórios, e mais alguns opcionais. Os arquivos obrigatórios são em C++: o de cabeçalhos (headers, extensão .h) e o de definições (o código propriamente dito, extensão .cpp). Eles podem ser editados no bloco de notas ou em qualquer editor de texto puro que você prefira.

A minha biblioteca se chama DebugLED, e por isso será gravada em uma pasta com este nome, dentro da pasta libraries, que fica dentro da pasta em que o ambiente do Arduino grava os seus programas. No meu computador, o caminho completo é /Users/augusto/Documents/Arduino/libraries/DebugLED, mas no seu isso pode variar bastante, dependendo do sistema operacional e de como foi a instalação.

Arquivo .h: a definição dos cabeçalhos

O primeiro dos arquivos essenciais é o de cabeçalhos, que vai se chamar DebugLED.h. Vou começar mostrando seu conteúdo completo:

/*
	DebugLED.h - a library to display debug information using Arduino's 
	onboard led.
	
	Pisca o led proporcionalmente a um valor analogico
	Se o parametro for negativo, pisca de forma diferenciada, corres-
	pondendo ao numero passado como parametro.
	
	(c) Augusto Campos 2014 - BR-Arduino.org
	
	Usage of the works is permitted provided that this instrument is 
	retained with the works, so that any entity that uses the works 
	is notified of this instrument. DISCLAIMER: THE WORKS ARE 
	WITHOUT WARRANTY.
*/
#ifndef DebugLED_h
#define DebugLED_h

#include "Arduino.h"

class DebugLED
{
  public:
    DebugLED(int pino);
    void sinaliza(int valorAnalogico);
  private:
    int _pino;
}

#endif

Colorizei os trechos para facilitar a explicação, mas peço desculpas se o que eu vou registrar a partir daqui fizer pouco ou nenhum sentido para os não-programadores.

O trecho em cinza, você já deve ter adivinhado, são comentários identificando a biblioteca, sua finalidade, a autoria e a licença de uso. Não existe um formato específico, mas normalmente são essas as 4 informações que constam, total ou parcialmente em inglês (mas poderia ser em qualquer idioma).

Os 2 trechos em marrom (no início e no final) são diretivas de compilação condicional, que usualmente constam nesta mesma forma (alterando apenas o nome da biblioteca) em todos os arquivos de cabeçalho. Sua finalidade é evitar problemas caso um mesmo programa tente incluir indevidamente mais de uma vez a mesma biblioteca.

A linha em vermelho é praticamente obrigatória em toda biblioteca feita para usar recursos do Arduino: ela faz com que a biblioteca possa usar os tipos e outras definições do próprio Arduino.

Finalmente, o trecho em verde é o que tem os cabeçalhos da biblioteca que estou criando. Geralmente ela segue essa mesma estrutura: cria uma classe ("class") com o nome da biblioteca e, na cláusula "public", indica o nome do construtor que será criado para a classe (em termos simples, é o nome da função que será criada para inicializar a nossa biblioteca), seguido das declarações de funções, variáveis e outras definições que serão exportadas pela biblioteca. Neste caso, tenho só a função sinaliza.

No caso da minha biblioteca, temos também uma cláusula "private", na qual eu defini uma variável _pino cujo valor deve ser mantido pelo sistema entre várias execuções da função da biblioteca, mas que não deve ser acessível globalmente. É costumeiro (mas não obrigatório) iniciar os nomes delas com um "_".

Arquivo .cpp: a lógica da biblioteca

Vamos agora à íntegra do segundo arquivo essencial, que é o que terá as definições das nossas funções (no meu caso, apenas de uma): o DebugLED.cpp. Ele tende a ser bem maior do que o de cabeçalho:


/*
	DebugLED.cpp - a library to display debug information using Arduino's 
	onboard led.
	
	Pisca o led proporcionalmente a um valor analogico
	Se o parametro for negativo, pisca de forma diferenciada, corres-
	pondendo ao numero passado como parametro.
	
	(c) Augusto Campos 2014 - BR-Arduino.org
	
	Usage of the works is permitted provided that this instrument is 
	retained with the works, so that any entity that uses the works 
	is notified of this instrument. DISCLAIMER: THE WORKS ARE 
	WITHOUT WARRANTY.
*/

#include "Arduino.h"
#include "DebugLED.h"

// Construtor, chamado na inicializacao da biblioteca/classe
DebugLED::DebugLED(int pino) {
  pinMode(pino, OUTPUT);
  _pino = pino;
}
  
// A funcao em si
void DebugLED::sinaliza(int valorAnalogico) {
  // Pisca o led proporcionalmente a um valor analogico
  // Se o parametro for negativo, pisca de forma diferenciada, corres-
  // pondendo ao numero passado como parametro.
  // Augusto Campos 2014 - BR-Arduino.org
  int piscas=0;
  if (valorAnalogico >= 0) {
    piscas=map(valorAnalogico,0,1023,1,10);
  } else {
    piscas=1+abs(valorAnalogico);
    if (piscas>8)  piscas=8;   
    // piscada longa preliminar
    for(int i=0; i<6; i++) {
      digitalWrite(_pino, HIGH);
      delay(5);
      digitalWrite(_pino, LOW);
      delay(30);
    }  
  }
  // serie de piscadas
  for(int i=0; i<piscas; i++) {
    digitalWrite(_pino, HIGH);
    delay(10);
    digitalWrite(_pino, LOW);
    delay(250);
  }
  delay(300);
}

De novo, comentando a partir das cores dos trechos.

As linhas em cinza são comentários, que são costumeiros e úteis mas não interferem na execução, nem no tamanho do executável transferido para o Arduino.

Em vermelho temos 2 diretivas de inclusão de headers que geralmente estarão presentes: os do próprio Arduino, e os da nossa biblioteca. É possível que você tenha mais headers de bibliotecas para incluir, dependendo do seu projeto.

Em marrom temos o construtor da nossa classe, que equivale à função de configuração que é chamada na sua inicialização. Note que o nosso construtor, chamado de DebugLED::DebugLED (essa repetição e o sinal de :: são usuais), tem um parâmetro chamado pino, pelo qual o programa que fizer uso da biblioteca consegue informar a ela em qual dos pinos do Arduino está conectado o led que ela deve usar (geralmente será o 13, que é o led interno de boa parte dos modelos, mas pode ser em qualquer outro pino de saída).

Finalmente, em verde temos a definição da única função da nossa biblioteca: DebugLED::sinaliza (que nossos programas chamarão pelo nome de DebugLED.sinaliza). Ela está um pouco maior e mais complexa do que a versão que vimos no artigo anterior, mas as explicações dele ainda valem: se a função receber um parâmetro positivo, irá considerar que se trata de um número na faixa entre 0 e 1023 (a faixa usada pelos pinos analógicos do Arduino) e piscar o led até 10 vezes, em um número de vezes proporcional ao parâmetro recebido. Mas se o parâmetro for negativo, a função entenderá que deve piscar diretamente aquele número de vezes indicado no parâmetro.

Um programa que faz uso da biblioteca

Após gravar a biblioteca na pasta apropriada (já mencionado acima) e sua(s) função(ões) em um programa, basta fazer o que já vimos no quadro à direita, lá no início do artigo, e que agora vou mostrar colorizadamente para poder explicar.

O programa a seguir foi feito no editor normal do ambiente de desenvolvimento do Arduino:

/*  Sensor-pisca: acompanha o valor retornado por um sensor analogico na porta 3
  
    (c)  Augusto Campos 28.11.2014 - BR-Arduino.org
    Usage of the works is permitted provided that this instrument is retained 
    with the works, so that any entity that uses the works is notified of 
    this instrument. DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. */
    
#include <DebugLED.h>
DebugLED debugLed(13);

void setup() {
}

void loop() {
  int sensor=analogRead(3);
  debugLed.sinaliza(sensor);
}

No programa acima, o trecho em cinza são comentários de identificação.

Em vermelho temos a parte crucial do uso de uma biblioteca: a linha que inclui os seus headers, e a linha que instancia a classe DebugLED, criando o objeto debugLED. Complicou? é devido a esta linha que, ao chamar a função definida na biblioteca, nós a chamamos de debugLed.sinaliza(): o objeto criado ao instanciar é o prefixo utilizado nas referências posteriores.

Ainda na linha da instanciação, há mais um detalhe importante: o parâmetro 13. Lembra de quando foi criado o construtor, no arquivo DebugLED.cpp? É este o momento em que ele é executado, e recebe como parâmetro o número do pino em que está o led interno.

A seguir, em marrom, temos a definição da função setup, obrigatória no Arduino e que, no nosso caso, está vazia: a inicialização necessária no pino do led será feita pela biblioteca, providenciamos para isso ao definir nosso construtor.

Finalmente, em verde, temos o nosso loop do Arduino. Note que ele é bem simples: grava na variável sensor o valor obtido do pino analógico 3, ao qual conectei um resistor fotossensível (detalhes no artigo anterior), e em seguida passa essa mesma variável como parâmetro para a função debugLed.sinaliza, definida na nossa biblioteca.

Na prática, o que o loop acima realiza é piscar o led interno, proporcionalmente à iluminação do ambiente. Se eu o coloco o sensor diretamente sob a luz do sol, ele pisca 8 a 9 vezes. Com a luz fluorescente do meu escritório, pisca 4 vezes e, no escuro quase completo, pisca apenas uma vez.

Em complemento aos arquivos acima, você pode ter arquivos opcionais, como os de exemplos de uso da sua biblioteca. Entre os arquivos opcionais, um merece destaque: o arquivo keywords.txt, que contém uma lista das classes e funções definidas na biblioteca, para que elas apareçam em destaque nos seus programas no editor do Arduino. Se tiver interesse, o formato dele é bem simples e pode ser consultado no tutorial.

Compilando o programa que faz uso da biblioteca

A versão curta: compilar e transferir para o Arduino o programa que faz uso da sua biblioteca usa exatamente o mesmo procedimento com o qual você já compila e transfere outros programas.

A versão longa: é neste momento que a sua biblioteca será compilada, também. Fique atento às mensagens de erro da compilação, porque elas podem fazer referência tanto ao seu programa quanto ao código da biblioteca.

Lembre-se de rolar para cima o campo das mensagens de erro, pois erros em bibliotecas muitas vezes geram erros também no programa, e geralmente estes são exibidos por último, escondendo as mensagens sobre os erros na biblioteca.

 
  1.  E aqui não é o lugar de falar em temas como coesão, acoplamento, orientação a objetos, etc.

Comentar

Dos leds ao Arduino, ESP8266 e mais

Aprenda eletrônica com as experiências de um geek veterano dos bits e bytes que nunca tinha soldado um led na vida, e resolveu narrar para você o que descobre enquanto explora esse universo – a partir da eletrônica básica, até chegar aos circuitos modernos.

Por Augusto Campos, autor do BR-Linux e Efetividade.net.

Recomendados

Livro recomendado


Artigos já disponíveis

Comunidade Arduino

O BR-Arduino é integrante da comunidade internacional de entusiastas do Arduino, mas não tem relação com os criadores e distribuidores do produto, nem com os detentores das marcas registradas.

Livros recomendados