цифровая электроника
вычислительная техника
встраиваемые системы

 

Как работать с двумя ядрами ESP32

Автор: Mike(admin) от 3-03-2018, 09:35

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


ESP32

Впрочем, их использование не слишком прозрачно и гибко. Конфигурация по умолчанию назначает первое ядро (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


Теги: ESP32



   Благодарим Вас за интерес к информационному проекту digitrode.ru.
   Если Вы хотите, чтобы интересные и полезные материалы выходили чаще, и было меньше рекламы,
   Вы можее поддержать наш проект, пожертвовав любую сумму на его развитие.


Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

Комментарии:

Оставить комментарий
Цитата
  • Группа: Гости
  • ICQ:
  • Регистрация: --
  • Статус:
  • Комментариев: 0
  • Публикаций: 0
^
Можно ещё добавить, что если функция не должна быть бесконечным циклом, то в конце она должна сама себя удалить, или микроконтроллер выдаст ошибку и реботнися.
void Task1 (void * parameter){
.....
.....
vTaskDelete( NULL );
}
И не плохо было бы ,если кто-нибудь написал как запускать функцию с аргументами.