До сих пор мы создавали задачи и не думали о том, что всем задачам, которыми управляет наш планировщик, при создании назначается определённый приоритет и обычно мы создавали несколько задач и управляли ими, используя при их создании какой-либо один приоритет для всех задач. Сегодня мы попытаемся назначить задачам приоритет разный и посмотреть, как это повлияет на их работу и взаимодействие между собой. Также полезно отметить, что приоритет задачам может назначаться не только при их создании, но и во время исполнения программы, то есть в любой момент. Также в одном из ранних уроков я говорил, что задачи в конкретный момент времени могут находиться в одном из четырёх состояний, причём в состоянии «Выполняется» может находиться только одна задача. Также если в состоянии «Готова к выполнению» находятся несколько задач с разным приоритетом, то в первую очередь будут выполняться те задачи, у которых приоритет больший. Если с наибольшим приоритетом в данном состоянии будут находиться сразу несколько задач, то по окончании выполнения какой-то задачи в состояние «Выполняется» из задач с одинаковым наибольшим приоритетом перейдёт задача, которая в состояние «Готова к выполнению» перешла первой из них.
Думаю, что рисовать диаграммы мы сегодня не будем, их в сети превеликое множество, а лучше посмотрим, как будет это выглядеть на практике.
Проект мы создадим на основе проекта урока 108 PARAM01 и назовём его TASK_PRIOPITIES.
Откроем наш проект в проектогенераторе Cube MX, и, вообще ничего не трогая сгенерируем проект для SystemWorkbench, откроем его там, настроим оптимизацию 1 и уберём отладочную конфигурацию, если таковая будет иметь место в проекте.
Откроем файл main.c, закомментируем строки с ошибками в инициализации DMA2D, соберём проект, если всё нормально собралось, то продолжаем дальше нашу работу над проектом.
Первым делом подправим шапочку в функции main(), чтобы заголовок был актуальным
TFT_DisplayString(0, 10, (uint8_t *)"Tasks priorities", CENTER_MODE);
В функции задач Task01 немного упростим вывод информации на дисплей, чтобы она выводилась за один приём, а также удалим оттуда задержку, чтобы задачи наши никогда не уходили в блокированное состояние, иначе мы не увидим разницу приоритетов. В бесконечном цикле функции задач у нас останется только вот что
for(;;)
{
sprintf(str1,"%lu %s %d ", osKernelSysTick(), arg->str_name, osThreadGetPriority(NULL));
TFT_DisplayString(120, arg->y_pos, (uint8_t *)str1, LEFT_MODE);
}
То есть то, что мы убрали задержку, обеспечит нам то, что одна задача будет выполняться в течение одного системного кванта, а остальные задачи будут «толпиться» в состоянии «Готова к выполнению». Также мы выводим на дисплей значение приоритета задачи.
Так как пока что у нас все задачи при создании получили все одинаковый приоритет osPriorityIdle или -3, что означает самый минимально-возможный приоритет, то нам сейчас интересно будет посмотреть, как у нас будет работать проект. Соберём проект, прошьём контроллер и посмотрим результат выполнения кода на дисплее нашей платы
Мы видим на экране дисплея, что сейчас у нас выполняются все три наши задачи. Конечно не совсем это происходит корректно, иногда путаются строки, вернее их значения (в момент съёмки кадра мы видим, что в третьей строке у нас написано task2), так как функция вывода строки на дисплей, у нас занимает немалое количество кода, поэтому, скорее всего и немалое количество времени, и поэтому данная функция может разрываться при переключении задач, хотя по определению все локальные переменные, находящиеся в функции, у нас должны сохранятся в стек, всё равно сильной корректности тут не будет. Я пробовал увеличивать стеки задач, это ни к чему не привело. Победить данный трабл мне позволило использование отдельной задачи для вызова функции вывода строки. Но для этого нам как-то потребуется туда эту строку передавать из других задач. На это у нас существуют очереди, поэтому это тема одного из следующих уроков. А пока будем довольствоваться таким выполнением задач. Нам нужно научиться переключать приоритеты во время выполнения кода и оценить это визуально. Этим мы и займёмся.
Давайте третьей задаче при создании назначим приоритет чуть выше, чем остальным
osThreadDef(tsk03, Task01, osPriorityLow, 0, 128);
Соберём проект, прошьём контроллер и увидим что у нас теперь будет выполняться только эта задача, так как после выполнения в течение одного системного кванта данная задача переходит сразу в состояние «Готова к выполнению» и тут же остальные задачи, находящиеся там, вынуждены её пропустить без очереди опять в состояние «Выполняется», так как они имеют более низкий приоритет
Поэтому у нас создалась такая ситуация, что выполняться будет только одна задача, а остальные две вообще выполняться никогда не будут. Поэтому, чтобы такой ситуации не происходило, нужно тщательно продумывать свой код и обдумывать приоритеты задач, а если они разные, то нужно обеспечить то, чтобы задачи, имеющие больший приоритет, иногда уходили в состояние блокировки или приостановке, позволяя выполняться задачам, имеющим более низкий приоритет, а иногда полезно переключать приоритеты во время выполнения программы. Теперь мы как раз этим с вами и займёмся.
Мы будем менять приоритеты через некоторое время. Поэтому в функции задачи по умолчанию StartDefaultTask, которая имеет больший приоритет, чем все задачи, мы сначала добавим локальную переменную
/* USER CODE BEGIN 5 */
uint32_t syscnt;
А затем добавим следующий код в её бесконечный цикл
for(;;)
{
syscnt = osKernelSysTick();
if((syscnt>10000)&&(syscnt<20000))
{
if(osThreadGetPriority(Task01Handle)==osPriorityIdle)
osThreadSetPriority(Task01Handle,osPriorityLow);
}
else if((syscnt>20000)&&(syscnt<30000))
{
if(osThreadGetPriority(Task02Handle)==osPriorityIdle)
osThreadSetPriority(Task02Handle,osPriorityLow);
}
else if((syscnt>30000)&&(syscnt<40000))
{
if(osThreadGetPriority(Task03Handle)==osPriorityLow)
osThreadSetPriority(Task03Handle,osPriorityIdle);
}
else if((syscnt>40000)&&(syscnt<50000))
{
if(osThreadGetPriority(Task02Handle)==osPriorityLow)
osThreadSetPriority(Task02Handle,osPriorityIdle);
}
else if((syscnt>50000)&&(syscnt<60000))
{
if(osThreadGetPriority(Task01Handle)==osPriorityLow)
osThreadSetPriority(Task01Handle,osPriorityIdle);
}
osDelay(1);
По данному коду мы видим, что как только у нас пройдёт время более 10000 системных квантов, что в нашем случае составит приблизительно 10 секунд, то первая задача у нас также получит приоритет osPriorityLow, как и третья, и они уже будут выполняться только вдвоём, а вторая задача остаётся с меньшим приоритетом и выполняться не будет. Мы проверяем сначала у задачи, приоритет которой мы переключаем, на то какой у неё приоритет уже существует в данный момент, с помощью функции osThreadGetPriority. Делается это для того, чтобы постоянно не вызывать в каждом цикле переключение задачи в течении следующих 10 секунд. А переключение приоритета достигается с помощью функции osThreadSetPriority, входными аргументами которой являются хендл задачи и приоритет, который с помощью данной функции получит задача.
Затем, когда у нас с момента старта контроллера у нас пройдёт уже 20000 системных квантов мы приоритет увеличим у второй задачи. После этого у нас задачи будут выполняться одновременно все, так как у них будет приоритет одинаковый.
Потом мы по истечении 30000 системных квантов после старта контроллера уменьшим приоритет третьей задачи. Поэтому после этого она должна будет перестать выполняться.
Далее на отметке 40000 системных квантов мы уменьшим приоритет второй задачи, в результате чего она также перестанет выполняться, а выполняться у нас будет только первая задача.
Затем на отметке 50000 системных квантов мы уменьшим приоритет и первой задачи. После этого наши задачи опять будут все выполняться, так как у них у всех будет один и тот же приоритет.
Соберём код, прошьём контроллер и убедимся во всём этом на практике
Мы видим, что всё у нас работает, как и ожидалось.
Итак, сегодня мы научились управлять приоритетами задач. Причём приоритеты задачам назначать мы умеем теперь не только при создании задачи, но и во время выполнения программы. Также мы визуально оценили разницу работы задач с различными приоритетами.
Всем спасибо за внимание!
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь STM32F746G-DISCOVERY
Смотреть ВИДЕОУРОК (нажмите на картинку)
Спасибо, интересный материал
Пушка конечно инфа. Сейчас в трендах есп32. Будет ли с ней цикл уроков?
Здравствуйте!
Это хорошо, что в тренде, только мы пока к нему не совсем готовы. По комментариям и личной переписке пока обнаружились пробелы в знании языка. Вот немного подтянем, и возможно что будет. По ESP8266 точно будет. Я прорабатываю и тот и другой, пока заметил, что сильно написание кода на них друг от друга не отличается. Так что те, кто захотят перейти на 32-е, то без проблем, изучив тех.доки, это сделают.