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?

Parents
  • Hello,

    What is your pin 3 connected to? Is it RED GREEN or BLUE pin of yours, or something else?

    To be honest, I am having a little trouble understanding exactly what you want to do. Are you trying to create a feedback loop to measure the average on the PWM pin, or are you using the PWM signal as a timer to trigger the SAADC readings?

    BR,

    Edvin

  • Hi,

    Pin 3 is not connected to anything. I'm just using it to verify that the ADC is synced with the PWM properly. I.e. I sample the PWM output and pin 3 output with the oscilloscope to see that the ADC sampling occurs when the PWM signal is high.

    Yeah, a little background info would probably be in place. But the short answer is: using the PWM signal as a timer to trigger the SAADC readings.

    The PWM is controlling an RGB LED (sinking it through transistors). To detect that the LED is working properly, we want to measure the voltage drop over the diodes, using the SAADC.

    For example, assume that the LED's are powered with 5V and have a voltage drop of 3V. A normally functioning diode, when on, would have a voltage of 5V-3V = 2V at its cathode. A diode that has short-circuited would have 5V and one that has open-circuited would have 0V (because we are sinking it to GND). If it is not on, the voltage would be >3.3V, even when it's working. (We are not really sure of this, but its a theory anyway)

    So, in order to reliably determine if the diode is working, we need to sync the SAADC reading with when the LED is ON, thus with the PWM.

  • Thanks for the thorough reply.

    I set the pin when I want to start the ADC conversion, and then clear it in the event handler. The issue is not that I'm not getting samples or that the event handler is called. I get a DONE event.

    The issue is that the sequence doesn't sync with the PWM. What I want is that if I want to acquire a sample, the ADC shall wait until the beginning of the next PWM cycle. Assuming the the acquisition time is t_acq and the PWM period is T, I would expect an interrupt at T+t_acq, but using the oscilloscope, I can see that these are not at all related.

    So I'm thinking that I have misinterpreted when PWM events are generated. I basically want an interrupt every T seconds, regardless if I'm changing the PWM duty cycles or not.

    If this is not clear I can write a sample program that explains the use case better?

  • The point of the peripherals on the nRF is that they should do as much as possible without having to interrupt or wait for the CPU. Especially with the PWM driver. If you generate a PWM signal to control an LED or an electric motor, you certainly don't want to get an application interrupt every 2ms to toggle the pin. So after you start the PWM, it will not generate an event on every PWM period. 

    I still don't understand whether you generate the PWM on the nRF to trigger samples, or if you ultimately want it to trigger on a PWM signal that is generated externally.

     

    obbe said:
    I basically want an interrupt every T seconds

     Sounds like a timer, and not PWM is the way to go.

    BR,

    Edvin

  • Ok, so I guess it all boils down to this question: Is it possible to generate an event that is synchronised with the start of a PWM period?

  • Yes. But then it will be synchronized with a pin interrupt, either going HiToLo or LoToHi.

    void pin_init(uint32_t pinselect)
    {  
        NRF_GPIOTE->CONFIG[CHANNEL_NUMBER] = GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos | 
                                             GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos | 
                                             pinselect << GPIOTE_CONFIG_PSEL_Pos | 
                                             GPIOTE_CONFIG_OUTINIT_High << GPIOTE_CONFIG_OUTINIT_Pos; // no effect when in event mode.
    
        NRF_PPI->CH[PIN_PPI_CH_A].EEP   = (uint32_t)&NRF_GPIOTE->EVENTS_IN[CHANNEL_NUMBER];
        NRF_PPI->CH[PIN_PPI_CH_A].TEP   = (uint32_t)&NRF_SAADC->TASKS_SAMPLE;
        
        NRF_PPI->CHENSET                = (1 << PIN_PPI_CH_A);
    }

    What you really are looking for is just the state change of the pin, and not the PWM events, right?

    BR,

    Edvin

Reply Children
No Data
Related