Unexpected Spikes in SAADC Output When Using Noise Shaping on nRF54L15

Hi Nordic team,

I’m working on the nRF54L15 DK with the SAADC peripheral and have encountered an issue when enabling noise shaping.

Here’s the setup:

  • Platform: nRF54L15 DK
  • SAADC resolution: 12-bit
  • Input range: Single-ended input, 0~VDD (3.3V)
  • Gain setting: 1/4
  • Sampling rate: 10k samples/sec
  • Buffer size: 1 sample (streamed continuously using TIMER + GPPI)
  • Mode: Advanced mode with noise shaping enabled (NRF_SAADC->NOISESHAPE = 1)

After converting the signed 12-bit samples to unsigned using 2’s complement mapping, I expected a clean sine wave ranging from 0 to 4095.

However, the resulting waveform contains occasional sharp spikes, which seem to appear periodically or randomly and do not correspond to the analog input signal.

Here is an example of the output waveform plotted in Excel:

To eliminate data conversion issues, I also tried printing the raw signed values directly, and the spikes were already present before conversion.

Here’s what I’ve verified so far:

  • Without noise shaping, the waveform is clean and matches the input.
  • With noise shaping, the overall shape is correct, but spikes appear.
  • Spikes occur even when the input is a low-noise sine wave (from a function generator).

I’m wondering:

  1. Is this expected behavior when using noise shaping on the nRF54L15 SAADC?
  2. Are there any known limitations or special considerations when using noise shaping with TIMER + PPI triggering?
  3. Is there a recommended method to reduce or filter these spikes in software or hardware?

The code i use is attached

Any insights or suggestions would be greatly appreciated.
Thanks in advance!

static void saadc_event_handler(nrfx_saadc_evt_t const * p_event)
{
    nrfx_err_t err;
    switch (p_event->type)
    {
        case NRFX_SAADC_EVT_READY:
        
            nrfx_timer_enable(&timer_instance);
            break;                        
            
        case NRFX_SAADC_EVT_BUF_REQ:
        
            err = nrfx_saadc_buffer_set(saadc_sample_buffer[(saadc_current_buffer++)%2], SAADC_BUFFER_SIZE);
            //err = nrfx_saadc_buffer_set(saadc_sample_buffer[((saadc_current_buffer == 0 )? saadc_current_buffer++ : 0)], SAADC_BUFFER_SIZE);
            if (err != NRFX_SUCCESS) {
                LOG_ERR("nrfx_saadc_buffer_set error: %08x", err);
                return;
            }
            break;

        case NRFX_SAADC_EVT_DONE:

            int64_t average = 1;
            int16_t max = INT16_MIN;
            int16_t min = INT16_MAX;
            int16_t current_value;
            //LOG_INF("SAADC buffer at 0x%x filled with %d samples", (uint32_t)p_event->data.done.p_buffer, p_event->data.done.size);
            

            //printk("ADC Samples:\n");
            for (int i = 0; i < p_event->data.done.size; i++) {
                current_value = ((int16_t *)(p_event->data.done.p_buffer))[i];
                uint16_t unsigned_val = (current_value < 0)?(uint16_t)(current_value + 4096):(uint16_t)(current_value);
                average += current_value;
                if (current_value > max) max = current_value;
                if (current_value < min) min = current_value;
                printk("%d\n", unsigned_val);
            }
            average /= p_event->data.done.size;
            //printk("\nAVG=%d, MIN=%d, MAX=%d\n", (int16_t)average, min, max);

            break;

        default:
            LOG_INF("Unhandled SAADC evt %d", p_event->type);
            break;
    }
}

