Robótica com C — Bloco 2
Na Aula 41d você viu o LCD em modo paralelo — 6 fios de dados + controle, totalizando até 12 conexões. Para projetos com motores, sensores e Bluetooth, esses pinos fazem falta. O módulo adaptador I2C soldado atrás do LCD resolve isso: reduz toda a comunicação para apenas 2 fios (SDA e SCL), que no Arduino Uno ficam nos pinos A4 e A5 — dedicados ao barramento I2C e não conflitam com nada.
| Aspecto | LCD Paralelo | LCD I2C |
|---|---|---|
| Fios de dados | 6 pinos digitais (D2–D7 ou similar) | 0 — comunicação via barramento |
| Fios totais | até 12 conexões | 4 — GND, VCC, SDA, SCL |
| Biblioteca | LiquidCrystal.h |
LiquidCrystal_I2C.h |
| Endereço I2C | Não aplicável | Geralmente 0x27 ou 0x3F |
| Custo extra | Nenhum | Módulo adaptador PCF8574 (~R$5) |
| Ideal para | Aprendizado inicial | Projetos com múltiplos componentes |
O protocolo I2C em uma linha: SDA (Serial Data) e SCL (Serial Clock) formam um barramento compartilhado onde vários dispositivos coexistem, cada um com um endereço único. O Arduino é o mestre — ele chama o endereço do LCD, e só aquele dispositivo responde. Você já usou I2C sem perceber no RTC DS3231 da Aula 41d.
LiquidCrystal_I2CA biblioteca não vem com a IDE. Instale uma vez e ela fica disponível para todos os projetos:
#include já funcionaNo Wokwi: a biblioteca já está disponível nativamente. Adicione um componente LCD 1602 I2C no diagrama e conecte: GND→GND, VCC→5V, SDA→A4, SCL→A5. O endereço padrão no simulador é 0x27.
| Pino do módulo I2C | Conectar em | Função |
|---|---|---|
| GND | GND do Arduino | Terra / referência |
| VCC | 5V do Arduino | Alimentação |
| SDA | Pino A4 do Uno | Dados seriais |
| SCL | Pino A5 do Uno | Clock serial |
Contraste apagado? O módulo I2C tem um pequeno potenciômetro azul na parte traseira. Se o LCD ligar mas não mostrar nada, gire esse potenciômetro com uma chave de fenda até os caracteres aparecerem. É o ajuste de contraste — e a causa número 1 de "o LCD não funciona" em hardware físico.
Descobrindo o endereço I2C: a maioria dos módulos usa 0x27, mas alguns usam 0x3F. Se o LCD não inicializar, rode o sketch de scanner I2C abaixo para descobrir o endereço correto do seu módulo:
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
Serial.println("Procurando dispositivos I2C...");
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print("Encontrado: 0x");
Serial.println(addr, HEX);
}
}
}
void loop() {}
A API da LiquidCrystal_I2C é quase idêntica à do LCD paralelo — as mesmas funções, só muda a inicialização:
// LCD I2C 16×2 // Endereço 0x27, 16 colunas, 2 linhas #include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); // endereço, colunas, linhas void setup() { lcd.init(); // inicializa o módulo I2C lcd.backlight(); // liga a luz de fundo lcd.setCursor(0, 0); // coluna 0, linha 0 lcd.print("Ola, Arduino!"); lcd.setCursor(0, 1); // coluna 0, linha 1 lcd.print("Curso AFF :)"); } void loop() { }
Ola, Arduino!
Curso AFF :)
Representação do display 16×2
| Função | O que faz | Observação |
|---|---|---|
lcd.init() |
Inicializa o módulo I2C | Sempre no setup(), antes de qualquer outra chamada |
lcd.backlight() |
Liga a luz de fundo | Sem isso o display fica escuro mesmo funcionando |
lcd.noBacklight() |
Desliga a luz de fundo | Útil para economizar energia em modo sleep |
lcd.setCursor(col, lin) |
Posiciona o cursor | col: 0–15, lin: 0–1 para display 16×2 |
lcd.print(valor) |
Escreve na posição atual | Aceita int, float, String, char[] |
lcd.clear() |
Apaga tudo e volta para (0,0) | Causa pisca — use espaços para sobrescrever |
lcd.createChar(id, mapa) |
Cria caractere customizado 5×8 px | Até 8 caracteres customizados por vez |
O padrão correto para atualizar valores no LCD sem causar pisca é: escrever o rótulo fixo uma vez no setup() e atualizar apenas o valor no loop(), usando espaços para limpar dígitos antigos:
/* * LCD I2C + LM35 — temperatura em tempo real * Linha 0: rótulo fixo "Temperatura:" * Linha 1: valor atualizado a cada 500ms */ #include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); const int PINO_LM35 = A0; void setup() { lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Temperatura:"); // rótulo — só no setup() } void loop() { int bruto = analogRead(PINO_LM35); float temp = bruto * (5.0 / 1023.0) * 100.0; lcd.setCursor(0, 1); lcd.print(temp, 1); // ex: "24.3" lcd.print(" C "); // espaços apagam dígitos anteriores delay(500); }
Regra dos espaços: se o valor anterior tinha mais dígitos que o atual (ex: "100.0" → "24.3"), os dígitos antigos ficam visíveis. Sempre termine o lcd.print() com alguns espaços extras para cobrir o pior caso. Para um float com 1 decimal, " " (6 espaços) é seguro.
Agora vamos combinar a máquina de estados da Aula 44 com o LCD — o display mostra o estado atual, a contagem regressiva e uma mensagem para o motorista. Este é o primeiro projeto que combina dois subsistemas simultaneamente:
/* * Semáforo com LCD I2C * Linha 0: estado atual + ícone * Linha 1: contagem regressiva em segundos */ #include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); const int PINO_VERDE = 9; const int PINO_AMARELO = 10; const int PINO_VERMELHO = 11; typedef enum { VERDE, AMARELO, VERMELHO } EstadoSemaforo; EstadoSemaforo estado = VERDE; unsigned long ultimaTroca = 0; unsigned long tempoDuEstado(EstadoSemaforo e) { switch (e) { case VERDE: return 4000; case AMARELO: return 3000; case VERMELHO: return 5000; } return 1000; } EstadoSemaforo proximoEstado(EstadoSemaforo e) { switch (e) { case VERDE: return AMARELO; case AMARELO: return VERMELHO; case VERMELHO: return VERDE; } return VERDE; } void atualizarLEDs(EstadoSemaforo e) { digitalWrite(PINO_VERDE, e == VERDE); digitalWrite(PINO_AMARELO, e == AMARELO); digitalWrite(PINO_VERMELHO, e == VERMELHO); } // Atualiza o LCD com estado atual e tempo restante void atualizarLCD(EstadoSemaforo e, unsigned long restante) { lcd.setCursor(0, 0); switch (e) { case VERDE: lcd.print("VERDE - SIGA "); break; case AMARELO: lcd.print("AMARELO - ATENC "); break; case VERMELHO: lcd.print("VERMELHO- PARE "); break; } lcd.setCursor(0, 1); lcd.print("Restam: "); lcd.print(restante / 1000); // ms → segundos lcd.print("s "); } void setup() { pinMode(PINO_VERDE, OUTPUT); pinMode(PINO_AMARELO, OUTPUT); pinMode(PINO_VERMELHO, OUTPUT); lcd.init(); lcd.backlight(); atualizarLEDs(estado); ultimaTroca = millis(); atualizarLCD(estado, tempoDuEstado(estado)); } void loop() { unsigned long agora = millis(); unsigned long decorrido = agora - ultimaTroca; unsigned long duracao = tempoDuEstado(estado); // Atualiza o display a cada 500ms (não a cada iteração — economiza ciclos) static unsigned long ultimaTelaAtualizada = 0; if (agora - ultimaTelaAtualizada >= 500) { ultimaTelaAtualizada = agora; unsigned long restante = (decorrido < duracao) ? duracao - decorrido : 0; atualizarLCD(estado, restante); } // Transição de estado if (decorrido >= duracao) { estado = proximoEstado(estado); ultimaTroca = agora; atualizarLEDs(estado); } }
VERDE - SIGA
Restam: 3s
AMARELO - ATENC
Restam: 2s
Variável static local: static unsigned long ultimaTelaAtualizada = 0 dentro do loop() é inicializada apenas uma vez — como uma variável global, mas com escopo local à função. Você usou static em C puro nas aulas de funções; aqui serve para controlar frequência de atualização do LCD sem declarar uma variável global a mais.
O LCD permite criar até 8 ícones de 5×8 pixels. Cada linha do caractere é uma máscara de 5 bits representada como um byte. É manipulação de bits na prática — o mesmo &, | e << que você usou na Aula 41b:
// Ícone de termômetro — 5 colunas × 8 linhas byte termometro[8] = { 0b00100, // linha 0 (topo): coluna do meio 0b01010, // linha 1 0b01010, // linha 2 0b01110, // linha 3 0b01110, // linha 4 0b11111, // linha 5 0b11111, // linha 6 0b01110 // linha 7 (base): bolbo }; void setup() { lcd.init(); lcd.backlight(); lcd.createChar(0, termometro); // registra no slot 0 lcd.setCursor(0, 0); lcd.write(0); // exibe o caractere customizado do slot 0 lcd.print(" Temp: 24.3C"); }
Gerador online de caracteres: acesse maxpromer.github.io/LCD-Character-Creator para desenhar ícones visualmente e gerar o array automaticamente. Útil para criar símbolos de grau (°), setinhas, ícones de bateria ou qualquer símbolo que o LCD não tem nativamente.
| Conceito | C puro | Arduino + LCD I2C |
|---|---|---|
| Exibir texto | printf("texto") no terminal |
lcd.print("texto") no display físico |
| Posicionar saída | Sequencial — sem controle de posição | lcd.setCursor(col, lin) — posição exata |
| Exibir float | printf("%.1f", temp) |
lcd.print(temp, 1) — segundo parâmetro = casas decimais |
| Protocolo de comunicação | Não aplicável (saída direta) | I2C via Wire.h — endereço 0x27 ou 0x3F |
| Variável local persistente | static dentro de função |
static dentro do loop() — idêntico |
| Caracteres customizados | Não existe | Bitmap 5×8 com createChar() + manipulação de bits |
Teste o que você aprendeu sobre o display LCD I2C: