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
Пн Вт Ср Чт Пт Сб Вс
« Июн    
 12345
6789101112
13141516171819
20212223242526
2728293031  

© 2013-2020 MicroTechnics.ru