Reduce external interrupt latency

pathob
Posts: 7
Joined: Tue Oct 11, 2016 8:08 am

Reduce external interrupt latency

Postby pathob » Sun Oct 08, 2017 9:55 pm

Hi,

we are doing some stuff with an external RF transceiver and need to respond to its interrupts as fast as (technically) possible. Writing into a queue in order to handle the interrupt in another task takes way too much time (about 13 us). Setting a bit and polling this bit in another task within an infinite loop is faster (2 us), but cannot be a real option, because this is waste of resources and prevents from deep sleep options. We would prefer to do some extensive stuff like SPI communication and printf's directly within the ISR, but this results in exceptions.

In Arduino, this is actually possible with a latency of 2-4 us. But we don't want to use Arduino...

What are our options?

Thank you very much in advance!
Last edited by pathob on Thu Oct 19, 2017 9:03 am, edited 1 time in total.

ESP_Angus
Posts: 2282
Joined: Sun May 08, 2016 4:11 am

Re: Reduce interrupt latency

Postby ESP_Angus » Sun Oct 08, 2017 10:39 pm

Hi Pathob,

Can you please post a simplified version of the (slow) code you have.

Angus

pathob
Posts: 7
Joined: Tue Oct 11, 2016 8:08 am

Re: Reduce interrupt latency

Postby pathob » Mon Oct 09, 2017 11:01 am

Hi Angus,

it is basically the GPIO example. I did a measurement again, the exact delay is 16 us.

Code: Select all

#include "esp_log.h"
#include "esp_intr_alloc.h"

#include "driver/gpio.h"

#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "freertos/queue.h"
#include "freertos/task.h"

#define GPIO_IN         GPIO_NUM_17
#define GPIO_OUT        GPIO_NUM_16

static const char *TAG = "APP";

static QueueHandle_t intr_queue = NULL;

static void IRAM_ATTR isr_handler(void* args) {
    gpio_num_t gpio_num = (gpio_num_t) args;
    xQueueSendFromISR(intr_queue, &gpio_num, NULL);
}

static void IRAM_ATTR isr_task(void* pvParams) {
    gpio_num_t gpio_num;
    int gpio_level = 0;

    while(1) {
        xQueueReceive(intr_queue, &gpio_num, portMAX_DELAY);

        gpio_level = gpio_get_level(gpio_num);
        gpio_set_level(GPIO_OUT, gpio_level);
        // ESP_LOGD(TAG, "GPIO[%d] intr, val: %d", gpio_num, gpio_level);
    }
}

static void IRAM_ATTR main_task(void* pvParams) {
    gpio_config_t gpio_conf;

    /*
     * Input pin
     */
    gpio_conf.intr_type = GPIO_PIN_INTR_ANYEDGE;
    gpio_conf.pin_bit_mask = BIT(GPIO_IN);
    gpio_conf.mode = GPIO_MODE_INPUT;
    gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    gpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
    gpio_config(&gpio_conf);

    /*
     * Output pin, "echos" input on interrupt
     */
    gpio_conf.intr_type = GPIO_PIN_INTR_DISABLE;
    gpio_conf.pin_bit_mask = BIT(GPIO_OUT);
    gpio_conf.mode = GPIO_MODE_OUTPUT;
    gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    gpio_conf.pull_down_en = GPIO_PULLUP_DISABLE;
    gpio_config(&gpio_conf);

    gpio_install_isr_service(0);
    gpio_isr_handler_add(GPIO_IN, isr_handler, (void*) GPIO_IN);

    intr_queue = xQueueCreate(10, sizeof(gpio_num_t));
    xTaskCreatePinnedToCore(isr_task, "isr_task", 2048, NULL, 10, NULL, 1);

    while (1) {}
}

void app_main() {
    xTaskCreatePinnedToCore(main_task, "main_task", 8192, NULL, 1, NULL, 0);
}
We also tried it with binary semaphors, but the latency was even worse.
Last edited by pathob on Thu Oct 19, 2017 9:00 am, edited 1 time in total.

WiFive
Posts: 3092
Joined: Tue Dec 01, 2015 7:35 am

Re: Reduce interrupt latency

Postby WiFive » Mon Oct 09, 2017 11:20 pm

Did you try adding a yieldfromisr

pathob
Posts: 7
Joined: Tue Oct 11, 2016 8:08 am

Re: Reduce interrupt latency

Postby pathob » Tue Oct 10, 2017 5:34 am

I tried it a while ago and caused an exception.

pathob
Posts: 7
Joined: Tue Oct 11, 2016 8:08 am

Re: Reduce external interrupt latency

Postby pathob » Thu Oct 19, 2017 9:23 am

So, today I had access to the oscilloscope again and this time I added "portYIELD_FROM_ISR" to my ISR handler so that I got the following:

Code: Select all

static void IRAM_ATTR isr_handler(void* args) {
    gpio_num_t gpio_num = (gpio_num_t) args;
    xQueueSendFromISR(intr_queue, &gpio_num, NULL);
    portYIELD_FROM_ISR();
}
The other code is equal to what I posted above.

Probably due to a release change of esp-idf (currently I'm on v2.1) the external interrupt latency without portYIELD_FROM_ISR was 18 us now. When I added portYIELD_FROM_ISR, the latency didn't get any better, but now it rarely ranges until 29 us!

We can do handling of external interrupts on an STM32Fx significantly less than 1 us (I don't have any concrete measurement right now), so please help me to improve these bad latency.

xryl669
Posts: 2
Joined: Tue Oct 09, 2018 4:31 pm

Re: Reduce external interrupt latency

Postby xryl669 » Tue Nov 06, 2018 9:03 pm

It's likely too late, but you have 3 options, depending on what are your latency expectations:
  1. If more than 10µs is acceptable and you still want to use a FreeRTOS task, use xTaskNotifyFromISR() in place of a queue, it's much faster. BTW, don't call portYieldFromISR unconditionally. You need to use the last parameter (it's a bool) from the XXXFromISR version, and only if it's true, call portYieldFromISR. Else, you'll end up scheduling away the task that should catch the event, lowering your latency.
  2. If you can live with 2µs latency, move reaction code into the interrupt (got ~2µs this way, not always feasible, BTW).
  3. If you want less, you'll have to learn/copy from the Xtensa assembly file found in esp32 folder (dport_panic_highint_hdl.S), and write a ASM version of the xt_highint5 version. For such a simple example, you might get a huge gain, but as soon as it becomes more complex, it's much much harder to do... and maintain.

User avatar
HelWeb
Posts: 36
Joined: Sat Mar 23, 2019 8:39 am

Re: Reduce external interrupt latency

Postby HelWeb » Mon Apr 22, 2019 3:16 am

It is a waste of processor time but is fast:
1) Start a task "Test" on core 1. In Test disable interrupts. All other tasks run on core 0 - where interrupts are allowed.
2) In program "Test" do an endless loop (which is never interrupted by a tick), which reads one (or more) pins.
Use the gpio- registers directly.
Watchdog for core 1 should be disabled. (menuconfig)
3) Echo the level of the tested pin to an output pin.

I tested a delay of 160ns. I think, this value is hard to beat with an interrupt routine. And the best: no assembler needed.
Fast is not fast enough :D

User avatar
HelWeb
Posts: 36
Joined: Sat Mar 23, 2019 8:39 am

Re: Reduce external interrupt latency to 160 nanos

Postby HelWeb » Mon Apr 22, 2019 7:56 am

/*
* FastIRQ
*
* Copyright (c) 2019, Dipl. Phys. Helmut Weber.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Helmut Weber ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the CoopOS library.
*
* Author: Helmut Weber <Dph.HelmutWeber@web.de>
*
* $Id: Task.h,v 1.1 2019/04/02 helmut Exp $
*/


#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/portmacro.h"

#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_event_loop.h"
#include "esp_task_wdt.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "rom/uart.h"
#include "driver/touch_pad.h"
#include "driver/gpio.h"



