Aula 42 — Entradas e Saídas Digitais

Robótica com C — Bloco 1

O mundo digital é binário — igual ao seu código

Em C você já trabalhou com true/false, 0/1, bits ligados e desligados. O mundo digital do Arduino é exatamente isso, mas no nível elétrico: cada pino digital está sempre em um de dois estados — HIGH (5V) ou LOW (0V). Sem meio-termo.

Dois papéis, dois comandos: um pino digital pode ser configurado para saída (o Arduino manda tensão para o mundo) ou entrada (o Arduino lê tensão que vem do mundo). Isso é definido por pinMode() — sempre no setup().

Modo Configuração Função de uso Componente típico
Saída pinMode(pino, OUTPUT) digitalWrite(pino, HIGH/LOW) LED, buzzer, relé, motor
Entrada pinMode(pino, INPUT) digitalRead(pino) → HIGH ou LOW Botão, chave, sensor digital
Entrada com pull-up pinMode(pino, INPUT_PULLUP) digitalRead(pino) → invertido Botão (ligação simplificada)

Saída digital: controlando múltiplos LEDs

Na Aula 41b você fez um LED piscar. Agora vamos controlar três LEDs independentemente — o mesmo princípio que vai controlar três servos, três motores ou três qualquer-coisa no robô.

A chave é tratar os pinos como um array de saídas — e você já sabe usar arrays em C:

// Três LEDs piscando em sequência — "efeito cascata"
// LEDs nos pinos 8, 9 e 10, cada um com resistor 220Ω → GND

#define NUM_LEDS  3
#define PAUSA     200  // ms entre cada LED

int pinos[NUM_LEDS] = {8, 9, 10};  // array de pinos — como um vetor de índices

void setup() {
    for (int i = 0; i < NUM_LEDS; i++) {
        pinMode(pinos[i], OUTPUT);   // configura cada pino como saída
    }
    Serial.begin(9600);
    Serial.println("Efeito cascata iniciado");
}

void loop() {
    // Acende um de cada vez, da esquerda para direita
    for (int i = 0; i < NUM_LEDS; i++) {
        digitalWrite(pinos[i], HIGH);   // acende
        delay(PAUSA);
        digitalWrite(pinos[i], LOW);    // apaga
    }
}

Efeito cascata iniciado

[LED 8 acende e apaga, depois LED 9, depois LED 10, e repete…]

Reconheceu o padrão? O for percorrendo o array de pinos é exatamente o mesmo que você usava para percorrer um vetor em C — for (int i = 0; i < NUM_LEDS; i++). A única diferença é que agora cada elemento é um número de pino, e a "operação sobre o elemento" é digitalWrite().

Entrada digital: lendo um botão

Um botão é o componente de entrada mais simples possível: quando pressionado, fecha um circuito. Quando solto, abre. O Arduino lê isso com digitalRead(), que retorna HIGH ou LOW.

Mas há um problema elétrico que você precisa conhecer antes de ligar qualquer botão:

O problema do pino flutuante: quando o botão está solto e o pino está configurado como INPUT simples, ele fica "no ar" — sem conexão definida com 5V nem com GND. Nessa condição, interferências eletromagnéticas do ambiente fazem o pino oscilar aleatoriamente entre HIGH e LOW. Seu código vai ler lixo. Isso se chama pino flutuante.

A solução é o resistor de pull-up: um resistor de ~10kΩ que mantém o pino conectado ao 5V enquanto o botão está solto. Quando o botão é pressionado, ele conecta o pino ao GND, forçando LOW. O Arduino já tem resistores de pull-up internos que você ativa com INPUT_PULLUP:

Situação Com INPUT simples Com INPUT_PULLUP
Botão solto Leitura aleatória (pino flutuante) HIGH — pull-up mantém em 5V
Botão pressionado Depende da ligação LOW — botão conecta ao GND
Resistor externo necessário? Sim, 10kΩ externo Não — pull-up interno já ativado
Ligação do botão Pino → botão → 5V Pino → botão → GND

Atenção à lógica invertida: com INPUT_PULLUP, botão solto = HIGH e botão pressionado = LOW. É o contrário do que parece intuitivo. Isso é normal — use if (leitura == LOW) para detectar o pressionamento.

// Botão no pino 7 (com INPUT_PULLUP) → LED no pino 8
// Ligação: pino 7 → uma perna do botão; outra perna do botão → GND

#define PINO_BOTAO  7
#define PINO_LED    8

