SAADC will not start

Hello,

I am trying to use the SAADC peripheral on my nrf5340dk without using the zephyr API; I am writing my program bare-metal. My goal is to set up the ADC in continuous mode (using the internal timer) with a constant sample rate, and I want an interrupt to be triggered after each read. Here is the code that I am running:

#include "zephyr/sys/printk.h"
#include "nrf.h"

#define ADC NRF_SAADC_S
#define SAMPLE_RATE (10000) // Hz
#define ADC_CLOCK_SPEED (16000000) // Hz

static uint16_t adc_data_buf[1]; // Single sample buffer

static void init_saadc();
static void start_saadc();

int main(void)
{
    init_saadc();
    start_saadc();

    printk("Ready\n");

    while (1); // Infinite loop
    return 0;
}

static void init_saadc()
{
    // Set resolution to 10-bit
    ADC->RESOLUTION = SAADC_RESOLUTION_VAL_10bit;

    // Configure channel 0 for A0 (AIN0)
    ADC->CH[0].PSELP = SAADC_CH_PSELP_PSELP_AnalogInput0;
    ADC->CH[0].CONFIG =
        (SAADC_CH_CONFIG_GAIN_Gain1_6 << SAADC_CH_CONFIG_GAIN_Pos) | // 1/6 gain
        (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) | // internal reference (0.6V)
        (SAADC_CH_CONFIG_MODE_SE << SAADC_CH_CONFIG_MODE_Pos) | // single-ended mode
        (SAADC_CH_CONFIG_TACQ_3us << SAADC_CH_CONFIG_TACQ_Pos); // 3us acquisition time

    // Set result buffer
    ADC->RESULT.PTR = (uint32_t)(adc_data_buf);
    ADC->RESULT.MAXCNT = 1;

    // Configure sampling rate
    uint32_t cc = ADC_CLOCK_SPEED / SAMPLE_RATE;
    ADC->SAMPLERATE = (cc << SAADC_SAMPLERATE_CC_Pos) |
                       SAADC_SAMPLERATE_MODE_Timers;

    // Enable interrupts for END event
	__enable_irq();
    ADC->INTENSET = SAADC_INTENSET_END_Set;
    NVIC_EnableIRQ(SAADC_IRQn);
    NVIC_ClearPendingIRQ(SAADC_IRQn);
    NVIC_SetPriority(SAADC_IRQn, 1);

    // Enable SAADC
    ADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled;
    printk("SAADC initialized\n");
}

static void start_saadc()
{
    ADC->TASKS_START = 1;
	while(!ADC->EVENTS_STARTED);
	ADC->EVENTS_STARTED = 0;

	ADC->TASKS_SAMPLE = 1;

	printk("SAADC started\n");
}


void SAADC_IRQHandler()
{
    if (ADC->EVENTS_END)
    {
        ADC->EVENTS_END = 0; // Clear the END event
        printk("ADC Value: %u\n", adc_data_buf[0]);
    }
}

I have printk statements throughout the initialization process so that I can see what is being executed. When I run this, I see "SAADC initialized", but I do not see "SAADC started" or "ready"; to me, this indicates that execution is stuck in the infinite loop in my start_saadc() function:

while(!ADC->EVENTS_STARTED);
ADC->EVENTS_STARTED = 0;

when I remove these two lines, all of the printk statements are successfully printed to the console, but my interrupt is never triggered, as I do not see it printed to the console.

The only line I have in my prj.conf file is CONFIG_ADC=y, and I am not sure if I need anything else in prj.conf to make this work.

I am new to both ADC and interrupts on nordic, so any help and guidance to solve this issue will be greatly appreciated!

-Taylor

Parents
  • Bug here:

        ADC->INTENSET = SAADC_INTENSET_END_Set;

    Fix:

        ADC->INTENSET = SAADC_INTENSET_END_Set << SAADC_INTENSET_END_Pos;

    Some stuff like events are in bit 0, other stuff ain't ..

  • Thank you,

    That solved the issue of getting out of the infinite loop: while(!ADC->EVENTS_STARTED), but the interrupt is still not being triggered at all.

    Is there anything that I am doing wrong in terms of setting up the interrupt?

    I modified the IRQ to the code below, and I never see "Interrupt Called" printed to the console, so I know that the IRQ is never executed.

    void SAADC_IRQHandler()
    {
        printk("Interrupt Called\n");
        if(ADC->EVENTS_END)
        {
            ADC->EVENTS_END = 0;
            printk("ADC Value: %u\n", adc_data_buf[0]);
        }
        NVIC_ClearPendingIRQ(SAADC_IRQn);
    }

    Best,

    Taylor

  • Don't clear IRQn in interrupt, but that's another issue. Depending on compiler, interrupt vector handler must be an exact name match to be called as a vector instead of the default handler. Only END interrupt is enabled, so don't waste time testing it. Use a DSB instruction to avoid problems later on.

    void SAADC_IRQHandler(void)
    {
        ADC->EVENTS_END = 0;
        // In case the code is later modified with no following instructions prevent a spurious extra interrupt
        __DSB();
        printk("ADC Value: %u\n", adc_data_buf[0]);
    }
    

    Use volatile keyword:

    static volatile uint16_t adc_data_buf[1]; // Single sample buffer

Reply
  • Don't clear IRQn in interrupt, but that's another issue. Depending on compiler, interrupt vector handler must be an exact name match to be called as a vector instead of the default handler. Only END interrupt is enabled, so don't waste time testing it. Use a DSB instruction to avoid problems later on.

    void SAADC_IRQHandler(void)
    {
        ADC->EVENTS_END = 0;
        // In case the code is later modified with no following instructions prevent a spurious extra interrupt
        __DSB();
        printk("ADC Value: %u\n", adc_data_buf[0]);
    }
    

    Use volatile keyword:

    static volatile uint16_t adc_data_buf[1]; // Single sample buffer

Children
  • Thankyou,

    I am still having trouble with interrupts though. I noticed that the specific line that is causing problems is 

        NVIC_EnableIRQ(SAADC_IRQn);
    

    whenever this line is included in my code, my board will constantly reboot itself over and over. Getting rid of this line causes the board to not reboot itself, but the interrupts are still never triggered.

    From what I have seen from other information online, since I am using nRF Connect for VS Code, Zephyr is automatically included in the build, and that interrupts need to be configured differently when using Zephyr.

    Do you have any information on how to configure the interrupts for a build with Zephyr?

    Best,

    Taylor

  • You can't set a high priority when using Zephyr or any other OS. Change the 1 to 6 and see if that helps. There are lots of examples for interrupts in Zephyr.

    Change:
      NVIC_SetPriority(SAADC_IRQn, 1);
    to:
      NVIC_SetPriority(SAADC_IRQn, 6);
    

Related