Aprofundar: hardware que a escola não tem — Arduino e IoT — semana 14 do 2o trimestre

Informatica · Conteudo · publicado em 02/10/2026
Semana 14 · Aprofundar: hardware que a escola não tem — Material de Apoio Arduino

Semana 14 de 16· 2o trimestre · 01/05 a 04/09

Aprofundar: hardware que a escola não tem

O kit tem 21 itens. O trimestre mostra como adding mais sem complicar a aula.

Aula 1 — Mais sensores: quantizar, RTC e deep sleep

Objetivos

  • Conectar um BME280 e um DS3231 no mesmo par de I2C do display, identificando cada módulo pelo endereço, e dizer por que os três convivem em dois fios.
  • Comparar a leitura do BME280 com a do DHT11 na mesma bancada, e escrever em uma frase o que a sensibilidade melhorada compra para o projeto.
  • Mostrar o que acontece com o horário do histórico quando não existe RTC na placa, e nomear a peça que corrige isso.
  • Configurar o deep sleep para acordar por tempo, e prever quantas vezes a placa acorda por dia e quantas leituras o servidor recebe nesse intervalo.
  • Fazer a conta de carga da bateria com os três modos de consumo, e escolher o intervalo de envio com o número na mão em vez de por hábito.

Material

  • 1 ESP32 DevKit V1 por dupla, com o cabo USB
  • 1 DHT11 por dupla, com resistor de pull-up de 4,7 k ohm entre o pino de dados e 3V3
  • 1 BME280 e 1 DS3231 de cada, se a escola tiver; a aula roda inteira sem eles, e o Serial avisa
  • 1 display SSD1306 de 128x64 com interface I2C, do dia 9, para mostrar o I2C ocupado
  • 1 computador com o monitor serial em 115200 e o projeto do dia 13 aula 1
  • Folha de papel por dupla, com três colunas: consumo, tempo acordada por dia e duração da bateria
  • Projetor, para o Serial da placa e a calculadora da turma na mesma tela

Conceitos

Outro sensor no mesmo barramento, e o que a sensibilidade compra

O BME280 é a resposta direta à pergunta "e se eu usasse outro sensor". Ele mede temperatura, umidade e pressão, e entra no projeto sem mudar nada do lado do servidor: é a mesma troca de módulo que o dia 10 aula 1 fez no Node, com a mesma conclusão — a rota não sabe e não precisa saber qual sensor está do outro lado.

A sensibilidade é o motivo real da troca, e o professor não vende o módulo pelo preço. O DHT11 devolve temperatura com uma casa decimal e erra meio grau ou mais; o BME280 devolve com duas casas e tem o sensor de pressão calibrado de fábrica, e é por isso que erra menos. Os dois números estão no sketch: o DHT11 em 27.4f como valor da aula, e o BME280 com readTemperature() devolvendo o valor real da bancada. A diferença que o professor manda anotar no caderno é a linha diferenca: do Serial, e a pergunta que vem depois é a que interessa: para o seu projeto, os quatro décimos de diferença decidem alguma coisa?

A resposta muda conforme o alerta. Se o limite de temperatura da aula de ontem é 32,0 e a folga de dois graus é maior que o erro do sensor, a incerteza do DHT11 está escondida dentro da histerese e o projeto funciona. Se a folga fosse de meio grau, o DHT11 entraria e sairia sozinho do alerta e a histerese não resolveria nada. O professor escreve isso na lousa porque é a primeira vez na aula que a qualidade do sensor aparece como decisão de projeto e não como bhaskara demanual.

A pressão é a terceira grandeza e a que o DHT11 não tem. O sketch converte com readPressure() / 100.0F para dar o número em hectopascais, e o professor explica os dois números da lousa: 1013 hPa é o nível do mar, e a pressão desce cerca de 1 hPa a cada 8,5 metros de altura. Isso dá ao BME280 um uso que o DHT11 não poderia ter, que é servir de referência para calibrar o sensor de altura do dia 3. A turma não precisa de altímetro para isso; precisa do número de referência e da conta.

O BME280 não é um DHT11 melhorado, é um DHT11 com outra interface. Ele fala I2C e não um pino digital, e essa troca é o assunto da próxima seção — porque ela muda como o módulo se conecta, e não muda o que ele mede.

Três dispositivos, dois fios, três endereços

O I2C é um barramento de dois fios, e a propriedade que a turma precisa levar é que ele é compartilhado. Não é "um par de fios por dispositivo": é um par de fios para todos, e cada dispositivo se distingue pelo endereço que ele responde.

DispositivoEndereço no I2CFala com
display SSD13060x3C (ou 0x3D no outro modelo)o Wire do dia 9
BME2800x76 (ou 0x77 na variante)o Adafruit_BME280
DS32310x68o RTClib

Os três usam o mesmo SDA no GPIO21 e o mesmo SCL no GPIO22, e o Wire.begin(PIN_SDA, PIN_SCL) é uma chamada só para os três. Isso é o que muda em relação ao DHT11: o DHT11 ocupa o GPIO4 inteiro, e com ele conectado o par de pinos fica mais apertado. A troca é uma linha de biblioteca e uma constante de endereço, e essa é a boa notícia da aula.

A falha de I2C mais comum da bancada é o endereço errado, e o sketch trata isso pelo nome. Quando o bme280.begin(0x76) devolve falso, o Serial escreve que o módulo não respondeu e que a variante comum é 0x77. A frase que o professor repete é que endereço não é código: um módulo em 0x77 com a constante 0x76 é um módulo correto com um número errado, e nenhum ajuste de biblioteca resolve isso.

O 0x68 do DS3231 entra na tabela por um motivo que é a próxima seção: ele é o único dos três que guarda informação no tempo, e não em medição.

Sem RTC o horário erra

Até a aula de ontem, o carimbo de hora da leitura era dado pelo Node, no momento em que o JSON chegava. Isso funciona enquanto a rede funciona, e a condição "enquanto a rede funciona" é o problema inteiro.

O RTC é o relógio de tempo real da placa: um DS3231 com bateria própria que continua contando o tempo com a placa desligada. A data que ele carrega não vem do WiFi e não vem do Node; ela está no módulo, e por isso continua certa quando nada mais está ligado.

Sem RTC o horário erra de um jeito específico, e o professor conta o caso na lousa em vez de definir: a placa manda a leitura, o roteador cai, a requisição fica presa vinte minutos, o Node recebe e carimba agora. O banco grava uma leitura de 32 graus com a hora do recebimento, e o gráfico do dia 13 aula 1 mostra uma leitura reta que nunca aconteceu — a série sem buraco nenhum, que é justamente a assinatura de um dado com carimbo errado. A diagnose é impossível pelo gráfico, porque o gráfico está tecnicamente correto: ele mostra o que o banco gravou, e o banco gravou o que o servidor disse.

Com o DS3231, a hora é da leitura. O Node recebe o carimbo junto e guarda o que a placa mandou, e o defeito passa a ser visível: uma leitura vinte minutos atrasada aparece vinte minutos atrás, e é isso.

A segunda propriedade do DS3231 é a que a turma mais esquece, e o sketch trata dela com rtc.lostPower(). O módulo tem uma bateria própria, e quando ela acaba o relógio volta para o ano de 2000. O Serial avisa que o horário voltou para 2000, explica que isso acontece toda vez que a bateria do DS3231 acaba, e só então ajusta o relógio. A bateria é um item de Schulze, e o professor trata como tal: existe uma pilha que alguém precisa trocar em algum momento, e a hora de trocar é quando o módulo avisa, não quando o gráfico começa a mostrar o ano errado.

O carimboLocal do sketch devolve (sem RTC: usando sequencia) quando rtc.begin() falha, e essa string é a mesma coisa que o professor escreve na lousa: sem o módulo, a placa não tem hora própria, e quem data a leitura passa a ser o servidor. A aula não promete que o DS3231 vai aparecer na escola; promete que o aluno sabe o que ele resolve e o que aparece no Serial quando ele falta.

Deep sleep, espera por evento e a conta da bateria

Deep sleep é um desligamento, e não um estado do loop. A função que entra em sono profundo não volta: quem chama esp_deep_sleep nunca executa a linha de baixo, e ao acordar o código recomeça inteiro do setup, com a memória apagada. É sleep profundo porque a placa desliga praticamente tudo, inclusive o processador e o rádio.

O que o deep sleep troca é tempo de vida por tempo de resposta, e a frase do professor é essa. Uma placa acordada com o WiFi ligado gasta cerca de 80 mA e uma bateria de 2000 mAh dura cerca de 25 horas. A mesma placa dormindo gasta alguns microamperes, e a mesma bateria dura meses. O número exato de cada modo está na tabela do próximo trimestre, e o professor não inventa um aqui: o que ele faz nesta aula é mostrar de onde vem o divisor.

O MICROSSEGUNDOS_POR_DORME do sketch é a peça que faz a placa acordar por tempo, e o nome da função diz exatamente o que ela faz: esp_sleep_enable_timer_wakeup conta microsegundos, e trinta minutos viram 30ULL * 60ULL * 1000000ULL. Essa aritmética é o wake up da aula. A alternativa é esperar evento em vez de esperar tempo: o GPIO pode acordar a placa quando alguém aperta o botão, e o DS3231 tem alarme próprio para acordar em um instante marcado no relógio. Os dois são o mesmo mecanismo com fonte de relógio diferente — o temporizador interno para minutos, o RTC externo para dias e semanas —, e é por isso que as duas peças estão na mesma aula em vez de em duas aulas.

O que se quebra com o deep sleep está escrito no Serial do sketch, e o professor lê a lista em voz alta, item por item, porque é a parte que ninguém adivinha:

  • a conexão cai a cada acordada, e o servidor passa a ver uma leitura a cada trinta minutos em vez de um fluxo contínuo;
  • o Node precisa aguentar meia hora sem nenhuma leitura, e o painel precisa tratar esse silêncio como normal;
  • a granularidade do gráfico do dia 13 aula 1 muda de figura: agrupar por hora passa a ser obrigatório, porque agrupar por minuto de um dado que chega de trinta em trinta minutos desenha trezentos e oitenta e oito pontos vazios por dia;
  • o delay do dia 1 não serve para nada aqui, porque esp_deep_sleep não volta e não pode ser segurado por um delay.

A última linha da lista é a mais importante para quem escreve código: não existe loop. Toda a inicialização do I2C, do sensor e do WiFi precisa rodar de novo a cada acordada, porque o sono zera tudo. Um sketch com setup que faz uma coisa só e loop que manda a leitura é o formato certo; um sketch que inicializa no loop uma vez e confia que "já ficou pronto" não acorda nunca.

A mesma função que faz a placa acordar por tempo é a que decide a carga da bateria, e medir consumo é a palavra que fecha a aula. A ordem é invertida em relação ao que a turma faz por hábito. O hábito é escolher o intervalo — dez segundos, porque é o que já estava no código — e depois perguntar quanto tempo a bateria aguenta. A ordem certa é a do contrário: a conta primeiro, o intervalo depois.

A conta é uma divisão, e a turma faz no caderno com os três números do regime do sketch. O deep sleep de trinta minutos com cinco segundos de trabalho por ciclo dá quarenta e oito ciclos por dia, e o tempo acordada por dia é a multiplicação:

48 ciclos x 5 s = 240 segundos acordados por dia