// Dimensions the buffer that the task being created will use as its stack.
// NOTE: This is the number of bytes the stack will hold, not the number of
// words as found in vanilla FreeRTOS.
#define STACK_SIZE 4096

// Structure that will hold the TCB of the task being created.
StaticTask_t xTaskBuffer;

// Buffer that the task being created will use as its stack. Note this is
// an array of StackType_t variables. The size of StackType_t is dependent on
// the RTOS port.
StackType_t xStack[ STACK_SIZE ];



#define PinA GPIO_NUM_18 // output shortcut > _____
#define PinB GPIO_NUM_19 // input < _____|----- scope


TaskHandle_t xHandle1 = NULL;
TaskHandle_t xHandle2 = NULL;



inline uint32_t IRAM_ATTR micros()
{
uint32_t ccount;
asm volatile ( "rsr %0, ccount" : "=a" (ccount) );
return ccount;
}

void IRAM_ATTR delayMicroseconds(uint32_t us)
{
if(us){
uint32_t m = micros();
while( (micros() - m ) < us ){
asm(" nop");
}
}
}




void Core1( void* p) {
gpio_set_direction(PinA, GPIO_MODE_OUTPUT);
gpio_set_direction(PinB, GPIO_MODE_INPUT );
printf("Start Core 1\n");
vTaskDelay(1000);

// I do not want an RTOS-Tick here
portDISABLE_INTERRUPTS(); // YEAH

while(1) {
register int level;
level=REG_READ(GPIO_IN_REG) & (1<< PinB );
if (level) {
// set the pinA (set in RTOS_2) to LOW as fast as possible
REG_WRITE(0x3ff4400c, 1<<PinA);// Low at 160 ns
REG_WRITE(0x3ff44008, 1<<PinA);// HIGH ~70ns
REG_WRITE(0x3ff4400c, 1<<PinA);// Low
}

}
}



// Function that creates a task to be pinned at Core 1
void StartCore1( void )
{
TaskHandle_t xHandle = NULL;

xHandle = xTaskCreateStaticPinnedToCore(
Core1, // Function that implements the task.
"Core1", // Text name for the task.
STACK_SIZE, // Stack size in bytes, not words.
( void * ) 1, // Parameter passed into the task.
//tskIDLE_PRIORITY,// Priority at which the task is created.
tskIDLE_PRIORITY+20,
xStack, // Array to use as the task's stack.
&xTaskBuffer, // Variable to hold the task's data structure.
1); // Core 1

}


void RTOS_1(void *p) {
while(1) {
printf("RTOS-1\n"); // demo for full function
vTaskDelay(1000);
}
}


void RTOS_2(void *p) {
while(1) {
//printf("RTOS-2\n");
gpio_set_level(PinA, 1);
vTaskDelay(1);
gpio_set_level(PinA, 0); // should not happen - Core1 should do it much faster !
vTaskDelay(10);

}
}


void app_main(void)
{

gpio_set_direction(PinA, GPIO_MODE_OUTPUT);
gpio_set_direction(PinB, GPIO_MODE_INPUT );


xTaskCreate(
RTOS_1, // Function that implements the task.
"RTOS-1", // Text name for the task.
STACK_SIZE, // Stack size in bytes, not words.
( void * ) 1, // Parameter passed into the task.
tskIDLE_PRIORITY+2,
&xHandle1); // Variable to hold the task's data structure.

xTaskCreate(
RTOS_2, // Function that implements the task.
"RTOS-2", // Text name for the task.
STACK_SIZE, // Stack size in bytes, not words.
( void * ) 1, // Parameter passed into the task.
tskIDLE_PRIORITY+2,
&xHandle2); // Variable to hold the task's data structure.




StartCore1();


while(1) {
vTaskDelay(10000);
}

}
Fast is not fast enough :D

User avatar
HelWeb
Posts: 36
Joined: Sat Mar 23, 2019 8:39 am

Re: Reduce external interrupt latency

Postby HelWeb » Mon Apr 22, 2019 5:37 pm

