STM32 с нуля. FreeRTOS. Кооперативная многозадачность.

Продолжаем работу с FreeRTOS, и в этой статье мы закончим обсуждение программы-примера, созданной ранее (тут). Там же можно найти теоретические сведения о разных типах многозадачности.

У нас получилось запустить наш код под управлением FreeRTOS, и вроде бы он даже работал так, как и задумывалось. Но сейчас мы убедимся, что это не совсем так )

Поправим программу – сделаем так, чтобы во время выполнения задачи 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’а. А там какая то лажа:
Использование FreeRTOS
Почему же так получилось?

А все просто. При вытесняющей многозадачности планировщик вызывается каждый квант времени, то есть это может произойти в любом месте задачи. Задача 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’а:
Работа с USART и FreeRTOS
Данные высылаются во внешний мир сплошным потоком, без потери информации, как в случае вытесняющей многозадачности. Поэтому эксперимент с многозадачностью кооперативной считаем удавшимся =) Давайте посмотрим, как разделяется процессорное время между задачами. Для этого, если помните, у нас дергается ножка PB0.
Использование FreeRTOS, кооперативная многозадачность
Задача vADCTask() получает управление на крошечные промежутки времени (по сравнению с vUSARTTask() ). Что, в целом, очень логично ) Аналого-цифровое преобразование выполняется намного быстрее, чем передача двух байт в USART.

Статья получилась кратенькая, но мы успели исправить ошибки предыдущей программы и при этом познакомиться с кооперативной многозадачностью в FreeRTOS. Надеюсь, получилось не только кратко, но и полезно =)

Понравилась статья? Поделись с друзьями!

STM32 с нуля. FreeRTOS. Кооперативная многозадачность.: 9 комментариев
    • Ну там просто переключение между задачами в строго определенное время происходит, через равные промежутки, а у нас одна задача выполняется намного дольше, чем вторая

      • я так понимаю, отталкиваемся от задач проекта – разве такие моменты, типа, какую многозадачность выбрать – не учитываются сразу во время планирования проекта?

        • Ну иногда очевидно, какой тип лучше подойдет, иногда нет. Везде есть как плюсы, так и свои минусы

  1. А если применить механизмы межзадачного взаимодействия, к примеру мютексы или бинарные семафоры, то всё можно разрулить и в вытесняющем режиме ядра FreeRTOSы.

  2. На самом деле и при кооперативной многозадачности все будет работать. Проблема в исходном примере в том, что vADCTask кладет в очередь данные намного быстрее, чем vUSARTTask их оттуда забирает. У исходного примера изначально некорректный способ решения задачи: прочитал из АЦП – отправил в USART. Нужно было отталкиваться либо от периодичности чтения АЦП, либо семафорами разруливать переполнение очереди.

  3. День добрый. Вся эта тема с FreeRTOS выглядит дико сложно. В общем у меня ряд вопросов. Использую конечно же Cube, процессор f103.
    1. Попробовал собрать проект, пишет что мол SysTick ему как источник прерывания для ОС не подходит. Надо мол другой таймер. А в чем дело? Я читал, что SysTick как раз по идее и нужен для реализации операционных систем. Почему тогда куб на него ругается?
    2. В STMках есть отличный механизм настройки приоритетов и подприоритетов, одна задача спокойно прерывает другую, не нарушая ее работу. У ОС своя аналогичная более навороченная, как я понял, система. Как в FreeRTOS учитывается, что у микроконтроллера есть свои приоритеты и своя примочка, их разруливающая. Не будут ли эти два механизма друг другу мешать?
    Заранее спасибо!

  4. Ладно. С первым вроде разобрался. SysTick ОС и использует. Только тогда другой вопрос. Зачем вообще нужно это прерывание Timebase Source. Оно вообще где и зачем используется и можно ли его отрубить за ненадобностью?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *