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

SAADC fails to sync with PWM via PPI

I'm looking to sync an ADC reading with the PWM output. I'm using PPI for this but fails to make it synchronise properly with the PWM.

I'm getting interrupts and sampled values, indicating something is sampled. But if I toggle a pin when starting the sampling and when its ended and then use an oscilloscope to compare with the PWM output, they are not synchronised.

Code for setting up SAADC and PPI:

// Configure SAADC and interrupts
NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_12bit << SAADC_RESOLUTION_VAL_Pos;
NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos;
sd_nvic_ClearPendingIRQ(SAADC_IRQn);
sd_nvic_SetPriority(SAADC_IRQn, 7U);

// Configure and enable channels (only one shown)
NRF_SAADC->CH[0].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos;
NRF_SAADC->CH[0].PSELP = 7U << SAADC_CH_PSELN_PSELN_Pos;

// Configure EasyDMA
NRF_SAADC->RESULT.PTR = (uint32_t)(buffer);
NRF_SAADC->RESULT.MAXCNT = 3U;

// Enable interrupts, configure PPI and start conversion
sd_nvic_ClearPendingIRQ(SAADC_IRQn);
sd_nvic_EnableIRQ(SAADC_IRQn);
NRF_SAADC->INTENSET = 0xFFFFFFFF;  // Ugly way of enabling all...
NRF_SAADC->TASKS_START = 1U;
NRF_PPI->CH[0].EEP = (uint32_t)(&(NRF_PWM0->EVENTS_SEQSTARTED[kIndicatorPwmSequence]));
NRF_PPI->CH[0].TEP = (uint32_t)(&NRF_SAADC->TASKS_SAMPLE);

// Pin toggle
NRF_P0->OUTSET = (1 << 3);

// Interrupt handling
void SAADC_IRQHandler(void) {
    if (NRF_SAADC->EVENTS_END) {
        NRF_P0->OUTCLR = (1 << 3);
        sd_nvic_DisableIRQ(SAADC_IRQn);
        NRF_SAADC->INTENCLR = 0xFFFFFFFF;  // Ugly, again...
        NRF_SAADC->EVENTS_END = 0U;
    }
}

Code for setting up PWM:

// Starting PWM
const uint32_t enable_mask = PWM_PSEL_OUT_CONNECT_Connected
                                 << PWM_PSEL_OUT_CONNECT_Pos;
NRF_PWM0->PSEL.OUT[RED] = enable_mask | (static_cast<uint32_t>(r_pin) << PWM_PSEL_OUT_PIN_Pos);
NRF_PWM0->PSEL.OUT[GREEN] = enable_mask | (static_cast<uint32_t>(g_pin) << PWM_PSEL_OUT_PIN_Pos);
NRF_PWM0->PSEL.OUT[BLUE] = enable_mask | (static_cast<uint32_t>(b_pin) << PWM_PSEL_OUT_PIN_Pos);
NRF_PWM0->ENABLE = PWM_ENABLE_ENABLE_Enabled << PWM_ENABLE_ENABLE_Pos;
NRF_PWM0->MODE = PWM_MODE_UPDOWN_UpAndDown << PWM_MODE_UPDOWN_Pos;
NRF_PWM0->PRESCALER = PWM_PRESCALER_PRESCALER_DIV_32 << PWM_PRESCALER_PRESCALER_Pos;
NRF_PWM0->COUNTERTOP = kDefaultCounterTop;
NRF_PWM0->LOOP = PWM_LOOP_CNT_Disabled << PWM_LOOP_CNT_Pos;
NRF_PWM0->DECODER = (PWM_DECODER_LOAD_Individual << PWM_DECODER_LOAD_Pos);
NRF_PWM0->SEQ[0].PTR = (uint32_t)(m_periods);
NRF_PWM0->SEQ[kIndicatorPwmSequence].CNT = 4U;
NRF_PWM0->SEQ[kIndicatorPwmSequence].REFRESH = 0U;
NRF_PWM0->SEQ[kIndicatorPwmSequence].ENDDELAY = 0U;
NRF_PWM0->TASKS_SEQSTART[0] = 1U;

// Changing period (example); done occassionally on demand
m_periods[0] = 300;
NRF_PWM0->TASKS_SEQSTART[0] = 1U;

I would expected when looking at the oscilloscope that pin 3 is high when the PWM goes high, but they seem to move independently. The time pin 3 is high is in the range of 1-5ms. Depending on how well the SAADC start synchronises with the PWM, I would have expected something much shorter? There seems to be some logic behind it, in that it can be quite stable at for example 1.5ms and then after 20 samples it jumps to for example 4.5ms.

The PWM is configured with a period of 4ms and I'm trying to sample the ADC every 500ms. The accuracy of the SAADC is not a major concern, we need to be in the 100mV range and we are fine.

I suspect I'm configuring the PPI triggering and/or interrupts wrong somehow?

Related