-
Notifications
You must be signed in to change notification settings - Fork 7.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Core 0 panic'ed (InstrFetchProhibited) after call to xTimerResetFromISR / xTimerStartFromISR (IDFGH-14499) #15269
Comments
Here's a bit more info on the backtrace:
Why this call fails is beyond me, because it is being executed in the TimerService Task of FreeRTOS, which shouldn't be in the ISR. I also tried both variants: Could it be, that the timer is initialized with What puzzles me about this issue is that there is no 5-second delay between From the code of the FreeRTOS kernel I understand why the timer gets immediatly called and maybe I should instead be using So I'm confused about this behaviour. |
I have also tried the variant shown in the generic_gpio example, which simply uses a queue and sends each state change to the queue. IRAM_ATTR static void _i2c_slave_isr_handler(void *arg)
{
uint32_t gpio_num = (uint32_t)arg;
xQueueSendFromISR(i2c_slave_ctx->gpio_queue, &gpio_num, NULL);
}
static void gpio_task_example(void *arg)
{
uint32_t io_num;
for (;;)
{
if (xQueueReceive(i2c_slave_ctx->gpio_queue, &io_num, portMAX_DELAY))
{
printf("GPIO[%" PRIu32 "] intr, val: %d\n", io_num, gpio_get_level(io_num));
}
}
}
void TA_i2c_slave_init()
{
// ...
// Configure I2C slave address GPIOs
gpio_config_t slave_addr_io_conf = {};
slave_addr_io_conf.intr_type = GPIO_INTR_ANYEDGE;
slave_addr_io_conf.mode = GPIO_MODE_INPUT;
slave_addr_io_conf.pin_bit_mask = GPIO_SLAVE_ADDR_INPUT_MASK;
// External pull-down on hardware
slave_addr_io_conf.pull_down_en = 0;
slave_addr_io_conf.pull_up_en = 0;
ESP_ERROR_CHECK(gpio_config(&slave_addr_io_conf));
// Using isr service to allow for other GPIO interrupts on this core (by other components)
ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_IRAM)); // must place handlers in IRAM
i2c_slave_ctx->gpio_queue = xQueueCreate(10, sizeof(uint32_t));
if (!i2c_slave_ctx->gpio_queue)
{
ESP_LOGE(TAG_I2C_SLAVE, "Failed to create GPIO queue");
vPortFree(i2c_slave_ctx->bytes);
vPortFree(i2c_slave_ctx);
return;
}
gpio_isr_handler_add(GPIO_A0, _i2c_slave_isr_handler, (void *)GPIO_A0);
gpio_isr_handler_add(GPIO_A1, _i2c_slave_isr_handler, (void *)GPIO_A1);
gpio_isr_handler_add(GPIO_A2, _i2c_slave_isr_handler, (void *)GPIO_A2);
gpio_isr_handler_add(GPIO_A3, _i2c_slave_isr_handler, (void *)GPIO_A3);
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);
// ...
} This works, according to the logs:
But it's a bit undesirable, since it fills up a queue with irrelevant values, where only the last one matters in my case. |
If I run the code like this, it works: #define DEBOUNCE_TIME_MS 5000
#define GPIO_A0 GPIO_NUM_39
#define GPIO_A1 GPIO_NUM_40
#define GPIO_A2 GPIO_NUM_41
#define GPIO_A3 GPIO_NUM_42
#define GPIO_SLAVE_ADDR_INPUT_MASK ((1ULL << GPIO_A0) | (1ULL << GPIO_A1) | (1ULL << GPIO_A2) | (1ULL << GPIO_A3))
typedef struct i2c_slave_context
{
i2c_slave_dev_handle_t handle;
QueueHandle_t event_queue;
QueueHandle_t gpio_queue;
TimerHandle_t debounce_timer;
// ...
} i2c_slave_context_t;
static StaticTimer_t debounce_timer_buffer = {};
static i2c_slave_context_t *i2c_slave_ctx = NULL;
IRAM_ATTR static void _i2c_slave_isr_handler(void *arg)
{
uint32_t gpio_num = (uint32_t)arg;
xQueueSendFromISR(i2c_slave_ctx->gpio_queue, &gpio_num, NULL);
}
static void _i2c_slave_debounce_timer_cb(TimerHandle_t xTimer)
{
i2c_slave_context_t *context = (i2c_slave_context_t *)pvTimerGetTimerID(xTimer);
static i2c_slave_event_t evt = I2C_SLAVE_EVT_RESTART;
ESP_LOGI(TAG_I2C_SLAVE, "Debounce timer expired");
// There is no need to check if the address changed, any state change on the address pins should trigger an I2C restart
if (xQueueSend(context->event_queue, &evt, DEBOUNCE_TIME_MS) != pdPASS)
{
ESP_LOGE(TAG_I2C_SLAVE, "Failed to send restart event");
}
}
static void gpio_task_example(void *arg)
{
uint32_t io_num;
for (;;)
{
if (xQueueReceive(i2c_slave_ctx->gpio_queue, &io_num, portMAX_DELAY))
{
printf("GPIO[%" PRIu32 "] intr, val: %d\n", io_num, gpio_get_level(io_num));
xTimerStart(i2c_slave_ctx->debounce_timer, DEBOUNCE_TIME_MS);
}
}
}
void TA_i2c_slave_init()
{
// ...
// Configure I2C slave address GPIOs
gpio_config_t slave_addr_io_conf = {};
slave_addr_io_conf.intr_type = GPIO_INTR_ANYEDGE;
slave_addr_io_conf.mode = GPIO_MODE_INPUT;
slave_addr_io_conf.pin_bit_mask = GPIO_SLAVE_ADDR_INPUT_MASK;
// External pull-down on hardware
slave_addr_io_conf.pull_down_en = 0;
slave_addr_io_conf.pull_up_en = 0;
ESP_ERROR_CHECK(gpio_config(&slave_addr_io_conf));
// Using isr service to allow for other GPIO interrupts on this core (by other components)
ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_IRAM)); // must place handlers in IRAM
i2c_slave_ctx->gpio_queue = xQueueCreate(10, sizeof(uint32_t));
if (!i2c_slave_ctx->gpio_queue)
{
ESP_LOGE(TAG_I2C_SLAVE, "Failed to create GPIO queue");
vPortFree(i2c_slave_ctx->bytes);
vPortFree(i2c_slave_ctx);
return;
}
gpio_isr_handler_add(GPIO_A0, _i2c_slave_isr_handler, (void *)GPIO_A0);
gpio_isr_handler_add(GPIO_A1, _i2c_slave_isr_handler, (void *)GPIO_A1);
gpio_isr_handler_add(GPIO_A2, _i2c_slave_isr_handler, (void *)GPIO_A2);
gpio_isr_handler_add(GPIO_A3, _i2c_slave_isr_handler, (void *)GPIO_A3);
// Create one-shot timer for address change debounce
i2c_slave_ctx->debounce_timer = xTimerCreateStatic("i2c_debounce", pdMS_TO_TICKS(DEBOUNCE_TIME_MS), pdFALSE, i2c_slave_ctx, _i2c_slave_debounce_timer_cb, &debounce_timer_buffer);
if (i2c_slave_ctx->debounce_timer == NULL)
{
ESP_LOGE(TAG_I2C_SLAVE, "Failed to create debounce timer");
vPortFree(i2c_slave_ctx->bytes);
vPortFree(i2c_slave_ctx);
return;
}
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);
// ...
} Output:
This is the least desired solution. I just did it to prove that the timer would be working, it only causes a panic directly after the ISR, WHICH SHOULD NOT HAPPEN when I'm using a FromISR function. The timer itself should not be called from ISR, but I think that is what's happening, otherwise I can't explain the InstrProhibited. |
From the backtrace it looks like it tries to call the timer callback, but it points to NULL. Is it possible you are simply corrupting the timer struct memory somewhere? You I tried a simplified example, which just tested reseting the timer from an ISR and that seem to work as expected at least. |
Answers checklist.
IDF version.
v5.4.0
Espressif SoC revision.
ESP32-S3 (ESP32-S3FN8) (revision 0.2)
Operating System used.
Linux
How did you build your project?
Command line with idf.py
If you are using Windows, please specify command line type.
None
Development Kit.
Custom board
Power Supply used.
External 5V
What is the expected behavior?
Timer gets started / reset after ISR completes and also gets executed after the timeout has been reached.
What is the actual behavior?
After
portYIELD_FROM_ISR()
in the interrupt service routine, I getCore 0 panic'ed (InstrFetchProhibited)
error.The ISR gets executed twice due to contact bouncing, which is the purpose of my timer, to de-bounce the GPIO input and use the last state.
After the second execution, the timer should be running for 5000ms (value chosen for debugging purposes) and then execute the callback.
But the panic happens almost immediately after the switch has been flipped.
My hardware in this case is a DIP switch, so it is safe to assume a reasonable delay for debouncing, instead of waiting for the input.
Steps to reproduce.
Code is provided in snippets relevant to the issue.
Debug Logs.
More Information.
I wanted to implement a debouncing mechanism for a DIP switch we use for I²C sub-addressing. DIP switch are notoriously bouncy, as such I've implemented the above ISR routine, which will get repeatedly called and reset the timer for the one-shot callback.
As seen in the comments, I simply call
xTimerResetFromISR
to reset the timer. As stated from the documentation, I could have also usedxTimerStartFromISR
, which has also been tried.Both functions should reset the timer value upon being called in quick succession, so only after the last call to the ISR, the timer should start running out. Effectively, this debounces the input, since we only act after some delay as the last input changed.
The text was updated successfully, but these errors were encountered: