This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

ADC Continuous Sampling Issue - NRF52840 on Nano 33

Hello,

I developed a script which has a 40Hz timer which repeatedly starts the NRF52840's SAADC in continuous mode (with a 40kHz rate) which is supposed to fill a revolving buffer in RAM while my CPU does other activities.

An interrupt is then triggered at the end of the ADC collection (SAADC "END" EVENT), which notifies the CPU to send out those ADC values either Serially or through BLE (working through that later).

My snippet implementation is below. The issue I am having right now is that no samples are being read into the buffer, and I confirmed this further by regularly checking the SAADC's RESULT.AMOUNT register which stayed at zero indefinitely. I also verified that the STARTED EVENT was triggered, just no samples are being read despite the SAADC being set to continuous mode.

Any ideas?

// Contains the (16-bit) results for the SAADC - volatile buffer
constexpr uint16_t NUM_SAADC_RESULT_BUFFERS = 40;
constexpr uint16_t SAADC_RESULT_BUFFER_SIZE = 1024;
volatile nrf_saadc_value_t SAADC_RESULT_BUFFER[NUM_SAADC_RESULT_BUFFERS * SAADC_RESULT_BUFFER_SIZE];

// Start the transmission
bool START_TRANSMISSION = false;

// Buffer index (For the SAADC Buffers)
uint32_t BUFFER_INDEX = 0;


/**
* SAADC IRQ Handler: Weak Link to be taken here and triggered during END event in SAADC (Enabled elsewhere)
*/
    extern "C" void SAADC_IRQHandler_v( void ) {
    // Check to see if the ADC has filled up the result buffer
    if (nrf_saadc_event_check(NRF_SAADC_EVENT_END))
    {
        nrf_saadc_event_clear(NRF_SAADC_EVENT_END); // Clear the "END" event
        
        START_TRANSMISSION = true; // Start transmission
        
        BUFFER_INDEX = BUFFER_INDEX >= NUM_SAADC_RESULT_BUFFERS - 1 ? 0 : BUFFER_INDEX + 1;
    }
}

static void configure_saadc_channel_2()
{
    // Configure A1 & A2 channel as differential
    nrf_saadc_channel_config_t 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_3US,
        .mode = NRF_SAADC_MODE_DIFFERENTIAL,
        .burst = NRF_SAADC_BURST_DISABLED,
        .pin_p = NRF_SAADC_INPUT_AIN2,
        .pin_n = NRF_SAADC_INPUT_AIN3
    };

    // Configure the NRF CH[1] to values above
    // CH[1] is used instead of CH[0] because CH[0] is used during the analogRead Arduino API Function
    NRF_SAADC->CH[1].CONFIG =
        ((channel_config.resistor_p << SAADC_CH_CONFIG_RESP_Pos) & SAADC_CH_CONFIG_RESP_Msk)
        | ((channel_config.resistor_n << SAADC_CH_CONFIG_RESN_Pos) & SAADC_CH_CONFIG_RESN_Msk)
        | ((channel_config.gain << SAADC_CH_CONFIG_GAIN_Pos) & SAADC_CH_CONFIG_GAIN_Msk)
        | ((channel_config.reference << SAADC_CH_CONFIG_REFSEL_Pos) & SAADC_CH_CONFIG_REFSEL_Msk)
        | ((channel_config.acq_time << SAADC_CH_CONFIG_TACQ_Pos) & SAADC_CH_CONFIG_TACQ_Msk)
        | ((channel_config.mode << SAADC_CH_CONFIG_MODE_Pos) & SAADC_CH_CONFIG_MODE_Msk)
        | ((channel_config.burst << SAADC_CH_CONFIG_BURST_Pos) & SAADC_CH_CONFIG_BURST_Msk);

    // Configure the Negative & Positive Ends of CH[1]
    NRF_SAADC->CH[1].PSELN = channel_config.pin_n;
    NRF_SAADC->CH[1].PSELP = channel_config.pin_p;
}

inline void sample_voltage()
{
    nrf_saadc_task_trigger(NRF_SAADC_TASK_START); // Start the ADC and prepare the result buffer in RAM.
}

