Для реализации "многопоточных" программ удобно пользоваться системами реального времени. Одна из которых это FreeRTOS. При открытии портала FreeRTOS.org поражает обилие и качество предоставленной документации. За что хочется сказать разработчикам продукта огромное спасибо!
Благодаря описанию, проблем с подключением FreeRTOS не возникает. Достаточно скачать архив, удалить все не ненужное и подключить к своему проекту некоторое количество исходников с указанием пути. В этом помогут эти две ссылки:
Внутри FreeRTOS уже есть порт под все Cortex-M ядра, которые используются в Миландр. Для 1986ВЕ1Т подходит профиль от Cоrtex-M0. Для отсчетов времени, при работе планировщика задач, используется встроенный в ядро системный таймер. Поэтому вне зависимости от периферии, которую каждый производитель добавляет к ядру ARM, порт от FreeRTOS уже является рабочим.
Для удобства использования FreeRTOS, эта система была добавлена в состав Pack_v6. Теперь для подключения FreeRTOS достаточно выставить пару галок в Keil. Выглядит это так:
Для простейшей проверки работы FreeRTOS был написан проект мигания светодиодами из нескольких потоков - GitHub. Проект создан сразу под несколько микроконтроллеров - переключается в выпадающем списке SelectTarget. (Но проверен пока только на 1986ВЕ3Т, остальные будут проверены после снятия карантина.)
Как видно по картинке, для минимального использования FreeRTOS необходимо подключить файлы, которые представлены в группе FreeRTOS в дереве проекта. При использовании Pack это происходит автоматически при установке галочки Core. При установке галки необходимо так-же выбрать вариант реализации менеджера памяти - heap_0 … heap_5. Описание вариантов доступно по этой ссылке. Попасть на страницу можно так-же кликнув на гиперссылку, которая выделена синим цветом справа от heap_3 на картинке. Это мое первое знакомство с FreeRTOS, поэтому рекомендаций по выбору менеджера памяти дать не могу.
Для запуска простого проекта с несколькими задачами, подключения галочки "Core" достаточно. Остальные файлы, которые были в составе FreeRTOS вынесены отдельными пунктами. Их можно подключить при необходимости. (На данный момент в Pack реализовано вот такое подключение исходников. Возможно позднее что-то будет изменено при возникновении потребности или большего осознания особенностей работы FreeRTOS).
По картинке так-же видно, что в дереве проекта подключено несколько файлов - port.c и FreeRTOSConfig.h. Это потому что проект реализован сразу для нескольких микроконтроллеров, в данном случае - четырех. Это все разные файлы, отличаются пути.
Как известно из 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 <MDR_Config.h> #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
Файл <MDR_Config.h> подключает файл для конкретного МК (например <MDR_ConfigVE3.h>), где задается какой таймер будет использован вместо 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.
Таймер обычно отсчитывает для ОС периоды в 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 и должна подстраиваться под конкретную сборку. Сейчас значение выставлено наугад, подбирая чтобы светодиод мигал приблизительно раз в секунду. Лучше всего было бы подключить осциллограф к светодиоду и выставить данный параметр так, чтобы на осциллографе период необходимы период.
Для того чтобы использовать FreeRTOS c Pack_v6 необходимо :
В архиве FreeRTOS достаточно много примеров, в частности работы с Ethernet, которые можно использовать для изучения.