void setup() {
    pinMode(PINO_LED,   OUTPUT);
    pinMode(PINO_BOTAO, INPUT_PULLUP);  // pull-up interno ativado
    Serial.begin(9600);
}

void loop() {
    int leitura = digitalRead(PINO_BOTAO);  // HIGH ou LOW

    if (leitura == LOW) {          // LOW = botão pressionado (pull-up invertido)
        digitalWrite(PINO_LED, HIGH);
        Serial.println("Botão pressionado — LED ligado");
    } else {                       // HIGH = botão solto
        digitalWrite(PINO_LED, LOW);
    }
}

[Botão solto — nada no Serial]

Botão pressionado — LED ligado

Botão pressionado — LED ligado

Botão pressionado — LED ligado

[Botão solto — LED apaga]

O problema do bounce (trepidação do contato)

Quando você aperta um botão físico, os contatos metálicos internos não fecham uma vez limpa — eles vibram microscopicamente por 5 a 20 milissegundos antes de estabilizar. O Arduino lê isso como dezenas de pressões rapidíssimas, quando você pressionou apenas uma vez. Isso se chama bounce.

// O que o Arduino realmente "vê" num único aperto de botão:
// LOW  HIGH  LOW  HIGH  LOW  HIGH  LOW  ← trepidação (bounce)
//                            LOW  ← sinal estável

A solução mais simples é o debounce por tempo: após detectar a primeira transição, ignore qualquer leitura nos próximos 50ms:

// Contador de cliques com debounce

#define PINO_BOTAO    7
#define PINO_LED      8
#define DEBOUNCE_MS  50  // tempo de espera após detectar borda

int contadorCliques = 0;
int estadoAnterior  = HIGH;   // pull-up: estado inicial é HIGH
unsigned long ultimoBounce = 0;

void setup() {
    pinMode(PINO_LED,   OUTPUT);
    pinMode(PINO_BOTAO, INPUT_PULLUP);
    Serial.begin(9600);
    Serial.println("Aguardando cliques...");
}

void loop() {
    int leituraAtual = digitalRead(PINO_BOTAO);
    unsigned long agora = millis();

    // Detectou mudança de estado?
    if (leituraAtual != estadoAnterior) {
        ultimoBounce = agora;          // registra o momento da mudança
    }

    // Só processa se passou o tempo de debounce
    if ((agora - ultimoBounce) > DEBOUNCE_MS) {
        if (leituraAtual == LOW) {     // botão estabilizou em pressionado
            contadorCliques++;
            digitalWrite(PINO_LED, HIGH);
            Serial.print("Clique #");
            Serial.println(contadorCliques);
        } else {
            digitalWrite(PINO_LED, LOW);
        }
    }

    estadoAnterior = leituraAtual;    // atualiza estado para próxima iteração
}

Aguardando cliques...

Clique #1

Clique #2

Clique #3

Sempre use debounce em botões físicos. No Wokwi o bounce é simulado de forma suave, mas no hardware real ele é muito mais agressivo — um único clique sem debounce pode registrar 10 a 50 cliques falsos.

Exemplo completo: botão que alterna o LED

Um padrão muito usado em robótica é o toggle: cada clique inverte o estado atual — se está ligado, desliga; se está desligado, liga. É diferente de "LED aceso enquanto botão pressionado". Aqui você detecta apenas a borda de descida (momento em que o botão é pressionado, não enquanto está pressionado):

/*
 * Botão toggle com debounce
 * Um clique liga; outro clique desliga
 * Usa detecção de borda: só age no momento do pressionamento
 */

#define PINO_BOTAO   7
#define PINO_LED     8
#define DEBOUNCE_MS 50

int estadoLED      = LOW;   // estado atual do LED
int estadoAnterior = HIGH;  // último estado lido do botão
int estadoEstavel  = HIGH;  // estado após debounce
unsigned long ultimoBounce = 0;

void setup() {
    pinMode(PINO_LED,   OUTPUT);
    pinMode(PINO_BOTAO, INPUT_PULLUP);
    Serial.begin(9600);
}

void loop() {
    int leituraAtual = digitalRead(PINO_BOTAO);

    if (leituraAtual != estadoAnterior) {
        ultimoBounce = millis();
    }

    if ((millis() - ultimoBounce) > DEBOUNCE_MS) {
        // Detecta BORDA DE DESCIDA: estava HIGH, agora é LOW
        if (leituraAtual == LOW && estadoEstavel == HIGH) {
            estadoLED = !estadoLED;              // inverte
            digitalWrite(PINO_LED, estadoLED);
            Serial.print("Toggle → LED ");
            Serial.println(estadoLED ? "LIGADO" : "DESLIGADO");
        }
        estadoEstavel = leituraAtual;
    }

    estadoAnterior = leituraAtual;
}

