STM Урок 116. FreeRTOS. Прерывания. Очереди в прерываниях. Часть 1



До сих пор мы вызывали различные функции, связанные с системой 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 Следующая часть

 

 

Отладочную плату можно приобрести здесь 32F746G-DISCOVERY

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM FreeRTOS. Прерывания. Очереди в прерываниях