A hardware/software processing logic question.

uC_Jon
Posts: 18
Joined: Fri Jan 05, 2024 7:03 pm

A hardware/software processing logic question.

Postby uC_Jon » Tue Apr 09, 2024 1:25 pm

First the changes I've made to one of the example programs (generic_gpio) seem to work, at least in the limited example environment, but I'm wondering if its the best logical/processing way of performing the task I need. I'm using an ESP32C6 if that makes any difference to the answers (if the behaviour type questions are device specific). I've included limited code snippets to show the kind of changes I've done.

I could probably muddle along trying various options till I hit one that seems to work (like the changes I made to the example program), but it seems that asking a couple of questions now that I have a basic idea will save me a lot of pain and effort later and doing something that seems to work but fails in multiple cases.

My requirement is to process a number of stand alone functions, including multiple I2C I/O's, and then kind of mash the results into a lvgl display and control a couple of latching relays.

Due to the number of external I2C devices being quite large my design has/will have a number of them tying their "int" pins to a single gpio pin. (Specifically two temp sensors, 1 battery backed RTC, one motion sensor all share one "int" pin and a couple of other I2C devices have their own unique "int" pins.)

The code changes to the generic_gpio example call the ISR on a level=HIGH instead of on an edge change (high/low); the reason being that either within the ISR or the servicing task another devices interrupt might be fired (or having cleared an interrupt on an I2C device it might fire again) and this way allows any int that hasn't been cleared to continue the gpio interrupt; at least that is what I read in some other non-esp device forum.

Code: Select all

gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_HIGH_LEVEL);
Within the ISR the first thing I do is DISABLE the pin's interrupt, and then send a notify to the task that a gpio interrupt happened... the task tests all the I2C devices until it finds the device that fired the interrupt and then clears it (some devices have their interrupt cleared by performing a read of a register to test if it was that device that signalled the "int" and so clears it automatically) and as the last thing it does is ENABLE the pins interrupt. (I used long delays because I wanted to test what would happen if another ISR got fired while I was in the middle of processing.)

Code: Select all

static void IRAM_ATTR gpio_isr_handler_0(void* arg)
{
    gpio_intr_disable((uint32_t) arg);
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    ESP_DRAM_LOGE(TAG, "I2C devices shared pin handler Pin: %d", (uint32_t) arg);
    vTaskNotifyGiveFromISR(task_to_notify, &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

Code: Select all

static void gpio_task_notify_example(void* arg)
{
    for (;;) {
        if (ulTaskNotifyTake(true, portMAX_DELAY)) {
            ESP_LOGI(TAG, "Pretending we're reading an I2C device");
            vTaskDelay(5000 / portTICK_PERIOD_MS);
            ESP_LOGI(TAG, "Pretending we're reading a different I2C device");
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            ESP_LOGI(TAG, "Pretending we've cleared an I2C device interrupt HIGH Pin: %d", GPIO_INPUT_IO_0);
            gpio_set_level(GPIO_OUTPUT_IO_0, false); // We actually clear the output which clears the input!
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            ESP_LOGI(TAG, "Reinitialising the interrupt");
            gpio_intr_enable(GPIO_INPUT_IO_0);
        }
    }
}

Code: Select all

   
    // app_main processing to trigger gpio "int"s
    while (1) {
        vTaskDelay(1000 / portTICK_PERIOD_MS);
        ESP_LOGI(TAG, "cnt: %d", cnt++);
        gpio_set_level(GPIO_OUTPUT_IO_0, !(cnt % 16));
        gpio_set_level(GPIO_OUTPUT_IO_1, !(cnt % 2));
    }
    
Question 1) Is disabling the interrupt in the ISR and re-enabling it in the task an acceptable way of preventing the re-firing of the interrupt which happens continually on a level=HIGH?

Question 2) I think, but am not 100% sure, that the re-firing happens the moment the ISR is exited, is this correct?

Question 3) What happens when a gpio interrupt is on a transition (low>high, high>low, both) and the code is already within an ISR? Does the interrupt get delayed till ISR exits or does the ISR get called again even if its in the middle of processing the previous interrupt, deal with that new interrupt then continue where the previous interrupt left off (would that be nested interrupts?)?

Question 4) If an edge interrupt calls the ISR 100 times while a task has only managed to process one time, and I'm using ulTASKNotifyTake(true, portMAX_DELAY), would I be right in thinking that it would only potentially run twice (depending on absolute timing)? The current processing run would run to completion; and then as it looped back it would see a notification and as it clears the count (instead of decrementing it) the next time around it would no longer have any notifications to take? (I'm guessing without clearing the count it would potentially loop 100 times?)

Question 5) The original example doesn't use YIELD_FROM_ISR but the freeRTOS documentation seems to suggest this should be called so should I follow the original gpio example or the documentation (although I I'm unsure I understand what calling it does and what the what difference using HigherPriotiyTaskWoken makes)?

Thanks in advance.

MicroController
Posts: 1733
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: A hardware/software processing logic question.

Postby MicroController » Tue Apr 09, 2024 2:49 pm

