Page 1 of 1

Question: How to access an I2C device from an ISR handler

Posted: Thu Jul 19, 2018 7:11 pm
by MickPF
I have created a driver for a touch panel (see below) that should read the data on the I2C bus within an ISR handler.
Unfortunately, access to the I2C bus generates panic (see below) when I touch the panel, the esp32 generates the interrupt and calls the ISR handler.

Does anyone know a solution for this problem?

Thanks in advance,
Michael

Code: Select all

esp_err_t IRAM_ATTR i2c_read(uint8_t addr, uint8_t reg, uint8_t *data, size_t len)
{
    esp_err_t           ret;
    i2c_cmd_handle_t    cmd;

    if (len == 0)
        return(ESP_OK);

    cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_ENABLE);
    i2c_master_write_byte(cmd, reg, I2C_ACK_CHECK_ENABLE);
    i2c_master_stop(cmd);
    ret = i2c_master_cmd_begin(I2C_USED_INTERFACE, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    if (ret != ESP_OK)
        return(ret);

    cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_READ, I2C_ACK_CHECK_ENABLE);
    if (len > 1)
        i2c_master_read(cmd, data, len - 1, I2C_ACK_VALUE);
    i2c_master_read_byte(cmd, data + len - 1, I2C_NACK_VALUE);
    i2c_master_stop(cmd);
    ret = i2c_master_cmd_begin(I2C_USED_INTERFACE, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    return(ret);
}

static void IRAM_ATTR ft6236_isr_handler(void *args)
{
    uint8_t                 i2c_buf[14];
    touch_panel_event_t     tp_event;

    if (i2c_read(FT6236_I2C_IO_ADDR, FT6236_REG_GEST_ID, i2c_buf, 14) == ESP_OK)
    {
        if (0 < i2c_buf[1] && i2c_buf[1] < 3)
        {
            ft6236_decode_gesture(i2c_buf, &tp_event);
            xQueueSendFromISR(msg_queue, &tp_event, NULL);
        }
    }
}

esp_err_t ft6236_initialize(void)
{
    int         i;
    esp_err_t   ret;

    gpio_set_direction(PIN_GPIO_FT6236_INT, GPIO_MODE_INPUT);
    gpio_set_pull_mode(PIN_GPIO_FT6236_INT, GPIO_PULLUP_ONLY);

    if ((ret = gpio_set_intr_type(PIN_GPIO_FT6236_INT, GPIO_INTR_NEGEDGE)) != ESP_OK)
        return(ret);
    if ((ret = gpio_install_isr_service(0)) != ESP_OK)
        return(ret);
    if ((ret = gpio_isr_handler_add(PIN_GPIO_FT6236_INT, ft6236_isr_handler, NULL)) != ESP_OK)
    {
        gpio_uninstall_isr_service();
        return(ret);
    }
    if ((ret = gpio_intr_enable(PIN_GPIO_FT6236_INT)) != ESP_OK)
    {
        gpio_isr_handler_remove(PIN_GPIO_FT6236_INT);
        gpio_uninstall_isr_service();
        return(ret);
    }
    return(ESP_OK);
}

Code: Select all

Guru Meditation Error: Core  0 panic'ed (Interrupt wdt timeout on CPU0)
Core 0 register dump:
PC      : 0x40088510  PS      : 0x00060c34  A0      : 0x80086ec6  A1      : 0x3ffb04c0  
0x40088510: vListInsert at /opt/esp32/esp-idf/components/freertos/list.c:188 (discriminator 1)

A2      : 0x3ffc47e0  A3      : 0x3ffb91cc  A4      : 0x00060c22  A5      : 0x00000001  
A6      : 0x00060c22  A7      : 0x00000000  A8      : 0x3ffb91cc  A9      : 0x3ffb91cc  
A10     : 0x00000019  A11     : 0x00000019  A12     : 0x00000014  A13     : 0x00000001  
A14     : 0x00060c23  A15     : 0x00000001  SAR     : 0x0000001b  EXCCAUSE: 0x00000005  
EXCVADDR: 0x00000000  LBEG    : 0x4000c2e0  LEND    : 0x4000c2f6  LCOUNT  : 0x00000000  
Core 0 was running in ISR context:
EPC1    : 0x400e5ac0  EPC2    : 0x400e9e86  EPC3    : 0x00000000  EPC4    : 0x40088510
0x400e5ac0: i2c_master_stop at /opt/esp32/esp-idf/components/driver/i2c.c:971