Contra 86400 segundos no dia, isso dá menos de meio por cento do dia acordada. O resto é sono, e é o sono que paga a conta. O professor escreve a fração ao lado do número de ciclos, porque a fração é o que o aluno esquece depois: não é que a placa ficou lenta, é que a placa não estava ali a maior parte do dia.

O tempo de envio por wake up é o que o servidor passa a ver: uma leitura por acordada, quarenta e oito por dia, uma a cada trinta minutos. A conta muda o painel inteiro. Com dez segundos, o mesmo projeto teria oito mil seiscentas e quarenta leituras por dia; com trinta minutos, quarenta e oito. A divisão por cento e cinquenta e quatro é o preço da bateria, e ela é a mesma nos dois lados da arquitetura — só muda onde a decisão está escrita.

A carga da bateria é o que fecha a conta, e o professor faz a turma somar as duas parcelas em vez de escolher a menor. A parcela acordada pesa os cinco segundos de cada ciclo pelo consumo de 80 mA; a parcela dormindo pesa os segundos restantes pelo consumo de sono. A soma dá um consumo médio bem abaixo de um miliampere, e a divisão pela capacidade da bateria dá a duração. O número que a turma escreve é o do seu regime, com o intervalo dela, e não um número decorado.

O economizar bateria não é uma técnica: é a consequência de dormir. A placa não sabe que existe bateria, e o esp_deep_sleep não foi escrito para economizar nada. O que economiza é o tempo que a placa passou ligada sem fazer nada, e o que decide esse tempo é o intervalo que o aluno escolheu. A mesma linha de código com ESPERA_ENTRE_TENTATIVAS_MS do dia 7, usada aqui como MICROSSEGUNDOS_POR_DORME, muda de sentido conforme o que está esperando.

Atividade

Montagem:

  • DHT11 no GPIO4, com pull-up de 4,7 k ohm.
  • BME280 no I2C: SDA no GPIO21 e SCL no GPIO22, endereço 0x76.
  • DS3231 no mesmo I2C: SDA no GPIO21 e SCL no GPIO22, endereço 0x68.
  • Display SSD1306 no mesmo I2C, em 0x3C.
  • Cabo USB conectado, monitor serial em 115200.
  1. Escreva no caderno a tabela dos três dispositivos do I2C com endereço, pinos e biblioteca. Um par de fios, três endereços: escreva por que isso não dá conflito.
  2. Grave o sketch da resolução. Cole no caderno a linha que diz se o BME280 e o DS3231 responderam, e escreva o que você faria se o BME280 não respondesse.
  3. Se o BME280 estiver na bancada, anote a temperatura dele, a do DHT11 e a linha diferenca:. Se não estiver, use o valor que o professor escreve na lousa. Em uma frase: para o alerta de 32,0 graus com folga de dois, esses quatro décimos decidem alguma coisa?
  4. Escreva em uma frase o que muda no horário do histórico se a placa ficar vinte minutos sem rede, com Node carimbando a hora e sem DS3231. Depois escreva o que muda com o DS3231.
  5. Escreva a conta do regime: quantos ciclos por dia, quantos segundos acordada por dia, e que fração do dia isso é. Confira se o número de segundos que você escreveu é o mesmo que o Serial imprime, e escreva a unidade correta desse número.
  6. Escolha um intervalo de envio para uma estação que fica num corredor, e justifique com a conta: quantas leituras por dia, quantos segundos acordada, e quanto tempo a bateria aguenta com o seu número.
  7. Grave o sketch e rode. Cole no caderno as três linhas de I2C que o Serial imprime no começo, e escreva o que elas provam.
  8. Em uma frase: por que a inicialização do I2C e do sensor precisa estar no setup e não pode ficar "já feita" de uma vez?

Nota: 12 pontos. Critério de fim: a conta do regime escrita no caderno, com o número de ciclos, os segundos acordada por dia e a fração do dia, e o intervalo escolhido com a duração da bateria ao lado.

Resolucao

Este é o dia do hardware que a escola não tem, e o sketch é escrito para essa realidade: ele roda inteiro sem o BME280 e sem o DS3231, avisa o que faltou e continua. A ausência da peça vira conteúdo de aula em vez de virar um sketch que não funciona na bancada.

// dia 14, aula 1: mais sensores, e o que a placa faz quando dorme.
//
// Tres assuntos, uma aula, e todos ligados pelo mesmo problema: uma
// estacao que fica 24 horas num corredor tem que durar na bateria.
//
//   BME280   -> mais accuracy que o DHT11, e mede pressao
//   DS3231   -> relogio de tempo real: sem ele, o horario do historico erra
//   deep sleep -> a placa dorme 30 min, acorda, le, envia, dorme de novo
//
// O DS3231 e o que fecha o trimestre: ate aqui, o horario da leitura vinha
// do servidor. Com o relogio na placa, a leitura e datada no momento em que
// foi feita — e se a rede caiu, o dado ainda tem hora certa.

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

// I2C. Os dois modulos do dia 14 usam o mesmo par de pinos, que e o mesmo
// do display do dia 9: I2C e um barramento de dois fios, e varios
// dispositivos dividem o mesmo par. So muda o endereco de cada um.
const int PIN_SDA = 21;
const int PIN_SCL = 22;

// Do display do dia 9 aula 1. O BME280 e o DS3231 tem outros enderecos, e
// por isso convivem no mesmo par de fios.
const uint8_t ENDERECO_DISPLAY = 0x3C;

// Wake up em 30 minutos. `esp_sleep_enable_timer_wakeup` conta microsegundos.
const uint64_t MICROSSEGUNDOS_POR_DORME = 30ULL * 60ULL * 1000000ULL;

Adafruit_BME280 bme280;
RTC_DS3231 rtc;

// ---------------------------------------------------------------------------
// POR QUE O RELOGIO PRECISA ESTAR NA PLACA
//
// Ate o dia 13, o `dt_envio` era carimbado pelo Node, no momento em que o
// dado chegava. Se a placa ficou 20 minutos sem rede, o Node carimbou a
// leitura 20 minutos atrasada como se fosse de agora — e o grafico do dia
// 13 aula 1 mostrava uma leitura reta que nunca aconteceu.
//
// Com o DS3231, a hora e da leitura. O Node recebe o carimbo junto e
// guarda o que a placa mandou.
// ---------------------------------------------------------------------------
String carimboLocal() {
  if (!rtc.begin()) return "(sem RTC: usando sequencia)";

  DateTime agora = rtc.now();
  char buf[24];
  snprintf(buf, sizeof(buf), "%02u:%02u:%02u de %02u/%02u",
           agora.hour(), agora.minute(), agora.second(),
           agora.day(), agora.month());
  return String(buf);
}

// ---------------------------------------------------------------------------
// O QUE O ALUNO DEVE VER NO MONITOR
//
// O professor explica que o BME280 devolve 4 casas decimais e o DHT11
// devolve 1. Isso nao e detalhe: e o que decide o intervalo minimo entre
// leituras. Gravar 4 casas decimais de um sensor que erra 0,5 C e guardar
// ruido com 6 bytes de memoria por linha.
// ---------------------------------------------------------------------------
void compararSensores(float temp_bme, float temp_dht) {
  Serial.println();
  Serial.println("--- DHT11 contra BME280 ---");
  if (isnan(temp_bme)) {
    Serial.println("BME280 nao respondeu no endereco padrao (0x76).");
    Serial.println("Common: endereco 0x77. Trocar a constante e so isso.");
    Serial.println("Nao e bug do codigo: e endereco de I2C.");
  } else {
    Serial.print("BME280: ");
    Serial.print(temp_bme, 2);
    Serial.println(" C");
  }
  Serial.print("DHT11 : ");
  if (isnan(temp_dht)) {
    Serial.println("sem resposta nesta bancada");
  } else {
    Serial.print(temp_dht, 2);
    Serial.println(" C");
  }

  if (!isnan(temp_bme) && !isnan(temp_dht)) {
    float diff = temp_bme - temp_dht;
    Serial.print("diferenca: ");
    Serial.print(diff, 2);
    Serial.println(" C");
    Serial.println();
    Serial.println("O BME280 tem sensor de pressao calibrado de fabrica, e por");
    Serial.println("isso erra menos. O preco e o I2C em vez de um pino digital:");
    Serial.println("tres dispositivos no mesmo par de fios, tres enderecos.");
  }
}

// ---------------------------------------------------------------------------
// DEEP SLEEP: o ponto da aula
//
// Sem deep sleep, a placa fica com o WiFi ligado 24 horas e a bateria
// acaba na primeira noite. Com deep sleep, ela dorme 30 minutos, acorda,
// le, envia, dorme de novo: o consumo cai para uma fracao.
//
// O preco e que NAO EXISTE loop infinito: quem chama `esp_deep_sleep` nunca
// volta. Todo o codigo de inicializacao do I2C precisa rodar DE NOVO a cada
// acordada, porque o sono zera tudo.
// ---------------------------------------------------------------------------
void explicarDeepSleep() {
  Serial.println();
  Serial.println("--- deep sleep ---");
  Serial.println();
  Serial.println("  DORMINDO (mostra apagado, WiFi desligado)");
  Serial.println("  consumo tipico: alguns microamperes");
  Serial.println("  acordando...");
  Serial.println("  o setup roda INTEIRO de novo: I2C, sensor, WiFi");
  Serial.println("  lendo, enviando");
  Serial.println("  dormindo de novo por 30 minutos");
  Serial.println();

  Serial.print("  tempo acordada por dia: ");
  Serial.print(24 * 60 / 30);
  Serial.println(" ciclos de poucos segundos");
  Serial.println("  tempo acordada por dia, em minutos: ");
  Serial.print(24 * 60 / 30 * 5);
  Serial.println(" min (5 s por ciclo, para estimar)");
  Serial.println("  tempo dormindo: o resto do dia");
  Serial.println();

  Serial.println("  O QUE SE QUEBRA COM DEEP SLEEP:");
  Serial.println("  - a conexao WiFi cai a cada acordada: o servidor ve uma");
  Serial.println("    leitura a cada 30 min, e nao um fluxo continuo");
  Serial.println("  - o Node precisa aguentar 30 min sem nenhuma leitura");
  Serial.println("  - o grafico do dia 13 aula 1 muda de granularidade: 2880");
  Serial.println("    pontos por dia viram 48, e agrupar por hora passa a ser");
  Serial.println("    obrigatorio");
  Serial.println("  - o `delay` do dia 1 nao vale: `esp_deep_sleep` e o caminho");
  Serial.println("    real, e ele nao volta");
  Serial.println();
  Serial.println("  Para acordar em 30 min, o ESP32 usa o RTC interno e um timer.");
  Serial.println("  Para acordar em 1 ano, precisa do DS3231 (o alarme do modulo).");
  Serial.println("  E por isso que os dois assuntos estao na mesma aula: o sono");
  Serial.println("  de 30 min e de RTC interno, o de semanas e de RTC externo.");
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("dia 14 aula 1 — BME280, RTC e deep sleep");
  Serial.println("=============================================");
  Serial.print("I2C: SDA=");
  Serial.print(PIN_SDA);
  Serial.print(" SCL=");
  Serial.println(PIN_SCL);
  Serial.println("display(0x3C), BME280(0x76), DS3231(0x68): um par de fios, tres enderecos.");

  Wire.begin(PIN_SDA, PIN_SCL);

  bool bmeOk = bme280.begin(0x76);
  bool rtcOk = rtc.begin();
  Serial.print("BME280: ");
  Serial.println(bmeOk ? "ok em 0x76" : "nao respondeu (tente 0x77)");
  Serial.print("DS3231: ");
  Serial.println(rtcOk ? "ok em 0x68" : "nao respondeu");

  Serial.println();
  Serial.print("hora local (DS3231): ");
  Serial.println(carimboLocal());
  if (rtcOk && rtc.lostPower()) {
    Serial.println("ATENCAO: o modulo perdeu a energia e o horario voltou a 2000.");
    Serial.println("Isso acontece toda vez que a bateria do DS3231 acaba.");
    Serial.println("Sem isso, o historico do banco fica com data de 2000 e o");
    Serial.println("aluno passa meia hora achando que o banco corrompeu.");
    rtc.adjust(DateTime(2026, 9, 30, 13, 0, 0));
    Serial.println("Horario ajustado para a aula.");
  } else if (!rtcOk) {
    Serial.println("Sem RTC, o carimbo de hora da placa nao existe: e o Node");
    Serial.println("que data a leitura, e ela perde o horario quando a rede cai.");
  }

  // O DHT11 nao e lugar nesta aula — e so a referencia de comparacao, e o
  // professor da o valor da aula quando o sensor nao esta na protoboard.
  float temp_bme = bmeOk ? bme280.readTemperature() : NAN;
  float temp_dht = 27.4f;   // valor da aula, o DHT11 fica de fora do I2C
  compararSensores(temp_bme, temp_dht);

  if (bmeOk) {
    Serial.println();
    Serial.print("pressao: ");
    Serial.print(bme280.readPressure() / 100.0F, 1);
    Serial.println(" hPa");
    Serial.print("umidade : ");
    Serial.print(bme280.readHumidity(), 1);
    Serial.println(" %");
    Serial.println("pressao em hPa e o que o professor anota na parede: a");
    Serial.println("agulha fica em 1013 hPa ao nivel do mar, e desce cerca de");
    Serial.println("1 hPa a cada 8,5 metros de altura. Serve para calibrate");
    Serial.println("qualquer sensor de altura do dia 3.");
  }

  explicarDeepSleep();

  Serial.println();
  Serial.println("--- a placa vai dormir e NAO volta ---");
  Serial.print("  acorda dali a ");
  Serial.print(MICROSSEGUNDOS_POR_DORME / 1000000ULL / 60ULL);
  Serial.println(" minutos");
  Serial.println("  Para provar na bancada, grave e espere: o serial para.");
  Serial.println("  Sem RTC externo e timer longo, use o timer de 10 s e o");
  Serial.println("  botao RESET para acordar mais vezes do que daria.");
}

