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

NRF51422 - GPIOTE PORT events stop being generated - why?

I am encountering a problem with GPIOTE PORT events on the NRF51422 in a custom design. The chip occasionally seems to be able to get into a state in which the registers are set up correctly for a PORT event and interrupt to be generated, but despite this no event occurs.

I have a pin which is connected to a push switch and external pullup resistor, which normally triggers various visible actions on the system. The symptom of the problem is that the system stops responding to the button.

If I hold the switch down when the problem is occuring and then halt the CPU, I can see in the debugger that:

  • PIN_CNF is set to input, connected, no pull, standard drive, sense low.
  • The IN register for the pin shows the input level as low.
  • The GPIOTE INTENSET register shows the PORT enable bit set.
  • EVENTS_PORT is zero.

If I set a breakpoint at the GPIOTE interrupt, then:

  • With the system working normally, the breakpoint is hit when the button is pressed and shows the registers in the state above, except that EVENTS_PORT reads 1, indicating the event has occured.

  • When the problem is occuring, the breakpoint is not hit, and if I halt the CPU I see the registers in the same state as above with EVENTS_PORT reading zero.

I can reproduce this problem on two copies of the board.

What could be preventing this event from being generated?

Parents
  • Ole Morten wrote:

    I agree on you line of thought, but when I'm testing this, I'm not able to generate a lockup, only losing the second interrupt. If I add a delay a in the IRQ handler between reading IN and toggling SENSE, and then toggle two pins with an interval b < a, I see that most often only the first toggle is detected.

    Have you been able to consistently get this to fail? If so, could you share the project you've been working with? Also, did you get anywhere with a workaround?

    I have implemented the fix I proposed, see patch below. Before this fix I could always reproduce the problem within a minute or two of trying; since applying it I have not seen it at all. At this point I am quite confident that the diagnosis and solution is correct.

    I cannot share the code as it is for a proprietary project, but there is nothing complex to it. There are two app_gpiote users, one monitoring two buttons and the other monitoring several status signals from other hardware on the board.

    As to why you have not been able to reproduce the issue, note that in the example I described, the lockup is not necessarily permanent - after step 5, new events on pin A are missed, but if pin B later goes low again, DETECT will be cleared and everything starts working again.

    If B remains high however the situation is permanent. And consider in particular the case where the system must first detect an event on pin A, to trigger the action which causes pin B to go low - you have a complete deadlock. This is the sort of situation I believe I was hitting.

    (Edit: Included wrong version of patch in answer - correct version now below.)

    
    diff --git a/app_gpiote.c b/app_gpiote.c
    index 05189b8..9bc1b9b 100644
    --- a/app_gpiote.c
    +++ b/app_gpiote.c
    @@ -41,8 +41,9 @@ static gpiote_user_t * mp_users = NULL;               /**< Array of GPIOTE users
      *
      * @param[in]   p_user   Pointer to user structure.
      * @param[in]   pins     Bitmask specifying for which pins the sense level is to be toggled.
    + * @param[in]   sense    Array of SENSE field settings to be updated.
      */
    -static void sense_level_toggle(gpiote_user_t * p_user, uint32_t pins)
    +static void sense_level_toggle(gpiote_user_t * p_user, uint32_t pins, uint32_t sense[])
     {
         uint32_t pin_no;
         
    @@ -52,22 +53,17 @@ static void sense_level_toggle(gpiote_user_t * p_user, uint32_t pins)
             
             if ((pins & pin_mask) != 0)
             {
    -            uint32_t sense;
    -
                 // Invert sensing.
                 if ((p_user->sense_high_pins & pin_mask) == 0)
                 {
    -                sense = GPIO_PIN_CNF_SENSE_High << GPIO_PIN_CNF_SENSE_Pos;
    +                sense[pin_no] = GPIO_PIN_CNF_SENSE_High << GPIO_PIN_CNF_SENSE_Pos;
                     p_user->sense_high_pins |= pin_mask;
                 }
                 else
                 {
    -                sense = GPIO_PIN_CNF_SENSE_Low << GPIO_PIN_CNF_SENSE_Pos;
    +                sense[pin_no] = GPIO_PIN_CNF_SENSE_Low << GPIO_PIN_CNF_SENSE_Pos;
                     p_user->sense_high_pins &= ~pin_mask;
                 }
    -
    -            NRF_GPIO->PIN_CNF[pin_no] &= ~GPIO_PIN_CNF_SENSE_Msk;
    -            NRF_GPIO->PIN_CNF[pin_no] |= sense;
             }
         }
     }
    @@ -79,10 +75,18 @@ void GPIOTE_IRQHandler(void)
     {
         uint8_t  i;
         uint32_t pins_state = NRF_GPIO->IN;
    +    uint32_t sense[NO_OF_PINS];
         
         // Clear event.
         NRF_GPIOTE->EVENTS_PORT = 0;
         
    +    // Backup and then disable all SENSE settings to ensure that DETECT signal is deasserted.
    +    for (i = 0; i < NO_OF_PINS; i++)
    +    {
    +        sense[i] = NRF_GPIO->PIN_CNF[i] & GPIO_PIN_CNF_SENSE_Msk;
    +        NRF_GPIO->PIN_CNF[i] &= ~GPIO_PIN_CNF_SENSE_Msk;
    +    }
    +
         // Check all users.
         for (i = 0; i < m_user_count; i++)
         {
    @@ -99,7 +103,7 @@ void GPIOTE_IRQHandler(void)
                 transition_pins = (pins_state ^ ~p_user->sense_high_pins) & p_user->pins_mask;
     
                 // Toggle SENSE level for all pins that have changed state.
    -            sense_level_toggle(p_user, transition_pins);
    +            sense_level_toggle(p_user, transition_pins, sense);
                 
                 // Call user event handler if an event has occurred.
                 event_high_to_low = (~pins_state & p_user->pins_high_to_low_mask) & transition_pins;
    @@ -111,6 +115,12 @@ void GPIOTE_IRQHandler(void)
                 }
             }
         }
    +
    +    // Apply new SENSE settings.
    +    for (i = 0; i < NO_OF_PINS; i++)
    +    {
    +        NRF_GPIO->PIN_CNF[i] |= sense[i];
    +    }
     }
    
    
