Nordic nRF54L15-DK Zephyr SAADC usando los drivers de nrfx y el TIMER/PPI en Linux

Refieraze al artículo sobre pasos a seguir para instalar el sistema de desarrollo de Zephyr que se necesita para proceder con el siguiente paso que aquí se demuestra. Este artículo muestra como utilizar el Nordic nRF54L15-DK SAADC usando los drivers nrfx y el TIMER/PPI. Este es un modo avanzado del SAADC para medir un voltaje externo (e.g., a batteria, celda solar, divisors con foto resistores, velocidad de viento, etc) a una frecuencia de muestreo bien alta. Aqui se usa el TIMER para iniciar el muestreo a través del DPPI/PPI, sin ninguna intervención de la unidad central de procesamiento. Para poner en efecto esto, se procede a crear los siguientes archivos dentro del directorio de su preferencia,

Primero se crea el archivo prj.conf,

#
# Copyright (c) 2024 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

CONFIG_LOG=y
# STEP 1 - Enable the ADC API and driver
CONFIG_ADC=y

El archivo de la aplicación llamado main.c es el siguiente,

/*
 * Copyright (c) 2024 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(Output, LOG_LEVEL_DBG);

/* STEP 2 - Include header for nrfx drivers */
#include <nrfx_saadc.h>
#include <nrfx_timer.h>
#include <helpers/nrfx_gppi.h>
#if defined(DPPI_PRESENT)
#include <nrfx_dppi.h>
#else
#include <nrfx_ppi.h>
#endif

/* STEP 3.1 - Define the SAADC sample interval in microseconds */
#define SAADC_SAMPLE_INTERVAL_US 50

/* STEP 4.1 - Define the buffer size for the SAADC */
#define SAADC_BUFFER_SIZE   8000

/* STEP 4.6 - Declare the struct to hold the configuration for the SAADC channel used to sample the battery voltage */
#if NRF_SAADC_HAS_AIN_AS_PIN

#if defined(CONFIG_SOC_NRF54L15)
#define NRF_SAADC_INPUT_AIN4 NRF_PIN_PORT_TO_PIN_NUMBER(11U, 1)
#define SAADC_INPUT_PIN NRF_SAADC_INPUT_AIN4
#else
BUILD_ASSERT(0, "Unsupported device family");
#endif
#else 
#define SAADC_INPUT_PIN NRF_SAADC_INPUT_AIN0
#endif
static nrfx_saadc_channel_t channel = NRFX_SAADC_DEFAULT_CHANNEL_SE(SAADC_INPUT_PIN, 0);


/* STEP 3.2 - Declaring an instance of nrfx_timer for TIMER2. */
#if defined(CONFIG_SOC_NRF54L15)
#define TIMER_INSTANCE_NUMBER 22
#else
#define TIMER_INSTANCE_NUMBER 2
#endif
const nrfx_timer_t timer_instance = NRFX_TIMER_INSTANCE(TIMER_INSTANCE_NUMBER);

/* STEP 4.2 - Declare the buffers for the SAADC */
static int16_t saadc_sample_buffer[2][SAADC_BUFFER_SIZE];

/* STEP 4.3 - Declare variable used to keep track of which buffer was last assigned to the SAADC driver */
static uint32_t saadc_current_buffer = 0;

static void configure_timer(void)
{
    nrfx_err_t err;

    /* STEP 3.3 - Declaring timer config and intialize nrfx_timer instance. */
    nrfx_timer_config_t timer_config = NRFX_TIMER_DEFAULT_CONFIG(1000000);
    err = nrfx_timer_init(&timer_instance, &timer_config, NULL);
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_timer_init error: %08x", err);
        return;
    }

    /* STEP 3.4 - Set compare channel 0 to generate event every SAADC_SAMPLE_INTERVAL_US. */
    uint32_t timer_ticks = nrfx_timer_us_to_ticks(&timer_instance, SAADC_SAMPLE_INTERVAL_US);
    nrfx_timer_extended_compare(&timer_instance, NRF_TIMER_CC_CHANNEL0, timer_ticks, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false);

}