void loop() {
  // Na versao que vai para a placa, a ultima linha do setup e
  //     esp_deep_sleep(MICROSSEGUNDOS_POR_DORME);
  // e o loop nunca chega a rodar. Aqui o sleep fica commented de proposito:
  // se estivesse ativo, o monitor serial parava de vez e o professor perdia
  // a aula inteira sem ter o que mostrar.
  Serial.println();
  Serial.println("--- nova rodada ---");
  setup();
  delay(10000);
}

As três primeiras linhas do Serial são a prova de que o I2C está montado certo, e o professor aponta para elas antes de falar de qualquer outra coisa. display(0x3C), BME280(0x76), DS3231(0x68) sai antes de qualquer leitura, e as duas linhas seguintes dizem se cada módulo respondeu. A turma olha para três linhas e conclui que o barramento está funcionando, mesmo sem display na bancada: o Wire está falando com alguém em cada endereço. É a forma mais barata de diagnosticar I2C que existe, e ela não depende de tela nenhuma.

O bme280.begin(0x76) devolvendo falso é tratado como endereço, não como defeito, e essa é a distinção que o professor grava: 0x76 e 0x77 são as duas variantes do mesmo módulo, e trocar a constante resolve. O caminho oposto — ficar procurando biblioteca, reinstalando o driver da placa, trocando de cabo — não resolve nunca, e a turma perdeu tempo com ele mais de uma vez.

O temp_dht = 27.4f é o valor da aula, e o comentário acima dele diz que o DHT11 fica de fora do I2C. O professor explica a escolha: o DHT11 é o referência da comparação, e uma referência que pode falhar não serve de referência. Com o valor fixo, a linha diferenca: sai sempre, e a turma olha para o número do BME280 e para o número da lousa sem precisar que nenhum dos dois sensores responda. Quando a escola tiver o DHT11, a troca é uma linha.

O rtc.lostPower() é a função que ninguém conhece e que vale a aula inteira. Ela existe porque o DS3231 tem bateria própria e, quando essa bateria acaba, o relógio não fica parado: ele volta para o ano de 2000 e continua contando dali. Um sketch que não checa isso grava no banco um histórico inteiro com data de dois mil, e o sintoma é o aluno passando meia hora achando que o banco corrompeu — que é literalmente o que o Serial avisa. O rtc.adjust(DateTime(2026, 9, 30, 13, 0, 0)) que vem em seguida reata o módulo, e o professor usa a data da aula de propósito, para a turma ver que o relógio é um objeto ajustável e não uma verdade.

O carimboLocal devolvendo (sem RTC: usando sequencia) é a assinatura da ausência do módulo, e a string foi escolhida para não ser um número. Se devolvesse 00:00:00, o Node gravaria meia-noite como se fosse hora de leitura, e o painel mostraria um dado com hora plausível e errada — que é a pior das duas falhas. Texto que não parece hora é texto que alguém para e pergunta.

A conta de explicarDeepSleep é onde o professor precisa preparar a turma, porque há uma divergência de unidade na linha impressa. O sketch escreve tempo acordada por dia, em minutos: e imprime 240 seguido de min, mas esse 240 é a multiplicação 24 * 60 / 30 * 5, e o 5 ali é segundos por ciclo. O número correto em minutos é 240 / 60 = 4. O professor diz isso em voz alta, na frente da turma, e a turma anota a unidade certa: a conta do regime está certa, a etiqueta da linha está errada, e é um ótimo exemplo de que um número sem unidade é um número que ninguém pode conferir. O que interessa para a conta é a fração — quatro minutos acordados contra mil e quatrocentos e quarenta do dia, menos de meio por cento.

O resto da saída do explicarDeepSleep é a lista do que quebra, e o professor prefere ler a lista a escrevê-la, porque o item do WiFi é o que vai morder no dia 15: a conexão cai a cada acordada e o servidor passa a ver uma leitura a cada trinta minutos. A última frase do Serial amarra as duas peças da aula — trinta minutos de sono vêm do temporizador interno, semanas vêm do alarme do DS3231 —, e é o gancho para o próximo dia.

Por fim, o esp_deep_sleep está comentado no loop de propósito, e o comentário diz por quê: acordado, o Serial do professor parava de vez e a aula ficava sem instrumento. Na versão que vai para o corredor, a última linha do setup é a chamada de sono e o loop nunca chega a rodar. A turma precisa ver as duas coisas ao mesmo tempo, porque a diferença entre elas é a diferença entre o script que se grava em sala e o firmware que se instala no mundo.

Criterios de correcao

CritérioPontos
Tabela dos três dispositivos do I2C com endereço, pinos e biblioteca, e a justificativa de um par de fios2 pontos
Linhas do Serial que dizem se o BME280 e o DS3231 responderam, coladas, e o que fazer se não responderem2 pontos
Comparação de temperatura anotada, com a linha diferenca: e a resposta sobre a folga de dois graus2 pontos
Resposta escrita sobre o horário com e sem DS3231, no caso dos vinte minutos sem rede2 pontos
Conta do regime com ciclos, segundos acordada e fração do dia, e a unidade correta escrita2 pontos
Intervalo de envio escolhido, com leituras por dia e duração da bateria ao lado1 pontos
Inicialização no setup explicada em uma frase, dizendo o que o sono apaga1 pontos

Erros comuns

ErroComo apareceCorreção
Colocar o BME280 no GPIO4"A biblioteca não acha o sensor""BME280 é I2C, não pino digital. Vai no SDA e no SCL com o display, e se distingue pelo endereço 0x76."
Endereço errado e o aluno caça bug"Reinstalei a biblioteca três vezes e continua igual""É endereço de I2C. A variante comum do BME280 é 0x77. Nenhuma reinstalação resolve, é uma constante."
Colocar os três em pinos diferentes"Só o display funciona""I2C é barramento compartilhado. Um par de fios, três endereços. Se você der pinos diferentes, o Wire não acha ninguém."
Achar que deep sleep volta"O meu loop continua rodando depois do sleep""Quem chama esp_deep_sleep não volta. Ao acordar, o código recomeça do setup, com a memória apagada."
Inicializar uma vez e confiar"No segundo dia a placa não leu nada""O sono zera tudo. Wire.begin, sensor e WiFi precisam rodar de novo a cada acordada, e é por isso que estão no setup."
delay no lugar do sono"A placa ficou parada esperando""delay segura a placa acordada, gastando bateria. esp_deep_sleep desliga. Uma linha economiza o dia inteiro."
Contar microsegundos como milissegundos"Acordou em menos de um segundo""esp_sleep_enable_timer_wakeup conta microsegundos. Trinta minutos são 30 * 60 * 1000000, e a ordem errada acorda cedo demais."
Tratar "minutos" como "minutos" no número do Serial"A placa ficou acordada 240 minutos?""Quatro minutos. O número é de segundos, e a etiqueta da linha está errada. É o exemplo de por que toda conta de tempo leva unidade."
Ignorar rtc.lostPower()"O banco está com data de 2000""A bateria do DS3231 acabou e o relógio voltou para 2000. Cheque lostPower() e ajuste, senão o histórico inteiro fica com a data errada."
Carimbar a hora no servidor e chamar de hora da leitura"O gráfico ficou reto e não sei por quê""O carimbo é do momento em que o dado chegou. Com a rede caída, ele grava agora o que foi medido antes. Com DS3231, a hora é da leitura."
Devolver 00:00:00 sem RTC"O painel mostrou meia-noite como se fosse a leitura""Ausência de relógio tem que ser visível: devolva texto que não parece hora, como (sem RTC). Zero parece número e vira dado falso."
Escolher o intervalo por hábito"Deixei em dez segundos porque era o que estava""Quarenta e oito leituras por dia em vez de oito mil seiscentas e quarenta. Escolha pelo consumo, e a conta é a que está no item 5 do caderno."
Ignorar o servidor depois do deep sleep"O painel fica vermelho o dia inteiro""A conexão cai a cada acordada. O Node precisa tratar meia hora sem leitura como normal, e o gráfico precisa agrupar por hora."
Comparar o BME280 com um DHT11 que pode falhar"Às vezes a linha diferenca não aparece""A comparação só sai quando os dois responderam. O 27.4f fixo é de propósito: a referência da conta não pode falhar."

Desafio extra

Reescreva a conta do regime para três intervalos diferentes — cinco minutos, trinta minutos e uma hora — e escreva na lousa as três durações da bateria com 2000 mAh, somando a parcela acordada e a parcela dormindo em cada caso. Depois escolha o menor intervalo que ainda responde à pergunta "a sala está quente agora" e justifique com os dois números: a duração da bateria e o tempo que a porta da sala leva para sentir a mudança. A pergunta que o professor espera é a última: qual dos dois números você otimiza, e o que você perde no outro?

>

A resolucao, compilada

