Robótica com C — Bloco 3: Projetos Práticos
Uma luz que acende sozinha quando detecta movimento e apaga automaticamente após alguns segundos sem atividade. O mesmo sistema usado em corredores, banheiros, garagens e áreas de segurança.
É o primeiro projeto do Bloco 3 e introduz o componente mais importante da automação residencial: o módulo relay — que permite ao Arduino controlar cargas de 110V/220V com segurança.
Conexão com o Bloco 2: este projeto usa exatamente os padrões que você dominou nas aulas 44–47: millis() para o temporizador, enum para os estados do sistema (AGUARDANDO, ATIVO, APAGANDO), e a arquitetura em camadas com sensores, estado e saída física separados.
O relay (relé) é um interruptor eletromecânico controlado eletricamente. Por dentro, um eletroímã é energizado por um sinal de 5V do Arduino, o que atrai uma alavanca metálica e fecha (ou abre) um contato de alta tensão. O circuito do Arduino e o circuito da lâmpada são completamente isolados — sem risco de 220V chegar ao microcontrolador.
| Aspecto | Detalhe |
|---|---|
| Tensão de controle | 5V (compatível direto com Arduino) |
| Lógica de acionamento | Ativo em LOW — digitalWrite(RELAY, LOW) liga a carga |
| Capacidade da carga | Até 10A / 250VAC ou 10A / 30VDC (verificar no módulo) |
| Isolamento | Optoacoplador — Arduino completamente isolado da rede elétrica |
| Corrente de controle | ~15–20 mA — dentro do limite seguro de um pino digital |
| Tempo de resposta | ~10 ms — adequado para automação residencial |
⚠️ Lógica invertida — muito importante: a maioria dos módulos relay de 1 canal para Arduino usa lógica ativa em LOW. Isso significa:
digitalWrite(PINO_RELAY, LOW) → relay aciona → lâmpada ligadigitalWrite(PINO_RELAY, HIGH) → relay desaciona → lâmpada apagaIsso parece contraintuitivo, mas é assim por projeto — garante que a carga fique desligada se o Arduino reiniciar ou perder energia. Verifique sempre o datasheet do seu módulo específico.
⚠️ Segurança com 110V/220V: nos projetos em simulação (Wokwi) e em testes iniciais, substitua a lâmpada real por um LED de 5V para validar o código. Só trabalhe com tensão de rede em bancada adequada, com isolamento e nunca com o circuito energizado ao fazer conexões físicas. Em caso de dúvida, simule no Wokwi e peça auxílio de alguém com experiência em elétrica.
Para simulação no Wokwi, a "lâmpada" será representada por um LED comum com resistor — o relay é controlado exatamente da mesma forma que controlaria uma lâmpada real:
| Componente | Pino / Conexão | Função |
|---|---|---|
| Sensor PIR | OUT → pino 2 | Detecta movimento — HIGH quando ativo |
| Módulo Relay | IN → pino 7 | Controla a lâmpada — lógica invertida |
| LED (simula lâmpada) | Saída NO do relay → LED → GND | Representa a carga controlada |
| LED de status | pino 8 → LED vermelho → GND | Indica que o sistema está no modo ATIVO |
| Potenciômetro (opcional) | cursor → A0 | Ajusta o tempo de espera em tempo real |
// Ligação resumida: // PIR: VCC→5V, GND→GND, OUT→pino 2 // Relay: VCC→5V, GND→GND, IN→pino 7 // LED lâmpada: relay NO → resistor 220Ω → LED → GND // LED status: pino 8 → resistor 220Ω → LED vermelho → GND // Potenc: terminal esq→5V, terminal dir→GND, cursor→A0
O projeto usa três estados: o sistema aguarda movimento, está ativo (lâmpada acesa, temporizador rodando) ou em transição de apagamento. O tempo de espera é ajustável via potenciômetro:
/* * ===================================================== * Projeto 01 — Luz automática com PIR e Relay * ===================================================== * AGUARDANDO → movimento detectado → ATIVO * ATIVO → sem movimento por T segundos → AGUARDANDO * T (tempo de espera) ajustável via potenciômetro * ===================================================== */ // ── Pinos ────────────────────────────────────────── const int PINO_PIR = 2; // saída do sensor PIR const int PINO_RELAY = 7; // controle do relay (lógica invertida) const int PINO_STATUS = 8; // LED de status do sistema const int PINO_POT = A0; // potenciômetro de tempo // ── Configuração ─────────────────────────────────── const unsigned long T_MIN = 5000; // 5s mínimo const unsigned long T_MAX = 30000; // 30s máximo // ── Máquina de estados ───────────────────────────── typedef enum { AGUARDANDO, ATIVO } EstadoLuz; EstadoLuz estado = AGUARDANDO; // ── Temporização ─────────────────────────────────── unsigned long ultimoMovimento = 0; // quando foi o último movimento // ════════════════════════════════════════════════════ // Lê e converte o tempo de espera do potenciômetro // ════════════════════════════════════════════════════ unsigned long lerTempoEspera() { int pot = analogRead(PINO_POT); // 0–1023 return map(pot, 0, 1023, T_MIN, T_MAX); // 5s–30s } // ════════════════════════════════════════════════════ // Controla relay e LED de status // Relay: LOW = aciona (lógica invertida) // ════════════════════════════════════════════════════ void setLuz(bool ligar) { digitalWrite(PINO_RELAY, ligar ? LOW : HIGH); // invertido! digitalWrite(PINO_STATUS, ligar ? HIGH : LOW); } // ════════════════════════════════════════════════════ // Processa o sensor PIR e o temporizador // ════════════════════════════════════════════════════ void processarPIR() { bool movimento = (digitalRead(PINO_PIR) == HIGH); unsigned long agora = millis(); unsigned long tempoEspera = lerTempoEspera(); switch (estado) { case AGUARDANDO: if (movimento) { estado = ATIVO; ultimoMovimento = agora; setLuz(true); Serial.print("Movimento! Luz ON — espera: "); Serial.print(tempoEspera / 1000); Serial.println("s"); } break; case ATIVO: if (movimento) { // Reinicia o temporizador enquanto há movimento ultimoMovimento = agora; } // Verifica se o tempo de espera expirou if (agora - ultimoMovimento >= tempoEspera) { estado = AGUARDANDO; setLuz(false); Serial.println("Sem movimento — Luz OFF"); } break; } } // ════════════════════════════════════════════════════ // SETUP e LOOP // ════════════════════════════════════════════════════ void setup() { pinMode(PINO_PIR, INPUT); pinMode(PINO_RELAY, OUTPUT); pinMode(PINO_STATUS, OUTPUT); // Garante que o relay começa desligado (HIGH = desligado na lógica invertida) digitalWrite(PINO_RELAY, HIGH); digitalWrite(PINO_STATUS, LOW); Serial.begin(9600); Serial.println("=== Luz Automática com PIR ==="); Serial.println("Aguardando movimento..."); Serial.println("(Ajuste o potenciômetro para mudar o tempo de espera)"); } void loop() { processarPIR(); // Imprime o tempo de espera configurado a cada 5s (debug) static unsigned long ultimoDebug = 0; if (millis() - ultimoDebug >= 5000) { ultimoDebug = millis(); if (estado == AGUARDANDO) { Serial.print("Aguardando... (tempo configurado: "); Serial.print(lerTempoEspera() / 1000); Serial.println("s)"); } } }
=== Luz Automática com PIR ===
Aguardando movimento...
(Ajuste o potenciômetro para mudar o tempo de espera)
Aguardando... (tempo configurado: 15s)
Movimento! Luz ON — espera: 15s
Movimento! Luz ON — espera: 15s
Sem movimento — Luz OFF
Aguardando... (tempo configurado: 8s)
No estado ATIVO, cada leitura de movimento chama ultimoMovimento = agora. Se o PIR continua detectando movimento, a condição agora - ultimoMovimento >= tempoEspera nunca será verdadeira — a luz permanece acesa pelo tempo necessário. Sem isso, a luz apagaria no meio de uma conversa ou passagem lenta.
digitalWrite(PINO_RELAY, HIGH) no setup()?Com lógica invertida, HIGH = relay desligado. Inicializar explicitamente garante que a lâmpada não ficará ligada durante o boot do Arduino (que pode demorar centenas de milissegundos). É uma medida de segurança — o estado inicial nunca deve ser ambíguo.
Porque queremos que o potenciômetro ajuste o tempo em tempo real. Se você girar o potenciômetro enquanto a luz está acesa, o novo tempo será aplicado ao próximo ciclo. lerTempoEspera() é chamada dentro do switch, então cada verificação usa o valor mais recente.
Padrão "temporizador reiniciável": guardar ultimoMovimento e comparar com millis() é um padrão que aparece em todos os projetos de automação com timeout — portão que fecha automaticamente, ar condicionado que desliga por inatividade, alarme que espera antes de acionar. Memorize esse padrão.
A luz só deve acionar de noite. Adicione um LDR e uma condição extra em processarPIR():
// Adicione no topo: const int PINO_LDR = A1; const int LIMIAR_NOTURNO = 300; // abaixo = escuro = noite // Dentro de case AGUARDANDO, modifique a condição: bool eNoite = (analogRead(PINO_LDR) < LIMIAR_NOTURNO); if (movimento && eNoite) { // Aciona apenas se for noite E houver movimento estado = ATIVO; ultimoMovimento = agora; setLuz(true); Serial.println("Noite + movimento → Luz ON"); }
Integre o LCD I2C da Aula 45 para exibir o estado e o tempo restante — exatamente como no semáforo:
// Após incluir Wire.h e LiquidCrystal_I2C.h:
void atualizarLCD() {
static unsigned long ultimaTela = 0;
if (millis() - ultimaTela < 500) return;
ultimaTela = millis();
lcd.setCursor(0, 0);
lcd.print(estado == ATIVO ? "LUZ: LIGADA " : "LUZ: AGUARDANDO ");
lcd.setCursor(0, 1);
if (estado == ATIVO) {
unsigned long restante = lerTempoEspera() - (millis() - ultimoMovimento);
lcd.print("Desliga em: ");
lcd.print(restante / 1000);
lcd.print("s ");
} else {
lcd.print("Tempo: ");
lcd.print(lerTempoEspera() / 1000);
lcd.print("s ");
}
}
Adicionar controle remoto por app é a transição natural para o Projeto 03. Basta incluir um processarSerial() no loop():
// Comandos para adicionar: // "L" → força ligar manualmente // "D" → força desligar manualmente // "A" → volta ao modo automático (PIR) typedef enum { AUTO_PIR, MANUAL_ON, MANUAL_OFF } ModoControle; ModoControle modo = AUTO_PIR; // No loop(): se modo == AUTO_PIR, chama processarPIR() // Caso contrário, o estado foi forçado manualmente
Construa incrementalmente. Comece com o código base, teste no Wokwi, depois adicione o LDR, depois o LCD, depois o Bluetooth. Cada passo é uma camada. Se algo quebrar, você sabe exatamente onde procurar.
| Componente | Especificação | Onde encontrar (BR) | Preço estimado |
|---|---|---|---|
| Arduino Uno ou Nano | ATmega328P | Mercado Livre, Robocore, FilipeFlop | R$ 25–60 |
| Sensor PIR HC-SR501 | Alcance ~7m, ângulo 120° | Mercado Livre, lojas de eletrônica | R$ 10–20 |
| Módulo Relay 1 canal | 5V, 10A/250VAC, com optoacoplador | Mercado Livre, FilipeFlop | R$ 8–15 |
| Potenciômetro 10kΩ | Linear | Qualquer loja de eletrônica | R$ 2–5 |
| LED + resistor 220Ω | Para teste sem rede elétrica | Qualquer loja de eletrônica | R$ 1–2 |
| Protoboard + jumpers | 400 pontos ou mais | Mercado Livre, Robocore | R$ 15–25 |
Kit recomendado para todos os projetos do Bloco 3: um kit iniciante de Arduino com protoboard, jumpers, LEDs, resistores, potenciômetro e sensores básicos cobre os primeiros 6 projetos. Procure por "kit Arduino starter" — custam entre R$80–150 e incluem a maioria dos componentes necessários.
| Conceito | Aula de origem | Onde aparece neste projeto |
|---|---|---|
enum de estados |
Aula 44 | typedef enum { AGUARDANDO, ATIVO } |
Temporizador com millis() |
Aula 41b / 44 | ultimoMovimento + comparação com tempoEspera |
| Sensor PIR digital | Aula 41c | digitalRead(PINO_PIR) == HIGH |
Leitura analógica + map() |
Aula 43 | Potenciômetro → lerTempoEspera() |
| Arquitetura em camadas | Aula 47 | processarPIR(), setLuz(), lerTempoEspera() |
| Inicialização segura de pinos | Aula 42 | digitalWrite(PINO_RELAY, HIGH) no setup() |
Debug com variável static |
Aula 45 | Print periódico a cada 5s no loop() |
Teste sua compreensão do relay e do projeto de luz automática: