Reduce external interrupt latency

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

Re: Reduce external interrupt latency

Postby HelWeb » Mon Apr 22, 2019 7:04 pm

If you use a one-way-signal as a global variable the communication is much faster. One-Way means Task 1 only writes, Tasks 2 only reads. (volatile).
Using this mechanism it is possible to get cycles of 300ns. But there is a drawback:
The task in core 0 MUST run without a taskswitch, delay etc. That is possible, but it must have the lowest priority.
And that means, it will run in a perfect uninterrupted loop - until a forced taskswitch after one ms will interrupt this loop.
Maybe more often depending on the other tasks running. And the delay than my be some ms !
There are different solutions. One is to use the semaphore (s.o.). That needs 2 µs latency to start the waiting task RTOS_2 in core 0.. After that you get a cylcetime of ~300ns (disable interrupts for core 0).
Here is the source to show superfast interaction:
External interrupt detected by task Core1 --300ns--> RTOS_2 (core 0) reacts.
Use it with a scope or a logic analyser: 2700000 served interrupts/s
(Pins 18 and 19 must be shortened)

BTW: a cooperative multitasking using only core 1 may be an other solution ;) because core 1 never will be interrupted by RTOS.

  1. /*
  2.  * FastIRQGlobalVar
  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. #include "freertos/FreeRTOS.h"
  58. #include "freertos/task.h"
  59. #include "esp_task_wdt.h"
  60.  
  61.  
  62. // Dimensions the buffer that the task being created will use as its stack.
  63. // NOTE:  This is the number of bytes the stack will hold, not the number of
  64. // words as found in vanilla FreeRTOS.
  65. #define STACK_SIZE 4096
  66.  
  67. // Structure that will hold the TCB of the task being created.
  68. StaticTask_t xTaskBuffer;
  69.  
  70. // Buffer that the task being created will use as its stack.  Note this is
  71. // an array of StackType_t variables.  The size of StackType_t is dependent on
  72. // the RTOS port.
  73. StackType_t xStack[ STACK_SIZE ];
  74.  
  75.  
  76.  
  77. #define PinA GPIO_NUM_18  // output          shortcut   > _____
  78. #define PinB GPIO_NUM_19  // input                      < _____|----- scope
  79.  
  80.  
  81. TaskHandle_t xHandle1 = NULL;
  82. TaskHandle_t xHandle2 = NULL;
  83.  
  84. volatile int LinkCore1Core0=0;
  85. #define LONG_TIME 0xffff
  86.  
  87.    
  88.    
  89. /*
  90.  * Macro to check the outputs of TWDT functions and trigger an abort if an
  91.  * incorrect code is returned.
  92.  */
  93. #define CHECK_ERROR_CODE(returned, expected) ({                        \
  94.             if(returned != expected){                                  \
  95.                 printf("TWDT ERROR\n");                                \
  96.                 abort();                                               \
  97.             }                                                          \
  98. })
  99.  
  100.  
  101.  
  102. inline uint32_t IRAM_ATTR micros()
  103. {
  104. uint32_t ccount;
  105. asm volatile ( "rsr %0, ccount" : "=a" (ccount) );
  106. return ccount;
  107. }
  108.  
  109. void IRAM_ATTR delayMicroseconds(uint32_t us)
  110. {
  111.   if(us){
  112.     uint32_t m = micros();
  113.     while( (micros() - m ) < us ){
  114.       asm(" nop");
  115.     }
  116.   }
  117. }
  118.  
  119.  
  120.  
  121.  
  122. void Core1( void* p) {
  123.  
  124.    gpio_set_direction(PinA, GPIO_MODE_OUTPUT);
  125.    gpio_set_direction(PinB, GPIO_MODE_INPUT );
  126.    
  127.    
  128.    printf("Start Core 1\n");
  129.    vTaskDelay(1000);
  130.    
  131.    // I do not want an RTOS-Tick here
  132.    portDISABLE_INTERRUPTS();  // YEAH
  133.  
  134.    while(1) {
  135.      register int level;
  136.      level=REG_READ(GPIO_IN_REG) & (1<< PinB );
  137.      if (level) {
  138.        REG_WRITE(0x3ff4400c, 1<<PinA);// Low at 160 ns
  139.        LinkCore1Core0=1;
  140.      }
  141.    }
  142. }
  143.  
  144.  
  145.  
  146. // Function that creates a task to be pinned at Core 1
  147. void StartCore1( void )
  148. {
  149.     TaskHandle_t xHandle = NULL;
  150.  
  151.     xHandle = xTaskCreateStaticPinnedToCore(
  152.                   Core1,       // Function that implements the task.
  153.                   "Core1",          // Text name for the task.
  154.                   STACK_SIZE,      // Stack size in bytes, not words.
  155.                   ( void * ) 1,    // Parameter passed into the task.
  156.                   tskIDLE_PRIORITY+2,
  157.                   xStack,          // Array to use as the task's stack.
  158.                   &xTaskBuffer,    // Variable to hold the task's data structure.
  159.                   1);              // Core 1
  160.  
  161. }
  162.  
  163.  
  164. void RTOS_1(void *p) {
  165.   while(1) {
  166.     esp_task_wdt_reset();
  167.     printf("RTOS-1\n"); // demo for full function
  168.     vTaskDelay(1000);
  169.   }
  170. }
  171.  
  172.  
  173. void RTOS_2(void *p) {
  174.   while(1) {
  175.     REG_WRITE(0x3ff44008, 1<<PinA);// High
  176.     while (LinkCore1Core0==0);
  177.    
  178.     REG_WRITE(0x3ff44008, 1<<PinA);// High
  179.     // ... do something
  180.    
  181.   }
  182. }
  183.  
  184.  
  185. void app_main(void)
  186. {
  187.  
  188.     gpio_set_direction(PinA, GPIO_MODE_OUTPUT);
  189.     gpio_set_direction(PinB, GPIO_MODE_INPUT );
  190.    
  191.    
  192.    
  193.     xTaskCreate(
  194.                   RTOS_1,       // Function that implements the task.
  195.                   "RTOS-1",          // Text name for the task.
  196.                   STACK_SIZE,      // Stack size in bytes, not words.
  197.                   ( void * ) 1,    // Parameter passed into the task.
  198.                   tskIDLE_PRIORITY+2,
  199.                   &xHandle1);    // Variable to hold the task's data structure.
  200.                  
  201.                  
  202.     // Watchdog satisfied by RTOS_1:
  203.     CHECK_ERROR_CODE(esp_task_wdt_init(2 /*sec*/, false), ESP_OK);
  204.     CHECK_ERROR_CODE(esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)), ESP_OK);
  205.     CHECK_ERROR_CODE(esp_task_wdt_add(xHandle1), ESP_OK);
  206.    
  207.                  
  208.     xTaskCreate(
  209.                   RTOS_2,       // Function that implements the task.
  210.                   "RTOS-2",          // Text name for the task.
  211.                   STACK_SIZE,      // Stack size in bytes, not words.
  212.                   ( void * ) 1,    // Parameter passed into the task.
  213.                   tskIDLE_PRIORITY+1,
  214.                   &xHandle2);    // Variable to hold the task's data structure.
  215.    
  216.  
  217.  
  218.  
  219.     StartCore1();
  220.  
  221.                  
  222.     while(1) {
  223.       vTaskDelay(10000);
  224.     }
  225.    
  226. }
  227.  
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 » Fri Apr 26, 2019 2:42 am

