ESP32 Dual Core: Periodic Task Interruption

LukeMet
Posts: 4
Joined: Tue Jul 30, 2024 10:44 am

ESP32 Dual Core: Periodic Task Interruption

Postby LukeMet » Tue Jul 30, 2024 2:26 pm

Hi there :)

I am trying to use the ESP32's dual cores as follows:
- Core 1: generate a certain, high-speed, data-encoded signal across multiple channels
- Core 0: Handle any other user inputs to modify any of the signals, and any other functions.

I have developed the code to generate the signals on an ESP32 without the use of splitting cores, and the code is working well. However, now I would like the run the code on independent cores as described above, so I made an initial test using the standard "dual core" example of blinking one LED on each of the cores, except now that Core0 handles the BlinkLED task (Task1), and Core1 handles the signal generation (Task 2).

When I ran this, I found that my signal generation task is interrupted periodically (about 2ms), and cannot produce the signals I expect.

The details of the signal are not relevant to my question, so I can simplify the waveform to the following and still replicate the issue:
- A square wave with a period of 50us.

I wrote a small function for the signal generation, and a small function for the BlinkLED, which each work as expected when run individually on my ESP32 (no core splitting). I then wrote a program that set these up as Task1 and Task2, to run on Core0 and Core1 respectively:
  1. #include "freertos/task.h"
  2.  
  3. void Task1code(void* pvParameters);  //Initiates Task1code
  4. void Task2code(void* pvParameters);  //Initiates Task2code
  5.  
  6. TaskHandle_t Task1;
  7. TaskHandle_t Task2;
  8.  
  9. // Digital pins
  10. const int signalPin = 4;  // D4
  11. const int ledPin = 15;  // D15
  12.  
  13. // Variables
  14. int state = 0;
  15. long int nextTransition;
  16.  
  17. void setup() {
  18.   pinMode(signalPin, OUTPUT);
  19.   pinMode(ledPin, OUTPUT);
  20.  
  21.   nextTransition = micros();
  22.  
  23.   //create a task that will be executed in the Task1code() function, with priority 1 and executed on core 0
  24.   xTaskCreatePinnedToCore(
  25.     Task1code, /* Task function. */
  26.     "Task1",   /* name of task. */
  27.     10000,     /* Stack size of task */
  28.     NULL,      /* parameter of the task */
  29.     1,         /* priority of the task */
  30.     &Task1,    /* Task handle to keep track of created task */
  31.     0);        /* pin task to core 0 */
  32.   delay(500);
  33.  
  34.   //create a task that will be executed in the Task2code() function, with priority 1 and executed on core 1
  35.   xTaskCreatePinnedToCore(
  36.     Task2code, /* Task function. */
  37.     "Task2",   /* name of task. */
  38.     10000,     /* Stack size of task */
  39.     NULL,      /* parameter of the task */
  40.     1,         /* priority of the task */
  41.     &Task2,    /* Task handle to keep track of created task */
  42.     1);        /* pin task to core 1 */
  43.   delay(500);
  44. }
  45.  
  46. // ------------------ CODE FOR CORE 0 ------------------
  47. //Task1code: blinks an LED every 1000 ms
  48. void Task1code(void* pvParameters) {
  49.   Serial.print("Task1 running on core ");
  50.   Serial.println(xPortGetCoreID());
  51.  
  52.   for (;;) {
  53.     digitalWrite(ledPin, HIGH);
  54.     delay(1000);
  55.     digitalWrite(ledPin, LOW);
  56.     delay(1000);
  57.   }
  58. }
  59.  
  60. // ------------------ CODE FOR CORE 1 ------------------
  61. //Task2code: blinks an LED every 700 ms
  62. void Task2code(void* pvParameters) {
  63.   Serial.print("Task2 running on core ");
  64.   Serial.println(xPortGetCoreID());
  65.  
  66.   for (;;) {
  67.  
  68.     if (micros() >= nextTransition) {  // current time exceeds next transition time
  69.       if (state == 0) {
  70.         digitalWrite(signalPin, HIGH);  // switch state
  71.         state = 1;
  72.       }
  73.       else if (state == 1) {
  74.         digitalWrite(signalPin, LOW);  // switch state
  75.         state = 0;
  76.       }
  77.       nextTransition += 25;  // next transition in 25us
  78.     }
  79.   }
  80. }
  81.  
  82. void loop() { // empty
  83. }

