USAGE FAULT - FATAL ERROR on GPIO callback when calling adc_read() (ncs 2.4.1)

I have a very simple program trying to handle asynchronous pin events via a callback function while also reading analog input (on different pins).  If the callback is installed, and I get a pin event while in the middle of an adc_read() call, get a USAGE FAULT:

Reading ADCs
- channel 0: 1586 mV
- channel 1: 1679 mV
- channel 2: 97 mV
- channel 3: 210 mV
Reading ADCs
- channel 0: 1590 mV
- channel 1: 1677 mV
- channel 2: 94 mV
- channel 3: 206 mV
[00:02:25.057,922] <err> os: ***** USAGE FAULT *****
[00:02:25.057,922] <err> os: Illegal use of the EPSR
[00:02:25.057,952] <err> os: r0/a1: 0x00013750 r1/a2: 0x20001f74 r2/a3: 0x00001000
[00:02:25.057,983] <err> os: r3/a4: 0x00000008 r12/ip: 0xaaaaaaaa r14/lr: 0x0000fdd7
[00:02:25.057,983] <err> os: xpsr: 0x20000016
[00:02:25.057,983] <err> os: Faulting instruction address (r15/pc): 0x00000008
[00:02:25.058,044] <err> os: >>> ZEPHYR FATAL ERROR 35: Unknown error on CPU 0
[00:02:25.058,044] <err> os: Fault during interrupt handling

[00:02:25.058,074] <err> os: Current thread: 0x200006d0 (idle)
�*** Booting Zephyr OS build v3.3.99-ncs1-1 *** system
*** Booting Zephyr OS build v3.3.99-ncs1-1 ***

Replicating source code below. If I comment out the adc_read() call, the GPIO callback works as expected.

#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <stdint.h>
#include <math.h>

#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>

const struct gpio_dt_spec button_dt = GPIO_DT_SPEC_GET(DT_ALIAS(sw1), gpios);

#define DT_SPEC_AND_COMMA(node_id, prop, idx) \
    ADC_DT_SPEC_GET_BY_IDX(node_id, idx),

/* Data of ADC io-channels specified in devicetree. */
static const struct adc_dt_spec adc_channels[] = { DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, DT_SPEC_AND_COMMA) };

static void _gpio_setup(void);
static void _adc_setup(void);
static void _button_pressed_cb(const struct device *port, struct gpio_callback *cb, uint32_t pins);

K_SEM_DEFINE(s_inputs_updated_sema, 0, 1);

static bool button_pressed = false;

//-----------------------------------------------------------------------------
static void _gpio_setup(void)
{
    struct gpio_callback gpio_cb_data;
    if (!gpio_is_ready_dt(&button_dt))
    {
        printk("Button dev not ready\n");
        return;
    }

    if (gpio_pin_configure_dt(&button_dt, GPIO_INPUT))
    {
        printk("Configure Button failed\n");
        return;
    }

    if (gpio_pin_interrupt_configure_dt(&button_dt, GPIO_INT_EDGE_BOTH))
    {
        printk("Error: failed to configure interrupt\n");
    }

    gpio_init_callback(&gpio_cb_data, _button_pressed_cb, BIT(button_dt.pin));
    gpio_add_callback(button_dt.port, &gpio_cb_data);
}

//-----------------------------------------------------------------------------
static void _button_pressed_cb(const struct device *port, struct gpio_callback *cb, uint32_t pins)
{
    button_pressed = gpio_pin_get_dt(&button_dt);
    k_sem_give(&s_inputs_updated_sema);
}

//-----------------------------------------------------------------------------
static void _adc_setup(void)
{
    int err;
	for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) {
		if (!device_is_ready(adc_channels[i].dev)) {
			printk("ADC controller device %s not ready\n", adc_channels[i].dev->name);
			return;
		}

		err = adc_channel_setup_dt(&adc_channels[i]);
		if (err < 0) {
			printk("Could not setup channel #%d (%d)\n", i, err);
			return;
		}
	}
}

//-----------------------------------------------------------------------------
static void _adc_read(void)
{
    printk("Reading ADCs\n");
    int err;
    uint16_t buf;
    struct adc_sequence sequence = {
        .buffer = &buf,
        /* buffer size in bytes, not number of samples */
        .buffer_size = sizeof(buf),
    };
    
    for (size_t i = 0U; i < ARRAY_SIZE(adc_channels); i++) {
        int32_t val_mv;

        adc_sequence_init_dt(&adc_channels[i], &sequence);

        if (adc_read(adc_channels[i].dev, &sequence) == 0)
        {
            val_mv = (int32_t)((int16_t)buf);
            err = adc_raw_to_millivolts_dt(&adc_channels[i], &val_mv);
            printk("- channel %d: %i mV\n", adc_channels[i].channel_id, val_mv);
        }
    }
}

//-----------------------------------------------------------------------------
int main(void)
{
    printk("main()\n");

    _gpio_setup();
    _adc_setup();

    while (1)
    {
        if (!k_sem_take(&s_inputs_updated_sema, K_MSEC(250)))
            printk("Handle button presses here\n");

        _adc_read();
    }
}

Associated overlay:

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

&adc {
    #address-cells = <1>;
    #size-cells = <0>;

    channel@0 {
        reg = <0>;
        zephyr,gain = "ADC_GAIN_1_6";
        zephyr,reference = "ADC_REF_INTERNAL";
        zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
        zephyr,input-positive = <NRF_SAADC_AIN1>;
        zephyr,resolution = <12>;
    };

    channel@1 {
        reg = <1>;
        zephyr,gain = "ADC_GAIN_1_6";
        zephyr,reference = "ADC_REF_INTERNAL";
        zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
        zephyr,input-positive = <NRF_SAADC_AIN2>;
        zephyr,resolution = <12>;
    };

    channel@2 {
        reg = <2>;
        zephyr,gain = "ADC_GAIN_1_6";
        zephyr,reference = "ADC_REF_INTERNAL";
        zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
        zephyr,input-positive = <NRF_SAADC_AIN3>;
        zephyr,resolution = <12>;
    };

    channel@3 {
        reg = <3>;
        zephyr,gain = "ADC_GAIN_1_6";
        zephyr,reference = "ADC_REF_INTERNAL";
        zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
        zephyr,input-positive = <NRF_SAADC_AIN4>;
        zephyr,resolution = <12>;
    };
};

And prj.conf:

CONFIG_SERIAL=y

CONFIG_LOG=y
CONFIG_LOG_PRINTK=y             # Redirect PRINTK to the logging subsystem
 
CONFIG_PRINTK=y

CONFIG_DISABLE_FLASH_PATCH=y            # Disable Cortex-M4 Flash Patch capabilities
CONFIG_BOOTLOADER_MCUBOOT=y

CONFIG_GPIO=y
CONFIG_ADC=y

# This option instructs the kernel to initialize stack areas with a known value (0xaa) before they are first used, so that the high
# water mark can be easily determined.
CONFIG_INIT_STACKS=y
CONFIG_THREAD_NAME=y            # This option allows to set a name for a thread.
CONFIG_THREAD_MONITOR=y         # This option instructs the kernel to maintain a list of all threads
CONFIG_THREAD_STACK_INFO=y

Related