Using GPIOTE to monitor PWM generation on the same output pin

Dear team,

I would like to have a function to measure the pulse width of the pulse that I generated and  wonder if that is possible.

For example, I can generate one pulse of 50us, and have the GPIOTE to detect the rising edge and falling edge.

I replicate this pulse into two pins 4, and 28 and set GPIOTE on these two pins to trigger two time captures via ppis.

I notice that prior to have gpiote detecting the transition, the PWM sets output  pins direction to output, but when I 

init with gpiote then the gpiote switch the pins direction to input.  

The problem I'm observing is, when I have GPIOTE attach to the same pin of the PWM output pin, the last pulse of the PWM

gets elongated, as if the PWM module lets the output floating when it stops, high-z. I can shorten this by changing pullup/pulldown option. If I

have several pulses, only the last pulse gets affected.  nrfx_gpiote_input_configure() changes GPIO.PIN.CNF and GPIO.DIR 

all into inputs for particular pin; I changed back but that didn't affect the waveform. The only way I could undo the

problem is disconnecting the GPIOTE by setting the GPIOTE.CONFIG[x].mode = disabled. I'm wondering why if I generate multiple pulses,

it's always the last pulse that have the problem but not other pulses. Thanks.

I'm using ncs v2.7.0, shell_module example as the starting point running on nrf52840 devkit. I can upload this experiment source code if needed.

Parents Reply
  • We need to generate various pulse widths at various times with various load. One pulse, on then off, can be hardwired with PPI, but we have to generate multiple channels of this.  Of course the system has the BLE running too so just using IRQ() isn't reliable for the timing. The use-case of this is, we need to make sure the generate pulses are within specs so we need to observe/measure what we created. Thanks.

