Início›Chars
⬤ LCD · Intermédio

🎨 Caracteres Custom numa Interface Real

⏱️ 30 min 🧩 CGRAM · createChar() · Dashboard com ícones · Barra de nível multi-bloco

Nesta página

🧠 Princípio — Os 8 Slots CGRAM

A CGRAM tem exactamente 8 slots (0–7). Cada slot guarda um bitmap de 5×8 pixels: 8 bytes onde apenas os 5 bits menos significativos de cada byte definem as colunas. Uma vez registado com createChar(slot, array), o char é impresso com lcd.write(slot).

Slots CGRAM
8
Chars 0–7
Resolução
5×8 px
Pixels por char
Registar
createChar()
Antes de lcd.init()
Imprimir
lcd.write(n)
n = número do slot
⚠️
Chama sempre lcd.createChar() antes de lcd.init(), ou pelo menos antes do primeiro lcd.print(). Reinicializar o LCD pode apagar a CGRAM.

🔧 Ícones para Dashboard de Motor

Para uma interface de controlo de motores os ícones mais úteis são: seta de direção, engrenagem de estado, bloco de barra de velocidade e símbolo de grau para ângulo de servo.

lcd_chars_motor.ino
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

// Slot 0 — Seta direita → (FORWARD)
byte fwd[8] = {
  0b00000, 0b00100, 0b00110,
  0b11111, 0b11111, 0b00110,
  0b00100, 0b00000
};
// Slot 1 — Seta esquerda ← (BACKWARD)
byte bwd[8] = {
  0b00000, 0b00100, 0b01100,
  0b11111, 0b11111, 0b01100,
  0b00100, 0b00000
};
// Slot 2 — Engrenagem ⚙ (motor activo)
byte gear[8] = {
  0b01110, 0b11111, 0b11011,
  0b11111, 0b01110, 0b11111,
  0b11011, 0b01110
};
// Slot 3 — Círculo ○ (motor parado)
byte stop_ic[8] = {
  0b01110, 0b10001, 0b10001,
  0b10001, 0b10001, 0b10001,
  0b01110, 0b00000
};
// Slot 4 — Grau ° (ângulo servo)
byte grau[8] = {
  0b00110, 0b01001, 0b01001,
  0b00110, 0b00000, 0b00000,
  0b00000, 0b00000
};
// Slot 5 — Bloco cheio █ (barra velocidade)
byte bloco[8] = {
  0b11111, 0b11111, 0b11111,
  0b11111, 0b11111, 0b11111,
  0b11111, 0b11111
};
// Slot 6 — Meio bloco ▌ (resolução de barra)
byte meio[8] = {
  0b11000, 0b11000, 0b11000,
  0b11000, 0b11000, 0b11000,
  0b11000, 0b11000
};
// Slot 7 — Alerta ⚠ (situação de emergência)
byte alerta[8] = {
  0b00100, 0b01110, 0b01110,
  0b11111, 0b10001, 0b11111,
  0b00000, 0b00100
};

void registarIcones() {
  lcd.createChar(0, fwd);     // → FWD
  lcd.createChar(1, bwd);     // ← BWD
  lcd.createChar(2, gear);    // ⚙ activo
  lcd.createChar(3, stop_ic); // ○ parado
  lcd.createChar(4, grau);    // ° grau
  lcd.createChar(5, bloco);   // █ barra
  lcd.createChar(6, meio);    // ▌ meia barra
  lcd.createChar(7, alerta);  // ⚠ alerta
}

void setup() {
  registarIcones();   // ANTES de lcd.init()
  lcd.init(); lcd.backlight();
}

📊 Barra de Nível com 3 Blocos — 60 Posições em 20 Chars

