Sensores
Sensores en la ESP32: DHT22, HC-SR04, PIR y el divisor de tensión
DHT22, HC-SR04 y PIR en la ESP32 con MicroPython: el protocolo de cada sensor, los errores que muestra la placa y el divisor de tensión en el ECHO de 5 V.
Por Prof. Rodrigo Faustino · Actualizado el · 13 min de lectura
Métricas de este artículo
- Palabras
- 2465
- Caracteres
- 13.281
- Tiempo de lectura
- ~13 min
Con entradas digitales y el ADC, la ESP32 ya consigue leer botones y tensiones. Los sensores de verdad, sin embargo, rara vez son tan simples. Muchos tienen un chip propio y conversan con la placa mediante un protocolo: una secuencia acordada de pulsos, con tiempos que hay que respetar. Otros funcionan con 5 V, una tensión que la ESP32 no soporta en los pines. Y casi todos tienen límites de velocidad, alcance y precisión que el programa necesita conocer.
Este artículo estudia tres sensores clásicos de laboratorio, cada uno representando una familia: el DHT22, que envía temperatura y humedad ya calculadas por un único cable; el HC-SR04, que mide distancia cronometrando un eco; y el PIR HC-SR501, que detecta presencia y entrega una señal digital. En el camino, el divisor de tensión aparece como la pieza que separa un montaje que funciona de una ESP32 dañada.
DHT22: números listos por un solo cable
El DHT22 (también vendido como AM2302) mide la temperatura, de −40 a 80 °C con precisión de ±0,5 °C, y la humedad relativa del aire, de 0 a 100 %, con ±2 % típico (hasta ±5 % en el peor caso), según la hoja de datos de Aosong. No es analógico: tiene un pequeño chip que hace las medidas y envía los números a la ESP32 por un cable de datos.
El MicroPython de la ESP32 ya trae el módulo dht en el firmware, y el simulador usa el mismo archivo original de la biblioteca oficial:
import dht
from machine import Pin
import time
sensor = dht.DHT22(Pin(4))
for i in range(5):
sensor.measure() # conversa con el sensor
t = sensor.temperature() # °C
u = sensor.humidity() # % de humedad relativa
print('%.1f °C %.1f %%' % (t, u))
time.sleep(2) # el DHT22 pide al menos 2 s entre lecturasHay una división de tareas que confunde a mucha gente: measure() es el único método que conversa con el sensor; temperature() y humidity() solo devuelven lo que llegó en la última conversación. Olvidar el measure() dentro del bucle hace que el programa imprima siempre el mismo valor, aunque la sala se esté calentando.
La conversación en el cable de datos
El protocolo del DHT22 es un intercambio corto y cronometrado. La ESP32 mantiene la línea de datos en 0 durante 18 ms (el "¡despierta!") y después la suelta. El sensor responde con 40 bits: 16 de humedad, 16 de temperatura y 8 de comprobación (checksum), una suma que permite detectar bits que llegaron mal. Cada bit se codifica por el tiempo en que la línea permanece en nivel alto: un pulso corto es 0, un pulso largo es 1. Sumando la preparación, cada measure() tarda cerca de 272 ms en el simulador, y el programa se queda parado durante ese tiempo.
La resistencia de pull-up y el ETIMEDOUT
En esa línea de datos ninguno de los dos lados "empuja" el nivel 1. La ESP32 y el sensor solo saben tirar a 0 o soltar la línea: es una salida de drenador abierto, el mismo principio que permite que varios aparatos compartan un cable sin cortocircuito. Cuando los dos sueltan, alguien tiene que llevar la línea a 1: es la resistencia de pull-up, de 10 kΩ al 3V3 en el circuito del ejemplo.
Sin pull-up, la ESP32 manda el "¡despierta!", suelta la línea y no ve ninguna respuesta. El measure() se rinde y la placa muestra:
OSError: [Errno 116] ETIMEDOUT
El 116 es el código de "tiempo agotado" de la biblioteca C usada en el firmware de la ESP32 (en computadores con Linux y en la antigua ESP8266, el mismo error aparece como 110; por eso los tutoriales antiguos muestran otro número). El mismo error aparece con el sensor apagado, con el cable de datos suelto o en el pin equivocado. Los módulos de DHT22 que vienen en una plaquita ya traen la resistencia; el sensor solo, de cuatro patas, no.
Un programa robusto trata ese error en lugar de colgarse:
import dht
from machine import Pin
import time
sensor = dht.DHT22(Pin(4))
for i in range(5):
try:
sensor.measure()
print('temperatura: %.1f °C' % sensor.temperature())
except OSError as e:
print('el sensor no respondió:', e)
time.sleep(2)Ejecútalo, luego borra la resistencia de pull-up en el simulador (selecciónala y Supr) y ejecuta de nuevo: el except pasa a capturar el error en cada vuelta.
Dos segundos entre lecturas
El fabricante pide al menos 2 s entre una medida y otra del DHT22 (en el DHT11, el hermano más simple, 1 s). Leer más rápido no da error garantizado, pero el fabricante no garantiza el resultado, y el simulador muestra el aviso dht-rapido. El DHT11, por cierto, es más barato y más limitado: mide de 0 a 50 °C con ±2 °C y de 20 a 90 % de humedad con ±5 %, y envía los valores en números enteros.
| DHT11 | DHT22 (AM2302) | |
|---|---|---|
| Temperatura | 0 a 50 °C, ±2 °C | −40 a 80 °C, ±0,5 °C |
| Humedad | 20 a 90 %, ±5 % | 0 a 100 %, ±2 % típico |
| Intervalo mínimo | 1 s | 2 s |
| En MicroPython | dht.DHT11(pin) | dht.DHT22(pin) |
Cambiar uno por otro en el código es una sola palabra, porque los dos usan el mismo protocolo de un cable y la misma biblioteca. Lo que cambia es la interpretación de los bytes: el DHT22 envía los valores en décimas y con bit de signo, para representar temperaturas negativas; el DHT11, en enteros. Usar DHT11 con un DHT22 en el banco de trabajo (o al revés) no da error: da números absurdos, lo que es peor. Comprueba el nombre impreso en el sensor antes de elegir la clase.
Del programa al semestre planificado
- Plan de enseñanza y calendario montados solos
- Diario de clases listo para pegar en el SIGA
- Diapositivas y listas de ejercicios con solucionario
Versión Community gratis · Windows
HC-SR04: medir distancia con un eco
El HC-SR04 mide distancia como un murciélago: emite un sonido demasiado agudo para el oído humano, el ultrasonido, y espera que el eco vuelva de un obstáculo. Cuanto más lejos esté el obstáculo, más tarda el eco. El módulo tiene cuatro pines: VCC (5 V), TRIG, ECHO y GND.
La conversación es así:
- la ESP32 manda un pulso de al menos 10 µs en el TRIG;
- el módulo emite el ultrasonido y pone el ECHO en 1;
- cuando el eco vuelve, el ECHO cae a 0.
El tiempo en que el ECHO está en 1 es el tiempo de ida y vuelta del sonido. El sonido recorre cerca de 1 cm cada 29 µs; ida y vuelta, 58 µs por centímetro. De ahí la cuenta más famosa de la electrónica de aficionado:
distancia (cm) = tiempo del ECHO (µs) ÷ 58
distancia = tiempo ÷ 58.Cronometrar el pulso con time_pulse_us
Medir cuánto tiempo está un pin en 1 es tan común que MicroPython tiene una función para eso: machine.time_pulse_us(pino, nivel, timeout_us). Espera a que el pin llegue al nivel, mide cuánto tiempo se queda ahí y devuelve el valor en microsegundos. Si algo sale mal, devuelve un número negativo:
- −2: el pulso ni siquiera empezó dentro del
timeout_us(sensor apagado, TRIG suelto, cable equivocado); - −1: el pulso empezó, pero no terminó a tiempo (obstáculo demasiado lejos).
from machine import Pin, time_pulse_us
import time
trig = Pin(25, Pin.OUT, value=0)
echo = Pin(18, Pin.IN)
def medir_cm():
trig.value(1)
time.sleep_us(10) # pulso de 10 µs en el TRIG
trig.value(0)
t = time_pulse_us(echo, 1, 30000)
if t < 0:
return None # el eco no volvió a tiempo
return t // 58
for i in range(10):
print(medir_cm())
time.sleep_ms(500)El módulo mide de 2 a 400 cm. Sin obstáculo en el alcance, el ECHO se queda en 1 unos 38 ms. Por eso el timeout_us de 30 000 µs devuelve −1 en ese caso, y la función lo convierte en None: el programa no se queda preso esperando.

