Por trás da tecnologia

Criando uma Biblioteca para Arduino – Parte 2

Eletrogate 4 de maio de 2023

Introdução

Nesse post, vamos continuar o desenvolvimento da nossa biblioteca para LCDs alfanuméricos com Arduino. Antes de iniciar a leitura, é importante ter lido a primeira parte, as apostilas da Eletrogate e ter conhecimento da linguagem C++.

(Esse post continua a criação da biblioteca “omincrystal”. Caso esteja buscando exemplos relacionados à configuração/publicação de uma biblioteca, sinta-se livre para pular para o tópico: “Configurando library.properties”)

Recapitulando

No último post, falamos sobre:

  • Dependências e como você deve analisá-las antes de incluir no projeto.
  • Planejamento para um bom desenvolvimento do projeto.
  • Estrutura do projeto.
  • Detalhes de implementação da classe principal com base no datasheet do LCD.

Por fim, criamos um .zip do código e adicionamos ao Arduino IDE pelo gerenciador de bibliotecas. Isso já é o suficiente para a criação de uma biblioteca de uso pessoal. Agora, vamos criar novas funcionalidades nesse projeto e configurar corretamente para compartilharmos com o público.


Materiais Necessários para o Projeto Criando uma Biblioteca para Arduino

Materiais:

Nosso objetivo é criar uma biblioteca capaz de rodar em todas as placas da plataforma Arduino. Você pode usar uma placa de sua preferência. Neste post, vamos usar Arduino Uno R3; a placa NodeMCU ESP-12E é apenas para realizar os testes de compatibilidade antes da publicação.

cta_cart

Circuito:

o circuito é igual ao do último post:

Acompanhe o desenvolvimento em: https://github.com/RecursiveError/omnicrystal


Atualizando a Biblioteca

No último post, adicionamos a biblioteca .zip a IDE. Mas, para enviar uma nova versão dessa mesma biblioteca por esse meio, vamos ter o seguinte erro:

Como o erro diz, a biblioteca já está instalada, porém, em uma versão diferente. Então, para atualizarmos, temos que, manualmente, apagar a biblioteca já instalada e, depois, enviar o novo .zip. Para isso, vá até Arquivo → preferências:

No campo “localização dos sketchbooks”, vai ter o caminho para a pastas onde seus sketches estão salvos. As bibliotecas instaladas pelo usuário também são salvas nessa pasta.

Vá até essa pasta e você vai encontrar uma pasta chamada “libraries”:

abra essa pasta e você vai achar suas bibliotecas instaladas e uma pasta nomeada “_”. Essa pasta é a nossa biblioteca; está com esse nome porque library.properties ainda não foi configurado

apague esse pasta e pronto: basta enviar um novo .zip para instalar, como mostra no final da primeira parte.

(caso essa pasta não exista, tente atualizar a IDE para uma versão mais recente. Caso atualização não seja possível, verifique no site da Arduino pela localização de bibliotecas para versões antigas da IDE)


Finalizando a Classe Principal

Atualmente, nosso projeto só é capaz de enviar texto, o que não é muito útil para quem precisa usar um LCD. Precisamos adicionar funções para:

  • Envio de vários tipos de dados
  • Envio de comandos
  • Configurar o LCD
  • Criar e enviar caracteres customizados

vamos criar cada e testar essas funcionalidades uma a uma, seguindo essa ordem.

“Print.h” e formatação de texto

Começando pela criação de uma função capaz de enviar vários tipos de dados para o LCD, temos que criar uma versão da mesma função para cada tipo de dado diferente, como int, float, bool, byte….etc, e transformar esses dados em caracteres para enviarmos a o display. Isso é um trabalho muito complexo e repetitivo. Foi pensando nisso que a Arduino criou a biblioteca “Print.h” no seu Core. Você já deve ter visto os métodos “print” e “println” em algum lugar, como, por exemplo, “Serial.println()”. Essas são as funções da “Print.h” e já fazem todo trabalho de receber e formatar vários tipos de dados. Para implementar elas no seu código, é bem simples: primeiro, você herda de forma publica a classe Print e cria um método virtual publico write: virtual size_t write(uint8_t);

(devido ao tamanho do código vamos abreviar as partes já criadas com “. . .” para facilitar a leitura das adições. O código completo vai ser disponibilizado ao final de cada etapa)

omnicrystal.h:

//classe principal
#ifndef OMNICRYSTAL_H
#define OMNICRYSTAL_H
...
class Omnicrystal : public Print{
    private:
    ...
    public:
        ...
        virtual size_t write(uint8_t); //adição da função virtual write na nossa classe principal
};

#endif

omnicrystal.cpp

#include <omnicrystal.h>

...
// implementação da função virtual write
size_t Omnicrystal::write(uint8_t data){
    send(data, 1);
    return 1;
}

O retorno size_t da função write indica se a função executou com sucesso. Como nosso LCD não verifica erros, sempre retornamos 1, ou seja, sempre assume sucesso na execução.

Agora, vamos fazer o processo para atualizar a biblioteca que vimos anteriormente. Vá até a pasta do projeto; compacte em .zip, como foi mostrado na parte 1, vá até libraries; apague manualmente e instale o novo zip.

(Download da versão dessa etapa: https://github.com/RecursiveError/omnicrystal/releases/download/v0.2.0-alpha/omnicrystal.zip)

Testando na Arduino Uno:

#include <omnicrystal.h>

LCDParallel LcdInter(6,7,2,3,4,5,8,9,10,11); // interface paralela usada
Omnicrystal lcd(LcdInter, Bus4Bits, 2, 16);

void setup() {
  lcd.begin(); // inicia o display

  lcd.print("NUM:");
  lcd.print(255); // envia o numero 255
  lcd.print(" HEX:");
  lcd.print(255, HEX); //envia o numero 255 em HEXADECIMAL
  
}

void loop() {}

Resultado:

Nossa biblioteca, agora, é capaz de enviar vários tipos de dados para o LCD. A próxima etapa é enviar comandos para o LCD.

Comandos e Configuração:

Como vimos na tabela de comandos de LCD, existem certos comandos que alteram o comportamento do LCD e comandos que realizam operações sem alterar o comportamento

(https://www.futurlec.com/LED/LCD16X2BLa.shtml)

Para facilitar, vamos dividir esses comandos em dois grupos: “command”, que realizam operações que não afetam o comportamento do LCD (Exemplo: limpar o LCD) e “config”, que altera o comportamento do LCD (Exemplo: ligar e desligar o curso).

Comandos:

Começando com o command, no nosso header (.h), vamos criar uma enum para listar todos os possíveis comandos e funções para enviar cada um dos comandos:

omnicrystal.h

//classe principal
#ifndef OMNICRYSTAL_H
#define OMNICRYSTAL_H
...

//comandos do LCD
enum LCDCommand {
    LCDClear = 0x01,
    LCDReset = 0x02,
    LCDShiftCursotLeft = 0x10,
    LCDShiftCursotRight = 0x14,
    LCDShiftDisplayLeft = 0x18,
    LCDShiftDisplayRight = 0x1C,
};


class Omnicrystal : public Print{
    private:
       ...
    public:
        ...
        Omnicrystal& command(LCDCommand);
        //funçoes para enviar cada um dos comandos
        Omnicrystal& clear();
        Omnicrystal& reset();
        Omnicrystal& move_cursor_left();
        Omnicrystal& move_cursor_right();
        Omnicrystal& move_display_left();
        Omnicrystal& move_display_right();
        ...
};

#endif

omnicrystal.cpp

...
/* ----------------- COMANDOS DO DISPLAY ---------------------*/
//envia comandos para o display
inline Omnicrystal& Omnicrystal::command(LCDCommand command){
    send(command, 0); //0 indica RS em low, significa que vamos envar um comando
    return *this; //apenas para encadeamento de metodos
}

Omnicrystal& Omnicrystal::clear(){
    command(LCDClear);
    delay(2); //clear e reset demoram 1.52ms para executar
    return *this;
}

Omnicrystal& Omnicrystal::reset(){
    command(LCDReset);
    delay(2); //clear e reset demoram 1.52ms para executar
    return *this;
}

inline Omnicrystal& Omnicrystal::move_cursor_left(){
    command(LCDShiftCursotLeft);
    return *this;
}

inline Omnicrystal& Omnicrystal::move_cursor_right(){
    command(LCDShiftCursotRight);
    return *this;
}

inline Omnicrystal& Omnicrystal::move_display_left(){
    command(LCDShiftDisplayLeft);
    return *this;
}

inline Omnicrystal& Omnicrystal::move_display_right(){
    command(LCDShiftCursotRight);
    return *this;
}

/* ----------------- FIM DOS COMANDOS DO DISPLAY ---------------------*/

a keyword “inline” usada é para sugerir para o compilador incorporar a função no código em vez de chama-la. Um exemplo: veja que a única coisa que a função “move_display_right()” faz chamar é função “commad()”, que a única coisa que faz é chamar “send()”. Usando a keyword “inline”, estamos dizendo para o compilador que ele pode optar por não criar uma chamada de função, e executar o conteúdo dela como se fosse parte daquela linha de código. Ou seja, quando adicionamos o “inline” e chamamos a função “move_display_right()”, ele pode otimizar e simplesmente chamar send(valor), sem chamar “move_display_right()” e “command()”.  Isso permite criar abstrações para facilitar o uso da nossa biblioteca sem perder desempenho.

( “inline” é apenas uma sugestão para o compilador. Seu uso incorreto pode aumentar drasticamente o consumo de memoria)

Falta apenas um comando: o comando para mover o cursor na coluna e linha. Mas, vamos deixar esse comando para depois de criar as funções para configuração, pois é nessas funções que vamos definir quantas linhas tem o LCD.

Vamos testar nosso código. Mesmo processo feito anteriormente: apagar manualmente e enviar o novo .zip

(Download da versão dessa etapa: https://github.com/RecursiveError/omnicrystal/releases/download/v0.3.0-alpha/omnicrystal.zip)

#include <omnicrystal.h>

LCDParallel LcdInter(6,7,8,9,10,11); // interface paralela usada
Omnicrystal lcd(LcdInter, Bus4Bits, 2, 16);

int num = 0;

void setup() {
  lcd.begin(); // inicia o display
  lcd.print("NUM:");
  lcd.print(255); // envia o numero 255
  lcd.print(" HEX:");
  lcd.print(255, HEX); //envia o numero 255 em HEXADECIMAL
}

void loop() {
  delay(1000);
  lcd.clear().reset().print("comando teste");
  delay(1000);
  lcd.clear().reset().print(num);
  num++;
  delay(1000);
}

Resultado:

Configuração:

Agora, vamos criar funções para configurar o LCD. Temos 3 tipos de configurações: “Entry mode”, que configura a direção de escrita do LCD; “Display control”, que configura exibição, e “Function Set”, que configura a interface de comunicação, quantidade de linhas e tamanho da fonte.

Como essas configurações mudam o comportamento do LCD, temos que guardar seu estado em alguma lugar. Então, vamos criar atributos e enums para cada tipo de configuração e, claro, funções para enviar essas configurações

omnicrystal.h

//classe principal
#ifndef OMNICRYSTAL_H
#define OMNICRYSTAL_H

...

enum BusType {
    Bus4Bits,
    Bus8Bits = 0x10,
};

//comandos do LCD
enum LCDCommand {
   ...
};


enum LCDEntryMode{
    LCDShiftMode = 0x01,
    LCDDirection = 0x02,
};

enum LCDDisplayControl{
    LCDBlink = 0x01,
    LCDCursor = 0x02,
    LCDDisplay = 0x04,

};

enum LCDFunctionSet{
    LCDChar = 0x04,
    LCDLines = 0x08,
    LCDBits = 0x10,
};

enum LCDCharSize {
    Char5x8,
    Char5x10 = 0x04,
};

class Omnicrystal : public Print{
    private:
        ...
        // variaveis de configuração inicializadas com a configuração padrão
        uint8_t entry_mode{0x06}; // shift Off, texto esquerda pra direita
        uint8_t display_control{0x0C}; //display ligado, cursor desligado, cursor piscando desligado
        uint8_t function_set{0}; // configurado no construtor
        ...
    public:
        Omnicrystal(LCDInterface &bridge, const BusType bus, uint8_t line, uint8_t col, LCDCharSize size = Char5x8) : _bridge{bridge}, _bus{bus},
            _line{line}, _col{col}{
            if(_line >= 2){
                function_set = 0x20 | 0x08 | _bus | size;
            }else{
                function_set = 0x20 | _bus | size;
            }
        }

        ...
        //funçoes para enviar configuração
        Omnicrystal& shift_on();
        Omnicrystal& shift_off();
        Omnicrystal& increment();
        Omnicrystal& decrement();
        Omnicrystal& cursor_blink_on();
        Omnicrystal& cursor_blink_off();
        Omnicrystal& cursor_on();
        Omnicrystal& cursor_off();
        Omnicrystal& display_on();
        Omnicrystal& display_off();
        ...
};

#endif

omnicrystal.cpp

...
/* ------------------ CONFIGURAÇÃO DO DISPLAY ------------------------*/

/*
essas funçoes recebem a posição em bit da flag na enum de cada configuração
liga os bits de acordo com a configuração, "on" coloca o bit em 1, "off" coloca o bit wm 0
*/

//configura o autoshift
Omnicrystal& Omnicrystal::shift_on(){
    entry_mode |= LCDShiftMode;
    send(entry_mode, 0);
    return *this;
}

Omnicrystal& Omnicrystal::shift_off(){
    entry_mode &= ~LCDShiftMode;
    send(entry_mode, 0);
    return *this;
}

//diz se ele aumenta ou diminue a posição apos cada letra
Omnicrystal& Omnicrystal::increment(){
    entry_mode |= LCDDirection;
    send(entry_mode,0);
    return *this;
}

Omnicrystal& Omnicrystal::decrement(){
    entry_mode &= ~LCDDirection;
    send(entry_mode,0);
    return *this;
}

//liga e desliga se o cursor piscando
Omnicrystal& Omnicrystal::cursor_blink_on(){
    display_control |= LCDBlink;
    send(display_control,0);
    return *this;
}

Omnicrystal& Omnicrystal::cursor_blink_off(){
    display_control &= ~LCDBlink;
    send(display_control,0);
    return *this;
}

//liga e desliga o cursor
Omnicrystal& Omnicrystal::cursor_on(){
    display_control |= LCDCursor;
    send(display_control,0);
    return *this;
}

Omnicrystal& Omnicrystal::cursor_off(){
    display_control &= ~LCDCursor;
    send(display_control,0);
    return *this;
}

//liga e desliga a exibição
Omnicrystal& Omnicrystal::display_on(){
    display_control |= LCDDisplay;
    send(display_control,0);
    return *this;
}

Omnicrystal& Omnicrystal::display_off(){
    display_control &= ~LCDDisplay;
    send(display_control,0);
    return *this;
}
/*------------------- FIM CONFIGURAÇÃO DO DISPLAY ---------------------*/

Agora, podemos adicionar nossas mudanças nas funções já existentes da classe, como a função “begin”, e, também, criar a função para selecionar a posição da linha/coluna.

omnicrystal.cpp

...

Omnicrystal& Omnicrystal::begin(){
    delay(50);//aguarda o LCD iniciar
    send8Bits(0x30, 0);
    delayMicroseconds(4100);
    send8Bits(0x30, 0);
    delayMicroseconds(100);
    send8Bits(0x30, 0);
    delayMicroseconds(100);

    //configura o modo de comunicação corretamente
    if(_bus == Bus4Bits){
        send8Bits(0x20, 0);
    }

    //envias as configuraçoes padroes do LCD
    send(function_set, 0);
    clear();
    reset();
    send(entry_mode, 0);
    send(display_control, 0);
    return *this;
}

...

Para a função de selecionar a posição, temos quer entender um pequeno detalhe sobre o Hitachi HD44780. Na tabela de comandos, podemos ver que a flag DL que é responsável pela quantidade de linhas e é representada por apenas 1 bits, o que significa que o controlador só pode ser configurado com 1 linha (DL = 0) e duas linhas (DL = 1). Então, como podem existir LCDs com mais de 2 linhas, como o 20×4? O que acontece é que a linha pode ter um tamanho máximo de 40 caracteres. Então, eles são reorganizados para fazer parecer que existem mais de 2 linhas:

Como podemos ver, para acessar a linha 3 de um LCD 20×4, estamos, na verdade, acessando a posição 20 da linha 1. Então, vamos salvar a posição de cada linha na nossa classe principal e criar a função para selecionar a linha.

omnicrystal.h

//classe principal
#ifndef OMNICRYSTAL_H
#define OMNICRYSTAL_H
...

class Omnicrystal : public Print{
    private:
        const uint8_t addrs[4] = {0x80, 0xC0, 0x80+20, 0xC0+20}; //endereços para LCD 16x2 e 20x4
        ...
    public:
        ...
        Omnicrystal& set_cursor(uint8_t line, uint8_t col);
        ...
};

omnicrystal.cpp

//muda a posição do cursor
Omnicrystal& Omnicrystal::set_cursor(uint8_t line, uint8_t col){
    if(line <= _line){
        if(col <= _col){
            send(addrs[line]+col, 0);
        }
    }
    return *this;
}

Vamos testar agora, apagando manualmente da IDE e enviando o novo .zip

(Download da versão dessa etapa: https://github.com/RecursiveError/omnicrystal/releases/download/v0.4.0-alpha/omnicrystal.zip)

#include <omnicrystal.h>

LCDParallel LcdInter(6,7,8,9,10,11); // interface paralela usada
Omnicrystal lcd(LcdInter, Bus4Bits, 2, 16);

void print_teste();
void shift_on_teste();
void display_off_teste();
void cursor_on_teste();

void setup() {
  lcd.begin(); // inicia o display
  lcd.clear().reset().print("comandos testes");
  delay(1000);
}

void loop(){
  print_teste();
  shift_on_teste();
  display_off_teste();
  cursor_on_teste();
}

//testa se é capaz de enviar diferentes tipos de dados pelo LCD
void print_teste(){
  lcd.reset().clear().print("Print teste");
  delay(1000);
  lcd.reset().clear().print("NUM:");
  lcd.print(255); // envia o numero 255
  lcd.set_cursor(1, 0).print("HEX:");
  lcd.print(255, HEX); //envia o numero 255 em HEXADECIMAL
  delay(1000);
}

//testa o modo de autoshift do LCD
void shift_on_teste(){
  lcd.reset().clear().print("shift on teste");
  lcd.set_cursor(1, 0).shift_on().decrement();
  for(int i = 0; i < 11; i++){
    lcd.print(i);
    delay(1000);
  }
  lcd.shift_off();
}

//testa a exebição do display
void display_off_teste(){
  lcd.reset().clear().print("desligando o lcd");
  delay(1000);
  lcd.display_off();
  lcd.reset().clear().print("ligando o lcd"); //voce pode escrever no display anquanto ele esta desligado
  delay(1000);
  lcd.display_on();
  delay(1000);  
}

//testa o cursor do LCD
void cursor_on_teste(){
  lcd.reset().clear().print("cursor on teste");
  lcd.set_cursor(1,0).cursor_on().cursor_blink_on();
  delay(3000);
  lcd.cursor_blink_off().cursor_off();
}

Resultado:

Caracteres customizados

O controlador HITACHI HD44780 permite ao usuário criar até 8 caracteres customizados, seguindo esse padrão:

Para criar um, apenas temos que enviar 0x40 + (posição << 3) (nenhum motivo especial para fazer o shift da posição em 3. É apenas um detalhe do HITACHI HD44780) para gravar (como são suportados 8 a posição vai de 0-7) e, depois, enviar 8 valores numéricos, em que seus bit representam a linha, como mostra a imagem acima. Para finalizar, temos que reposicionar o cursor. Para escrever os valores gravados nessas posições, basta apenas chamar “write(posição)”

você pode criar seus caracteres customizados neste site: https://maxpromer.github.io/LCD-Character-Creator/

omnicrystal.h

//classe principal
#ifndef OMNICRYSTAL_H
#define OMNICRYSTAL_H

...

class Omnicrystal : public Print{
    private:
        ...
    public:
        ...
        Omnicrystal& create_char(uint8_t c[8], uint8_t pos);
        ...

};

#endif

omnicrystal.cpp

...
Omnicrystal& Omnicrystal::create_char(uint8_t c[8], uint8_t pos){
    pos &= 0b00000111;
    send(0x40 | (pos<<3), 0);
    for(size_t i = 0; i < 8; i++){
        send(c[i], 1);
    }
    return *this;
}

Atualizando e testando.

(Download da versão dessa etapa: https://github.com/RecursiveError/omnicrystal/releases/download/v0.5.0-alpha/omnicrystal.zip)

#include <omnicrystal.h>

LCDParallel LcdInter(6,7,8,9,10,11); // interface paralela usada
Omnicrystal lcd(LcdInter, Bus4Bits, 2, 16);

byte customChar[] = {
  B00110,
  B00110,
  B11110,
  B11111,
  B00011,
  B00001,
  B00000,
  B11000
};

byte customChar2[] = {
  B01010,
  B01010,
  B00000,
  B00000,
  B10001,
  B11011,
  B11011,
  B00000
};

byte customChar3[] = {
  B01100,
  B01100,
  B01111,
  B11111,
  B11000,
  B10000,
  B00000,
  B00011
};

byte customChar4[] = {
  B11000,
  B00000,
  B00001,
  B00011,
  B11111,
  B11110,
  B00110,
  B00110
};

byte customChar5[] = {
  B00000,
  B11011,
  B11011,
  B10001,
  B00000,
  B00000,
  B01010,
  B01010
};

byte customChar6[] = {
  B00011,
  B00000,
  B10000,
  B11000,
  B11111,
  B01111,
  B01100,
  B01100
};

void setup() {
  lcd.begin(); // inicia o display
  lcd.create_char(customChar,0)
    .create_char(customChar2,1)
    .create_char(customChar3,2)
    .create_char(customChar4,3)
    .create_char(customChar5,4)
    .create_char(customChar6,5);
  lcd.set_cursor(0, 0);
  lcd.write(0);
  lcd.write(1);
  lcd.write(2);
  lcd.set_cursor(1, 0);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
  lcd.print("ELETROGATE");
  lcd.set_cursor(0, 3);
  lcd.print("Blog");

}

void loop(){
}

Resultado:

E, com isso, finalizamos a classe principal.


Adicionando Módulos

Um dos objetivos desse projeto é o suporte a criação de interfaces customizadas. Já criamos uma interface paralela, então, agora, vamos criar uma interface I2C para o pcf8754 com a “Wire.h”. Os pacotes de informação do PCF8754 funcionam neste esquema:

Apenas temos que organizar os dados recebidos pela função “send” para enviar dados corretamente (importante observar que esse modulo possui apenas o Bus de 4Bits).

defaltmodules.h

//Modulos Pre-definos para nossa lib
#ifndef DEFULT_MODULES_H
#define DEFULT_MODULES_H
...
#include "Wire.h"
...

//Funciona apenas no mode de 4BITS!!!
class LCDPCF8754 : public LCDInterface{
    private:
        const uint8_t _addr; //endereço I2C
    public:
        LCDPCF8754(const uint8_t addr): _addr{addr}{
            Wire.begin(); //inicia o I2C do hardware
        }
        void send(uint8_t config, uint8_t data);
};

#endif

defaltmodules.cpp

#include "defultmodules.h"
...
void LCDPCF8754::send(uint8_t config, uint8_t data){
    uint8_t package = (config & 0b00000111) | (data & 0xF0) | 0x08; //organiza os bits corretamente e envia o pacote
    Wire.beginTransmission(_addr);
    Wire.write(package);
    Wire.endTransmission();
}

Simples assim! Temos um novo modulo para nosso projeto. Agora, é hora de testar!

(Download da versão dessa etapa: https://github.com/RecursiveError/omnicrystal/releases/download/v0.6.0-alpha/omnicrystal.zip)

Esquema:

Código:

#include <omnicrystal.h>

LCDPCF8754 LcdInter(0x27); // interface i2c usada
Omnicrystal lcd(LcdInter, Bus4Bits, 2, 16);

byte customChar[] = {
  B00110,
  B00110,
  B11110,
  B11111,
  B00011,
  B00001,
  B00000,
  B11000
};

byte customChar2[] = {
  B01010,
  B01010,
  B00000,
  B00000,
  B10001,
  B11011,
  B11011,
  B00000
};

byte customChar3[] = {
  B01100,
  B01100,
  B01111,
  B11111,
  B11000,
  B10000,
  B00000,
  B00011
};

byte customChar4[] = {
  B11000,
  B00000,
  B00001,
  B00011,
  B11111,
  B11110,
  B00110,
  B00110
};

byte customChar5[] = {
  B00000,
  B11011,
  B11011,
  B10001,
  B00000,
  B00000,
  B01010,
  B01010
};

byte customChar6[] = {
  B00011,
  B00000,
  B10000,
  B11000,
  B11111,
  B01111,
  B01100,
  B01100
};

void setup() {
  lcd.begin(); // inicia o display
  lcd.create_char(customChar,0)
    .create_char(customChar2,1)
    .create_char(customChar3,2)
    .create_char(customChar4,3)
    .create_char(customChar5,4)
    .create_char(customChar6,5);
  lcd.set_cursor(0, 0);
  lcd.write(0);
  lcd.write(1);
  lcd.write(2);
  lcd.set_cursor(1, 0);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
  lcd.print("ELETROGATE");
  lcd.set_cursor(0, 3);
  lcd.print("Blog");

}

void loop(){
}

O código é o mesmo do último teste. Só mudamos uma única linha para usar o modulo I₂C.

Resultado:


Configurando library.properties

Como foi mostrado na estrutura do projeto no primeiro post, temos o arquivo “library.properties”, que são informações adicionais sobre nossa biblioteca. Nele, ficam informações como: nome do autor, nome da biblioteca, plataformas suportadas, etc. Você pode ver todas as informações em https://arduino.github.io/arduino-cli/0.20/library-specification/#libraryproperties-file-format. Aqui, vamos apenas trabalhar com as obrigatórias, que são:

  • name (nome da biblioteca)
  • version (versão da biblioteca)
  • author (nome do autor/autores separados por vírgula)
  • maintainer (nome e email do manterdor do projeto)
  • sentence (pequena explicação sobre a biblioteca)
  • paragraph (uma explicação mais detalhada de sentence)
  • category (categoria da biblioteca [todas as categorias])
  • url (site de download da biblioteca)
  • architectures (plataformas suportadas)

Para configurar, é bem simples: basta fazer “chave=valor”. Segue o exemplo desse projeto:

library.properties

name=omnicrystal
version=1.0.0
author=Guilherme Silva Schultz
maintainer=Guilherme Silva Schultz <guilhermesschultz.contato@gmail.com>
sentence=Modular Library for HITACHI HD44780
paragraph=This library allows the user to easily create their own modules for HITACHI HD44780 LCD Displays.
category=Display
url=https://github.com/RecursiveError/omnicrystal
architectures=*

(a cada atualização, temos que alterar a versão do projeto de acordo com: https://semver.org)

Com isso, finalizamos totalmente a criação de uma biblioteca de uso pessoal. Abordamos todos os detalhes, desde a estrutura até a implementação. Você pode fazer o download dessa versão em: https://github.com/RecursiveError/omnicrystal/releases/download/v1.0.0/omnicrystal.zip.


Compartilhando a Biblioteca

Agora que terminamos a criação da biblioteca, podemos escolher compartilhar a biblioteca com o publico. Mas, primeiro, temos que fazer algumas mudanças e algumas adições.

O guia de criação de bibliotecas do Arduino define algumas sugestões de estilo de código para padronizar as bibliotecas na plataforma. Enquanto sua biblioteca é de uso pessoal, o estilo do código é de sua preferência. Mas, quando queremos compartilhar, seguir um estilo padronizado pode ajudar bastante o usuário. Seguindo o guia de estilo, temos que mudar as funções da nossa biblioteca de snake_case para CamelCase, então:

omnicrystal.h

class Omnicrystal : public Print{
    private:
        const uint8_t addrs[4] = {0x80, 0xC0, 0x80+20, 0xC0+20}; //endereços para LCD 16x2 e 20x4
        LCDInterface &_bridge;
        const BusType _bus; // tipo de comunicação 4 ou 8 bits
        const uint8_t _line; // quantidade de linhas no display
        const uint8_t _col; //quantidade de colunas por linhas

        // variaveis de configuração inicializadas com a configuração padrão
        uint8_t entry_mode{0x06}; // shift Off, escrita da esquerda pra direita
        uint8_t display_control{0x0C}; //display ligado, cursor desligado, cursor piscando desligado
        uint8_t function_set{0}; // configurado no construtor

        void send4Bits(uint8_t data, uint8_t RS_state);
        void send8Bits(uint8_t data, uint8_t RS_state);
        void send(uint8_t data, uint8_t RS_state);

    public:
        Omnicrystal(LCDInterface &bridge, const BusType bus, uint8_t line, uint8_t col, LCDCharSize size = Char5x8) : _bridge{bridge}, _bus{bus},
            _line{line}, _col{col}{
            if(_line >= 2){
                function_set = 0x20 | 0x08 | _bus | size;
            }else{
                function_set = 0x20 | _bus | size;
            }
        }

        //envia o codigo dos comandos
        Omnicrystal& command(LCDCommand);
        //funçoes para enviar cada um dos comandos
        Omnicrystal& clear();
        Omnicrystal& reset();
        Omnicrystal& moveCursorLeft();
        Omnicrystal& moveCursorRight();
        Omnicrystal& moveDisplayLeft();
        Omnicrystal& moveDisplayRight();

        //funçoes para enviar configuração
        Omnicrystal& shiftOn();
        Omnicrystal& shiftOff();
        Omnicrystal& increment();
        Omnicrystal& decrement();
        Omnicrystal& cursorBlinkOn();
        Omnicrystal& cursorBlinkOff();
        Omnicrystal& cursorOn();
        Omnicrystal& cursorOff();
        Omnicrystal& displayOn();
        Omnicrystal& displayOff();
        Omnicrystal& setCursor(uint8_t line, uint8_t col);

        Omnicrystal& createChar(uint8_t c[8], uint8_t pos);

        Omnicrystal& begin();
        virtual size_t write(uint8_t);
};

(as mesma mudanças de nome foram feitos no Arquivo omnicrystal.cpp)

Keywords.txt

o Arquivo Keywords.txt não é necessário para as versões mais atuais da Arduino IDE, mas é importante para manter a compatibilidade com versões antigas. Nesse arquivo, temos tags que representam certos tipos de dados

KEYWORD1 tipos de dados
KEYWORD2 funções
KEYWORD3 estruturas
LITERAL1 constantes

Fonte: https://arduino.github.io/arduino-cli/0.20/library-specification/#keywordstxt-format

No arquivo keywords.txt, colocamos o nome de nossas estruturas seguido de sua respectiva tag:

keywords.txt

# Datatypes (KEYWORD1)
###############################################################

Omnicrystal KEYWORD1
LCDPCF8754  KEYWORD1
LCDParallel KEYWORD1

BusType           KEYWORD1
LCDCommand        KEYWORD1
LCDEntryMode      KEYWORD1
LCDDisplayContro  KEYWORD1
LCDFunctionSet    KEYWORD1
LCDCharSize       KEYWORD1


# Methods and Functions (KEYWORD2)
###############################################################

command   KEYWORD2
clear   KEYWORD2
reset   KEYWORD2
moveCursorLeft KEYWORD2
moveCursorRight KEYWORD2
moveDisplayLeft KEYWORD2
moveDisplayRight KEYWORD2
shiftOn KEYWORD2
shiftOff KEYWORD2
increment KEYWORD2
decrement KEYWORD2
cursorBlinkOn KEYWORD2
cursorBlinkOff KEYWORD2
cursorOn KEYWORD2
cursorOff KEYWORD2
displayOn KEYWORD2
displayOff KEYWORD2
setCursor KEYWORD2
createChar KEYWORD2
begin KEYWORD2
write KEYWORD2

# Constants (LITERAL1)
########################################################

Bus4Bits LITERAL1
Bus8Bits LITERAL1
LCDClear LITERAL1
LCDReset LITERAL1

LCDShiftCursotLeft LITERAL1
LCDShiftCursotRight LITERAL1
LCDShiftDisplayLeft LITERAL1
LCDShiftDisplayRight LITERAL1

LCDShiftMode LITERAL1
LCDDirection LITERAL1
LCDBlink LITERAL1
LCDCursor LITERAL1
LCDDisplay LITERAL1
LCDChar LITERAL1
LCDLines LITERAL1
LCDBits LITERAL1
Char5x8 LITERAL1
Char5x10 LITERAL1

Criando Exemplos

Um dos arquivos vistos no primeiro post foi a pasta “exemples”. Lá, ficam os códigos que aparecem na aba Exemples da Arduino IDE. Para criar, é bem simples: basta criar um novo sketch, escrever o seu código e mover para a pasta “exemples” do seu projeto. Simples assim.

(A localização do sketchbook já foi apresentada no tópico “Atualizando a biblioteca” neste post)

vamos criar um exemplo de hello world

#include <omnicrystal.h>

//selecione a interface que deseja usar
/*
#define RS 6
#define EN 7
#define D4 8
#define D5 9
#define D6 10
#define D7 11

LCDParallel LcdInter(RS,EN,D4,D5,D6,D7);
*/
LCDPCF8754 LcdInter(0x27);
Omnicrystal lcd(LcdInter, Bus4Bits, 2, 16);

void setup() {
  lcd.begin().print("Hello World!!!");
}

void loop(){
}

Publicando

usaremos o site https://github.com para publicar a nossa biblioteca. Você pode ler mais sobre essa plataforma em: https://docs.github.com/pt/get-started

Repositório do Projeto: https://github.com/RecursiveError/omnicrystal.

Também é importante criar uma documentação do projeto. A documentação nada mais é que um arquivo que explica como usar seu código. Assim, usuários novos não vão ter dificuldades em usar. Em repositórios Git, é comum ter uma breve apresentação ou até mesmo a documentação no arquivo “Readme.md”. Mas isso são detalhes sobre Git, não sobre Arduino.

Para fazer o download da última versão, basta ir em Code → Download as ZIP

O processo de atualização é o mesmo: apague a atual manualmente e instale o novo .zip pela IDE.

Testando:

Agora que a biblioteca está corretamente configurada, podemos achar os exemplos na aba “Exemplos” da Arduino IDE:

Vamos testar o exemplo “hello_world” em outra placa para verificar se ela funciona corretamente.

Materiais:

Circuito:

Resultado:

Testando e funcionando. Agora, os teste e atualizações continuam, mas este Post chegou ao fim. Espero que tenham gostado de acompanhar o desenvolvimento dessa biblioteca. Futuras atualizações serão lançadas no repositório: https://github.com/RecursiveError/omnicrystal.


Adicionando no "Library Manager"

Você já deve ter visto que a IDE tem uma aba para instalar bibliotecas automaticamente. Mas, como nós podemos adicionar as nossas bibliotecas lá? Isso é uma tarefa bem mais complicada que envolve conhecimentos que vão além do Arduino, como conhecimento de contribuição em repositórios Git e instalação de ferramentas de linha de comando. Seria possível criar um post inteiro só para cobrir todos os detalhes do “Library Manager”. Portanto, está fora do objetivo desse post. Então, vamos explicar de uma forma mais geral, apenas para dar um norte a desenvolvedores mais experientes em busca de informações.

Primeiro de tudo, você deve verificar se o repositório do projeto está de acordo com os seguintes requisitos: https://github.com/arduino/library-registry/blob/main/FAQ.md#submission-requirements

(https://github.com/arduino/library-registry/blob/main/FAQ.md tem outras informações muito uteis para a publicação na plataforma oficial)

Com o objetivo de verificar esses requisitos, a Arduino criou a ferramenta de linha de comando: Arduino-lint. Com essa ferramenta, é possível verificar automaticamente os requisitos. Para instalar, siga o tutorial da documentação em: https://arduino.github.io/arduino-lint/1.2/installation/.

Com lint instalado, o que você deve fazer é rodar o comando: arduino-lint –compliance specification –project-type library  <caminho do projeto>. Isso vai verificar por erros na sua biblioteca. Se você obter o resultado:

Significa que você pode avançar para a prossiga etapa. Caso obtenha erros, você deve corrigir de acordo com: https://arduino.github.io/arduino-lint/1.2/rules/library/

Agora, você deve rodar o comando: arduino-lint –library-manager submit <caminho do projeto>

Isso vai verificar se é possível Adicionar sua biblioteca ao Index.

(isso só vai apresentar erro caso os requerimentos não sejam satisfeitos)

Caso o resultado seja o mesmo da imagem anterior, significa que você pode publicar sua biblioteca no “Library Manager”.

Seguindo o tutorial do https://github.com/arduino/library-registry, temos que fazer um fork do projeto, adicionar o link do nosso repositório no arquivo “repositories.txt” e enviar um pull request. Caso não tenha problemas, sua biblioteca vai ser adicionado ao Library Manager em poucos dias e você vai ser capaz de instalar diretamente pela IDE.

Existem muito mais detalhes para abordar, mas isso já não faz mais parte do tema. Os links de referências podem ser de grande ajuda para quem deseja se aprofundar no assunto.


Sobre o Autor


Guilherme Schultz
LinkedIn

Apaixonado por tecnologia, Autodidata em eletrônica e desenvolvimento embarcado.


Eletrogate

4 de maio de 2023

A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.

Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!

Eletrogate Robô

Cadastre-se e fique por
dentro de novidades!