generate an interrupt on a nrfx_timer TASKS_CAPTURE

I would like to use GPIOTE, PPI, and TIMER to do the following.

I'm using three GPIO pins. Each pin will get square-wave like signals of varying frequency. On each pin I need to get the interval between rising edges. My thought is to use GPIOTE to detect the rising edge on each pin and use PPI to connect that GPIOTE event to a TIMER capture task. Since I'm using NRFX_TIMER1 there are four channels available so I can assign each pin's GPIOTE interrupt to its own CC channel. After two interrupts from a pin, I'll be able to calculate the interval between rising edges for that pin. I would need to get the CC[x] value at the time of the capture task for each GPIOTE interrupt. Then do something like current - previous to get the number of timer ticks between rising edges.

I can put together code that gets me to the capture task for each pin but I can't figure out how to generate an interrupt so that I can retrieve the timer's tick count at the time of the capture task. I think I need three separate interrupts, one for each pin and the interrupt would have to be generated at the time of the capture task. Can you tell me how to do this?

I have thought about using EVENTS_COMPARE but doesn't that require the CC[x] register to hold a compare to value plus I wouldn't know in advance how to set the compare value.

I've looked into the EGU peripheral but I can't figure out how to assign a separate  EGU to a given capture task. Any ideas or clarification would be greatly appreciated.

#include <stdbool.h>
#include "nrf.h"
#include "nrfx_gpiote.h"
#include "nrfx_ppi.h"
#include "nrfx_timer.h"
#include "nrf_delay.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#define PWM_ONE_PIN      NRF_GPIO_PIN_MAP(0, 29)
#define PWM_TWO_PIN      NRF_GPIO_PIN_MAP(0, 30)
#define PWM_THR_PIN      NRF_GPIO_PIN_MAP(0, 31)

static const     nrfx_timer_t m_timer1 = NRFX_TIMER_INSTANCE(0);
static uint32_t  m_rpm1_count = 0;

nrf_ppi_channel_t ppi_channel_1, ppi_channel_2, ppi_channel_3;


void in_pin_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action)
{

}

void timer_handler_count(nrf_timer_event_t event_type, void * p_context)
{

}

void timer_handler_read_counts(nrf_timer_event_t event_type, void * p_context)
{
    uint32_t count1 = 0, count2 = 0, count3 = 0;
    count1 = nrfx_timer_capture_get(&m_timer1, NRF_TIMER_CC_CHANNEL0);
    count2 = nrfx_timer_capture_get(&m_timer1, NRF_TIMER_CC_CHANNEL1);
    count3 = nrfx_timer_capture_get(&m_timer1, NRF_TIMER_CC_CHANNEL2);

    NRF_LOG_INFO("C1: %d   C2: %d   C3: %d", count1, count2, count3);
}

/**
 * @brief Function for configuring: PWM_ONE_PIN pin for input sensing
 */
static void gpiote_init(void)
{
    ret_code_t err_code;

    err_code = nrfx_gpiote_init();
    APP_ERROR_CHECK(err_code);

    nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
    in_config.pull = NRF_GPIO_PIN_PULLDOWN;

    err_code = nrfx_gpiote_in_init(PWM_ONE_PIN, &in_config, in_pin_handler);
    APP_ERROR_CHECK(err_code);
    nrfx_gpiote_in_event_enable(PWM_ONE_PIN, false);

    err_code = nrfx_gpiote_in_init(PWM_TWO_PIN, &in_config, in_pin_handler);
    APP_ERROR_CHECK(err_code);
    nrfx_gpiote_in_event_enable(PWM_TWO_PIN, false);

    err_code = nrfx_gpiote_in_init(PWM_THR_PIN, &in_config, in_pin_handler);
    APP_ERROR_CHECK(err_code);
    nrfx_gpiote_in_event_enable(PWM_THR_PIN, false);
}

