Ações: rele, MOSFET e servo — Arduino e IoT — semana 10 do 1o trimestre

Informatica · Conteudo · publicado em 02/10/2026
Semana 10 · Ações: rele, MOSFET e servo — Material de Apoio Arduino

Semana 10 de 13· 1o trimestre · 01/02 a 30/04

Ações: rele, MOSFET e servo

Atrito com carga: ligar e desligar coisa que não e LED.

Aula 1 — Rele e MOSFET: acender e desligar carga

Objetivos

  • Acender e apagar uma carga de verdade usando um módulo de rele de 1 canal.
  • Ligar e desligar uma carga de maior corrente com um MOSFET, em vez de usar o rele.
  • Explicar por que a maioria dos módulos de rele aciona em LOW, e escrever o código com essa inversao.
  • Justificar com um exemplo quando escolher rele e quando escolher MOSFET.
  • Apontar, num circuito com carga indutiva, onde entra o diodo de protecao.

Material

  • 1 ESP32 DevKit V1 por aluno, com o cabo USB
  • 1 módulo de rele de 1 canal (KY-019 ou equivalente) por aluno
  • 1 MOSFET do kit, do tipo IRF520N ou equivalente, com dissipador
  • 1 LED de 5 V ou uma lampada de bancada, como carga
  • 1 protoboard de 830 pontos e 6 jumpers por aluno
  • 1 módulo MB102 por dupla
  • 1 diodo 1N4007 por dupla, para a montagem da carga indutiva

Conceitos

O problema do corrente

O pino do ESP32 entrega corrente de ordem de poucos miliamperes. Um LED com resistor passa. Uma lampada, um motor, um aquecedor, não.

Entre o pino e a carga precisa entrar alguma coisa. As duas respostas do kit são o rele e o MOSFET, e elas resolvem o mesmo problema de jeitos diferentes.

CaracteristicaReleMOSFET
Como funcionauma parte move e fecha o contatonenhum movimento, decide eletronicamente
Velocidade de comutalenta, alguns milissegundosquase instantanea
Durabilidade mecanicadesgasta com muitos ciclospraticamente não tem desgaste
Consumo quando ligadocorrente da bobina, alguns mApraticamente zero
Ruido eletricoo clique gera interferenciapraticamente silencioso
Carga ideallampada, aquecedor, ruido toleradomotor, PWM, ruido proibido
Customais carobarato

Rele: a inversao que derruba o aluno

O módulo de rele de 1 canal do kit tem um transistor NPN na entrada. Com esse transistor, o rele aciona com o pino em LOW:

digitalWrite(PINO_RELE, LOW);    // rele ACENDE
digitalWrite(PINO_RELE, HIGH);   // rele DESLIGA

Este e o primeiro caso do curso em que a lógica do programa e o contrario do nome da função. E o professor insiste: o HIGH e o LOW são o nivel no pino, não o estado do rele. Não existe "alto" e "baixo" no rele; existem nivel no transistor.

PinoEstado do releCarga
LOWfechado, a corrente passaligada
HIGHaberto, a corrente passadesligada

Existem módulos com a lógica invertida e módulos com a lógica direta. O código tem que casar com o módulo da sua bancada, e a unica forma de descobrir qual e o seu e gravar e ver. O professor pede isso na atividade de propósito: e mais barato descobrir do que deduzir.

MOSFET: o gate

O MOSFET do kit e um transistor de efeito de campo. O sinal entra pelo gate, e o gate e como uma pequena antena: ele controla se os outros dois terminais, dreno e fonte, passam corrente.

  • O MOSFET do kit e um transistor de baixa potência. Ele não suporta corrente de linha: para ligar uma lampada de tomada por tras dele e preciso um rele.
  • Gate com resistor: a razão e a subida rapida de tensão. No instante da virada, o capacitor interno do gate precisa descarregar, e essa corrente de descarga sai pelo pino. A resistencia limita o pico e protege o GPIO. O valor da aula e 220 ohm a 1 k ohm.
Terminal do MOSFETVai para
Gateo GPIO27, com resistor de 220 ohm no caminho
Drenoa carga
Fontea trilha -

Carga indutiva e o diodo de protecao

Lampada e resistor: quando a corrente para, o campo eletrico simplesmente desaparece. Bobina e motor: não.

Uma bobina guarda energia em campo magnetico. Quando o campo e desligado de repente, essa energia precisa ir para algum lugar, e ela vira um pico de tensão no transistor — dezenas de vezes a tensão de alimentacao. Isso queima o MOSFET.

A solução e um diodo em paralelo com a carga, com o catodo no lado de alimentacao:

+ ---+--------- carga ---------+
    |                          |
    +---------|<|---------------+

Quando o campo e desligado, a corrente induzida ganha caminho pelo diodo e não pelo transistor. E por isso que o relé e melhor que o MOSFET para carga indutiva com pouca comutacao: o MOSFET e o MOSFET, a rede fica isolada do circuito.

Atividade

Montagem:

  • IN do módulo de rele no GPIO26.
  • VCC do rele na trilha + e GND do rele na trilha -.
  • Gate do MOSFET no GPIO27, com resistor de 220 ohm no caminho.
  • Drain do MOSFET na carga (LED ou lampada), e a outra perna da carga na trilha -.
  • Source do MOSFET na trilha -.

Nota: 10 pontos. Critério de fim: o serial mostra as duas cargas alternando a cada 2,5 segundos, e o aluno sabe dizer, sem consultar, qual nivel aciona o rele.

  1. Grave o sketch da resolucao com o rele ligado e confirme que o rele não acende no primeiro teste. Anote o nivel que você escreveu.
  2. Troque para o nivel oposto e grave. Agora o rele acende. Escreva no caderno, ao lado do código, a frase: "este módulo aciona em ___".
  3. Rode o sketch com o rele acionado por 30 segundos seguidos e conte os cliques. O clique some depois de um tempo? Descreva o que o módulo esta fazendo internamente.
  4. Monte a parte do MOSFET e confirme que a carga acende com HIGH no GPIO27. Em uma frase: por que a lógica do rele e a do MOSFET são opostas.
  5. Retire o resistor de 220 ohm do gate e grave. Se a carga falhar ou o ESP32 reiniciar, o que isso indica sobre a corrente de pico? Escreva a resposta em uma frase.
  6. Escreva na lousa o esquema de um motor com o diodo em paralelo. Em uma frase, o que aconteceria com o MOSFET se o diodo fosse removido.

Resolucao

#include <Arduino.h>

const int PINO_RELE = 26;
const int PINO_MOSFET = 27;
const int INTERVALO = 2500;

int voltas = 0;

void setup() {
  pinMode(PINO_RELE, OUTPUT);
  pinMode(PINO_MOSFET, OUTPUT);

  Serial.begin(115200);
  Serial.println();
  Serial.println("Rele e MOSFET — acender e apagar carga");
  Serial.println("O modulo rele deste kit aciona em LOW.");
  Serial.println("rele | mosfet | tempo");
}

void loop() {
  voltas++;

  digitalWrite(PINO_RELE, LOW);
  digitalWrite(PINO_MOSFET, HIGH);
  Serial.print(" ON   |   ON    | ");
  Serial.print(voltas * INTERVALO);
  Serial.println(" ms");
  delay(INTERVALO);

  digitalWrite(PINO_RELE, HIGH);
  digitalWrite(PINO_MOSFET, LOW);
  Serial.print(" OFF  |   OFF   | ");
  Serial.print(voltas * INTERVALO);
  Serial.println(" ms");
  delay(INTERVALO);
}

Por que assim e não de outro jeito. As duas linhas de acionamento são deliberadamente opostas: LOW para o rele, HIGH para o MOSFET. O professor deixa isso na tela sem comentario na primeira gravação, apaga o LOW e pede para alguem ler em voz alta. A turma ve que o mesmo digitalWrite com o mesmo valor produz efeito oposto nas duas pecas, e essa e a aula inteira.

