Biblioteca Arduino: aprenda como criar a sua – Parte 1 4

Você que já é fã de Arduino e já desenvolveu vários projetos principalmente usando módulos ou sensores como MPU6050, DHT11, BPM180 e etc, deve ter notado aquelas linhas no topo do código que dizem #include <MPU6050.h> ou #include <DHT.h>. Esses arquivos são chamados de bibliotecas e neste post iremos mostrar o que são as bibliotecas, para que servem, porque são usadas e como desenvolver sua própria biblioteca Arduino.

Como exemplo para este tutorial, iremos desenvolver uma biblioteca para utilização do driver de motor NodeMCU     

Driver de motor

O que é e para que serve uma biblioteca Arduino

Uma biblioteca nada mais é do que um conjunto de instruções desenvolvidas para executar tarefas específicas relacionadas a um determinado dispositivo. Por exemplo, pensando em um acelerômetro MPU6050 e sua biblioteca MPU6050.h. Essa biblioteca tem funções desenvolvidas especificamente para executar tarefas como, configurar o acelerômetro, ler dados de aceleração, giroscópio, temperatura e etc.

A utilização de uma biblioteca facilita o desenvolvimento, tornando o código mais simples e organizado. Por exemplo, a biblioteca do MPU6050 tem mais ou menos 4000 linhas de código (sem contar outras bibliotecas utilizadas dentro dela). É obrigatório o uso dessa biblioteca? Não. Mas então teríamos que escrever, se não as 4000 linhas de código, várias linhas para utilizar o MPU6050 em todo seu potencial.

Bibliotecas podem ser desenvolvidas por qualquer pessoa. Podemos ver muitas bibliotecas da Adafruit e Sparkfun, dentre as outras milhares espalhadas pelo GitHub. Podemos encontrar bibliotecas ótimas, ruins e até mesmo algumas que não funcionam. Uma dica interessante ao avaliar uma biblioteca é olhar o número de estrelas e issues no GitHub e ver também se ela está disponível oficialmente na IDE Arduino.

E foram as bibliotecas que ajudaram a tornar o Arduino tão popular, onde crianças e adultos não precisam ser engenheiros ou desenvolvedores de software, para criarem seus projetos e colocarem sua criatividade em prática. O próprio “jeitão” de escrever código para Arduino segue o princípio da facilidade. Por exemplo a função digitalWrite(pino, valor). Com apenas essa função ligamos ou desligamos um pino do Arduino. Mas veja como ela é por dentro e o que teríamos que escrever caso não usássemos as bibliotecas Arduino:

void digitalWrite(uint8_t pin, uint8_t val)
{
	uint8_t timer = digitalPinToTimer(pin);
	uint8_t bit = digitalPinToBitMask(pin);
	uint8_t port = digitalPinToPort(pin);
	volatile uint8_t *out;

	if (port == NOT_A_PIN) return;

	// If the pin that support PWM output, we need to turn it off
	// before doing a digital write.
	if (timer != NOT_ON_TIMER) turnOffPWM(timer);

	out = portOutputRegister(port);

	uint8_t oldSREG = SREG;
	cli();

	if (val == LOW) {
		*out &= ~bit;
	} else {
		*out |= bit;
	}

	SREG = oldSREG;
}

Explore o seguinte link e descubra a origem das várias outras funções que facilitam o desenvolvimento com Arduino como setup(), loop(), Serial.begin(), Serial.available(), Serial.println(), digitalRead(), delay(), etc.

https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/cores/arduino

Componentes de uma biblioteca Arduino

Uma biblioteca Arduino consiste basicamente de 3 componentes/arquivos. São eles:

  • Arquivo header(cabeçalho) com extensão “.h” que contém uma listagem (declaração) de todas as funções e variáveis da nossa biblioteca.
  • Arquivo source(fonte) com extensão  “.cpp” que contém o corpo e funcionamento/lógica (definição) das funções.
  • Arquivo keywords.txt que é específico da IDE Arduino e usado para dar coloração as funções da biblioteca enquanto se escreve um código.

Mais a frente no tutorial iremos mostrar como construir cada um desses arquivos já com foco no driver de motor para NodeMCU.

Driver de motor para NodeMCU

Antes de começarmos a desenvolver uma biblioteca, precisamos de uma razão para tal e entender o funcionamento do dispositivo para qual escreveremos a biblioteca.

Para este tutorial escolhemos o driver de motor para NodeMCU. Esse driver é baseado no CI L293D, controla até dois motores além de expor vários pinos do NodeMCU como pinos digitais, UART e SPI. Podemos também utilizar uma fonte separada para os motores. Veja na figura abaixo as indicações de cada parte do driver:

