Robótica com C — Bloco 1
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) |
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().
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]
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.
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.
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.
| 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 |
Teste o que você aprendeu sobre entradas e saídas digitais: