This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

SAADC V2 Sample Values Low

I am in the process of integrating the latest SAADC driver into my project and have noticed an issue after adding the errata 122 workaround to my saadc event handler per the example here. Without the workaround I am seeing expected values from two channels (VDD and a thermistor) and as expected, current consumption never returns to normal. With the workaround added to my event handler I am getting value that are quite a bit lower than they should be. VDD seems to be slightly low but the value is also within a reasonable range of the actual voltage that makes it difficult to tell. The value I am getting back from my thermistor is significantly lower however.  Has anyone noticed this behavior in the V2 driver?

I am using a Rev2 nrf52840 with the thread and zigbee SDK 4.1 and the drivers from SDK 17.0.2.

Relevant code:

void saadc_callback(nrfx_saadc_evt_t const *p_event)
{
    ret_code_t err_code = NRF_SUCCESS;

    if (p_event->type == NRFX_SAADC_EVT_DONE)
    {
        nrf_gpio_pin_clear(SENSOR_1_EN);

//        err_code = nrfx_saadc_buffer_convert(p_event->data.done.p_buffer, samples_in_buffer);
//        APP_ERROR_CHECK(err_code);

        //update_temperature_attribute(MULTI_SENSOR_ENDPOINT, convertTemp(p_event->data.done.p_buffer[0]));
        zb_schedule_app_callback((zb_callback_t)(process_temperature), MULTI_SENSOR_ENDPOINT, ZB_TRUE, p_event->data.done.p_buffer[0], ZB_FALSE);        // Param 1 = endpoint (10), CB_Param = SAADC buffer value

        // Need to eventually determine why either not allowing the SAADC to calibrate (commenting the below block out) or only calibrating at any conditional interval (m_adc_evt_counter % 10 == 0) causes power consumption to be stuck in the 1.2mA range.
        if(m_adc_evt_counter)
        {
            nrfx_saadc_abort();
            zb_schedule_app_callback(calibrate_adc, 0, ZB_FALSE, 0, ZB_FALSE);
        }

        m_adc_evt_counter++;
        
        NRF_LOG_INFO("VDD: %d", p_event->data.done.p_buffer[1]);
    }
    else if(p_event->type == NRFX_SAADC_EVT_CALIBRATEDONE)
    {            
        NRF_LOG_INFO("SAADC Calibration Complete");
    }

    // Workaround from Errata 122
    volatile uint32_t temp1;
    volatile uint32_t temp2;
    volatile uint32_t temp3;

    temp1 = *(volatile uint32_t *)0x40007640ul;
    temp2 = *(volatile uint32_t *)0x40007644ul;
    temp3 = *(volatile uint32_t *)0x40007648ul;

    *(volatile uint32_t *)0x40007FFCul = 0ul; 
    *(volatile uint32_t *)0x40007FFCul; 
    *(volatile uint32_t *)0x40007FFCul = 1ul;

    *(volatile uint32_t *)0x40007640ul = temp1;
    *(volatile uint32_t *)0x40007644ul = temp2;
    *(volatile uint32_t *)0x40007648ul = temp3;
}

static void init_saadc()
{
    ret_code_t err_code = NRF_SUCCESS;
    
    std::array <nrfx_saadc_channel_t, 2> channels = {
    { 
        { .channel_config = { .resistor_p = NRF_SAADC_RESISTOR_DISABLED, .resistor_n = NRF_SAADC_RESISTOR_DISABLED, .gain = NRF_SAADC_GAIN1_4, .reference = NRF_SAADC_REFERENCE_VDD4, .acq_time = NRF_SAADC_ACQTIME_10US, .mode = NRF_SAADC_MODE_SINGLE_ENDED, .burst = NRF_SAADC_BURST_ENABLED, .pin_p = NRF_SAADC_INPUT_AIN5, .pin_n = NRF_SAADC_INPUT_DISABLED }, .pin_p = NRF_SAADC_INPUT_AIN5, .pin_n = NRF_SAADC_INPUT_DISABLED, .channel_index = 0 },
        { .channel_config = { .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_10US, .mode = NRF_SAADC_MODE_SINGLE_ENDED, .burst = NRF_SAADC_BURST_ENABLED, .pin_p = NRF_SAADC_INPUT_VDD, .pin_n = NRF_SAADC_INPUT_DISABLED }, .pin_p = NRF_SAADC_INPUT_VDD, .pin_n = NRF_SAADC_INPUT_DISABLED, .channel_index = 1 }
    }
    };
    
    err_code = nrfx_saadc_init(NRFX_SAADC_CONFIG_IRQ_PRIORITY);
    APP_ERROR_CHECK(err_code);
    
    err_code = nrfx_saadc_channels_config(channels.data(), channels.size());
    APP_ERROR_CHECK(err_code);

    while (nrfx_saadc_offset_calibrate(saadc_callback) != NRF_SUCCESS)
    {
        __WFE();
        __SEV();
        __WFE();
    }
}

void zb_app_timer_handler(zb_uint8_t param)
{
    ret_code_t err_code = NRF_SUCCESS;
    
    nrf_gpio_pin_set(SENSOR_1_EN);
    
    err_code = nrfx_saadc_simple_mode_set((1 << 0 | 1 << 1), NRF_SAADC_RESOLUTION_14BIT, NRF_SAADC_OVERSAMPLE_32X, saadc_callback);
    APP_ERROR_CHECK(err_code);
    
    err_code = nrfx_saadc_buffer_set(m_buffer_pool.data(), m_buffer_pool.size());
    APP_ERROR_CHECK(err_code);   
    
    err_code = nrfx_saadc_mode_trigger();
    APP_ERROR_CHECK(err_code);
    
    zb_ret_t zb_err_code = zb_schedule_app_alarm(zb_app_timer_handler, 0, 60 * ZB_TIME_ONE_SECOND);
    APP_ERROR_CHECK(zb_err_code);
}

Parents Reply Children
  • Hi Jørgen,

    The PSEL pin is correct after letting the workaround run. I did not check the VDD channel, but channel 0 at 0x40007510 has a value of 6 (AIN5) after several samples are taken.

    Just for clarification, the value that is returned from the input channel is never the correct value when the workaround is in place, it always reads a low value.

    Edit:

    It looks like the value of the PSEL register is reset to 0 up to the moment the callback is entered. Upon entering the callback the value changes to the correct input and is then reset to 0.

  • Over the past couple of afternoons I have tried a few things in the hopes that this is a self inflicted error.

    • I've changed my buffer and channel configuration arrays to C arrays, moved them to global scope and made them static.
    • Disabled oversampling
    • Tried sampling only one channel
    • Changed the analog input being sampled
    • Changed resolution

    None of the above made a difference.

  • After a little more digging today I think I may have determined the problem. Starting with the example I linked in my original post I modified the channel inputs to my own and ran the firmware. With this configuration I am getting the same results as when I have the workaround enabled in my project. Commenting out the workaround in this sample project has no effect on the values returned by the ADC.

    Going deeper, I replaced the macro configuration of one channel with my custom channel configuration (channel 0 from original post). Once again, the low value readings appear, but they are the same values as when using the default channel configuration macro. I run one more time, this time changing my configuration to use a gain of 1/6 and the internal reference and I have now gotten the low value as before.

    My conclusion at this point is that the channels are not being correctly configured once the workaround is applied, other than PSEL pins. Looking through the driver it appears that the channels are only ever configured when a call to nrfx_saadc_channels_config is made and never again. Since the workaround power cycles the peripheral any subsequent samples are using the default state of the peripheral registers.

    I think the behavior described is certainly a bug but can be mitigated by relocating the call to nrfx_saadc_channels_config, and not too difficult to fix in the driver. This does bring up a couple of additional questions however:

    1. If samples are taken at fairly long intervals, say every minute, or even a few seconds, is there any advantage to using this workaround instead of initializing/uninitializing the saadc for each use since it has to be totally reconfigured anyway? I'd imagine there is a little additional latency in the time to first sample but that may not be significant.
    2. Since the workaround power cycles the saadc is it also necessary to run a calibration? I assume any previous calibration would be lost during a power cycle. If yes, it seems like question 1 comes into play again.
  • Hi,

    This maskes a lot of sense. I was actually looking into this before, but did not see the problem as I used the default configs for most configs, and the pin configs are re-applied by nrfx_saadc_simple_mode_set().

    1. The workaround was in this case applied to reduce the current consumption between samples, and not for the issue described in Errata 212. The workaround is actually applied in the driver as well, when you call nrfx_saadc_simple_mode_set(), but since this was done right before sampling in this example, the current consumption between sampling is quite high. There could possibly be a better way to solve this, but I did not have time to investigate it further before posting the examples. I believe that uninitializing the driver will not solve this issue that I was seeing. One possible solution could be to store the config in temporary variables and re-apply this after the power cycle.
    2. No, the calibration data is stored in the temp variables and re-applied by the workaround, no need to redo this.

    Best regards,
    Jørgen

  • Thanks for the assistance Jørgen. I've got sampling running acceptably well between my findings and your latest input and will go ahead and mark this answered to get it out of your queue.

Related