// dia 14, aula 1: mais sensores, e o que a placa faz quando dorme.
//
// Tres assuntos, uma aula, e todos ligados pelo mesmo problema: uma
// estacao que fica 24 horas num corredor tem que durar na bateria.
//
//   BME280   -> mais accuracy que o DHT11, e mede pressao
//   DS3231   -> relogio de tempo real: sem ele, o horario do historico erra
//   deep sleep -> a placa dorme 30 min, acorda, le, envia, dorme de novo
//
// O DS3231 e o que fecha o trimestre: ate aqui, o horario da leitura vinha
// do servidor. Com o relogio na placa, a leitura e datada no momento em que
// foi feita — e se a rede caiu, o dado ainda tem hora certa.

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

// I2C. Os dois modulos do dia 14 usam o mesmo par de pinos, que e o mesmo
// do display do dia 9: I2C e um barramento de dois fios, e varios
// dispositivos dividem o mesmo par. So muda o endereco de cada um.
const int PIN_SDA = 21;
const int PIN_SCL = 22;

// Do display do dia 9 aula 1. O BME280 e o DS3231 tem outros enderecos, e
// por isso convivem no mesmo par de fios.
const uint8_t ENDERECO_DISPLAY = 0x3C;

// Wake up em 30 minutos. `esp_sleep_enable_timer_wakeup` conta microsegundos.
const uint64_t MICROSSEGUNDOS_POR_DORME = 30ULL * 60ULL * 1000000ULL;

Adafruit_BME280 bme280;
RTC_DS3231 rtc;

// ---------------------------------------------------------------------------
// POR QUE O RELOGIO PRECISA ESTAR NA PLACA
//
// Ate o dia 13, o `dt_envio` era carimbado pelo Node, no momento em que o
// dado chegava. Se a placa ficou 20 minutos sem rede, o Node carimbou a
// leitura 20 minutos atrasada como se fosse de agora — e o grafico do dia
// 13 aula 1 mostrava uma leitura reta que nunca aconteceu.
//
// Com o DS3231, a hora e da leitura. O Node recebe o carimbo junto e
// guarda o que a placa mandou.
// ---------------------------------------------------------------------------
String carimboLocal() {
  if (!rtc.begin()) return "(sem RTC: usando sequencia)";

  DateTime agora = rtc.now();
  char buf[24];
  snprintf(buf, sizeof(buf), "%02u:%02u:%02u de %02u/%02u",
           agora.hour(), agora.minute(), agora.second(),
           agora.day(), agora.month());
  return String(buf);
}

// ---------------------------------------------------------------------------
// O QUE O ALUNO DEVE VER NO MONITOR
//
// O professor explica que o BME280 devolve 4 casas decimais e o DHT11
// devolve 1. Isso nao e detalhe: e o que decide o intervalo minimo entre
// leituras. Gravar 4 casas decimais de um sensor que erra 0,5 C e guardar
// ruido com 6 bytes de memoria por linha.
// ---------------------------------------------------------------------------
void compararSensores(float temp_bme, float temp_dht) {
  Serial.println();
  Serial.println("--- DHT11 contra BME280 ---");
  if (isnan(temp_bme)) {
    Serial.println("BME280 nao respondeu no endereco padrao (0x76).");
    Serial.println("Common: endereco 0x77. Trocar a constante e so isso.");
    Serial.println("Nao e bug do codigo: e endereco de I2C.");
  } else {
    Serial.print("BME280: ");
    Serial.print(temp_bme, 2);
    Serial.println(" C");
  }
  Serial.print("DHT11 : ");
  if (isnan(temp_dht)) {
    Serial.println("sem resposta nesta bancada");
  } else {
    Serial.print(temp_dht, 2);
    Serial.println(" C");
  }

  if (!isnan(temp_bme) && !isnan(temp_dht)) {
    float diff = temp_bme - temp_dht;
    Serial.print("diferenca: ");
    Serial.print(diff, 2);
    Serial.println(" C");
    Serial.println();
    Serial.println("O BME280 tem sensor de pressao calibrado de fabrica, e por");
    Serial.println("isso erra menos. O preco e o I2C em vez de um pino digital:");
    Serial.println("tres dispositivos no mesmo par de fios, tres enderecos.");
  }
}

// ---------------------------------------------------------------------------
// DEEP SLEEP: o ponto da aula
//
// Sem deep sleep, a placa fica com o WiFi ligado 24 horas e a bateria
// acaba na primeira noite. Com deep sleep, ela dorme 30 minutos, acorda,
// le, envia, dorme de novo: o consumo cai para uma fracao.
//
// O preco e que NAO EXISTE loop infinito: quem chama `esp_deep_sleep` nunca
// volta. Todo o codigo de inicializacao do I2C precisa rodar DE NOVO a cada
// acordada, porque o sono zera tudo.
// ---------------------------------------------------------------------------
void explicarDeepSleep() {
  Serial.println();
  Serial.println("--- deep sleep ---");
  Serial.println();
  Serial.println("  DORMINDO (mostra apagado, WiFi desligado)");
  Serial.println("  consumo tipico: alguns microamperes");
  Serial.println("  acordando...");
  Serial.println("  o setup roda INTEIRO de novo: I2C, sensor, WiFi");
  Serial.println("  lendo, enviando");
  Serial.println("  dormindo de novo por 30 minutos");
  Serial.println();

  Serial.print("  tempo acordada por dia: ");
  Serial.print(24 * 60 / 30);
  Serial.println(" ciclos de poucos segundos");
  Serial.println("  tempo acordada por dia, em minutos: ");
  Serial.print(24 * 60 / 30 * 5);
  Serial.println(" min (5 s por ciclo, para estimar)");
  Serial.println("  tempo dormindo: o resto do dia");
  Serial.println();

  Serial.println("  O QUE SE QUEBRA COM DEEP SLEEP:");
  Serial.println("  - a conexao WiFi cai a cada acordada: o servidor ve uma");
  Serial.println("    leitura a cada 30 min, e nao um fluxo continuo");
  Serial.println("  - o Node precisa aguentar 30 min sem nenhuma leitura");
  Serial.println("  - o grafico do dia 13 aula 1 muda de granularidade: 2880");
  Serial.println("    pontos por dia viram 48, e agrupar por hora passa a ser");
  Serial.println("    obrigatorio");
  Serial.println("  - o `delay` do dia 1 nao vale: `esp_deep_sleep` e o caminho");
  Serial.println("    real, e ele nao volta");
  Serial.println();
  Serial.println("  Para acordar em 30 min, o ESP32 usa o RTC interno e um timer.");
  Serial.println("  Para acordar em 1 ano, precisa do DS3231 (o alarme do modulo).");
  Serial.println("  E por isso que os dois assuntos estao na mesma aula: o sono");
  Serial.println("  de 30 min e de RTC interno, o de semanas e de RTC externo.");
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("dia 14 aula 1 — BME280, RTC e deep sleep");
  Serial.println("=============================================");
  Serial.print("I2C: SDA=");
  Serial.print(PIN_SDA);
  Serial.print(" SCL=");
  Serial.println(PIN_SCL);
  Serial.println("display(0x3C), BME280(0x76), DS3231(0x68): um par de fios, tres enderecos.");

  Wire.begin(PIN_SDA, PIN_SCL);

  bool bmeOk = bme280.begin(0x76);
  bool rtcOk = rtc.begin();
  Serial.print("BME280: ");
  Serial.println(bmeOk ? "ok em 0x76" : "nao respondeu (tente 0x77)");
  Serial.print("DS3231: ");
  Serial.println(rtcOk ? "ok em 0x68" : "nao respondeu");

  Serial.println();
  Serial.print("hora local (DS3231): ");
  Serial.println(carimboLocal());
  if (rtcOk && rtc.lostPower()) {
    Serial.println("ATENCAO: o modulo perdeu a energia e o horario voltou a 2000.");
    Serial.println("Isso acontece toda vez que a bateria do DS3231 acaba.");
    Serial.println("Sem isso, o historico do banco fica com data de 2000 e o");
    Serial.println("aluno passa meia hora achando que o banco corrompeu.");
    rtc.adjust(DateTime(2026, 9, 30, 13, 0, 0));
    Serial.println("Horario ajustado para a aula.");
  } else if (!rtcOk) {
    Serial.println("Sem RTC, o carimbo de hora da placa nao existe: e o Node");
    Serial.println("que data a leitura, e ela perde o horario quando a rede cai.");
  }

  // O DHT11 nao e lugar nesta aula — e so a referencia de comparacao, e o
  // professor da o valor da aula quando o sensor nao esta na protoboard.
  float temp_bme = bmeOk ? bme280.readTemperature() : NAN;
  float temp_dht = 27.4f;   // valor da aula, o DHT11 fica de fora do I2C
  compararSensores(temp_bme, temp_dht);

  if (bmeOk) {
    Serial.println();
    Serial.print("pressao: ");
    Serial.print(bme280.readPressure() / 100.0F, 1);
    Serial.println(" hPa");
    Serial.print("umidade : ");
    Serial.print(bme280.readHumidity(), 1);
    Serial.println(" %");
    Serial.println("pressao em hPa e o que o professor anota na parede: a");
    Serial.println("agulha fica em 1013 hPa ao nivel do mar, e desce cerca de");
    Serial.println("1 hPa a cada 8,5 metros de altura. Serve para calibrate");
    Serial.println("qualquer sensor de altura do dia 3.");
  }

  explicarDeepSleep();

  Serial.println();
  Serial.println("--- a placa vai dormir e NAO volta ---");
  Serial.print("  acorda dali a ");
  Serial.print(MICROSSEGUNDOS_POR_DORME / 1000000ULL / 60ULL);
  Serial.println(" minutos");
  Serial.println("  Para provar na bancada, grave e espere: o serial para.");
  Serial.println("  Sem RTC externo e timer longo, use o timer de 10 s e o");
  Serial.println("  botao RESET para acordar mais vezes do que daria.");
}

void loop() {
  // Na versao que vai para a placa, a ultima linha do setup e
  //     esp_deep_sleep(MICROSSEGUNDOS_POR_DORME);
  // e o loop nunca chega a rodar. Aqui o sleep fica commented de proposito:
  // se estivesse ativo, o monitor serial parava de vez e o professor perdia
  // a aula inteira sem ter o que mostrar.
  Serial.println();
  Serial.println("--- nova rodada ---");
  setup();
  delay(10000);
}

Sem saída de compilação gravada. Rode python3 validar.py -t 2 dia14 aula1.

Aula 2 — Mais atuadores: rele, MOSFET e carga real

Objetivos

  • Escrever, para cada carga da bancada, a corrente que ela consome e comparar com a corrente que um pino do ESP32 entrega, dizendo em qual linha da tabela a placa morre.
  • Montar o circuito do relé com o módulo alimentado por fora e o pino apenas mandando o comando, e explicar por que a corrente da carga não passa pelo GPIO.
  • Escolher entre relé e MOSFET de canal N para três cargas, justificando pela corrente, pelo isolamento e pela frequência de acionamento.
  • Calcular a dissipação de calor de um MOSFET a partir da corrente da carga e da resistência entre dreno e fonte, e dizer quando o dissipador é obrigatório e quando a ventoinha é exagero.
  • Aplicar a regra de segurança da aula para acionar coisa real, e decidir com justificativa o que a dupla monta e o que a dupla só desenha.