ℹ️
Usando 3 estados por posição (vazio, meio-bloco ▌, bloco cheio █) obtemos 20×3 = 60 níveis de resolução numa barra de 20 caracteres. É 3× mais preciso que usar apenas cheio/vazio.
barra_3blocos.ino
// Barra de 60 posições em 20 chars — usa slots 5 (█) e 6 (▌)
void barraVelocidade(int linha, int vel255) {
  // vel255: 0–255 → mapear para 0–60 posições
  int pos60  = vel255 * 60 / 255;
  int cheios = pos60 / 3;        // blocos █ completos
  int resto  = pos60 % 3;        // 0=nada, 1=▌, 2=█ parcial

  lcd.setCursor(0, linha);
  for (int i = 0; i < 20; i++) {
    if      (i < cheios)           lcd.write(5);  // █ cheio
    else if (i == cheios && resto == 1) lcd.write(6);  // ▌ meio
    else if (i == cheios && resto == 2) lcd.write(5);  // █ (2/3)
    else                           lcd.write(32); // espaço
  }
}

// Uso no loop:
// barraVelocidade(2, velocidade);  // linha 2, velocidade 0-255

🖥️ Dashboard Completo com Ícones

dashboard_icones.ino
#include <AFMotor.h>
AF_DCMotor motor(1);

bool  forward = true;
int   vel     = 0;
bool  parado  = true;

void dashboardMotor(int anguloServo) {
  // Linha 0 — direção com ícone
  lcd.setCursor(0, 0);
  if (parado) {
    lcd.write(3);                          // ○ parado
    lcd.print(" Motor: PARADO    ");
  } else {
    lcd.write(forward ? 0 : 1);           // → ou ←
    lcd.print(forward ? " Motor: FORWARD   "
                       : " Motor: BACKWARD  ");
  }

  // Linha 1 — velocidade numérica
  lcd.setCursor(0, 1);
  lcd.write(2);                             // ⚙
  lcd.print(" Vel: "); lcd.print(vel);
  lcd.print(" (");   lcd.print(vel*100/255);
  lcd.print("%)");  lcd.print("   ");

  // Linha 2 — barra de velocidade
  barraVelocidade(2, vel);

  // Linha 3 — servo e alerta
  lcd.setCursor(0, 3);
  lcd.print("Sv: "); lcd.print(anguloServo);
  lcd.write(4);                             // °
  if (vel > 220) {
    lcd.print("  "); lcd.write(7);          // ⚠ velocidade alta
    lcd.print(" VEL. ALTA! ");
  } else {
    lcd.print("  OK           ");
  }
}
Motor: FORWARD  Vel: 200 (78%)  Sv: 90 OK

🎬 Animação de 8 Frames

💡
Os 8 slots são actualizados dinamicamente com createChar() — podes mudar o bitmap de um slot em tempo de execução. Usa isso para animar um ícone único sem gastar mais slots.
animacao_motor.ino
// Motor DC: animação de engrenagem a girar — 4 frames no slot 2
byte frames[4][8] = {
  {0b01110, 0b11111, 0b11011, 0b11111, 0b01110, 0b11111, 0b11011, 0b01110}, // frame 0
  {0b01110, 0b11101, 0b11111, 0b10111, 0b01110, 0b11101, 0b11111, 0b01110}, // frame 1
  {0b01110, 0b11111, 0b01111, 0b11111, 0b01110, 0b11111, 0b11110, 0b01110}, // frame 2
  {0b01110, 0b10111, 0b11111, 0b11101, 0b01110, 0b10111, 0b11111, 0b01110}, // frame 3
};

int           frameAtual  = 0;
unsigned long tAnim       = 0;
#define       ANIM_MS     150   // ms entre frames

void animarMotor(bool ativo) {
  if (!ativo) return;
  if (millis() - tAnim > ANIM_MS) {
    frameAtual = (frameAtual + 1) % 4;
    lcd.createChar(2, frames[frameAtual]); // Actualiza slot 2 em runtime
    // Re-imprimir o char na posição para mostrar frame novo
    lcd.setCursor(0, 1); lcd.write(2);
    tAnim = millis();
  }
}

// No loop(), chamar animarMotor(motor_a_funcionar);

🏆 Para ir mais longe

PR.FT9_5 · LCD 20×4 · L293D Motor Driver · Arduino UNO Rev3 · Início · led.dge.medu.pt