A razão tecnica esta no transistor do módulo de rele: e um NPN, que conduz quando recebe LOW. O professor mostra o esquema do módulo do kit, aponta o transistor, e explica que por isso LOW aciona. Não e aleatoriedade nem mau funcionamento.

O intervalo e o tempo que cada estado fica na tela. 2500 ms e longo de propósito. O aluno precisa ter tempo de ouvir o clique do rele e ver a carga acesa antes de tudo apagar. Com 500 ms, como na aula do LED, a carga fica Piscando e o aluno não chega a ouvir o som que diferencia as duas pecas.

O pinMode dos dois pinos esta no setup porque sem ele não acontece nada — e aqui o sintoma e especialmente enganoso: o rele fica sempre desligado, sem clique e sem erro, e o aluno acredita que o módulo queimou.

Criterios de correcao

CritérioPontos
Rele ligado com a inversao correta do módulo da bancada, e a frase anotada no caderno3 pontos
MOSFET com gate, dreno e fonte na posicao correta2 pontos
Resistor de serie no gate presente1 ponto
Serial mostra os dois estados e o tempo decorrido2 pontos
Item 6 respondido: a função do diodo em paralelo com carga indutiva esta escrita2 pontos

Erros comuns

ErroComo apareceCorrecao
Escrever HIGH para acionar o releNada acontece, sem erro nenhum"Este módulo aciona em LOW. O HIGH e o nivel no pino, não o estado do rele."
Esquecer o pinModeRele nunca fecha, e o aluno culpa o módulo"Sem pinMode(..., OUTPUT) o pino e entrada. E o mesmo erro do dia 2."
Resistor do rele"Funciona, mas não faz barulho""Com resistor na entrada, o transistor não satura. Ligue direto no IN."
MOSFET com gate e fonte trocadosNada acende"Gate e o pino do sinal. Fonte vai para o GND, sempre."
MOSFET sem resistor no gateEsp32 reiniciando, ou nada funcionando"Ponha 220 ohm a 1 k em serie com o gate. O pico de comutacao precisa de caminho."
Usar MOSFET para lampada e reclamar do preco"O MOSFET custa mais que o rele""Para lampada, e mesmo preco e menos pratico. O MOSFET ganha em velocidade e silencio."
Motor sem diodo"O MOSFET queimou no primeiro liga-desliga""Diodo em paralelo com a carga, catodo no lado positivo. Sem ele não ha por onde a energia vai."

Desafio extra

Escreva o código que inverte a lógica do rele em um unico lugar, de modo que exista uma constante RELE_ACIONA_EM_LOW e todo o resto do programa use um "acionar" e um "desacionar" que ninguem precise lembrar a polaridade. Depois explique em três linhas qual das duas pecas escolheria para um portao eletrico que abre e fecha cinco vezes por hora, e por que a escolha muda com a frequência.

>

A resolucao, compilada

// Aula 1 do dia 10: as duas formas de acender e apagar uma carga.
//
// Rele e MOSFET estao na mesma aula porque os dois resolvem o mesmo problema —
// o ESP32 nao aguenta a corrente da carga — e cada um resolve de um jeito. O
// rele carrega a carga electrica; o MOSFET nao. O aluno precisa saber qual
// escolher, e isso e o que a atividade cobra.

#include <Arduino.h>

// Rele de 1 canal. A maioria dos modulos do kit tem a logica invertida:
// aciona em LOW. Este comentario e o aviso mais importante da aula.
const int PINO_RELE = 26;

// MOSFET tipo IRF520N do kit. O sinal vai para o gate, que e a terceira perna.
const int PINO_MOSFET = 27;

// Tempo que cada estado fica na tela, em milissegundos.
const int INTERVALO = 2500;

int voltas = 0;

void setup() {
  // Os dois pinos sao saida. Sem o pinMode, nada funciona — e o mesmo
  // defeito do dia 2, agora aplicado a uma carga que faz barulho.
  pinMode(PINO_RELE, OUTPUT);
  pinMode(PINO_MOSFET, OUTPUT);

  Serial.begin(115200);
  Serial.println();
  Serial.println("Rele e MOSFET — acender e apagar carga");
  Serial.println("O modulo rele deste kit aciona em LOW.");
  Serial.println("rele | mosfet | tempo");
}