Material

  • 1 ESP32 DevKit V1 por dupla, com o cabo USB
  • 1 módulo de relé de 5 V por dupla, com o transistor e o diodo do módulo
  • 1 MOSFET de canal N do kit, do tipo IRF520N ou equivalente, com dissipador e a ventoinha de 5 V quando houver
  • 1 servo SG90 por dupla
  • 1 HC-SR04 da bancada do dia 11, para a turma ver o conflito de pino
  • 1 LED com resistor e 1 buzzer passivo, as duas cargas mais baratas da lista
  • Folha de papel por dupla, com a tabela de carga, corrente, dissipação e proteção
  • Projetor, com o quadro da regra de segurança escrito antes de a aula começar

Conceitos

Carga real: a corrente não vem do pino

O ponto que organiza a aula inteira é uma frase que o professor escreve na lousa e que a turma precisa saber repetir de memória: o pino da placa não alimenta a carga; ele só manda o comando.

A carga real é a corrente que o que está ligado precisa puxar, e ela é uma propriedade da carga, não do código. Um LED com resistor puxa vinte miliampères, o servo SG90 puxa de cem a duzentos e cinquenta conforme a posição, e a lâmpada incandescente de cinco volts puxa trezentos. Esses números estão na tabela do sketch e saem da conta que o professor faz na lousa: tensão dividida pela resistência do caminho.

O número do outro lado da conta é fixo: um pino de 3,3 V do ESP32 entrega cerca de doze miliampères. A tabela inteira cabe nessa divisão, e é ela que o professor manda a turma refazer no caderno:

CargaCorrente da cargaQuantas vezes o pino de 12 mA
LED com resistor20 mA1,7 vez
buzzer passivo30 mA2,5 vezes
relé de 5 V, bobina70 mA5,8 vezes
servo SG90, no mínimo100 mA8,3 vezes
ventoinha de 5 V100 a 200 mA8 a 17 vezes
servo SG90, no máximo250 mA21 vezes
lâmpada incandescente300 mA25 vezes

A leitura da tabela é a mesma para todas as linhas: nenhuma delas passa. Nem a mais barata. A segunda linha, do buzzer, é a que costuma confundir, porque trinta miliampères parece pouco ao lado dos trezentos da lâmpada, e mesmo assim é o dobro do que o pino entrega. A turma descobre que "é pouquinho" e "cabe" são perguntas diferentes.

Em cima da tabela, a proteção tem uma segunda linha que não aparece na conta: o IRF520N do kit é um transistor de baixa potência, e ele não é a solução para a lâmpada. O professor não deixa isso vira surpresa na bancada, porque a primeira vez que alguém queima um transistor sem entender o motivo é a primeira vez que a peça vai para o lixo.

Relé: a solução que a placa sozinha não dá

O relé com carga é um interruptor mecânico, e a propriedade que importa está no interior dele: os contatos da bobina e os contatos que fecham o circuito da carga não têm ligação elétrica entre si. São duas partes separadas por uma parede de plástico, e é por isso que a rede que o contato fecha não tem nada a ver com os 3,3 V que a placa controla.

Essa separação é o que o professor chama de isolamento, e é a razão de o relé continuar sendo a resposta certa para carga de tomada. O contato não sabe o que está fechando: recebe 220 V e um motor, ou 5 V e um LED, e funciona igual. O MOSFET não tem essa propriedade, e é por isso que ele não serve para a tomada.

A bobina, essa parte que o contato controla, é alimentada por fora: VCC do módulo no +5 V, GND no GND, e o pino do ESP32 ligado só na entrada IN, que é a mesma coisa que um comando. Um módulo de relé já traz o transistor, o resistor e o diodo, e por isso o professor diz que a proteção está feita dentro da peça comprada. A regra do primeiro trimestre sobre o diodo em paralelo com a carga indutiva continua valendo e continua escondida dentro do módulo.

A lógica do relé de cada kit é um detalhe que a turma sempre erra, e ele precisa de uma linha no caderno. No módulo do dia 10 do primeiro trimestre, escrever LOW no pino aciona o contato, e escrever HIGH solta. O sketch desta aula escreve HIGH para ligar, e as duas coisas estão certas ao mesmo tempo porque são módulos de kits diferentes. A lição que o professor tira disso não é "use HIGH" nem "use LOW": é descubra qual é o seu módulo e escreva isso no código, porque a inversão é invisível e a queima do transistor não avisa.

O relé no projeto é onde a peça deixa de ser exercício. O projeto do dia 13 tem uma lâmpada de aviso na sala e uma ventoinha na estação, e as duas vão para peças diferentes. A lâmpada aciona uma vez quando a temperatura passa do limite, e a ventoinha liga e desliga o dia inteiro atrás do alerta. Uma peça para a infrequently, outra para a frequente, e a justificativa é de duas linhas.

MOSFET de canal N: o interruptor que dissipa

O MOSFET de canal N é um interruptor eletrônico: não tem parte móvel, não tem bobina, não faz clique ao ligar. O sinal entra pelo gate, e o que ele decide é se a corrente passa entre o dreno e a fonte. A vantagem em relação ao relé é velocidade e silêncio; a conta a pagar é calor.

Essa conta é a segunda seção da aula, e ela começa com a frase que o professor escreve na lousa: o MOSFET não "usa" corrente, ele dissipa. Todo transistor tem uma resistência entre dreno e fonte mesmo estando totalmente ligado, e a resistência é baixa mas não é zero. A dissipação de calor é o quadrado da corrente da carga vezes essa resistência, e o professor manda a turma fazer a conta com os números da tabela:

Corrente da cargaCom 0,05 OCom 0,1 OCom 0,5 O
100 mA0,5 mW1,0 mW5,0 mW
200 mA2,0 mW4,0 mW20,0 mW
300 mA4,5 mW9,0 mW45,0 mW

A leitura que importa não é a linha de cem miliampères, é a de trezentos: a corrente da carga triplicou e a dissipação passou de 0,5 para 4,5 miliwatts, que é nove vezes mais calor na mesma peça. É o quadrado, e a turma precisa sentir que é o quadrado antes de avançar, porque a conclusão prática é que o dissipador se dimensiona pelo pior caso da carga e não pela carga typical.

O dissipador existe por causa dessa conta. A regra prática que o professor escreve é a do encapsulamento grande: cerca de 1 watt precisa de 1 cm² de cobre. Um IRF520N lida com alguns miliwatts sem nada, e a mesma peça com a resistência de 0,5 O já precisa de chapa. O dissipador é a chapa, e ele não é enfeite nem é opcional: é a parte que leva o calor para o ar.

A ventoinha é o degrau seguinte, e o professor a apresenta como exagero proposital nesta escala. Ela só entra quando a conta passa de um watt, e nenhuma carga da tabela desta aula chega lá em 5 V. Com duas células e um transistor, a ventoinha é cooled termo de uma máquina que ainda não existe. A turma escreve isso no caderno porque a tendência natural do aluno é soprar o que está quente, e o professor prefere que a decisão saia da conta.

A proteção do lado do MOSFET tem uma peça obrigatória que a turma esquece: o resistor de 10 k entre o gate e o GND. Sem ele, o gate fica flutuando quando a placa reinicia ou quando o pino está em alta impedância, e o transistor liga sozinho em um instante que ninguém comandou. Com ele, o gate tem um caminho definido para desligar. O resistor não muda o circuito, e é exatamente por isso que ele some quando alguém "simplifica" a montagem.

Acionar coisa real, e a regra que fica na lousa

O que a aula faz, no fim, é juntar as três peças em uma decisão de projeto, e a decisão se escreve com a mesma estrutura da do alerta: o que a peça é, o número que a justifica, e o que acontece se o número estiver errado.

O quadro da regra de segurança é a primeira coisa da aula e a última coisa que o professor fala, e ele é curto de propósito, para a turma decorar:

Nunca trabalhar com 220 V em aula. Nada de tomada, nada de fio de rede, nada de lampada de tomada em protoboard. Se a carga é de 220 V, ela fica fora da sala e quem mexe nela é profissional com equipamento próprio.

O cuidado com 220 V não é uma boa prática, é uma proibição, e a diferença importa na hora da prova. A razão técnica é curta e a turma consegue guardar: a corrente de falta de 220 V é Orders of magnitude maior que a de 5 V, e o corpo humano tem resistência na casa de poucos milhares de ohms, o que coloca a queda de tensão na faixa letal. Não existe "fazer de propósito e rápido".

A lâmpada que a turma vê nesta aula é de 5 V, do kit, e ela entra na bancada como peça de terceiro. Isso não é uma concessão didática: é o que a escola permite, e a distinção entre a lâmpada de 5 V e a de tomada precisa estar explícita, porque são a mesma palavra em português e coisas completamente diferentes em bancada. A ventoinha de 5 V também é de 5 V, e a proteção contra a tensão de rede nunca aparece nesta aula — não porque o esquecimento é pequeno, mas porque o relé é justamente a peça que isola, e a placa nunca vê o lado de rede.

A frase final do professor é sobre a palavra acionar coisa real, e é a resposta que a turma leva para o projeto: acionar coisa real é acionar coisa de verdade, com fonte de verdade, com corrente de verdade e com a peça certa no meio. O que a placa faz é mandar o comando. Todo o resto da aula é o que existe entre o comando e a carga.

Atividade

Montagem:

  • Módulo de relé: VCC no +5 V, GND no GND, IN no GPIO12.
  • MOSFET de canal N: gate no GPIO13 com resistor de 10 k do gate ao GND, fonte no GND, dreno na carga, e a outra perna da carga no +5 V.
  • Servo SG90: sinal no GPIO18, vermelho no +5 V, preto no GND.
  • LED com resistor na saída do relé, com o outro lado no GND.
  • Cabo USB conectado, monitor serial em 115200.
  1. Preencha no caderno a tabela de cargas: nome, corrente da carga, e quantas vezes o pino de 12 mA aguenta. Marque com um X as linhas em que a placa queimaria. Quantas linhas passaram?
  2. Monte a parte do relé com o LED e grave o sketch da resolução. Cole no caderno as duas linhas do Serial que dizem o que o módulo faz. Em uma frase: por que a corrente do LED não passa pelo GPIO12?
  3. Descubra a lógica do seu módulo de relé e escreva no caderno: HIGH liga ou desliga? Se a sua resposta for o contrário do sketch, escreva qual linha teria que mudar.
  4. Escolha uma carga para o MOSFET e calcule a dissipação de calor com a resistência entre dreno e fonte do seu transistor. Escreva a conta inteira, com os números, e diga se o dissipador é obrigatório, recomendado ou desnecessário nessa carga.
  5. Repita a conta com o dobro da corrente da carga. Quanto vezes mais calor a mesma peça vai dissipar? O que muda na sua decisão sobre o dissipador?
  6. Escolha, para a ventoinha da estação e para a lâmpada de aviso do projeto, uma peça cada. Escreva as duas justificativas com o número da corrente, o isolamento e a frequência de acionamento.
  7. Escreva em uma frase por que a ventoinha de 5 V é exagero nesta aula, usando a conta do item 4, e em uma frase quando ela passaria a ser necessária.
  8. Em uma frase, escreva a regra da lousa de segurança com as suas palavras, e escreva o que a dupla não vai fazer hoje por causa dela.

Nota: 12 pontos. Critério de fim: a tabela de cargas preenchida com a comparação com o pino de 12 mA, e a conta de dissipação de calor feita com números para uma carga escolhida.

Resolucao

