Using timer capture, gpiote and ppi together to measure pulse widths

Hi,

I am trying to set up a project that continuously measures the pulse width of a signal that may or may not be present at a pin.

I want to use a Timer in capture mode that automatically starts when the edge signal changes, and generates an interrupt when the next edge is detected so it can store the value in a running array. Then after the interrupt the process starts again.

(I am using SDK 17.0.2)

Timer->GPIOTE->PPI

I am struggling a little with the config, and am wondering if this is the correct approach or not:

#include "nrfx_timer.h"
#include "nrfx_gpiote.h"
#include "nrfx_ppi.h"

#define IR_SAMPLE_SIZE  32

#define IR_GPIOTE_CHANNEL 0

static volatile uint32_t IR_PulseTimes[IR_SAMPLE_SIZE];
static volatile uint16_t IR_PulseTimesIndex;

#define TIMER_INST_IDX 4 // Or the desired timer instance index
static nrfx_timer_t timer_inst = NRFX_TIMER_INSTANCE(TIMER_INST_IDX);

static nrf_ppi_channel_t ppi_channel;

static void timer_handler(nrf_timer_event_t event_type, void *p_context) 
{
    (void)p_context; // Unused parameter

    if (event_type == NRF_TIMER_TASK_CAPTURE0) 
    {
        IR_PulseTimes[IR_PulseTimesIndex] = nrfx_timer_capture(&timer_inst, NRF_TIMER_CC_CHANNEL0);
        if (++IR_PulseTimesIndex >= IR_SAMPLE_SIZE)
        {
            IR_PulseTimesIndex = 0;
        }
    }
}

void IR_Init(void)
{
    memset((uint8_t*)IR_PulseTimes, 0x00, sizeof(IR_PulseTimes));
    IR_PulseTimesIndex = 0;

    nrfx_gpiote_in_config_t config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(false);
    config.pull = NRF_GPIO_PIN_NOPULL;
    APP_ERROR_CHECK(nrfx_gpiote_in_init(IO_IR, &config, NULL));

    nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
    timer_cfg.frequency = NRF_TIMER_FREQ_16MHz; 
    timer_cfg.mode = NRF_TIMER_MODE_TIMER;
    timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
    nrfx_timer_init(&timer_inst, &timer_cfg, timer_handler);    
    
    // Find an available PPI channel
    nrfx_ppi_channel_alloc(&ppi_channel);
    
    // Configure PPI channel to connect GPIOTE event to TIMER capture task
    nrfx_ppi_channel_assign(ppi_channel,
                nrfx_gpiote_in_event_addr_get(IO_IR),
                nrfx_timer_capture_task_address_get(&timer_inst, NRF_TIMER_CC_CHANNEL0));
    
    
}

void IR_Enable(bool Enabled)
{
    if (Enabled)
    {
        memset((uint8_t*)IR_PulseTimes, 0x00, sizeof(IR_PulseTimes));
        IR_PulseTimesIndex = 0;
        nrfx_timer_compare_int_enable(&timer_inst, NRF_TIMER_CC_CHANNEL0);
        nrfx_gpiote_in_event_enable(IO_IR, true);
        nrfx_timer_enable(&timer_inst);
        nrf_ppi_channel_enable(ppi_channel);
    }
    else
    {
        nrf_ppi_channel_disable(ppi_channel);
        nrfx_timer_disable(&timer_inst);
        nrfx_gpiote_in_event_enable(IO_IR, false);
        nrfx_timer_compare_int_disable(&timer_inst, NRF_TIMER_CC_CHANNEL0);
    }
}

I would appreciate any pointers or examples to get this working, i cannot seem to get the interrupt firing.

Many thanks

Billy

  • The use of interrupts works fine but may lead to issues if higher-priority interrupt mess about, typically with BLE and Softdevice enabled. There are several posts on the devzone discussing various pulsewidth measurements.

    Here is my test code which may or may not be useful; I use EGU in addition to PPI. This code is designed to simply run under the debugger where the results can be display by viewing the peripheral registers. Connect PIN_PULSE_1 to PIN_PULSE_2 to test without an external signal.

    // 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).
    #define PIN_PULSE_1     13
    #define PIN_PULSE_2     14
    #define PPI_CHANNEL_A    0
    #define PPI_CHANNEL_B    1
    #define PPI_CHANNEL_C    2
    #define PPI_CHANNEL_D    3
    #define PPI_CHANNEL_E    4
    #define GPIOTE_CHANNEL_A 0
    #define GPIOTE_CHANNEL_B 1
    #define GPIOTE_CHANNEL_C 2
    
    // 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
    // STOP eclipses 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;
        // Configure input for capture both rising and falling edges
        NRF_GPIOTE->CONFIG[GPIOTE_CHANNEL_C] = GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos     |
                                               GPIOTE_CONFIG_POLARITY_Toggle << 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_GPIOTE->EVENTS_PORT = 0;
        NRF_PPI->CHENSET = (1UL << PPI_CHANNEL_B) | (1UL << PPI_CHANNEL_A) | (1UL << PPI_CHANNEL_D) | (1UL << PPI_CHANNEL_E);
    
        NRF_PPI->CH[PPI_CHANNEL_C].EEP = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_CHANNEL_C]; // Event
        NRF_PPI->CH[PPI_CHANNEL_C].TEP = (uint32_t)&NRF_TIMER1->TASKS_CAPTURE[2];            // Task #1
        NRF_PPI->CHENSET = (1UL << PPI_CHANNEL_C);                                           // Enable PPI channel
    
        // Use Timer 1 in 32-bit timer mode at 16MHz
        NRF_TIMER1->MODE      = TIMER_MODE_MODE_Timer<<TIMER_MODE_MODE_Pos; // Timer mode
        NRF_TIMER1->BITMODE   = TIMER_BITMODE_BITMODE_32Bit<<TIMER_BITMODE_BITMODE_Pos; // 32 bits resolution
        NRF_TIMER1->PRESCALER = 0UL; // Prescaler = 0
    //  NRF_TIMER1->SHORTS    = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos
    //                        | TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_STOP_Pos;
        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);
           NRF_TIMER1->TASKS_CAPTURE[1] = 1;
        }
    }
    

  • Also in addition to my code above I wrote a handler for the SENT protocol, which is similar to IrDA in some ways; might be worth perusing.

    sent-protocol-based-sensor-configuration-with-nrf-boards

  • Hi Billy, 
    My suggestion is to try breaking down the configuration and go step by step. 

    You should try to verify: 

    1 - You can trigger the GPIOTE event and get an interrupt

    2 - The GPIOTE event is connected to PPI and that trigger a capture task. 

    3 - Check if the CAPTURE task can generate the interrupt to store the value.

Related