nRF54L15: SENSE flips from HIGH to LOW after motion and never flips back — why?

Hi all,

I'm hoping someone can explain a behavior I've observed. I have a workaround but I want to understand the mechanism.

Setup

  • nRF54L15, NCS v3.1.0
  • ICM-42670-P IMU on P0.02 (INT1), pulsed-mode interrupt (~100µs HIGH per Wake-on-Motion event)
  • DT: gpios = <&gpio0 2 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>;
  • At boot:
    gpio_pin_configure_dt(&int1_gpio, GPIO_INPUT);
    gpio_pin_interrupt_configure_dt(&int1_gpio, GPIO_INT_EDGE_RISING);
    nrf_gpio_cfg_sense_set(int1_gpio.pin, NRF_GPIO_PIN_SENSE_HIGH);
    

I never touch PIN_CNF again after this.

What I observe

I added a shell command that reads NRF_P0->PIN_CNF[2] directly:

  • Cold boot: SENSE = HIGH (0x2). Pin idle LOW. Everything as configured. ✓
  • After any motion (even a single tap): SENSE = LOW (0x3). Pin idle LOW. LATCH bit 2 stuck at 1, write-1-to-clear has no effect.
  • After more motion: still SENSE = LOW. It never flips back to HIGH on its own.

My two questions

1. Why does SENSE flip from HIGH to LOW after a motion pulse?

My application code never writes PIN_CNF. Something else is modifying it. What is it, and why?

2. Why doesn't SENSE flip back to HIGH at rest?

Once SENSE is LOW and the pin is also LOW, they match — DETECT should be asserted continuously. But intuitively, the same mechanism that flipped HIGH→LOW should flip LOW→HIGH at some point (e.g. on the next motion pulse, or from the level match itself). It doesn't. SENSE stays on LOW indefinitely until I manually set it again. Why is this asymmetric?

Consequence

sys_poweroff() after motion wakes immediately, because SENSE=LOW matches the idle-LOW pin → DETECT asserted → instant wake.

Workaround that works (but I don't fully understand why)

Don't arm SENSE at boot. Arm it only right before sys_poweroff():

nrf_gpio_cfg_sense_set(int1_gpio.pin, NRF_GPIO_PIN_SENSE_HIGH);
sys_poweroff();

With this change, runtime shows SENSE = DISABLED regardless of motion, and the sleep moment gets a clean HIGH arm. Sleep works reliably.

But I don't understand why deferring the arm avoids the flip, or why early-arming causes the asymmetric stuck-on-LOW behavior.

Any insight would be hugely appreciated.

Thanks!

Parents
  • Hi,

    1. Why does SENSE flip from HIGH to LOW after a motion pulse?

    This is most likely done by the Zephyr GPIO driver. You configured it to "GPIO_INT_EDGE_RISING" so the driver implements a "sense toggle" pattern. Basically, it toggles SENSE to LOW when the pin goes HIGH so that it can detect when the pin returns to LOW, which signal the end of the pulse. Then it should toggle it back to HIGH to re-arm for the next rising edge.

    2. Why doesn't SENSE flip back to HIGH at rest?

    However, your pulse is only 100us. So, if your pulse ends before the driver's ISR re-arm SENSE to HIGH, the LATCH bit for the LOW sense is immediately set and can't be cleared while the pin remains LOW.

    Workaround that works (but I don't fully understand why)

    Don't arm SENSE at boot. Arm it only right before sys_poweroff():

    nrf_gpio_cfg_sense_set(int1_gpio.pin, NRF_GPIO_PIN_SENSE_HIGH);
    sys_poweroff();
    

    With this change, runtime shows SENSE = DISABLED regardless of motion, and the sleep moment gets a clean HIGH arm. Sleep works reliably.

    But I don't understand why deferring the arm avoids the flip, or why early-arming causes the asymmetric stuck-on-LOW behavior.

    Any insight would be hugely appreciated.

    This workaround works because you don't let the GPIOTE driver do his things before powering off the chip. But the underlying problem would be that you shouldn't use the GPIOTE driver at the same time as manually changing SENSE. Basically if you don't mess with the GPIOTE driver, it should be able to get that rising edge just fine. And you can manually set the SENSE just before poweroff like this:

    /* Disable the runtime interrupt */
    gpio_pin_interrupt_configure_dt(&int1_gpio, GPIO_INT_DISABLE);
    
    /* Arm SENSE only now, with pin confirmed idle LOW */
    nrf_gpio_cfg_sense_set(int1_gpio.pin, NRF_GPIO_PIN_SENSE_HIGH);
    
    sys_poweroff();

    Best regards,

    Simon D-M

Reply
  • Hi,

    1. Why does SENSE flip from HIGH to LOW after a motion pulse?

    This is most likely done by the Zephyr GPIO driver. You configured it to "GPIO_INT_EDGE_RISING" so the driver implements a "sense toggle" pattern. Basically, it toggles SENSE to LOW when the pin goes HIGH so that it can detect when the pin returns to LOW, which signal the end of the pulse. Then it should toggle it back to HIGH to re-arm for the next rising edge.

    2. Why doesn't SENSE flip back to HIGH at rest?

    However, your pulse is only 100us. So, if your pulse ends before the driver's ISR re-arm SENSE to HIGH, the LATCH bit for the LOW sense is immediately set and can't be cleared while the pin remains LOW.

    Workaround that works (but I don't fully understand why)

    Don't arm SENSE at boot. Arm it only right before sys_poweroff():

    nrf_gpio_cfg_sense_set(int1_gpio.pin, NRF_GPIO_PIN_SENSE_HIGH);
    sys_poweroff();
    

    With this change, runtime shows SENSE = DISABLED regardless of motion, and the sleep moment gets a clean HIGH arm. Sleep works reliably.

    But I don't understand why deferring the arm avoids the flip, or why early-arming causes the asymmetric stuck-on-LOW behavior.

    Any insight would be hugely appreciated.

    This workaround works because you don't let the GPIOTE driver do his things before powering off the chip. But the underlying problem would be that you shouldn't use the GPIOTE driver at the same time as manually changing SENSE. Basically if you don't mess with the GPIOTE driver, it should be able to get that rising edge just fine. And you can manually set the SENSE just before poweroff like this:

    /* Disable the runtime interrupt */
    gpio_pin_interrupt_configure_dt(&int1_gpio, GPIO_INT_DISABLE);
    
    /* Arm SENSE only now, with pin confirmed idle LOW */
    nrf_gpio_cfg_sense_set(int1_gpio.pin, NRF_GPIO_PIN_SENSE_HIGH);
    
    sys_poweroff();

    Best regards,

    Simon D-M

Children
No Data
Related