nRF9160 SAADC nrfx api: high power consumption after uninit

Hi,

as mentioned in a previous ticket I'm developing a successor to one of our products.
One important feature is that we can change pin functions anytime. (output, input, pwm, ain, ...)
Because of this the devicetree isn't the way to go for us and we are trying to solve as much as possible with the nrfx api.

A problem we are face right now is that the SAADC causes a high power consumption after uninitializing it.
Instead of returning to 10-20µA it stays at around 650µA.

We are doing the following: configuring the saadc, triggering one measurement in blocking mode and deactivating/deconfiguring the saadc again.

int32_t ADC_ConvertSingleADC(TADC_AInput eAdcCAIn, TADC_AcqTimeUs eAdcAcqTimeUs, TADC_Res eAdcResolution)
{
  int32_t iErr;
  int16_t s16SampleBuffer;

  nrfx_saadc_adv_config_t adc_config = NRFX_SAADC_DEFAULT_ADV_CONFIG;
  nrfx_saadc_channel_t channel_config = NRFX_SAADC_DEFAULT_CHANNEL_SE((eAdcCAIn), 0);

  channel_config.channel_config.gain = psADC_Cfg->eGain;
  channel_config.channel_config.reference = psADC_Cfg->eRefType;
  channel_config.channel_config.acq_time = eAdcAcqTimeUs;

  nrfx_saadc_init(7);
  iErr = nrfx_saadc_channel_config(&channel_config);
  if(iErr != NRFX_SUCCESS)
    return(0);
  iErr = nrfx_saadc_advanced_mode_set((0x01), eAdcResolution, &adc_config, NULL);
  if(iErr != NRFX_SUCCESS)
    return(0);
  iErr = nrfx_saadc_buffer_set(&s16SampleBuffer, sizeof(s16SampleBuffer)/sizeof(int16_t));
  if(iErr != NRFX_SUCCESS)
    return(0);
  iErr = nrfx_saadc_mode_trigger();
  if(iErr != NRFX_SUCCESS)
    return(iErr);

  nrfx_saadc_uninit();
  nrfx_saadc_channels_deconfig(0x01);

  return(s16SampleBuffer);
}

Any idea what the problem could be?

I've tested this with nRF Connect SDK version 1.8.0 and 2.0.2.

Kind Regards
Johannes

Parents Reply Children
  • Hi Håkon,

    what a timing. I just wanted to post my findings. ^^

    What I found is that in the zephyr implementation after a blocking adc measurement is triggered the adc is stopped with exactly this call.
    When issuing a stop task after the measurement has finished the power consumption is down to what I was expecting.

    Another thing that needs to be done is to add the saadc_channels_disable function to the SDK version 1.8.0. Without it the pin cannot be used for anything else.

    Here are the functions with my changes:

    nrfx_err_t nrfx_saadc_mode_trigger(void)
    {
        NRFX_ASSERT(m_cb.saadc_state != NRF_SAADC_STATE_UNINITIALIZED);
        NRFX_ASSERT(m_cb.saadc_state != NRF_SAADC_STATE_IDLE);
    
        if (!m_cb.p_buffer_primary)
        {
            return NRFX_ERROR_NO_MEM;
        }
    
        nrfx_err_t result = NRFX_SUCCESS;
        switch (m_cb.saadc_state)
        {
            case NRF_SAADC_STATE_SIMPLE_MODE:
                nrf_saadc_enable(NRF_SAADC);
                // When in simple blocking or non-blocking mode, buffer size is equal to activated channel count.
                // Single SAMPLE task is enough to obtain one sample on each activated channel.
                // This will result in buffer being filled with samples and therefore END event will appear.
                nrf_saadc_buffer_init(NRF_SAADC, m_cb.p_buffer_primary, m_cb.size_primary);
                if (m_cb.event_handler)
                {
                    m_cb.saadc_state = NRF_SAADC_STATE_SIMPLE_MODE_SAMPLE;
                    nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_START);
                }
                else
                {
                    nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_START);
                    while (!nrf_saadc_event_check(NRF_SAADC, NRF_SAADC_EVENT_STARTED))
                    {}
                    nrf_saadc_event_clear(NRF_SAADC, NRF_SAADC_EVENT_STARTED);
    
                    nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_SAMPLE);
                    while (!nrf_saadc_event_check(NRF_SAADC, NRF_SAADC_EVENT_END))
                    {}
                    nrf_saadc_event_clear(NRF_SAADC, NRF_SAADC_EVENT_END);
                    nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_STOP);
                    nrf_saadc_disable(NRF_SAADC);
                }
                break;
    
            case NRF_SAADC_STATE_ADV_MODE:
                nrf_saadc_enable(NRF_SAADC);
                if (m_cb.event_handler)
                {
                    // When in advanced non-blocking mode, latch whole buffer in EasyDMA.
                    // END event will arrive when whole buffer is filled with samples.
                    m_cb.saadc_state = NRF_SAADC_STATE_ADV_MODE_SAMPLE;
                    nrf_saadc_buffer_init(NRF_SAADC, m_cb.p_buffer_primary, m_cb.size_primary);
                    nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_START);
                    break;
                }
    
                // When in advanced blocking mode, latch single chunk of buffer in EasyDMA.
                // Each chunk consists of single sample from each activated channels.
                // END event will arrive when single chunk is filled with samples.
                nrf_saadc_buffer_init(NRF_SAADC,
                                      &m_cb.p_buffer_primary[m_cb.samples_converted],
                                      m_cb.channels_activated_count);
    
                nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_START);
                while (!nrf_saadc_event_check(NRF_SAADC, NRF_SAADC_EVENT_STARTED))
                {}
                nrf_saadc_event_clear(NRF_SAADC, NRF_SAADC_EVENT_STARTED);
    
                if (m_cb.oversampling_without_burst)
                {
                    // Oversampling without burst is possible only on single channel.
                    // In this configuration more than one SAMPLE task is needed to obtain single sample.
                    uint32_t samples_to_take =
                        nrf_saadc_oversample_sample_count_get(nrf_saadc_oversample_get(NRF_SAADC));
    
                    for (uint32_t sample_idx = 0; sample_idx < samples_to_take; sample_idx++)
                    {
                        nrf_saadc_event_clear(NRF_SAADC, NRF_SAADC_EVENT_DONE);
                        nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_SAMPLE);
                        while (!nrf_saadc_event_check(NRF_SAADC, NRF_SAADC_EVENT_DONE))
                        {}
                    }
                }
                else
                {
                    // Single SAMPLE task is enough to obtain one sample on each activated channel.
                    // This will result in chunk being filled with samples and therefore END event will appear.
                    nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_SAMPLE);
                }
                while (!nrf_saadc_event_check(NRF_SAADC, NRF_SAADC_EVENT_END))
                {}
                nrf_saadc_event_clear(NRF_SAADC, NRF_SAADC_EVENT_END);
                nrf_saadc_task_trigger(NRF_SAADC, NRF_SAADC_TASK_STOP);
    
                m_cb.samples_converted += m_cb.channels_activated_count;
                if (m_cb.samples_converted < m_cb.size_primary)
                {
                    result = NRFX_ERROR_BUSY;
                }
                else
                {
                    m_cb.samples_converted  = 0;
                    m_cb.p_buffer_primary   = m_cb.p_buffer_secondary;
                    m_cb.size_primary       = m_cb.size_secondary;
                    m_cb.p_buffer_secondary = NULL;
                }
                nrf_saadc_disable(NRF_SAADC);
                break;
    
            default:
                result = NRFX_ERROR_INVALID_STATE;
                break;
        }
    
        return result;
    }
    

    static void saadc_channels_disable(uint32_t channel_mask)
    {
        while (channel_mask)
        {
            uint8_t channel = __CLZ(__RBIT(channel_mask));
            channel_mask &= ~(1 << channel);
            m_cb.channels_configured &= ~(1 << channel);
            m_cb.channels_activated &= ~(1 << channel);
    
            m_cb.channels_pselp[channel] = NRF_SAADC_INPUT_DISABLED;
            m_cb.channels_pseln[channel] = NRF_SAADC_INPUT_DISABLED;
            nrf_saadc_channel_input_set(NRF_SAADC, channel,
                                        NRF_SAADC_INPUT_DISABLED, NRF_SAADC_INPUT_DISABLED);
        }
        m_cb.channels_activated_count = 0;
        for(int i = 0; i < SAADC_CH_NUM; i++)
        {
            if(m_cb.channels_activated & (1 << i))
            {
                m_cb.channels_activated_count++;
            }
        }
    }
    
    void nrfx_saadc_uninit(void)
    {
        nrfx_saadc_abort();
        NRFX_IRQ_DISABLE(SAADC_IRQn);
        nrf_saadc_disable(NRF_SAADC);
        saadc_channels_disable(m_cb.channels_configured | m_cb.channels_activated);
        m_cb.saadc_state = NRF_SAADC_STATE_UNINITIALIZED;
    }

    Kind regards,
    Johannes

  • Hi Johannes,

     

    Thank you for updating with your findings!

    JoEi said:

    what a timing. I just wanted to post my findings. ^^

    What I found is that in the zephyr implementation after a blocking adc measurement is triggered the adc is stopped with exactly this call.
    When issuing a stop task after the measurement has finished the power consumption is down to what I was expecting.

    Another thing that needs to be done is to add the saadc_channels_disable function to the SDK version 1.8.0. Without it the pin cannot be used for anything else.

    Yes, this is similar to the fix that we added ~6 months ago:

    https://github.com/zephyrproject-rtos/hal_nordic/blob/master/nrfx/drivers/src/nrfx_saadc.c#L295

     

    Kind regards,

    Håkon

Related