Микроконтроллер ESP32 со встроенными возможностями беспроводной связи по Wi-Fi и Bluetooth представляет собой довольно мощный инструмент для реализации концепции Интернета вещей. При этом немаловажным моментом является наличие двух высокопроизводительных ядер в одном корпусе.

Впрочем, их использование не слишком прозрачно и гибко. Конфигурация по умолчанию назначает первое ядро (core_0) на выполнение задач радиочастотного модуля и протоколов беспроводной связи, а код, скомпилированный, например, в Arduino IDE, назначается второму ядру (core_1). Операционная система FreeRTOS может управлять всем процессом, поэтому программисту не нужно напоминать, какое ядро что делает. Тем не менее, в некоторых практических целях или чисто из любопытства хочется по управлять обоими ядрами, и в этом материале будет показано, как это сделать.
ESP8266 мог справиться с серьезными задачами с помощью лишь одного ядра. Имея больше доступных вычислительных ресурсов, ESP-32 можно было бы рассматривать как расширенный ESP8266, если бы не этот второй процессор. Поэтому, возможно, core_0 не так занят, как одно ядро ESP8266, и если это так, то ядро с кодом Arduino должна иметь возможность «красть» немного времени у core_0 и запускать рабочую нагрузку Arduino на обоих ядрах. Espressif предоставляет (почти) всю документацию на ESP-32 в Интернете. Специальный код, который автору скетча Arduino необходим для создания задач выглядит так:
xTaskCreatePinnedToCore(
Task1, /* pvTaskCode */
"Workload1", /* pcName */
1000, /* usStackDepth */
NULL, /* pvParameters */
1, /* uxPriority */
&TaskA, /* pxCreatedTask */
0); /* xCoreID */
Из вышеизложенного мы создали Task1 (имя функции), и теперь нашими намерениями является запустить эту задачу на core_0. & TaskA ссылается на новый дескриптор (обработчик) объекта, созданный более ранним кодом:
TaskHandle_t TaskA, TaskB;Учитывая приведенный выше код, я также создадим Task2 (имя функции):
xTaskCreatePinnedToCore(
Task2,
"Workload2",
1000,
NULL,
1,
&TaskB,
1);
И TaskB будет работать на core_1, который является базовым ядром по умолчанию для всего кода Arduino. Task1 и Task2 представлены двумя функциями с тем же именем. В данном коде Arduino вызываются заголовочные файлы Task1.h и Task2.h, выполняемые внутри функций (рабочая нагрузка, WorkLoad), и этот код (в данном примере) работает с той же самой рабочей нагрузкой, представляющей собой просто кучу вычислений, необходимых для потребления циклов. В действительности каждая именованная функция будет выполняться по-разному. Функции имеют дескриптор контекста TaskA и TaskB соответственно, который был создан оператором TaskHandle_t.
core_1 – это место, где код Arduino стартует по умолчанию. Это означает, что setup() работает на core_1, как и loop(). Разумеется, в функционировании по умолчанию core_1 обрабатывает все, что происходит в секции loop(). Определив новую Task2, мы добавим вторую задачу (функцию) в core_1, которая является дескриптором объекта TaskB.
По расширению Task1 (функция с некоторыми задачами) выполняется в контексте TaskA (дескриптор) и работает на core_0, поэтому это становится дополнительной задачей, так как многие задачи Espressif уже запущены на этом ядре. Обработчик объекта TaskA тогда является, по сути, заимствованным ресурсом из блока радиочастотных задач, которому присваивается core_0. Следует проявлять осторожность при загрузке core_0, так как, как и в случае с ESP8266, если стеки протоколов не обслуживаются регулярно, система может стать нестабильной и даже давать сбой.
Итак, непосредственно код функционирования двух ядер. Основной файл DualCore:
#include <Streaming.h> // Ref: http://arduiniana.org/libraries/streaming/
#include "Workload.h"
#include "Task1.h"
#include "Task2.h"
TaskHandle_t TaskA, TaskB;
void setup() {
Serial.begin(115200);
delay(500); // small delay
// Ref: http://esp32.info/docs/esp_idf/html/db/da4/task_8h.html#a25b035ac6b7809ff16c828be270e1431
xTaskCreatePinnedToCore(
Task1, /* pvTaskCode */
"Workload1", /* pcName */
1000, /* usStackDepth */
NULL, /* pvParameters */
1, /* uxPriority */
&TaskA, /* pxCreatedTask */
0); /* xCoreID */
xTaskCreatePinnedToCore(
Task2,
"Workload2",
1000,
NULL,
1,
&TaskB,
1);
}
void loop() {
// Эта задача будет выполняться в контексте ESP32 Arduino по умолчанию
unsigned long start = millis();
Serial << "Task 0 complete running on Core " << (xPortGetCoreID()) << " Time = " << (millis() - start) << " mS" << endl ;;
delay(10) ;
}
Task1.h:
void workLoad (void);
void Task1( void * parameter )
{
for (;;) {
unsigned long start = millis();
workLoad();
Serial << "Task 1 complete running on Core " << (xPortGetCoreID()) << " Time = " << (millis() - start)<< " mS" << endl;
delay(10) ;
}
}
Task2.h:
void workLoad (void);
void Task2( void * parameter )
{
for (;;) {
unsigned long start = millis();
workLoad();
Serial << "Task 2 complete running on Core " << (xPortGetCoreID()) << " Time = " << (millis() - start) << " mS" << endl ;
delay(10) ;
}
}
Workload.h
void workLoad (void) {
unsigned long loops1 = 1000 ;
unsigned long loops2 = 1000 ;
unsigned long qq = 0 ;
float t1;
int t2;
int t3;
for ( long i = 0; i < loops1; i++) {
for (long j = 1; j < loops2; j++) {
qq++;
t1 = 5000.0 * i;
t2 = 150 * 1234 * i;
t3 = j % 554 ;
}
}
}
Вот что мы можем увидеть на экране последовательного порта:
Task 0 complete running on Core 1 Time = 0 mS
Task 1 complete running on Core 0 Time = 0 mS
Task 2 complete running on Core 1 Time = 2 mS
Task 0 complete running on Core 1 Time = 0 mS
Task 1 complete running on Core 0 Time = 0 mS
Task 2 complete running on Core 1 Time = 2 mS
© digitrode.ru