Update2019/12:
This listing is for esp-idf Version 3.1
For version 4.1 see below !


I am awaiting a shit storm ;)

There are some people trying to start a program without RTOS using core 1.
It is not easy. You may think using make menuconfig to set RTOS to core0
and write a function start_cpu1 should do the job - but it does not!
If you look at components/esp32/cpu_start.c and components/freertos/port.c
you will find, that this way is not possible the easy way.
Well, I did'nt succeed.
But why should you want to do it?
One reason is the interrupt latency of 2 µs. Compared to other MCUs with one
core and less speed this value is not acceptable.
I am sure for many people in development departments interested in the whole
bunch of advatages of the Esp32 the big interrupt latency is a reason
not to use this part.
Espressif should do something to not lose a good reputation! Its not enough
to explain how it should be done. Its not so easy - otherwise we had a solution
since years.
Espressif should do it for the customers!

If someone gives me a tool to use core1 without RTOS AND a really fast interrupt
would be my hero.

Until then I have to live with my workaround and maybe some others would like to
test it. A workaround is far away from beeing a perfect solution - but better than nothing.

As I have explained, running an RTOS task (just one!) pinned to core 1 and disabling
all interrupts let us perform some functions without preemption.
This task may be (ab)used to scan pins and detect rising edges very fast - and call
"interrupt routines".
Here is a template to read the first 32 pins, mask the interesting input pins and
call interrupt routines (up to 32 different are possible) from a table of function pointers.
Fastest reaction time (from rising edge to first line in the interrupt routine) is about
160 ns. The interrupts have a priority: lower pin numbers get served first.
There are some examples in the program.

