espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.35k stars 7.21k forks source link

Can we have SPI transfert during an interrupt execution ? (IDFGH-11904) #1428

Closed vortex314 closed 6 years ago

vortex314 commented 6 years ago

As i am working on a local positioning system based on DWM1000 from DecaWave, the time between interrupts and processing data need to be as small as possible < 1msec. So the ESP32 is triggered by an external signal and then executes an SPI transfert with the device during the interrupt. When trying this I get an exception , which makes me suspect that the SPI handling can not be invoked from an interrupt cycle. 0x400888d6: vListInsert at /home/lieven/esp/esp-idf/components/freertos/./list.c:188 (discriminator 1) 0x40087283: vTaskPlaceOnEventList at /home/lieven/esp/esp-idf/components/freertos/./tasks.c:3485 0x40085fec: xQueueGenericReceive at /home/lieven/esp/esp-idf/components/freertos/./queue.c:2520 0x40135049: spi_device_get_trans_result at /home/lieven/esp/esp-idf/components/driver/./spi_master.c:727 0x401139e3: SPI_ESP32::exchange(Bytes&, Bytes&) at /home/lieven/workspace/esp-ebos/main/./Hardware.cpp:240 0x4012e13f: readfromspi at /home/lieven/workspace/esp-ebos/components/DWM1000/./decaSpi.cpp:51 0x4012d2c9: dwt_readfromdevice at /home/lieven/workspace/esp-ebos/components/DWM1000/./deca_device.c:3149 0x4012d31d: dwt_read32bitoffsetreg at /home/lieven/workspace/esp-ebos/components/DWM1000/./deca_device.c:3149 0x4012dc3d: dwt_isr at /home/lieven/workspace/esp-ebos/components/DWM1000/./deca_device.c:3149 0x4012e283: dwt_to_isr(void*) at /home/lieven/workspace/esp-ebos/components/DWM1000/./DWM1000.cpp:259 0x40084dfa: gpio_intr_service at /home/lieven/esp/esp-idf/components/driver/./gpio.c:428 0x400829d5: _xt_lowint1 at /home/lieven/esp/esp-idf/components/freertos/./xtensa_vectors.S:1105 0x400d25ab: esp_vApplicationIdleHook at /home/lieven/esp/esp-idf/components/esp32/./freertos_hooks.c:85

kewalmshah commented 6 years ago

Yes! You can use queue for interrupt handling which can ne seen from the log. Can you post the source code for more help on this ? thanks

vortex314 commented 6 years ago

@kewalmshah thanks for the support. In the mean time I addressed the issue in another way. 1/ I created a separate task that is being waked up by the ISR . based on vTaskNotifyGiveFromISR from ISR . the task executing the SPI transferts . however I measured a "big" delay between ISR invocation and the task started : 800 µsec to 1300 µsec . so in most of the case that the interrupt was invoked, the CPU was already too late to react to the external chip 2/ Based on 1/ I pinned the task to another CPU : xTaskCreatePinnedToCore . now the delay between interrupt and task wake up is : 10-15 µsec . I guess in the first case the 1/ the CPU core was busy with other tasks. I notice the message in the logs : I (203) cpu_start: Pro cpu start user code , maybe something related that the app is not running on the App cpu. . see : https://github.com/vortex314/esp-ebos/blob/master/components/DWM1000/DWM1000_Anchor.cpp I'll give it later another try to do SPI from interrupt.

tostasmistas commented 6 years ago

Hello,

I came across this issue as I am facing a similar problem. Is it in fact possible to have this kind of nested interrupts, that is, to do an SPI transaction within another interrupt?

At the moment I have an external ADC being interfaced via SPI, that triggers a GPIO interrupt (attached to run on core 1) at the frequency of the predefined sampling rate. Within this GPIO interrupt I would like to do an SPI transaction to fetch the samples, but I then have this problem:

Guru Meditation Error: Core  0 panic'ed (Interrupt wdt timeout on CPU0)
Register dump:
PC      : 0x40085b54  PS      : 0x00060034  A0      : 0x8008726c  A1      : 0x3ffb05a0
0x40085b54: uxPortCompareSet at C:/msys32/home/mreis/esp/esp-idf/components/freertos/tasks.c:4571
 (inlined by) vPortCPUAcquireMutexIntsDisabled at C:/msys32/home/mreis/esp/esp-idf/components/freertos/portmux_impl