Note que temos as saídas para motor A-, A+, B-, B+. As saídas A- e B- são utilizadas para configurar a força com que os motores irão girar. Para isso utilizaremos PWM. Já as saídas A+ e B+, configuram o sentido de rotação dos motores.

Quando o NodeMCU é encaixado no driver os pinos ficam conectados da seguinte maneira:Biblioteca Arduino

Lembre-se desses pinos pois serão utilizados no desenvolvimento da nossa biblioteca.

O funcionamento do driver é bem simples. Para controlar um motor basta conectar seus terminais nos pinos A- e A+. No pino A- utilizamos PWM para configurar a força do motor e no pino A+ o sentido. Então se utilizarmos 10% de força no pino A- e utilizarmos nível 1 na saída A+, o motor gira para um lado com força de 10%. Se utilizarmos força de 100% no pino A- e utilizarmos nível 1 na saída A+, o pino gira com força total para um lado. Se utilizarmos nível 0 na saída A+, o motor gira para o lado contrário.

Colocando a ideia acima em forma de programação Arduino, teríamos algo parecido com o seguinte, fazendo o driver ir para frente por 2 segundos e depois para trás por 2 segundos:

void setup() {
    pinMode(5, OUTPUT); // saída A-
    pinMode(0, OUTPUT); // saída A+
    pinMode(4, OUTPUT); // saída B-
    pinMode(2, OUTPUT); // saída B+
}

void loop() {
    analogWrite(5, 1024); // PWM no pino A-
    digitalWrite(0, HIGH); // sentido no pino A+
    analogWrite(4, 1024); // PWM no pino B-
    digitalWrite(2, HIGH); // sentido no pino B+
    delay(2000);

    analogWrite(5, 1024); // PWM no pino A-
    digitalWrite(0, LOW); // sentido no pino A+
    analogWrite(4, 1024); // PWM no pino A-
    digitalWrite(2, LOW); // sentido no pino B+
    delay(2000);	
}

Agora que sabemos o funcionamento do nosso dispositivo (driver motor NodeMCU), já podemos seguir com o desenvolvimento da biblioteca.

Pensando nas funcionalidades da nossa biblioteca arduino

Vamos pensar que nossa biblioteca irá precisar atender 5 tarefas/comandos do driver de motor, considerando que temos 2 motores conectados.

  1. ir para frente
  2. ir para trás
  3. virar a esquerda
  4. virar a direita
  5. parar

Cada uma dessas tarefas será uma função da nossa biblioteca.

Também daremos ao usuário da nossa biblioteca a opção de realizar os comandos acima com uma determinada força(PWM). Isso será nosso parâmetro de função.

Assim teremos 4 funções que levam parâmetro mais a função de parada.

goForward(uint16_t pwm)
goBackward(uint16_t pwm)
turnRight(uint16_t pwm)
turnLeft(uint16_t pwm)
stop()

Construindo nossa biblioteca arduino

Arquivo header

Nossa biblioteca terá o nome de NodeMotorDriver, portanto nosso arquivo header terá nome NodeMotorDriver.h

Dentro desse arquivo teremos uma classe com nome de nossa biblioteca. Dentro da classe teremos nossas funções e variáveis que serão utilizadas.

class NodeMotorDriver
{
	public:
		NodeMotorDriver(uint8_t pinMotorA1, uint8_t pinMotorA2, uint8_t pinMotorB1, uint8_t pinMotorB2);
		void goForward(uint16_t pwm);
		void goBackward(uint16_t pwm);
		void turnRight(uint16_t pwm);
		void turnLeft(uint16_t pwm);
		void stop();	

	private:
		uint8_t m_pinA1;
		uint8_t m_pinA2;
		uint8_t m_pinB1;
		uint8_t m_pinB2;
};

Veja que temos uma função ali que não estava em nosso planos:

NodeMotorDriver(uint8_t pinMotorA1, uint8_t pinMotorA2, uint8_t pinMotorB1, uint8_t pinMotorB2);

Essa função chama-se construtor(constructor) e será executada sempre quando criamos um novo objeto da classe NodeMotorDriver. É útil para configuração de valores iniciais para variáveis membros da classe. Geralmente irá conter código que iria na função setup() do Arduino. Mais a frente mostraremos a definição dessa função construtora.  

Note também que temos as palavras public e private. Public significa que as funções e variáveis estarão acessíveis para o usuário da biblioteca. Private restringe o uso de funções e bibliotecas para apenas aquela classe em específico. Dependendo do tipo de variável não há necessidade do usuário escrever um valor diferente naquela variável, então utilizamos como private. Já as funções e variáveis públicas estarão disponíveis para o usuário chamá-las ao decorrer do programa.