void timer_init()
{
    ret_code_t err_code;

    nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
    timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;

    err_code = nrfx_timer_init(&m_timer1, &timer_cfg, timer_handler_read_counts);
    APP_ERROR_CHECK(err_code);

    nrfx_timer_enable(&m_timer1);
}

void ppi_init()
{
    ret_code_t err_code;

    err_code = nrfx_ppi_channel_alloc(&ppi_channel_1);
    APP_ERROR_CHECK(err_code);
    
    err_code = nrfx_ppi_channel_alloc(&ppi_channel_2);
    APP_ERROR_CHECK(err_code);
    
    err_code = nrfx_ppi_channel_alloc(&ppi_channel_3);
    APP_ERROR_CHECK(err_code);
    
    uint32_t gpiote_evt_addr_1              = nrfx_gpiote_in_event_addr_get(PWM_ONE_PIN);
    uint32_t gpiote_evt_addr_2              = nrfx_gpiote_in_event_addr_get(PWM_TWO_PIN);
    uint32_t gpiote_evt_addr_3              = nrfx_gpiote_in_event_addr_get(PWM_THR_PIN);
    
    uint32_t timer1_capture1_task_addr  = nrfx_timer_task_address_get(&m_timer1, NRF_TIMER_TASK_CAPTURE0);
    uint32_t timer1_capture2_task_addr  = nrfx_timer_task_address_get(&m_timer1, NRF_TIMER_TASK_CAPTURE1);
    uint32_t timer1_capture3_task_addr  = nrfx_timer_task_address_get(&m_timer1, NRF_TIMER_TASK_CAPTURE2);
    uint32_t timer1_clear_task_addr     = nrfx_timer_task_address_get(&m_timer1, NRF_TIMER_TASK_CLEAR);

    err_code = nrfx_ppi_channel_assign(ppi_channel_1,
                                       gpiote_evt_addr_1,
                                       timer1_capture1_task_addr);
    APP_ERROR_CHECK(err_code);
//    err_code = nrfx_ppi_channel_fork_assign(ppi_channel_1,
//                                            <generate an interrupt so I can get and use the value in cc[0]>);
//    APP_ERROR_CHECK(err_code);

    err_code = nrfx_ppi_channel_assign(ppi_channel_2,
                                       gpiote_evt_addr_2,
                                       timer1_capture2_task_addr);
    APP_ERROR_CHECK(err_code);
//    err_code = nrfx_ppi_channel_fork_assign(ppi_channel_2,
//                                            <generate an interrupt so I can get and use the value in cc[1]>);
//    APP_ERROR_CHECK(err_code);

    err_code = nrfx_ppi_channel_assign(ppi_channel_3,
                                       gpiote_evt_addr_3,
                                       timer1_capture3_task_addr);
    APP_ERROR_CHECK(err_code);
//    err_code = nrfx_ppi_channel_fork_assign(ppi_channel_3,
//                                            <generate an interrupt so I can get and use the value in cc{2]>);
//    APP_ERROR_CHECK(err_code);

    err_code = nrfx_ppi_channel_enable(ppi_channel_1);
    APP_ERROR_CHECK(err_code);
    err_code = nrfx_ppi_channel_enable(ppi_channel_2);
    APP_ERROR_CHECK(err_code);
    err_code = nrfx_ppi_channel_enable(ppi_channel_3);
    APP_ERROR_CHECK(err_code);
}

/**
 * @brief Function for application main entry.
 */
int main(void)
{
    uint32_t err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);

    NRF_LOG_DEFAULT_BACKENDS_INIT();
    
    gpiote_init();
    timer_init();
    ppi_init();
    
    NRF_LOG_INFO("Example start\r\n");
    
    while (true)
    {
        while(NRF_LOG_PROCESS() == true);
    }
}