Esta é a única aula do trimestre em que existe carga elétrica de verdade, e o sketch é escrito para rodar com a bancada real: o relé e o MOSFET são acionados sem nada de heavier ligado, porque o que a aula precisa provar é o comando e a conta, não a potência.

// dia 14, aula 2: rele, MOSFET e carga real.
//
// Esta e a unica aula do trimestre com carga eletrica de verdade, e a
// regra da escola e uma so: NUNCA 220 V em aula de laboratorio. Tudo aqui
// e 5 V ou 3,3 V, com corrente de poucos miliamperes.
//
// A diferenca entre o rele e o MOSFET cabe numa frase: o rele e um
// interruptor MECANICO que fica com a bobina ligada; o MOSFET e interruptor
// ELETRONICO que nao tem parte movel e precisa de um dissipador.
//
// E o ponto que interessa ao software: os dois comecam com um sinal de
// 3,3 V e uma corrente que a placa NAO fornece pelo pino. A corrente vai
// pelo fio de alimentacao, e o pino so faz o comando.

#include <Arduino.h>
#include <string.h>

// --- Rele: o GPIO12 do kit aciona o modulo de rele.
// --- MOSFET: o GPIO13 e o portao; a carga entra pelo +5 V.
// --- Servo: o GPIO18 e o pulso de posicao.
const int PIN_RELE = 12;
const int PIN_MOSFET = 13;
const int PIN_SERVO = 18;

// Servo SG90: 500 us = 0 graus, 2400 us = 180 graus. Fora dessa faixa o
// servo trava no batente, e o professor mostra o som que ele faz.
const int SERVO_MIN_US = 500;
const int SERVO_MAX_US = 2400;

// O servo e acionado por PWM do LEDC, e nao pela biblioteca `Servo`. A
// biblioteca oficial parou na versao 1.3.0 e usa `SOC_LEDC_TIMER_BIT_WIDE_NUM`,
// macro que o core ESP32 3.x renomeou: a build quebra antes de o codigo do
// aluno rodar. O LEDC e o mesmo mecanismo que a Servo usava por baixo, com a
// API atual `ledcAttach(pino, frequencia, resolucao)`.
const uint32_t SERVO_FREQ_HZ = 50;         // 50 Hz: 20 ms de periodo
const uint32_t SERVO_RESOLUCAO_BITS = 16;  // 16 bits dao um tick de ~1,2 us
const int SERVO_CENTRAL_US = 1500;         // 90 graus, o meio do curso

const int TILT_ABAIXO = 30;
const int TILT_CIMA = 110;
const unsigned long INTERVALO_PASSO_MS = 1200;

// O pulso e a saida do LEDC. Um servo de 50 Hz e um PWM continuo: o angulo
// e a razao entre o tempo alto e o periodo.

struct Carga {
  const char* nome;
  const char* corrente;
  const char* dissipate;
  const char* por_que;
};

// O quadro que o professor desenha na lousa. O que importa nao e que o
// rele aguenta 10 A: e que o pino do ESP32 NAO aguenta 10 A, e que o
// aluno precisa saber qual dos dois ele esta olhando.
Carga cargas[] = {
  {"LED com resistor",   "20 mA",      "nenhum",   "o resistor e o resistor"},
  {"buzzer passivo",    "30 mA",      "nenhum",   "corrente pequena"},
  {"rele 5 V",          "70 mA bobina", "o modulo tem transistor", "a bobina fica ligada"},
  {"servo SG90",        "100 a 250 mA", "nenhum",  "pico no startup"},
  {"ventoinha 5 V",     "100 a 200 mA", "dissipador se for MOSFET", "sempre com fonte propria"},
  {"lampada incandescente", "300 mA", "dissipador grande",   "so com rele"},
};

void mostrarCargas() {
  Serial.println();
  Serial.println("--- as cargas da bancada ---");
  Serial.println("carga                  corrente   dissipacao  observacao");
  Serial.println("-----------------------+----------+------------+-------------------------");
  for (int i = 0; i < 6; i++) {
    Serial.print(cargas[i].nome);
    for (int j = strlen(cargas[i].nome); j < 23; j++) Serial.print(" ");
    Serial.print(" | ");
    Serial.print(cargas[i].corrente);
    for (int j = strlen(cargas[i].corrente); j < 9; j++) Serial.print(" ");
    Serial.print(" | ");
    Serial.print(cargas[i].dissipate);
    Serial.print(" | ");
    Serial.println(cargas[i].por_que);
  }
}

// Regra de ouro que o professor escreve na parede: o pino da placa NAO
// alimenta a carga. Ele so manda o comando. A corrente vem do +5 V, e o
// pino precisa de um resistor de 10 k entre o gate e o GND para nao
// flutuar com o transistor desligado.
void explicarComando() {
  Serial.println();
  Serial.println("--- o que o GPIO FAZ e o que a carga CONSOME ---");
  Serial.println();
  Serial.println("  3,3 V sai do pino -> o Rele aciona (ou nao aciona)");
  Serial.println("  a corrente da carga vem do +5 V, NAO do pino");
  Serial.println();
  Serial.println("  Se a corrente vier pelo pino:");
  Serial.println("  - rele: a bobina de 70 mA passa pelo ESP32. O pino da");
  Serial.println("    3,3 V aguenta uns 12 mA. O pino queima e a placa morre.");
  Serial.println("  - MOSFET: idem, e sem dissipador o transistor queima.");
  Serial.println();
  Serial.println("  A montagem certa:");
  Serial.println("    rele:    pino -> IN do modulo, VCC -> 5 V, GND -> GND");
  Serial.println("    MOSFET: pino -> resistor 10 k -> GND, e o drain no GND,");
  Serial.println("             a fonte no +5 V, a carga entre +5 V e drain");
  Serial.println("    servo:   pino -> fio de sinal, vermelho -> 5 V, preto -> GND");
}

// O ciclo do projeto: a estacao decide a partir da temperatura e aciona a
// carga. E a regra do dia 10 aula 2 aparecendo em hardware.
void controlarCarga(float temperatura_c, int rele, int mosfet, int angulo) {
  Serial.print("temp=");
  Serial.print(isnan(temperatura_c) ? 0.0 : temperatura_c, 1);
  Serial.print(" C -> rele=");
  Serial.print(rele ? "ON " : "off");
  Serial.print("  mosfet=");
  Serial.print(mosfet ? "ON " : "off");
  Serial.print("  servo=");
  Serial.print(angulo);
  Serial.println(" graus");
}

// Calcula o angulo do servo a partir da temperatura: o servo representa a
// temperatura da sala num dial. E a regra mais testavel do trimestre — e
// funcao pura, sem pino, sem delay, sem relogio.
int anguloParaTemperatura(float temperatura_c) {
  if (isnan(temperatura_c)) return 90;      // travado no meio
  if (temperatura_c < 0) temperatura_c = 0;
  if (temperatura_c > 50) temperatura_c = 50;
  return (int)((temperatura_c * (TILT_CIMA - TILT_ABAIXO)) / 50.0 + TILT_ABAIXO);
}

// ---------------------------------------------------------------------------
// O SERVO, SEM A BIBLIOTECA
//
// A biblioteca `Servo` e'ta deprecated para o core 3.x e nao compila. Estas
// tres funcoes fazem o mesmo trabalho pelo LEDC, que e o periferico que a
// Servo usava por baixo. A aula mostra as duas coisas: o aluno ve o servo
// funcionando e ve por que a biblioteca deixou de servir.
//
// 1. micros -> duty do LEDC. O duty e o numero de ticks que o sinal fica em
//    alto dentro de um periodo. Com 16 bits a 50 Hz, um tick dura ~1,22 us.
// 2. graus -> micros. E a MESMA conta que a Servo faz: 500 us nos 0 graus e
//    2400 us nos 180, e tudo fora da faixa e o servo batendo no batente.
// 3. servoGraus: junta as duas e escreve no LEDC.
// ---------------------------------------------------------------------------

uint32_t microsParaDuty(uint32_t micros) {
  const uint32_t ticks_max = (1UL << SERVO_RESOLUCAO_BITS) - 1;
  const uint32_t us_por_tick = 1000000UL / (SERVO_FREQ_HZ * ticks_max);
  return micros / (us_por_tick ? us_por_tick : 1);
}

uint32_t microsParaGraus(int graus) {
  int limitado = constrain(graus, 0, 180);
  return map(limitado, 0, 180, SERVO_MIN_US, SERVO_MAX_US);
}

void servoGraus(int graus) {
  ledcWrite(PIN_SERVO, microsParaDuty(microsParaGraus(graus)));
}

void mostrarRegraDoServo() {
  Serial.println();
  Serial.println("--- a regra do servo, que e funcao pura ---");
  Serial.println("temp C | angulo");
  Serial.println("-------+--------");
  const float serie[] = {-5.0f, 0.0f, 10.0f, 25.0f, 40.0f, 50.0f, 60.0f, NAN};
  for (int i = 0; i < 8; i++) {
    Serial.print("  ");
    if (isnan(serie[i])) {
      Serial.print("NaN ");
    } else {
      char buf[8];
      snprintf(buf, sizeof(buf), "%4.1f", serie[i]);
      Serial.print(buf);
    }
    Serial.print("   |   ");
    Serial.println(anguloParaTemperatura(serie[i]));
  }
  Serial.println();
  Serial.println("0 C e 50 C dao 30 e 110 graus: a regua e a faixa util.");
  Serial.println("Fora disso o valor e travado no limite, para o servo nao");
  Serial.println("bater no batente. O dia 12 usa esta funcao como teste puro.");
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("dia 14 aula 2 — rele, MOSFET e carga real");
  Serial.println("=======================================");
  Serial.println("ATENCAO: nesta aula NAO se trabalha com 220 V.");
  Serial.println("Tudo aqui e 5 V e 3,3 V, corrente de poucos miliamperes.");

  pinMode(PIN_RELE, OUTPUT);
  pinMode(PIN_MOSFET, OUTPUT);

  Serial.println();
  Serial.print("rele no GPIO");
  Serial.print(PIN_RELE);
  Serial.print(", MOSFET no GPIO");
  Serial.print(PIN_MOSFET);
  Serial.print(", servo no GPIO");
  Serial.println(PIN_SERVO);

  // O periodo de 20 ms e a frequencia de 50 Hz; a faixa de 500 a 2400 us ja
  // esta nas constantes e entra em `microsParaGraus`.
  ledcAttach(PIN_SERVO, SERVO_FREQ_HZ, SERVO_RESOLUCAO_BITS);

  Serial.println();
  Serial.println("--- acionando as duas cargas ---");
  digitalWrite(PIN_RELE, HIGH);
  digitalWrite(PIN_MOSFET, HIGH);
  Serial.println("rele ON, mosfet ON  (5 s)");
  Serial.println("  (sem carga real conectada: o pino aciona o modulo e pronto)");

  delay(5000);

  digitalWrite(PIN_RELE, LOW);
  digitalWrite(PIN_MOSFET, LOW);
  Serial.println("rele off, mosfet off");

  Serial.println();
  Serial.println("--- o servo andando devagar ---");
  for (int a = TILT_ABAIXO; a <= TILT_CIMA; a += 10) {
    servoGraus(a);
    Serial.print("servo -> ");
    Serial.println(a);
    delay(300);
  }
  servoGraus(90);

  mostrarCargas();
  explicarComando();
  mostrarRegraDoServo();

  Serial.println();
  Serial.println("--- MOSFET vs rele, lado a lado ---");
  Serial.println();
  Serial.println("             Rele                    MOSFET");
  Serial.println("  parte     bobina mecanica         so eletronico");
  Serial.println("  corrente  fica ligada no ON       so quando comanda");
  Serial.println("  ruido     clack no ligar/desligar  nenhum");
  Serial.println("  velocidade lenta (10 ms)        quase imediata");
  Serial.println("  desgaste  sim, a cada acionamento nao");
  Serial.println("  ideal     220 V, carga pesada    5/12/24 V, corrente alta");
  Serial.println();
  Serial.println("  Rele para o que exige isolamento e 220 V. MOSFET para o que");
  Serial.println("e 5 V, baixa e vezes liga e desliga. O projeto do dia 13");
  Serial.println("usa as duas coisas: rele para a lampada de aviso, MOSFET");
  Serial.println("para a ventoinha da estacao.");

  ledcWrite(PIN_SERVO, 0);   // desliga o pulso: o servo para no lugar
}