void loop() {
  voltas++;

  // Rele ligado: neste modulo, escrever LOW aciona. A inversao esta no
  // software porque nao da para trocar o modulo.
  digitalWrite(PINO_RELE, LOW);
  digitalWrite(PINO_MOSFET, HIGH);
  Serial.print(" ON   |   ON    | ");
  Serial.print(voltas * INTERVALO);
  Serial.println(" ms");
  delay(INTERVALO);

  // Rele desligado: escrever HIGH solta o contato.
  digitalWrite(PINO_RELE, HIGH);
  digitalWrite(PINO_MOSFET, LOW);
  Serial.print(" OFF  |   OFF   | ");
  Serial.print(voltas * INTERVALO);
  Serial.println(" ms");
  delay(INTERVALO);
}

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

Aula 2 — Servo SG90: a biblioteca de servo e o angulo controlado

Objetivos

  • Posicionar um servo SG90 em um angulo escolhido, usando apenas o PWM do ESP32.
  • Explicar por que o servo espera um sinal de 50 Hz e o que o período de 20 ms tem a ver com isso.
  • Converter um angulo em largura de pulso com map, e converter a largura de pulso em valor de duty.
  • Medir em quantos microssegundos o servo responde, e comparar com o valor do manual.
  • Dizer por que o servo recebe alimentacao separada do pino de sinal.

Material

  • 1 ESP32 DevKit V1 por aluno, com o cabo USB
  • 1 servo SG90 (tamanho pequeno, 180 graus) por aluno
  • 1 protoboard de 830 pontos e 4 jumpers por aluno
  • 1 módulo MB102 por dupla, para alimentar o servo pelo 5 V
  • 1 potenciometro de 10 k ohm por dupla, para a atividade de comparacao

Conceitos

O servo não sabe onde esta

Esse e o ponto que o professor precisa martelar, porque contraria a intuicao: o servo não tem sensor de posicao. Não existe encoder, não existe potentiometro interno que ele leia. O que existe e um circuito que mede a duracao de um pulso e compara com a posicao atual.

Por isso o servo tem um angulo de verdade entre 0 e 180 graus, e por isso ele "sabe" onde esta: a posicao vem da propria mecanica. Se você projetar um servo que não deveria ter batente, esse servo não existe.

O sinal de 50 Hz

O servo espera um pulso a cada 20 milissegundos. Isso vem da historia do modelo: 50 Hz era a frequência de alternancia da rede eletrica europeia, de onde saiu o número.

ParametroValorPor que
Frequência do sinal50 Hzpadrao do servo analogico
Período20 ms1 segundo dividido por 50
Pulso para 0 graus500 us2,5 % do período
Pulso para 180 graus2500 us12,5 % do período
Pulso para 90 graus1500 us7,5 % do período

O servo e um consumidor de PWM. O pulso alto e o "sinal", e o tempo que ele dura e a informação. Esses números vem do manual do SG90: não são deduziveis. A aula diz isso ao aluno em voz alta, igual fez com a sequência de inicializacao do display.

Do angulo ao duty: a conta

O caminho tem duas etapas, e o aluno precisa ver as duas.

Etapa 1, angulo vira pulso. O map transforma 0-180 em 500-2500:

int pulso = map(angulo, 0, 180, 500, 2500);

Etapa 2, pulso vira duty. O duty e a fracao do período que o pino fica em HIGH:

duty = pulso / periodo

Para 90 graus: pulso de 1500 us em período de 20000 us = 0,075, ou 7,5 %. Com o PWM de 16 bits, 7,5 % de 65536 vale 4915.

O código faz a conta com deslocamento de bits, e não com multiplicação:

uint32_t duty = (uint32_t)(((uint64_t)pulso << RESOLUCAO_PWM) / PERIODO_US);

Multiplicar por 65536 e o mesmo que deslocar 16 bits para a esquerda. Com o uint64_t no meio, o calculo não estoura: 2500 vezes 65536 passa de 32 bits com sobra.