Someone may ask "What the hell should this has it to do with interrupts?"

Here is a code, where we use
(core 1)
xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
and
(core 0)
if( xSemaphoreTake( xSemaphore, LONG_TIME ) == pdTRUE ) {
// ... do something
}

The core 0 task does not know that the signal is not comming from a real interrupt.
The latency is 2µs
  1. /*
  2.  * FastIRQSemaphore
  3.  *
  4.  * Copyright (c) 2019, Dipl. Phys. Helmut Weber.
  5.  * All rights reserved.
  6.  *
  7.  * Redistribution and use in source and binary forms, with or without
  8.  * modification, are permitted provided that the following conditions
  9.  * are met:
  10.  * 1. Redistributions of source code must retain the above copyright
  11.  *    notice, this list of conditions and the following disclaimer.
  12.  * 2. Redistributions in binary form must reproduce the above copyright
  13.  *    notice, this list of conditions and the following disclaimer in the
  14.  *    documentation and/or other materials provided with the distribution.
  15.  * 3. Neither the name of the Institute nor the names of its contributors
  16.  *    may be used to endorse or promote products derived from this software
  17.  *    without specific prior written permission.
  18.  *
  19.  * THIS SOFTWARE IS PROVIDED BY Helmut Weber ``AS IS'' AND
  20.  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  21.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  22.  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
  23.  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  24.  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  25.  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  26.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  27.  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  28.  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  29.  * SUCH DAMAGE.
  30.  *
  31.  * This file is part of the CoopOS library.
  32.  *
  33.  * Author: Helmut Weber <Dph.HelmutWeber@web.de>
  34.  *
  35.  * $Id: Task.h,v 1.1 2019/04/02  helmut Exp $
  36.  */
  37.  
  38. //#pragma GCC optimize ("O2")
  39.  
  40. #include "freertos/FreeRTOS.h"
  41. #include "freertos/task.h"
  42. #include "freertos/portmacro.h"
  43.  
  44. #include "esp_wifi.h"
  45. #include "esp_system.h"
  46. #include "esp_event.h"
  47. #include "esp_event_loop.h"
  48. #include "esp_task_wdt.h"
  49. #include "nvs_flash.h"
  50. #include "driver/gpio.h"
  51. #include "driver/uart.h"
  52. #include "rom/uart.h"
  53. #include "driver/touch_pad.h"
  54. #include "driver/gpio.h"
  55. #include "soc/gpio_periph.h"
  56. #include "freertos/semphr.h"
  57.  
  58.  
  59.  
  60. // Dimensions the buffer that the task being created will use as its stack.
  61. // NOTE:  This is the number of bytes the stack will hold, not the number of
  62. // words as found in vanilla FreeRTOS.
  63. #define STACK_SIZE 4096
  64.  
  65. // Structure that will hold the TCB of the task being created.
  66. StaticTask_t xTaskBuffer;
  67.  
  68. // Buffer that the task being created will use as its stack.  Note this is
  69. // an array of StackType_t variables.  The size of StackType_t is dependent on
  70. // the RTOS port.
  71. StackType_t xStack[ STACK_SIZE ];
  72.  
  73.  
  74.  
  75. #define PinA GPIO_NUM_18  // output          shortcut   > _____
  76. #define PinB GPIO_NUM_19  // input                      < _____|----- scope
  77.  
  78.  
  79. TaskHandle_t xHandle1 = NULL;
  80. TaskHandle_t xHandle2 = NULL;
  81.  
  82. volatile int IsLow=0;
  83. SemaphoreHandle_t xSemaphore = NULL;
  84. #define LONG_TIME 0xffff
  85.  
  86.    
  87.  
  88.  
  89.  
  90. inline uint32_t IRAM_ATTR micros()
  91. {
  92. uint32_t ccount;
  93. asm volatile ( "rsr %0, ccount" : "=a" (ccount) );
  94. return ccount;
  95. }
  96.  
  97. void IRAM_ATTR delayMicroseconds(uint32_t us)
  98. {
  99.   if(us){
  100.     uint32_t m = micros();
  101.     while( (micros() - m ) < us ){
  102.       asm(" nop");
  103.     }
  104.   }
  105. }
  106.  
  107.  
  108.  
  109.  
  110. void Core1( void* p) {
  111. //static signed BaseType_t xHigherPriorityTaskWoken;
  112. static int xHigherPriorityTaskWoken =(int)pdTRUE;
  113.    gpio_set_direction(PinA, GPIO_MODE_OUTPUT);
  114.    gpio_set_direction(PinB, GPIO_MODE_INPUT );
  115.    
  116.    
  117.    printf("Start Core 1\n");
  118.    vTaskDelay(1000);
  119.    
  120.    // I do not want an RTOS-Tick here
  121.    portDISABLE_INTERRUPTS();  // YEAH
  122.  
  123.    while(1) {
  124.      register int level;
  125.      level=REG_READ(GPIO_IN_REG) & (1<< PinB );
  126.      if (level) {
  127.        REG_WRITE(0x3ff4400c, 1<<PinA);// Low at 160 ns
  128.        xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
  129.      }
  130.    }
  131. }
  132.  
  133.  
  134.  
  135. // Function that creates a task to be pinned at Core 1
  136. void StartCore1( void )
  137. {
  138.     TaskHandle_t xHandle = NULL;
  139.  
  140.     xHandle = xTaskCreateStaticPinnedToCore(
  141.                   Core1,       // Function that implements the task.
  142.                   "Core1",          // Text name for the task.
  143.                   STACK_SIZE,      // Stack size in bytes, not words.
  144.                   ( void * ) 1,    // Parameter passed into the task.
  145.                   tskIDLE_PRIORITY+2,
  146.                   xStack,          // Array to use as the task's stack.
  147.                   &xTaskBuffer,    // Variable to hold the task's data structure.
  148.                   1);              // Core 1
  149.  
  150. }
  151.  
  152.  
  153. void RTOS_1(void *p) {
  154.   while(1) {
  155.     printf("RTOS-1\n"); // demo for full function
  156.     vTaskDelay(1000);
  157.   }
  158. }
  159.  
  160.  
  161. void RTOS_2(void *p) {
  162.   while(1) {
  163.     REG_WRITE(0x3ff44008, 1<<PinA);// High
  164.     if( xSemaphoreTake( xSemaphore, LONG_TIME ) == pdTRUE ) {
  165.       // ... do something
  166.     }
  167.     //while(IsLow==0);
  168.   }
  169. }
  170.  
  171.  
  172. void app_main(void)
  173. {
  174.  
  175.     gpio_set_direction(PinA, GPIO_MODE_OUTPUT);
  176.     gpio_set_direction(PinB, GPIO_MODE_INPUT );
  177.    
  178.     xSemaphore = xSemaphoreCreateBinary();
  179.    
  180.    
  181.     xTaskCreate(
  182.                   RTOS_1,       // Function that implements the task.
  183.                   "RTOS-1",          // Text name for the task.
  184.                   STACK_SIZE,      // Stack size in bytes, not words.
  185.                   ( void * ) 1,    // Parameter passed into the task.
  186.                   tskIDLE_PRIORITY+2,
  187.                   &xHandle1);    // Variable to hold the task's data structure.
  188.                  
  189.     xTaskCreate(
  190.                   RTOS_2,       // Function that implements the task.
  191.                   "RTOS-2",          // Text name for the task.
  192.                   STACK_SIZE,      // Stack size in bytes, not words.
  193.                   ( void * ) 1,    // Parameter passed into the task.
  194.                   tskIDLE_PRIORITY+20,
  195.                   &xHandle2);    // Variable to hold the task's data structure.
  196.    
  197.  
  198.  
  199.  
  200.     StartCore1();
  201.  
  202.                  
  203.     while(1) {
  204.       vTaskDelay(10000);
  205.     }
  206.    
  207. }
  208.  
  209.  
Fast is not fast enough :D

Who is online

Users browsing this forum: Majestic-12 [Bot] and 44 guests