Reply
  • Ole Morten wrote:

    I agree on you line of thought, but when I'm testing this, I'm not able to generate a lockup, only losing the second interrupt. If I add a delay a in the IRQ handler between reading IN and toggling SENSE, and then toggle two pins with an interval b < a, I see that most often only the first toggle is detected.

    Have you been able to consistently get this to fail? If so, could you share the project you've been working with? Also, did you get anywhere with a workaround?

    I have implemented the fix I proposed, see patch below. Before this fix I could always reproduce the problem within a minute or two of trying; since applying it I have not seen it at all. At this point I am quite confident that the diagnosis and solution is correct.

    I cannot share the code as it is for a proprietary project, but there is nothing complex to it. There are two app_gpiote users, one monitoring two buttons and the other monitoring several status signals from other hardware on the board.

    As to why you have not been able to reproduce the issue, note that in the example I described, the lockup is not necessarily permanent - after step 5, new events on pin A are missed, but if pin B later goes low again, DETECT will be cleared and everything starts working again.

    If B remains high however the situation is permanent. And consider in particular the case where the system must first detect an event on pin A, to trigger the action which causes pin B to go low - you have a complete deadlock. This is the sort of situation I believe I was hitting.

    (Edit: Included wrong version of patch in answer - correct version now below.)

    
    diff --git a/app_gpiote.c b/app_gpiote.c
    index 05189b8..9bc1b9b 100644
    --- a/app_gpiote.c
    +++ b/app_gpiote.c
    @@ -41,8 +41,9 @@ static gpiote_user_t * mp_users = NULL;               /**< Array of GPIOTE users
      *
      * @param[in]   p_user   Pointer to user structure.
      * @param[in]   pins     Bitmask specifying for which pins the sense level is to be toggled.
    + * @param[in]   sense    Array of SENSE field settings to be updated.
      */
    -static void sense_level_toggle(gpiote_user_t * p_user, uint32_t pins)
    +static void sense_level_toggle(gpiote_user_t * p_user, uint32_t pins, uint32_t sense[])
     {
         uint32_t pin_no;
         
    @@ -52,22 +53,17 @@ static void sense_level_toggle(gpiote_user_t * p_user, uint32_t pins)
             
             if ((pins & pin_mask) != 0)
             {
    -            uint32_t sense;
    -
                 // Invert sensing.
                 if ((p_user->sense_high_pins & pin_mask) == 0)
                 {
    -                sense = GPIO_PIN_CNF_SENSE_High << GPIO_PIN_CNF_SENSE_Pos;
    +                sense[pin_no] = GPIO_PIN_CNF_SENSE_High << GPIO_PIN_CNF_SENSE_Pos;
                     p_user->sense_high_pins |= pin_mask;
                 }
                 else
                 {
    -                sense = GPIO_PIN_CNF_SENSE_Low << GPIO_PIN_CNF_SENSE_Pos;
    +                sense[pin_no] = GPIO_PIN_CNF_SENSE_Low << GPIO_PIN_CNF_SENSE_Pos;
                     p_user->sense_high_pins &= ~pin_mask;
                 }
    -
    -            NRF_GPIO->PIN_CNF[pin_no] &= ~GPIO_PIN_CNF_SENSE_Msk;
    -            NRF_GPIO->PIN_CNF[pin_no] |= sense;
             }
         }
     }
    @@ -79,10 +75,18 @@ void GPIOTE_IRQHandler(void)
     {
         uint8_t  i;
         uint32_t pins_state = NRF_GPIO->IN;
    +    uint32_t sense[NO_OF_PINS];
         
         // Clear event.
         NRF_GPIOTE->EVENTS_PORT = 0;
         
    +    // Backup and then disable all SENSE settings to ensure that DETECT signal is deasserted.
    +    for (i = 0; i < NO_OF_PINS; i++)
    +    {
    +        sense[i] = NRF_GPIO->PIN_CNF[i] & GPIO_PIN_CNF_SENSE_Msk;
    +        NRF_GPIO->PIN_CNF[i] &= ~GPIO_PIN_CNF_SENSE_Msk;
    +    }
    +
         // Check all users.
         for (i = 0; i < m_user_count; i++)
         {
    @@ -99,7 +103,7 @@ void GPIOTE_IRQHandler(void)
                 transition_pins = (pins_state ^ ~p_user->sense_high_pins) & p_user->pins_mask;
     
                 // Toggle SENSE level for all pins that have changed state.
    -            sense_level_toggle(p_user, transition_pins);
    +            sense_level_toggle(p_user, transition_pins, sense);
                 
                 // Call user event handler if an event has occurred.
                 event_high_to_low = (~pins_state & p_user->pins_high_to_low_mask) & transition_pins;
    @@ -111,6 +115,12 @@ void GPIOTE_IRQHandler(void)
                 }
             }
         }
    +
    +    // Apply new SENSE settings.
    +    for (i = 0; i < NO_OF_PINS; i++)
    +    {
    +        NRF_GPIO->PIN_CNF[i] |= sense[i];
    +    }
     }
    
    
Children
No Data
Related