O periferico que faz isso e o LEDC, que esta no chip e no core. E o mesmo periferico que o tone do dia 7 usa por baixo.

Por que a biblioteca não esta sendo usada

A biblioteca ESP32Servo seria o caminho obvio, e ela não esta instalada na maquina do professor. O contrato do material proibe instalar. Então a aula faz a conta na mao, com ledcAttach e ledcWrite, que são do core.

O ganho pedagogico e maior do que parece: escrevendo a conta, o aluno descobre que "biblioteca de servo" e so uma traducao de angulo para duty. No dia 11, quando o servo precisar de ir e voltar, ele sabe o que acontece em cada linha.

A moral que o professor escreve na lousa: quando uma biblioteca não esta disponivel, você não perde a aula. Você perde so a atalho.

Alimentacao: 5 V separado

O servo SG90 tem motor proprio dentro. Na posicao, ele pede de 100 a 250 mA — muito mais do que um pino entrega, e mais do que o regulator da placa consegue fornecer sem passar aperto.

A solução do kit e alimentar pelo 5V do módulo MB102, e usar o pino do ESP32 so para o sinal:

FioVai para
VCC do servo5V do MB102
GND do servoGND do MB102
Sinal (branco ou amarelo)o GPIO26

O GND tem de ser comum entre o servo e o ESP32. Sem GND comum não existe referência de tensão compartilhada e o sinal não chega ao servo. E o mesmo erro do dia 8 com o DHT11.

O sintoma de alimentacao errada e o mais enganoso de todos: a placa reinicia no meio do movimento. O aluno culpa o código e não a fonte.

Atividade

Montagem:

  • VCC do servo no 5V do MB102.
  • GND do servo no GND do MB102.
  • Fio de sinal do servo no GPIO26.

Nota: 10 pontos. Critério de fim: o servo percorre 0, 45, 90, 135 e 180 graus, e o monitor serial mostra o angulo e o pulso correspondente.

  1. Alimente o servo pelo MB102 e grave o sketch da resolucao. Confira se o servo chega a 180 graus.
  2. Anote o pulso que o serial mostra para cada um dos cinco angulos. Compare com a tabela da aula. Bate?
  3. Troque PULSO_MINIMO_US de 500 para 2000, grave e observe. O que o servo faz? Ele ainda obedece a ordem do programa? Escreva a resposta em uma frase.
  4. Desligue o servo do MB102 e alimente-o pelo 5V da propria placa. Grave e mova o servo. Descreva o que aconteceu. Em uma frase: por que o pino de sinal do ESP32 não pode alimentar o servo.
  5. Escreva a conta do duty para o angulo de 45 graus no caderno, sem usar o monitor: qual e o pulso, e qual e a fracao do período? Depois confere com o serial e diga se acertou.
  6. Troque FREQUENCIA_SERVO de 50 para 100 e grave. O servo obedece? Em uma frase, o que a frequência do sinal controla: o angulo ou a atualização da posicao?

Resolucao

#include <Arduino.h>

const int PINO_SERVO = 26;
const int FREQUENCIA_SERVO = 50;
const long PERIODO_US = 1000000L / FREQUENCIA_SERVO;
const int PULSO_MINIMO_US = 500;
const int PULSO_MAXIMO_US = 2500;
const int RESOLUCAO_PWM = 16;

const int ANGULOS[] = {0, 45, 90, 135, 180};
const int TOTAL_ANGULOS = 5;
const int ESPERA_ANGULO = 700;

int posicao = 0;

void moverServo(int angulo) {
  int pulso = map(angulo, 0, 180, PULSO_MINIMO_US, PULSO_MAXIMO_US);
  uint32_t duty = (uint32_t)(((uint64_t)pulso << RESOLUCAO_PWM) / PERIODO_US);

  ledcAttach(PINO_SERVO, FREQUENCIA_SERVO, RESOLUCAO_PWM);
  ledcWrite(PINO_SERVO, duty);
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("Servo SG90 — angulo controlado");
  Serial.println("angulo | pulso us | duty");

  moverServo(90);
  delay(300);
}