Parents
  • Did not manage to look at this today, due to summer holidays and high inflow of cases. I will try to look at this soon. I appreciate your patience.

  • Hi Susheel, No worries, I've worked out something that works for me using the EGU peripheral. But I do have a couple of questions about what I've done in the attached code.

    1) When I make a nrfx_ppi_channel_fork_assign(ppi_channel_1, call, (like L186), the documentation states that, "Each TEP implements a fork mechanism that enables a second task to be triggered at the same time as the task specified in the TEP is triggered. This second task is configured in the task end point register in the FORK registers groups, e.g. FORK.TEP[0] is associated with PPI channel CH[0]." (nRF52 family->nRF52840->Product Specification->Peripherals->PPI)

    So I'm wondering what is meant by "... at the same time as the..." ? Is there any priority given to either task? Can I be sure that the task specified in the nrfx_ppi_channel_assign(ppi_channel_1, call is done before the the task that is specified in the nrfx_ppi_channel_fork_assign(ppi_channel_1, call ? Or is task execution order undefined ?

    2) I don't understand how the nRF52840 makes the connection between my SWI1_EGU1_IRQHandler, (L.32), and my specifying (uint32_t)&NRF_EGU1->TASKS_TRIGGER[0] in the nrfx_ppi_channel_fork_assign call, (L.188). I can understand what is happening within the egu_init, (L.100), and the use of NVIC. What I don't get is how the connection between SWI1_EGU1_IRQn and SWI1_EGU1_IRQHandler is made? In situations like nrfx_gpiote_in_init, an ISR name is provided, (or not), so that the Nordic peripheral knows what to use as the ISR. With the EGU peripheral it isn't as obvious.

    Although I can see that the specific name, "SWI1_EGU1_IRQHandler" is important since the EGU peripheral doesn't know what to do if it is named something else. I can't find anything about this in the documentation. Can you clarify ?

     
     // This code uses the nRF52840 DK Board, SES v. 5.50b, and SDK v. 16
     // It uses GPIOTE, PPI, TIMER, and EGU.
    
    #include <stdbool.h>
    #include "nrf.h"
    #include "nrfx_gpiote.h"
    #include "nrfx_ppi.h"
    #include "nrfx_timer.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #define PWM_ONE_PIN              NRF_GPIO_PIN_MAP(0, 29)
    #define PWM_TWO_PIN              NRF_GPIO_PIN_MAP(0, 30)
    #define PWM_THR_PIN              NRF_GPIO_PIN_MAP(0, 31)
    
    #define RPM_CONVERSION_AT_2MHZ   120000000u
    
    static const     nrfx_timer_t m_timer1 = NRFX_TIMER_INSTANCE(0);
    // Note the use of timer0 which indicates the project is non-BLE
    // If this timer instance is changed to a BLE friendly app, be sure
    // to make changes to NRF_TIMER?-> calls.
    static uint32_t  m_previous_tick0 = 0, m_interval0 = 0;
    static uint32_t  m_previous_tick1 = 0, m_interval1 = 0;
    static uint32_t  m_previous_tick2 = 0, m_interval2 = 0;
    static uint8_t   m_tick0_done = 0, m_tick1_done = 0, m_tick2_done = 0;
    
    nrf_ppi_channel_t ppi_channel_1, ppi_channel_2, ppi_channel_3;
    
    
    void SWI1_EGU1_IRQHandler(void)
    {
        // The m_intervalx values should be the number of ticks from
        // rising edge to rising edge of the pulses on pin x.
        // There will be a problem when the timer's counter
        // rolls-over. To alleviate this, simplely return if m_previous_tickx
        // is larger than NRF_TIMERx->CC[x]. Of course you also have to
        // up-date m_previous_tickx before returning.
        // My reasoning is that you will get at least one valid interval
        // in 5 trys. On the other hand, all of this processing within the ISR
        // doesn't seem optimal. Maybe just try 5 times and hope for the best?
        if (NRF_EGU1->EVENTS_TRIGGERED[0] != 0)
        {
            NRF_EGU1->EVENTS_TRIGGERED[0] = 0;
            if(m_tick0_done >= 5)
            {
                m_tick0_done++;
                return;
            }
            if ((m_tick0_done == 0) || (NRF_TIMER0->CC[0] < m_previous_tick0))
            {
                m_tick0_done++;
                m_previous_tick0 = NRF_TIMER0->CC[0];
                return;
            }
            m_tick0_done++;
            m_interval0 = (NRF_TIMER0->CC[0] - m_previous_tick0);
            m_previous_tick0 = NRF_TIMER0->CC[0];
        }
    
        if (NRF_EGU1->EVENTS_TRIGGERED[1] != 0)
        {
            NRF_EGU1->EVENTS_TRIGGERED[1] = 0;
            if(m_tick1_done >= 5)
            {
                m_tick1_done++;
                return;
            }
            if ((m_tick1_done == 0) || (NRF_TIMER0->CC[1] < m_previous_tick1))
            {
                m_tick1_done++;
                m_previous_tick1 = NRF_TIMER0->CC[1];
                return;
            }
            m_tick1_done++;
            m_interval1 = NRF_TIMER0->CC[1] - m_previous_tick1;
            m_previous_tick1 = NRF_TIMER0->CC[1];
        }
        if (NRF_EGU1->EVENTS_TRIGGERED[2] != 0)
        {
            NRF_EGU1->EVENTS_TRIGGERED[2] = 0;
            if(m_tick2_done >= 5)
            {
                m_tick2_done++;
                return;
            }
            if ((m_tick2_done == 0) || (NRF_TIMER0->CC[2] < m_previous_tick2))
            {
                m_tick2_done++;
                m_previous_tick2 = NRF_TIMER0->CC[2];
                return;
            }
            m_tick2_done++;
            m_interval2 = NRF_TIMER0->CC[2] - m_previous_tick2;
            m_previous_tick2 = NRF_TIMER0->CC[2];
        }
    }
    
    void egu_init(void)
    {
        NVIC_ClearPendingIRQ(SWI1_EGU1_IRQn);
        NVIC_EnableIRQ(SWI1_EGU1_IRQn);
        NRF_EGU1->INTENSET = (1 << 0) | (1 << 1) | (1 << 2);
    }
    
    
    void egu_uninit(void)
    {
        NVIC_ClearPendingIRQ(SWI1_EGU1_IRQn);
        NVIC_DisableIRQ(SWI1_EGU1_IRQn);
        NRF_EGU1->INTENCLR = (1 << 0) | (1 << 1) | (1 << 2);
    }
    
    
    void timer_handler_read_counts(nrf_timer_event_t event_type, void * p_context)
    {
        // Dummy ISR required for the timer init function.
    }
    
    
    static void gpiote_init(void)
    {
        ret_code_t err_code;
    
        err_code = nrfx_gpiote_init();
        APP_ERROR_CHECK(err_code);
    
        nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
        in_config.pull = NRF_GPIO_PIN_PULLDOWN;
    
        err_code = nrfx_gpiote_in_init(PWM_ONE_PIN, &in_config, NULL);
        APP_ERROR_CHECK(err_code);
        nrfx_gpiote_in_event_enable(PWM_ONE_PIN, false);
    
        err_code = nrfx_gpiote_in_init(PWM_TWO_PIN, &in_config, NULL);
        APP_ERROR_CHECK(err_code);
        nrfx_gpiote_in_event_enable(PWM_TWO_PIN, false);
    
        err_code = nrfx_gpiote_in_init(PWM_THR_PIN, &in_config, NULL);
        APP_ERROR_CHECK(err_code);
        nrfx_gpiote_in_event_enable(PWM_THR_PIN, false);
    }
    
    void timer_init()
    {
        ret_code_t err_code;
    
        nrfx_timer_config_t timer_cfg = NRFX_TIMER_DEFAULT_CONFIG;
        timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32;
        timer_cfg.frequency = NRF_TIMER_FREQ_2MHz;
        // Note use of the prescaler. This is important to keep in mind
        // if trying to calculate a time for the period between rising edges.
    
        err_code = nrfx_timer_init(&m_timer1, &timer_cfg, timer_handler_read_counts);
        APP_ERROR_CHECK(err_code);
    
        nrfx_timer_enable(&m_timer1);
    }
    
    void ppi_init()
    {
        ret_code_t err_code;
    
        err_code = nrfx_ppi_channel_alloc(&ppi_channel_1);
        APP_ERROR_CHECK(err_code);
        
        err_code = nrfx_ppi_channel_alloc(&ppi_channel_2);
        APP_ERROR_CHECK(err_code);
        
        err_code = nrfx_ppi_channel_alloc(&ppi_channel_3);
        APP_ERROR_CHECK(err_code);
        
        uint32_t gpiote_evt_addr_1              = nrfx_gpiote_in_event_addr_get(PWM_ONE_PIN);
        uint32_t gpiote_evt_addr_2              = nrfx_gpiote_in_event_addr_get(PWM_TWO_PIN);
        uint32_t gpiote_evt_addr_3              = nrfx_gpiote_in_event_addr_get(PWM_THR_PIN);
        
        uint32_t timer1_capture1_task_addr  = nrfx_timer_task_address_get(&m_timer1, NRF_TIMER_TASK_CAPTURE0);
        uint32_t timer1_capture2_task_addr  = nrfx_timer_task_address_get(&m_timer1, NRF_TIMER_TASK_CAPTURE1);
        uint32_t timer1_capture3_task_addr  = nrfx_timer_task_address_get(&m_timer1, NRF_TIMER_TASK_CAPTURE2);
    
        err_code = nrfx_ppi_channel_assign(ppi_channel_1,
                                           gpiote_evt_addr_1,
                                           timer1_capture1_task_addr);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_ppi_channel_fork_assign(ppi_channel_1,
                                                // generate an interrupt so I can get and use the value in cc[0]
                                                (uint32_t)&NRF_EGU1->TASKS_TRIGGER[0]);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_ppi_channel_assign(ppi_channel_2,
                                           gpiote_evt_addr_2,
                                           timer1_capture2_task_addr);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_ppi_channel_fork_assign(ppi_channel_2,
                                                // generate an interrupt so I can get and use the value in cc[1]
                                                (uint32_t)&NRF_EGU1->TASKS_TRIGGER[1]);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_ppi_channel_assign(ppi_channel_3,
                                           gpiote_evt_addr_3,
                                           timer1_capture3_task_addr);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_ppi_channel_fork_assign(ppi_channel_3,
                                                // generate an interrupt so I can get and use the value in cc{2]
                                                (uint32_t)&NRF_EGU1->TASKS_TRIGGER[2]);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrfx_ppi_channel_enable(ppi_channel_1);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_ppi_channel_enable(ppi_channel_2);
        APP_ERROR_CHECK(err_code);
        err_code = nrfx_ppi_channel_enable(ppi_channel_3);
        APP_ERROR_CHECK(err_code);
    }
    
    /**
     * @brief Function for application main entry.
     */
    int main(void)
    {
        uint32_t err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);
    
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    
        egu_init();    
        gpiote_init();
        timer_init();
        ppi_init();
        
        NRF_LOG_INFO("Example start\r\n");
        
        while (true)
        {
            while(NRF_LOG_PROCESS() == true);
            //
            while ((m_tick0_done < 5) && (m_tick1_done < 5) && (m_tick2_done < 5));
            // This is bad because if there aren't signals on any of the pins,
            // it becomes an infinite loop. This is for Proof of Concept.
            // A watch-dog  or better logic is required.
            //
            nrfx_timer_pause(&m_timer1);
            // With NRFX_TIMER0 set at a frequency of 2MHz, I can calculate a conversion
            // from ticks to RPM :
            // [1 rotation / m_intervalx (ticks / 0.5uS)] * [1 000 000uS / Sec] * [60 Sec / Min]
            // Yielding a converstion factor of 120 000 000
            NRF_LOG_INFO("C1: %ld   C2: %ld   C3: %ld", \
                         (RPM_CONVERSION_AT_2MHZ / m_interval0), \
                         (RPM_CONVERSION_AT_2MHZ / m_interval1), \
                         (RPM_CONVERSION_AT_2MHZ / m_interval2));
            //NRF_LOG_INFO("C1: %ld   C2: %ld   C3: %ld", m_interval0, m_interval1, m_interval2);
            m_tick0_done = m_tick1_done = m_tick2_done = 0;
            m_interval0 = m_interval1 = m_interval2 = 0;
            nrfx_timer_clear(&m_timer1);
            nrfx_timer_resume(&m_timer1);
        }
    }
    
    

  • matty said:
    So I'm wondering what is meant by "... at the same time as the..." ? Is there any priority given to either task? Can I be sure that the task specified in the nrfx_ppi_channel_assign(ppi_channel_1, call is done before the the task that is specified in the nrfx_ppi_channel_fork_assign(ppi_channel_1, call ? Or is task execution order undefined ?

    The trigger happens in the hardware and there is no priority. Both TEP are triggered at the same HFCLK cycle.

    matty said:
    2) I don't understand how the nRF52840 makes the connection between my SWI1_EGU1_IRQHandler, (L.32), and my specifying (uint32_t)&NRF_EGU1->TASKS_TRIGGER[0] in the nrfx_ppi_channel_fork_assign call, (L.188). I can understand what is happening within the egu_init, (L.100), and the use of NVIC. What I don't get is how the connection between SWI1_EGU1_IRQn and SWI1_EGU1_IRQHandler is made? In situations like nrfx_gpiote_in_init, an ISR name is provided, (or not), so that the Nordic peripheral knows what to use as the ISR. With the EGU peripheral it isn't as obvious.

    If you are using the SWI interrupt directly then it is not directly implicit or deducible in the EGU irq where the event came from. If you are using modules\nrfx\drivers\src\nrfx_swi.c then I think you can make a similar connection with event handler in SWI/EGU similar to what you mentioned in the event handler in gpiote_in_init. I haven't directly used this before, but it is best to use the driver instead of directly implementing your own IRQ handler.