static void configure_saadc(void)
{
    nrfx_err_t err;

    IRQ_CONNECT(DT_IRQN(DT_NODELABEL(adc)),
                DT_IRQ(DT_NODELABEL(adc), priority),
                nrfx_isr, nrfx_saadc_irq_handler, 0);

    
    err = nrfx_saadc_init(DT_IRQ(DT_NODELABEL(adc), priority));
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_init error: %08x", err);
        return;
    }

    NRF_SAADC->NOISESHAPE = 1;

    #if defined(CONFIG_SOC_NRF54L15)
        channel.channel_config.gain = NRF_SAADC_GAIN1_4;
    #else
        channel.channel_config.gain = NRF_SAADC_GAIN1_4;
    #endif

    err = nrfx_saadc_channels_config(&channel, 1);
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_channels_config error: %08x", err);
        return;
    }

    nrfx_saadc_adv_config_t saadc_adv_config = NRFX_SAADC_DEFAULT_ADV_CONFIG;
    err = nrfx_saadc_advanced_mode_set(BIT(0),
                                        NRF_SAADC_RESOLUTION_12BIT,
                                        &saadc_adv_config,
                                        saadc_event_handler);
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_advanced_mode_set error: %08x", err);
        return;
    }
                                            
    err = nrfx_saadc_buffer_set(saadc_sample_buffer[0], SAADC_BUFFER_SIZE);
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_buffer_set error: %08x", err);
        return;
    }
    err = nrfx_saadc_buffer_set(saadc_sample_buffer[1], SAADC_BUFFER_SIZE);
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_buffer_set error: %08x", err);
        return;
    }

    err = nrfx_saadc_mode_trigger();
    if (err != NRFX_SUCCESS) {
        LOG_ERR("nrfx_saadc_mode_trigger error: %08x", err);
        return;
    }

}

Best regards,
Kyle Anderson

Parents
  • Hello Kyle,

    Before we go into your questions, I would like to clarify one thing first. Are you asking about the few spikes at the slopes of the wave, or the spikes at every trough?

    Do you have the excel files with the raw values and the value after conversion?

    Best regards,

    Hieu

  • I want to know the raw code result when i enable the noise shaping is correct or not.

    And know how to fixed the spike found after the conversion process.

    Thank you.

  • What is the channel configuration you are using?

    What is the input signal? I understand it is from a function generator, but what is its configuration?

    How is the function generator connected to the DK?

    In this code snippet:

    //printk("ADC Samples:\n");
                for (int i = 0; i < p_event->data.done.size; i++) {
                    current_value = ((int16_t *)(p_event->data.done.p_buffer))[i];
                    uint16_t unsigned_val = (current_value < 0)?(uint16_t)(current_value + 4096):(uint16_t)(current_value);
                    average += current_value;
                    if (current_value > max) max = current_value;
                    if (current_value < min) min = current_value;
                    printk("%d\n", unsigned_val);
                }
                average /= p_event->data.done.size;
                //printk("\nAVG=%d, MIN=%d, MAX=%d\n", (int16_t)average, min, max);

    Which value is associated with which CSV file?

    I looked at your raw data but there are a few things that I can't understand.

    First, if the input is a sine wave, then the ADC measurement should be a sine wave too, but... it isn't quite one. Looks like something is flipped?

    How did this data point happen, I don't even understand:

    T Val
    690 -1868
    691 -1772
    692 -2017
    693 -184
    694 1961
    695 1807
    696 1856

    It's these data point like the -184 above that cause the spikes in your graph.

    Secondly, I sorted the raw value from lowest to highest, and plot it

    Notice how there is a complete absence of value around 0?

    Usually, you don't miss value around the average. You would miss value at min and max.

    At least, it looks like there are no spike from the SAADC peripheral. The issue must come from the setup, configurations, and calculations.

    It must be something simple, but for I can't imagine it for some reasons...

    • Channel configuration: P1.11(AIN4), 

      NRFX_SAADC_DEFAULT_CHANNEL_SE(SAADC_INPUT_PIN, 0)

    • Signal: Sine wave 3.0V Vpp, 1.5V Offset 2.44Hz
    • Connection Setting; P1.11 and GND
    • The value associated with the CSV file: unsigned_val

    This is the configuration.

    Thank you for your help.

    Best regards,

    Kyle Anderson

  • Hi Kyle,

    We are still investigating. Could you please try with oversampling?

    Best regards,

    Hieu

Reply Children
Related