Все мы знаем, что FreeRTOS — это операционная система для проектов, где каждой задаче установлены строгие рамки времени, чтобы любое действие было гарантированно обработано. На этом познания об этой системе у большинства айтишников заканчиваются, по той простой причине, что 99% из нас не собираются никогда в жизни разрабатывать ничего наподобие софта тормозной системы автомобиля или медицинского оборудования. Но я бы хотел сегодня немного рассказать об этой системе, потому как она вполне может пригодиться многим из нас по другим причинам. Ведь FreeRTOS вполне может оказаться отличным следующим шагом для саморазвития после Arduino, потому как поддерживает невероятное количество процессоров и при разрастании проекта немногим сложнее «Hello World» её использование будет проще для написания кода за счёт своей продуманной архитектуры. Её можно использовать на микроконтроллерах, с которыми многие уже привыкли работать в своих проектах выходного дня и которые обходятся в смешные деньги, например AVR, ESP32 и STM32. Сегодня я покажу, насколько просто использовать преимущества FreeRTOS на примере контроллера ESP32 и фреймворка от производителя Espressif — ESP-IDF, для своей линейки микроконтроллеров.
Начнём
В первую очередь нужно принять тот факт, что хотя FreeRTOS и называется операционной системой, она не является универсальной повседневной системой, как, например, Linux или Windows, которые мы устанавливаем на компьютеры. Её нельзя установить на компьютер и даже на микроконтроллер и запускать в ней различный софт. Тут немного другой подход. Но прежде чем углубляться, давайте рассмотрим возможные подходы к решению задачи написания кода для микрокомпьютера или микроконтроллера.
▍ 1. Linux
Установка Linux-дистрибутива и компиляция своей программы или даже софта с github по принципу, похожему на установку софта на обычный компьютер. Такой вариант годится для относительно мощных платформ вроде Raspberry. Linux поддерживает многоядерность, многопоточность, как правило, уже имеет драйверы практически для любого оборудования, можно легко использовать стандартные PCIE, USB и даже SDIO и UART устройства вроде GPS модулей, Bluetooth устройств. С каждым новым ядром списки поддерживаемого оборудования пополняются семимильными шагами. Это отличное, универсальное, но очень дорогое решение, если нужно просто опрашивать кнопочки, моргать светодиодами и крутить моторчики. Цены на такие платы начинаются от 3к. руб. и вверх на луну. Однако, я уже рассказывал, как для запуска Linux можно использовать отработавшее свою жизнь бытовое оборудование в постах про «Одноплатный компьютер из камеры видеонаблюдения» и «Превращаем TV-box в мини-компьютер».
▍ 2. Написание программ с использованием платформы Arduino
Отличный вариант для самых простых микроконтроллеров, чтобы ознакомить детишек с тем, что такое микроконтроллер и как просто поморгать LED. Скрывает сложности как программирования, так и аппаратной части, чем невероятно снижает порог вхождения. За счёт огромного количества наработанного кода это очень популярное решение. Есть оверхед по ресурсам, но сильно расстраиваться не стоит, потому как, например, в ESP32 под капотом Arduino мы найдём тот же FreeRTOS.
▍ 3. Компиляция своей программы без операционки (bare metal)
Пишем на чистом Си или даже ассемблере, используя библиотеки производителя платформы и различные сторонние библиотеки, если нужно задействовать модули WiFi, Bluetooth, протоколы вроде 1wire, I2C e.t.c. Требует полный спектр знаний в аппаратной части и электронике, в цифровой логике, во внутренней периферии микроконтроллера, такой как регистры процессора, таймеры, компараторы, умения работать на низком уровне с прерываниями. Хоть это и даёт программисту полный контроль над системой, позволяя оптимизировать производительность и использовать все возможности аппаратного обеспечения, но код пишется медленно. Для большинства людей сложно, очень сложно.
Ничего не понятно!
▍ 4. FreeRTOS
Это операционная система, предоставляющая набор компонентов, из которых мы включаем в свой проект только необходимые и используем их в своём коде. Она не устанавливается как Linux! Она собирается в единую прошивку вместе с нашим проектом.
Часть команды, часть корабля
При этом она корректно использует все необходимые функции аппаратной платформы, такие как поддержка множества ядер процессора, WiFi и BT модули.
Выбор и настройка компонентов FreeRTOS через 'idf.py menuconfig'
Если нам нужно только получить доступ к GPIO, то нужно подключить компонент driver. Для HTTP-сервера подключаем esp_http_server. А в коде программы подключить заголовки «driver/ledc.h» и «esp_http_server.h». Но чтобы HTTP-сервер обрёл смысл на нашей железке, нам обязательно понадобится ещё и сеть, а это компонент esp_wifi, который можно использовать как WiFi-клиент или точку доступа. Соответственно не забываем и про заголовок «esp_wifi.h». В общем при настройке проекта нужно чётко понимать, какие компоненты будут задействованы. Все настройки конфигуратора будут сохранены в файл «sdkconfig».
Тут некий читатель наверняка начал подозревать, что в проектах с перспективой на разрастание использование FreeRTOS будет не только более профессиональным, но и более простым решением за счёт отличной структурированности этой системы. И такой читатель чертовски прав!
Давайте сначала я покажу, как поморгать светодиодом, а потом перейдём к более интересной задаче, которая будет не намного сложнее, но ближе к практическому применению: используем встроенный в ESP32 WiFi-модуль, чтобы подключиться к сети, прямо на ESP32 поднимем HTTP-сервер, который будет обрабатывать запросы по пути /feed и позволит нам управлять шаговым двигателем через интернет. И всё это на популярной плате ESP-WROOM-32, которая у большинства тех читателей, кто дочитал до этого места, наверняка валяется в загашнике. Цена этой платы, близкая к цене чашечки кофе в три сотни рубчиков, просто смешна. А если учесть, что на борту не только двухъядерный процессор частотой до 240Mhz, а также модули WiFi и BT со встроенной или подключаемой внешней антенной, то эта плата просто вне конкуренции для подобных проектов. Кроме того, на плате уже есть встроенный UART-адаптер, который позволяет просто воткнуть плату в USB, без программатора прошить прошивку и даже получать отладочную информацию нашего приложения через консоль. Кстати, есть более дешёвые модули без встроенного UART-контроллера, которые по размерам очень малы и, соответственно, более энергоэффективны без питания этой микросхемы. Такие удобнее использовать уже в готовых устройствах, которые достаточно прошить один раз через внешний USB UART адаптер.
ESP32 без UART-контроллера и с IPEX выходом на внешнюю антенну WiFi
Hello world на FreeRTOS в ESP32: моргаем LED
Приборы и материалы:
- Компьютер с USB.
- ESP-WROOM-32 для простоты с USB-интерфейсом.
- LED и желательно резистор на 100-1000 Ом, но не обязательно.
- Паяльник или макетные провода.
Для того, чтобы в миллионный раз не описывать одно и то же и не утонуть в подробностях настройки под Linux/Windows/MAC и под любимую IDE, я буду давать ссылки на уже существующие посты. Настраиваем по любой инструкции из интернета ESP-IDF любым подходящим способом.
Заглядываем в загашник и находим какой-то ESP32 микроконтроллер с WiFi на борту, который уже и не вспомните для чего брали. Моя ставка, что это будет что-то вроде ESP-WROOM-32. Но даже если это более простой ESP8266, не расстраивайтесь. На таких тоже можно собрать проект под управлением FreeRTOS, однако придётся установить другой фреймворк ESP8266_RTOS_SDK.
После настройки SDK в системе убеждаемся, что idf.py работает, и запускаем в директории, где хотим создать проект:
$ idf.py create-project habr-esp32-led
cd habr-esp32-led/
Будет создана директория habr-esp32-led. Сильно не радуйтесь, увидев CMakeLists.txt, потому что эти проекты управляются не через cmake, а через idf.py.
У вас есть директория habr-esp32-led, в которой найдётся habr-esp32-led/main/CMakeLists.txt следующего содержания:
idf_component_register(SRCS "habr-esp32-led.c"
INCLUDE_DIRS ".")
Чтобы проект мог использовать GPIO, нам нужно подключить компонент «driver» для работы с GPIO. С добавлением новых Си заголовков всегда нужно подключать соответствующие компоненты в этом файле. Вот так должно получиться после изменений:
idf_component_register(SRCS "habr-esp32-led.c"
INCLUDE_DIRS "."
REQUIRES driver
)
Должна получиться вот такая структура проекта с двумя различными CMakeLists.txt:
habr-esp32-led
- CMakeLists.txt
- sdkconfig
- main
- CMakeLists.txt
- habr-esp32-led.c
Записываем следующий код в ./habr-esp32-led/main/habr-esp32-led.c:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include <stdio.h>
#define LED_PIN GPIO_NUM_2 // Пин для подключения светодиода
void app_main(void)
{
gpio_reset_pin(LED_PIN); // Сбрасываем настройки пина
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT); // Устанавливаем пин как выход
while (1) {
gpio_set_level(LED_PIN, 1); // Включаем светодиод
vTaskDelay(1000 / portTICK_PERIOD_MS); // Задержка 1 секунда
gpio_set_level(LED_PIN, 0); // Выключаем светодиод
vTaskDelay(1000 / portTICK_PERIOD_MS); // Задержка 1 секунда
}
}
Уверен, кто уже имеет некий опыт в программировании на Си под микроконтроллеры, по аналогии с Arduino поймёт по комментариям, что тут происходит, но всё равно хоть немного я должен пояснить.
Задаём алиас, который заявляет, что LED_PIN полностью аналогичен использованию GPIO_NUM_2, что в свою очередь говорит контроллеру в дальнейшем работать с пином GPIO под номером 2. Вы можете использовать и другие GPIO-выходы.
void app_main() — это главная функция входа в программу, такая как void loop() в Arduino или void main() в классическом Си. Выход из этой функции не предусмотрен, и она запускается лишь единожды. Соответственно все настройки портов не выносятся в отдельную функцию setup(), а бесконечный цикл организуется через «while (1) {}».
vTaskDelay() блокирует текущий поток на заданное количество тиков, которое зависит от частоты процессора, поэтому для использования более удобных миллисекунд, чтобы не пересчитывать каждый раз при переконфигурировании под различные процессоры или разные частоты текущего процессора, мы переводим миллисекунды в тики, используя константу фреймфорка portTICK_PERIOD_MS.
Можно запустить конфигуратор из директории проекта habr-esp32-led и перенастроить проект, но для текущей задачи менять пока ничего не потребуется:
$ idf.py menuconfig
Выбирайте ваш конкретный контроллер, например для ESP-WROOM-32:
$ idf.py set-target esp32
Вот такие контроллеры поддерживаются в ESP32 IDF:
$ idf.py set-target
Usage: idf.py set-target [OPTIONS] {esp32|esp32s2|esp32c3|esp32s3|esp32c2|esp32c6|esp32h2|esp32p4|linux|esp32c5|esp32c61}
Try 'idf.py set-target --help' for help.
Error: Missing argument '{esp32|esp32s2|esp32c3|esp32s3|esp32c2|esp32c6|esp32h2|esp32p4|linux|esp32c5|esp32c61}'. Choose from:
esp32,
esp32s2,
esp32c3,
esp32s3,
esp32c2,
esp32c6,
esp32h2,
esp32p4,
linux,
esp32c5,
esp32c61
После следующей команды проект будет несколько минут компилироваться и в конце напишет примерно следующее:
idf.py build
...
habr-esp32-led.bin binary size 0x2d2a0 bytes. Smallest app partition is 0x100000 bytes. 0xd2d60 bytes (82%) free.
Project build complete. To flash, run:
idf.py flash
or
idf.py -p PORT flash
or
python -m esptool --chip esp32 -b 460800 --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size 2MB --flash_freq 40m 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/habr-esp32-led.bin
or from the "/mnt/raid/devel/habr/habr-esp32-led/build" directory
python -m esptool --chip esp32 -b 460800 --before default_reset --after hard_reset write_flash "@flash_args"
Это означает, что прошивка собрана и готова к тому, чтобы быть залитой в микроконтроллер:
idf.py flash
Или с указанием нужного USB порта:
idf.py -p /dev/ttyUSB0 flash
Это прошьёт программу в микроконтроллер и наша программа автоматически запустится.
Подключив светодиод к выводам GPIO2 и GND, можно увидеть его мигание. Подключать нужно через токоограничивающий резистор, но если использовать слабый светодиод, то он не нанесёт ущерба вашей плате даже без резистора, если всего несколько раз мигнёт для проверки.
Вот что мы должны увидеть:
LED на микроконтроллере ESP32 должен мигать
Для отладки можно подключиться к ESP32-консоли через minicom или picocom, так как на платах ESP32 с USB подключением уже есть UART-контроллер:
picocom -b 115200 -r -l /dev/ttyUSB0
Так мы сможем увидеть лог загрузки после нажатия RESET и работы FreeRTOS, а также всё, что выводят наши команды printf().
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:6276
load:0x40078000,len:15728
ho 0 tail 12 room 4
load:0x40080400,len:4
load:0x40080404,len:3860
entry 0x4008063c
I (31) boot: ESP-IDF v5.4-dev-3602-ga97a7b0962-dirty 2nd stage bootloader
I (31) boot: compile time Dec 10 2024 15:54:10
I (31) boot: Multicore bootloader
I (35) boot: chip revision: v3.1
I (38) boot.esp32: SPI Speed: 40MHz
I (41) boot.esp32: SPI Mode: DIO
I (45) boot.esp32: SPI Flash Size: 4MB
I (48) boot: Enabling RNG early entropy source…
I (53) boot: Partition Table:
I (55) boot: ## Label Usage Type ST Offset Length
I (62) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (68) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (75) boot: 2 factory factory app 00 00 00010000 00100000
I (81) boot: End of partition table
I (85) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=21f1ch (139036) map
I (140) esp_image: segment 1: paddr=00031f44 vaddr=3ff80000 size=00018h ( 24) load
I (140) esp_image: segment 2: paddr=00031f64 vaddr=3ffb0000 size=03f38h ( 16184) load
I (150) esp_image: segment 3: paddr=00035ea4 vaddr=40080000 size=0a174h ( 41332) load
I (167) esp_image: segment 4: paddr=00040020 vaddr=400d0020 size=8c988h (575880) map
I (364) esp_image: segment 5: paddr=000cc9b0 vaddr=4008a174 size=0d6bch ( 54972) load
I (397) boot: Loaded app from partition at offset 0x10000
I (397) boot: Disabling RNG early entropy source…
I (408) cpu_start: Multicore app
I (416) cpu_start: Pro cpu start user code
I (416) cpu_start: cpu freq: 160000000 Hz
I (416) app_init: Application information:
I (416) app_init: Project name: esp32shneck
I (420) app_init: App version: 1
I (424) app_init: Compile time: Dec 10 2024 15:54:09
I (429) app_init: ELF file SHA256: 932c20a3e…
I (433) app_init: ESP-IDF: v5.4-dev-3602-ga97a7b0962-dirty
I (439) efuse_init: Min chip rev: v0.0
I (443) efuse_init: Max chip rev: v3.99
I (447) efuse_init: Chip rev: v3.1
I (451) heap_init: Initializing. RAM available for dynamic allocation:
I (457) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (462) heap_init: At 3FFB81B0 len 00027E50 (159 KiB): DRAM
I (467) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (473) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (478) heap_init: At 40097830 len 000087D0 (33 KiB): IRAM
I (485) spi_flash: detected chip: generic
I (487) spi_flash: flash io: dio
I (491) main_task: Started on CPU0
I (501) main_task: Calling app_main()
I (521) gpio: GPIO[14]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (521) gpio: GPIO[25]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (521) gpio: GPIO[26]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (531) gpio: GPIO[27]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (541) FEEDER: GPIO is initialised
I (541) FEEDER: Init Wi-Fi…
I (551) main_task: Returned from app_main()
I (561) wifi:wifi driver task: 3ffc2e48, prio:23, stack:6656, core=0
I (571) wifi:wifi firmware version: 0a80d45
I (571) wifi:wifi certification version: v7.0
I (571) wifi:config NVS flash: enabled
I (571) wifi:config nano formatting: disabled
I (571) wifi:Init data frame dynamic rx buffer num: 32
I (581) wifi:Init static rx mgmt buffer num: 5
I (581) wifi:Init management short buffer num: 32
I (591) wifi:Init dynamic tx buffer num: 32
I (591) wifi:Init static rx buffer size: 1600
I (601) wifi:Init static rx buffer num: 10
I (601) wifi:Init dynamic rx buffer num: 32
I (601) wifi_init: rx ba win: 6
I (611) wifi_init: accept mbox: 6
I (611) wifi_init: tcpip mbox: 32
I (611) wifi_init: udp mbox: 6
I (621) wifi_init: tcp mbox: 6
I (621) wifi_init: tcp tx win: 5760
I (621) wifi_init: tcp rx win: 5760
I (621) wifi_init: tcp mss: 1440
I (631) wifi_init: WiFi IRAM OP enabled
I (631) wifi_init: WiFi RX IRAM OP enabled
W (641) wifi:Password length matches WPA2 standards, authmode threshold changes from OPEN to WPA2
I (641) phy_init: phy_version 4840,02e0d70,Sep 2 2024,19:39:07
I (721) wifi:mode: sta (a0:a3:b3:fe:eb:e5)
I (721) wifi:enable tsf
I (721) FEEDER: Wi-Fi is started
I (741) wifi:new:<1,0>, old:<1,0>, ap:<255,255>, sta:<1,0>, prof:1, snd_ch_cfg:0x0
I (741) wifi:state: init -> auth (0xb0)
I (751) wifi:state: auth -> assoc (0x0)
I (761) wifi:state: assoc -> run (0x10)
I (781) wifi:connected with WIFIAP, aid = 3, channel 1, BW20, bssid = a0:f3:c1:6c:e6:93
I (781) wifi:security: WPA2-PSK, phy: bgn, rssi: -76
I (781) wifi:pm start, type: 1
I (781) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us
I (801) wifi:<ba-add>idx:0 (ifx:0, a0:f3:c1:6c:e6:92), tid:0, ssn:1, winSize:64
I (881) wifi:dp: 2, bi: 102400, li: 4, scale listen interval from 307200 us to 409600 us
I (881) wifi:AP's beacon interval = 102400 us, DTIM period = 2
I (1821) esp_netif_handlers: sta ip: 192.168.1.171, mask: 255.255.255.0, gw: 192.168.1.1
I (1821) FEEDER: Got IP address: 192.168.1.171
Крутим шаговик по HTTP на заданное количество шагов в ESP32
Для этого проекта нам понадобится уже повозиться подольше.
Добавляем к приборам и материалам:
- Микросхема драйвера двигателя L293D или похожая L298.
- Макетная плата с проводами dupont или запаять всё на «соплях».
- Шаговый мотор, желательно на напряжение 5В (я выкорчевал из CD-привода от древнего ноутбука).
- Желательно поставить конденсатор в параллель с питанием примерно 100mF и напряжением выше 10V.
Шаговый двигатель отличается от других типов тем, что подавая попеременно напряжения на различные обмотки мы можем точно поворачивать его на определённый угол, отсчитывая количество сделанных шагов. Выбрал я этот тип двигателя лишь для усложнения проекта, чтобы был практический смысл выделить задачу отсчитывания шагов в отдельный таск (грубо говоря, поток в терминологии FreeRTOS). Ну ещё и потому, что у меня в груде компьютерного хлама шаговики валяются в ассортименте.
К сожалению, как в случае со светодиодом, схалтурить и пустить ток напрямую с микроконтроллера на мотор тут не удастся. Слишком уж большой ток потянет даже самый маленький шаговичёк, но всё равно тока от микроконтроллера не будет достаточно, чтобы его повернуть. Я использовал маломощный драйвер моторов L293D, потому как для моего шаговика от ноутбуковского CD-привода хватает для проверки программы токов и напряжения в 5В, взятых прямо с питания ESP32-контроллера. Если использовать другие шаговые моторы, то нужно проверить их напряжение питания. Скорее всего, придётся подключать к микросхеме повышенное напряжение. Вместо L293D вполне можно использовать с похожей распиновкой L298, которая позволяет крутить мотор токами до 3 ампер на каждый выход при напряжении до 50 вольт. Однако из-за своего расположения ног L298 не становится на макетную плату. На Ali можно найти такие микросхемы в виде готовых модулей L298, которые можно подключать без пайки.
▍ Собираем железо
Ориентировочная схема подключения
Обратите внимание в документации, что микросхемы драйвера почти всегда имеют два напряжения: ~ 5В для питания внутренней логики (ножка 16 на L293D) и повышенное напряжение для питания моторов (Ножка 8 на L293D). В своей сборке, как я сказал, для питания шаговика было достаточно 5 вольт и я взял напряжение прямо с платы ESP32, которую запитал от USB. Это очень упростило схему и позволило запаять все провода подвесным монтажом. Разница со схемой только в том, что вместо двух моторов DC мы подключаем две обмотки шагового мотора. Нам понадобится шаговик с 4 контактами. Прозвоните 4 выхода и выясните, которые из них образуют замкнутые пары обмоток, каждую из которых следует подключать к выходам драйвера OUT1-OUT2 и OUT3-OUT4 соответственно.
L293D распиновка
Итак, на input 1-4 мы подаём маломощный сигнал с указанных в нижеприведённом коде GPIO микроконтроллера, а к output 1-4 подключаем обмотки шагового мотора. Для того, чтобы разрешить выходной сигнал, оба пина enable (Enable1,2, Enable3,4) замыкаем на VCC +5v. В этом проекте мы не будем управлять ими через GPIO, как нарисовано в ориентировочной схеме.
Однако если вы питаете схему напряжения выше 5 вольт, то используйте любой понижающий преобразователь, который на схеме выглядит как LM7805. Выходные 5 вольт будут питать ESP32 и логику драйвера мотора, а высокое напряжение без понижения через драйвер запитает моторы.
Итог сборки железа
▍ Код
По инструкции из начала поста создайте новый проект в директории esp32_shneck.
Меняем содержимое ./esp32_shneck/main/CMakeLists.txt на следующее:
PRIV_REQUIRES spi_flash
INCLUDE_DIRS ""
REQUIRES driver esp_wifi esp_netif nvs_flash esp_http_server
)
Опция REQUIRES подключает в проект следующие модули ESP-IDF:
— driver — поддержка базовых функций таких как GPIO, SPI, I2C, UART, PWM,
— esp_wifi — WiFi клиент,
— esp_netif — WiFi интерфейс,
— nvs_flash — работа с флэш памятью, которая нужна для корректной работы драйвера WiFi,
— esp_http_server — HTTP сервер.
Основная программа нашего проекта на Си ./esp32_shneck/main/esp32shneck.c:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_http_server.h"
#include "freertos/queue.h"
#include "driver/ledc.h"
#define TAG "FEEDER"
#define WIFI_SSID "WifiAP"
#define WIFI_PASSWORD "WifiPassword"
#define IN1_GPIO GPIO_NUM_25 // IN1 L293
#define IN2_GPIO GPIO_NUM_26 // IN2 L293
#define IN3_GPIO GPIO_NUM_27 // IN3 L293
#define IN4_GPIO GPIO_NUM_14 // IN4 L293
#define STEP_DELAY_MS 10
const int step_sequence[8][4] = {
{0, 1, 0, 0},
{0, 1, 0, 1},
{0, 0, 0, 1},
{1, 0, 0, 1},
{1, 0, 0, 0},
{1, 0, 1, 0},
{0, 0, 1, 0},
{0, 1, 1, 0}
};
QueueHandle_t shneckQueue;
void step_motor_gpio_init() {
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << IN1_GPIO) | (1ULL << IN2_GPIO) |
(1ULL << IN3_GPIO) | (1ULL << IN4_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io_conf);
ESP_LOGI(TAG, "GPIO is initialised");
}
void step_motor_step(int step) {
gpio_set_level(IN1_GPIO, step_sequence[step][0]);
gpio_set_level(IN2_GPIO, step_sequence[step][1]);
gpio_set_level(IN3_GPIO, step_sequence[step][2]);
gpio_set_level(IN4_GPIO, step_sequence[step][3]);
}
void schneck_rotate_task(void *pvParameters) {
int direction = 1; // 1 - clockwise, -1 - counterclockwise
int current_step = 0;
int steps_left = 0;
while(1) {
if (steps_left == 0) {
if (xQueueReceive(shneckQueue, &steps_left, portMAX_DELAY))
ESP_LOGI(TAG, "Got %d steps from queue", steps_left);
}
else {
step_motor_step(current_step);
current_step = (current_step + direction + 8) % 8;
steps_left--;
}
vTaskDelay(pdMS_TO_TICKS(STEP_DELAY_MS));
}
}
esp_err_t index_handler(httpd_req_t *req) {
char *indexhtml = "<!DOCTYPE html><html><head><title>GET</title></head><body><button onclick=\"fetch('/feed?steps=100')\">FEED</button></body></html>";
httpd_resp_send(req, indexhtml, strlen(indexhtml));
return ESP_OK;
}
esp_err_t feed_handler(httpd_req_t *req) {
char query[128];
char value[32];
int error = 0;
if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) {
ESP_LOGI(TAG, "URL Query: %s", query);
if (httpd_query_key_value(query, "steps", value, sizeof(value)) == ESP_OK) {
ESP_LOGI(TAG, "Steps Parameter Value: %s", value);
int steps = atoi(value);
ESP_LOGI(TAG, "Steps as Integer: %d", steps);
if (steps > 0) {
ESP_LOGI(TAG, "Rotating motor ...");
if (xQueueSend(shneckQueue, &steps, pdMS_TO_TICKS(100)) != pdPASS) {
ESP_LOGW("ShneckTask", "Queue is full...");
error = 1;
}
} else {
ESP_LOGW(TAG, "Invalid steps value: %d", steps);
}
} else {
ESP_LOGW(TAG, "Parameter 'steps' not found");
}
} else {
ESP_LOGI(TAG, "No URL query found");
}
const char *resp_str = "Steps parameter processed";
httpd_resp_send(req, resp_str, strlen(resp_str));
if (error == 1) {
ESP_LOGW(TAG, "Error during queueing");
return ESP_ERR_NOT_ALLOWED;
}
return ESP_OK;
}
httpd_handle_t start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
if (httpd_start(&server, &config) == ESP_OK) {
httpd_uri_t write_servo_uri = {
.uri = "/feed",
.method = HTTP_GET,
.handler = feed_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &write_servo_uri);
httpd_uri_t index_uri = {
.uri = "/",
.method = HTTP_GET,
.handler = index_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(server, &index_uri);
}
return server;
}
void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGI(TAG, "Wi-Fi disconnected, reconnecting...");
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Got IP address: " IPSTR, IP2STR(&event->ip_info.ip));
start_webserver();
}
}
void http_server_task(void *pvParameters) {
ESP_LOGI(TAG, "Init Wi-Fi...");
esp_netif_init();
esp_event_loop_create_default();
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, &instance_any_id);
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, &instance_got_ip);
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASSWORD,
},
};
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "Wi-Fi is started");
vTaskDelete(NULL);
}
void app_main(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
shneckQueue = xQueueCreate(10, sizeof(int));
step_motor_gpio_init();
xTaskCreate(schneck_rotate_task, "schneck_rotate_task", 2048, NULL, 5, NULL);
xTaskCreate(http_server_task, "http_server_task", 8192, NULL, 5, NULL);
}
▍ Объяснения по коду
Директивами include подключаются заголовочные файлы с функциями необходимых нам компонентов.
WIFI_SSID и WIFI_PASSWORD задают реквизиты подключения к сети WiFi для работы нашей программы в качестве клиента.
IN1_GPIO, IN2_GPIO, IN3_GPIO, IN4_GPIO задают, на каких GPIO будем выдавать сигналы для нашего шагового мотора.
STEP_DELAY_MS — задержка между шагами мотора. Чем меньше задержка, тем быстрее вращение.
step_sequence — таблица истинности, которая задаёт состояния четырёх выходов микроконтроллера, которые подключены на входы драйвера мотора IN1, IN2, IN3, IN4. Всего 8 строк, каждая из которых описывает состояния обмоток шаговика для циклических 8 шагов. Можно было бы обойтись четырьмя циклическими шагами, но тогда мотор может работать не так плавно и больше шуметь. Если переключаем состояния в сторону увеличения номера строки в таблице состояний, то мотор будет вращаться в одну сторону. Если уменьшать номер строки, то мотор будет вращаться в противоположную. Если мотор не будет вращаться, а начнёт вибрировать на месте, то нужно переключить направление одной или другой обмотки (поменять местами OUT1 и OUT2 или OUT 3 и OUT4).
Для полного понимания FreeRTOS рекомендую божественный курс Владимира Мединцева.
Но для запуска проекта лишь вскользь галопом по Европам. Находим функцию входа в программу app_main() в которой код:
- инициализирует внутренюю флэш память для корректной работы WiFi драйвера через nvs_flash_init(),
- создаёт очередь shneckQueue через которую мы будем передавать заказы на вращение шаговика от задачи (потока) HTTP сервера к задаче, работающей с железом
- инициализируем GPIO step_motor_gpio_init()
- создаём и запускаем две одновременно работающих задачи FreeRTOS, которые заданы функциями schneck_rotate_task() и http_server_task(). Ни одна из этих функций не должна никогда завершаться, иначе это закрашит всю систему. Каждая из этих функций, чтобы стать задачей, принимает аргумент pvParameters.
▍ schneck_rotate_task()
В этой функции переменная current_step отвечает за текущее состояние выходов для подачи на мотор, которое определяется строкой в таблице step_sequence под номером от 0 до 7. Если на следующем шаге current_step превысит 7, то для текущего состояния выбирается нулевая строка. Между шагами выдерживается пауза, заданная в STEP_DELAY_MS.
steps_left определяет, сколько всего шагов необходимо сделать по указанию пользователя. Изначально это значение равно нулю и мотор находится в состоянии покоя. На каждой итерации бесконечного цикла через функцию xQueueReceive() происходит попытка получить из очереди новый объект для обработки. В нашем случае объект в очереди это целое число, предписывающее сделать указанное количество шагов. Но для более сложных проектов это может быть более сложная структура, имеющая большее количество данных, и я специально сделал это через очередь, чтобы читатель понял удобство межпроцессного взаимодействия в ОС FreeRTOS.
▍ http_server_task()
В этой задаче инициализируется подсистема WiFi и задаётся обработчик событий WiFi wifi_event_handler(), который при успешном подключении к сети запустит start_webserver().
А вот в коде этой функции самое время вкусить прелесть и простоту встроенного в FreeRTOS веб-сервера. Мы задаём два обработчика HTTP-путей:
"/" — запустит index_handler(),
"/feed" — запустит feed_handler().
Это, конечно, не Django и не Laravel, но для сервака за $3 — просто праздник!
index_handler() вываливает пользователю HTML с кнопкой, а по нажатию на кнопку отправляется «заказ» на поворот на 100 шагов по IP адресу нашего ESP32 в сети 192.168.1.10/feed?steps=100.
Аргумент steps мы обрабатываем из GET-запроса функцией httpd_req_get_url_query_str(), а по результатам обработки даже высылаем пользователю HTTP-ответ функцией httpd_resp_send().
▍ Результат
Вращение шагового мотора от CD-привода ноутбука
Развивая проект за смешные деньги на основе ESP32, FreeRTOS и шаговых двигателей, можно создать программно-аппаратный комплекс для самых разных целей: автоматической кормушки для кота или сельской живности, трекинговой системы для телескопа, фотоаппарата или солнечных панелей, автоматизации теплиц (например, для точного открывания форточек), управления шторами или жалюзи, а также машинками через TCP/IP.
© 2025 ООО «МТ ФИНАНС»
Telegram-канал со скидками, розыгрышами призов и новостями IT ?
Комментарии (66)
mafia8
02.01.2025 10:08Чем отличается RTOS от обычной OS? Можно даже на примере. Взяли 3 одинаковых железки, на них запустили RTOS, Ubuntu, Windows и запустили одинаковую задачу. Windows запустила дефрагментатор реестра, а задачу отложила на потом, на Ubuntu была запущена другая программа, которая нагрузила процессор, и только на RTOS задача была решена за приемлемое время.
А то есть статья на эту тему на Хабре: RTOS или не RTOS вот в чем вопрос .
Pelemeshka
02.01.2025 10:08Тем что для выполнения любого (задачи / функции / действия) можно установить лимит по времени и если RTOS не успевает, то эта (задача / функция / действие) вываливается с ошибкой TIMEOUT. Реакцию на такое событие уже должен заложить программист, только вот часто не закладывают...
Здесь дело не в производительности а в гарантии того что тебе будет известно успела ли система все вовремя или нет.
mafia8
02.01.2025 10:08В Windows можно поставить таймер и прекратить выполнение действия по таймеру?
NutsUnderline
02.01.2025 10:08смотря какое действие и смотря какой таймер. на счет железа 100% не скажу (но там весьма кучеряво) а вот gui подвисает только в корявых программах ;)
kipar
02.01.2025 10:08Основное отличие FreeRTOS от линукса и винды в том что у винды минимальные требования к памяти измеряются в гигабайтах, у линукса в мегабайтах, у фриртос в килобайтах, так что это продукты из совсем разных категорий.
С практической стороны, всё что она гарантирует с точки зрения RT - то что каждую миллисекунду (можно реже, а вот чаще - фиг, если без хаков) будет вызвано прерывание и оно переключит исполнение на самую приоритетную из неспящих задач. Если на линуксе и винде отключить запуск пользовательских программ, то можно добиться примерно тех же гарантий, вполне есть реалтайм системы и на том и на том. Но реалтайм систему явно проще сделать (и проще сертифицировать) на фриртос где все задачи создаются в твоей программе, чем на винде, где какой-нибудь драйвер будет вызывать синий экран смерти а ты узнаешь об этом только во время эксплуатации.
Vadim_1984
02.01.2025 10:08>чтобы любое действие было гарантированно обработано. На этом познания об этой системе у большинства айтишников заканчиваются
И чем это гарантируется))? В рамках какой концепции ОСРВ?) Познания айтишников..
Vadim_1984
02.01.2025 10:08минусуют...ну давайте разберём
These systems must provide absolute guarantees that a certain action will occur by a certain time.
Под action совсем не имеется ввиду гарантированная обработка действия. Action может быть и прекращение этой обработки, если она не может быть завершена в пределах заданного времени.
Serge78rus
02.01.2025 10:08Есть пара замечаний
После прочтения данного текста, у "самых маленьких", на которых ориентирован материал, может возникнуть ложное впечатление, что это именно FreeRTOS обеспечивает простой доступ к периферии, но это не так. FreeRTOS лишь обеспечивает многозадачность и предоставляет элементы синхронизации, на этом ее функции исчерпываются. Упрощенный доступ к периферии предоставляют библиотеки среды esp-idf, не имеющие непосредственного отношения к FreeRTOS.
Не стоит забывать, что ESP32 не входит в число платформ, официально поддерживаемых FreeRTOS. Так что все претензии к стабильности работы должны предъявляться не к разработчикам RTOS, которая сама по себе является крайне надежной системой, а к разработчикам esp-idf, которые осуществляли портирование данной операционки на платформу ESP32.
ПС: пока писал комментарий, @rsashka уже написал примерно то же, что у меня в первом пункте, но более развернуто и с примерами.
ripandtear
02.01.2025 10:08Достаточно поверхностная статья, хоть и подразумевается "Для самых маленьких". Собственно, ничего не говорится о том, что же такое ОСРВ, какие есть типы систем реального времени (т.н. мягкое и жесткое реальное время), почему для МК стоит выбирать ОСРВ а не запихивать весь проект внутрь какого-нибудь while(1), и так далее.
Если касаться FreeRTOS, то стоило на мой взгляд, начать с перевода начала книги (Например главы 1.1.4 Why use an RTOS ?) Mastering the FreeRTOS Realtime Kernel. Книга доступна бесплатно.
Да и в целом, большинство ответов как, зачем и почему достаточно хорошо изложены в ней.SmartTherm
02.01.2025 10:08вообще говоря как минимум не для всякого МК стоит выбирать какую-либо ОС и даже РВ. Очень даже спокойно весть проект может быть запихнут в while(1); а все остальное жить в прерываниях
ripandtear
02.01.2025 10:08Все зависит от проекта в первую очередь. Что-то простое и небольшое, наверное да. Но в основном как только появляется необходимость использовать больше 3-5 интерфейсов - USB, Ethernet, много микросхем подключенных по SPI/SSP/что-то еще (Например, сразу 6 микросхем АЦП), возможно дисплей и др. - while(1) моментально упирается в потолок адекватной скорости внесения изменений, и главное, отладки временной диаграммы работы платы/прибора/устройства.
Ну и существует стандартная история - это когда человек пишет такой код в стиле while(1), потом понимает что всё, все глючит и не работает - увольняется (или его увольняют) т.к. более он не в состоянии понять, как довести работу до конца.nv13
02.01.2025 10:08Наверно всё таки не совсем так - while это же разновидность планировщика, только не в ос, а в приложении. Если подходить с этой позиции то можно обойтись и им, правда второй вопрос будет - зачем так, если есть ос.
ripandtear
02.01.2025 10:08в ОСРВ у вас есть вытесняющая многозадачность с набором всевозможных примитивов синхронизации и т.п.
while(1) с прерываниями - будете либо много тратить время в обработчиках прерываний (а вложенность весьма конечна и быстро вход/выход работает только на архитектуре ARM), либо ждать пока исполнение в while(1) доберется до нужного места. Будете вносить изменения внутрь while(1) - без контроля у вас начнут разъезжаться обработчики интерфейсов, особенно много ошибок можно наделать в USB. И вам придется каждый раз тратить много усилий на контроль всего этого.
У вас там никаких гарантий по времени перехода скорее всего не будет. Ну либо будет, но цена затраченных усилий будет скорее всего не соответствовать итоговому решению.nv13
02.01.2025 10:08Я видел реальную систему многоканальной обработки на 4ядерном процессоре, работающая с двумя eth интерфейсами, блоком разделяемой памяти и хост интерфейсом. По таймеру каждому каналу предоставлялось время на вызов последовательности функций обработки соотв буфера, в том числе из 3d party библиотеки. Управление через хост интерфейс и разделяемую память позволяло менять параметры обработки, обратно работал printf для логгирования. Модернизация сводилась к изменению -созданию функций, пока производительности хватало. Я там копался в паре мест -ни разу не сталкивался с проблемами в спланированных вычислениях, только табличку расширял для контроля загрузки
Система была реального времени, так как гарантировала время обработки данных, реакции на команду и загрузку системы. Другое дело, что она была не как её, интерактивная)
И была причина, почему там не было никакой ОС -лицензия стоила существенные деньги, существенно дороже, чем МонтаВиста, которая стояла на хосте, и где кроме rt_fifo ничего, вродебы, не использовалось.
Я никоим образом не пытаюсь принизить достоинств осрв, особенно для систем с мышкой и юзером, или если там ещё и полноценные приложения, но while до сих пор имеет право на жизнь, имхо
SmartTherm
02.01.2025 10:08простое и небольшое, наверное да
или что-то непростое и быстрое
моментально упирается в потолок адекватной скорости внесения изменений,
скорее скорости написания/освоения... и тут вылазят библиотеки на все случаи жизни от андурины и тут после невидимого while(1) появляется loop().
ripandtear
02.01.2025 10:08Что-то непростое и быстрое прекрасно пишется и с использованием FreeRTOS.
Причем тут библиотеки не очень понятно. Никто вам их не запрещает использовать при таковой необходимости и с FreeRTOS - только могут быть нюансы в настройке софтверного таймера, если библиотека использует такое понятие для своей внутренней работы.SmartTherm
02.01.2025 10:08быстрее прерываний ничего не сделать. Использование оберток от FreeRTOS скорости не добавляет. (Это утверждение не в контексте ESP, если не очень понятно о чем речь)
только могут быть нюансы в настройке софтверного таймера,
В ардуиновских библиотеках могут быть где угодно нюансы, но в среднем они таки работают и много времени экономят
Zuy
02.01.2025 10:08В Tesla Model S прошивки практически всех блоков написаны были в стиле while(1). Если где и была RTOS то чаще всего в ней создавали задачи типа task_1ms, task_10ms, task_100ms и в них уже в том же стили крутили логику.
SergeyNovak Автор
02.01.2025 10:08Конечно не для всякого проекта это надо, но как только "вырастаем" до реального проекта с HTTP, WiFi, управлением периферией вроде моторов, то разбиение на таски ой как облегчает жизнь.
HiTawr
02.01.2025 10:08Да, так и не понятно почему не запихивать внутрь грамотного while?
ripandtear
02.01.2025 10:08В принципе, я бы сказал так - грамотный while это фактически прообраз (или очень приближенный) к ОСРВ код сам по себе.
Потому что вам все равно требуется некий системный таймер с прерыванием, чтобы контролировать то, что сейчас выполняет ядро МК - контроль за процессорным временем. Например, если вы не успеете обработать USB, из-за внесенных изменений в код - то он у вас отвалится. Потому что в это время МК будет занят чем-то другим, и без ОСРВ выйти из обработки у него не выйдет - только там в обработчике еще через n-времени проверять некий статус, надо ли вернуться куда-то там, т.к. нет программных приоритетов и нет вытесняющей многозадачности.
Либо/вместе с тем же код в остальных прерываниях начнет пухнуть из-за всяких проверок на что где заполнилось или не пришло/не отправилось, что отрицательно влияет на общую структуру программы в целом, не говоря уже об ограничениях вложенности и скорости входа/выхода в этих функциях в зависимости от архитектуры МК.
Отдельная категория проблем - это организация подобной программы с архитектурой while(1) для работы в различных режимах энергопотребления и переключения между ними. Тут я только могу пожелать удачи и хорошего настроения тому человеку, что все это будет отлаживать каждый раз после внесения изменений, на большом проекте в условиях командной работы, когда над проектом работает несколько и более программистов.
В целом, на эту тему можно развернуто написать довольно большой текст.victor_1212
02.01.2025 10:08это супер старая проблема, в сложных случаях всегда старались разделить обработку прерывания на быструю и медленную часть, и делать отдельно, по возможности иметь несколько видов процессов - типа с быстрым переключением контекста, и с более медленным, комбинация while + switch напоминает переключение таких быстрых процессов, также часто используется для парсинга пакетов, например dpi,
про командную работу верно замечено, типа много лет приходилось наблюдать, со временем привыкаешь
checkpoint
02.01.2025 10:08А кто нибудь пробовал RIOT OS ? Это настоящая многозадачная операционная система для микроконтроллеров и простого железа.
GennPen
02.01.2025 10:08LED на микроконтроллере ESP32 должен мигать
А возможно и не замигает потому что спалит порт. Никогда-никогда не подключайте светодиод к выходу без токоограничивающего резистора.
JBFW
02.01.2025 10:08Можно использовать светодиод из светодиодной ленты - у него падение напряжения около 3В, питание процессора 3.3В, за счет минимальной разницы напряжений ток будет небольшим.
Ну, просто светодиодов можно феном кучу навыпаивать за пару минут, а проводной резистор, удобный для эксперимментов, не микроскопический SMD - еще найди сейчас в загашниках...
GennPen
02.01.2025 10:08за счет минимальной разницы напряжений ток будет небольшим.
У него слишком крутая вольт-амперная характеристика на рабочем напряжении. И даже с таким минимальным разбросом нужно ставить соответствующий резистор. А еще лучше не ставить светодиоды с такой маленькой разницей между рабочим напряжением и напряжением падения светодиода.
JBFW
02.01.2025 10:08Ну, можно считать что это такой лайфхак "не по правилам".
Оно работает. А вот красненьким ярким светодиодом спалить порт вполне можно
kuzzdra
02.01.2025 10:08Ну, можно считать что это такой лайфхак "не по правилам".
По правилам китайской электроники если можно что-то сэкономить - значит нужно.
halfworld
02.01.2025 10:08В "Искусстве схемотехники", ещё 1986 года издания, была фраза типа "если элемент повышает надежность, то он должен стоять в схеме". Но в последнее время часто вижу схемы где этим пренебрегают. Буквально конструкция выходного дня (в понедельник уже не работает, сгорела).
Astroscope
02.01.2025 10:08Абсолютно надежным считается устройство, которое проработает первые несколько минут после распаковки - столько, сколько нужно среднестатистическому покупателю условного алиэкспресса на то, чтобы подтвердить получение товара и поставить пять звезд продавцу. Это одновременно ответ и вам, и @kuzzdra на один камент выше. :)
nochkin
02.01.2025 10:08Особенно интересно такая "надёжность" смотрится на фоне позитивных отзывов в стиле "получил, но ещё не подключал. пять звёзд"
Aleksandrmetal
02.01.2025 10:08Вообще у esp32 можно настраивать выходной ток, так что можно и без резистора..
SergeyNovak Автор
02.01.2025 10:08PWM что-ли?
Aleksandrmetal
02.01.2025 10:08Есть специальная функция gpio_set_drive_capability() , которая устанавливает выходной ток от 5 до 40мА.
GennPen
02.01.2025 10:08Но все же, я бы придерживался общепринятых стандартов по подключению через токоограничивающий резистор. А эту функцию оставил бы как защитную, например при подключении MOSFET-танзисторов.
JBFW
02.01.2025 10:08К слову, совсем незачем огорчаться, "если у вас только ESP8266". Для всяких полезных в хозяйстве вещей ее возможностей более чем достаточно, а там где недостаточно - уже можно и об одноплатнике из тв-бокса подумать
SmartTherm
02.01.2025 10:08это сначала кажется, что ее возможностей достаточно. А потом выясняется, что памяти там кот наплакал
GennPen
02.01.2025 10:08Да не только памяти. Аппаратных возможностей тоже маловато. И зачастую задумываешься о небольшой переплате за ESP32, где с аппаратными возможностями все гораздо лучше.
Astroscope
02.01.2025 10:08Для хоббийных проектов абсолютная разница в цене настолько ничтожна, что нет никаких разумных причин выбирать ESP8266 при наличии в свободном доступе любых ESP32, коих целая линейка на самом деле, даже если они многократно избыточны. Здесь избыточны, там уже не очень - а платы в коробке запасены для неизвестно чего и, вот, как раз понадобились. Ну, я так думаю. Для производства, где десятки центов, а то и единицы долларов на сотнях тысяч дают весьма ощутимые деньги, этот подход, конечно, неприменим.
GennPen
02.01.2025 10:08Для производства, где десятки центов, а то и единицы долларов на сотнях тысяч дают весьма ощутимые деньги, этот подход, конечно, неприменим.
Ну, например ESP8266 можно легко заменить на ESP32-C3, который на копейки дороже, но имеет встроенные 4Мб флеш-памяти.
Astroscope
02.01.2025 10:08Не могу говорить за любое производство, потому что в каждом случае есть своя индивидуальная совокупность факторов и еще и субъективная оценка весов этих факторов. Например, кому-то покажется, что есть смысл переходить на ESP32-C3 из-за масштабируемости/унификации, когда по сути одна и та же прошивка используется на разных моделях похожих изделий - чтобы и памяти, и производительности было на вырост, пусть сейчас и не нужно, особенно при не слишком больших тиражах, когда цена разработки уже чувствуется в себестоимости, а не размазывается на сотни тысяч или миллионы изделий. На больших тиражах, уверен, многие производства будут смотреть на безумные с точки зрения хоббийщика решения вроде контроллеров с одноразовой записью, подобранные максимально в обрез по своим возможностям, потому что каждый цент на миллионе изделий - это довольно много.
sterr
02.01.2025 10:08проще RPi Pico тогда использовать. Там уже все есть.
SergeyNovak Автор
02.01.2025 10:08Без WiFi/BT или еще какой-то связи с внешним миром за те деньги - это что-то на расточительно-транжирском.
zbot
02.01.2025 10:08я конечно могу ошибаться но помоему под ESP32 (в отличии от 8266) в принципе невозможно писать не под freеrtos, даже если вы "типа не под freertos" на си пишете, исполняется это как задача в среде rtos.
Если не прав поправьте аргументированно.
jaha33
02.01.2025 10:08На всех esp32 вы создаете проект только в составе idf (экосистема esp с freertos, готовыми дровами под периферию,)
Создать проект на несколько сот байт, который условно дергает gpio в цикле, невозможно. Как бы все open source, но примеров как завезти контроллер с нуля производитель не дает. Вы даже свой загрузчик сделать не можете, загрузчик вшит и закрыт. Может и есть какие то потуги сделать "обезжиренный" проект, но от энтузиастов и неясного качества
NutsUnderline
02.01.2025 10:08меня покоробило несколько шероховатостей во введении. вроде мелкие придирки, но люди такое читают, запоминают как неперерекаемую истину .....
SergeyNovak Автор
02.01.2025 10:08Будем играть в "Угадай мелодию" или "Советский партизан на допросе у врага" ?
NutsUnderline
02.01.2025 10:08по разделу "начнем" может сложиться впечатление что какие то ОС "ставят" на микроконтроллер, очень упрощенное пояснение.
По разделу linux может сложиться впечатление что под linux еще проще будет помигать лампочкой, что уже не так. При этом не указано про сложности с реализации реального времени и зачем вообще тогда нужны эти rtos. кстати самые дешевые платы с linux есть чуть ли не дешевле 700 руб.
после чего начинающих добивают словом оверхед, прерывания, на самом деле еще и калбэки, а добиваем резко заклинанием idf.py menuconfig хотя idf мы еще не поставили. пожалуй уже не для самых маленьких.
дальше все довольно красиво, но кидает начинающего в командную строку. а есть между прочим express-ide, а там еще и отладчик и монитор встроенные. эти вещи не вот уж прямо про rtos но в esp все глубоко завязано, плюс всякие присущие тонкости, которые было бы полезно знать начинающим rtos ерам
Catmengi
02.01.2025 10:08Интересно было бы увидеть статью про elf-loader(espressif/elf_loader) под esp32
SergeyNovak Автор
02.01.2025 10:08Хотелось бы понять сферу, где это может понадобиться с учётом того, что обычные Linux ELF бинарники не подходят от любой платформы и даже собранные под ESP32 не будут иметь практически ничего из привычных классических аттрибутов повседневной ОС: диска, драйверов, библиотек, API ядра и многого другого.
NutsUnderline
02.01.2025 10:08потому что это офигенно :) на самом деле можно построить свою ОС с подгружаемыми программами пользователя, чтобы не компилировать не всю прошивку а маленький elf. что то такое есть в flipper zero ив некоторых других системах. профит от этого сомнителен когда исполняемый код находиться только во флеш и очевиден когда грузиться в оперативку. но в esp32 да и в stm32 подход к этому довольно гибридный, так что профит не очевиден.
Mcublog
02.01.2025 10:08Присоединяюсь к комментариям выше про то, что FreeRTOS фактически планировщик с набором сервисов синхронизации. Также замечу, что есть порт для win/lin, может пригодится при осваивании или эмуляции.
На сколько помню порт основан на posix тредах. Причем после того, как фриртос попала в руки Амазон, они что-то сломали в этом порте. Не помню, что, но помню, что легко было исправить, больше было похоже на опечатку, может быть поправили уже.
Вот моя репа с зарисовками по запуску фриртос+cmake на компе, это форк другого проекта с моими мелкими испавлениями, может кому-то пригодится
smart_alex
02.01.2025 10:08Понадобилось в Ардуино распараллелить две задачи - использовал xTaskCreatePinnedToCore - результат так себе. Вроде бы процессы исполняются на разных ядрах, а взаимозависимость сохраняется и никакие настройки не позволяют получить два не влияюших друг на друга процесса (по задержкам и стабильности работы).
NutsUnderline
02.01.2025 10:08это наверное на esp32 тоже? там ядра крутят еще bt и wifi задачи, у меня и вообще вылеты пошли когда я ядра назначал, а отлаживать что и как в данном случае неудобно
smart_alex
02.01.2025 10:08Да, ESP32. Я ожидал, что если процессы выполняются на разных ядрах, то не будут влиять друг на друга.
В реальности если активен один процесс, то "заикается" другой. Настройки приоритетов и прочие не помогают.
И это разные ядра :) что у них там происходит на одном ядре даже проверять не хочу :)
GennPen
02.01.2025 10:08В реальности если активен один процесс, то "заикается" другой.
А вы уверены, что задача запускается именно на другом ядре?
Не все ESP32 двухядерные.
xTaskCreatePinnedToCore может возвращать ошибку.
Используемый фреймворк может быть настроен на использование только одного ядра.smart_alex
02.01.2025 10:08Да, сама модель ESP32 двухядерная и функции идентификации ядра для потока возвращают разные ядра.
GennPen
02.01.2025 10:08Весьма странно. А не пробовали аналогичный код не через Arduino, а на чистом ESP-IDF, может проделки Arduino?
smart_alex
02.01.2025 10:08Чистый ESP-IDF будет, вероятно, в следующей жизни :)
На всякий случай, под "заиканиями" я понимаю джиттер потока данных на MOSI с частотой SPI 4 МГц. Возможно это так и должно быть на такой частоте, но SPI аппаратный и потоки на разных ядрах - вроде бы мешать друг другу не должны.
GennPen
02.01.2025 10:08Может DMA попробовать использовать? Вот тут кой какие примеры есть: hideakitai/ESP32DMASPI: SPI library for ESP32 which use DMA buffer to send/receive transactions
4chemist
02.01.2025 10:08Стоит посмотреть сколько процессов вообще крутится на ESP32 uxTaskGetNumberOfTasks. У меня 11 показывает в скетче осуществляющем только соединение Wi-Fi.
smart_alex
02.01.2025 10:08Всего 13. Видимо 11 стандартных, как у вас, и 2 моих, раскиданных по двум ядрам.
И что это нам даёт?
Одно ядро обслуживает динамическую LED матрицу, другое ядро - веб-сервер. Как только веб-сервер начинает выдавать страницы, матрица начинает мигать.
rsashka
FreeRTOS, это только ядро (потоки и многозадачность), и программировать под нее мало чем отличается от программирования для bare metal. Те же обработчики прерываний, те же сторонние библиотеки и "если нужно задействовать модули WiFi, Bluetooth, протоколы вроде 1wire, I2C e.t.c. Требует полный спектр знаний в аппаратной части и электронике, в цифровой логике, во внутренней периферии микроконтроллера ...", а то, о чем вы пишите (ESP-IDF), это Espressif IoT Development Framework — Фреймворк разработки IoT (Интернет Вещей) только для платформы ESP32.
В данном случае FreeRTOS, это как ядро Linux в любом дистрибутиве, тогда как состав различных программ в каждом отдельном дистрибутиве может быть разным. Точно так же как и ядро для дистрибутива может быть заменено на какое нибудь другое (например на FreeBSD или на оборот, хоть это и не просто).
Поэтому когда вы пишите "Выбор и настройка компонентов FreeRTOS через 'idf.py menuconfig' ", то это неправильно. Это настраивается ESP-IDF и к FreeRTOS он не имеет никакого отношения (точнее FreeRTOS является одним из множества компонентов этого фреймворка, точно так же как и при настройке компонентов прошивки под STM32).
jaha33
Да и вообще ESP это не лучшая платформа для изучения freertos, в IDF вместе с rtos в подарок идут свои приоритетные потоки, захардкоженные настройки и неудобная диагностика.
В этом плане получше будет stm32+jlink, или вообще под винду собрать проект, благо на сайте фриртоса инструкции вполне себе ок