static void configure_saadc_channels()
{
    nrf_saadc_disable();
    
    // Configure each SAADC Channel
    // Note: Channel 1 (CH[0]) is not used as it is reserved for analogRead functionality
    // through Arduino's platform.
    configure_saadc_channel_2();
    
    // Configure the resolution
    NRF_SAADC->RESOLUTION = NRF_SAADC_RESOLUTION_12BIT; // 12-bit - 14-bit attainable only through oversampling
    
    // Disable oversampling
    //NRF_SAADC->OVERSAMPLE = NRF_SAADC_OVERSAMPLE_DISABLED;
    
    // Enable Continuous Mode & set to 16MHz / 400 (CC)
    NRF_SAADC->SAMPLERATE = (SAADC_SAMPLERATE_MODE_Timers << SAADC_SAMPLERATE_MODE_Pos)
                            | ((uint32_t)400 << SAADC_SAMPLERATE_CC_Pos);
    
    // Configure RESULT Buffer and MAXCNT
    NRF_SAADC->RESULT.PTR = (uint32_t)SAADC_RESULT_BUFFER;
    NRF_SAADC->RESULT.MAXCNT = SAADC_RESULT_BUFFER_SIZE;
    
    // Enable the SAADC IRQ
    NRF_SAADC->EVENTS_END = 0;
    nrf_saadc_int_enable( NRF_SAADC_INT_END ); // Set the END mask to an interrupt
    NVIC_SetPriority( SAADC_IRQn, 3UL );
    NVIC_EnableIRQ( SAADC_IRQn );
    
    nrf_saadc_enable(); // Enable the SAADC
    
    // Calibrate the SAADC by finding its offset
    NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;
    while (NRF_SAADC->EVENTS_CALIBRATEDONE == 0);
    NRF_SAADC->EVENTS_CALIBRATEDONE = 0;
    while (NRF_SAADC->STATUS == (SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos));
}

static uint32_t inline collect_adc_data()
{
    nrf_saadc_disable();
    NRF_SAADC->RESULT.PTR = (uint32_t)(SAADC_RESULT_BUFFER + (BUFFER_INDEX * SAADC_RESULT_BUFFER_SIZE));
    nrf_saadc_enable(); // Enable the SAADC
    
    sample_voltage();
}

