Aula 41d — HC-SR04, Display LCD e Módulo RTC

Robótica com C — Bloco 1

📡 Sensor Ultrassônico HC-SR04

O HC-SR04 mede distância por ultrassom: emite um pulso de 40 kHz pelo pino TRIG e mede o tempo até o eco chegar ao pino ECHO. Como o som viaja a ~343 m/s (0,0343 cm/µs), o tempo de ida e volta dividido por 2 fornece a distância. É o sensor de desvio de obstáculos mais usado em robótica por custar menos de R$10 e ser preciso entre 2 cm e 4 metros.

Pino Função Configurar como
VCC Alimentação 5V —
GND Terra —
TRIG Dispara o pulso ultrassônico (entrada do sensor) OUTPUT
ECHO Fica HIGH enquanto aguarda o eco (saída do sensor) INPUT
// HC-SR04: TRIG → pino 9, ECHO → pino 10

const int PINO_TRIG = 9;
const int PINO_ECHO = 10;

void setup() {
    pinMode(PINO_TRIG, OUTPUT);
    pinMode(PINO_ECHO, INPUT);
    Serial.begin(9600);
}

// Função encapsulada — retorna distância em cm
long medirDistanciaCm() {
    // Garante TRIG em LOW antes de disparar
    digitalWrite(PINO_TRIG, LOW);
    delayMicroseconds(2);

    // Pulso de 10µs — dispara o ultrassom
    digitalWrite(PINO_TRIG, HIGH);
    delayMicroseconds(10);
    digitalWrite(PINO_TRIG, LOW);

    // pulseIn() mede quanto tempo ECHO fica HIGH (µs)
    long duracao = pulseIn(PINO_ECHO, HIGH);

    // Distância = tempo × 0,0343 cm/µs ÷ 2 (ida e volta)
    return (duracao * 0.0343) / 2;
}

void loop() {
    long dist = medirDistanciaCm();

    Serial.print("Distancia: ");
    Serial.print(dist);
    Serial.println(" cm");

    if (dist < 20) Serial.println("  >> Obstaculo proximo!");

    delay(500);
}

Distancia: 85 cm

Distancia: 34 cm

Distancia: 12 cm

  >> Obstaculo proximo!

Por que encapsular em função? A função medirDistanciaCm() aplica diretamente o que você aprendeu nas aulas de funções em C: lógica reutilizável com retorno claro. No robô, você vai chamar essa função periodicamente sem repetir as 7 linhas de protocolo. Nas aulas 51–52, ela será chamada dentro de uma struct para dois sensores simultâneos.

Dois sensores com struct — antecipando o robô

Quando o robô tiver sensores na frente e na lateral, a struct encapsula os pinos e reutiliza a mesma função de medição:

// Dois HC-SR04 com struct — padrão que será usado no robô

typedef struct {
    int  pinTrig;
    int  pinEcho;
    const char* nome;
} Ultrassonico;

void inicializarUS(Ultrassonico* s) {
    pinMode(s->pinTrig, OUTPUT);
    pinMode(s->pinEcho, INPUT);
}

long medirCm(Ultrassonico* s) {
    digitalWrite(s->pinTrig, LOW);  delayMicroseconds(2);
    digitalWrite(s->pinTrig, HIGH); delayMicroseconds(10);
    digitalWrite(s->pinTrig, LOW);
    return (pulseIn(s->pinEcho, HIGH) * 0.0343) / 2;
}

Ultrassonico frente  = {9,  10, "FRENTE"};
Ultrassonico lateral = {6,  7,  "LATERAL"};

void setup() {
    inicializarUS(&frente);
    inicializarUS(&lateral);
    Serial.begin(9600);
}

void loop() {
    Serial.print(frente.nome);  Serial.print(": "); Serial.print(medirCm(&frente));
    Serial.print(" cm  |  ");
    Serial.print(lateral.nome); Serial.print(": "); Serial.print(medirCm(&lateral));
    Serial.println(" cm");
    delay(300);
}

Reconheceu o padrão? Ultrassonico*, s->pinTrig, inicializarUS(&frente) — é exatamente a mesma lógica das funções de lista encadeada das aulas 37–38, agora aplicada ao hardware.

🖥️ Display LCD — exibindo informação com LiquidCrystal.h

O display LCD 16×2 (16 colunas, 2 linhas) exibe texto sem precisar de Serial ou computador conectado — fundamental para o robô mostrar status, distância e modo de operação de forma autônoma.

Pode ser controlado de duas formas: modo paralelo (6 fios de dados + controle) ou I2C (apenas 2 fios via adaptador). Aqui cobrimos o modo paralelo — o mais fácil de entender internamente:

// Display LCD 16×2 em modo paralelo
// Pinos: RS=12, EN=11, D4=5, D5=4, D6=3, D7=2
// Alimentação: VSS→GND, VDD→5V, V0→potenciômetro (contraste), A→5V, K→GND

#include <LiquidCrystal.h>

const int rs=12, en=11, d4=5, d5=4, d6=3, d7=2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);   // objeto da classe

void setup() {
    lcd.begin(16, 2);              // inicializa: 16 colunas, 2 linhas
    lcd.print("Ola, Arduino!");   // linha 0, coluna 0
    lcd.setCursor(0, 1);           // move para linha 1
    lcd.print("Curso AFF :)");
}

void loop() { }

Para dados dinâmicos, atualize dentro do loop() — mas evite lcd.clear() pois causa pisca. A técnica correta é escrever por cima com espaços:

// LCD dinâmico: temperatura em tempo real (LM35 no A0)

#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int PINO_LM35 = A0;