void loop() {
  int angulo = ANGULOS[posicao];
  moverServo(angulo);

  Serial.print(angulo);
  Serial.print("graus | ");
  Serial.print(map(angulo, 0, 180, PULSO_MINIMO_US, PULSO_MAXIMO_US));
  Serial.print(" us   | ");
  Serial.print(ledcRead(PINO_SERVO));
  Serial.println();

  posicao++;
  if (posicao >= TOTAL_ANGULOS) {
    posicao = 0;
  }

  delay(ESPERA_ANGULO);
}

Por que assim e não de outro jeito. O ledcAttach esta dentro da função moverServo, e não no setup. Isso parece desperdicio — toda chamada refaz a mesma coisa — e funciona porque o periferico aceita reatribuicao sem custo visivel a essa taxa de chamada. O professor explica a troca: se o servo usasse duas frequencias diferentes, o attach teria que ficar no setup, uma vez por frequência, e o aluno entenderia que attach e "configurar", não "ligar".

O ledcRead no serial e o que prova que a conta foi feita. Sem ele, o aluno tem uma linha com o angulo e outra com um número que ele não sabe de onde saiu. Com ele, a coluna do duty e a evidência de que map e o deslocamento de bits fizeram o trabalho.

O map aparece duas vezes no programa: uma em moverServo, outra no Serial.print. Isso e de propósito. Se o serial lesse o duty calculado e dividisse de novo, o aluno veria duas contas e não saberia qual delas o servo recebeu. Imprimir o pulso de novo com map obriga as duas contas a concordarem — e quando o aluno muda PULSO_MINIMO_US, as duas mudam juntas e ele vê o efeito.

O moverServo(90) antes do primeiro Serial.begin deixa o servo no meio. Sem isso, o servo fica em 0 até o primeiro comando, e se ele estiver preso em uma posicao por maquina, o primeiro movimento e um tranco.

O delay(700) entre angulos e o tempo que o servo leva para chegar na posicao. Abaixo disso, o comando chega antes do movimento terminar e o servo fica tremendo entre duas posicoes. 700 ms e mais do que suficiente para um SG90, e o aluno descobre que "esperar o actuator terminar" e uma categoria de tempo diferente de "esperar o intervalo".

Criterios de correcao

CritérioPontos
Servo alimentado pelo 5V do MB102, com GND comum e sinal no GPIO262 pontos
Conversao angulo para pulso feita com map, e pulso para duty com a conta do período3 pontos
Cinco angulos percorridos, e o servo chega a 1802 pontos
Serial mostra angulo e pulso, e a coluna do duty confere com a conta do caderno1 ponto
Item 6 respondido: a frequência controla a atualização, não o angulo2 pontos

Erros comuns

ErroComo apareceCorrecao
Servo alimentado pelo pino do ESP32"A placa reinicia quando o servo anda""Alimente pelo 5V do MB102. O pino so manda sinal."
Servo tremendo entre duas posicoes"Fica vibrando não chega""Aumente o ESPERA_ANGULO. O comando esta chegando antes do movimento terminar."
Valores de pulso trocados"O servo vai para 180 quando peço 0""Verifique se PULSO_MINIMO_US e PULSO_MAXIMO_US estão em ordem crescente."
Usar map de 500 para 2500 em vez de 2500 para 500"O servo anda ao contrario""O map mapeia o primeiro intervalo no segundo, na ordem. Minimo para minimo."
duty calculado sem dividir pelo período"O servo mal se mexe""O duty e uma fracao do período. Divida o pulso pelo período."
uint32 estourando na conta"O servo vai para um angulo aleatorio""Use uint64_t no meio do calculo e converta no fim."
Sem GND comum"O servo não responde a nada""GND do servo tem de ir no mesmo GND do ESP32."

Desafio extra

Escreva a função que faz o servo ir de 0 a 180 e voltar, em movimento continuo, usando millis em vez de delay — um passo de meio grau a cada 20 ms. Depois calcule quantos passos o servo precisa para varrer a faixa inteira e escreva, em três linhas, por que isso e uma taxa de atualização e não um angulo.