void setup()
{
    collect_adc_data();
}

  • Hello,

    I notice that you are triggering the START task in your sample_voltage function - could you change this to TASKS_SAMPLE?
    The START task only starts the SAADC and prepares the result buffer - it does not actually perform a conversion. While the continuous mode issues its own SAMPLE tasks using the internal timer, it seems to need to have the sequence started by the SAMPLE task.

    I have created an internal ticket with our developers to have this reviewed and highlighted in the documentation if confirmed.

    Please also be aware of the Errata 237 for the nRF52840's SAADC when using the SAADC peripheral directly.

    Best regards,
    Karl

  • Hi Karl,

    Thanks! My apologies, I am unfamiliar with the rules of the forum.

    There is a peculiar issue with the NRF52840 SAADC sampling. For an unknown reason, triggering the SAMPLE Task in the SAADC only triggers the collection sequence in the SAADC (on continuous) if there is a while loop with a bit of trivial code in it (if empty, it does not work).

    Note, other code elsewhere in the program runs normally (Nowhere else are there interrupts, other than a 40Hz TIMER4); it is the SAADC_IRQHandler_v that never triggers. Also, note this is a configuration with interrupts.

    As you can see in the code below, interrupts are enabled at priority 3 (unsigned long), I am just unsure what could cause the SAADC to stall like that and never send the END update (nor collect any ADC reads).  

    // Contains the (16-bit) results for the SAADC - volatile buffer
    constexpr uint16_t NUM_SAADC_RESULT_BUFFERS = 40;
    constexpr uint16_t SAADC_RESULT_BUFFER_SIZE = 1024;
    volatile nrf_saadc_value_t SAADC_RESULT_BUFFER[NUM_SAADC_RESULT_BUFFERS * SAADC_RESULT_BUFFER_SIZE];
    
    // Start the transmission
    bool START_TRANSMISSION = false;
    
    // Buffer index (For the SAADC Buffers)
    uint32_t BUFFER_INDEX = 0;
    
    
    /**
    * SAADC IRQ Handler: Weak Link to be taken here and triggered during END event in SAADC (Enabled elsewhere)
    */
    extern "C" void SAADC_IRQHandler_v( void ) {
        // Check to see if the ADC has filled up the result buffer
        if (nrf_saadc_event_check(NRF_SAADC_EVENT_END))
        {
            nrf_saadc_event_clear(NRF_SAADC_EVENT_END); // Clear the "END" event
            
            START_TRANSMISSION = true; // Start transmission
            
            BUFFER_INDEX = BUFFER_INDEX >= NUM_SAADC_RESULT_BUFFERS - 1 ? 0 : BUFFER_INDEX + 1;
        }
    }
    
    static void configure_saadc_channel_2()
    {
        // Configure A1 & A2 channel as differential
        nrf_saadc_channel_config_t 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_3US,
            .mode = NRF_SAADC_MODE_DIFFERENTIAL,
            .burst = NRF_SAADC_BURST_DISABLED,
            .pin_p = NRF_SAADC_INPUT_AIN2,
            .pin_n = NRF_SAADC_INPUT_AIN3
        };
    
        // Configure the NRF CH[1] to values above
        // CH[1] is used instead of CH[0] because CH[0] is used during the analogRead Arduino API Function
        NRF_SAADC->CH[1].CONFIG =
            ((channel_config.resistor_p << SAADC_CH_CONFIG_RESP_Pos) & SAADC_CH_CONFIG_RESP_Msk)
            | ((channel_config.resistor_n << SAADC_CH_CONFIG_RESN_Pos) & SAADC_CH_CONFIG_RESN_Msk)
            | ((channel_config.gain << SAADC_CH_CONFIG_GAIN_Pos) & SAADC_CH_CONFIG_GAIN_Msk)
            | ((channel_config.reference << SAADC_CH_CONFIG_REFSEL_Pos) & SAADC_CH_CONFIG_REFSEL_Msk)
            | ((channel_config.acq_time << SAADC_CH_CONFIG_TACQ_Pos) & SAADC_CH_CONFIG_TACQ_Msk)
            | ((channel_config.mode << SAADC_CH_CONFIG_MODE_Pos) & SAADC_CH_CONFIG_MODE_Msk)
            | ((channel_config.burst << SAADC_CH_CONFIG_BURST_Pos) & SAADC_CH_CONFIG_BURST_Msk);
    
        // Configure the Negative & Positive Ends of CH[1]
        NRF_SAADC->CH[1].PSELN = channel_config.pin_n;
        NRF_SAADC->CH[1].PSELP = channel_config.pin_p;
    }
    
    inline void sample_voltage()
    {
        nrf_saadc_task_trigger(NRF_SAADC_TASK_START); // Start the ADC and prepare the result buffer in RAM.
        nrf_saadc_task_trigger(NRF_SAADC_TASK_SAMPLE);
    }
    
    static void configure_saadc_channels()
    {
        nrf_saadc_disable();
        
        // Configure each SAADC Channel
        // Note: Channel 1 (CH[0]) is not used as it is reserved for analogRead functionality
        // through Arduino's platform.
        configure_saadc_channel_2();
        
        // Configure the resolution
        NRF_SAADC->RESOLUTION = NRF_SAADC_RESOLUTION_12BIT; // 12-bit - 14-bit attainable only through oversampling
        
        // Disable oversampling
        //NRF_SAADC->OVERSAMPLE = NRF_SAADC_OVERSAMPLE_DISABLED;
        
        // Enable Continuous Mode & set to 16MHz / 400 (CC)
        NRF_SAADC->SAMPLERATE = (SAADC_SAMPLERATE_MODE_Timers << SAADC_SAMPLERATE_MODE_Pos)
                                | ((uint32_t)400 << SAADC_SAMPLERATE_CC_Pos);
        
        // Configure RESULT Buffer and MAXCNT
        NRF_SAADC->RESULT.PTR = (uint32_t)SAADC_RESULT_BUFFER;
        NRF_SAADC->RESULT.MAXCNT = SAADC_RESULT_BUFFER_SIZE;
        
        // Enable the SAADC IRQ
        NRF_SAADC->EVENTS_END = 0;
        nrf_saadc_int_enable( NRF_SAADC_INT_END ); // Set the END mask to an interrupt
        NVIC_SetPriority( SAADC_IRQn, 3UL );
        NVIC_EnableIRQ( SAADC_IRQn );
        
        nrf_saadc_enable(); // Enable the SAADC
        
        // Calibrate the SAADC by finding its offset
        NRF_SAADC->TASKS_CALIBRATEOFFSET = 1;
        while (NRF_SAADC->EVENTS_CALIBRATEDONE == 0);
        NRF_SAADC->EVENTS_CALIBRATEDONE = 0;
        while (NRF_SAADC->STATUS == (SAADC_STATUS_STATUS_Busy << SAADC_STATUS_STATUS_Pos));
    }
    
    static uint32_t inline collect_adc_data()
    {
        nrf_saadc_disable();
        NRF_SAADC->RESULT.PTR = (uint32_t)(SAADC_RESULT_BUFFER + (BUFFER_INDEX * SAADC_RESULT_BUFFER_SIZE));
        nrf_saadc_enable(); // Enable the SAADC
        
        sample_voltage();
    }
    
    void setup()
    {
        collect_adc_data();
    }

  • Hi Karl,

    I may have accidentally locked this ticket. I responded to your above question in a message 1hr ago.

    Best

  • I ended up figuring out the issue. Because triggering the START register takes a non-negligible amount of time (>1/(16MHz) period), the SAMPLE task was not finished collecting samples by the time the 40Hz TIMER triggered another SAMPLE command, this caused a suspected HardFault in the SAADC, freezing the peripheral and stalling the board. 

    I resolved this issue by repositioning the START register to trigger after the SAMPLE task, incrementing the RESULT.PTR buffer in the rotating 40K sample array. I made sure during initialization the RESULT.PTR buffer (which is double buffered) was set to the beginning of the desired section of RAM. Now the SAMPLE task is completed by each 40Hz cycle and the SAADC sends the desired spacing in its reads. 

Related