До сих пор мы вызывали различные функции, связанные с системой FreeRTOS, в функциях и процедурах, не связанных с обработчиками прерываний.
Но возникает логичный вопрос. А как же всё-таки использовать различный функционал операционной системы FreeRTOS именно в телах обработчиков прерываний, чтобы обработка прерываний оставалась корректной, то есть чтобы она не продолжалась слишком долго и чтобы различные данные и реакции на события прерываний нормально передавались в различные задачи, причём и с различными приоритетами. Оказывается, в системе FreeRTOS это всё не так тривиально, как хотелось бы и механизм работы с API операционной системы в обработчиках прерываний очень сильно отличается от аналогичного механизма в участках кода, не связанными с функциями-обработчиками прерываний.
Но спешу вас обрадовать, что все эти трудности мы легко обходим за счёт использования функций порта для контроллеров STM.
Также все мы прекрасно понимаем, что код в обработчиках прерываний должен быть минимальным, поэтому мы будем обработчик использовать лишь только для того, чтобы получить данные от определённого события, а дальше уже посредством очереди мы их передадим в какую-либо задачу, чтобы освободить ресурсы для обработки следующих прерываний, иначе мы рискуем какие-либо события либо обработать слишком поздно в лучшем случае, а в худшем вообще пропустить.
Механизм обработки прерываний в системе FreeRTOS с помощью функций стандартной библиотеки данной операционной системы описан в официальной документации. Поэтому мы его подробно касаться не будем, да нам это и не очень нужно, так как заботиться об этом отличии будут наши библиотеки, которые подключатся с помощью проектогенератора. Мы познакомимся только лишь с основными критериями обработки прерываний.
Основное отличие функций библиотеки FreeRTOS, используемых именно в обработчиках прерываний — это наличие у их имён префиксов FromISR или From_ISR. Также в функциях библиотеки FreeRTOS, вызываемых из обработчиков прерываний есть ещё один параметр pxHigherPriorityTaskWoken, передающейся в виде указателя типа BaseType_t. Значение по данному адресу устанавливает сама API-функция. Данное значение несёт в себе информацию о том, разблокирована ли более высокоприоритетная задача. Перед вызовом функции мы по данному адресу прописываем значение pdFALSE. Если в качестве функции будет, например, выдача семафора из функции-обработчика прерывания, то в случае, если после вызова функции мы получим по данному адресу значение pdTRUE, что значит то, что более высокоприоритетная задача заблокирована, то мы принудительно передаём ей контекст с помощью вызова функции-макроса portSWITCH_CONTEXT(). На разных портах и платформах имя данного макроса может различаться.
Но, как я и сказал выше, данная информация нам при написании нашего кода не пригодится, так как мы будем использовать другие, более, скажем так, высокоуровневые функции.
Поэтому давайте попробуем что-либо написать по поводу работы с обработчиками прерываний.
Как видно из названия темы урока, мы будем использовать очереди, поэтому проект мы создадим на основе проекта урока 111 TASKS_QUEUES и назовём мы его USART_ISR_QUEUE. И, как я думаю, вы все догадались, что в качестве источника событий для прерываний мы будем использовать именно USART. Думаю, что это будет очень интересная тема, так как данная шина используется очень часто и нам просто необходимо знать, как принимать из неё данные именно с использованием операционной системы FreeRTOS.
Откроем наш проект в Cube MX, перейдём в раздел Configuration и в настройках USART включим прерывания
Применим настройки, сгенерируем проект и откроем его в System Workbench.
Настроим уровень оптимизации в 1, удалим отладочные настройки, а также закомментируем неизвестные среде строки в инициализации видеоускорителя DMA2D, откроем файл main.c и попробуем собрать проект.
Если всё нормально, то продолжим.
В функции задачи по умолчанию удалим тело бесконечного цикла, оставив там только небольшую задержку
for(;;)
{
osDelay(1);
}
Функцию для задач Task01 мы переименуем. Теперь это будет совсем другая задача и выполнять она будет совсем другую миссию
void TaskParseUSART(void const * argument)
Тело данной функции пока полностью очистим.
Исправим также прототип данной функции.
Удалим также хендлы трёх задач, а добавим один хендл
osThreadId TaskStringOutHandle, TaskParseUSARTHandle;
Удалим структуру параметров
typedef struct struct_arg_t {
char str_name[10];
uint16_t y_pos;
uint32_t delay_per;
} struct_arg;
Также удалим объявление параметров
struct_arg arg01, arg02, arg03;
Идём в функцию main() и исправим шапку дисплея
TFT_DisplayString(0, 10, (uint8_t *)"ISR USART QUEUE", CENTER_MODE);
Вывод задач удалим
TFT_SetTextColor(LCD_COLOR_MAGENTA);
TFT_DisplayString(14, 60, (uint8_t *)"Task1:", LEFT_MODE);
TFT_DisplayString(14, 110, (uint8_t *)"Task2:", LEFT_MODE);
TFT_DisplayString(14, 160, (uint8_t *)"Task3:", LEFT_MODE);
Удалим инициализацию параметров
/* add threads, ... */
strcpy(arg01.str_name,"task1");
strcpy(arg02.str_name,"task2");
strcpy(arg03.str_name,"task3");
arg01.y_pos = 60;
arg02.y_pos = 110;
arg03.y_pos = 160;
arg01.delay_per = 1000;
arg02.delay_per = 677;
arg03.delay_per = 439;
Удалим создание трёх задач и создадим одну
osThreadDef(tsk01, Task01, osPriorityIdle, 0, 128);
Task01Handle = osThreadCreate(osThread(tsk01), (void*)&arg01);
osThreadDef(tsk02, Task01, osPriorityIdle, 0, 128);
Task02Handle = osThreadCreate(osThread(tsk02), (void*)&arg02);
osThreadDef(tsk03, Task01, osPriorityLow, 0, 128);
Task03Handle = osThreadCreate(osThread(tsk03), (void*)&arg03);
osThreadDef(tskparseusart, TaskParseUSART, osPriorityBelowNormal, 0, 512);
TaskParseUSARTHandle = osThreadCreate(osThread(tskparseusart), NULL);
Добавим функцию-обработчик прерываний от USART после функции TaskParseUSART
//---------------------------------------------------------------
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
}
//---------------------------------------------------------------
Нам необходимо будет создать ещё одну очередь для передачи принятого из USART байта для передачи его из обработчика прерываний в задачу-обработчик. Задача-обработчик уже, как мы понимаем, будет выполняться не в теле функции-обработчика, тем самым мы организовываем отложенную обработку прерываний. То есть мы весь рутинный механизм разспознавания и обработки данного байта, а также слияния всех байтов поручаем задаче. Тем самым мы позволяем в любой момент, если даже мы ещё не выполнили весь код по работе с байтами из USART, обрабатывать нашему контроллеру другие прерывания.
Создадим сначала идентификатор очереди
osMailQId strout_Queue;
osMessageQId USART_Queue;
Ещё создадим глобальный буфер для байта, принятого из USART
char str1[60];
char buf1[1];
Напишем макрос для размера очереди
#define MAIL_SIZE (uint32_t) 1
#define QUEUE_SIZE (uint32_t) 10
Размер 10 был выбран экспериментально, При нашей скорости вполне справляется.
В функции main() создадим очередь
strout_Queue = osMailCreate(osMailQ(stroutqueue), NULL);
osMessageQDef(usart_Queue, QUEUE_SIZE, uint8_t);
USART_Queue = osMessageCreate(osMessageQ(usart_Queue), NULL);
В теле функции задачи по умолчанию StartDefaultTask запустим приём данных из USART
HAL_UART_Transmit(&huart1,(uint8_t*)"\r\n",2,0x1000);
HAL_UART_Receive_IT(&huart1, (uint8_t*)buf1,1);
Удалим локальную переменную для хранения системных тиков
/* USER CODE BEGIN 5 */
uint32_t syscnt;
Бесконечный цикл в данной функции очистим от кода, оставив там только задержку
for(;;)
{
osDelay(1);
}
В функции-обработчике USART примем байт и передадим его в очередь
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart==&huart1)
{
osMessagePut(USART_Queue, buf1[0], 100);
HAL_UART_Receive_IT(&huart1, (uint8_t*)buf1,1);
}
}
В следующей части нашего занятия мы напишем функцию парсинга строки, на пишем скрипт для терминальной программы в целях автоматизации передачи данных через USART и проверим наш клиент на практике.
Предыдущий урок Программирование МК STM32 Следующая часть
Отладочную плату можно приобрести здесь STM32F746G-DISCOVERY
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)