Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кармин Новиелло - Освоение STM32.pdf
Скачиваний:
2743
Добавлен:
23.09.2021
Размер:
47.68 Mб
Скачать

Запуск FreeRTOS

613

к взаимоблокировкам (deadlocks)19, если приоритет ISR, которая делает вызов HAL_Delay(), выше, чем у таймера SysTick (и это всегда верно, если вы используете FreeRTOS, как было сказано выше). Поэтому лучше использовать другой таймер для HAL.

Чтобы изменить источник временного отсчета HAL, следуйте инструкциям, изложенным в Главе 11.

23.2.1.3.Как разрешить поддержку FPU в ядрах Cortex-M4F и Cortex-M7

Если у вас микроконтроллер STM32 на базе Cortex-M4F или Cortex-M7, и, если вы попытаетесь скомпилировать проект, то увидите несколько ошибок, сгенерированных ассемблером, как показано на рисунке 9.

Рисунок 9: Ошибки, сгенерированные GCC при попытке скомпилировать файлы с исходным кодом FreeRTOS без включения модуля FPU

Эти ошибки вызваны тем фактом, что архитектуры Cortex-M4F или Cortex-M7 предо-

ставляют специальный модуль операций с плавающей точкой (Floating Point Unit, FPU), ко-

торый позволяет обрабатывать операции с плавающей точкой непосредственно в аппаратной части, без необходимости в специализированных и неизбежно медленных функций, предоставляемых библиотекой среды выполнения Си. Процессоры, оснащенные модулем FPU, реализованы с дополнительными аппаратными регистрами, которые необходимо сохранить во время операции переключения контекста. По этой причине переносимый код FreeRTOS для GCC для архитектур M4F/7 ожидает, что FPU включен, который по умолчанию отключен.

Чтобы включить его, перейдите в раздел Project Settings → C/C++ Build → Settings → Target Processor и выберите пункт FP instructions (hard) в поле Float ABI, а в поле FPU Type выберите fpv4-sp-d16, если у вас микроконтроллер STM32 на базе Cortex-M4F или fpv5-sp-d1620, если у вас микроконтроллер на базе Cortex-M7. Если вы работаете с новейшими микроконтроллерами STM32F76xx, которые предоставляют модуль FPU двойной точности, вам нужно выбрать запись fpv5-d16.

Теперь вам нужно пересобрать всю структуру с исходными файлами.

23.3. Управление потоками

После того, как мы настроили проект Eclipse, можно начать написание кода, используя уровень CMSIS-RTOS, а, следовательно, и FreeRTOS.

19В конкурентном программировании взаимоблокировка – это ситуация, в которой каждый из двух или более конкурентных потоков выполнения ожидают завершения другого, и, следовательно, ни один из них никогда не сделает этого. Подвергнуться взаимоблокировке совсем не сложно, и все программисты рано или поздно сталкиваются с этим трудным в отладке событием.

20fpv4-sp-d16 означает, что микроконтроллер реализует модуль операций с плавающей точкой, соответствующий архитектуре VFPv4-D16 с одинарной точностью (sp), тогда как fpv5-sp-d16 относится к архитектуре VFPv5-D16 с одинарной точностью (sp).

Запуск FreeRTOS

614

В основе всех ОСРВ лежит понятие потока, которое мы проанализировали в первом параграфе данной главы. Поток – это не что иное, как функция Си, которую FreeRTOS необходимо определить следующим образом:

void ThreadFunc(void const *argument) { while(1) {

...

}

osThreadTerminate(NULL);

}

Функция osThreadTerminate() используется для завершения потока, и она принимает идентификатор потока (Thread ID, TID), который мы рассмотрим через некоторое время. Поток обычно состоит из бесконечного цикла, который содержит команды потока. Размещение osThreadTerminate() вне этого цикла обычно является мерой предосторожности на случай, если управление выходит из этого цикла, поскольку некорректно завершать поток, просто возвращаясь из его функции. Передача параметра NULL в функцию osThreadTerminate() приведет к тому, что FreeRTOS завершит текущий поток.

Чтобы запустить новый поток с API-интерфейсом CMSIS-RTOS, мы используем следующую функцию:

osThreadId osThreadCreate(const osThreadDef_t *thread_def, void *argument);

osThreadDef_t – дескриптор потока, то есть структура Си, определенная следующим образом:

typedef struct os_thread_def {

 

char

*name;

/* Имя потока

*/

os_pthread

pthread;

/* Указатель на функцию потока

*/

osPriority

tpriority;

/* Начальный приоритет потока

*/

uint32_t

instances;

/* Максимальное количество экземпляров этой функции потока:

 

 

 

это бессмысленно во FreeRTOS

*/

uint32_t

stacksize;

/* Требования к размеру стека в словах;

 

 

 

0 - размер стека по умолчанию

*/

#if( configSUPPORT_STATIC_ALLOCATION == 1 )

 

uint32_t

*buffer;

/* Буфер стека при статическом выделении

*/

osStaticThreadDef_t *controlblock; /* Блок управления для хранения данных потока

 

 

 

при статическом выделении

*/

#endif

 

 

 

} osThreadDef_t;

 

 

