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?

  • 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];
    +    }
     }
    
    
  • Hi Nordic team ! This issue is showstopper for me too. I have been successfully using the following IRQ handler for long time.

    void GPIOTE_IRQHandler(void) {
    uint8_t  i,j;
    uint32_t pins_state = 0;
    uint32_t sense_set = 0;
    
    // Clear event.
    NRF_GPIOTE->EVENTS_PORT = 0;
    
    do{
    	pins_state = NRF_GPIO->IN;
    	// Check all users.
    	for (i = 0; i < m_user_count; i++)
    	{
    		gpiote_user_t * p_user = &mp_users[i];
    
    		// Check if user is enabled.
    		if (((1 << i) & m_enabled_users_mask) != 0)
    		{
    			uint32_t transition_pins;
    			uint32_t event_low_to_high;
    			uint32_t event_high_to_low;
    
    			// Find set of pins on which there has been a transition.
    			transition_pins = (pins_state ^ p_user->last_state_pins) & p_user->pins_mask;
    			p_user->last_state_pins = pins_state;
    
    			// if there is no changes for this user, look for next user
    			if( !transition_pins )
    				continue;
    
    			// Mark the IO pins which sense needs to be set again
    			sense_set |= p_user->pins_mask;
    			pins_sense_disable( i );
    
    			// Toggle SENSE level for all pins that have changed state.
    			// not needed anymore, as the sense will be set according to the pins_state
    			//sense_level_toggle(p_user, transition_pins);
    
    			// Call user event handler if an event has occurred.
    			event_high_to_low = (~pins_state & p_user->pins_high_to_low_mask) & transition_pins;
    			event_low_to_high = (pins_state & p_user->pins_low_to_high_mask) & transition_pins;
    
    			p_user->event_handler(event_low_to_high, event_high_to_low);
    		}
    	}
    }while( pins_state ^ NRF_GPIO->IN ); // loop until some pins have been changed in between
    
    // re-assign the sense states to the pins
    pins_sense_set( pins_state, sense_set );
    

    }

  • Hi OM, What about nRF51822?, I guess the behaviour is the same for both, am I right? regards, Mo

  • Here's a version of mine with some optimization:

    void GPIOTE_IRQHandler(void)
    {		
    uint8_t i;
    uint32_t pins_state = NRF_GPIO->IN;	//save current input state
    uint32_t triggered_pins = 0ul;			//save pins that actually generated IRQ
    uint8_t sense[NO_OF_PINS];					//save sense configuration
    
    //clear event
    NRF_GPIOTE->EVENTS_PORT = 0ul;
    
    // Backup and then disable all SENSE settings to ensure that DETECT signal is deasserted.
    // Check if pin value and sense agree and store it in triggered_pins
    for (i = 0; i < NO_OF_PINS; i++)
    {
    	//store sense value of each pin
    	sense[i] = (uint8_t) ( (NRF_GPIO->PIN_CNF[i] & GPIO_PIN_CNF_SENSE_Msk) >> GPIO_PIN_CNF_SENSE_Pos) );	
    	NRF_GPIO->PIN_CNF[i] &= ~GPIO_PIN_CNF_SENSE_Msk;		//disable sense field in pincnf
    	if ( (sense[i] == GPIO_PIN_CNF_SENSE_High) && ((pins_state & (1ul << i)) != 0) )	//sense high, pin high
    	{
    		triggered_pins |= (1u << i);		//flag current pin caused IRQ
    		sense[i] = GPIO_PIN_CNF_SENSE_Low;	//flip sense value (IRQ won't be recalled until pin changes value again
    	} else if ( (sense[i] == GPIO_PIN_CNF_SENSE_Low) && ((pins_state & (1ul << i)) == 0) )							//sense low, pin low
    	{
    		triggered_pins |= (1u << i);		//flag current pin caused IRQ
    		sense[i] = GPIO_PIN_CNF_SENSE_High;	//flip sense value
    	}
    }
    
    //***
    // here check if bits in triggered_pins are one of the ISRs you need, and compare it with pins_state for the transition you need
    // call related ISRs
    // i.e.
    // if ( triggered_pins & pins_state & (1ul << IRQpin) ) {dosmth} <- for lowtohi
    // if ( triggered_pins & ~pins_state & (1ul << IRQpin) ){dosmth} <- for hitolow
    //***
    
    // now restore modified sense values in PIN_CNF
    for (i = 0; i < NO_OF_PINS; i++)
     {
    	NRF_GPIO->PIN_CNF[i] |= (sense[i] << GPIO_PIN_CNF_SENSE_Pos);
     }
    
    }
    
Related