Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs

nRF SAADC: nrf_saadc_channel_limits_set() on channel 0 corrupts readings on all channels when low limit > ~-100

Issue description

I am using SAADC in single-ended mode with 3 channels (0/1/2). Measurements are performed by enabling SAADC, running offset calibration, configuring the result buffer for 3 samples, starting, sampling once, waiting for END, then stopping.

Everything works as expected until I set channel limits on channel 0 using nrf_saadc_channel_limits_set(). If I set the low limit higher than approximately -100 (e.g., -10), the samples returned in the result buffer for all three channels become “meaningless” (very small numbers). If I remove the limits call, or set low limit to something like -200, the readings are correct again.

This behavior occurs only when applying limits to channel 0. Applying “similar” limits to channels 1 or 2 does not break readings.

Reproduction steps

Code below reproduces the issue:

typedef enum
{
    CURRENT_SAADC_CH = 0,
    TEMPERATURE_SAADC_CH,
    VBATT_SAADC_CH,
    SAADC_CHANNELS_ENABLED, // element count
} td_saadc_channels_t;

static nrf_saadc_value_t _sample[SAADC_CHANNELS_ENABLED];

static bool module_init = false;

void td_saadc_init(void)
{
    nrf_saadc_resolution_set(NRF_SAADC_RESOLUTION_12BIT);
    nrf_saadc_oversample_set(NRF_SAADC_OVERSAMPLE_DISABLED);
    nrf_saadc_int_disable(NRF_SAADC_INT_ALL);
    nrf_saadc_event_clear(NRF_SAADC_EVENT_END);
    nrf_saadc_event_clear(NRF_SAADC_EVENT_STARTED);
    nrf_saadc_event_clear(NRF_SAADC_EVENT_STOPPED);

    // current
    {
        nrf_saadc_channel_config_t ch0_cfg = {
            .resistor_p = NRF_SAADC_RESISTOR_DISABLED,
            .resistor_n = NRF_SAADC_RESISTOR_DISABLED,
            .gain = NRF_SAADC_GAIN1_6,
            .reference = NRF_SAADC_REFERENCE_INTERNAL,
            .acq_time = NRF_SAADC_ACQTIME_5US,
            .mode = NRF_SAADC_MODE_SINGLE_ENDED,
            .burst = NRF_SAADC_BURST_DISABLED,
            .pin_p = SAADC_INPUT_CURR,
            .pin_n = NRF_SAADC_INPUT_DISABLED};
        nrf_saadc_channel_init(CURRENT_SAADC_CH, &ch0_cfg);
    }

    // temperature
    {
        nrf_saadc_channel_config_t ch1_cfg = {
            .resistor_p = NRF_SAADC_RESISTOR_DISABLED,
            .resistor_n = NRF_SAADC_RESISTOR_DISABLED,
            .gain = NRF_SAADC_GAIN1_3,
            .reference = NRF_SAADC_REFERENCE_INTERNAL,
            .acq_time = NRF_SAADC_ACQTIME_10US,
            .mode = NRF_SAADC_MODE_SINGLE_ENDED,
            .burst = NRF_SAADC_BURST_DISABLED,
            .pin_p = SAADC_INPUT_TEMP,
            .pin_n = NRF_SAADC_INPUT_DISABLED};
        nrf_saadc_channel_init(TEMPERATURE_SAADC_CH, &ch1_cfg);
    }

    // VBatt input ~7.2V through 100k/100k+470k divider, expected voltage ~7.2/5.3 = 1.36V
    {
        nrf_saadc_channel_config_t ch2_cfg = {
            .resistor_p = NRF_SAADC_RESISTOR_DISABLED,
            .resistor_n = NRF_SAADC_RESISTOR_DISABLED,
            .gain = NRF_SAADC_GAIN1_3,
            .reference = NRF_SAADC_REFERENCE_INTERNAL,
            .acq_time = NRF_SAADC_ACQTIME_10US,
            .mode = NRF_SAADC_MODE_SINGLE_ENDED,
            .burst = NRF_SAADC_BURST_DISABLED,
            .pin_p = SAADC_INPUT_VBATT,
            .pin_n = NRF_SAADC_INPUT_DISABLED};
        nrf_saadc_channel_init(VBATT_SAADC_CH, &ch2_cfg);
    }
    module_init = true;
}

uint32_t td_saadc_single_measurement(void)
{
    if (!module_init)
    {
        return NRF_ERROR_INVALID_STATE;
    }

    nrf_saadc_enable();
    nrf_saadc_channel_limits_set(CURRENT_SAADC_CH, -10, 1365); // this is the line that cause the issue

    nrf_saadc_event_clear(NRF_SAADC_EVENT_CALIBRATEDONE);
    nrf_saadc_task_trigger(NRF_SAADC_TASK_CALIBRATEOFFSET);
    while (!nrf_saadc_event_check(NRF_SAADC_EVENT_CALIBRATEDONE))
        ;
    nrf_saadc_event_clear(NRF_SAADC_EVENT_CALIBRATEDONE);

    nrf_saadc_buffer_init(_sample, SAADC_CHANNELS_ENABLED);
    nrf_saadc_event_clear(NRF_SAADC_EVENT_STARTED);
    nrf_saadc_task_trigger(NRF_SAADC_TASK_START);
    while (!nrf_saadc_event_check(NRF_SAADC_EVENT_STARTED))
        ;

    nrf_saadc_task_trigger(NRF_SAADC_TASK_SAMPLE);
    while (!nrf_saadc_event_check(NRF_SAADC_EVENT_END))
        ;
    nrf_saadc_event_clear(NRF_SAADC_EVENT_END);
    NRF_LOG_INFO("SAADC done event, samples: current %d, temperature %d, vbatt %d",
                    _sample[0],
                    _sample[1],
                    _sample[2]);
    NRF_LOG_FLUSH();

    nrf_saadc_event_clear(NRF_SAADC_EVENT_STOPPED);
    nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
    while (!nrf_saadc_event_check(NRF_SAADC_EVENT_STOPPED))
        ;

    nrf_saadc_disable();
    NRF_LOG_FLUSH();

    return NRF_SUCCESS;
}

Observed log output (bad case)

When low limit is around -10 (or generally > ~-100):

<info> app: SAADC done event, samples: current 11, temperature 38, vbatt 57

Observed log output (good case)

When the low limit call is commented out, or low limit is <= ~-200:

<info> app: SAADC done event, samples: current 39, temperature 1814, vbatt 342

Key condition

  • Only setting low limit on channel 0 causes the issue.
  • Setting limits on other channels does not reproduce the problem (with the same measurement flow).
  • High limit value doesn't influence the result

Development Setup

  • Bare Metal
  • NRF version: nRF5_SDK_17.1.0_ddde560
  • SoftDevice: s140
  • Bug reproduced on nrf52840dk 1.0.0 and 2.0.1
Parents Reply Children
Related