.h:86
 (inlined by) vTaskEnterCritical at C:/msys32/home/mreis/esp/esp-idf/components/freertos/tasks.c:4216
0x40085b54: uxPortCompareSet at C:/msys32/home/mreis/esp/esp-idf/components/freertos/tasks.c:4571
 (inlined by) vPortCPUAcquireMutexIntsDisabled at C:/msys32/home/mreis/esp/esp-idf/components/freertos/portmux_impl
.h:86
 (inlined by) vTaskEnterCritical at C:/msys32/home/mreis/esp/esp-idf/components/freertos/tasks.c:4216
0x40087269: xQueueGenericSendFromISR at C:/msys32/home/mreis/esp/esp-idf/components/freertos/queue.c:2037
0x4008497c: spi_intr at C:/msys32/home/mreis/esp/esp-idf/components/driver/spi_master.c:333
0x40082b39: _xt_lowint1 at C:/msys32/home/mreis/esp/esp-idf/components/freertos/xtensa_vectors.S:1105
0x400d2023: esp_vApplicationIdleHook at C:/msys32/home/mreis/esp/esp-idf/components/esp32/freertos_hooks.c:85

It looks to me like the problem occurs inside the SPI interrupt, when the transaction elements are to be placed in a queue.

To overcome it I have since changed the interrupt to notify a task (using FreeRTOS task notifications) that also runs on core 1 and that works. However, I would rather have the transaction inside the interrupt if it were possible, to minimize the delay of the entire operation.

The interrupt is configured as follows through a task running on core 1:

gpio_set_intr_type(DRDY_GPIO, GPIO_INTR_NEGEDGE);
gpio_install_isr_service(ESP_INTR_FLAG_IRAM); // install GPIO ISR service
gpio_isr_handler_add(DRDY_GPIO, drdyISR, NULL); // hook ISR handler for DRDY pin

And in the interrupt I would like to do this:

void IRAM_ATTR drdyISR() {
  spi_transaction_t transaction;
  static uint8_t dataOut[9]; // 24 status bits + 24 bits x 2 channels

  ext_adcSelectSlave();

  for (uint8_t i = 0; i < 9; ++i) {
    memset(&transaction, 0, sizeof(transaction)); // zero out the transaction
    transaction.flags = SPI_TRANS_USE_RXDATA;
    transaction.length = 8;
    transaction.rxlength = 0;
    transaction.tx_buffer = NULL;
    spi_device_transmit(ext_adcHandler, &transaction);
    dataOut[i] = *(uint8_t*)transaction.rx_data;
  }

  ext_adcDeselectSlave();
}

Thanks

vortex314 commented 6 years ago

@tostasmistas , my POV is that the mutex mechanisms like you see in the stacktrace prevent this. I see there : vPortCPUAcquireMutexIntsDisabled which is happening within an interrupt itself. As Freertos makes a distinction with mutex calls coming from an ISR or a user task, my guess is that the spi interface is based on a call from a user task. The only way around it would be to write your own SPI routines at register level. But again it's my best guess on this issue. Looking at the stack trace the interrupt locks a mutex that it tries to lock again a bit further on, so it gets in a deadlock with his own and the WDT fires.

tostasmistas commented 6 years ago

@vortex314 thanks for the reply. When you mentioned the option to write the SPI routines at register level I searched around for alternatives implementations of the SPI master driver.

I ended up founding this driver that does non-queued data transfers and a first implementation seems to indicate that SPI transactions are working fine while done inside the GPIO interrupt.

E-Lagori commented 8 months ago

I am using spi_device_polling_transmit(), It works correctly with that

It only works when the vtaskdelay in the main loop is 100ms or more

while(1){ // ESP_LOGI("ADC Value","%f",dadc2432_getADCval(&adc)); if (adc.memovfl){ for (int i = 0; i<MEMLEN; i++) ESP_LOGI("SPI","%d. %f",i,adc.mem[i]119.20928955078125e-9 5 +2.5); adc.memovfl = 0; }

    vTaskDelay(100/portTICK_PERIOD_MS); // This is important
}