======Подключаем FreeRTOS====== Для реализации "многопоточных" программ удобно пользоваться системами реального времени. Одна из которых это FreeRTOS. При открытии портала [[https://www.freertos.org/index.html|FreeRTOS.org]] поражает обилие и качество предоставленной документации. За что хочется сказать разработчикам продукта огромное спасибо! Благодаря описанию, проблем с подключением FreeRTOS не возникает. Достаточно скачать архив, удалить все не ненужное и подключить к своему проекту некоторое количество исходников с указанием пути. В этом помогут эти две ссылки: - [[https://www.freertos.org/a00017.html|Организация файлов в архиве]] - что удалять. - [[https://www.freertos.org/Creating-a-new-FreeRTOS-project.html|Создание проекта]] - что подключать. Внутри FreeRTOS уже есть порт под все Cortex-M ядра, которые используются в Миландр. Для 1986ВЕ1Т подходит профиль от Cоrtex-M0. Для отсчетов времени, при работе планировщика задач, используется встроенный в ядро системный таймер. Поэтому вне зависимости от периферии, которую каждый производитель добавляет к ядру ARM, порт от FreeRTOS уже является рабочим. Для удобства использования FreeRTOS, эта система была добавлена в состав Pack_v6. Теперь для подключения FreeRTOS достаточно выставить пару галок в Keil. Выглядит это так: {{doc:doclist:freertos_keil.png}} Для простейшей проверки работы FreeRTOS был написан проект мигания светодиодами из нескольких потоков - [[https://github.com/StartMilandr/MDR_Pack_v6/tree/master/PACK_Gen/Files/Examples/All_Boards/FreeRTOS/FreeRTOS_Leds|GitHub]]. Проект создан сразу под несколько микроконтроллеров - переключается в выпадающем списке SelectTarget. //(Но проверен пока только на 1986ВЕ3Т, остальные будут проверены после снятия карантина.)// Как видно по картинке, для минимального использования FreeRTOS необходимо подключить файлы, которые представлены в группе FreeRTOS в дереве проекта. При использовании Pack это происходит автоматически при установке галочки Core. При установке галки необходимо так-же выбрать вариант реализации менеджера памяти - heap_0 ... heap_5. Описание вариантов доступно по этой [[https://www.freertos.org/a00111.html|ссылке]]. Попасть на страницу можно так-же кликнув на гиперссылку, которая выделена синим цветом справа от heap_3 на картинке. Это мое первое знакомство с FreeRTOS, поэтому рекомендаций по выбору менеджера памяти дать не могу. Для запуска простого проекта с несколькими задачами, подключения галочки "Core" достаточно. Остальные файлы, которые были в составе FreeRTOS вынесены отдельными пунктами. Их можно подключить при необходимости. //(На данный момент в Pack реализовано вот такое подключение исходников. Возможно позднее что-то будет изменено при возникновении потребности или большего осознания особенностей работы FreeRTOS).// По картинке так-же видно, что в дереве проекта подключено несколько файлов - port.c и FreeRTOSConfig.h. Это потому что проект реализован сразу для нескольких микроконтроллеров, в данном случае - четырех. Это все разные файлы, отличаются пути. * В файле **port.c** реализовано само портирование системы под ядро ARM. Этот файл свой под каждое ядро - Cortex-M0 (M1), M3, M4. * Все настройки вынесены в **FreeRTOSConfig.h**. Этот файл копируется в каждый проект для возможности настройки системы. Описание параметров можно найти на том же сайте [[https://www.freertos.org/a00110.html|FreeRTOS.org]], либо можно поискать тоже самое на русском - [[http://microsin.net/programming/arm/freertos-customisation.html|microsin.net]] =====Подключение MDR_Timer вместо SysTimer для 1986ВЕ1Т/1986ВЕ3Т===== Как известно из errata, в микроконтроллерах 1986ВЕ1Т и 1986ВЕ3Т системный таймер не считает, пока ядро останавливается для отработки EEPROM_Delay - задержки доступа к Flash памяти (Потому что память не умеет работать так быстро, как ядро). По этой причине, системный таймер не дает точных отсчетов времени - он считает медленнее чем положено. Чтобы избавится от данной проблемы надо вместо системного таймера, подключить к FreeRTOS любой из аппаратных таймеров доступных в микроконтроллере. Делается это все в том-же файле port.c. Чтобы не сильно менять файл исходный port.c (на тот случай, если его придется обновлять из первоисточника или подключать другую реализацию) я реализовал подключение аппаратного таймера в отдельном файле **port_timer_mdr.h** и подключил его к port.c. Файл port_timer_mdr.h преднамеренно не имеет защиты от двойного подключения, как это принято в заголовочных файлах, чтобы его вторичное подключение сразу давало ошибку на этапе компиляции. Этот файл подключается только в port.c и нигде больше подключаться не должен! //(Реализовывать код в заголовочном файле не красиво, но хотелось оставить возможность выбирать вариант с системным таймером и с аппаратным таймером. А кроме этого иметь возможность по быстрому подключить реализацию с аппаратным таймером к другому port.c, например для того же GCC, не сильно меняя исходный файл. Так проще отслеживать изменения, если они будут происходить при обновлении FreeRTOS от первоисточника.)// В port.c получилось так: #if (configOVERRIDE_DEFAULT_TICK_CONFIGURATION == 0) ... // исходный код работы с SysTimer #else #include "port_timer_mdr.h" // код с MDR_Timer #endif // configOVERRIDE_DEFAULT_TICK_CONFIGURATION В файле FreeRTOSConfig.h назначается новое название обработчика, которое будет использоваться вместо SysTick_Handler: #include #if( configOVERRIDE_DEFAULT_TICK_CONFIGURATION == 0 ) // Исходный обработчик #define xPortSysTickHandler SysTick_Handler #else // Обработчик аппаратного прерывания от MDR_Timer. // MDR_FREE_RTOS_TIMER_HANDLER выбирается в MDR_Config.h #define xPortSysTickHandler MDR_FREE_RTOS_TIMER_HANDLER // Компенсация времени обработки в режиме configUSE_TICKLESS_IDLE == 1 #if configUSE_TICKLESS_IDLE #define portMISSED_COUNTS_FACTOR MDR_FREE_RTOS_TIMER_MISSED_FACTOR #endif #endif Файл подключает файл для конкретного МК (например ), где задается какой таймер будет использован вместо SysTimer: //=========================== FreeRTOS (for FreeRTOSConfig.h) =========================== // Выбор таймера для отсчета configTICK_RATE_HZ, по умолчанию - системный таймер SysTimer #define configOVERRIDE_DEFAULT_TICK_CONFIGURATION 1 #if configOVERRIDE_DEFAULT_TICK_CONFIGURATION != 0 // Выбор аппаратного таймера для FreeRTOS (вместо SysTimer который в ВЕ1 и ВЕ3 имеет ошибку в errata) #define MDR_FREE_RTOS_TIMER MDR_TIMER4ex #define MDR_FREE_RTOS_TIMER_HANDLER TIMER4_IRQHandler // Подстройка отсчетов времени при configUSE_TICKLESS_IDLE = 1 // !!! Значение неправильное (взято наглаз по скорости мигания светодиода), перемерить осциллографом и поменять!!! // см функцию - vPortSuppressTicksAndSleep() файл FreeRTOS/port.c #define MDR_FREE_RTOS_TIMER_MISSED_FACTOR 445 #endif Если configOVERRIDE_DEFAULT_TICK_CONFIGURATION = 1, то вместо SysTimer будет использоваться таймер MDR_FREE_RTOS_TIMER с прерыванием MDR_FREE_RTOS_TIMER_HANDLER. В случае 1986ВЕ1 и 1986ВЕ3Т удобно назначит на эти цели таймер MDR_TIMER4, потому что он по регистрам управляется отдельно от первых трех. В частности его нельзя запустить / остановить синхронно подав частоту через регистр TIM_CLOCK. =====Опция configUSE_TICKLESS_IDLE===== Таймер обычно отсчитывает для ОС периоды в 1мс. Но возможен случай, когда все задачи приостановлены или находятся в режиме ожидания, например в функции sleep(). Если время простоя оставляет величину много большую чем 1мс, то имеет смысл не генерить прерывания каждую 1мс, а выставит таймер сразу на ожидаемое время бездействия. Такой режим работы FreeRTOS включается в FreeRTOSConfig.h параметром configUSE_TICKLESS_IDLE = 1, а отрабатывается функцией: void vPortSuppressTicksAndSleep(TickType_t xExpectedIdleTime); Функция настраивает таймер на ожидаемое время простоя xExpectedIdleTime, а затем переводит ядро в режим SLEEP функцией __wfi(). Ядро выйдет из режима сна когда возникнет какое-нибудь прерывание. После выхода из сна функция vPortSuppressTicksAndSleep() проверяет по таймеру ли было пробуждение, или по какому то другому прерыванию и затем перенастраивает таймер для продолжения нормального отсчета 1мс. Данный режим снижает энергопотребление, но приводит к тому что есть небольшая задержка на время пока таймер останавливается и перенастраивается. Данная задержка введена параметром portMISSED_COUNTS_FACTOR в файле port.c: /* A fiddle factor to estimate the number of SysTick counts that would have occurred while the SysTick counter is stopped during tickless idle calculations. */ #ifndef portMISSED_COUNTS_FACTOR #define portMISSED_COUNTS_FACTOR ( 45UL ) #endif Эта задержка определена для системного таймера. При использовании режима configUSE_TICKLESS_IDLE с аппаратным таймером нужна подобная задержка. Полагаю она будет зависеть от опций оптимизации компилятора, поэтому данная задержка вынесена в MDR_ConfigVE3.h под именем MDR_FREE_RTOS_TIMER_MISSED_FACTOR и должна подстраиваться под конкретную сборку. Сейчас значение выставлено наугад, подбирая чтобы светодиод мигал приблизительно раз в секунду. Лучше всего было бы подключить осциллограф к светодиоду и выставить данный параметр так, чтобы на осциллографе период необходимы период. =====Особенности текущей реализации===== * При создании проекта необходимо **увеличить размер кучи**. Значения из Pack по умолчанию не позволяют системе выделить память под контекст задач. Указать размер памяти можно в ассемблерном файле setup_MDR1986VE*.s, либо в закладке Configuration Wizard, которую Keil предлагает при отображении setup_MDR1986VE*.s файла (внизу окна редактора). Если память выделить не удастся, то исполнение будет висеть на строке configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY ), файл //tasks.c//. На картинке ниже так-же увеличен стек, но делать этого нет необходимости, можно было оставить 0x400. Важен размер именно кучи, чтобы каждой задаче выделился свой стек из этой памяти. Можно поискать статьи на данную тему, например эта [[https://habr.com/ru/post/352782/|HABR: Отладка многопоточных программ на базе FreeRTOS.]] {{doc:doclist:freertos_heapsize.png}} * Проект собирается под //ARM Compiler V5// и под //ARM Compiler V6//. Некоторые функции port.c реализованы на ассемблере, который разный у компиляторов V5 и V6. Для компилятора V6 порт //port.c// берется из директории FreeRTOS\portable\GCC, а для V5 из директории FreeRTOS\portable\RVDS. Keil автоматически подключает //port.c// из нужной директории при смене компилятора. Пути прописаны в Milandr.MDR1986VExx.pdsc - файле конфигурации пака. * Возможность использования аппаратного таймера добавлена только в порт для микроконтроллеров на базе ядра Cortex-M0(М1). И по умолчанию включена для 1986Ве1Т/1986ВЕ3Т в файлах MDR_ConfigVE1.h/MDR_ConfigVE3.h. В остальных микроконтроллерах ошибки системного таймера нет, поэтому необходимости в использовании аппаратного таймера нет. =====ИТОГО===== Для того чтобы использовать FreeRTOS c [[https://github.com/StartMilandr/MDR_Pack_v6|Pack_v6]] необходимо : * Создать проект и подключить FreeRTOS галочкой Core, выбрав необходимый менеджер памяти. * Увеличить размер кучи в файле setup_MDR1986VE*.s * Для 1986ВЕ1Т/1986ВЕ3Т выбрать аппаратный таймер для FreeRTOS в файле MDR_ConfigVEx.h. //(Для других МК будет использоваться системный таймер.)// * При необходимости в энергосбережении выключить configUSE_TICKLESS_IDLE = 1. Для 1986ВЕ1Т/1986ВЕ3Т в таком случае подобрать параметр MDR_FREE_RTOS_TIMER_MISSED_FACTOR в MDR_ConfigVEх.h В архиве FreeRTOS достаточно много примеров, в частности работы с Ethernet, которые можно использовать для изучения.