Ainda no arquivo header precisamos incluir outra biblioteca que é a Arduino.h. Incluir essa biblioteca nos possibilita utilizar funções como pinMode(), digitalWrite(), etc. que são funções do Arduino.

#include <Arduino.h>

E finalmente envolvemos o header em uma guarda de inclusão, que evita problemas de compilação caso o usuário inclua nossa biblioteca duas vezes.

#ifndef NodeMotorDriver_h
#define NodeMotorDriver_h

// código da classe aqui

#endif

Veja abaixo o código completo do header:

/*
  NodeMotorDriver.h - Library for use with NodeMCU L293D driver board
  MIT License
*/

// guarda de inclusão
#ifndef NodeMotorDriver_h
#define NodeMotorDriver_h

#include <Arduino.h>

class NodeMotorDriver
{
	public:
		NodeMotorDriver(uint8_t pinMotorA1, uint8_t pinMotorA2, uint8_t pinMotorB1, uint8_t pinMotorB2);
		void goForward(uint16_t pwm);
		void goBackward(uint16_t pwm);
		void turnRight(uint16_t pwm);
		void turnLeft(uint16_t pwm);
		void stop();	

	private:
		uint8_t m_pinA1;
		uint8_t m_pinA2;
		uint8_t m_pinB1;
		uint8_t m_pinB2;
};

#endif

Arquivo source

No arquivo source é onde definimos a lógica de nossas funções e utilizamos as variáveis declaradas no arquivo header. Terá o mesmo nome da biblioteca com terminação .cpp.

Começando pela função construtora podemos ver a configuração do modo dos pinos como OUTPUT e atribuição das variáveis membro da classe.

NodeMotorDriver::NodeMotorDriver(uint8_t pinMotorA1, uint8_t pinMotorA2, uint8_t pinMotorB1, uint8_t pinMotorB2)
{
	pinMode(pinMotorA1, OUTPUT);
	pinMode(pinMotorA2, OUTPUT);
	pinMode(pinMotorB1, OUTPUT);
	pinMode(pinMotorB2, OUTPUT);
	m_pinA1 = pinMotorA1; // speed
	m_pinA2 = pinMotorA2; // direction
	m_pinB1 = pinMotorB1; // speed
	m_pinB2 = pinMotorB2; // direction
}

Então definimos as funções de movimento.

void NodeMotorDriver::goForward(uint16_t pwm)
{
	analogWrite(m_pinA1, pwm);
	digitalWrite(m_pinA2, HIGH);
	analogWrite(m_pinB1, pwm);
	digitalWrite(m_pinB2, HIGH); 
}

Note que o valor de PWM será passado pelo usuário como um parâmetro na chamada de função e os valores das variáveis membro já foram atribuídos pela função construtora. As outras funções de movimento seguem a mesma lógica apenas alternando entre HIGH e LOW.

Por último temos a função stop que desliga todos os motores.

void NodeMotorDriver::stop()
{
	analogWrite(m_pinA1, 0);
	digitalWrite(m_pinA2, 0);
	analogWrite(m_pinB1, 0);
	digitalWrite(m_pinB2, 0);
}

Não podemos esquecer também de incluir nosso arquivo header onde fizemos as declarações de função.

#include "NodeMotorDriver.h"

O arquivo source completo pode ser visto abaixo:

/*
  NodeMotorDriver.cpp - Library for use with NodeMCU L293D driver board
  MIT License
*/

#include "NodeMotorDriver.h"

NodeMotorDriver::NodeMotorDriver(uint8_t pinMotorA1, uint8_t pinMotorA2, uint8_t pinMotorB1, uint8_t pinMotorB2)
{
	pinMode(pinMotorA1, OUTPUT);
	pinMode(pinMotorA2, OUTPUT);
	pinMode(pinMotorB1, OUTPUT);
	pinMode(pinMotorB2, OUTPUT);
	m_pinA1 = pinMotorA1; // speed
	m_pinA2 = pinMotorA2; // direction
	m_pinB1 = pinMotorB1; // speed
	m_pinB2 = pinMotorB2; // direction
}

void NodeMotorDriver::goForward(uint16_t pwm)
{
	analogWrite(m_pinA1, pwm);
	digitalWrite(m_pinA2, HIGH);
	analogWrite(m_pinB1, pwm);
	digitalWrite(m_pinB2, HIGH); 
}

