Writing to settings (non-volatile memory) causes GPIO interrupt triggering losses

SDK 2.1.0

nRF9160

Hi,

I recently configured a GPIO input in my project with an associated interrupt whenever it detects a high to low transition. The transition is externally generated every 20ms.

With each interruption, a work is rescheduled.

Everything works as expected until there is a great write (buffer[3500]) to the Settings.

After writing, the work is executed, meaning that the transition was not detected.

It seems to me that it's a problem of priorities...

I wanted to know if it is possible, and how, to change the priority of GPIO interrupts.

Regards,

Ricardo

  • Hello Ricardo,

    Can you please elaborate a bit on how it is not working? When you perform the flash write to settings, does the GPIO input fail to detect changes when it is written, so that you are loosing events? Is it able to detect interrupts when the flash write is complete? Do you get a delayed event? 

    Can you also please share how you have set up the GPIO interrupt? Some snippets, perhaps?

    Best regards,

    Edvin

  • Hi Edvin,

    I'm sorry for the delay in responding.

    I'll try to explain myself better, but you understand the problem.
    I have a change being generated externally every 20ms on a GPIO port, and every time this change is detected, a work is rescheduled for 25ms.
    While there is no writing to settings, all changes are correctly detected on the GPIO port.
    When there is a write to settings, during the write time, there is no change detected.
    After completing writing in settings, the detection of changes in GPIOs will be detected again.

    My GPIO port is configured like this:

    #define ZERO_CROSSING_NODE DT_NODELABEL(uc_zero_cross)
    
    /* declare a pointer to devicetree node identifier */
    static const struct gpio_dt_spec zero_crossing = GPIO_DT_SPEC_GET(ZERO_CROSSING_NODE, gpios);
    
    /* Defines the callback used to detect a gpio state change */
    static struct gpio_callback zero_crossing_gpio_cb;
    
    /* Work queue used by zero crossing */
    static struct k_work_q *zero_crossing_work_q;
    
    /* Work structure used by zero crossing */
    static struct k_work_delayable zero_crossing_work;
    
    
    ret_code_t
    UC_ZeroCrossing_Init(struct k_work_q *work_q)
    {
    	/* Check for NULL pointers in the arguments */
    	if (!work_q) {
    		return INVALID_ARGUMENT;
    	} 
    	
    	/* Initialise control variables */
    	int configure_gpio_result = NRF_SUCCESS;
    
        /* Check if device is ready */
        if (!device_is_ready(zero_crossing.port)) {
            LOG_ERR("Zero Crossing GPIO device not ready");
            return ERROR;
        }
        /* Configure gpio port */
        configure_gpio_result = gpio_pin_configure_dt(&zero_crossing, GPIO_INPUT);
        if (NRF_SUCCESS != configure_gpio_result) {
            LOG_ERR("Failed to configure Zero Crossing GPIO pin (%d)", configure_gpio_result);
            return ERROR;
        }
        configure_gpio_result = gpio_pin_interrupt_configure_dt(&zero_crossing, GPIO_INT_EDGE_TO_INACTIVE);
        if (NRF_SUCCESS != configure_gpio_result) {
            LOG_ERR("Failed to configure Zero Crossing GPIO pin (%d)", configure_gpio_result);
            return ERROR;
        }
    	/* configure a gpio calback */
        gpio_init_callback(&zero_crossing_gpio_cb, UC_ZeroCrossing_gpio_cb, BIT(zero_crossing.pin));
        gpio_add_callback(zero_crossing.port, &zero_crossing_gpio_cb);
        
    	
    	/* Initialise the kwork structure to report a last gasp */
        UC_Utils_WorkInitDelayable(&zero_crossing_work, UC_ZeroCrossing_LastGasp);
    
        /* Update the work queue used for report a last gasp */
        zero_crossing_work_q = work_q;
    
        LOG_INF("Zero Crossing driver initialised.");
    
        return SUCCESS;
    }
    
    void 
    UC_ZeroCrossing_gpio_cb(const struct device *dev, struct gpio_callback *cb,
                                        uint32_t pins)
    {
    
        LOG_DBG("Zero Cross detected - Reschedule work");
    
        /* Schedule task to report a last gasp */
        int submit_result = k_work_reschedule_for_queue(zero_crossing_work_q, &zero_crossing_work, K_MSEC(ZERO_CROSSING_RESCHEDULE_TIME_MS));
        if (submit_result < NRF_SUCCESS) {
            LOG_ERR("Failed to submit Zero Crossing work to queue (%d)", submit_result);
        }
    }
    
    void 
    UC_ZeroCrossing_LastGasp(struct k_work *work)
    {
        LOG_WRN("Zero Crossing NOT detected.");
        
        /* Trigger a Lasp Gasp */
            zc_callback();
    }

    Log output:

    [00:00:50.504,913] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.524,932] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.544,921] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.564,910] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.584,930] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.604,919] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.624,908] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.644,927] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.664,916] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.673,919] <inf> UC_SETTI: Start Saving JSON script
    [00:00:50.761,016] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.764,984] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.784,912] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.804,565] <inf> UC_SETTI: Saved new JSON script
    [00:00:50.804,901] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.805,480] <wrn> UC_ZCROSS: Zero Crossing NOT detected.
    [00:00:50.824,920] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.844,909] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.864,898] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.884,918] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work
    [00:00:50.904,907] <dbg> UC_ZCROSS: UC_ZeroCrossing_gpio_cb: Zero Cross detected - Reschedule work

    As you can see from the log time, between 50.664sec and 50.764sec, only one change (50.761sec) was detected in the GPIO port.

    Thanks for the help.


    Regards,
    Ricardo

  • Ok, I understand. So while you are performing the flash operation, no interrupts are forwarded to your application, and when the write is done, you will get one interrupt (50.761), which is the queued up interrupt, and then it will presume as normal from 50.764 (until the next flash operation). 

    What kind of work is it that you are rescheduling on the GPIO interrupt? 

    The thing is that when you are performing flash write operations, the CPU is occupied, so it is not possible to do anything else at that point in time. 

    How time sensitive is the work you are rescheduling. It could be possible to limit the amount you write to flash (but do it more often), so that it doesn't take 90ms. But unless you time it to happen directly after a cross detection, it may still block your interrupt from triggering exactly when it occurs. However, if only one interrupt happens while you are writing to flash, this interrupt will be forwarded to the CPU (your application) when the flash write is done. 

    Alternatively, it is possible to do some tricks using DPPI to count the number of cross detections while the flash write operations are ongoing, and then catch up with the work once the flash write operation is done. Let me know what you think.

    Best regards,

    Edvin

  • Hi Edvin,

    Thanks for the explanation.

    I thought it could be some error in my implementation, or that it was possible to change the priorities of writing to flash in relation to GPIO interrupts... being a CPU limitation, I have to think of another way.

    What kind of work is it that you are rescheduling on the GPIO interrupt? 

    It's nothing elaborate, it's just a calback assigned by lwm2m to notify the failure and imminent shutdown.

    It could be possible to limit the amount you write to flash (but do it more often), so that it doesn't take 90ms.

    The write to flash is a single 3500 byte buffer, it's not easy to split this write, and as you say, it can always match and fail.

    Alternatively, it is possible to do some tricks using DPPI to count the number of cross detections while the flash write operations are ongoing, and then catch up with the work once the flash write operation is done.

    I don't need to know how many transitions happened, I just need to notify them as soon as possible if one fails.

    I'm more inclined to, before writing, cancel the scheduled work, at least I won't get false positives every time I finish writing.


    Regards,
    Ricardo

  • I think I understand. 

    Whenever you get a GPIO interrupt, it will be sent to your application, unless another process is blocking it, which in your case is the flash write. In that case, it will be forwarded to your application once the blocking action is finished. 

    RicardoCarvalho said:
    I don't need to know how many transitions happened, I just need to notify them as soon as possible if one fails.

    In that case, you will be notified as soon as possible, once the flash write has finished. If it fails twice during the flash write, however, you will only be notified once, because when an interrupt is being handled, it is also cleared. If an interrupt happens twice before you handle it, it doesn't stack, so you will only know that it happens once.

    So as I mentioned, it is possbile to set up a counter using the DPPI (a peripheral used to link up events and tasks in other peripherals). So you could link up the event where the GPIO triggers, to a task in a timer set in counter mode to increment by one. This way you can have an event counter to count the number of events without involving the CPU. Then, you can later in the interrupt handler check how much the timer has incremented to see how many times it happened. However, that is only if you need the amount of times it happened while writing.

    Best regards,

    Edvin

Related