Some quick thoughts:
1) The interrupt outputs of I2C chips are commonly active LOW (open drain), so
2) I would process FALLING edge interrupts in the following manner:
3) When a falling edge is detected (ISR), send a signal/notification to a task to handle it.
The handling in the task is basically: 1. wait for signal/notification, 2. poll I2C devices for interrupt condition, 3. goto 1.
I'm unsure I understand what calling it does and what the what difference using HigherPriotiyTaskWoken makes)
When an ISR modifies a queue, semaphore or the like, FreeRTOS does not immediately take note of this. The sending or receiving done by the ISR may cause a waiting task to become unblocked, i.e. runnable, but FreeRTOS will not know about/check this until either a) the next FreeRTOS tick interrupt or b) you call a variant of YIELD_FROM_ISR. So if you don't call the yield function, the newly runnable task may remain dormant until the next FreeRTOS tick, delaying processing of the event. If you do call YIELD_FROM_ISR, the ISR may 'return' directly to the newly runnable task, causing immediate handling of the event.

uC_Jon
Posts: 18
Joined: Fri Jan 05, 2024 7:03 pm

Re: A hardware/software processing logic question.

Postby uC_Jon » Tue Apr 09, 2024 4:08 pm

MicroController wrote:
Tue Apr 09, 2024 2:49 pm
Some quick thoughts:
1) The interrupt outputs of I2C chips are commonly active LOW (open drain), so

That's true, I was just adapting the code from the example... I should have put GPIO_INTR_LOW_LEVEL and changed the code accordingly.

2) I would process FALLING edge interrupts in the following manner:
3) When a falling edge is detected (ISR), send a signal/notification to a task to handle it.
The handling in the task is basically: 1. wait for signal/notification, 2. poll I2C devices for interrupt condition, 3. goto 1.

The reason for my using "HIGH" (should be "LOW") is that if the timing of the falling edge trigger and the devices being polled is "off" (by some variable but non-zero amount), then with multiple devices an edge trigger from one device might be missed because the task has not yet reset the device that caused it to enter the task but has already passed the device that would have caused the next ISR invocation (the edge trigger gets lost because the line is still low when the all ready polled device pulls the line low). I guess I could 1) make the task loop wait on a new task notification. 2) poll all the devices (or until a device says its interrupted). 3) test for the "int" pin still being low and if so loop through the devices again. 4) goto 1 when no devices are still pulling the "int" pin low.

I'm unsure I understand what calling it does and what the what difference using HigherPriotiyTaskWoken makes)
When an ISR modifies a queue, semaphore or the like, FreeRTOS does not immediately take note of this. The sending or receiving done by the ISR may cause a waiting task to become unblocked, i.e. runnable, but FreeRTOS will not know about/check this until either a) the next FreeRTOS tick interrupt or b) you call a variant of YIELD_FROM_ISR. So if you don't call the yield function, the newly runnable task may remain dormant until the next FreeRTOS tick, delaying processing of the event. If you do call YIELD_FROM_ISR, the ISR may 'return' directly to the newly runnable task, causing immediate handling of the event.

Arrrr now I understand it. Thanks for the info.

MicroController
Posts: 1733
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: A hardware/software processing logic question.

Postby MicroController » Tue Apr 09, 2024 5:58 pm

uC_Jon wrote:
Tue Apr 09, 2024 4:08 pm
I guess I could 1) make the task loop wait on a new task notification. 2) poll all the devices (or until a device says its interrupted). 3) test for the "int" pin still being low and if so loop through the devices again. 4) goto 1 when no devices are still pulling the "int" pin low.
You're absolutely right. Indeed I missed the case where one device issues an interrupt after being polled but before another device's interrupt is cleared. I think with the extra step of 'repeat while INT is low' everything should work quite neatly.
Additionally, you can check for int==LOW right after receiving a notification to avoid reacting to a pending notification from an interrupt you already cleared in the previous iteration.
Last edited by MicroController on Tue Apr 09, 2024 6:32 pm, edited 1 time in total.

uC_Jon
Posts: 18
Joined: Fri Jan 05, 2024 7:03 pm

Re: A hardware/software processing logic question.

Postby uC_Jon » Tue Apr 09, 2024 6:17 pm

MicroController wrote:
Tue Apr 09, 2024 5:58 pm
uC_Jon wrote:
Tue Apr 09, 2024 4:08 pm
I guess I could 1) make the task loop wait on a new task notification. 2) poll all the devices (or until a device says its interrupted). 3) test for the "int" pin still being low and if so loop through the devices again. 4) goto 1 when no devices are still pulling the "int" pin low.
You're absolutely right. Indeed I missed the case where one device issues an interrupt before another device's interrupt is cleared. I think with the extra step of 'repeat while INT is low' everything should work quite neatly.
Additionally, you can check for int==LOW right after receiving a notification to avoid reacting to a pending notification from an interrupt you already cleared in the previous iteration.

Now that sounds like a good idea; especially the pre-test. Would be cleaner than faffing around with disabling and re-enabling gpio irq's.

MicroController
Posts: 1733
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: A hardware/software processing logic question.

Postby MicroController » Tue Apr 09, 2024 6:41 pm

uC_Jon wrote:
Tue Apr 09, 2024 6:17 pm
Would be cleaner than faffing around with disabling and re-enabling gpio irq's.
That's what I figured too :)

Best thing is: The code will become extremely simple, like

Code: Select all

while(1) {
  waitForNotification();
  while(intPinIsLow()) {
    pollAllDevices();
  }
}

Who is online

Users browsing this forum: Google [Bot] and 32 guests