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

Turn off BLE for optimal ADC readings

I'm working on an application in which we are using a custom board with the NRF52382, which is powered by a battery. We sample our ADC every 6ms, record these values for about twenty seconds, and then transmit them to a phone using the NUS_uart service.

We've found that there is some noise introduced by what I assume is the link layer in the soft device sending acknowledgement packets to see if the connection with the paired phone is still present. If I disable the softdevice and don't advertise on the disconnect, when observing our signal coming in to the ADC with an oscilloscope, this noise seems to not be present. I believe that radio transmissions are dropping the reference voltage of our power supply, and thus affecting the information coming off our sensors.

I'm wondering what the optimal approach to preventing the radio from drawing power during our sampling / recording period would be. If it would be possible to turn off the link layer for a portion of time during the reading, I think this would be sufficient. Otherwise perhaps tuning the connection parameters in combination with the softdevice scheduler looks like another viable approach, but I'm unsure of the specifics to go about doing this.

Parents
  • You can either request a timeslot, then you will get an un-interrupted timeslot that you can do what you like. Or you can use radio notifications, and plan your ADC operations after or between radio events.

  • I've attempted using both the timeslot API to request a timeslot in which the radio is inactive. I've tried to request a period of 10.5 seconds, but it seems that the connection interval takes priority and the radio starts chatting again.

    To aid in this, I've configured a radio notification event to set a volatile flag before and after radio events, such that I'll omit capturing a reading when a radio is about to occur, and request another timeslot instead.

    I based my timeslot code on Ole Bauck's timeslot.c from https://devzone.nordicsemi.com/f/nordic-q-a/19200/timeslot-extension/74376#74376

    #include <stdint.h>
    #include <stdbool.h>
    #include "nrf.h"
    #include "app_error.h"
    #include "nrf_sdh.h"
    #include "nrf_soc.h"
    #include "boards.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    /**Constants for timeslot API
    */
    static nrf_radio_request_t  m_timeslot_request;
    static uint32_t             m_slot_length;
    
    volatile uint32_t total_timeslot_length;
    volatile uint32_t extend_success_count;
    volatile uint32_t fail_count;
    
    static nrf_radio_signal_callback_return_param_t signal_callback_return_param;
    
    #define TIMESLOT_EXTEND_LENGTH 200//was 200
    #define TIMESLOT_TIMER_INTERRUPT_END_MARGIN		50		//margin in us on timer 0 interrupt before the timeslot ends
    #define QUIET_TIME 10500000 //10.5 seconds
    
    /**@brief Request next timeslot event in earliest configuration
     */
    uint32_t request_next_event_earliest(void)
    {
        m_slot_length                                  = 15000;
        m_timeslot_request.request_type                = NRF_RADIO_REQ_TYPE_EARLIEST;
        m_timeslot_request.params.earliest.hfclk       = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED;
        m_timeslot_request.params.earliest.priority    = NRF_RADIO_PRIORITY_NORMAL;
        m_timeslot_request.params.earliest.length_us   = m_slot_length;
        m_timeslot_request.params.earliest.timeout_us  = 1000000;
        return sd_radio_request(&m_timeslot_request);
    }
    
    
    /**@brief Configure next timeslot event in earliest configuration
     */
    void configure_next_event_earliest(void)
    {
        m_slot_length                                  = 15000;
        m_timeslot_request.request_type                = NRF_RADIO_REQ_TYPE_EARLIEST;
        m_timeslot_request.params.earliest.hfclk       = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED;
        m_timeslot_request.params.earliest.priority    = NRF_RADIO_PRIORITY_NORMAL;
        m_timeslot_request.params.earliest.length_us   = m_slot_length;
        m_timeslot_request.params.earliest.timeout_us  = 1000000;
    }
    
    
    /**@brief Configure next timeslot event in normal configuration
     */
    void configure_next_event_normal(void)
    {
        m_slot_length                                 = 15000;
        m_timeslot_request.request_type               = NRF_RADIO_REQ_TYPE_NORMAL;
        m_timeslot_request.params.normal.hfclk        = NRF_RADIO_HFCLK_CFG_XTAL_GUARANTEED;
        m_timeslot_request.params.normal.priority     = NRF_RADIO_PRIORITY_HIGH;
        m_timeslot_request.params.normal.distance_us  = 100000;
        m_timeslot_request.params.normal.length_us    = m_slot_length;
    }
    
    
    /**@brief Timeslot signal handler
     */
    void nrf_evt_signal_handler(uint32_t evt_id, void * p_context)
    {
    	UNUSED_PARAMETER(p_context);
        uint32_t err_code;
    
        switch (evt_id)
        {
            case NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN:
                //No implementation needed
                break;
            case NRF_EVT_RADIO_SESSION_IDLE:
                //No implementation needed
                break;
            case NRF_EVT_RADIO_SESSION_CLOSED:
                //No implementation needed, session ended
                break;
            case NRF_EVT_RADIO_BLOCKED:
                //Fall through
            case NRF_EVT_RADIO_CANCELED:
                err_code = request_next_event_earliest();
                APP_ERROR_CHECK(err_code);
                break;
            default:
                break;
        }
    }
    
    
    /**@brief Timeslot event handler
     */
    nrf_radio_signal_callback_return_param_t * radio_callback(uint8_t signal_type)
    {
        switch(signal_type)
        {
            case NRF_RADIO_CALLBACK_SIGNAL_TYPE_START:
    			NRF_TIMER0->INTENSET = TIMER_INTENSET_COMPARE0_Msk;
    			NRF_TIMER0->BITMODE = (TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos);
    			NRF_TIMER0->PRESCALER = (4 << TIMER_PRESCALER_PRESCALER_Pos);
                NRF_TIMER0->CC[0] = m_slot_length - TIMESLOT_TIMER_INTERRUPT_END_MARGIN;
                NVIC_EnableIRQ(TIMER0_IRQn);
    
    			total_timeslot_length = m_slot_length;
    
    			signal_callback_return_param.params.request.p_next = NULL;
                signal_callback_return_param.params.extend.length_us = TIMESLOT_EXTEND_LENGTH;
                signal_callback_return_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND;
    
                break;
    
            case NRF_RADIO_CALLBACK_SIGNAL_TYPE_RADIO:
                signal_callback_return_param.params.request.p_next = NULL;
                signal_callback_return_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE;
                break;
    
            case NRF_RADIO_CALLBACK_SIGNAL_TYPE_TIMER0:
    			//timeslot is ending, signal the end action
                NRF_LOG_INFO("Timeslot ending after %d microseconds\n", NRF_TIMER0->CC[0]);
    			signal_callback_return_param.params.request.p_next = NULL;
    			signal_callback_return_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_END;
                break;
            case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_SUCCEEDED:
    
    			extend_success_count++;
    
                if(total_timeslot_length >= QUIET_TIME)
    			{
    				signal_callback_return_param.params.request.p_next = NULL;
    				signal_callback_return_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_NONE;
    			}
    			else
    			{
    				total_timeslot_length += TIMESLOT_EXTEND_LENGTH;
    
    				NRF_TIMER0->CC[0] = total_timeslot_length - TIMESLOT_TIMER_INTERRUPT_END_MARGIN;
    				signal_callback_return_param.params.request.p_next = NULL;
    				signal_callback_return_param.params.extend.length_us = TIMESLOT_EXTEND_LENGTH;
    				signal_callback_return_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_EXTEND;
    			}
                break;
            case NRF_RADIO_CALLBACK_SIGNAL_TYPE_EXTEND_FAILED:
    
                fail_count++;
    
    			signal_callback_return_param.params.request.p_next = NULL;
                signal_callback_return_param.callback_action = NRF_RADIO_SIGNAL_CALLBACK_ACTION_END;
                break;
            default:
                //No implementation needed
                break;
        }
        return (&signal_callback_return_param);
    }
    
    
    /**@brief Function for initializing the timeslot API.
     */
    uint32_t timeslot_sd_init(void)
    {
        uint32_t err_code;
    
        err_code = sd_radio_session_open(radio_callback);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    
        err_code = request_next_event_earliest();
        if (err_code != NRF_SUCCESS)
        {
            (void)sd_radio_session_close();
            return err_code;
        }
        return NRF_SUCCESS;
    }
    

    This is what I've done to set up a Radio notification to set a flag to True when the radio is inactive, and false when the radio is active:

    void SWI1_IRQHandler(bool radio_evt)
    {
        radio_flag = !radio_flag;
    }
    
    /**@brief Function for initializing Radio Notification Software Interrupts.
     */
    uint32_t radio_notification_init(uint32_t irq_priority, uint8_t notification_type, uint8_t notification_distance)
    {
        uint32_t err_code;
    
        err_code = sd_nvic_ClearPendingIRQ(SWI1_IRQn);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    
        err_code = sd_nvic_SetPriority(SWI1_IRQn, irq_priority);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    
        err_code = sd_nvic_EnableIRQ(SWI1_IRQn);
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    
        // Configure the event
        return sd_radio_notification_cfg_set(notification_type, notification_distance);
    }

    Which I enable with

    err_code = radio_notification_init(3, NRF_RADIO_NOTIFICATION_TYPE_INT_ON_BOTH, NRF_RADIO_NOTIFICATION_DISTANCE_5500US);
    APP_ERROR_CHECK(err_code);

    after I call ble_stack_init();

    Within my adc callback, I have the following:

    					if ( get_radio_flag() )
    					{
    						set_value_in_array( adc_counter, adc_value[1]);
    
    						adc_counter++;
    
    						if (adc_counter >= get_max_readings())
    						{
    							end_reading_routine();
    						}
    					}
    
    					else
    					{
    					//error code should be checked here
    						request_next_event_earliest();
    					}

    EDIT: by accessing radio_flag directly as a global volatile instead of using a getter, and toggling the value of radio_flag as the sole task of SWI1_IRQHandler, I was able to see some reduction in noise in my readings.

    I'd like to make use of the timeslot API to further reduce the noise in my signal, as the approach I'm taking doesn't always prevent noise from affecting my readings (this is on a custom board, in which a battery is the voltage source)

    Please let me know if you need any additional information or if I'm doing anything that's obviously incorrect.

  • If you are not using timeslot, then you should make sure to request HFCLK by calling sd_clock_hfclk_request() and wait sd_clock_hfclk_is_running() for before starting ADC measurements. When all ADC measurements are done you can call sd_clock_hfclk_release().

  • Can you provide some clarification as to what this will accomplish?

  • The accuracy depend on an accurate internal clock for sampling, the best accuracy is ensured by starting the external high frequency crystal. 

Reply Children
No Data
Related