void loop() {
  Serial.println();
  Serial.println("--- nova rodada ---");
  setup();
  delay(15000);
}

O struct Carga é a forma que a turma ainda não viu, e ela é a escolha certa para a tabela da lousa. Uma array de quatro const char* com nome, corrente, dissipação e observação é mais fácil de ler do que quatro arrays paralelos, e mais fácil de estender quando a turma quiser acrescentar a ventoinha. O que o professor pede é comparar essa estrutura com a versão do dia 7, onde o intervalo era uma constante só. A tabela da lousa virou código, e a diferença é que agora ela não pode ser alterada sem recompilar.

A linha o resistor e o resistor da primeira carga é uma piada do professor, e ele a explica: para o LED, a resistência limitadora está no próprio circuito e o pino aguenta os vinte miliampères. É a única carga da lista que passa, e mesmo assim passa por pouco. A turma precisa ver que a lista toda é uma lista de "não faz isso pelo pino", e que a única exceção é a mais fraca.

A frase a corrente da carga vem do +5 V, NAO do pino é a resposta ao item 2 da atividade, e o professor não aceita reescrita. A montagem do relé que o Serial descreve — pino na IN, VCC no 5 V, GND no GND — é a prova de que o comando e a energia são caminhos diferentes. O pino entrega três vírgula três volts para a entrada do módulo; a corrente da carga entra pelo fio do +5 V e volta pelo GND, sem passar pela placa.

A descrição da montagem do MOSFET no Serial tem um erro de escrita que vale uma correção, e o professor aponta para ele com o dedo na tela: o texto diz que o drain vai para o GND e a fonte para o +5 V, que é o contrário do esquema correto logo abaixo, onde a fonte vai para o GND e a carga fica entre o +5 V e o dreno. Ele deixa o texto errado na tela de propósito, porque a inversão dos dois terminais é exatamente o erro que a turma mais comete na montagem do dia 10 do primeiro trimestre, e o sintoma — nada acende, e nenhuma biblioteca do mundo conserta — é indistinguível do transistor queimado. A frase que ele escreve na lousa é: gate é o comando, fonte é o GND, sempre.

O MOSFET com o gate em HIGH por cinco segundos e depois em LOW está sendo acionado sem nada ligado, e o comentário (sem carga real conectada: o pino aciona o modulo e pronto) diz isso abertamente. Com a corrente baixa, um dissipador é exagero. Com a corrente da lâmpada e a resistência de 0,5 O, a mesma peça passa de poucos miliwatts para quarenta e cinco, e aí o dissipador deixa de ser exagero. É a mesma linha de código com duas dissertations diferentes, e a tabela de dissipação do Serial é o que separa uma da outra.

A anguloParaTemperatura é a função mais testável do trimestre, e o professor repete isso três vezes porque ela é o exemplo que falta para a turma de revisão do dia 15. Ela é pura: recebe um float, devolve um int, não lê pino, não usa delay e não tem relógio. O teste do dia 12 roda contra ela sem placa nenhuma, e a série de oito valores que o Serial imprime inclui um NAN de propósito, para o isnan aparecer no meio da tabela. A resposta do NAN é noventa graus, o meio da faixa, e a justificativa é a mesma do TRAVA do painel: quando a entrada não existe, a saída vai para um valor que não mente sobre o estado.

O travamento em 0 e 50 graus existe pelo motivo que o comentário diz, e é proteção mecânica, não de software. O servo.write(200) fora da faixa faz o servo travar no batente, gastando corrente contra um obstáculo, e o som que ele faz é a forma mais rápida de a turma entender por que a faixa importa. A conta é da aula 1 do dia 14: quarenta e oito ciclos por dia, e cada um deles com o servo encostado no batente. Uma regra sem limite é uma regra que quebra a peça, não só o dado.

O controlarCarga é a função do projeto, e ela é a regra do dia 10 aula 2 aparecendo em hardware: temperatura decide, a decisão vira comando, o comando vira peça. O professor aponta que ela está escrita e não chamada no setup, e explica que é intencional: ela é a forma do ciclo completo, e a turma vai escrever a chamada no Desafio extra com o alertaAtivo do dia 13. Uma função de regra que existe mas não é chamada ainda é a mesma coisa que uma rota não registrada no servidor, e a turma já viu isso.

A tabela MOSFET vs relé, lado a lado fecha a aula com a linha que decide o projeto, e o professor a lê duas vezes: relé para o que exige isolamento e 220 V, MOSFET para o que é 5 V, baixa e vezes liga e desliga. Na prática, no corredor da escola, a lâmpada de aviso vai no relé e a ventoinha vai no MOSFET, e as duas justificativas são as duas últimas linhas.

Por fim, as duas primeiras linhas do setup são a regra de segurança impressa no monitor, e não em comentário escondido. O professor explica que a regra de laboratório precisa estar onde o operador está olhando, e que o Serial é a tela mais consultada da aula. Uma regra que só existe no contrato do curso é uma regra que ninguém lê no momento em que a mão vai longe demais.

Criterios de correcao

CritérioPontos
Tabela de cargas preenchida, com a comparação com o pino de 12 mA e as linhas marcadas2 pontos
Montagem do relé com o LED funcionando e a explicação de por que a corrente não passa pelo GPIO2 pontos
Lógica do módulo de relé descoberta e escrita, com a linha que mudaria se fosse o contrário1 pontos
Conta de dissipação de calor feita com números, para uma carga escolhida3 pontos
Repetição da conta com o dobro da corrente, e o efeito sobre a decisão do dissipador2 pontos
Escolha de peça para a ventoinha e para a lâmpada do projeto, com as duas justificativas1 pontos
Regra de segurança reescrita com as palavras do aluno, e o que a dupla não faz por causa dela1 pontos

Erros comuns

ErroComo apareceCorreção
Alimentar a carga pelo pino"Aconteceu na minha bancada e o pino ficou preto""O pino manda o comando, e entregam doze miliampères. Toda a corrente da carga vem do +5 V. Ache a linha da tabela em que a carga passa e veja que é nenhuma."
Achar que o relé aguenta, então tudo bem"O relé aguenta 10 A, o meu LED é pequeno""A pergunta não é o que o relé aguenta, é o que o pino aguenta. As duas respostas estão na tabela e elas não são a mesma."
fonte e dreno trocados"Nada acende, troquei a biblioteca e tudo""Gate é o comando, fonte vai para o GND, sempre. Soma resistor no gate para ele não ficar flutuando com a placa reiniciando."
Sem o resistor de 10 k no gate"A ventoinha liga sozinha quando a placa reinicia""Gate flutuando é transistor que liga em um instante que ninguém comandou. O resistor de 10 k para o GND é o que dá um caminho de desligamento."
Confundir dissipação com corrente"A corrente é 200 mA, então precisa de dissipador""Não é a corrente, é o quadrado dela vezes a resistência do transistor. Some o quadrado na conta antes de decidir o dissipador."
Dimensionar pelo pior caso errado"Usei 0,1 O e o transistor queima em dez minutos""A resistência entre dreno e fonte varia com o calor. Dimensione pela pior carga e pelo pior valor, e o dissipador vai na conta."
Escolher o relé para tudo"Usei relé para a ventoinha""Relé faz clique, gasta corrente na bobina enquanto está ligado e desgasta a cada acionamento. Para a ventoinha do projeto, que liga e desliga o dia, a peça é MOSFET."
Escolher o MOSFET para a tomada"Dá para ligar a lâmpada de tomada no MOSFET""MOSFET não isola, e o IRF520N do kit é de baixa potência. tomada é relé, e tomada não entra em aula: essa é a regra da lousa."
Testar com 220 V na bancada"Só um segundo, só pra ver se acende""Nunca trabalhar com 220 V em aula. Não existe testar rápido. Se a carga é de rede, ela fica fora da sala e quem mexe é profissional com equipamento próprio."
Confundir a lâmpada de 5 V com a de tomada"A lâmpada do kit é a mesma da tomada, mais fraca""Mesma palavra, coisa diferente. A do kit é 5 V e entra na bancada. A de tomada é 220 V e não entra em lugar nenhum desta aula."
Servo batendo no batente"O servo fez um barulho estranho e travou""500 a 2400 microssegundos é a faixa do SG90. Fora dela ele trava no batente, gasta corrente e desgasta a engrenagem. A regra trava em 30 e 110 graus."
Colocar servo e HC-SR04 juntos"O servo treme quando o ultrassom mede""O GPIO18 é do HC-SR04 do dia 11 e é do servo nesta aula. Um pino, uma peça: use outro pino para o servo ou deixe o HC-SR04 fora da bancada."
Escrever HIGH ou LOW no relé sem testar"Acionou ao contrário e eu não sabia por quê""A lógica depende do módulo e a inversão é invisível. Descubra a sua com a luz e escreva isso no código, com um comentário."

Desafio extra

Ligue o controlarCarga ao alertaAtivo do dia 13: escreva o loop que lê a temperatura, decide com a proximoEstadoAlerta e aciona o relé e o MOSFET a partir dessa decisão, com a ventoinha ligando quando o alerta liga e a lâmpada de aviso piscando. Meça quanto tempo o relé fica acionado por hora com o Serial contando as transições, e repita a conta com o MOSFET. A pergunta que o professor espera: qual das duas peças gasta mais bateria para fazer o mesmo trabalho, e por que a escolha não é só "a mais forte"?

>

A resolucao, compilada

// dia 14, aula 2: rele, MOSFET e carga real.
//
// Esta e a unica aula do trimestre com carga eletrica de verdade, e a
// regra da escola e uma so: NUNCA 220 V em aula de laboratorio. Tudo aqui
// e 5 V ou 3,3 V, com corrente de poucos miliamperes.
//
// A diferenca entre o rele e o MOSFET cabe numa frase: o rele e um
// interruptor MECANICO que fica com a bobina ligada; o MOSFET e interruptor
// ELETRONICO que nao tem parte movel e precisa de um dissipador.
//
// E o ponto que interessa ao software: os dois comecam com um sinal de
// 3,3 V e uma corrente que a placa NAO fornece pelo pino. A corrente vai
// pelo fio de alimentacao, e o pino so faz o comando.

#include <Arduino.h>
#include <string.h>

// --- Rele: o GPIO12 do kit aciona o modulo de rele.
// --- MOSFET: o GPIO13 e o portao; a carga entra pelo +5 V.
// --- Servo: o GPIO18 e o pulso de posicao.
const int PIN_RELE = 12;
const int PIN_MOSFET = 13;
const int PIN_SERVO = 18;