Fíjate en Pin(25, Pin.OUT, value=0): el value=0 se aplica antes de que el pin se convierta en salida, así que el TRIG nunca pasa por un instante en nivel alto al configurarse. ¿Y por qué el GPIO25, y no el GPIO5 de muchos tutoriales? El GPIO5 es un pin de strapping con pull-up de fábrica: nace en 1 cuando la placa se enciende. Conectado al TRIG, en el momento en que el programa lo pone en 0 el módulo ve una bajada y hace una medida "fantasma". El simulador reproduce ese comportamiento, igual que la placa.
El ECHO tiene 5 V: el divisor de tensión
El HC-SR04 se alimenta con 5 V, y el ECHO sale con 5 V cuando está en 1. Los pines de la ESP32 trabajan con 3,3 V y aguantan como máximo 3,6 V. Conectar el ECHO directamente a un GPIO puede dañar la ESP32. El simulador muestra el aviso crítico cinco-volts-gpio cuando eso ocurre.
La solución clásica es bajar la tensión con un divisor de tensión: dos resistencias en serie entre el ECHO y el GND, con el GPIO conectado en el medio.
V_medio = V_entrada × R2 / (R1 + R2)
Con R1 = 1 kΩ (entre el ECHO y el medio) y R2 = 2 kΩ (entre el medio y el GND):
V_medio = 5 × 2000 / (1000 + 2000) ≈ 3,33 V
Este valor cumple las dos exigencias a la vez: está por debajo de los 3,6 V que soporta el pin y por encima de los cerca de 2,48 V que necesita la ESP32 para leer un 1 (tres cuartos de 3,3 V, como vimos en el artículo sobre GPIO). Cuando el ECHO está en 0, el medio queda en 0 V. La señal llega entera, solo que con la tensión correcta.
Tres reglas para elegir las resistencias
- La razón
R2 / (R1 + R2)define la tensión. Con 1 kΩ y 2 kΩ, dos tercios; - Intercambiar las dos de lugar lo cambia todo. Con 2 kΩ arriba y 1 kΩ abajo sobra solo un tercio, cerca de 1,67 V, que no llega a los 2,48 V: el pin leería siempre 0 y el sensor parecería roto;
- Valores muy altos dejan lentos los flancos de la señal; valores muy bajos gastan corriente en vano. La pareja 1 kΩ y 2 kΩ es la más común con el HC-SR04.
Comprobar la cuenta con el ADC
Se puede comprobar la cuenta de un divisor con el ADC del artículo anterior. El circuito del ejemplo de abajo monta justamente el divisor "intercambiado" (2 kΩ arriba, 1 kΩ abajo), conectado a los 5 V del VIN, y mide el punto medio en el GPIO34:
from machine import Pin, ADC
adc = ADC(Pin(34))
v = adc.read_uv() / 1_000_000
print('en el medio del divisor: %.2f V' % v)
print('según la cuenta: %.2f V' % (5 * 1000 / (2000 + 1000)))Las dos líneas muestran prácticamente el mismo valor, cerca de 1,67 V. ¿Y por qué no medir el divisor correcto, de 3,33 V? Porque, en 11 dB, el ADC de la ESP32 se satura cerca de 3,13 V. El pin aguanta los 3,33 V; es el conversor el que no consigue medirlos. Es un buen ejemplo de que "el pin lo soporta" y "el ADC lo mide" son cosas distintas.
Existe también la variante HC-SR04P, que funciona con 3,3 V y no necesita el divisor. Si tienes dudas sobre qué módulo hay en el banco de trabajo, monta el divisor: funciona con los dos.
PIR HC-SR501: ¿pasó alguien?
El PIR HC-SR501 es el sensor de las luces automáticas de pasillo. Percibe la radiación infrarroja del calor del cuerpo que se mueve delante de la cúpula blanca. Una persona quieta, o una pared caliente, no lo dispara: lo que detecta es el cambio.
Son tres pines: VCC (5 V, en el VIN), OUT y GND. Una buena noticia: aunque se alimenta con 5 V, el OUT sale con 3,3 V, así que puede ir directo a un GPIO, sin divisor. El comportamiento del OUT es:
- movimiento detectado: OUT pasa a 1 al instante;
- el movimiento se detuvo: OUT sigue en 1 durante el tiempo de retención y solo entonces cae a 0;
- nuevo movimiento durante la retención: el tiempo vuelve a empezar.
El tiempo de retención se ajusta en un pequeño potenciómetro del módulo (el trimpot). En el circuito del ejemplo está en 3 s, y en el simulador cambias el valor en el panel de la pieza.
Para el programa, entonces, "llegó alguien" es el flanco de subida del OUT, y "todo quieto" es el flanco de bajada, unos segundos después de que la persona se va. Detectar flancos es el trabajo de Pin.irq:
from machine import Pin
import time
pir = Pin(27, Pin.IN)
led = Pin(2, Pin.OUT)
def mudou(pino):
if pino.value():
print('¡movimiento!')
else:
print('todo quieto')
led.value(pino.value())
pir.irq(mudou, Pin.IRQ_RISING | Pin.IRQ_FALLING)
while True:
time.sleep(1)Con IRQ_RISING | IRQ_FALLING, la función se ejecuta en los dos flancos y pino.value() dice cuál fue. El LED azul de la placa acompaña al sensor. En el simulador, usa el botón de pasar la mano por delante del PIR; pásala de nuevo antes de que el OUT caiga y verás que el "todo quieto" tarda más.
Un cuidado de la placa real que el simulador no reproduce: después de encenderse, el PIR necesita un tiempo para "calentarse", y al comienzo puede dispararse solo. En un proyecto de verdad, espera un poco antes de confiar en las primeras lecturas.
La IA que recuerda tu vida y vive en tu ordenador
- Memoria a largo plazo con la fuente de cada respuesta
- Funciona sin conexión: tus datos se quedan en tu PC
- Lee PDF, diapositivas, fotos y grabaciones de reuniones
Plan Esencial gratis para siempre
Tres sensores, tres lecciones
Cada sensor de este artículo enseña algo que vale para decenas de otros:
- el DHT22 muestra el patrón "sensor con chip y protocolo": una biblioteca se ocupa de los tiempos, el programa llama a
measure(), respeta el intervalo mínimo y trata elOSErrorcuando la conversación falla; - el HC-SR04 muestra el patrón "medir tiempo": el valor está en la duración de un pulso, y
time_pulse_uscon untimeout_usbien elegido evita que el programa se cuelgue; - el PIR muestra el patrón "evento": lo que interesa es el cambio, y las interrupciones en los flancos son la herramienta correcta.
Y los tres juntos muestran la regla eléctrica más importante de la ESP32: ningún pin recibe más de 3,6 V. Antes de conectar un sensor, comprueba la tensión de su salida y, si es de 5 V, el divisor entra en el circuito.
Errores comunes en el banco de trabajo
Casi todos los problemas con estos tres sensores caen en una de estas situaciones:
ETIMEDOUTen el DHT22: falta el pull-up en la línea de datos, el sensor está sin alimentación o el cable de datos está en otro pin. Comprueba los tres antes de desconfiar del código;- el DHT22 devuelve siempre el mismo valor: el bucle llama a
temperature()pero olvidó elmeasure(); - el HC-SR04 solo devuelve
None: el obstáculo está fuera del alcance, el TRIG está suelto, el VCC está en el 3V3 en vez del VIN, o el divisor se montó al revés y el pin nunca ve el 1; - el HC-SR04 mide una vez de más al encender: el TRIG está en un pin de strapping que nace en 1, como el GPIO5;
- el PIR "se dispara solo" nada más encender: es el calentamiento del sensor, que la placa real tiene y el simulador omite;
- el PIR tarda en decir "todo quieto": es el tiempo de retención del trimpot, no un retraso del programa.
El simulador ayuda en buena parte de ellos: la pestaña Avisos señala el pull-up que falta, los 5 V en un GPIO y el sensor sin alimentación, y la pestaña Pines muestra el pulso del ECHO con la duración en microsegundos.
Juntar sensores sin colgarse
Un último aviso, que lleva al penúltimo artículo de la serie. Cada measure() del DHT22 tarda unos 272 ms, y cada medida del HC-SR04 puede tardar hasta el timeout_us. Si el programa también tiene que hacer parpadear un LED, actualizar una pantalla y atender un botón, esas esperas sumadas empiezan a estorbar. Cómo organizar varias tareas con tiempos distintos sin que una cuelgue a las otras es el tema de Programar sin colgarse.
Antes de eso, el próximo artículo muestra cómo mostrar las lecturas en una pantalla: I2C con OLED y LCD. En la ruta de sensores, cada parte de este artículo se convierte en una lección con circuito listo y desafíos como el termómetro, la cinta métrica y la alarma de presencia.
Para practicar en PyPlaca
Rutas
Lecciones
- DHT22: temperatura y humedad
- HC-SR04: midiendo distancia con sonido
- El ECHO tiene 5 V: divisor de tensión
- PIR: ¿pasó alguien?