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

nrfx_saadc advanced mode oversampling

Hi all,

I am currently developing audio sampling on a custom board using the nrf52832. I'd like to sample at 20khz and am using the nrfxlib driver for this (https://github.com/zephyrproject-rtos/hal_nordic/blob/master/nrfx/drivers/include/nrfx_saadc.h).

I am running the ADC at approx. 20khz now and using the internal ADC timer and am now trying to do oversampling however I could not find any example code on how to do this.

Regular results are around 4000 (raw adc values) but when I add oversampling I get strange outliers (some around 7000 and some around 16000) that do not make any sense to me. Is my implementation correct or do you have any advice on what to do here?

static volatile nrf_saadc_value_t acoustic_buffer[1];

static volatile u32_t sample_counter = 0;
static volatile u32_t interrupts_triggered = 0;


void acoustic_interrupt(nrfx_saadc_evt_t const * p_event)
{
	/* Helper variables */
	static s32_t acoustic_window_ptp_prev = 0;
	static s32_t acoustic_window_ptp_diff = 0;

	s16_t acoustic_sample = 0;
	nrfx_err_t err;
	nrfx_saadc_evt_t saadc_event = *p_event;

	interrupts_triggered++;

	switch (saadc_event.type) {
		case NRFX_SAADC_EVT_DONE: {
			acoustic_sample = (s16_t) *(saadc_event.data.done.p_buffer);
			err = nrfx_saadc_buffer_set((nrf_saadc_value_t*) acoustic_buffer, sizeof(acoustic_buffer));
			if (NRFX_SUCCESS != err) {
				LOG_ERR("IRQ: %x", err);
			}
			/* Throw away garbage values */
			if (acoustic_sample > 5000) {
				return;
			}
			sample_counter++;
			break;
		}
		case NRFX_SAADC_EVT_BUF_REQ:
			return;
		case NRFX_SAADC_EVT_CALIBRATEDONE:
			LOG_INF("SAADC calibrated");
			return;
		case NRFX_SAADC_EVT_LIMIT:
		case NRFX_SAADC_EVT_READY:
		case NRFX_SAADC_EVT_FINISHED:
		default:
			LOG_WRN("Unexpected event received: %x", saadc_event.type);
			return;
	}

	if (acoustic_sample > 4000) {
		LOG_WRN("%i: %i / %i",sample_counter % acoustic_oversample_count, acoustic_sample, acoustic_sample_ac);
	}
}

Parents
  • Sample data that I get:

    3788
    3782
    3785
    3786
    3783
    3782
    3781
    15112
    3782
    3777
    3779
    3783
    3782
    

    With an internal ADC reference and 1/6 Gain, the DC offset is approx 0.81Volt, however, the outlier of 15112 I can't explain as this would mean a peak of 3.5 Volt which is highly unlikely.

  • Hello,

    If you compare your implementation with the saadc from the nRF5 SDK example:

    SDK\examples\peripheral\saadc\

    which uses a double buffer. 

    In fact, I think you should take a look at this example. I don't understand why you use nrf_drv_saadc_buffer_convert() in your NRFX_SAADC_EVT_DONE event. What you should use is nrf_drv_saadc_buffer_convert(). 

    Did you find this implementation somewhere? I am sorry if that is a stupid question. I have not worked much with NCS (the zephyr SDK) yet. Check out the implementation in the saadc example from the nRF5 SDK. 

    Even though your implementation is working, it may be that when you set the single buffer in each interrupts, it may act a bit weird if it doesn't have time to do this on time (perhaps it is blocked by a BLE event some times?). The values that you say that you see sometimes looks like multiples of ~3785

    Try to use the double buffers, and use nrf_drv_saadc_buffer_convert() in the interrupt, and see if it still glitches like this. 

    Best regards,

    Edvin

  • Hi Edvin,

    Thank you for the response I will try with proper double buffers I think here lies the problem.

    Regarding the implementation that I'm using: it is a fork of the official HAL from Nordic here https://github.com/NordicSemiconductor/nrfx/tree/v2.2.0 .

    This HAL does not have the function nrf_drv_saadc_buffer_convert, which is why I'm using the buffer_init inside my IRQ in stead.

    Is this then the correct approach? Or what should I replace nrf_drv_saadc_buffer_convert with based on this HAL?

    Best regards,

    Mattia

  • Hello Mattia,

    I am sorry for the late reply.

    I didn't realize the saadc drivers from NCS were so different from the nRF5 SDK drivers.

    If you look at the advanced mode example implementation found here, you can see that there are some differences in the event handler. Look at how they handle the p_event->data.done.p_buffer[].

    Also, they don't set the buffer in NRFX_SAADC_EVT_DONE, but in the NRFX_SAADC_EVT_BUF_REQ event. 

    They also use the double buffer. sample0 and sample1, both containing SAMPLES_NUMBER (1000) elements. 

    What does your saadc configuration look like?

    nrfx_saadc_adv_config_t config = ?

    I suggest you try to use double buffering, like it is done in the example from the link. You don't need to for-loop in the event handler. It can be done elsewhere to free up the buffer (in case you are working on your buffer, and another ADC_DONE event occurs before you are done. 

    BR,
    Edvin

