ESP32 Dual Core: Periodic Task Interruption
Posted: 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:
Here you can see both outputs running at once: (D0: signal, D1: LED)
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.
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:
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
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:
- #include "freertos/task.h"
- void Task1code(void* pvParameters); //Initiates Task1code
- void Task2code(void* pvParameters); //Initiates Task2code
- TaskHandle_t Task1;
- TaskHandle_t Task2;
- // Digital pins
- const int signalPin = 4; // D4
- const int ledPin = 15; // D15
- // Variables
- int state = 0;
- long int nextTransition;
- void setup() {
- pinMode(signalPin, OUTPUT);
- pinMode(ledPin, OUTPUT);
- nextTransition = micros();
- //create a task that will be executed in the Task1code() function, with priority 1 and executed on core 0
- xTaskCreatePinnedToCore(
- Task1code, /* Task function. */
- "Task1", /* name of task. */
- 10000, /* Stack size of task */
- NULL, /* parameter of the task */
- 1, /* priority of the task */
- &Task1, /* Task handle to keep track of created task */
- 0); /* pin task to core 0 */
- delay(500);
- //create a task that will be executed in the Task2code() function, with priority 1 and executed on core 1
- xTaskCreatePinnedToCore(
- Task2code, /* Task function. */
- "Task2", /* name of task. */
- 10000, /* Stack size of task */
- NULL, /* parameter of the task */
- 1, /* priority of the task */
- &Task2, /* Task handle to keep track of created task */
- 1); /* pin task to core 1 */
- delay(500);
- }
- // ------------------ CODE FOR CORE 0 ------------------
- //Task1code: blinks an LED every 1000 ms
- void Task1code(void* pvParameters) {
- Serial.print("Task1 running on core ");
- Serial.println(xPortGetCoreID());
- for (;;) {
- digitalWrite(ledPin, HIGH);
- delay(1000);
- digitalWrite(ledPin, LOW);
- delay(1000);
- }
- }
- // ------------------ CODE FOR CORE 1 ------------------
- //Task2code: blinks an LED every 700 ms
- void Task2code(void* pvParameters) {
- Serial.print("Task2 running on core ");
- Serial.println(xPortGetCoreID());
- for (;;) {
- if (micros() >= nextTransition) { // current time exceeds next transition time
- if (state == 0) {
- digitalWrite(signalPin, HIGH); // switch state
- state = 1;
- }
- else if (state == 1) {
- digitalWrite(signalPin, LOW); // switch state
- state = 0;
- }
- nextTransition += 25; // next transition in 25us
- }
- }
- }
- void loop() { // empty
- }
Here you can see both outputs running at once: (D0: signal, D1: LED)
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.
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:
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