Fore some people 2 µs interrupt latency may be no problem. They should use the normal way.
But some others need a very fast interrupt response - and maybe this can help.
The times printed in ns may have an error +/- 20 ns or even more. Use a scope to test.

Until Espressif delivers the miracle of the 12 cycle interrupt latency mentioned here:
https://www.sciencedirect.com/topics/co ... pt-latency
this workaround may help.
  1. /*
  2.  * FastIRQ32
  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. #include "freertos/FreeRTOS.h"
  58. #include "freertos/task.h"
  59. #include "esp_task_wdt.h"
  60.  
  61. #include "xtensa/core-macros.h"
  62. #include "sdkconfig.h"
  63. #include "soc/rtc.h"
  64. //#include "esp32/clk.h"
  65. #include "esp_system.h"
  66. //#include "test_utils.h"
  67.  
  68.  
  69. // Dimensions the buffer that the task being created will use as its stack.
  70. // NOTE:  This is the number of bytes the stack will hold, not the number of
  71. // words as found in vanilla FreeRTOS.
  72. #define STACK_SIZE 4096
  73.  
  74. // Structure that will hold the TCB of the task being created.
  75. StaticTask_t xTaskBuffer;
  76.  
  77. // Buffer that the task being created will use as its stack.  Note this is
  78. // an array of StackType_t variables.  The size of StackType_t is dependent on
  79. // the RTOS port.
  80. StackType_t xStack[ STACK_SIZE ];
  81.  
  82.  
  83.  
  84. #define PinA GPIO_NUM_22  // output          shortcut   >------|
  85. #define PinB GPIO_NUM_23  // input                      <------|-----> scope
  86.  
  87.  
  88. TaskHandle_t xHandle1 = NULL;
  89. TaskHandle_t xHandle2 = NULL;
  90.  
  91. uint32_t ccount, StartTime, EndTime;
  92.  
  93. volatile int IsrOk=0;
  94. #define LONG_TIME 0xffff
  95.  
  96.    
  97.    
  98. /*
  99.  * Macro to check the outputs of TWDT functions and trigger an abort if an
  100.  * incorrect code is returned.
  101.  */
  102. #define CHECK_ERROR_CODE(returned, expected) ({                        \
  103.             if(returned != expected){                                  \
  104.                 printf("TWDT ERROR\n");                                \
  105.                 abort();                                               \
  106.             }                                                          \
  107. })
  108.  
  109.  
  110.  
  111.  
  112. // Here we prepare the Interrupt service Routines for
  113. // interrupts (rising edge) for pins 0-31
  114.  
  115. void Isr0(int param) {
  116. }
  117.  
  118. void Isr1(int param) {
  119. }
  120.  
  121. void Isr2(int param) {
  122. uint32_t Pin2Time;
  123.   __asm__ __volatile__("esync; rsr %0,ccount":"=a" (Pin2Time));
  124.   printf("here is pin2 at : %u\n", Pin2Time);
  125. }
  126.  
  127. void Isr3(int param) {
  128. }
  129.  
  130. void Isr4(int param) {
  131. }
  132.  
  133. void Isr5(int param) {
  134. }
  135.  
  136. void Isr6(int param) {
  137. }
  138.  
  139. void Isr7(int param) {
  140. }
  141.  
  142. void Isr8(int param) {
  143. }
  144.  
  145. void Isr9(int param) {
  146. }
  147.  
  148. void Isr10(int param) {
  149. }
  150.  
  151. /// .....
  152.  
  153. void Isr23(int param) {
  154. uint32_t diff;
  155.  
  156.   // this is only to measure latency
  157.   // it is not part of the interrupt detecting
  158.  
  159.   REG_WRITE( GPIO_OUT_W1TC_REG, 1<<PinA);// Low: Stop Pin HIGH of pinA set in RTOS_2
  160.   IsrOk=1;
  161.   printf("                  Isr23 ready\n");
  162.  
  163. }
  164.  
  165. // .......
  166.  
  167. void Isr30(int param) {
  168. }
  169.  
  170. void Isr31(int param) {
  171.   printf("IRQ 31\n");
  172. }
  173.  
  174. void IsrDefault(int param) {
  175.   printf("Default ISR: Pin %d\n", param);
  176.   // for bouncing contacts you should insert a delay
  177. }
  178.  
  179.  
  180. // This is the table of function pointers to the Interrupt Service Routines 0-31
  181. typedef void (*Func)(int);
  182. Func Isrs[32] = { IsrDefault, IsrDefault, Isr2,       IsrDefault, IsrDefault, IsrDefault, IsrDefault, IsrDefault, IsrDefault, IsrDefault,
  183.                   IsrDefault, IsrDefault, IsrDefault, IsrDefault, IsrDefault, IsrDefault, IsrDefault, IsrDefault, IsrDefault, IsrDefault,
  184.                   IsrDefault, IsrDefault, IsrDefault, Isr23,      IsrDefault, IsrDefault, IsrDefault, IsrDefault, IsrDefault, IsrDefault,
  185.                   IsrDefault, IsrDefault};
  186. //                                             ------ !!!!! -------
  187.  
  188.  
  189.  
  190. void Core1( void* p) {
  191. volatile uint32_t inReg;
  192. volatile uint32_t oldInReg;
  193. uint32_t inMask=0;
  194.  
  195.    gpio_set_direction(PinA, GPIO_MODE_OUTPUT);
  196.    gpio_set_direction(PinB, GPIO_MODE_INPUT );
  197.    //gpio_set_pull_mode(PinB, GPIO_PULLUP_ONLY);  // B
  198.    
  199.    
  200.    // set a mask to filter the pins which produce an interrupt on rising edge
  201.    inMask |= (1<<PinB);  // 23
  202.    inMask |= (1<<2);     // for experiments with pin2: Connect pin2 to 3.3V with a button (or wire)
  203.    inMask |= (1<<4);     // for experiments with pin4: Connect pin2 to 3.3V with a button (or wire)
  204.    
  205.    
  206.  
  207.    // prepare the start
  208.    inReg=(REG_READ(GPIO_IN_REG) & inMask);
  209.    oldInReg=inReg;  
  210.    
  211.    printf("Start Core 1\n");
  212.    vTaskDelay(1000);
  213.    
  214.    // I do not want an RTOS-Tick here
  215.    portDISABLE_INTERRUPTS();  // YEAH
  216.    
  217.    while(1) {
  218.      
  219.      do {
  220.        while(inReg == oldInReg) {
  221.          inReg=(REG_READ(GPIO_IN_REG) & inMask);
  222.        }
  223.        oldInReg=inReg;
  224.      } while (inReg == 0);
  225.      
  226.      // rising edge detected
  227.      
  228.      //            TESTTIME
  229.      
  230.          
  231.      //printf("EndTime %d\n",EndTime);
  232.      // now one of out pins in mask got level change
  233.      // we only react on rising edges
  234.      
  235. // ----------------------------------------                             scope values
  236.      // possibility 1)
  237.      // fastest:
  238.      // to measure the time until the rising edge is detected:          220 ns
  239.      // REG_WRITE( GPIO_OUT_W1TC_REG, 1<<PinA);// Low
  240.      // Without REG_WRITE, which costs 50ns, we are here after
  241.      // about 170 ns
  242. // ----------------------------------------
  243.      
  244.      //printf("Level changed %x\n", inReg);
  245.      
  246.      // possibility 2)
  247.      // call interrupt routine
  248.      // to measure time until the rising edge reaches IRQ-Routine       296 ns
  249.      // (direct calling)
  250.      //if (inReg & (1<<PinB)) Isrs[23]( 111);
  251.      
  252.      // possibility 3)
  253.      // test all Pins (0-31) and dispatch to interrupt routine
  254.      // to measure time if ALL pins are tested                          2 µs
  255.      
  256.      // the mask is prepared to allow interrupts at pin2
  257.      // and pin 4. Try it with a button or a wire against 3.3V
  258.      
  259.      //for (register int i=0; i<32; i++) {
  260.       //if (inReg & (1<<i)) Isrs[i]( i);
  261.      //}
  262.      
  263.      // possibility 4)
  264.      // test 4 pins and measure the time for 4. pin
  265.      // of the pins to test                      
  266.      
  267.      register uint32_t inRegTest = inReg;
  268.      
  269.      // Example: here we test pins 20 to 31
  270.      // our testpin  23  is the 4. pin to test:                         495 ns
  271.      inRegTest>>=20;  
  272.      for (register int i=20; i<31; i++) {
  273.        if (inRegTest & 1) Isrs[i](i);
  274.        inRegTest>>=1;
  275.      }
  276.      
  277.      // Example: here we test pins 23 to 31
  278.      // our testpin  23  is the 1. pin to test:                         375 ns
  279.      //inRegTest>>=23;  
  280.      //for (register int i=23; i<31; i++) {
  281.        //if (inRegTest & 1) Isrs[i](i);
  282.        //inRegTest>>=1;
  283.      //}
  284.      
  285.      
  286.      // Example: here we test pins 0 to 31
  287.      // here we can test Pin 2 and/or Pin4
  288.      // Simple priority: first tested is serviced first
  289.      // All Level-Changes at time TESTTIME are serviced, even if the changed
  290.      // again in the meantime.
  291.      // All short pulses between TESTTIME and end of all ServiceRoutines are lost
  292.      // But it is possible to test inside service routines again: complicated and
  293.      // Error-prone!
  294.      // Since the "Interrupt-Routines" are normal functions all RTOS-Commands to
  295.      // send Information to Core 0 are allowed.
  296.      // Core 1 function "Core1" does not yield !!! and should not be considered
  297.      // as an RTOS-Function though it is.
  298.      
  299.      //inRegTest>>=0;  
  300.      //for (register int i=0; i<31; i++) {
  301.        //if (inRegTest & 1) Isrs[i](i);
  302.        //inRegTest>>=1;
  303.      //}
  304.      
  305.      
  306.      
  307.      
  308.    }
  309.    
  310.    
  311.      
  312. }
  313.  
  314.  
  315.  
  316.  
  317. // Function that creates a task to be pinned at Core 1
  318. void StartCore1( void )
  319. {
  320.     TaskHandle_t xHandle = NULL;
  321.  
  322.     xHandle = xTaskCreateStaticPinnedToCore(
  323.                   Core1,           // Function that implements the task.
  324.                   "Core1",         // Text name for the task.
  325.                   STACK_SIZE,      // Stack size in bytes, not words.
  326.                   ( void * ) 1,    // Parameter passed into the task.
  327.                   tskIDLE_PRIORITY+2,
  328.                   xStack,          // Array to use as the task's stack.
  329.                   &xTaskBuffer,    // Variable to hold the task's data structure.
  330.                   1);              // Core 1
  331.  
  332. }
  333.  
  334.  
  335. void RTOS_1(void *p) {
  336.   while(1) {
  337.     esp_task_wdt_reset();
  338.     printf("RTOS-1\n"); // demo for full function
  339.     vTaskDelay(1000);
  340.   }
  341. }
  342.  
  343.  
  344. // This task simulates an interrupt at PinA
  345. void RTOS_2(void *p) {
  346.   vTaskDelay(2000); // let's start the interrupt controller first
  347.   while(1) {
  348.    
  349.     printf("----\n>>> RTOS-2: Set PinA\n");
  350.     // here we only produce a rising edge
  351.     // the "Interrupt Routine" resets the level of PinA to measure latency
  352.     //
  353.    
  354.     IsrOk=0;
  355.     REG_WRITE( GPIO_OUT_W1TS_REG, 1<<PinA);// High
  356.    
  357.     // very dirty, well, it is a workaround ;)
  358.     __asm__ __volatile__("esync; rsr %0,ccount":"=a" (StartTime));
  359.     //while(REG_READ( GPIO_IN_REG) & (1<<PinB) ) ;
  360.     while (IsrOk==0); // Wait for handshake of interrupt routine Isr23
  361.     __asm__ __volatile__("esync; rsr %0,ccount":"=a" (EndTime));
  362.     // this is not as precise as a scope - but not everyone has it
  363.     printf("<<< EndTime-StartTime %d 240MHz-CPU-Cycles",EndTime-StartTime );
  364.     printf(" = %d ns \n----\n",1000*(EndTime-StartTime)/240-80);
  365.    
  366.    
  367.     vTaskDelay(500);
  368.     // ... do something
  369.    
  370.   }
  371. }
  372.  
  373.  
  374. void app_main(void)
  375. {
  376.  
  377.     gpio_set_direction(PinA, GPIO_MODE_OUTPUT);
  378.     gpio_set_direction(PinB, GPIO_MODE_INPUT );
  379.    
  380.    
  381.    
  382.     xTaskCreate(
  383.                   RTOS_1,       // Function that implements the task.
  384.                   "RTOS-1",          // Text name for the task.
  385.                   STACK_SIZE,      // Stack size in bytes, not words.
  386.                   ( void * ) 1,    // Parameter passed into the task.
  387.                   tskIDLE_PRIORITY+2,
  388.                   &xHandle1);    // Variable to hold the task's data structure.
  389.                  
  390.                  
  391.     // Watchdog satisfied by RTOS_1:
  392.     CHECK_ERROR_CODE(esp_task_wdt_init(2 /*sec*/, false), ESP_OK);
  393.     CHECK_ERROR_CODE(esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(0)), ESP_OK);
  394.     CHECK_ERROR_CODE(esp_task_wdt_add(xHandle1), ESP_OK);
  395.    
  396.                  
  397.     xTaskCreate(
  398.                   RTOS_2,       // Function that implements the task.
  399.                   "RTOS-2",          // Text name for the task.
  400.                   STACK_SIZE,      // Stack size in bytes, not words.
  401.                   ( void * ) 1,    // Parameter passed into the task.
  402.                   tskIDLE_PRIORITY+1,
  403.                   &xHandle2);    // Variable to hold the task's data structure.
  404.    
  405.  
  406.  
  407.  
  408.     StartCore1();
  409.  
  410.                  
  411.     while(1) {
  412.       vTaskDelay(10000);
  413.     }
  414.    
  415. }
  416.  
  417.  