static void saadc_event_handler(nrfx_saadc_evt_t const * p_event)
{
    nrfx_err_t err;
    switch (p_event->type)
    {
        case NRFX_SAADC_EVT_READY:
        
           /* STEP 5.1 - Buffer is ready, timer (and sampling) can be started. */
            nrfx_timer_enable(&timer_instance);
            break;                        
            
        case NRFX_SAADC_EVT_BUF_REQ:
        
            /* STEP 5.2 - Set up the next available buffer. Alternate between buffer 0 and 1 */
            err = nrfx_saadc_buffer_set(saadc_sample_buffer[(saadc_current_buffer++)%2], SAADC_BUFFER_SIZE);
            //err = nrfx_saadc_buffer_set(saadc_sample_buffer[((saadc_current_buffer == 0 )? saadc_current_buffer++ : 0)], SAADC_BUFFER_SIZE);
            if (err != NRFX_SUCCESS) {
                LOG_ERR("nrfx_saadc_buffer_set error: %08x", err);
                return;
            }
            break;

        case NRFX_SAADC_EVT_DONE:

            /* STEP 5.3 - Buffer has been filled. Do something with the data and proceed */
            int64_t average = 0;
            int16_t max = INT16_MIN;
            int16_t min = INT16_MAX;
            int16_t current_value; 
            for(int i=0; i < p_event->data.done.size; i++){
                current_value = ((int16_t *)(p_event->data.done.p_buffer))[i];
                average += current_value;
                if(current_value > max){
                    max = current_value;
                }
                if(current_value < min){
                    min = current_value;
                }
            }
            average = average/p_event->data.done.size;
            LOG_INF("SAADC buffer at 0x%x filled with %d samples", (uint32_t)p_event->data.done.p_buffer, p_event->data.done.size);
            LOG_INF("AVG=%d, MIN=%d, MAX=%d", (int16_t)average, min, max);
            break;
        default:
            LOG_INF("Unhandled SAADC evt %d", p_event->type);
            break;
    }
}

static void configure_saadc(void)
{
    nrfx_err_t err;

    /* STEP 4.4 - Connect ADC interrupt to nrfx interrupt handler */
    IRQ_CONNECT(DT_IRQN(DT_NODELABEL(adc)),
                DT_IRQ(DT_NODELABEL(adc), priority),
                nrfx_isr, nrfx_saadc_irq_handler, 0);

    
    /* STEP 4.5 - Initialize the nrfx_SAADC driver */
    err = nrfx_saadc_init(DT_IRQ(DT_NODELABEL(adc), priority));
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_init error: %08x", err);
        return;
    }

    /* STEP 4.7 - Change gain config in default config and apply channel configuration */
#if defined(CONFIG_SOC_NRF54L15)
    channel.channel_config.gain = NRF_SAADC_GAIN1_4;
#else
    channel.channel_config.gain = NRF_SAADC_GAIN1_6;
#endif
    err = nrfx_saadc_channels_config(&channel, 1);
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_channels_config error: %08x", err);
        return;
    }

    /* STEP 4.8 - Configure channel 0 in advanced mode with event handler (non-blocking mode) */
    nrfx_saadc_adv_config_t saadc_adv_config = NRFX_SAADC_DEFAULT_ADV_CONFIG;
    err = nrfx_saadc_advanced_mode_set(BIT(0),
                                        NRF_SAADC_RESOLUTION_12BIT,
                                        &saadc_adv_config,
                                        saadc_event_handler);
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_advanced_mode_set error: %08x", err);
        return;
    }
                                            
    /* STEP 4.9 - Configure two buffers to make use of double-buffering feature of SAADC */
    err = nrfx_saadc_buffer_set(saadc_sample_buffer[0], SAADC_BUFFER_SIZE);
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_buffer_set error: %08x", err);
        return;
    }
    err = nrfx_saadc_buffer_set(saadc_sample_buffer[1], SAADC_BUFFER_SIZE);
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_buffer_set error: %08x", err);
        return;
    }

    /* STEP 4.10 - Trigger the SAADC. This will not start sampling, but will prepare buffer for sampling triggered through PPI */
    err = nrfx_saadc_mode_trigger();
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_mode_trigger error: %08x", err);
        return;
    }

}

