Analógico
El ADC de la ESP32: atenuación, curva y por qué no llega a 3,3 V
Cómo mide tensiones la ESP32 con MicroPython: read y read_uv, ADC1 y ADC2, las cuatro atenuaciones, la curva de calibración, el LDR y la media móvil.
Por Prof. Rodrigo Faustino · Actualizado el · 13 min de lectura
Métricas de este artículo
- Palabras
- 2472
- Caracteres
- 13.163
- Tiempo de lectura
- ~13 min
Hasta aquí, todo pin leía 0 o 1. Mucho de lo que hay en el mundo, sin embargo, no es de encendido y apagado: la posición de una perilla de volumen, la claridad de una sala, la temperatura de un líquido, la carga de una batería. Para traer esas magnitudes al programa, la ESP32 tiene un conversor analógico-digital, el ADC: mide la tensión en un pin y devuelve un número proporcional a ella.
El ADC de la ESP32 es útil, rápido y está lleno de trampas. La lectura no es exactamente proporcional a la tensión, el fondo de escala cambia con un ajuste llamado atenuación, la mitad de los pines analógicos deja de funcionar cuando el Wi-Fi se enciende y, con el ajuste por defecto, no llega a los 3,3 V que entrega la placa. Este artículo explica cada una de esas trampas y su porqué, con números que vienen de la calibración del propio chip.
De tensión a número
MicroPython ofrece el ADC como la clase ADC, del módulo machine. Recibe un Pin y tiene tres maneras de leer:
from machine import Pin, ADC
import time
adc = ADC(Pin(34)) # GPIO34, un pin del ADC1
for i in range(20):
print(adc.read(), adc.read_u16(), adc.read_uv())
time.sleep_ms(250)read()devuelve un número de 0 a 4095. Son 12 bits: el rango de medida se divide en 4096 escalones;read_u16()devuelve la misma lectura estirada a 0 a 65535. Existe para que un programa funcione igual en placas con ADC de resoluciones distintas;read_uv()devuelve la tensión en microvoltios (1 V = 1 000 000 µV), ya corregida por la calibración del chip, con resolución de 1 mV.
Abre el ejemplo en el simulador, ejecútalo y arrastra el eje del potenciómetro mientras el programa corre. La pestaña Pines muestra el GPIO34 como ADC, con la lectura y la tensión.
Si solo quieres saber "cuántos voltios hay en el pin", read_uv() es el camino más directo: divide por un millón y listo. read() y read_u16() son útiles cuando lo que interesa es una posición relativa ("el eje está al 40 % del giro"), pero, como veremos, ese número no es una regla perfecta.

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
ADC1, ADC2 y el Wi-Fi
La ESP32 tiene dos conversores, y no todo pin está conectado a uno de ellos:
- ADC1: GPIO 32 a 39. En la DevKit V1 son el D32, el D33, el D34, el D35, el VP (GPIO36) y el VN (GPIO39);
- ADC2: GPIO 0, 2, 4, 12 a 15 y 25 a 27.
La diferencia importa por un motivo práctico: el ADC2 lo usa el Wi-Fi. Después de encender el Wi-Fi de la ESP32, el sistema que controla la radio se queda con el ADC2, y una lectura en un pin del ADC2 falla. En MicroPython v1.29.0, el error es OSError: [Errno 116] ETIMEDOUT: la lectura esperó a que el conversor quedara libre y se rindió. El simulador reproduce el mismo error y añade el aviso adc2-wifi, explicando la causa.
La regla práctica es simple: para proyectos con Wi-Fi, usa el ADC1. Los GPIO 34 a 39 son elecciones excelentes, porque son solo de entrada y no tienen otra función que estorbe. Los ejemplos de este artículo usan el 34 y el 35.
La referencia interna y la atenuación
Por dentro, el ADC de la ESP32 compara la tensión del pin con una referencia interna de cerca de 1,1 V. Solo, mide únicamente hasta cerca de eso: cualquier cosa por encima se convierte en el número máximo, 4095. Para medir tensiones mayores, la entrada pasa antes por un atenuador, que divide la tensión antes de la comparación. Cuánto dividir lo eliges tú, con el método atten():
| Atenuación | Fondo de escala (calibración) | Rango bueno (documentación) |
|---|---|---|
ADC.ATTN_0DB | ≈ 1,04 V | 0,10 a 0,95 V |
ADC.ATTN_2_5DB | ≈ 1,36 V | 0,10 a 1,25 V |
ADC.ATTN_6DB | ≈ 1,88 V | 0,15 a 1,75 V |
ADC.ATTN_11DB | ≈ 3,13 V | 0,15 a 2,45 V |
La columna del medio es la tensión a la que la lectura llega al 4095 para una ESP32 típica, según la calibración de ESP-IDF; la última columna es el rango en el que la documentación de MicroPython considera confiable la medida. 11 dB es el valor por defecto de MicroPython: crear el ADC o llamar a init() sin indicar la atenuación vuelve a 11 dB y 12 bits.
Por qué no llega a 3,3 V
Mira la última fila de la tabla: incluso en la atenuación máxima, el fondo de escala queda en torno a 3,13 V. Un pin conectado directamente al 3V3 lee 4095, pero un pin a 3,15 V también lee 4095, y uno a 3,2 V también. El tramo entre ~3,13 V y 3,3 V simplemente no existe para el ADC. Por eso, al girar un potenciómetro conectado entre el GND y el 3V3, la lectura llega al máximo un poco antes del final del giro.
Y hay más: la documentación considera confiable la medida en 11 dB solo hasta 2,45 V. Entre 2,45 V y 3,13 V la curva del ADC se tuerce: la misma variación de tensión produce cada vez menos escalones de lectura. La calibración del chip compensa parte de eso en read_uv(), pero la precisión cae. Si tu sensor llega hasta 3,3 V y necesitas medir bien la parte de arriba, la solución es un divisor de tensión que traiga la señal al rango bueno.
El pie de la curva: tensiones muy bajas
En el otro extremo también hay un límite. Por debajo de algo entre 75 mV y 142 mV, según la atenuación, la lectura se queda en 0. Con el potenciómetro todo hacia el lado del GND, read() da 0, mientras que read_uv() muestra cerca de 0,14 V, el menor valor que la calibración consigue representar en esa atenuación. La documentación recomienda no confiar en medidas por debajo de 0,10 V a 0,15 V, y ese es el motivo.
Por qué no usar siempre 11 dB
Si 11 dB mide el rango más ancho, ¿por qué existen las otras? Porque los 4096 escalones se reparten por todo el rango. Una señal que solo llega a 0,9 V, leída en 11 dB, usa menos de la cuarta parte de los escalones. En 0 dB, la misma señal usa casi todos: la medida queda mucho más fina. La regla es elegir la menor atenuación en la que tu señal todavía quepa.
from machine import Pin, ADC
adc = ADC(Pin(34))
for nome, at in (('0 dB', ADC.ATTN_0DB), ('2,5 dB', ADC.ATTN_2_5DB),
('6 dB', ADC.ATTN_6DB), ('11 dB', ADC.ATTN_11DB)):
adc.atten(at)
print(nome, adc.read(), adc.read_uv())El circuito de este ejemplo tiene una resistencia de 27 kΩ en serie entre el 3V3 y el potenciómetro, que "roba" la mayor parte de la tensión: el cursor solo va de 0 V hasta cerca de 0,9 V (el divisor ahora es 3,3 × 10 kΩ / 37 kΩ). Con el eje al final del giro, la lectura en 0 dB llega cerca de 3500, mientras que en 11 dB queda cerca de 900. La tensión es la misma; lo que cambia, casi cuatro veces, es la resolución. read_uv(), en cambio, da prácticamente el mismo valor en todas: ya devuelve la tensión, no los escalones.
La lectura no es una regla: read() × read_uv()
Con el potenciómetro exactamente en la mitad del giro, la tensión en el cursor es 1,65 V, la mitad de 3,3 V. Si el ADC fuera una regla perfecta de 0 a 3,3 V, read() daría 2047, la mitad de 4095. En la ESP32 en 11 dB, da 1872.
Dos cosas explican la diferencia. Primero, el fondo de escala no es 3,3 V, sino ~3,13 V. Segundo, la relación entre la tensión y los escalones no es una recta: tiene un desplazamiento al comienzo y se tuerce al final. La ESP32 graba en fábrica datos de calibración, y ESP-IDF tiene un modelo de esa curva; MicroPython usa ese modelo para convertir los escalones en tensión en read_uv(). El simulador hace el camino contrario (de la tensión del circuito a los escalones) con el mismo modelo de calibración de ESP-IDF, invertido. Por eso los números que ves en la pantalla son los que mostraría una ESP32 típica.
La consecuencia para tu código: para convertir a voltios, usa read_uv(). Hacer read() * 3.3 / 4095 es un error común y da valores con algunos por ciento de error, y más todavía en los extremos del rango.
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
Potenciómetro: el divisor de tensión que gira
El ADC mide tensión y casi no consume corriente del pin. Entonces, para medir "cuánto giró el eje" o "cuánta luz hay en la sala", el truco es transformar esa magnitud en una tensión. El circuito que hace eso es el divisor de tensión: dos resistencias en serie entre la alimentación y el GND, con el pin conectado al punto medio. La tensión en el medio es:
V_medio = V_alimentación × R_abajo / (R_arriba + R_abajo)
El potenciómetro es un divisor ajustable: una resistencia de 10 kΩ con un cursor que se desliza sobre ella. Los extremos van al GND y al 3V3; el cursor toma una fracción de la tensión. Como el potenciómetro del laboratorio es lineal, la tensión crece en línea recta con el giro: en el medio, 1,65 V; a un cuarto, 0,825 V.
Una regla que protege la placa: conecta los extremos del potenciómetro siempre al 3V3, nunca al VIN. El VIN tiene 5 V, y un pin de la ESP32 aguanta como máximo 3,6 V.
Mapear la lectura a lo que importa
Casi nunca quiere el programa el número de 0 a 4095 en sí: quiere un brillo de LED (0 a 65535), un ángulo de servo (0 a 180), un porcentaje. Pasar de un rango a otro es mapear:
from machine import Pin, ADC, PWM
import time
pot = ADC(Pin(34))
led = PWM(Pin(18), freq=1000, duty_u16=0)
def mapear(x, de_min, de_max, para_min, para_max):
return (x - de_min) * (para_max - para_min) // (de_max - de_min) + para_min
for i in range(40):
leitura = pot.read()
led.duty_u16(mapear(leitura, 0, 4095, 0, 65535))
print(leitura, '->', mapear(leitura, 0, 4095, 0, 100), '%')
time.sleep_ms(250)Con esto el potenciómetro se vuelve un dimmer: girar el eje cambia el brillo del LED en el GPIO18. Fíjate en el orden de la cuenta: multiplicar antes de dividir evita perderlo todo en el camino (1872 // 4095 daría 0). La misma función sirve para rangos invertidos: mapear(x, 0, 4095, 100, 0) hace que el 0 se convierta en 100.
Arduino tiene una función map() igual a esta. En MicroPython no existe, y el map de Python es otra cosa (aplica una función a cada elemento de una secuencia). Por eso conviene tener la tuya.
LDR: medir la luz
El LDR (light dependent resistor, el GL5528 del catálogo) es una resistencia que cambia con la luz. Según la hoja del fabricante, con 10 lux (la luz de una habitación medio oscura) queda entre 10 y 20 kΩ; en la oscuridad, sube a 1 MΩ o más. Entre 10 y 100 lux, la resistencia sigue aproximadamente R ≈ R10 × (lux/10)^(−0,7): duplicar la luz no divide la resistencia por dos.
Como el ADC no mide resistencia, el LDR entra en un divisor con una resistencia fija de 10 kΩ. Con el LDR arriba (en el 3V3) y la resistencia abajo (en el GND), la tensión en el pin es 3,3 × 10 kΩ / (R_LDR + 10 kΩ):
- claro: R_LDR pequeña, tensión cerca de 3,3 V, lectura alta;
- oscuro: R_LDR enorme, tensión cerca de 0 V, lectura baja.
Intercambiar el LDR y la resistencia de lugar invierte todo. Los dos montajes funcionan; lo importante es saber cuál montaste.
¿Por qué 10 kΩ? Un divisor es más sensible cuando la resistencia fija tiene el mismo orden de magnitud que el LDR en el rango de luz que más interesa. Con 10 kΩ, la transición entre "luz de habitación" y "casi oscuro" cae en el medio de la escala del ADC.
from machine import Pin, ADC
import time
luz = ADC(Pin(35))
LIMIAR = 1500 # por debajo de esto, consideramos oscuro
for i in range(30):
leitura = luz.read()
print(leitura, 'oscuro' if leitura < LIMIAR else 'claro')
time.sleep_ms(300)Ejecútalo y cambia la luz del ambiente del LDR en el simulador (en el Circuito, en la 2D o en la 3D). Anota la lectura con 1, 10, 100 y 1000 lux y fíjate en que no crece en línea recta. Para encender una luz cuando oscurece, eso no estorba: basta con elegir un umbral, como en el ejemplo.
Ruido y media móvil
En la placa de verdad, leer varias veces el mismo potenciómetro parado no da siempre el mismo número: la lectura "salta" algunos escalones. Eso es ruido, que viene de la alimentación, de los cables y del propio conversor. Un programa que enciende un LED cuando la lectura pasa de un umbral puede quedarse parpadeando justo encima de él.
El simulador todavía no añade ese ruido: el tamaño del ruido real se va a medir en una placa del laboratorio antes de entrar en la simulación. Aun así, vale la pena aprender el remedio desde ya, y los desafíos de la ruta sacuden el eje a propósito para comprobar si el programa se defiende.
El remedio más simple es la media móvil: en vez de usar la última lectura, usar la media de las N últimas. Una lectura rara aislada cambia poco la media.
from machine import Pin, ADC
import time
pot = ADC(Pin(34))
ultimas = []
for i in range(30):
ultimas.append(pot.read())
if len(ultimas) > 5:
ultimas.pop(0) # sale la más antigua
media = sum(ultimas) // len(ultimas)
print(ultimas[-1], media)
time.sleep_ms(100)El tamaño de la ventana es un compromiso. Una ventana grande da una media más calmada, pero tarda en seguir un cambio de verdad; una ventana pequeña responde rápido, pero filtra menos. Gira el eje de una vez de un extremo al otro y mira cómo la media llega al nuevo valor en escalones, a lo largo de cinco lecturas.
Otra defensa contra la lectura que oscila encima del umbral es la histéresis: usar dos umbrales, uno para encender y otro, un poco más abajo, para apagar. La luz se enciende por debajo de 1400 y solo se apaga por encima de 1600, por ejemplo. Entre los dos, el estado no cambia.
Otros detalles del ADC en MicroPython
Algunos comportamientos que el simulador reproduce de la v1.29.0 y que evitan sorpresas:
ADC(Pin(34))devuelve siempre el mismo objeto para el mismo pin;- configurar el canal deja el pin en modo analógico: deja de funcionar como entrada digital, salida o pull;
width()elige la resolución deread(), de 9 a 12 bits (12 es el valor por defecto);deinit()apaga el bloque del conversor; leer después daOSError: [Errno 1] EPERM;- un pin de ADC sin nada conectado lee 0 y genera el aviso adc-solto: igual que en lo digital, un pin analógico suelto no tiene tensión definida.
Resumen para llevar al banco de trabajo
- Usa el ADC1 (GPIO 32 a 39) siempre que el proyecto tenga Wi-Fi;
- Para voltios, usa
read_uv();read()no es proporcional a la tensión; - En 11 dB (el valor por defecto), la lectura se satura cerca de 3,13 V y es más confiable hasta 2,45 V;
- Elige la menor atenuación en la que la señal quepa, para ganar resolución;
- Las magnitudes que no son tensión (posición, luz) entran por un divisor de tensión;
- Nada por encima de 3,6 V en un pin: alimenta divisores y potenciómetros por el 3V3;
- Contra el ruido, media móvil; contra la oscilación encima del umbral, histéresis.
El próximo artículo, sobre sensores DHT22, HC-SR04 y PIR, usa el ADC para comprobar la cuenta de un divisor de tensión, y muestra por qué el ADC no consigue medir una señal de 3,33 V que el pin, aun así, aguanta. La ruta analógica rehace este artículo en lecciones con circuito listo, del primer read() al desafío de montar el LDR.
Para practicar en PyPlaca
Rutas
Lecciones
- El ADC: de tensión a número
- Potenciómetro: el divisor de tensión
- Atenuación: eligiendo el rango del ADC
- Mapear un rango: del ADC a lo que importa
- LDR: midiendo la luz
- Media móvil: calmando la lectura