>

A resolucao, compilada

// Aula 2 do dia 10: o servo, que e a saida que aceita um numero como comando.
//
// Um servo nao e um LED com brilho. Ele recebe um numero de graus e vai
// ate la. O numero vira tempo de pulso: 500 microssegundos sao 0 graus, 2500
// microssegundos sao 180. Esta aula e sobre essa conversao, feita com o PWM
// do proprio chip, sem depender de biblioteca de servo.
//
// A ESP32Servo nao esta instalada na maquina do professor. Em vez de instalar,
// o codigo usa o periferico LEDC, que esta no core e faz exatamente o que a
// biblioteca faria: gerar 50 Hz com a largura de pulso pedida.

#include <Arduino.h>

// Pino de sinal do servo. 26 e um GPIO comum.
const int PINO_SERVO = 26;

// Frequencia do sinal de controle, em Hz. O servo espera 50 Hz: e um padrao
// de fabrica consolidado, nao uma escolha do ESP32. O periodo e 1 dividido
// por 50, ou seja, 20 milissegundos.
const int FREQUENCIA_SERVO = 50;

// Periodo do sinal em microssegundos: 1 segundo tem 1 milhao de microssegundos,
// dividido por 50 Hz da 20 milissegundos.
const long PERIODO_US = 1000000L / FREQUENCIA_SERVO;

// Largura de pulso que significa 0 graus e 180 graus, em microssegundos.
// Estes numeros vem do manual do SG90, nao da logica: sao a unica parte do
// codigo que o aluno nao consegue adivinhar.
const int PULSO_MINIMO_US = 500;
const int PULSO_MAXIMO_US = 2500;

// Resolucao do PWM: 16 bits dao 65536 niveis. Com um periodo de 20 mil
// microssegundos, sobra meio microssegundo de precisao, que e mais do que o
// servo distingue.
const int RESOLUCAO_PWM = 16;

// Os quatro angulos do varredor, em graus.
const int ANGULOS[] = {0, 45, 90, 135, 180};
const int TOTAL_ANGULOS = 5;

// Tempo que o servo fica parado em cada angulo, em milissegundos.
const int ESPERA_ANGULO = 700;

int posicao = 0;

// Converte o angulo em largura de pulso e escreve no PWM. Esta e a funcao que
// a biblioteca de servo faria: ela existe aqui para o aluno ver o calculo.
void moverServo(int angulo) {
  // map transforma 0-180 em 500-2500. E a mesma funcao que o aluno ja usou
  // com o potenciometro no dia 5, e por isso que ela vale a pena ter aprendido.
  int pulso = map(angulo, 0, 180, PULSO_MINIMO_US, PULSO_MAXIMO_US);

  // O duty e a fracao do periodo que o pino fica em HIGH. Em um periodo de
  // 20 mil microssegundos, um pulso de 1 mil e 5% de duty. Com 16 bits, 5%
  // vale 0,05 vezes 65536, que e 3276.
  uint32_t duty = (uint32_t)(((uint64_t)pulso << RESOLUCAO_PWM) / PERIODO_US);

  ledcAttach(PINO_SERVO, FREQUENCIA_SERVO, RESOLUCAO_PWM);
  ledcWrite(PINO_SERVO, duty);
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("Servo SG90 — angulo controlado");
  Serial.println("angulo | pulso us | duty");
  Serial.println();

  // Deixa o servo no meio antes da primeira mensagem, para nao bater no
  // batente de 180 no reposicionamento.
  moverServo(90);
  delay(300);
}

void loop() {
  int angulo = ANGULOS[posicao];
  moverServo(angulo);

  Serial.print(angulo);
  Serial.print("graus | ");
  Serial.print(map(angulo, 0, 180, PULSO_MINIMO_US, PULSO_MAXIMO_US));
  Serial.print(" us   | ");
  Serial.print(ledcRead(PINO_SERVO));
  Serial.println();

  posicao++;
  if (posicao >= TOTAL_ANGULOS) {
    posicao = 0;
  }

  delay(ESPERA_ANGULO);
}

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