Вот тут нарисовано, что общее, а что частное. Переферия вся шарится между ядрами. Память - тоже.
В том же документе есть раздел Interrupt Matrix.
Хотя, да, документация у Espressif весьма обрывочная.
Вот тут нарисовано, что общее, а что частное. Переферия вся шарится между ядрами. Память - тоже.
В том же документе есть раздел Interrupt Matrix.
Хотя, да, документация у Espressif весьма обрывочная.
Например, выбрать ядро для Ардуины (где будет запущен loop()) можно так:
благодарю!
Можете также посоветовать, какая мне нужна IDE чтобы корректно использовать функции в т.ч. многоядерности? ArduinoIDE с поддержкой esp32s3 достаточно, или необходимо переходить на иную?
P.s. я вообще в шоке, до чего мощные штуки для разработки нынче можно купить по цене обеда в столовой
Arduino IDE достаточно.
Однако смотрите: когда вы на одном ядре играетесь, то вероятность что у вас две задачи сконфликтуют при доступе к одному ресурсу - нулевая. Так шедулер устроен.
А вот задачи на двух разных ядрах - легко. Поэтому, почитайте про объекты синъронизации в ESP32. Они там представлены ядром операционной системы: семафоры (xSemaphoreCreate) и критические секции.
Хорошо, поищу инфу. Как мне кажется, по моей задумке там конфликта в принципе быть не должно, если 1 ядро связь и немного тригонометрии, а второе ШИМ (в пульте с такой же платой на 2м ядре будет экран по i2c). Т.е. общих ресурсов почти не будет - лишь глобальные переменные, причем 1 ядро туда только пишет, другое - только читает.
Интересно, сколько проца будет требовать беспроводная связь. На esp8266 замечал лаги по wi-fi, когда проц был занят другим (расчетами цветов для 200+ ws2812)
хотя да мне ничто не мешает тригонометрию тоже на 2м ядре считать, первое разгрузится
Т.е. связь, менюшки и действия на 1м ядре, тригонометрия и ШИМ на 2м - вполне логично…
а вот тут, к сожалению, если глобальная переменная длиннее, чем 8 бит, придется делать так:
#define BARRIER(_Name) portMUX_TYPE _Name = portMUX_INITIALIZER_UNLOCKED
#define barrier_lock(_Name) portENTER_CRITICAL(&_Name)
#define barrier_unlock(_Name) portEXIT_CRITICAL(&_Name)
BARRIER(test); //глобальная переменная, синхрообъект
Core0
...
barrier_lock(); //запрещаем доступ. все кто попытаются - повиснут в ожидании. Операция должна быть быстрой, а то сработает watchdog
write_variable(...); // Пишем
barrier_unlock(); // Разрешаем доступ
...
Core1
...
barrier_lock()
read_variable(...)
barrier_unlock()
...
это без вариантов, или только если мне критично работать с целиком обновленными данными?
скажем, если я “разрыв” между ядрами сделаю на координатах джойстика то не столь страшно, если X будет новая, а Y останется старая
Хотя гипотетически я могу и копию всей структуры сделать для второго ядра, а потом блокировать работу чисто на время копирования новых данных
Там же есть команды останова одного из ядер, или подобное?
watchdog это определятор зависания?
Надо пробовать. Но вообще, хорошей практикой считается использование синхронизации. Иначе у вас такая МАГИЯ попрёт, что вам ни один форум не поможет. Глюки страшнейшие, при гонках ядер. Вплоть до того, что одно ядро делает var++, а второе, читая эту переменную, иногда читает 0 :).
Нет, ядра не останавливаются. Там помимо ваштх задач еще есть много чего. Например, Idle Task, Loop Task, WiFi tasks, Serial task. Тысячи их.
Когда одно из ядер делает barrier_lock(), то
Если ядро0 уже сделало barrier_lock(), то первое ядро подвиснет, при порпытке barrier_lock().
Если ядро подвисло надолго, то система перезагрузится по watchdog.
Когда ядро0 сделает barrier_unlock(), то ядро1 отвиснет (если оно висело, конечно)
Так это и работает.
Т.е. вы сами вызываете функцию “подвешивания” ядра
Да и их там много разных. Слава богу, есть функция, чтобы все это отключить
благодарю за развернутые ответы!
Значит, скорее всего, определю какой-нибудь массив или структуру для “обмена” данными между ядрами и буду блокировать когда нулевое будет писать в него новые циферки
Для тех, кто в поисках, нашел еще одну интересную плату: WeAct STM32WB55CGU6. Особо уже не вникаю, но там немало выводов и беспроводная связь есть
Я вас еще одним вопросом помучаю. Вижу, вы очень много нюансов знаете касаемо принципов работы встроенной ОС и ресурсов чипа. Где черпали информацию? Это всё в даташите, или есть какой-то полезный сайт?
Нет в чипе встроенной ОС. Програмирование основано на FreeRTOS. Описание прогаммирования всех модулей здесь Peripherals API - ESP32 - — ESP-IDF Programming Guide v4.3 documentation
В основном - в примерах и коде самого ESP-IDF. Еще помогли esp32.com (форум) и stackoverflow.
Даташит мог бы быть и по-лучше. Точнее,ESP32 Technical Reference Manual.
Затем, есть книжка по ЕСП32, написанная неким Kolban’ом. По-моему, так и называется: Kolban’s book on ESP32. Отличное интро, чтобы быстро освоится.
По FreeRTOS есть отдельное большое количество примеров и прочего. Но ФриРТОС ничем особо не отличается от RTOS с которой я имел дело раньше (RTEMS), поэтому процесс перехода был мгновенный. Считайте, что ФриРТОС - это не ОС в привычном смысле слова, а еще одна библиотека.
Проще всего поинтересоваться этим в WinAPI. Основные принципы те же самые, а литературы - море.
Windows Application Programming Interface? Вы имеете ввиду, что работа прошивки по распределению ресурсов между юзер-скетчем и системными процессами схожа с таковой на винде?
Принципы-то в целом я понимаю, мне конкретика нужна будет: доступные методы, настройки и прочее
Так кажется въехал, ESP-IDF FreeRTOS это “многозадачная ось” для МК, то есть наверное в пакете esp32-s3 для ArduinoIDE, помимо методов взаимодействия с привычными ресурсами (таймеры, пины, память) будут методы взаимодействия с этой осью, инфу о которой хз пока где искать)) (ESP-IDF Programming Guide - ESP32-S3 - — ESP-IDF Programming Guide v5.2.5 documentation наверное здесь) А сам скетч, получается, крутится в своем потоке, или в двух потоках, если я использую многоядерность, но под управлением ESP-IDF FreeRTOS, которая также по таймингам отдает ресурсы, необходимые для функционирования встроенных протоколов беспроводных сетей (типа системных прерываний, переключений между потоками). Верно?
Чем занимается FreeRTOS:
FreeRTOS входит в состав ESP-IDF и используется последним для запуска задач Bluetooth, WiFi и прочего.
FreeRTOS ничегошеньки не знает про “ресурсы” процессора, в смысле переферии. Поэтому FreeRTOS не может “распределять ресурсы”. Вместо этого FreeRTOS дает тебе функции, типа xSemaphoreTake() или portENTER_CRITICAL, которыми “обкладывают” используемые ресурсы.
Простое правило: если у тебя есть какая-то переменная к которой обращабтся на чтение-запись из разных задач, то такой доступ нужно обложить синхрообъектами.
Документация на FreeRTOS находится на их сайте + существенные изменения задокументированы на сайте Espressif, в разделе про FreeRTOS.
По поводу многозадачности и “отдает по таймингу”.
В операционках реального времени, как правило, нет вытесняющей многозадачности: типа, задача отработала 15 миллисекунд, прерывем ее внаглую, отдаем управление другой. Так сделано, например, в Windows и Linux.
Во FreeRTOS не так. Вместо этого задача сама должна в нужный момент дать другим задачам поработать. Если этого не делать, то задача Idle, которая занимается успокоением watchdog’ов, не получит управления, счетчик watchdog’а переполнится и система перезагрузится.
Например, задача
while(1) {}
Скорее всего перезагрузит твой процессор, т.к. просто не отдаст управление никому.
Как отдать управление?
Ну, например, delay(1).
Или любое другое т.н. блокирующее действие: например, захватить семафор, который уже захвачен.
Еще способы отдать управление: vTaskDelay(), portYIELD(), yield() и т.п.
В сети большое количество туториалов про FreeRTOS.
Начни с того, что изучи, как задачи запускать (xTaskCreate, xTaskCreatePinnedToCore), и как семафоры работают (xSemaphoreCreate(), xSemaphoreGet(), xSemaphoreGive()).
Сделай простенький скетч:
Две задачи, одна на одном ядре, другая на другом, обе прибавляют единицу к некой глобальной переменной:
void task(void *) {
for (int i=0; i < 1000; i++) {
global_var++;
yield();
}
vTaskDelete(NULL);
}
После того, как задачи отработают, посмотри значение переменной global_var: там будет что угодно но не 2000
Потом добавь к коду выше обхект синхронизации, посмотри как поменяется результат.
По началу этого будет достаточно.
спасибо большое! очень развернуто. А что будет, если я пустой loop() скомпилирую? В какой момент loop() будет отдавать управление? Нужно в нем создать delay(20); (встречал в примерах), чтобы отрабатывали “системные” задачи? Или концепция esp в принципе предполагает лишь запуск задач по событиям?
Очень извиняюсь, но слегка каша… Часть отлично понимаю, но есть серые пятна в основополагающих вопросах
Правильно понял, что delay может стать ниразу не (1), а будет ждать, пока какая-то другая задача, которой шедулер отдал управление не вернет управление? Т.е. для точного тайминга функции delay использовать в принципе нельзя - только прерывания?
если правильно понял из-за почти одновременной записи в область памяти, превышающую 1 байт, ядро 0 может записать одну часть числа, ядро 1 - другую часть, причем в результате инкремента также не вовремя прочитанного из памяти младшего и/или старшего байтов.