Toggle → LED LIGADO

Toggle → LED DESLIGADO

Toggle → LED LIGADO

Toggle → LED LIGADO

Toggle → LED DESLIGADO

Por que isso importa para robótica? O padrão toggle aparece o tempo todo: um botão que ativa/desativa o modo autônomo do robô, uma chave que liga/desliga os motores, um sensor que alterna entre "patrulhar" e "parar". Entender borda de descida é fundamental.

Aplicando C ao Arduino: struct para encapsular um botão

Quando o projeto tem 2 ou 3 botões, repetir as variáveis de debounce para cada um vira bagunça. A solução é exatamente o que você aprendeu nas aulas de struct em C — encapsule tudo que pertence a um botão em um único tipo:

// Struct que representa um botão com debounce embutido
// Aplica diretamente o que você aprendeu sobre structs em C

typedef struct {
    int pino;
    int estadoAnterior;
    int estadoEstavel;
    unsigned long ultimoBounce;
} Botao;

// Inicializa um botão
void inicializarBotao(Botao* b, int pino) {
    b->pino          = pino;
    b->estadoAnterior = HIGH;
    b->estadoEstavel  = HIGH;
    b->ultimoBounce   = 0;
    pinMode(pino, INPUT_PULLUP);
}

// Retorna true somente no momento do clique (borda de descida)
bool foiClicado(Botao* b) {
    int leitura = digitalRead(b->pino);

    if (leitura != b->estadoAnterior) {
        b->ultimoBounce = millis();
    }

    bool clicou = false;
    if ((millis() - b->ultimoBounce) > 50) {
        if (leitura == LOW && b->estadoEstavel == HIGH) {
            clicou = true;
        }
        b->estadoEstavel = leitura;
    }

    b->estadoAnterior = leitura;
    return clicou;
}

// Dois botões independentes controlando dois LEDs
Botao btn1, btn2;
int ledA = LOW, ledB = LOW;

void setup() {
    inicializarBotao(&btn1, 7);
    inicializarBotao(&btn2, 6);
    pinMode(8, OUTPUT);
    pinMode(9, OUTPUT);
    Serial.begin(9600);
}

void loop() {
    if (foiClicado(&btn1)) {
        ledA = !ledA;
        digitalWrite(8, ledA);
        Serial.println("Botão 1 → LED A toggle");
    }
    if (foiClicado(&btn2)) {
        ledB = !ledB;
        digitalWrite(9, ledB);
        Serial.println("Botão 2 → LED B toggle");
    }
}

Isso é C de verdade rodando no Arduino. A struct Botao, o typedef, o ponteiro Botao*, o operador ->, a passagem por referência com &btn1 — tudo idêntico ao que você praticou nas aulas 13–28. O Arduino não muda nada disso.

C vs Arduino — Entradas e Saídas Digitais

Conceito C puro (PC) Arduino
Ler estado de um botão Não existe diretamente — requer driver de hardware ou biblioteca do OS digitalRead(pino) — retorna HIGH ou LOW
Ligar/desligar saída Manipulação direta de registradores do chip digitalWrite(pino, HIGH/LOW)
Proteger pino de entrada Circuito externo obrigatório INPUT_PULLUP ativa resistor interno
Detectar borda (clique) Comparar estado anterior com estado atual — igual Comparar estado anterior com estado atual — igual
Usar structs para organizar ✅ — padrão do curso ✅ — funciona identicamente
Múltiplas entradas Array de descritores de arquivo ou registradores Array de pinos + for para inicializar

Quiz — Aula 42

Teste o que você aprendeu sobre entradas e saídas digitais:

1. O que acontece com um pino configurado como INPUT simples quando o botão está solto?

2. Com INPUT_PULLUP, qual é o valor retornado por digitalRead() quando o botão está pressionado?

3. O que é "bounce" em um botão físico?

4. No código de debounce, para que serve a variável ultimoBounce?

5. O que é "borda de descida" na leitura de um botão com pull-up?

6. Por que usar uma struct Botao em vez de variáveis soltas quando há múltiplos botões?

7. Qual é a forma correta de inicializar três pinos como saída usando um array?

8. Qual expressão do C que você já conhece é usada no exemplo para alternar o estado do LED?