Продолжаем работу с FreeRTOS, и в этой статье мы закончим обсуждение программы-примера, созданной ранее. Там же можно найти теоретические сведения о разных типах многозадачности. Итак, у нас получилось запустить наш код под управлением FreeRTOS, и вроде бы он даже работал так, как и задумывалось. Но сейчас мы убедимся, что это не совсем так...
Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32, а также статья на смежную тему из нового курса: STM32CubeMx. Быстрый старт с FreeRTOS для STM32.
Поправим программу – сделаем так, чтобы во время выполнения задачи vADCTask на определенной ножке выставлялась логическая единица, а при выполнении задачи vUSARTTask – на выходе был 0. Для этих целей выберем вывод PB0. Настраиваем:
port.GPIO_Mode = GPIO_Mode_Out_PP; port.GPIO_Pin = GPIO_Pin_0; port.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOB, &port);
Не забываем включить тактирование:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
Немного подкорректируем код наших функций-задач:
/***************************************************************************************/ void vADCTask (void *pvParameters) { while(1) { GPIO_SetBits(GPIOB, GPIO_Pin_0); data = ADC_GetConversionValue(ADC1); if (data > 0x9B2) { sendData[0] = 'O'; sendData[1] = 'K'; } else { sendData[0] = 'N'; sendData[1] = 'O'; } xQueueSend(xDataQueue, &sendData[0], 0); } vTaskDelete(NULL); } /***************************************************************************************/ void vUSARTTask(void *pvParameters) { while(1) { xQueueReceive(xDataQueue, &usartData[0], 0); usartCounter = 0; while(usartCounter < 2) { USART_SendData(USART1, usartData[usartCounter]); GPIO_ResetBits(GPIOB, GPIO_Pin_0); while(!USART_GetFlagStatus(USART1, USART_FLAG_TC)); usartCounter++; } } vTaskDelete(NULL); } /***************************************************************************************/
Здесь мы вставили пару строк для работы с выводом PB0. Заметьте, что в задаче vUSARTTask – строчка GPIO_ResetBits(GPIOB, GPIO_Pin_0) находится не в начале программы, а в середине. Связано это с тем, что большая часть работы в этой задаче осуществляется именно в цикле и чаще всего при передаче управления задаче vUSARTTask она продолжает выполнение из цикла while(usartCounter < 2).
Запускаем отладчик, настраиваем логический анализатор (кто не в курсе как – смотрим тут). Но пока сконцентрируем внимание на окошке USART’а. А там такое:
Почему же так получилось?
А все просто. При вытесняющей многозадачности планировщик вызывается каждый квант времени, то есть это может произойти в любом месте задачи. Задача vUSARTTask выполняется намного дольше задачи vADCTask и может прерываться планировщиком во время передачи байта в USART или, например, когда один байт передан, а второй еще нет. И в итоге мы получаем то, что получаем, то есть абсолютно неверный результат работы программы. Прежде чем исправлять это безобразие, давайте посмотрим, что там у нас на выводе PB0. Не зря же мы его дергали )
Каждая задача выполняется 1 мс, а затем управление получает другая. Вспоминаем, что по дефолту квант времени равен именно 1 миллисекунде, то есть все верно. Тем не менее, в данном примере применение вытесняющей многозадачности неоправданно и приводит к ошибкам, так что будем переходить к кооперативной многозадачности. Что это такое и как работает, мы уже обсуждали в предыдущей статье, переходим сразу к делу.
Открываем файл freertosconfig.h и находим строку:
define configUSE_PREEMPTION 1
Значение 1 соответствует вытесняющей многозадачности, так как нам нужна кооперативная многозадачность, ставим 0:
define configUSE_PREEMPTION 0
Готово, теперь надо поправить код. Мы должны вручную вызывать планировщик задач, поэтому в конце каждой задачи (но в пределах цикла while(1) ) вызываем функцию taskYIELD(). Вот как будет выглядеть задача vUSARTTask():
void vUSARTTask(void *pvParameters) { while(1) { xQueueReceive(xDataQueue, &usartData[0], 0); usartCounter = 0; while(usartCounter < 2) { USART_SendData(USART1, usartData[usartCounter]); GPIO_ResetBits(GPIOB, GPIO_Pin_0); while(!USART_GetFlagStatus(USART1, USART_FLAG_TC)); usartCounter++; } taskYIELD(); } vTaskDelete(NULL); }
Давайте попробуем, что из этого получилось. Компилируем, идем в отладчик и запускаем программу (F5). Смотрим в окошко USART’а:
Данные высылаются во внешний мир сплошным потоком, без потери информации, как в случае вытесняющей многозадачности. Поэтому эксперимент с многозадачностью кооперативной считаем удавшимся ) Давайте посмотрим, как разделяется процессорное время между задачами. Для этого, если помните, у нас дергается ножка PB0.
Задача vADCTask() получает управление на крошечные промежутки времени (по сравнению с vUSARTTask() ). Что, в целом, очень логично. Аналого-цифровое преобразование выполняется намного быстрее, чем передача данных в USART.
Статья получилась кратенькая, но мы успели исправить ошибки предыдущей программы и при этом познакомиться с кооперативной многозадачностью в FreeRTOS. Надеюсь, получилось не только кратко, но и полезно )
угу, полезно. Только не въехала, почему в этом примере вытесняющая многозадачность не оправданна? и почему приводит к ошибкам?
Ну там просто переключение между задачами в строго определенное время происходит, через равные промежутки, а у нас одна задача выполняется намного дольше, чем вторая
я так понимаю, отталкиваемся от задач проекта - разве такие моменты, типа, какую многозадачность выбрать - не учитываются сразу во время планирования проекта?
Ну иногда очевидно, какой тип лучше подойдет, иногда нет. Везде есть как плюсы, так и свои минусы
А если применить механизмы межзадачного взаимодействия, к примеру мютексы или бинарные семафоры, то всё можно разрулить и в вытесняющем режиме ядра FreeRTOSы.
На самом деле и при кооперативной многозадачности все будет работать. Проблема в исходном примере в том, что vADCTask кладет в очередь данные намного быстрее, чем vUSARTTask их оттуда забирает. У исходного примера изначально некорректный способ решения задачи: прочитал из АЦП - отправил в USART. Нужно было отталкиваться либо от периодичности чтения АЦП, либо семафорами разруливать переполнение очереди.
День добрый. Вся эта тема с FreeRTOS выглядит дико сложно. В общем у меня ряд вопросов. Использую конечно же Cube, процессор f103.
1. Попробовал собрать проект, пишет что мол SysTick ему как источник прерывания для ОС не подходит. Надо мол другой таймер. А в чем дело? Я читал, что SysTick как раз по идее и нужен для реализации операционных систем. Почему тогда куб на него ругается?
2. В STMках есть отличный механизм настройки приоритетов и подприоритетов, одна задача спокойно прерывает другую, не нарушая ее работу. У ОС своя аналогичная более навороченная, как я понял, система. Как в FreeRTOS учитывается, что у микроконтроллера есть свои приоритеты и своя примочка, их разруливающая. Не будут ли эти два механизма друг другу мешать?
Заранее спасибо!
Не должно мешать.
Ладно. С первым вроде разобрался. SysTick ОС и использует. Только тогда другой вопрос. Зачем вообще нужно это прерывание Timebase Source. Оно вообще где и зачем используется и можно ли его отрубить за ненадобностью?