PWM
PWM na prática com a ESP32: LED, buzzer e servo
Modulação por largura de pulso na ESP32 com MicroPython — frequência, duty, por que 1000 Hz vira 998, brilho de LED, notas no buzzer e o ângulo do servo SG90.
Por Prof. Rodrigo Faustino · Atualizado em · 12 min de leitura
Um pino digital só sabe duas coisas: 0 V ou 3,3 V. Mesmo assim, a ESP32 deixa um LED a meia-luz, toca uma melodia num buzzer e posiciona o braço de um servo em 37 graus. O truque por trás das três coisas é o mesmo: ligar e desligar o pino muito rápido, controlando quanto tempo ele fica em cada nível. Isso se chama PWM, pulse width modulation, ou modulação por largura de pulso.
Neste artigo vamos entender os dois números que descrevem um PWM, ver como a ESP32 gera esses sinais por dentro — incluindo o motivo de freq() devolver 998 quando você pediu 1000 —, e aplicar o PWM nas três peças mais comuns de laboratório: o LED, o buzzer passivo e o servo SG90. Cada uma usa o PWM de um jeito diferente, e entender essa diferença é o que separa quem copia exemplos de quem consegue adaptá-los.
Os dois números do PWM
Um sinal PWM é uma onda quadrada que se repete. Ele é descrito por dois números:
- a frequência: quantas vezes por segundo o ciclo se repete. A 1000 Hz, são mil ciclos por segundo, e cada ciclo dura 1 ms. O tempo de um ciclo é o período:
período = 1 / frequência; - o duty (ciclo de trabalho): a fração do período em que o pino fica em nível alto. Com duty de 50 %, o pino passa metade do ciclo em 3,3 V e metade em 0 V; com 10 %, só um décimo do ciclo em 3,3 V.
Se o ciclo é rápido o bastante, quem recebe o sinal não percebe as piscadas, só a média. Para um LED, a média é o brilho: duty de 50 % dá uma tensão média de 1,65 V e um LED mais fraco. Para um motor, a média é a força. Para um buzzer, ao contrário, o que importa é justamente a vibração — e aí a frequência é que manda. E para um servo, o que conta é a largura exata de cada pulso, em microssegundos.
Da ementa ao semestre planejado
- Plano de ensino e calendário montados sozinhos
- Diário de aulas pronto para colar no SIGA
- Slides e listas de exercícios com gabarito
Versão Community grátis · Windows
PWM no MicroPython da ESP32
O PWM é a classe PWM do módulo machine. Ela recebe um Pin e, opcionalmente, a frequência e o duty:
from machine import Pin, PWM
led = PWM(Pin(18), freq=1000, duty_u16=32768) # 1000 Hz, 50 %
led.duty_u16(16384) # 25 %
led.duty(256) # o mesmo em 10 bits (0 a 1023)
led.duty_ns(250000) # o mesmo em tempo: 250 µs em alto
led.freq(2000) # muda a frequência
led.deinit() # desliga o PWM; o pino fica em 0São três jeitos de dizer o duty, cada um útil num caso:
duty_u16(x), de 0 a 65535: o jeito portátil, igual em todas as placas com MicroPython. 65535 é sempre aceso;duty(x), de 0 a 1023: a escala antiga de 10 bits, ainda muito usada em tutoriais. Na ESP32 ela é sóduty_u16 >> 6;duty_ns(x): o tempo em nível alto, em nanossegundos. É o mais natural para servos, que entendem largura de pulso.
Se você não disser nada, o PWM nasce com 5000 Hz e duty de 50 % (duty_u16 32768). Esse padrão é razoável para LEDs e desastroso para servos, como veremos.
Os limites da ESP32
O periférico que gera PWM na ESP32 se chama LEDC (o nome vem de LED control, mas ele serve para tudo). Seus limites, que o simulador reproduz com as mensagens da placa, são:
- frequência de 1 Hz a 40 MHz. Fora disso:
ValueError: frequency must be from 1Hz to 40MHz; - 16 canais: até 16 pinos com PWM ao mesmo tempo;
- no máximo 8 frequências diferentes ao mesmo tempo, porque os canais dividem 8 temporizadores (2 modos com 4 timers cada). Pedir uma nona frequência num pino novo dá
RuntimeError: out of PWM channels:16; - os GPIOs 34 a 39 são só entrada e não fazem PWM: a placa responde
OSError: (-258, 'ESP_ERR_INVALID_ARG').
A regra das 8 frequências raramente incomoda em projetos de aula, mas aparece quando alguém tenta tocar várias notas diferentes em vários buzzers ao mesmo tempo. Pinos com a mesma frequência dividem um temporizador, então dez LEDs a 1000 Hz cabem tranquilamente.
Por que 1000 Hz vira 998 Hz
Rode este exemplo e repare no que freq() imprime:
from machine import Pin, PWM
import time
led = PWM(Pin(18), freq=1000, duty_u16=0)
print('pedi 1000, a placa usa', led.freq())
for f in (5000, 2000, 700):
led.freq(f)
print('pedi', f, '-> a placa usa', led.freq())
for duty in (6553, 32768, 65535):
led.duty_u16(duty)
time.sleep(1)
led.duty_u16(0)A saída começa com 998, e não 1000. A ESP32 não "inventa" a frequência: o LEDC conta pulsos de um relógio interno de 80 MHz e divide esse relógio por um número. Esse divisor tem uma parte fracionária limitada, então nem toda frequência sai exata. Para 1000 Hz não existe divisor perfeito, e a placa usa o mais próximo — que dá 998 Hz. Pedindo 2000 Hz, a placa usa 1997; pedindo 700, usa 701; 5000 sai exato. freq() sempre devolve a frequência que a placa realmente gera, não a que você pediu.
O mesmo cálculo define a resolução do duty, ou seja, em quantos degraus o período é dividido. Quanto mais alta a frequência, menos contagens do relógio cabem num período, e menos degraus sobram: a 50 Hz e a 1000 Hz, o duty tem 16 bits; a 5000 Hz, 13 bits; a 20 kHz, 11 bits; e no extremo de 40 MHz, só 1 bit — quase não sobram degraus para o duty. Você continua escrevendo duty_u16 de 0 a 65535 em todos os casos — o MicroPython converte —, mas em frequências muito altas vários valores vizinhos viram o mesmo degrau.
O simulador faz essas contas com as mesmas fórmulas do driver do ESP-IDF que o MicroPython v1.29.0 usa. Por isso o 998 aparece igual na tela e na placa.
Sinal invertido e o pino depois do deinit
O construtor também aceita invert=True, que inverte a saída: o duty passa a medir o tempo em nível baixo. É útil com cargas ligadas "ao contrário" — como o LED RGB de ânodo comum, em que cada cor acende com o pino em 0. Sem invert, o mesmo efeito exige fazer 65535 - duty em todo lugar do programa.
Quando o PWM é desligado com deinit(), o pino continua sendo uma saída, parada no nível de repouso — em 0 com o sinal normal. E chamar Pin(18, Pin.OUT) tira o pino do controle do PWM e devolve o comando ao Pin, o que permite alternar entre os dois usos no mesmo programa.
LED: brilho e fade
Para um LED, o duty é o brilho. O circuito é o mesmo LED com resistor de 220 Ω do primeiro artigo, agora no GPIO18. A frequência precisa ser alta o bastante para o olho não ver as piscadas: abaixo de uns 30 Hz o LED pisca visivelmente em vez de parecer mais fraco. Centenas ou milhares de hertz são a escolha segura.
O fade — acender e apagar devagar — é só um laço que muda o duty aos poucos. Pensar em porcentagem facilita a conta:
from machine import Pin, PWM
import time
led = PWM(Pin(18), freq=1000, duty_u16=0)
for vez in range(3):
for p in range(0, 101, 5): # 0, 5, ..., 100 %
led.duty_u16(p * 65535 // 100)
time.sleep_ms(30)
for p in range(100, -1, -5): # 100, 95, ..., 0 %
led.duty_u16(p * 65535 // 100)
time.sleep_ms(30)
led.deinit()Dois detalhes desse código pegam iniciantes. O primeiro é a divisão inteira //: duty_u16 só aceita inteiros, e p * 65535 / 100 daria um float e um TypeError. O segundo é a duração do fade: são 20 degraus de 30 ms, 600 ms para acender. Passo menor deixa a transição mais suave, espera menor deixa mais rápida.
Um terceiro detalhe é de percepção: o olho não enxerga o brilho em linha reta. A diferença entre 0 % e 10 % parece muito maior que a diferença entre 90 % e 100 %. Fades que parecem "profissionais" usam degraus pequenos no começo e maiores no fim (uma curva exponencial ou quadrática). Para aprender, degraus iguais bastam — e deixam a matemática clara.
Cores com o LED RGB
O LED RGB de cátodo comum são três LEDs num corpo só, vermelho, verde e azul, com o negativo compartilhado. Com um PWM em cada perna, o olho mistura as cores: vermelho com verde dá amarelo, verde com azul dá ciano, os três juntos dão branco. Nas cores de tela, cada canal vai de 0 a 255; multiplicando por 257, 255 vira exatamente 65535.
Há um detalhe físico que a conta revela: o vermelho tem tensão direta de cerca de 2,0 V, e o verde e o azul ficam perto de 3,0 V. Com 3,3 V no pino e 220 Ω, o vermelho recebe uns 6 mA e os outros dois pouco mais de 1 mA — por isso o "branco" sai rosado. Para equilibrar, ou se diminui o duty do vermelho, ou se usa resistores menores no verde e no azul.
A IA que lembra da sua vida e mora no seu computador
- Memória de longo prazo com a fonte de cada resposta
- Funciona offline: seus dados ficam no seu PC
- Lê PDFs, slides, fotos e gravações de reunião
Plano Essencial grátis para sempre
Buzzer passivo: a frequência vira nota
Som é ar vibrando. O buzzer passivo tem uma plaquinha que se mexe a cada mudança de tensão: se o pino liga e desliga 440 vezes por segundo, a plaquinha vibra 440 vezes por segundo e você ouve a nota lá. Aqui a frequência deixa de ser detalhe técnico e vira a nota; o duty de 50 % dá a onda mais "cheia", e o duty 0 faz silêncio.
from machine import Pin, PWM
import time
buzzer = PWM(Pin(23), freq=440, duty_u16=0)
def tocar(freq, ms):
buzzer.freq(freq)
buzzer.duty_u16(32768) # onda quadrada: metade em cima, metade embaixo
time.sleep_ms(ms)
buzzer.duty_u16(0) # silêncio entre as notas
time.sleep_ms(50)
# dó, ré, mi, fá, sol, lá, si, dó
for nota in (262, 294, 330, 349, 392, 440, 494, 523):
tocar(nota, 250)
buzzer.deinit()No simulador, ligue o botão Som da bancada antes de rodar (o navegador exige um clique antes do primeiro som). As frequências da escala de dó maior, arredondadas e com o lá em 440 Hz, são: dó 262, ré 294, mi 330, fá 349, sol 392, lá 440, si 494 e o dó seguinte 523. Uma oitava acima é sempre o dobro da frequência.
Repare que o tocar() muda a frequência a cada nota. Como só existe um buzzer, só uma frequência está em uso por vez, e o limite de 8 frequências nem chega perto.
O buzzer ativo é outra peça: tem um oscilador dentro e toca um tom fixo (perto de 2300 Hz, pela ficha típica) com tensão contínua. Basta Pin(23, Pin.OUT).on(). Ele é mais simples, mas toca uma nota só. O passivo, ao contrário, fica mudo com tensão parada e precisa do PWM — em troca, toca qualquer melodia.
Servo SG90: o ângulo vem da largura do pulso
O servo SG90 é um motorzinho com engrenagens e um circuito de controle que segura o braço num ângulo entre 0° e 180°. Ele não entende "porcentagem de brilho". Entende um pulso que chega 50 vezes por segundo (um a cada 20 ms) e cuja largura diz o ângulo:
- pulso de cerca de 0,5 ms (500 µs): 0°;
- pulso de cerca de 2,4 ms (2400 µs): 180°;
- no meio, proporcional: perto de 1,45 ms, uns 90°.
Por isso, com servo, o jeito mais claro de definir o duty é o duty_ns(), que recebe exatamente a largura do pulso em nanossegundos (1 µs = 1000 ns). A conta de graus para microssegundos fica numa função:
from machine import Pin, PWM
import time
servo = PWM(Pin(18), freq=50, duty_u16=0) # 50 Hz, ainda sem pulso
def angulo(graus):
pulso_us = 500 + graus * 1900 // 180 # 0° = 500 µs ... 180° = 2400 µs
servo.duty_ns(pulso_us * 1000)
for g in (0, 45, 90, 135, 180, 90):
angulo(g)
time.sleep(1)Três cuidados fazem a diferença entre um servo que obedece e um servo que treme.
Comece com duty_u16=0. Sem ele, o PWM nasce com duty de 50 %: a 50 Hz isso é um pulso de 10 ms, muito fora da faixa, e o servo dá um tranco para um dos extremos assim que o programa liga. Com duty 0, não sai pulso nenhum e o braço fica onde está até o primeiro angulo().
Dê tempo para o braço chegar. O motor gira com velocidade de verdade — cerca de 0,1 s a cada 60°, ou 0,6° por milissegundo. De 0° a 180° leva uns 300 ms. Se o programa mandar outro ângulo antes, o braço muda de rumo no meio do caminho. O simulador reproduz essa velocidade: o braço não "teleporta".
Os extremos variam. Os 500 e 2400 µs são valores típicos; cada servo tem os seus. Se o SG90 da bancada força e vibra no 0° ou no 180°, encolha um pouco a faixa até ele parar de forçar.
Quem prefere a escala de 10 bits pode usar duty(): a 50 Hz, a faixa útil do SG90 fica aproximadamente entre 26 e 123. Funciona, mas esconde a ideia de "largura do pulso" atrás de um número sem unidade.

Alimentação: 5 V, não 3,3 V
O motor do servo puxa bem mais corrente que um LED, principalmente na partida. Ligado no 3V3 da placa, ele pode derrubar a tensão a ponto de reiniciar a ESP32 no meio do movimento — um sintoma confuso, porque parece erro de programa. O simulador mostra o aviso servo-em-3v3 nesse caso. A ligação certa é:
- fio vermelho no VIN (os 5 V que vêm da USB);
- fio marrom no GND, o mesmo GND da placa;
- fio laranja no pino do PWM.
O GND comum é indispensável: o pulso só tem significado se o servo e a ESP32 medirem a tensão a partir da mesma referência. Com vários servos ou outras cargas fortes, a USB deixa de dar conta — uma porta USB 2.0 fornece até 500 mA —, e o caminho é uma fonte externa de 5 V, com o GND ligado ao da placa.
Quando o PWM não faz o que você espera
Um roteiro para os problemas mais comuns:
- O LED pisca em vez de ficar fraco: frequência baixa demais. Suba para centenas de hertz;
TypeErrorno duty: o valor é float. Use//ouint();- O servo dá um tranco ao ligar: faltou
duty_u16=0no construtor; - O servo treme parado: pulso fora da faixa do seu servo, ou alimentação fraca;
out of PWM channels: frequências diferentes demais ao mesmo tempo, ou mais de 16 pinos com PWM;- O pino fica em 0 depois do programa: é o
deinit()fazendo o seu trabalho — o pino continua saída, em nível baixo.
A aba Pinos do simulador ajuda nos diagnósticos: ela mostra cada pino com PWM, a frequência real e o duty, e a aba Avisos aponta os problemas elétricos.
Para onde ir depois
PWM é a ponte entre o mundo digital do programa e grandezas contínuas: brilho, som, posição. O caminho inverso — medir uma grandeza contínua e transformá-la em número — é o trabalho do conversor analógico-digital, assunto do próximo artigo, O ADC da ESP32. Juntando os dois, um potenciômetro vira dimmer de LED ou controle de servo em poucas linhas.
A trilha de PWM refaz este artigo em lições curtas, com o LED a meia-luz, o fade, as cores do RGB, a escala no buzzer e a varredura do servo, e termina com um desafio em que você mesmo monta o servo na bancada.
Para praticar no PyPlaca
Trilhas
Lições
- PWM: o LED a meia-luz
- Fade: acender e apagar devagar
- LED RGB: misturando cores
- Buzzer passivo: notas e melodias
- Servo SG90: o ângulo vem do pulso