Last edited by HelWeb on Sat Dec 21, 2019 3:32 am, edited 1 time in total.
Fast is not fast enough :D

edigi32
Posts: 8
Joined: Fri Oct 05, 2018 6:49 am

Re: Reduce external interrupt latency

Postby edigi32 » Tue Jun 18, 2019 1:46 pm

Unfortunatelly what you've described here as a solution is not a real solution except maybe for the simplest cases.
Don't misunderstand me, I have stumbled to this issue as well, and asked for solution.
viewtopic.php?t=9418

What you describe here (if I understand it correctly) is basically dedicating the entire app core to polling GPIOs. The trap here that when you have to act on some GPIO change the time spent on that has to be minimized as every clock cycle spent in acting GPIO change is blind time for watching GPIO change.
Not only this, but some events are not directly related to GPIO change. E.g. in my case I use the built in PCNT to count external edges but since the counter is only 16 bits (or rather 15 due to sign bit/direction) overflow must be handled via interrupt.
So while it's very nice that you can watch 32 GPIOs with a single read (ESP32 has more but let's forget it now) in many cases it's not sufficient to watch only for GPIO but maybe for other things as well (ESP32 internal interrupts and it's not just my PCNT example).
Pretty quickly this won't scale well and you get into the magnitude of normal interrupt latency.

