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

Поделиться!

Подписаться
Уведомление о
guest
9 Комментарий
старее
новее большинство голосов
Inline Feedbacks
View all comments
Алиса Алексеева
7 лет назад

угу, полезно. Только не въехала, почему в этом примере вытесняющая многозадачность не оправданна? и почему приводит к ошибкам?

Алиса Алексеева
Reply to  Aveal
7 лет назад

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

Volldemar
Volldemar
7 лет назад

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

Konstantin
Konstantin
6 лет назад

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

Dimaster
Dimaster
3 лет назад

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

Dimaster
Dimaster
3 лет назад

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

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Август 2020
Пн Вт Ср Чт Пт Сб Вс
 12
3456789
10111213141516
17181920212223
24252627282930
31  

© 2013-2020 MicroTechnics.ru