Однако API-интерфейс CMSIS-RTOS предоставляет удобный макрос osThreadDef() для определения и инициализации параметров дескриптора потока. А теперь самое время увидеть практический пример.

Имя файла: src/main-ex1.c

12int main(void) {

13osThreadId blinkTID;

14

Запуск FreeRTOS

615

15HAL_Init();

16Nucleo_BSP_Init();

18osThreadDef(blink, blinkThread, osPriorityNormal, 0, 100);

19blinkTID = osThreadCreate(osThread(blink), NULL);

21

osKernelStart();

22

 

23/* Бесконечный цикл */

24while (1);

25}

26

27void blinkThread(void const *argument) {

28while(1) {

29HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);

30osDelay(500);

31}

32osThreadTerminate(NULL);

33}

34

35void SysTick_Handler(void) {

36HAL_IncTick();

37HAL_SYSTICK_IRQHandler();

38osSystickHandler();

39}

Строки [18:19] определяют и создают новый поток, присваивая ему имя "blink" и передавая указатель на функцию blinkThread(), которая будет представлять собой наш поток. Затем потоку присваивается нормальный приоритет (подробнее о нем в ближайшее время). Четвертый параметр относится к числу максимальных экземпляров, которые может иметь поток, но FreeRTOS его не использует, поэтому в данном случае он не имеет смысла. Наконец, последний параметр определяет размер стека.

API-интерфейс CMSIS-RTOS выражает размер стека потока в байтах, и вы найдете эту информацию в уровне CMSIS-RTOS, разработанном ST поверх FreeRTOS. Тем не менее, FreeRTOS определяет размер стека кратным размеру слова, которое в процессоре Cortex-M является 32-разрядным и, следовательно, размером 4 Байт. Это означает, что значение, которое мы передаем макросу osThreadDef(), умножается на четыре внутри FreeRTOS. Это говорит о действующей переносимости этих уровней абстракции.

Затем osThreadCreate() эффективно создает новый поток и просит ядро запланировать его выполнение, возвращая идентификатор потока (Thread ID, TID): он используется другими API-интерфейсами для управления состоянием потока и его конфигурацией. Обратите внимание, что, как только поток определен с помощью макроса osThreadDef(), мы используем макрос osThread(), чтобы сослаться на этот поток в другой части кода. Второй параметр функции, osThreadCreate(), является необязательным параметром для передачи в поток. Наконец, мы запускаем планировщик ядра с помощью функции osKernelStart(), которая никогда не возвращается, если только что-то пойдет не так.

Запуск FreeRTOS

616

Функция blinkThread() является не более, чем вездесущим мигающим приложением. Единственным заметным отличием является использование функции osDelay() вместо классической HAL_Delay(): osDelay() спроектирована таким образом, что поток будет оставаться в заблокированном состоянии в течение 500 мс без влияния на производительность ЦПУ. По истечении этого времени поток будет возобновлен, а светодиод LD2 снова будет переключаться. Подробнее о функции osDelay() мы поговорим позже.

Наконец, обратите внимание, что, поскольку мы используем здесь SysTick в качестве временного отсчета для ядра FreeRTOS, нам нужно добавить вызов функции osSystickHandler() внутри обработчика исключения таймера и сконфигурировать таймер для генерации тика каждые 1 мс (это выполняется в процедуре SystemClock_Config(), как было показано в Главе 10).

23.3.1. Состояния потоков

Во FreeRTOS поток может быть в двух основных состояниях выполнения: «выполняется» (running) и «не выполняется» (not running). В одноядерной архитектуре только один поток может находиться в состоянии «выполняется».

Во FreeRTOS состояние «не выполняется» характеризуется несколькими подсостояниями, как показано на рисунке 10. Не выполняющийся поток может быть «готов к выполнению», англ. ready (это также состояние новых потоков), то есть он готов быть спланированным к выполнению ядром ОСРВ.

Рисунок 10: Возможные состояния потока во FreeRTOS

Выполняющийся поток может добровольно приостановить свое выполнение, вызвав функцию osThreadSuspend(), которая принимает TID приостанавливаемого потока или NULL, если она вызывается тем же потоком. В этом случае поток принимает состояние

«приостановлен» (suspended)21. Для возобновления приостановленного потока использу-

ется osThreadResume().

Выполняющийся поток может перевести себя в состояние «заблокирован» (blocked), начав тем самым ожидание «внешнего» события. Это событие может быть, например, примитивом синхронизации (например, семафором), который будет разблокирован из другого потока. Другим источником состояния «заблокирован» является функция osDelay(), которая переводит поток в состояние «заблокирован», пока не истечет указанное время

21 Во FreeRTOS это состояние называется «остановлен» (stopped), как показано на рисунке 10.