zephyr-adc-lectura-analogica

ADC en Zephyr: leer señales analógicas

  • 3 min

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";
};
Copied!

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:

  1. Qué canales leer (máscara de bits).
  2. Dónde guardar los datos (buffer).
  3. Qué resolución usar (10 bits, 12 bits…).
  4. 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;
}
Copied!

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.