Esp32 - getting to the core of the task

I had this laying around. Not exactly what you proposed, but feel free to convert it into the example you want. The LED On/Off times are all prime numbers. So, you get some good, seemingly random, blink interleaving.

To see the diagnostic prints, set the Core Debug Level to "Info". Otherwise, set it to "Error".

#include "Arduino.h"

struct Blink {
  uint8_t pin;
  TickType_t period;
};

void blinkingTask(void *pvParameters);

void setup() {
  Serial.begin(115200);
  vTaskDelay(2000);
  log_i("Starting");

  Blink blinkingData[] = {
    { 12, 1154 },
    { 27, 2066 },
    { 33, 4138 },
    { 15, 6002 }
  };
  const size_t numBlinks = sizeof(blinkingData) / sizeof(blinkingData[0]);

  char taskName[25];

  for (uint8_t taskNum = 0; taskNum < numBlinks; taskNum++) {
    sprintf(taskName, "Blink Task - %d", taskNum);
    BaseType_t returnCode = xTaskCreatePinnedToCore(blinkingTask, taskName, 4000, blinkingData + taskNum, 2, NULL, CONFIG_ARDUINO_RUNNING_CORE);
    if (returnCode != pdPASS) {
      log_e("Failed to create task: %s. Resetting", taskName);
      vTaskDelay(1000);
      ESP.restart();
    }
    log_i("Task: %s started", taskName);
  }

  // Delay long enough for all tasks to start before blinkingData is destroyed
  vTaskDelay(1000);
}

void loop() {
}

void blinkingTask(void *pvParameters) {
  Blink *blinkInfo = reinterpret_cast<Blink*>(pvParameters);
  uint8_t pin = blinkInfo->pin;
  TickType_t halfPeriod = blinkInfo->period / 2;
  pinMode(pin, OUTPUT);
  TickType_t wakeTime = xTaskGetTickCount();

  for (;;) {
    log_i("Time: %10u, Pin %2d LOW", xTaskGetTickCount(), pin);
    digitalWrite(pin, LOW);
    xTaskDelayUntil(&wakeTime, halfPeriod);
    log_i("Time: %10u, Pin %2d HIGH", xTaskGetTickCount(), pin);
    digitalWrite(pin, HIGH);
    xTaskDelayUntil(&wakeTime, halfPeriod);
  }
}
2 Likes

Can't we store constant data outside RAM?

Where?
Anyway, as a local variable, it's on the stack while setup() runs. It goes away when setup() exits. That's as long as it's needed.

In PROGMEM?

And where do the values come from?

Take a look at the definition of "PROGMEM" for ESP32 in pgmspace.h:

#define PROGMEM

It's only there for compatibility with code for other Arduino platforms:

I initialize them here:

  Blink blinkingData[] = {
    { 12, 1154 },
    { 27, 2066 },
    { 33, 4138 },
    { 15, 6002 }
  };

Oh ok

ok, will do

Oh, I see

Cool

Currently, I am experimenting with the prototype sketch in order to prepare self testing questions which should also be appealing to the Forum Members. Thanks for the follow up.

I have uploaded the following sketch into ESP32 and my observation on the outcome is accounted below. The self testing questions will appear in my post #10 after reading the sketch more carefully and the task switching theory behind multitasking.

//function declarations
TaskHandle_t  Task10Handle;
TaskHandle_t  Task11Handle;

//GPIO definitions
#define LED   2
#define LED10 21
#define LED11 22

//task creation using FreeRTOS.h Librray functions
void setup()
{
  Serial.begin(9600);
  pinMode(LED, OUTPUT);
  pinMode(LED10, OUTPUT);
  pinMode(LED11, OUTPUT);

  xTaskCreatePinnedToCore(Task10, "Task-10", 2048, NULL, 1, &Task10Handle, 1);
  xTaskCreatePinnedToCore(Task11, "Task-11", 2048, NULL, 1, &Task10Handle, 1);
}

//LED blinks at 100 ms pause
void loop()
{
  digitalWrite(LED, HIGH);
  delay(100);
  digitalWrite(LED, LOW);
  delay(100);
}

//LED10 blins at 500 ms interval
void Task10(void *pvParameters)
{
  while (true)
  {
    digitalWrite(LED10, HIGH);
    delay(500);
    digitalWrite(LED10, LOW);
    delay(500);
  }
}

//LED11 blinks at 1000 ms interval
void Task11(void *pvParameters)
{
  while (true)
  {
    digitalWrite(LED11, HIGH);
    delay(1000);
    digitalWrite(LED11, LOW);
    delay(1000);
  }
}

When I look only on LED (covering the other two), I observe that it keeps blinking at 100 ms interval. This is fine for me.

When I look only on LED10 (covering the other two), I observe that it keeps blinking at 500 ms interval. This is fine for me.

When I look only on LED11 (covering the other two), I observe that it keeps blinking at 10000 ms interval. This is fine for me.

When I look onto all the three LEDs, I observe that they are blinking smoothly (no random, no interleaving) maintaining their respective intervals, and it appears that they are blinking concurrently which is not possible with a sketch that runs three subprograms sequentially. Now, slowly understanding the beauty and utility of "single core multitasking operation". Thanks for the advice to begin the study with a single core.

Aside from being slightly more cluttered and less concise (my opinion only), that's essentially the same as the code I posted.

You obviously don't understand the problem:
Where reside the values before they are copied into RAM?

There're literal constants in the program flash. What are you trying to get at?

It's true to the definition of delay() on the ESP: it's equivalent to vTaskDelay() and blocks a task until the time has elapsed. The cores are not noticeably burdend by your example, you can add as many concurrently blinking LEDs as you like.

So is it possible to use a const record in flash directly instead of getting it copied into the stack?

I don't know. Please post a working modification of my code that does that.

Me 2, that's why I'm asking.

This is also what I believe. It is not only the ESP32, it also the case with the newer 8 bit processors such as the ATmega4809 where there is a unified address space between flash and RAM. The compiler simply assigns constants (literals) to the flash memory and they have no existence in RAM ( stack/heap) and all that without the programmer explicitly using "PROGMEM".
If someone has a clearer explanation I'd be interested to hear it (without dragging this thread too much off topic, that is).

4 Likes

It would likely be the linker doing that, but valid point. EXCEPT the ESP32 is a oddball. The flash interfaces via SPI. That's glacially slow compared to the processor. So, I'll opine that chunks of code & constants are loaded into processor RAM / Cache (or IRAM_ATTR if specified) as execution proceeds. Only if there's an access miss does it pull from SPI flash. It would just be too slow going out to SPI flash for data / instructions every time they're needed.

3 Likes

Thanks for this clarification. I already suspected that even on the 8 bitters the PROGMEM values are copied to RAM before use. But I understand that all this is a different topic.

No No, Don't worry, As the O.P. I'm not fussed, i still see this as being On-Topic
and i'm working on stuff in the background anyway and until i get back to this post
Feel Free to continue this line of questioning . By All means.