Aprofundar: hardware que a escola não tem — Arduino e IoT — semana 14 do 2o trimestre
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
BME280e umDS3231no mesmo par deI2Cdo display, identificando cada módulo pelo endereço, e dizer por que os três convivem em dois fios. - Comparar a leitura do
BME280com a doDHT11na mesma bancada, e escrever em uma frase o que asensibilidademelhorada compra para o projeto. - Mostrar o que acontece com o horário do histórico quando não existe
RTCna placa, e nomear a peça que corrige isso. - Configurar o
deep sleeppara 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 bateriacom 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
DHT11por dupla, com resistor de pull-up de 4,7 k ohm entre o pino de dados e3V3 - 1
BME280e 1DS3231de cada, se a escola tiver; a aula roda inteira sem eles, e oSerialavisa - 1 display
SSD1306de 128x64 com interfaceI2C, do dia 9, para mostrar oI2Cocupado - 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
Serialda 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.
| Dispositivo | Endereço no I2C | Fala com |
|---|---|---|
display SSD1306 | 0x3C (ou 0x3D no outro modelo) | o Wire do dia 9 |
BME280 | 0x76 (ou 0x77 na variante) | o Adafruit_BME280 |
DS3231 | 0x68 | o 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
Nodeprecisa 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
delaydo dia 1 não serve para nada aqui, porqueesp_deep_sleepnão volta e não pode ser segurado por umdelay.
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:
DHT11noGPIO4, com pull-up de 4,7 k ohm.BME280noI2C:SDAnoGPIO21eSCLnoGPIO22, endereço0x76.DS3231no mesmoI2C:SDAnoGPIO21eSCLnoGPIO22, endereço0x68.- Display
SSD1306no mesmoI2C, em0x3C. - Cabo USB conectado, monitor serial em 115200.
- Escreva no caderno a tabela dos três dispositivos do
I2Ccom endereço, pinos e biblioteca. Um par de fios, três endereços: escreva por que isso não dá conflito. - Grave o sketch da resolução. Cole no caderno a linha que diz se o
BME280e oDS3231responderam, e escreva o que você faria se oBME280não respondesse. - Se o
BME280estiver na bancada, anote a temperatura dele, a doDHT11e a linhadiferenca:. Se não estiver, use o valor que o professor escreve na lousa. Em uma frase: para o alerta de32,0graus com folga de dois, esses quatro décimos decidem alguma coisa? - Escreva em uma frase o que muda no horário do histórico se a placa ficar vinte minutos sem rede, com
Nodecarimbando a hora e semDS3231. Depois escreva o que muda com oDS3231. - 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
Serialimprime, e escreva a unidade correta desse número. - 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
bateriaaguenta com o seu número. - Grave o sketch e rode. Cole no caderno as três linhas de
I2Cque oSerialimprime no começo, e escreva o que elas provam. - Em uma frase: por que a inicialização do
I2Ce do sensor precisa estar nosetupe 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ério | Pontos |
|---|---|
Tabela dos três dispositivos do I2C com endereço, pinos e biblioteca, e a justificativa de um par de fios | 2 pontos |
Linhas do Serial que dizem se o BME280 e o DS3231 responderam, coladas, e o que fazer se não responderem | 2 pontos |
Comparação de temperatura anotada, com a linha diferenca: e a resposta sobre a folga de dois graus | 2 pontos |
Resposta escrita sobre o horário com e sem DS3231, no caso dos vinte minutos sem rede | 2 pontos |
| Conta do regime com ciclos, segundos acordada e fração do dia, e a unidade correta escrita | 2 pontos |
Intervalo de envio escolhido, com leituras por dia e duração da bateria ao lado | 1 pontos |
Inicialização no setup explicada em uma frase, dizendo o que o sono apaga | 1 pontos |
Erros comuns
| Erro | Como aparece | Correçã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
ESP32entrega, 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 peloGPIO. - Escolher entre
reléeMOSFETde 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
MOSFETa partir da corrente da carga e da resistência entredrenoefonte, e dizer quando odissipadoré obrigatório e quando aventoinhaé 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
MOSFETde canal N do kit, do tipoIRF520Nou equivalente, comdissipadore aventoinhade 5 V quando houver - 1 servo
SG90por dupla - 1
HC-SR04da bancada do dia 11, para a turma ver o conflito de pino - 1
LEDcom resistor e 1 buzzer passivo, as duas cargas mais baratas da lista - Folha de papel por dupla, com a tabela de carga, corrente,
dissipaçãoeproteçã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:
| Carga | Corrente da carga | Quantas vezes o pino de 12 mA |
|---|---|---|
LED com resistor | 20 mA | 1,7 vez |
| buzzer passivo | 30 mA | 2,5 vezes |
relé de 5 V, bobina | 70 mA | 5,8 vezes |
servo SG90, no mínimo | 100 mA | 8,3 vezes |
ventoinha de 5 V | 100 a 200 mA | 8 a 17 vezes |
servo SG90, no máximo | 250 mA | 21 vezes |
lâmpada incandescente | 300 mA | 25 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 carga | Com 0,05 O | Com 0,1 O | Com 0,5 O |
|---|---|---|---|
| 100 mA | 0,5 mW | 1,0 mW | 5,0 mW |
| 200 mA | 2,0 mW | 4,0 mW | 20,0 mW |
| 300 mA | 4,5 mW | 9,0 mW | 45,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é:VCCno+5 V,GNDnoGND,INnoGPIO12. MOSFETde canal N: gate noGPIO13com resistor de10 kdo gate aoGND,fontenoGND,drenona carga, e a outra perna da carga no+5 V.- Servo
SG90: sinal noGPIO18, vermelho no+5 V, preto noGND. LEDcom resistor na saída dorelé, com o outro lado noGND.- Cabo USB conectado, monitor serial em 115200.
- Preencha no caderno a tabela de cargas: nome, corrente da carga, e quantas vezes o pino de
12 mAaguenta. Marque com umXas linhas em que a placa queimaria. Quantas linhas passaram? - Monte a parte do
relécom oLEDe grave o sketch da resolução. Cole no caderno as duas linhas doSerialque dizem o que o módulo faz. Em uma frase: por que a corrente doLEDnão passa peloGPIO12? - Descubra a lógica do seu módulo de
relée escreva no caderno:HIGHliga ou desliga? Se a sua resposta for o contrário do sketch, escreva qual linha teria que mudar. - Escolha uma carga para o
MOSFETe calcule a dissipação de calor com a resistência entredrenoefontedo seu transistor. Escreva a conta inteira, com os números, e diga se odissipadoré obrigatório, recomendado ou desnecessário nessa carga. - 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? - Escolha, para a
ventoinhada estação e para alâmpadade 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. - Escreva em uma frase por que a
ventoinhade 5 V é exagero nesta aula, usando a conta do item 4, e em uma frase quando ela passaria a ser necessária. - 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ério | Pontos |
|---|---|
| Tabela de cargas preenchida, com a comparação com o pino de 12 mA e as linhas marcadas | 2 pontos |
Montagem do relé com o LED funcionando e a explicação de por que a corrente não passa pelo GPIO | 2 pontos |
Lógica do módulo de relé descoberta e escrita, com a linha que mudaria se fosse o contrário | 1 pontos |
| Conta de dissipação de calor feita com números, para uma carga escolhida | 3 pontos |
Repetição da conta com o dobro da corrente, e o efeito sobre a decisão do dissipador | 2 pontos |
Escolha de peça para a ventoinha e para a lâmpada do projeto, com as duas justificativas | 1 pontos |
| Regra de segurança reescrita com as palavras do aluno, e o que a dupla não faz por causa dela | 1 pontos |
Erros comuns
| Erro | Como aparece | Correçã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.