static void configure_ppi(void)
{
    nrfx_err_t err;
    /* STEP 6.1 - Declare variables used to hold the (D)PPI channel number */
    uint8_t m_saadc_sample_ppi_channel;
    uint8_t m_saadc_start_ppi_channel;

    /* STEP 6.2 - Trigger task sample from timer */
    err = nrfx_gppi_channel_alloc(&m_saadc_sample_ppi_channel);
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_gppi_channel_alloc error: %08x", err);
        return;
    }

    err = nrfx_gppi_channel_alloc(&m_saadc_start_ppi_channel);
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_gppi_channel_alloc error: %08x", err);
        return;
    }

    /* STEP 6.3 - Trigger task sample from timer */
    nrfx_gppi_channel_endpoints_setup(m_saadc_sample_ppi_channel, 
                                      nrfx_timer_compare_event_address_get(&timer_instance, NRF_TIMER_CC_CHANNEL0),
                                      nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_SAMPLE));

    /* STEP 6.4 - Trigger task start from end event */
    nrfx_gppi_channel_endpoints_setup(m_saadc_start_ppi_channel, 
                                      nrf_saadc_event_address_get(NRF_SAADC, NRF_SAADC_EVENT_END),
                                      nrf_saadc_task_address_get(NRF_SAADC, NRF_SAADC_TASK_START));

    /* STEP 6.5 - Enable both (D)PPI channels */ 
    nrfx_gppi_channels_enable(BIT(m_saadc_sample_ppi_channel));
    nrfx_gppi_channels_enable(BIT(m_saadc_start_ppi_channel));
}


int main(void)
{
    configure_timer();
    configure_saadc();  
    configure_ppi();
    k_sleep(K_FOREVER);
}

Finalmente se crea el siguiente archivo nrf54l15dk_nrf54l15.overlay overlay mostrado a continuación,

/ {
	zephyr,user {
		io-channels = <&adc 0>;
	};
};

&adc {
	#address-cells = <1>;
	#size-cells = <0>;
	status = "okay";
	channel@0 {
		reg = <0>;
		zephyr,gain = "ADC_GAIN_1_4";
		zephyr,reference = "ADC_REF_INTERNAL";
		zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
		zephyr,input-positive = <NRF_SAADC_AIN4>; /* P1.11 for the nRF54L15 DK */
		zephyr,resolution = <14>;
	};
};

El resultado debe ser como el siguiente árbol de directorios y archivos,

├── CMakeLists.txt
├── nrf54l15dk_nrf54l15.overlay
├── prj.conf
└── src
    └── main.c

Ahora se procede a construir este projecto dentro del ambiente virtual de python,

digikey_coffee_cup (venv) $  west build -p always -b nrf54l15dk/nrf54l15/cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=nrf54l15dk_nrf54l15.overlay

Finalmente se conecta el nRF54L15-DK Nordic kit vía la interfaz USB para programarlo,

digikey_coffee_cup (venv) $  west flash
-- west flash: rebuilding
ninja: no work to do.
-- west flash: using runner nrfutil
-- runners.nrfutil: reset after flashing requested
Using board 001057777221
-- runners.nrfutil: Flashing file: /home/engineer/Digikey/Projects/Workspace/zephyr/zephyrproject/zephyr/adc/build/zephyr/zephyr.hex
-- runners.nrfutil: Connecting to probe
-- runners.nrfutil: Programming image
-- runners.nrfutil: Verifying image
-- runners.nrfutil: Reset
-- runners.nrfutil: Board(s) with serial number(s) 1057777221 flashed successfully.

