Продолжаем учиться писать код для микроконтроллера ESP8266.
На данном занятии мы также продолжим тему передачи данных по шине UART, но попытаемся мы передать данные на ПК уже с использованием ОС. В качестве ОС мы будем использовать операционную системы FreeRTOS.
FreeRTOS — многозадачная операционная система реального времени (ОСРВ) для встраиваемых систем. Использование данной ОС позволит нам организовать сразу несколько потоков и в любой момент уничтожить любой из них.
Операционная система реального времени позволяет нам добиться организации процессов, также многозадачности, причём часть работы по данной организации система берёт на себя, но некоторая часть данной организации вопросов также ложится и на разработчика приложения.
С системой FreeRTOS мы с вами уже хорошо знакомы, так как использовали её для написания наших проектов для контроллера STM32. Поэтому теоретическую часть по работе с данной системой мы смело можем пропустить, так как мы уже знаем, что представляют собой такие механизмы данной операционной системы, как семафоры, задачи, очереди и т.д. Тем не менее код для контроллера ESP8266 будет отличаться, так как имена функций, структур, макросов для STM32 мы использовали специфические для используемых библиотек порта для контроллера STM32, а для написания кода для ESP8266 принято использование классического интерфейса. Но разница здесь не столь велика, так что я думаю вместе мы с этим разберёмся без особого труда.
Поэтому давайте сразу же приступим к делу, а если что будет непонятно, то разберёмся по ходу написания наших проектов.
Схема для начала у нас будет простейшая — отладочная плата, подключенная к ПК по USB
А проект мы создадим из проекта прошлого занятия с именем I2C_LCD2004_REMAP и назовём его UART_TX_RTOS.
С настройкой проекта придётся повозиться, так как для работы с системой FreeRTOS используется совсем другой SDK. За основу был взят официальный комплект ESP8266_RTOS_SDK-2.0.0.
Для использования в наших проектах данного комплекта SDK создадим каталог (или папку) с именем ESP8266_RTOS_SDK в каталоге ESP8266, в котором у нас хранится обычный подключаемый комплект и скопируем в данный каталог содержимое каталога ESP8266_RTOS_SDK-2.0.0 официального комплекта.
Откроем наш проект в Eclipse и внесём сначала изменения в свойствах проекта во вкладке C/C++ General -> Paths and Symbols -> Includes -> GNU C.
Во-первых, во всех путях заменим имя каталога ESP8266_NONOS_SDK на UART_TX_RTOS. Также добавим ещё 2 пути: D:\ESP8266\ESP8266_RTOS_SDK\include\espressif
и D:\ESP8266\ESP8266_RTOS_SDK\include\freertos.
В результате получится вот так
Сохраним изменения и перейдём к файлу сценария Makefile. Принцип построения данного файла остаётся прежним, только в него было внесено несколько новых переменных, также изменились имена подключаемых библиотек и путей, поэтому не будем его собирать по крупицам, а лучше я сразу дам всё его содержимое
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
include ../common.mk SDK := D:/ESP8266/ESP8266_RTOS_SDK SDK_INC := $(SDK)/include SDK_DRIVER_INC := $(SDK)/driver_lib/include SDK_LIBDIR = lib LD_SCRIPT = eagle.app.v6.ld LIBS = cirom crypto espconn espnow freertos gcc hal lwip main mesh mirom net80211 nopoll phy pp mbedtls openssl smartconfig wpa wps airkiss SDK_INCDIR = extra_include include driver_lib/include include/espressif include/lwip include/lwip/ipv4 include/lwip/ipv6 include/nopoll include/spiffs include/ssl include/json include/openssl SDK_INCDIR := $(addprefix -I$(SDK)/,$(SDK_INCDIR)) SDK_LIBDIR := $(addprefix $(SDK)/,$(SDK_LIBDIR)) LD_SCRIPT := $(addprefix -T$(SDK)/ld/,$(LD_SCRIPT)) LIBS := $(addprefix -l,$(LIBS)) XTENSA := D:/ESP8266/xtensa-lx106-elf CC := $(XTENSA)/bin/xtensa-lx106-elf-gcc AR := $(XTENSA)/bin/xtensa-lx106-elf-ar LD := $(XTENSA)/bin/xtensa-lx106-elf-gcc SIZE := $(XTENSA)/bin/xtensa-lx106-elf-size CC_FLAGS := -g -O0 -std=gnu99 -Wpointer-arith -Wundef -Werror -Wl,-EL \ -fno-inline-functions -nostdlib -mlongcalls \ -mtext-section-literals -mno-serialize-volatile -D__ets__ \ -DICACHE_FLASH -c LDFLAGS = -nostdlib -Wl,--no-check-sections -u call_user_start -Wl,-static ESPTOOL := esptool SRC_DIR = src/ src_files := $(wildcard $(SRC_DIR)*) obj_files := $(addprefix build/, $(addsuffix .o, $(basename $(notdir $(src_files))))) define build-obj @echo "CC "$(notdir $@) @$(CC) -Iinc -I$(SDK_INC) -I$(SDK_INC)/json -I$(SDK_INC)/espressif $(SDK_INCDIR) \ $(CC_FLAGS) -c $< -o $@ endef .PHONY: all clean build/%.o: src/%.c inc/%.h inc/user_config.h $(call build-obj) build/app_app.a: $(obj_files) @echo "AR build/app_app.a" @$(AR) cru build/app_app.a $(obj_files) build/app.out: build/app_app.a @echo "LD build/app.out" @$(LD) -L$(SDK_LIBDIR) $(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group $(LIBS) build/app_app.a $(SDK_LIBDIR)/libdriver.a -Wl,--end-group -o build/app.out all: build/app.out @echo "SIZE build/app.out" @$(SIZE) build/app.out @echo "ESPTOOL build/app.out-0x00000.bin build/app.out-0x10000.bin" @$(ESPTOOL) elf2image build/app.out flash: @$(ESPTOOL) -p $(PORT) -b $(BAUDRATE) write_flash --no-compress -ff 40m -fm dio -fs 4MB 0x00000 build/app.out-0x00000.bin 0x20000 build/app.out-0x20000.bin clean: @rm -v build/*.o build/app_app.a build/app.out build/app.out-0x00000.bin build/app.out-0x20000.bin |
Удалим из каталога проекта файлы i2c_user.c, i2c_user.h, lcd.c и lcd.h.
Из файла main.h удалим подключение всех заголовочных файлов и подключим вместо них другие
1 2 3 4 5 6 7 8 |
#define MAIN_H_ //------------------------------------------------ #include "esp_common.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "uart.h" #include "gpio.h" |
Из заголовка следующей функции в файле main.c удалим атрибут
uint32 ICACHE_FLASH_ATTR user_rf_cal_sector_set(void)
А здесь удалим перечисляемый тип
enum flash_size_map size_map = system_get_flash_size_map();
Из тела функции user_init удалим полностью весь код, включая и бесконечный цикл, который больше не потребуется, так как код до него никогда не дойдёт.
Если мы сейчас попытаемся собрать код, то у нас он все равно не соберётся, так как у нас кое-чего не хватает в библиотеке SDK и в тулчейне.
В каталоге ESP8266\xtensa-lx106-elf\xtensa-lx106-elf\lib не хватает файла libhal.a, поэтому скопируем его туда. Также в каталоге D:\ESP8266\ESP8266_RTOS_SDK\lib не хватает файла libmesh.a, поэтому также его туда скопируем. Здесь возникает резонный вопрос: а где взять эти файлы? Честно признаться, не помню, где я их нашел, поэтому возьмёте их вот в этом архиве.
Вот теперь проект соберётся и можно смело писать код для работы с операционной системой FreeRTOS.
В функции user_init произведём настройку UART на определённую скорость обмена
1 2 3 4 |
void ICACHE_FLASH_ATTR user_init() { UART_SetBaudrate(UART0,BIT_RATE_115200); UART_SetBaudrate(UART1,BIT_RATE_115200); |
Проверим его работу
1 2 3 4 |
UART_SetBaudrate(UART1,BIT_RATE_115200); os_printf("\r\n\r\n"); os_printf("SDK version:%s\n", system_get_sdk_version()); os_printf("\r\n\r\n"); |
Создадим функцию для первой задачи
1 2 3 4 5 6 |
#include "main.h" //------------------------------------------------------ void ICACHE_FLASH_ATTR task1(void *pvParameters) { } //------------------------------------------------------ |
Объявим переменную для счётчика и символьный массив
1 2 3 4 |
void ICACHE_FLASH_ATTR task1(void *pvParameters) { uint32 cnt; char str01[20]; |
Добавим бесконечный цикл, в котором выведем в терминальную программу имя задачи и значение счётчика
1 2 3 4 5 6 |
char str01[20]; while(1) { snprintf(str01, sizeof(str01), "Task1: %7lu", cnt); os_printf("%s\n", str01); } |
Мы использовали функцию подготовки строки потому, что функция os_printf некорректно работает с типами long.
Увеличим значение счётчика на один, затем, если оно достигнет некоторого порога, обнулим его, и затем применим задержку на 1 секунду
1 2 3 4 |
os_printf("%s\n", str01); cnt++; if(cnt>=10000000) cnt=0; vTaskDelay(1000 / portTICK_RATE_MS); |
Как мы знаем, применение обычных задержек в FreeRTOS недопустимо, так как они останавливают ход программы, а нам этого делать нельзя, так как во время задержки работа планировщика и остальных задач не должна приостанавливаться, поэтому существуют специальные задержки для FreeRTOS.
Создадим нашу задачу в user_init(), пока без всяких параметров
1 2 |
os_printf("\r\n\r\n"); xTaskCreate(task1, "task1", 256, NULL, 1, NULL); |
Соберём наш проект.
Только перед тем как прошивать контроллер, лучше его полностью очистить и прошить по адресу 0x3FC000 файл esp_init_data_default_v05.bin, то есть подготовить, как мы это делали в уроке 5.
Теперь можно проект прошить.
Мы увидим, что в терминальной программе наш счётчик будет увеличиваться раз в секунду, то есть наша задача работает
Создадим аналогичную функцию для другой задачи ниже функции task1, только задержку сделаем чуть поменьше
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//------------------------------------------------------ void ICACHE_FLASH_ATTR task2(void *pvParameters) { uint32 cnt=0; char str01[20]; while(1) { snprintf(str01, sizeof(str01), "Task2: %7lu", cnt); os_printf("%s\n", str01); cnt++; if(cnt>=10000000) cnt=0; vTaskDelay(900 / portTICK_RATE_MS); } } //------------------------------------------------------ |
Создадим данную задачу в user_init()
1 2 |
xTaskCreate(task1, "task1", 256, NULL, 1, NULL); xTaskCreate(task2, "task2", 256, NULL, 1, NULL); |
Соберём код, пошьём контроллер, и увидим, как наши задачи будут работать одновременно. Поначалу будет казаться, что задачи работают синхронно, но вскоре счётчик задачи 2 обгонит счётчик первой задачи
Так как коды функций задач почти одинаковые, есть смысл использовать для них только одну функцию и применить параметры.
Удалим функцию task2 вместе с телом.
Объявим глобальную структуру, в полях которой будет храниться номер задачи и величина задержки, и объявим две переменные типа такой структуры
1 2 3 4 5 6 7 8 |
#include "main.h" //------------------------------------------------------ typedef struct { unsigned char num_task; unsigned short del; } pData; pData dt1, dt2; |
В функции user_init инициализируем поля данных переменных
1 2 3 |
os_printf("\r\n\r\n"); dt1.del = 1000; dt1.num_task = 1; dt2.del = 900; dt2.num_task = 2; |
Изменим вызов функций создания наших задач, используя в качестве параметров указатель на переменные структур
1 2 3 |
dt2.del = 900; dt2.num_task = 2; xTaskCreate(task1, "task1", 256, (void *) &dt1, 1, NULL); xTaskCreate(task1, "task2", 256, (void *) &dt2, 1, NULL); |
В функции task1, которая теперь служит функцией для обеих наших задач, объявим указатель на тип нашей структуры и присвоим ей адрес параметров из входящих аргументов
1 2 |
uint32 cnt=0; pData *pdt = (pData*) pvParameters; |
Немного изменим код вызова функции подготовки строки, добавив туда номер задачи из параметров
snprintf(str01, sizeof(str01), "Task%d: %7lu", pdt->num_task, cnt);
Также изменим код вызова функции задержки, используя её значение также из параметров
vTaskDelay(pdt->del / portTICK_RATE_MS);
Соберём код, прошьём контроллер, и увидим, что наша программа будет работать также, как и без применения параметров, зато мы избавились от лишней функции
Таким образом, сегодня мы познакомились с тем, как можно настроить тулчейн для сборки проектов в IDE Eclipse для работы с контроллером ESP8266 под управлением операционной системы реального времени FreeRTOS, также мы создали пробный проект, в котором одновременно работают две задачи, а ещё повторили, как применять параметры в задачах.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP8266 Следующий урок
Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU
Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Здравствуйте! В данном уроке столкнулся с тем, что даже Ваш код не компилируется. Вываливается ошибка:
f:/esp8266/xtensa-lx106-elf/bin/../lib/gcc/xtensa-lx106-elf/5.2.0/../../../../xtensa-lx106-elf/bin/ld.exe: cannot find crt1-sim.o: No such file or directory
f:/esp8266/xtensa-lx106-elf/bin/../lib/gcc/xtensa-lx106-elf/5.2.0/../../../../xtensa-lx106-elf/bin/ld.exe: cannot find _vectors.o: No such file or directory
f:/esp8266/xtensa-lx106-elf/bin/../lib/gcc/xtensa-lx106-elf/5.2.0/../../../../xtensa-lx106-elf/bin/ld.exe: cannot find -lhal
collect2.exe: error: ld returned 1 exit status
хотя библиотека ESP8266_RTOS_SDK-2.0.0. Это что, нехватка файлов?
Нашел указанные файлы, но результата все равно нет: «ld.exe: cannot find -lsim». Что это может быть, несоответствие версий?
Решил вопрос! Скачал последнюю версию esp8266-gcc5.2.0-r17.exe, распаковал, и взял оттуда библиотеку. Кстати, в моей первой версии по пути f:/xtensa-lx106-elf/xtensa-lx106-elf/lib не было папки lib! Почему, не знаю. Но факт, я ее создавал сам и помещал в нее необходимый файл.
Спасибо Вам за уроки!
Здравствуйте! Спасибо за Ваши труды. У меня при компиляции возникли ошибки не может найти файлы crt1-sim.o и _vectors.o.
c:/esp8266/xtensa-lx106-elf/bin/../lib/gcc/xtensa-lx106-elf/5.2.0/../../../../xtensa-lx106-elf/bin/ld.exe: cannot find crt1-sim.o: No such file or directory
c:/esp8266/xtensa-lx106-elf/bin/../lib/gcc/xtensa-lx106-elf/5.2.0/../../../../xtensa-lx106-elf/bin/ld.exe: cannot find _vectors.o: No such file or directory
Не знаете где эти файлы взять и в чем может быть проблема? Спасибо!
Погуглил вроде дело в кроскомпиляторе и утилите Make. Можете выложить архивы с утилитой Make и вашим кроскомпилятором. Т.к. есть подозрение, что время прошло и там уже новый компилятор на сайте, который не работает. Спасибо!
Каким-то чудом все заработало! переставил кроскомпилятор и добавил файлы hal и mesh.
Всем терпения. При компиляции проекта возникли проблемы с файлами
d:/esp8266/xtensa-lx106-elf/bin/../lib/gcc/xtensa-lx106-elf/5.2.0/../../../../xtensa-lx106-elf/bin/ld.exe: cannot find -lmesh
elf/5.2.0/../../../../xtensa-lx106-elf/bin/ld.exe: cannot find -lhal
Частично проблема решилась со скачиванием с github свежего ESP8266_RTOS_SDK_v2.1.1. При распаковке в папке lib лежал недостающий файл libhal.a. А проблема с -lmesh осталась, пришлось схитрить и удалить из makefile слово mesh. Тогда все успешно собралось. Я предполагаю mesh связана с mesh сетями, и думаю в этом курсе они нам не пригодятся.
https://github.com/espressif/ESP8266_RTOS_SDK/archive/refs/tags/v2.1.1.zip
Да простят небеса мое меня за невнимательность. Решение ошибки есть на сайте и в видео уроке.