void NodeMotorDriver::goBackward(uint16_t pwm)
{
	analogWrite(m_pinA1, pwm);
	digitalWrite(m_pinA2, LOW);
	analogWrite(m_pinB1, pwm);
	digitalWrite(m_pinB2, LOW);
}

void NodeMotorDriver::turnRight(uint16_t pwm)
{
	analogWrite(m_pinA1, pwm);
	digitalWrite(m_pinA2, HIGH);
	analogWrite(m_pinB1, pwm);
	digitalWrite(m_pinB2, LOW);
}

void NodeMotorDriver::turnLeft(uint16_t pwm)
{
	analogWrite(m_pinA1, pwm);
	digitalWrite(m_pinA2, LOW);
	analogWrite(m_pinB1, pwm);
	digitalWrite(m_pinB2, HIGH);
}

void NodeMotorDriver::stop()
{
	analogWrite(m_pinA1, 0);
	digitalWrite(m_pinA2, 0);
	analogWrite(m_pinB1, 0);
	digitalWrite(m_pinB2, 0);
}

Arquivo keywords.txt

Esse arquivo não é obrigatório mas é interessante para a experiência do usuário que irá utilizar nossa biblioteca. Com ele podemos definir quais funções terão uma coloração diferente quando se escreve código.

# Arduino IDE Keywords for Syntax Coloring

# Keyword for class NodeMotorDriver
NodeMotorDriver	KEYWORD1

# Keyword for class functions
goForward	KEYWORD2
goBackward	KEYWORD2
turnRight	KEYWORD2
turnLeft	KEYWORD2
stop		KEYWORD2

Nome de classe deve utilizar KEYWORD1 e terá coloração laranja negrito. Nome de função deve utilizar KEYWORD2 e terá coloração laranja.

Utilização da biblioteca

Pronto agora temos toda a biblioteca construída. Para utilizá-la devemos colocar os 3 arquivos dentro de uma pasta com o nome da biblioteca NodeMotorDriver, e copiar para a pasta libraries da IDE Arduino.

Então basta incluir a biblioteca no código e utilizar as funções. Veja abaixo um pequeno exemplo de código utilizando nossa biblioteca.

#include <NodeMotorDriver.h>

NodeMotorDriver nodeMotorDriver(5, 0, 4, 2);

void setup() {
    
}

void loop() {
  nodeMotorDriver.goForward(1024);
  delay(1000);
  nodeMotorDriver.goBackward(500);
  delay(1000);
  nodeMotorDriver.turnRight(1024);
  delay(1000);
  nodeMotorDriver.turnLeft(500);
  delay(1000);
  nodeMotorDriver.stop();
  delay(1000);
}

Primeiro utilizamos nossa função construtora definindo um nome de objeto da classe e os pinos de saída dos motores. Na função loop apenas chamamos as funções de movimento passando a força do movimento(PWM) como parâmetro de função.

No próximo post iremos mostrar como utilizar essa biblioteca arduino em um projeto real de robô com NodeMCU e driver de motor controlado pelo celular. Fique ligado!

Veja também uma explicação em vídeo sobre criação de biblioteca Arduino no canal WRKits:

Referências

https://www.arduino.cc/en/Hacking/LibraryTutorial
https://www.arduino.cc/en/Reference/APIStyleGuide
https://playground.arduino.cc/Code/Library
https://github.com/kecsot/Arduino-Motor-H-Bridge-L293d/tree/master/Motor
https://github.com/kecsot/Arduino-Motor-H-Bridge-L293d/blob/master/Motor/Motor.cpp
https://github.com/kecsot/Arduino-Motor-H-Bridge-L293d/blob/master/Motor/Motor.h

Gostou de aprender como desenvolver uma biblioteca arduino? Ajude-nos a melhorar o blog comentando abaixo sobre este tutorial. Se tiver dúvidas ou quiser compartilhar seu projeto com outros makers não deixe de visitar nosso Fórum!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

4 Comentários

  1. Muito bom aprendi muito estou sempre acompanhado as postagens

    LUCAS GONCALVES OLIVEIRA
    1. Olá Lucas!

      Que bom que o post foi útil para você!

      Não deixe de conferir a parte 2 desse post onde exploramos um pouco mais sobre bibliotecas Arduino na prática: https://www.filipeflop.com/blog/robo-com-nodemcu-controlado-via-celular/

      Abraço!

  2. Qual IDE você utilizou para montar a biblioteca?

    Isaac da Silva Barbosa Miranda
    1. Olá Isaac!

      Eu usei um editor de texto. O Gedit do Linux.

      Mas você poderia usar a própria IDE Arduino, usando a opção no menu Sketch->Adicionar arquivo.

      Como é um projeto pouco complexo, o editor de texto já me serviu.