Продолжаем работу с 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. Надеюсь, получилось не только кратко, но и полезно )