Reply
  • Hello Mattia,

    I am sorry for the late reply.

    I didn't realize the saadc drivers from NCS were so different from the nRF5 SDK drivers.

    If you look at the advanced mode example implementation found here, you can see that there are some differences in the event handler. Look at how they handle the p_event->data.done.p_buffer[].

    Also, they don't set the buffer in NRFX_SAADC_EVT_DONE, but in the NRFX_SAADC_EVT_BUF_REQ event. 

    They also use the double buffer. sample0 and sample1, both containing SAMPLES_NUMBER (1000) elements. 

    What does your saadc configuration look like?

    nrfx_saadc_adv_config_t config = ?

    I suggest you try to use double buffering, like it is done in the example from the link. You don't need to for-loop in the event handler. It can be done elsewhere to free up the buffer (in case you are working on your buffer, and another ADC_DONE event occurs before you are done. 

    BR,
    Edvin

Children
  • Hi Edvin,

    Many thanks for your answer, I could not find any example code. I have not yet tried this but I am certain this will resolve my question, I have verified your answer.

    Best regards,

    Mattia

  • Hi Edvin,

    I have been using the double buffering implementation quite succesfull, however, I am now trying to run the ISR on a lower priority level since it is conflicting with some other system interrupts (radio).

    Whenever I lower the priority my initial problem is popping up again: weird outliers. Do you have any idea what's the cause?

    Best regards,

    Mattia

  • Hi Edvin,

    I have been using the example https://github.com/NordicSemiconductor/nrfx/wiki/SAADC-Advanced-mode-with-IRQs

     but I am struggling t get it working when I want to stop and continue the conversion at some point.

    my init funcition is the same, with some difference based on my application, 500 samples instead of 1000 and my sampling freq is 10kHz:

    static void adc_init(void) {
        ret_code_t err_code;
        nrfx_saadc_channel_t tuner_ch   = NRFX_SAADC_DEFAULT_CHANNEL_SE(GUITAR_IN_ADC_CH, 0);
        nrfx_saadc_adv_config_t adc_cfg = {
            .oversampling      = NRF_SAADC_OVERSAMPLE_DISABLED, // Disable oversample.
            .burst             = false,                         // Disable burst.
            .internal_timer_cc = 16000000UL/ADC_SAMPLE_RATE,    // Sample at 10kHz.
            .start_on_end      = false,                         // Latch next buffer at END event in interrupt.
        };
        err_code = nrfx_saadc_init(NRFX_SAADC_CONFIG_IRQ_PRIORITY);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_saadc_channels_config(&tuner_ch, 1);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_saadc_advanced_mode_set((1<<0), NRF_SAADC_RESOLUTION_10BIT, &adc_cfg, adc_evt_handler);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_saadc_buffer_set(adc_buf[buf_to_load % 2], SAMPLES_NUMBER);
        APP_ERROR_CHECK(err_code);
        buf_to_load++;
    }

    and whenever I want to get a set of samples i do:

     my event handler looks like this, I modified it a bit for the sake of my application.

    static void adc_evt_handler(nrfx_saadc_evt_t const * p_event) {
        switch (p_event->type) {
            case NRFX_SAADC_EVT_DONE: {
                // Buffer with data is available here: p_event->data.done.p_buffer
                adc_samples = p_event->data.done.size;
                for (uint32_t i = 0; i < adc_samples; i++) {
                    guitar_samples[i] = p_event->data.done.p_buffer[i];
                }
                eventq_add(EVENT_TUNER_ADC_READY, 0, NULL);
            }
            break;
            case NRFX_SAADC_EVT_BUF_REQ:
                nrfx_saadc_buffer_set(adc_buf[buf_to_load % 2], SAMPLES_NUMBER);
                buf_to_load++;
                break;
            default:
                break;
        }
    }

    The first time I get the samples correctly. From what I understand since I configured the adc

    with :

            .start_on_end      = false,                         // Latch next buffer at END event in interrupt.
    

    Once the event :

    NRFX_SAADC_EVT_DONE
    is published ADC block wont convert anymore.
    If call again
    ret_code_t err_code = nrfx_saadc_mode_trigger();
    APP_ERROR_CHECK(err_code);
    error_code is 8.
    What is the proper way of utilizing the adc to start stop samples on demand without having the ADC running non_stop ?
Related