Para ver el funcionamiento de este projecto, se procede a abrir un terminal de minicom como sigue,

digikey_coffee_cup $ minicom -D /dev/ttyACM1

Welcome to minicom 2.9

OPTIONS: I18n 
Port /dev/ttyACM1, 20:30:04

Press CTRL-A Z for help on special keys
*** Booting Zephyr OS build v4.2.0-5624-gfb7a74ebbd0a ***
[00:00:00.400,672] <inf> Output: SAADC buffer at 0x20000c28 filled with 8000 samples
[00:00:00.400,688] <inf> Output: AVG=-72, MIN=-100, MAX=148
[00:00:00.798,458] <inf> Output: SAADC buffer at 0x20004aa8 filled with 8000 samples
[00:00:00.798,464] <inf> Output: AVG=-72, MIN=-108, MAX=-56
[00:00:01.196,330] <inf> Output: SAADC buffer at 0x20000c28 filled with 8000 samples
[00:00:01.196,336] <inf> Output: AVG=-72, MIN=-100, MAX=-56
[00:00:01.594,142] <inf> Output: SAADC buffer at 0x20004aa8 filled with 8000 samples
[00:00:01.594,157] <inf> Output: AVG=-72, MIN=-100, MAX=-56
[00:00:01.991,977] <inf> Output: SAADC buffer at 0x20000c28 filled with 8000 samples
[00:00:01.991,983] <inf> Output: AVG=-72, MIN=-104, MAX=-52
[00:00:02.389,785] <inf> Output: SAADC buffer at 0x20004aa8 filled with 8000 samples
[00:00:02.389,791] <inf> Output: AVG=-72, MIN=-100, MAX=-56
[00:00:02.787,606] <inf> Output: SAADC buffer at 0x20000c28 filled with 8000 samples
[00:00:02.787,622] <inf> Output: AVG=-72, MIN=-104, MAX=-56
[00:00:03.185,387] <inf> Output: SAADC buffer at 0x20004aa8 filled with 8000 samples
[00:00:03.185,393] <inf> Output: AVG=-72, MIN=-96, MAX=-56
[00:00:03.583,139] <inf> Output: SAADC buffer at 0x20000c28 filled with 8000 samples
[00:00:03.583,145] <inf> Output: AVG=-72, MIN=-100, MAX=-56
[00:00:03.980,978] <inf> Output: SAADC buffer at 0x20004aa8 filled with 8000 samples
[00:00:03.980,993] <inf> Output: AVG=-72, MIN=-104, MAX=-52
[00:00:04.378,823] <inf> Output: SAADC buffer at 0x20000c28 filled with 8000 samples
[00:00:04.378,829] <inf> Output: AVG=-72, MIN=-100, MAX=-60
[00:00:04.776,526] <inf> Output: SAADC buffer at 0x20004aa8 filled with 8000 samples
[00:00:04.776,532] <inf> Output: AVG=-72, MIN=-100, MAX=-56
[00:00:05.174,313] <inf> Output: SAADC buffer at 0x20000c28 filled with 8000 samples
[00:00:05.174,328] <inf> Output: AVG=-72, MIN=-104, MAX=-56


Se observa los valores adquiridos por el SAADC usado en el programa del nRF54L15-DK alterna entre dos localizaciones, cada una correspondiendo a dos “buffers” independientes. El máximo, el promedio y el mínimo se muestran para cada colección de muestras en cada ´buffer´. El nRF54L15-DK disponible en DigiKey

image

es una excelente plataforma para desarrollar applicaciones de artefactos de internet (IoT).

Que tenga un buen día.

Este artículo esta disponible en inglés aquí,

This article is available in english here.

1 Like