Even if in a simple case the gains are significant the price to be paid is high. You have to do some very strict linear programming in app core and relocate what's supposed to be on app core to pro core. Basically mess up structured code logic to fit to this peculiar architecture and work around the interrupt latency weekness.
If it appeals and works to you nothing is wrong with that.

However most of the people who need really fast interupt reaction time will just use the right tool for that and in the present form ESP32 does not belong to that circle.

Again don't misunderstand me, ESP32 is a fantastic package (good value) for many things and I like to work with it many cases, however when it comes to interrupt latency it's simply not competitive enough and it's very hard if not impossible to make a generic enough improvement to this. Anyone with some micro experience can immediatelly come to this kind of polling solution that you've created but can see also that it's not good enough for most cases, especially that it means sacrifying an entire core to it.

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

Re: Reduce external interrupt latency

Postby HelWeb » Thu Dec 12, 2019 2:46 am

@edigi32
You are right!
As I mentioned: "this workaround may help."
It is an act of despair. But the prices of ESP32 are so low ;)
Maybe for some of us it is a solution.
:D
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 » Sat Dec 21, 2019 3:23 am

The ESP32 has 2 cores and for most applications one core running RTOS-tasks is enough.
If we dedicate the 2. core to a single - never interrupted - task we can achieve - while running a WebServer !

- count and react on more than 3,000,000 external "interrupts" per second
- building pulses with a granularity of 50 ns (Nanos!)
- react on an external interrupt within 170 ns


For some it is a waste of a core - for others it is the solution they have searched for :D

Source and README.md: (for esp-idf Version 4.1)
https://github.com/MacLeod-D/ESp32-Fast-external-IRQs
Fast is not fast enough :D

DrMickeyLauer
Posts: 126
Joined: Sun May 22, 2022 2:42 pm

Re: Reduce external interrupt latency

Postby DrMickeyLauer » Fri Dec 22, 2023 5:37 pm

Resurrecting this thread, since there is an amazing patch from the community that might change the opportunities for software that requires very low latency: Take a look at https://github.com/espressif/esp-idf/pull/12800, and please test and comment, if you're interested!

Who is online

Users browsing this forum: Baidu [Spider] and 102 guests