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

GPIOTE two events for the same pin?

Hello,

For nRF51822 and nRF52832, I'm using GPIOTE, PPI and timers to measure the duration that a pin, which is normally low, goes hi (typically 100-300 milliseconds). I don't want to use GPIOTE_CONFIG_POLARITY_Toggle because I'd have to keep track of which edge in the interrupt routine. So I'd like to use GPIOTE to watch for both GPIOTE_CONFIG_POLARITY_LoToHi and GPIOTE_CONFIG_POLARITY_HiToLo on the same pin.

This post states that using two GPIOTE events on the same pin breaks the one-pin-one-GPIOTE rule. Here's my code:

void gpiote_ppi_init(void) {

    nrfx_err_t err_code = NRF_SUCCESS;

    // Configure GPIOTE channel 0 as event that occurs when pin changes from digital
    // lo to hi.
    NRF_GPIOTE->CONFIG[0] =  (GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos)
                | (SENSOR_PIN << GPIOTE_CONFIG_PSEL_Pos)
                | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
  
    // Configure GPIOTE channel 1 as event that occurs when pin changes from digital
    // hi to lo.
    NRF_GPIOTE->CONFIG[1] =  (GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos)
                | (RH_PHOTOTRANSISTOR_COMPARATOR_PIN << GPIOTE_CONFIG_PSEL_Pos) // using GPIO photoTransistorDigitalPin as input
                | (GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos);
  
    // Interrupt only on hi to low.
    NRF_GPIOTE->INTENCLR = GPIOTE_INTENCLR_IN0_Msk | GPIOTE_INTENCLR_IN2_Msk | GPIOTE_INTENCLR_IN3_Msk;
    NRF_GPIOTE->INTENSET = GPIOTE_INTENSET_IN1_Msk;
  
    // Clear all events.
    NRF_GPIOTE->EVENTS_IN[0] = 0;
    NRF_GPIOTE->EVENTS_IN[1] = 0;
    NRF_GPIOTE->EVENTS_IN[2] = 0;
    NRF_GPIOTE->EVENTS_IN[3] = 0;
  

    // PPI channels
    uint8_t chan_0 = 255;
    uint8_t chan_1 = 255;
    uint8_t chan_2 = 255;
    uint8_t chan_3 = 255;

    err_code = nrfx_ppi_channel_alloc(&chan_0);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_ppi_channel_alloc(&chan_1);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_ppi_channel_alloc(&chan_2);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_ppi_channel_alloc(&chan_3);
    APP_ERROR_CHECK(err_code);

    // Configure PPI channel 0 to increment TIMER1 when TIMER2 overflows.
    err_code = nrfx_ppi_channel_assign(chan_0, nrfx_timer_event_address_get(&m_timer2,NRF_TIMER_EVENT_COMPARE0), nrfx_timer_task_address_get(&m_timer1,NRF_TIMER_TASK_COUNT));
    APP_ERROR_CHECK(err_code);

    // Configure PPI channel 1 to start TIMER2 when GPIOTE channel 0 event occurs.
    err_code = nrfx_ppi_channel_assign(chan_1, (uint32_t)&NRF_GPIOTE->EVENTS_IN[0],nrfx_timer_task_address_get(&m_timer2,NRF_TIMER_TASK_START));
    APP_ERROR_CHECK(err_code);

    // Configure PPI channel 2 to capture TIMER1 when GPIOTE channel 1 event occurs.
    err_code = nrfx_ppi_channel_assign(chan_2, (uint32_t)&NRF_GPIOTE->EVENTS_IN[1],nrfx_timer_task_address_get(&m_timer2,NRF_TIMER_TASK_CAPTURE0));
    APP_ERROR_CHECK(err_code);

    // Configure PPI channel 3 to capture TIMER2 when GPIOTE channel 1 event occurs.
    err_code = nrfx_ppi_channel_assign(chan_3, (uint32_t)&NRF_GPIOTE->EVENTS_IN[1],nrfx_timer_task_address_get(&m_timer1,NRF_TIMER_TASK_CAPTURE0));
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_ppi_channel_enable(chan_0);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_ppi_channel_enable(chan_1);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_ppi_channel_enable(chan_2);
    APP_ERROR_CHECK(err_code);

    err_code = nrfx_ppi_channel_enable(chan_3);
    APP_ERROR_CHECK(err_code);

}

So far I've not noticed any issues and wanted to be assured that we won't experience any problems. Is this safe for production?

When we revise our board next, should I be connecting our sensor to two GPIO pins and use one pin to watch for LoToHi and the other for HiToLo?

Many thanks,

Tim

  • Thank you, Simonr, for checking with Susheel. Appreciate it.

    Tim

  • Hi Tim

    I've talked to Susheel, and he agrees with me. Seeing as this is outside of the GPIOTE specification, we can't guarantee that this will work and we strongly recommend that you revise your board to uphold the one-pin-one-GPIOTE rule. As already stated, you might be good in your current setup, but any changes or untested operations on your device might cause a race condition between the GPIOTEs on this pin, leading to a crash/error.

    Best regards,

    Simon

  • Thanks, Simon, for checking with Susheel about this. I've revised our board to use two pins, so new product will be correct. We have product in customer use (around the world) where only one pin is used. While we have not experienced any issues, still we can push an update out via our iOS app and OTA DFU. I wonder about using one of these two approaches:

    1) Configure GPIOTE channel to watch for LoToHi event and trigger interrupt. In the interrupt routine, reconfigure the channel to watch for HiToLo. Potential issue is the interrupt does not happen before the HiToLo event occurs.

    2) Configure GPIOTE channel to watch for toggle event and use PPI groups to enable/disable PPI channels. Initially PPI group for channel to start timer1 is enabled. Add two additional PPI channels that, in a PPI group, on GPIOTE channel event, disable group for channel to start timer1 and enable channel to stop timer1. See this post.

    Reasonable? Pros/cons to each?

    Also, I've changed calls to  nrfx_ppi_channel_enable() to:

    err_code = sd_ppi_channel_assign(chan_0, nrfx_gpiote_in_event_addr_get(MY_PIN1),nrfx_timer_task_address_get(&m_timer1,NRF_TIMER_TASK_START));

    and now get this compiler warning:

    passing argument 2 of 'sd_ppi_channel_assign' makes pointer from integer without a cast [-Wint-conversion]
    passing argument 3 of 'sd_ppi_channel_assign' makes pointer from integer without a cast [-Wint-conversion]

    Seems to work, but would like to understand and address the warnings.

    Many thanks, Simon.

    Tim

Related