NRF54L15 - adc_channel_setup_dt - Invalid argument (-22)

Based on the DevAcademy here is my following setup using a custom NRF54L15 chip with the NRF54L15-DK devicetree with following overlay:

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


    chosen {
        zephyr,console = &uart21;
        zephyr,shell-uart = &uart21;
        zephyr,uart-mcumgr = &uart21;
        zephyr,bt-mon-uart = &uart21;
        zephyr,bt-c2h-uart = &uart21;
    };

    aliases {
        /delete-property/ led3;
        /delete-property/ sw3;
        /delete-property/ sw2;
        /delete-property/ sw1;
        /delete-property/ sw0;
    };
};


/delete-node/ &button0;
/delete-node/ &button1;
/delete-node/ &button2;
/delete-node/ &button3;
/delete-node/ &{/buttons/};
/delete-node/ &led3;

&led0 {
    gpios = <&gpio1 7 0>; // B
    label = "BLUE LED 0";
};

&led1 {
    gpios = <&gpio1 6 0>; // G
    label = "GREEN LED 1";
};

&led2 {
    gpios = <&gpio1 8 0>; // R
    label = "RED LED 2";
};

&adc {
    status = "okay";
    #address-cells = <1>;
    #size-cells = <0>;
    channel@1 {
        reg = <1>;
        zephyr,gain = "ADC_GAIN_1_6";
        zephyr,reference = "ADC_REF_INTERNAL";
        zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40)>; // Increased to 40us for your 2M Ohm divider
        zephyr,input-positive = <NRF_SAADC_AIN1>;
        zephyr,resolution = <12>;
    };
};

&spi20 {
    status = "okay";
    pinctrl-0 = <&spi20_default>;
    pinctrl-1 = <&spi20_sleep>;
    pinctrl-names = "default", "sleep";
    cs-gpios = <&gpio1 13 0>;
};

&uart20 {
    status = "disabled";
};

&uart21 {
    status = "okay";
    current-speed = <115200>;
    pinctrl-0 = <&uart21_default>;
    pinctrl-1 = <&uart21_sleep>;
    pinctrl-names = "default", "sleep";
};

&pinctrl {
    uart21_default: uart21_default {
        group1 {
            psels = <NRF_PSEL(UART_TX, 1, 4)>;
        };
        group2 {
            psels = <NRF_PSEL(UART_RX, 1, 3)>;
            bias-pull-up;
        };
    };

    uart21_sleep: uart21_sleep {
        group1 {
            psels = 
            <NRF_PSEL(UART_TX, 1, 4)>, 
            <NRF_PSEL(UART_RX, 1, 3)>;
            low-power-enable;
        };
    };

    spi20_default: spi20_default {
        group1 {
            psels = <NRF_PSEL(SPIM_SCK, 1, 11)>,
                    <NRF_PSEL(SPIM_MOSI, 1, 10)>,
                    <NRF_PSEL(SPIM_MISO, 1, 0)>;
        };
    };

    spi20_sleep: spi20_sleep {
        group1 {
            psels = <NRF_PSEL(SPIM_SCK, 1, 11)>;
        };
    };
};

prj.conf:

CONFIG_GPIO=y
CONFIG_ADC=y


code:
static const struct adc_dt_spec adc_channel = ADC_DT_SPEC_GET(DT_PATH(zephyr_user));

/* Divider calculation remains in C as it's math-based:
 * (2M + 510k) / 510k = 4.9215...
 */
#define BATTERY_DIVIDER_FACTOR 4.9215f

int16_t sample_buffer;

int read_battery_voltage(void)
{
    int err;

    /* 1. Check if the device is ready */
    if (!adc_is_ready_dt(&adc_channel))
    {
        printk("ADC device not ready\n");
        return -1;
    }

    /* 2. Setup the channel once (from DT) */
    err = adc_channel_setup_dt(&adc_channel);
    if (err < 0)
    {
        printk("Could not setup channel (%d)\n", err);
        return -1;
    }

    /* 3. Prepare the sequence */
    struct adc_sequence sequence = {
        .buffer = &sample_buffer,
        .buffer_size = sizeof(sample_buffer),
        // All other fields (resolution, channels) are pulled from DT by macros below
    };
    adc_sequence_init_dt(&adc_channel, &sequence);

    /* 4. Perform the read */
    err = adc_read_dt(&adc_channel, &sequence);
    if (err < 0)
    {
        printk("ADC read failed (%d)\n", err);
        return -1;
    }

    int32_t mv_value = sample_buffer;

    /* 5. Convert raw value to mV using DT-specific helper */
    err = adc_raw_to_millivolts_dt(&adc_channel, &mv_value);
    if (err < 0)
    {
        return -1;
    }

    /* 6. Scale back up to actual battery voltage */
    return (int)(mv_value * BATTERY_DIVIDER_FACTOR);
}


ADC pin is 1.05. I also tried to use  io-channels = <&adc 0>; with channel@1 and also with ADC_ACQ_TIME_DEFAULT but no luck either.
Always get Could not setup channel (-22)

Appreciate any help.
Parents
  • Hi,

    Thanks for the detailed setup and code. The -22 (EINVAL) is most likely caused by an unsupported ADC gain setting on nRF54L15. Could you please try changing the channel gain in &adc to ADC_GAIN_1_4 in your devicetree:

    zephyr,gain = "ADC_GAIN_1_4";

    This gain is recommended for nRF54L15 and is used in nRF Connect SDK Intermediate Course. Let us know if this resolves the issue.

    Best Regards,
    Syed Maysum

  • Another question. I have a voltage divider with 2MΩ and 510kΩ, which results on a 4.2V battery at 0.8V. Now I discovered that AIN1 (P1.05) are always at VIN 3.3V. It does not matter if the pin is floating or if its connected to my voltage divider. How can that be? Is it misconfigured?

    &adc {
        status = "okay";
        #address-cells = <1>;
        #size-cells = <0>;
        channel@1 {
            reg = <1>;
            zephyr,gain = "ADC_GAIN_1_4";
            zephyr,reference = "ADC_REF_INTERNAL";
            zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40)>; // Increased to 40us for your 2M Ohm divider
            zephyr,input-positive = <NRF_SAADC_AIN1>;
            zephyr,resolution = <12>;
        };
    };



    EDIT: 
    Adding the following config command resolved the issue:
    nrf_gpio_cfg_default(NRF_GPIO_PIN_MAP(1, 5));

    Is this the default procedure when dealing with high-impedance analog inputs?
Reply
  • Another question. I have a voltage divider with 2MΩ and 510kΩ, which results on a 4.2V battery at 0.8V. Now I discovered that AIN1 (P1.05) are always at VIN 3.3V. It does not matter if the pin is floating or if its connected to my voltage divider. How can that be? Is it misconfigured?

    &adc {
        status = "okay";
        #address-cells = <1>;
        #size-cells = <0>;
        channel@1 {
            reg = <1>;
            zephyr,gain = "ADC_GAIN_1_4";
            zephyr,reference = "ADC_REF_INTERNAL";
            zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40)>; // Increased to 40us for your 2M Ohm divider
            zephyr,input-positive = <NRF_SAADC_AIN1>;
            zephyr,resolution = <12>;
        };
    };



    EDIT: 
    Adding the following config command resolved the issue:
    nrf_gpio_cfg_default(NRF_GPIO_PIN_MAP(1, 5));

    Is this the default procedure when dealing with high-impedance analog inputs?
Children
  • Hi,

    Good to hear that. Actually this is possible with a high-impedance divider. GPIO pins default to a digital configuration, and with a 2 MΩ / 510 kΩ source the pin can be internally biased toward VIN (3.3 V), so the ADC ends up measuring that instead of the divider voltage.

    Resetting the pin to a default state with nrf_gpio_cfg_default() removes the internal GPIO influence and allows the SAADC to see the correct voltage. So yes this is a reasonable practice for such inputs.

    Best Regards,
    Syed Maysum

Related