Children
  • But PPI measurements  will have the same error sources as the PWM here, no?

    So how will it help to calibrate it with PPI?

    When you use only PWM, without GPIOTE, I guess you get an error then, right?
    What is that error?

  • TIMER has implicit priority with the tasks, only one of which is detailed in the product specification: "If the START task and the STOP task are triggered at the same time, meaning within the same period of PCLK16M, the STOP task will be prioritized".

    Summary: STOP eclipses both START and CLEAR; CLEAR eclipses START. This is a pain, since PPI could have otherwise used TEP to STOP and FORK to CLEAR. However there is a workaround by simply delaying the CLEAR by a single clock cycle at 16MHz by using the EGU without any firmware. This code performs CAPTURE, STOP and CLEAR with a single 16MHz clock cycle inserted between each step.

    The other thing to remember is that although Interrupts are level-sensitive and must be cleared each use, Events are edge sensitive and do not require clearing via TEP, FORK or indeed firmware. Here is a working solution which measures every PWM pulse; (a simpler solution could measure every other PWM pulse). I spoofed the PWM and didn't bother adding the Capture[0] interrupt which would save the active-high PWM pulse-width somewhere. Tested using debugger, works fine. A single pin can be used as an input source for multiple channels of GPIOTE Events (though not for Tasks).

    // Testing on nRF52833DK
    #define PIN_PULSE_1     13 // PWM Output pin
    #define PIN_PULSE_2     14 // GPIOTE multiple channel input pin
    #define PPI_CHANNEL_A    0
    #define PPI_CHANNEL_B    1
    #define PPI_CHANNEL_D    3
    #define PPI_CHANNEL_E    4
    #define GPIOTE_CHANNEL_A 0 // PWM Falling edge
    #define GPIOTE_CHANNEL_B 1 // PWM Rising edge
    
    // TIMER STOP eclipses both START and CLEAR; CLEAR eclipses START
    static void TestPPI_Pulsewidth(void)
    {
        NRF_P0->OUTCLR = (1 << PIN_PULSE_1);
        NRF_P0->OUTCLR = (1 << PIN_PULSE_2);
        // Configuration                       Direction    Input            Pullup         Drive Level      Sense Level
        // ================================    ==========   ==============   ============   ==============   =============
        NRF_P0->PIN_CNF[PIN_PULSE_1]        = (PIN_OUTPUT | PIN_CONNECT    | PIN_PULLNONE | PIN_DRIVE_H0H1 | PIN_SENSE_HIGH);
        NRF_P0->PIN_CNF[PIN_PULSE_2]        = (PIN_INPUT  | PIN_CONNECT    | PIN_PULLNONE | PIN_DRIVE_H0H1 | PIN_SENSE_LOW);
    
        // Configure input for start on rising edge with capture on falling edge
        NRF_GPIOTE->CONFIG[GPIOTE_CHANNEL_B] = GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos     |
                                               GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos |
                                               PIN_PULSE_2                   << GPIOTE_CONFIG_PSEL_Pos     |
                                               GPIOTE_CONFIG_OUTINIT_Low     << GPIOTE_CONFIG_OUTINIT_Pos;
        NRF_GPIOTE->CONFIG[GPIOTE_CHANNEL_A] = GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos     |
                                               GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos |
                                               PIN_PULSE_2                   << GPIOTE_CONFIG_PSEL_Pos     |
                                               GPIOTE_CONFIG_OUTINIT_Low     << GPIOTE_CONFIG_OUTINIT_Pos;
    
        // Start timer on rising edge of pulse via PPI
        NRF_PPI->CH[PPI_CHANNEL_B].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL_B];
        NRF_PPI->CH[PPI_CHANNEL_B].TEP = (uint32_t)&NRF_TIMER1->TASKS_START;
    
        // Capture count and stop timer on falling edge via PPI
        NRF_PPI->CH[PPI_CHANNEL_A].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL_A]; // Event
        NRF_PPI->CH[PPI_CHANNEL_A].TEP = (uint32_t)&NRF_TIMER1->TASKS_CAPTURE[0];            // Task #1
        NRF_PPI->FORK[PPI_CHANNEL_A].TEP = (uint32_t)&NRF_EGU0->TASKS_TRIGGER[0];            // Task #2
    
        NRF_PPI->CH[PPI_CHANNEL_D].EEP = (uint32_t)&NRF_EGU0->EVENTS_TRIGGERED[0];           // Event
        NRF_PPI->CH[PPI_CHANNEL_D].TEP = (uint32_t)&NRF_TIMER1->TASKS_STOP;                  // Task #1
        NRF_PPI->FORK[PPI_CHANNEL_D].TEP = (uint32_t)&NRF_EGU0->TASKS_TRIGGER[1];            // Task #2
    
        NRF_PPI->CH[PPI_CHANNEL_E].EEP = (uint32_t)&NRF_EGU0->EVENTS_TRIGGERED[1];           // Event
        NRF_PPI->CH[PPI_CHANNEL_E].TEP = (uint32_t)&NRF_TIMER1->TASKS_CLEAR;                 // Task #1
    
        // Enable all 4 PPI channels
        NRF_PPI->CHENSET = (1UL << PPI_CHANNEL_B) | (1UL << PPI_CHANNEL_A) | (1UL << PPI_CHANNEL_D) | (1UL << PPI_CHANNEL_E);
    
        // Use Timer 1 in 32-bit timer mode at 16MHz
        NRF_TIMER1->MODE      = TIMER_MODE_MODE_Timer<<TIMER_MODE_MODE_Pos;
        NRF_TIMER1->BITMODE   = TIMER_BITMODE_BITMODE_32Bit<<TIMER_BITMODE_BITMODE_Pos;
        NRF_TIMER1->PRESCALER = 0UL; // Prescaler = 0
        while (1)
        {
           // Spoof a PWM pulse on PIN_PULSE_1
           NRF_P0->OUTSET = (1 << PIN_PULSE_1);
           for (uint32_t i=0; i<1000000; i++) ;
           NRF_P0->OUTCLR = (1 << PIN_PULSE_1);
        }
    }

Related