0x400e9e86: esp_vApplicationWaitiHook at /opt/esp32/esp-idf/components/esp32/freertos_hooks.c:66

0x40088510: vListInsert at /opt/esp32/esp-idf/components/freertos/list.c:188 (discriminator 1)


Backtrace: 0x40088510:0x3ffb04c0 0x40086ec3:0x3ffb04e0 0x40085bec:0x3ffb0500 0x400e5f1a:0x3ffb0540 0x400d29da:0x3ffb0580 0x40082bf1:0x3ffb05a0 0x400852ae:0x3ffb05f0 0x400825c1:0x3ffb0610 0x400e9e83:0x00000000
0x40088510: vListInsert at /opt/esp32/esp-idf/components/freertos/list.c:188 (discriminator 1)

0x40086ec3: vTaskPlaceOnEventList at /opt/esp32/esp-idf/components/freertos/tasks.c:4609

0x40085bec: xQueueGenericReceive at /opt/esp32/esp-idf/components/freertos/queue.c:2037

0x400e5f1a: i2c_master_cmd_begin at /opt/esp32/esp-idf/components/driver/i2c.c:1271

0x400d29da: i2c_read at /home/mick/Projects/ESP32/ILI9488_FT6236/main/interfaces.c:161

0x40082bf1: ft6236_isr_handler at /home/mick/Projects/ESP32/ILI9488_FT6236/main/ft6236.c:230

0x400852ae: gpio_intr_service at /opt/esp32/esp-idf/components/driver/gpio.c:549

0x400825c1: _xt_medint2 at /opt/esp32/esp-idf/components/freertos/xtensa_vectors.S:1185

0x400e9e83: esp_vApplicationWaitiHook at /opt/esp32/esp-idf/components/esp32/freertos_hooks.c:66


Core 1 register dump:
PC      : 0x40085e69  PS      : 0x00060034  A0      : 0x800862be  A1      : 0x3ffb9580  
0x40085e69: uxPortCompareSet at /opt/esp32/esp-idf/components/freertos/port.c:374
 (inlined by) vPortCPUAcquireMutexIntsDisabledInternal at /opt/esp32/esp-idf/components/freertos/portmux_impl.inc.h:86
 (inlined by) vPortCPUAcquireMutexIntsDisabled at /opt/esp32/esp-idf/components/freertos/portmux_impl.h:98
 (inlined by) vPortCPUAcquireMutex at /opt/esp32/esp-idf/components/freertos/port.c:365

A2      : 0x3ffb106c  A3      : 0x00000000  A4      : 0x80085e06  A5      : 0x3ffb0bd0  
A6      : 0x3ffb0c18  A7      : 0x00000001  A8      : 0x0000abab  A9      : 0x0000abab  
A10     : 0x00060023  A11     : 0xb33fffff  A12     : 0x0000cdcd  A13     : 0x3ffb0ba0  
A14     : 0x00000008  A15     : 0x00000001  SAR     : 0x00000020  EXCCAUSE: 0x00000005  
EXCVADDR: 0x00000000  LBEG    : 0x00000000  LEND    : 0x00000000  LCOUNT  : 0x00000000  

Backtrace: 0x40085e69:0x3ffb9580 0x400862bb:0x3ffb95a0 0x400878ac:0x3ffb95c0 0x40087862:0x00000000
0x40085e69: uxPortCompareSet at /opt/esp32/esp-idf/components/freertos/port.c:374
 (inlined by) vPortCPUAcquireMutexIntsDisabledInternal at /opt/esp32/esp-idf/components/freertos/portmux_impl.inc.h:86
 (inlined by) vPortCPUAcquireMutexIntsDisabled at /opt/esp32/esp-idf/components/freertos/portmux_impl.h:98
 (inlined by) vPortCPUAcquireMutex at /opt/esp32/esp-idf/components/freertos/port.c:365

0x400862bb: vTaskSwitchContext at /opt/esp32/esp-idf/components/freertos/tasks.c:4609

0x400878ac: _frxt_dispatch at /opt/esp32/esp-idf/components/freertos/portasm.S:406

0x40087862: _frxt_int_exit at /opt/esp32/esp-idf/components/freertos/portasm.S:206

Re: Question: How to access an I2C device from an ISR handler

Posted: Thu Jul 19, 2018 7:56 pm
by vonnieda
It's best to do as little as possible in an ISR and let another task handle any heavy lifting. What I do for this kind of thing is post an event to a queue and let another task process the event.

So, in pseudocode:

1. Create a queue with xQueueCreate.
2. Create a task with xTaskCreate and in it's handler function run an infinite loop calling xQueueReceive. This will block most of the time.
3. In your ISR, use xQueueSendFromISR to post an event to the queue. It can just be a 0 if you don't have any specific info to send.
4. Back in your task, when the ISR posts an event the queue will wake up and you can do whatever you need to, including access I2C.

Jason

Re: Question: How to access an I2C device from an ISR handler

Posted: Thu Jul 19, 2018 8:06 pm
by Deouss
I had similar problem once and what I did was I put critical section on whole interrupt.

Re: Question: How to access an I2C device from an ISR handler

Posted: Fri Jul 20, 2018 7:21 am
by MickPF
Hello,

I came up with the idea proposed by vonneida, but from my point of view, it's not a real solution, but a workaround. Then I can also use the touch panel in polling mode. It is not fine but it works...

If I see it right, it's a bug in ESP-IDF, which will not be fixed in the (near) future. After all, it is the purpose of ISR handler existence to retrieve data from an interface/port ...

I don't understand what Deouss means, because his information is too short. But I think it will most likely be a similar workaround.

Re: Question: How to access an I2C device from an ISR handler

Posted: Fri Jul 20, 2018 10:14 am
by ESP_Sprite
Okay, I think the issue is that you come from a more simple embedded environment, with just a bunch of peripherals and no complex things like WiFi, BT, USB or whatever, possibly without an OS to get between you and the bare metal. (Not implying there's anything wrong with that, I've written code for that environment many times and still do.) In this environment, it's indeed perfectly acceptable to just stash whatever you need in the ISR; it's a mostly elegant and simple solution to what you want.

However, the ESP32 is not like this: it has a RTOS, which complicates life with task switching. You may ask yourself: why did we do this? It only stands between you and writing your bare metal code, and introduces race conditions and other frustrating things, right?

Well, the answer is twofold: first of all, you're not alone on the CPU anymore, and secondly, you (or others) may want to use this 2x240MHz powerhouse for all CPU power it's worth.

Let's start off with the second bit. Say you're decoding an MP3 on your ESP32. The ESP32 does not have many buffers, so you probably want to keep decoding this MP3 at a steady speed. Now, for the user interface, you have written an interrupt handler that receives a touch interrupt and then asks the touch controller the data. Now what happens? The IRQ starts, the CPU tells the I2C controller to read the data... and then just twiddles its thumbs until the answer comes. The CPU is pretty fast and I2C is really slow, but because you're in an interrupt, the CPU can't just go back to decoding MP3s, it has to actively wait until the I2C-controller is done. And when it's finally done, the MP3 decoder may not have had enough time to do its decoding and your audio stutters.

For the first scenario, you have to imagine the MP3 data comes from somewhere. As the ESP32 is a WiFi-enabled chip, it's not unlikely it's streamed over WiFi. This means an entire WiFi-driver and IP-stack are hard at work keeping the connection open by recieving and responding to packets at a breakneck speed. If you then hold up the CPU in a interrupt handler, this stream gets stopped; the server may think the line is congested and throttle its output, and again, you get stutters in your audio stream.

So how is this solved? By keeping your interrupts to a minimum and using a task for anything that doesn't need to happen the exact microsecond the interrupt arrives. If you use a queue to communicate between the interrupt and the task, the task will normally be asleep and use zero percent CPU time, As soon as it awakes, you can use the I2C driver to interrogate the touch driver. And what'd'ya know, because you're now calling the I2C driver from a task, while it's waiting it can now ask the OS to do other things instead, and resume the task as soon as data gets in. So instead of twiddling its virtual thumbs, your CPU happily resumes decoding MP3s and answering to WiFi packets.

So is the absence of an IRQ-capable I2C drivers a bug? I'd say it is not, it may even be a feature, making it harder to shoot yourself in the foot and introducing all kinds of CPU-power and latency issues in other places. Instead, use vonnida's suggestion: it's not a workaround and as I said, unlike your poll-in-a-thread method it doesn't use any CPU power until an interrupt happens. It's actually not a workaround but a pretty well-known pattern; the Linux kernel for instance calls it separation of the top half and the bottom half of an interrupt handler.