void setup() {
    lcd.begin(16, 2);
    lcd.print("Temperatura:");   // rótulo fixo — só no setup()
}

void loop() {
    float temp = analogRead(PINO_LM35) * (5.0 / 1023.0) * 100.0;

    lcd.setCursor(0, 1);         // posiciona na linha 1
    lcd.print(temp, 1);          // ex: "24.3"
    lcd.print(" C   ");          // espaços cobrem dígitos anteriores

    delay(500);
}

Métodos mais usados do LCD:

Versão I2C para projetos maiores: o LCD via I2C usa apenas 2 fios (SDA=A4, SCL=A5) em vez de 6, liberando pinos digitais para motores e sensores. Requer a biblioteca LiquidCrystal_I2C.h e um módulo adaptador soldado atrás do LCD. Na Aula 46 usaremos LCD I2C — aqui você aprendeu o funcionamento interno para entender o que o adaptador esconde.

🕐 Módulo RTC DS3231 — relógio de tempo real

O módulo RTC (Real Time Clock) DS3231 é um relógio de alta precisão que continua funcionando mesmo quando o Arduino é desligado — ele tem uma bateria CR2032 própria. Comunica via I2C: SDA → pino A4, SCL → pino A5 no Uno.

Requer a biblioteca RTClib: Sketch → Incluir Biblioteca → Gerenciar Bibliotecas → "RTClib" → Instalar.

// Módulo RTC DS3231
// Ligação I2C: SDA → A4, SCL → A5, VCC → 3.3V ou 5V, GND → GND

#include <Wire.h>
#include <RTClib.h>

RTC_DS3231 rtc;    // objeto da classe — instância, como você aprendeu na 41a

void setup() {
    Serial.begin(9600);

    if (!rtc.begin()) {
        Serial.println("RTC nao encontrado! Verifique a ligacao.");
        while (1);    // trava — sem RTC o programa não faz sentido
    }

    // Se a bateria morreu, ajusta com a hora da compilação do sketch
    if (rtc.lostPower()) {
        Serial.println("RTC sem hora — ajustando...");
        rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    }
}

void loop() {
    DateTime agora = rtc.now();   // objeto com data e hora atual

    // Formato DD/MM/AAAA HH:MM:SS
    Serial.print(agora.day(),    DEC); Serial.print("/");
    Serial.print(agora.month(),  DEC); Serial.print("/");
    Serial.print(agora.year(),   DEC); Serial.print("  ");
    Serial.print(agora.hour(),   DEC); Serial.print(":");
    Serial.print(agora.minute(), DEC); Serial.print(":");
    Serial.print(agora.second(), DEC); Serial.println();

    delay(1000);
}

28/3/2026 14:35:02

28/3/2026 14:35:03

28/3/2026 14:35:04

F("texto") e F(__DATE__): a macro F() armazena a string na memória flash (32 KB) em vez de copiar para a RAM (2 KB) durante a execução. Com apenas 2 KB de RAM no Uno, usar F() em mensagens longas ou constantes de texto é uma boa prática. __DATE__ e __TIME__ são macros do compilador que inserem a data e hora da compilação diretamente no binário.

Combinando HC-SR04 + LCD — prévia do robô

O padrão final das aulas de robótica é exatamente este: um sensor lê o mundo, o LCD exibe o resultado, o código decide o que fazer. Aqui está uma miniatura desse padrão:

/*
 * HC-SR04 + LCD 16×2
 * Exibe distância em tempo real no display
 * Se dist < 20cm: linha 2 mostra "OBSTACULO!"
 */

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const int PINO_TRIG = 9;
const int PINO_ECHO = 10;

long medirDistanciaCm() {
    digitalWrite(PINO_TRIG, LOW);  delayMicroseconds(2);
    digitalWrite(PINO_TRIG, HIGH); delayMicroseconds(10);
    digitalWrite(PINO_TRIG, LOW);
    return (pulseIn(PINO_ECHO, HIGH) * 0.0343) / 2;
}

void setup() {
    pinMode(PINO_TRIG, OUTPUT);
    pinMode(PINO_ECHO, INPUT);
    lcd.begin(16, 2);
    lcd.print("Distancia:");
}

void loop() {
    long dist = medirDistanciaCm();

    lcd.setCursor(0, 1);
    lcd.print(dist);
    lcd.print(" cm      ");   // espaços cobrem dígitos antigos

    lcd.setCursor(9, 1);
    if (dist < 20) lcd.print("OBST!");
    else           lcd.print("     ");

    delay(300);
}

Esse é o núcleo do robô da Aula 52: medirDistanciaCm() retorna a distância, o LCD exibe, e um if decide a ação. Nas próximas aulas você vai acrescentar motores, mais sensores e uma máquina de estados — mas a estrutura central permanece essa.

Quiz — Aula 41d

Teste o que você aprendeu sobre HC-SR04, LCD e RTC:

1. No HC-SR04, qual pino deve ser configurado como OUTPUT e qual como INPUT?

2. O que a função pulseIn(PINO_ECHO, HIGH) retorna no contexto do HC-SR04?

3. Por que é importante encapsular o código do HC-SR04 numa função medirDistanciaCm()?

4. Por que não se deve usar lcd.clear() repetidamente dentro do loop()?

5. Qual é a vantagem do LCD I2C sobre o LCD paralelo para projetos de robótica?

6. Por que o módulo RTC DS3231 continua sabendo a hora mesmo após o Arduino ser desligado?

7. O que faz F("texto") no Arduino?

8. No exemplo HC-SR04 + LCD, por que o rótulo "Distancia:" é escrito no setup() e não no loop()?