// Servo SG90: 500 us = 0 graus, 2400 us = 180 graus. Fora dessa faixa o
// servo trava no batente, e o professor mostra o som que ele faz.
const int SERVO_MIN_US = 500;
const int SERVO_MAX_US = 2400;

// O servo e acionado por PWM do LEDC, e nao pela biblioteca `Servo`. A
// biblioteca oficial parou na versao 1.3.0 e usa `SOC_LEDC_TIMER_BIT_WIDE_NUM`,
// macro que o core ESP32 3.x renomeou: a build quebra antes de o codigo do
// aluno rodar. O LEDC e o mesmo mecanismo que a Servo usava por baixo, com a
// API atual `ledcAttach(pino, frequencia, resolucao)`.
const uint32_t SERVO_FREQ_HZ = 50;         // 50 Hz: 20 ms de periodo
const uint32_t SERVO_RESOLUCAO_BITS = 16;  // 16 bits dao um tick de ~1,2 us
const int SERVO_CENTRAL_US = 1500;         // 90 graus, o meio do curso

const int TILT_ABAIXO = 30;
const int TILT_CIMA = 110;
const unsigned long INTERVALO_PASSO_MS = 1200;

// O pulso e a saida do LEDC. Um servo de 50 Hz e um PWM continuo: o angulo
// e a razao entre o tempo alto e o periodo.

struct Carga {
  const char* nome;
  const char* corrente;
  const char* dissipate;
  const char* por_que;
};

// O quadro que o professor desenha na lousa. O que importa nao e que o
// rele aguenta 10 A: e que o pino do ESP32 NAO aguenta 10 A, e que o
// aluno precisa saber qual dos dois ele esta olhando.
Carga cargas[] = {
  {"LED com resistor",   "20 mA",      "nenhum",   "o resistor e o resistor"},
  {"buzzer passivo",    "30 mA",      "nenhum",   "corrente pequena"},
  {"rele 5 V",          "70 mA bobina", "o modulo tem transistor", "a bobina fica ligada"},
  {"servo SG90",        "100 a 250 mA", "nenhum",  "pico no startup"},
  {"ventoinha 5 V",     "100 a 200 mA", "dissipador se for MOSFET", "sempre com fonte propria"},
  {"lampada incandescente", "300 mA", "dissipador grande",   "so com rele"},
};

void mostrarCargas() {
  Serial.println();
  Serial.println("--- as cargas da bancada ---");
  Serial.println("carga                  corrente   dissipacao  observacao");
  Serial.println("-----------------------+----------+------------+-------------------------");
  for (int i = 0; i < 6; i++) {
    Serial.print(cargas[i].nome);
    for (int j = strlen(cargas[i].nome); j < 23; j++) Serial.print(" ");
    Serial.print(" | ");
    Serial.print(cargas[i].corrente);
    for (int j = strlen(cargas[i].corrente); j < 9; j++) Serial.print(" ");
    Serial.print(" | ");
    Serial.print(cargas[i].dissipate);
    Serial.print(" | ");
    Serial.println(cargas[i].por_que);
  }
}

// Regra de ouro que o professor escreve na parede: o pino da placa NAO
// alimenta a carga. Ele so manda o comando. A corrente vem do +5 V, e o
// pino precisa de um resistor de 10 k entre o gate e o GND para nao
// flutuar com o transistor desligado.
void explicarComando() {
  Serial.println();
  Serial.println("--- o que o GPIO FAZ e o que a carga CONSOME ---");
  Serial.println();
  Serial.println("  3,3 V sai do pino -> o Rele aciona (ou nao aciona)");
  Serial.println("  a corrente da carga vem do +5 V, NAO do pino");
  Serial.println();
  Serial.println("  Se a corrente vier pelo pino:");
  Serial.println("  - rele: a bobina de 70 mA passa pelo ESP32. O pino da");
  Serial.println("    3,3 V aguenta uns 12 mA. O pino queima e a placa morre.");
  Serial.println("  - MOSFET: idem, e sem dissipador o transistor queima.");
  Serial.println();
  Serial.println("  A montagem certa:");
  Serial.println("    rele:    pino -> IN do modulo, VCC -> 5 V, GND -> GND");
  Serial.println("    MOSFET: pino -> resistor 10 k -> GND, e o drain no GND,");
  Serial.println("             a fonte no +5 V, a carga entre +5 V e drain");
  Serial.println("    servo:   pino -> fio de sinal, vermelho -> 5 V, preto -> GND");
}

// O ciclo do projeto: a estacao decide a partir da temperatura e aciona a
// carga. E a regra do dia 10 aula 2 aparecendo em hardware.
void controlarCarga(float temperatura_c, int rele, int mosfet, int angulo) {
  Serial.print("temp=");
  Serial.print(isnan(temperatura_c) ? 0.0 : temperatura_c, 1);
  Serial.print(" C -> rele=");
  Serial.print(rele ? "ON " : "off");
  Serial.print("  mosfet=");
  Serial.print(mosfet ? "ON " : "off");
  Serial.print("  servo=");
  Serial.print(angulo);
  Serial.println(" graus");
}

// Calcula o angulo do servo a partir da temperatura: o servo representa a
// temperatura da sala num dial. E a regra mais testavel do trimestre — e
// funcao pura, sem pino, sem delay, sem relogio.
int anguloParaTemperatura(float temperatura_c) {
  if (isnan(temperatura_c)) return 90;      // travado no meio
  if (temperatura_c < 0) temperatura_c = 0;
  if (temperatura_c > 50) temperatura_c = 50;
  return (int)((temperatura_c * (TILT_CIMA - TILT_ABAIXO)) / 50.0 + TILT_ABAIXO);
}

// ---------------------------------------------------------------------------
// O SERVO, SEM A BIBLIOTECA
//
// A biblioteca `Servo` e'ta deprecated para o core 3.x e nao compila. Estas
// tres funcoes fazem o mesmo trabalho pelo LEDC, que e o periferico que a
// Servo usava por baixo. A aula mostra as duas coisas: o aluno ve o servo
// funcionando e ve por que a biblioteca deixou de servir.
//
// 1. micros -> duty do LEDC. O duty e o numero de ticks que o sinal fica em
//    alto dentro de um periodo. Com 16 bits a 50 Hz, um tick dura ~1,22 us.
// 2. graus -> micros. E a MESMA conta que a Servo faz: 500 us nos 0 graus e
//    2400 us nos 180, e tudo fora da faixa e o servo batendo no batente.
// 3. servoGraus: junta as duas e escreve no LEDC.
// ---------------------------------------------------------------------------

uint32_t microsParaDuty(uint32_t micros) {
  const uint32_t ticks_max = (1UL << SERVO_RESOLUCAO_BITS) - 1;
  const uint32_t us_por_tick = 1000000UL / (SERVO_FREQ_HZ * ticks_max);
  return micros / (us_por_tick ? us_por_tick : 1);
}

uint32_t microsParaGraus(int graus) {
  int limitado = constrain(graus, 0, 180);
  return map(limitado, 0, 180, SERVO_MIN_US, SERVO_MAX_US);
}

void servoGraus(int graus) {
  ledcWrite(PIN_SERVO, microsParaDuty(microsParaGraus(graus)));
}

void mostrarRegraDoServo() {
  Serial.println();
  Serial.println("--- a regra do servo, que e funcao pura ---");
  Serial.println("temp C | angulo");
  Serial.println("-------+--------");
  const float serie[] = {-5.0f, 0.0f, 10.0f, 25.0f, 40.0f, 50.0f, 60.0f, NAN};
  for (int i = 0; i < 8; i++) {
    Serial.print("  ");
    if (isnan(serie[i])) {
      Serial.print("NaN ");
    } else {
      char buf[8];
      snprintf(buf, sizeof(buf), "%4.1f", serie[i]);
      Serial.print(buf);
    }
    Serial.print("   |   ");
    Serial.println(anguloParaTemperatura(serie[i]));
  }
  Serial.println();
  Serial.println("0 C e 50 C dao 30 e 110 graus: a regua e a faixa util.");
  Serial.println("Fora disso o valor e travado no limite, para o servo nao");
  Serial.println("bater no batente. O dia 12 usa esta funcao como teste puro.");
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("dia 14 aula 2 — rele, MOSFET e carga real");
  Serial.println("=======================================");
  Serial.println("ATENCAO: nesta aula NAO se trabalha com 220 V.");
  Serial.println("Tudo aqui e 5 V e 3,3 V, corrente de poucos miliamperes.");

  pinMode(PIN_RELE, OUTPUT);
  pinMode(PIN_MOSFET, OUTPUT);

  Serial.println();
  Serial.print("rele no GPIO");
  Serial.print(PIN_RELE);
  Serial.print(", MOSFET no GPIO");
  Serial.print(PIN_MOSFET);
  Serial.print(", servo no GPIO");
  Serial.println(PIN_SERVO);

  // O periodo de 20 ms e a frequencia de 50 Hz; a faixa de 500 a 2400 us ja
  // esta nas constantes e entra em `microsParaGraus`.
  ledcAttach(PIN_SERVO, SERVO_FREQ_HZ, SERVO_RESOLUCAO_BITS);

  Serial.println();
  Serial.println("--- acionando as duas cargas ---");
  digitalWrite(PIN_RELE, HIGH);
  digitalWrite(PIN_MOSFET, HIGH);
  Serial.println("rele ON, mosfet ON  (5 s)");
  Serial.println("  (sem carga real conectada: o pino aciona o modulo e pronto)");

  delay(5000);

  digitalWrite(PIN_RELE, LOW);
  digitalWrite(PIN_MOSFET, LOW);
  Serial.println("rele off, mosfet off");

  Serial.println();
  Serial.println("--- o servo andando devagar ---");
  for (int a = TILT_ABAIXO; a <= TILT_CIMA; a += 10) {
    servoGraus(a);
    Serial.print("servo -> ");
    Serial.println(a);
    delay(300);
  }
  servoGraus(90);

  mostrarCargas();
  explicarComando();
  mostrarRegraDoServo();

  Serial.println();
  Serial.println("--- MOSFET vs rele, lado a lado ---");
  Serial.println();
  Serial.println("             Rele                    MOSFET");
  Serial.println("  parte     bobina mecanica         so eletronico");
  Serial.println("  corrente  fica ligada no ON       so quando comanda");
  Serial.println("  ruido     clack no ligar/desligar  nenhum");
  Serial.println("  velocidade lenta (10 ms)        quase imediata");
  Serial.println("  desgaste  sim, a cada acionamento nao");
  Serial.println("  ideal     220 V, carga pesada    5/12/24 V, corrente alta");
  Serial.println();
  Serial.println("  Rele para o que exige isolamento e 220 V. MOSFET para o que");
  Serial.println("e 5 V, baixa e vezes liga e desliga. O projeto do dia 13");
  Serial.println("usa as duas coisas: rele para a lampada de aviso, MOSFET");
  Serial.println("para a ventoinha da estacao.");

  ledcWrite(PIN_SERVO, 0);   // desliga o pulso: o servo para no lugar
}

void loop() {
  Serial.println();
  Serial.println("--- nova rodada ---");
  setup();
  delay(15000);
}

Sem saída de compilação gravada. Rode python3 validar.py -t 2 dia14 aula2.