A hardware/software processing logic question.
Posted: 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.
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.)
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.
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);
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 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.