Un ADC (Analog-to-Digital Converter) es el periférico que convierte una señal analógica en un número digital.
La temperatura, la luz o la presión sonora varían de forma continua. Para procesarlas, el ADC toma muestras y las representa mediante valores discretos.
Conviene avisarlo desde el principio: el ADC en Zephyr requiere más configuración que en otros entornos más sencillos.
En Arduino haces analogRead(A0) y listo. En Zephyr, el driver de ADC está diseñado para ser ultra-flexible: soporta lecturas por DMA, muestreo diferencial, ganancias programables, buffers circulares…
Esa flexibilidad hace que leer un valor requiera algo de boilerplate o código de configuración. Vamos a desgranarlo paso a paso.
Configurar el hardware en el overlay
Lo primero es definir en el Devicetree que queremos usar un canal analógico. A diferencia de los GPIO, aquí usamos la propiedad io-channels.
Supongamos que queremos leer un potenciómetro en el canal 0 del ADC.
/* app.overlay */
/ {
zephyr,user {
/* Definimos una "propiedad" virtual para acceder al ADC desde C */
io-channels = <&adc0 0>; // Canal 0 del ADC0
};
};
/* ¡Importante! Activar el ADC */
&adc0 {
status = "okay";
};La estructura adc_sequence
En Zephyr no se lee “un pin”. Se lanza una secuencia de lectura.
Para leer, necesitamos configurar una estructura adc_sequence que le dice al hardware:
- Qué canales leer (máscara de bits).
- Dónde guardar los datos (buffer).
- Qué resolución usar (10 bits, 12 bits…).
- Oversampling (si queremos promediar por hardware).
Ejemplo: leer un potenciómetro
#include <zephyr/kernel.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/devicetree.h>
/* Datos de configuración del canal */
static const struct adc_dt_spec adc_channel = ADC_DT_SPEC_GET(DT_PATH(zephyr_user));
/* Buffer para guardar el resultado (int16_t porque puede ser diferencial) */
int16_t sample_buffer[1];
/* Estructura de secuencia */
struct adc_sequence sequence = {
.buffer = sample_buffer,
.buffer_size = sizeof(sample_buffer),
// Opcional: resolución, etc. Se suele coger del DT
};
int main(void)
{
int err;
if (!adc_is_ready_dt(&adc_channel)) {
printk("ADC no está listo\n");
return 0;
}
/* 1. Configurar el canal (Ganancia, Referencia, Tiempo de adquisición) */
/* Zephyr configura esto automáticamente si usamos ADC_DT_SPEC_GET
y lo definimos bien en el overlay, o podemos hacerlo manual: */
err = adc_channel_setup_dt(&adc_channel);
if (err < 0) {
printk("Error configurando canal (%d)\n", err);
return 0;
}
/* 2. Preparar la secuencia */
/* Le decimos qué canal específico leer usando la info del Devicetree */
err = adc_sequence_init_dt(&adc_channel, &sequence);
if (err < 0) {
printk("Error iniciando secuencia\n");
return 0;
}
while (1) {
/* 3. Leer (Bloqueante) */
err = adc_read(adc_channel.dev, &sequence);
if (err < 0) {
printk("Error leyendo ADC (%d)\n", err);
} else {
/* 4. Convertir a milivoltios (Opcional pero útil) */
int32_t val_mv = sample_buffer[0];
/* Esta función usa la config de referencia y ganancia
para darnos milivoltios reales */
if (adc_raw_to_millivolts_dt(&adc_channel, &val_mv) == 0) {
printk("Lectura raw: %d, voltaje: %d mV\n", sample_buffer[0], val_mv);
} else {
printk("Lectura raw: %d; referencia no convertible a mV\n",
sample_buffer[0]);
}
}
k_msleep(1000);
}
return 0;
}Ojo con el buffer: Aunque solo leas un valor, adc_read espera escribir en un array. Si tu buffer es demasiado pequeño, puedes provocar corrupción de memoria.