Here you can see both outputs running at once: (D0: signal, D1: LED)
DualCoreBothSignals.png
DualCoreBothSignals.png (30.08 KiB) Viewed 1283 times

However, looking closer, you can see that the output on D0 is interrupted periodically for 1ms, every 1ms. This should just simply show a constant square wave with a period of 50us.
DualCoreSignalZoom1.png
DualCoreSignalZoom1.png (47.21 KiB) Viewed 1283 times

Furthermore, when the interruption ends, the output switches much more rapidly than expected for a while, then goes back to the expected 50us period, before being interrupted again. The rapid transitions occur with a period of about 4ms:
DualCoreSignalZoom2.png
DualCoreSignalZoom2.png (18.31 KiB) Viewed 1283 times

I have read Espressif's documentation on the tick interrupt, time-slicing, core pinning (affinity) and priotitisation. It would seem that the 1ms interrupt is connected to the tick interrupt, and the rapid transitions afterward are perhaps tasks that the scheduler delayed due to the interrupt, then executed rapidly afterwards. However, as far as I understand I have only created two tasks and pinned one task to each core. So when a new tick-interrupt arrives, neither of the tasks should be neglected in order to run some other task, and there should be no priority clashes between the two tasks.

Would really appreciate your help in understanding the cause of these interrupts, perhaps more insight on scheduling/priority/tick-interrupt, and most importantly: how I can configure my system to generate these signals constantly without interruption :)

ESP_Sprite
Posts: 9757
Joined: Thu Nov 26, 2015 4:08 am

Re: ESP32 Dual Core: Periodic Task Interruption

Postby ESP_Sprite » Wed Jul 31, 2024 3:59 am

Maybe not the answer you want to hear: Use hardware peripherals. As the ESP32 cores have other things to do, they're not that good at bitbanging protocols. That's the reason the ESP32 includes a lot of flexible peripherals: e.g. the parrallel mode of the I2S peripheral or the RMT peripheral are very capable of generating arbritrary signals without the CPU continuously needing to mind them.

vvb333007
Posts: 3
Joined: Wed Jul 31, 2024 5:53 am

Re: ESP32 Dual Core: Periodic Task Interruption

Postby vvb333007 » Wed Jul 31, 2024 6:03 am

Your Task2 (protocol handling) is not alone on Core#1 . Arduino also runs on Core#1. Try to switch cores for your protocol generation, let the protocol part to be on Core#0. Just an idea.

LukeMet
Posts: 4
Joined: Tue Jul 30, 2024 10:44 am

Re: ESP32 Dual Core: Periodic Task Interruption

Postby LukeMet » Thu Aug 01, 2024 11:01 am

Thanks a bunch @ESP_Sprite, I've replicated my program using the RMT peripheral, and as you say, the CPU doesn't need to constantly mind the signal generation, so I don't encounter this issue.

Just wondering if you've seen any documentation/support for the Sync Manager for Arduino? I will have to sync my signal channels in pairs, and this doesn't seem documented in Arduino, although it is for IDF.

And thanks for the suggestion @vvb333007 - I no longer face the issue I described above but will try out your suggestion for interest. And still curious about what's happening behind the scenes of that issue if either of you have further insight :)

ESP_Sprite
Posts: 9757
Joined: Thu Nov 26, 2015 4:08 am

Re: ESP32 Dual Core: Periodic Task Interruption

Postby ESP_Sprite » Fri Aug 02, 2024 2:44 am

I don't think the sync manager functionality is supported in the Arduino native libraries, but as Arduino for the ESP32 is built on top of ESP-IDF, you should be able to use that API in your Arduino code.

Who is online

Users browsing this forum: No registered users and 135 guests