Reply
  • matty said:
    So I'm wondering what is meant by "... at the same time as the..." ? Is there any priority given to either task? Can I be sure that the task specified in the nrfx_ppi_channel_assign(ppi_channel_1, call is done before the the task that is specified in the nrfx_ppi_channel_fork_assign(ppi_channel_1, call ? Or is task execution order undefined ?

    The trigger happens in the hardware and there is no priority. Both TEP are triggered at the same HFCLK cycle.

    matty said:
    2) I don't understand how the nRF52840 makes the connection between my SWI1_EGU1_IRQHandler, (L.32), and my specifying (uint32_t)&NRF_EGU1->TASKS_TRIGGER[0] in the nrfx_ppi_channel_fork_assign call, (L.188). I can understand what is happening within the egu_init, (L.100), and the use of NVIC. What I don't get is how the connection between SWI1_EGU1_IRQn and SWI1_EGU1_IRQHandler is made? In situations like nrfx_gpiote_in_init, an ISR name is provided, (or not), so that the Nordic peripheral knows what to use as the ISR. With the EGU peripheral it isn't as obvious.

    If you are using the SWI interrupt directly then it is not directly implicit or deducible in the EGU irq where the event came from. If you are using modules\nrfx\drivers\src\nrfx_swi.c then I think you can make a similar connection with event handler in SWI/EGU similar to what you mentioned in the event handler in gpiote_in_init. I haven't directly used this before, but it is best to use the driver instead of directly implementing your own IRQ handler.

Children
Related