Есть в FreeRTOS встроенная функция vTaskDelay которая на N тиков системного таймера отдает управление другим задачам. В результате можно делать тупые циклы с ожиданием чего-либо и не париться по поводу процессорного времени. Очень удобно. Но есть проблема, минимальное время которая эта задержка может организовать составляет 1 тик системного таймера. Обычно это около 1 миллисекунда. Но иногда требуются задержки меньше. Да, можно повысить скорость тиков системного таймера. Даже в 10 или 100 раз, при 72 Мегагерцах какого-нибудь STM32 это вполне себе работает. Правда на переключение контекста будет уходить больше процессорного времени. Впрочем, всегда можно работать в кооперативном режиме, а не вытесняющем. Тут в принципе нет вытеснения, а управление передаешь вручную через функцию taskYIELD или любую другую с ожиданием. Те же Delay, Очереди, Семафоры и мало ли что еще.
Но все равно — решение так себе, особенно если ради какой-нибудь небольшой задачи приходится разгонять процессы во всей операционке. Значит надо сделать свой маленький таймер, который сам по себе будет тикать в прерывании и обеспечит нам работу всех этих выдержек. Что я и сделал.
Таймер взял самый бомжовскйи. На STM32F103C8T6 нет, к сожалению, Basic Timers ТIM6 и ТIM7 — это самые простые, самые примитивные считалки. В них нет ни завата, ни регистров сравнения для ШИМ и их не жалко отдать под такое дело, но они есть либо в самых жирных, либо в самых нищих вариация серии F10x. В моей нету. Ну окей, возьмем другой таймер. Общего назначения. Я взял Timer 2.
Настраиваются таймеры элеменатрно, тут не нужны даже никакие библиотеки. Главное понять откуда берется тактирование, какая величина и что надо включить. Смотрим в RM0008 структуру тактирования таймера 2. Раздел 7.2 Clocks
У меня в системе предделители обычно настроены на максимальную частоту и на этой шине 36 мегагерц. Тактирование нашего дополнительного таймера я хочу видеть с частотой 10 килогерц. Та что делим 36 мегагерц на 36, а потом еще на 100. И получим искомое.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Подаем тактирование на таймер от шины APB1 TIM2->PSC = 35; // Частота этой шины 36 мегагерц. Так что в предделитель записываем 36-1, получим 1МГц TIM2->ARR = 99; // Потолком счета таймера укажем 100-1. Получим деление на 100 в частоте вызова прерываний. TIM2->CR1 |= TIM_CR1_ARPE | TIM_CR1_URS | TIM_CR1_CEN; // ARPE=1 - буфферизируем регистр предзагрузки таймера опциональная вещь. // URS=1 - разрешаем из событий таймера только события от переполнения // CEN=1 - запускаем таймер TIM2->DIER |= TIM_DIER_UIE; // UIE=1 - Разрешаем прерывание от переполнения NVIC_SetPriority(TIM2_IRQn,14); // Очень важный момент!!! Надо правильно выставить приоритеты. Они должны быть в диапазоне между // configKERNEL_INTERRUPT_PRIORITY и configMAX_SYSCALL_INTERRUPT_PRIORITY NVIC_EnableIRQ(TIM2_IRQn); // Разрешаем прерывания от таймера 2 через NVIC |
При настройке прерываний важно учесть один тонкий момент. Дело в том, что у нас будут использоваться API функции RTOS вида ***FromISR. А если такие функции выполняются из обработчика прерываний, то приоритет этого обработчика должен быть не выше максимального приоритета RTOS. То есть, в конфиге RTOS есть такие строчки:
1 2 3 4 5 6 7 8 9 10 | /* This is the raw value as per the Cortex-M3 NVIC. Values can be 255 (lowest) to 0 (1?) (highest). */ #define configKERNEL_INTERRUPT_PRIORITY 255 /* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ #define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 /* equivalent to 0xb0, or priority 11. */ |
Что такое 255 и 191? Это код приоритета системы NVIC. Про работу NVIC я уже писал ранее. У STM32 приоритет прерывания задается старшим ниблом этого байта. Причем 0 это высший приоритет, а F низший. Т.е. 255 = 0xFF или F_ если закрыть младший нибл, или 15 в десятичной системе — низший приоритет у прерывания ядра. 191 = 0xBF, т.е. приоритет системных вызовов SYSCALL равен B_ или 11. Выше на 4 ступеньки.
В градациях нотации функции группы NVIC из CMSIS это от 0 до 15. Где 0 высший, а 15 низший. И если обработчик прерывания использует функции ***FromISR, то его приоритет должен быть между KERNEL_INTERRUPT_PRIORITY и MAX_SYSCALL_INTERRUPT_PRIORITY. Поэтому я взял и сделал его 14.
Что будет если оставить дефолтный приоритет в 0, т.е. наивысший? А будет весело! У вас будет рандомно при вызове API функций вызываться Hard Fault Handler. Причем не в момент их вызова из прерывания, там то обычно все на ура проходит. А где то в недрах диспетчера. При обработке этих очередей, таймеров, мутексов и прочего, что вы там из обработчика запнули. Т.е. система будет хаотично вешаться. Причем хаотично это не на раз два, а скажем, на 100 000 вызов прерывания. Или на 1000, или на 100500й. В общем, далеко не сразу ее скрючит. Но гарантированно.
Я три дня убил на то, чтобы понять какого хрена у меня вылезает хардфаулт. Перетряхнул очереди, стек, вызовы. Докопался до того, что ранее проверенная функция проверки семафора делает такое западло. Причем не всегда. А только под фазу луны. Оттрасировал почти все ядро FreeRTOS. Заменил красивую систему на семафорах убогим поделием из миллиона костылей и десятка структур, чтобы убрать из прерывания апи вызовы, ну чисто поприколу, локализовать проблему и… помогло. Стал детально копать и только потом заметил, что выставил приоритет не тому прерыванию. Просто опечатка, вместо таймера 2 поставил на таймер 3. Еще ведь проверил пару раз и в шары продолбился два раза же.
Инициализация готова. Теперь надо добавить обработчик прерывания. А также разрешить глобальные прерывания. У меня они уже разрешены в RTOS.
1 2 3 4 5 6 7 8 9 10 | void TIM2_IRQHandler() { if(TIM2->SR & TIM_SR_UIF) // Проверяем, что это нас именно переполнение вызывало. Т.к. у таймера несколько видов событий и переполнение одно из { TIM2->SR = ~TIM_SR_UIF; //сбросить флаг. Да тут, в отличии от AVR это надо делать вручную. // Тут делаем свои дела } } |
Прерывание готово. Все тикает и вызывается. Осталось решить вопрос с организацией нашей задержки. Самое просто, что приходит в голову это захреначить в задаче конечный автомат, вращать его постоянно, а в прерывании инкрементировать переменные выдержек и ставить флаги. Проблема в том, что тут надо будет постоянно вызывать задачу-автомат, а она будет жрать время проца. Лучше пусть таймер в прерывании постоянно тикает сам по себе, а как дотикает задержкой, то разбудит соответствующую задачу и она продолжит.
Разбудить можно по разному. Можно через отправки сообщения в очередь, можно через семафор, но я предпочел другой способ. Уж не знаю когда и в какой версии, но во FreeRTOS появилась такая фича как FreeRTOS Task Notification. Т.е. задача в нужное время укладывается спать и разбудить ее можно послав ей уведомление. Которое просто пнет ее сапогом. Уведомление будем слать из прерывания.
Чем Notify отличается тогда от семафора? Концептуально — ничем. Но, работает быстрей, жрет меньше памяти, и в качестве цели для адресации использует не имя семфаора, а имя задачи. Минусы тоже есть — в отличии от семафора, мы не имеем обратной связи. Т.е. если мы ставим уже где-то поднятый семафор, то от функции xSemaphoreGive получим False в ответе. А извещение шлется и шлется. Никакого ответа нет. Но и не надо. Вот и все ограничения. Ну и передача адресата через handle задачи тоже удобно получается. Не нужно заводить семафор под каждый чих, не нужно париться по поводу того откуда это у нас что вызывается. В качестве индентификатора используется handle задачи, который всегда с тобой. Только руку протяни.
За основу я взял таймерную службу из моего диспетчера, который вы все уже много раз видел в курсе по AVR. Обгрыз от него все лишнее, а остаток засунул практически без изменений. Да он статичен, да висит в памяти. Но расход небольшой и вполне экономный.
В статичных переменных задаем массивчик:
#define DelayCOUNT 10
static struct
{
TaskHandle_t Message;
uint16_t Time;
}
delays100us[DelayCOUNT];
Первый элемент это заголовок задачи, второй время. Мне хватит 65535 тиков. А так хоть до какого можно влепить. Количество одновременно работающих задержек задается с небольшим запасом. Причем речь о одновременно работающих, т.е. по потокам. А если у вас в одном процессе несколько задержек одна за другой, то они все лягут в один слот. Добавляем в инициализацию либы еще и обнуление массива. Ну на всякий случай:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | void Delay_100usInit(void) { RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Тактирование на таймер 6 TIM2->PSC = 36; // Шина на 36мгц, делим. Получаем 1Мгц TIM2->ARR = 100; // Тикать будет с частотой 10Кгц. TIM2->CR1 |= TIM_CR1_ARPE | TIM_CR1_URS | TIM_CR1_CEN; // Запуск таймера TIM2->DIER |= TIM_DIER_UIE; // И прерываний NVIC_SetPriority(TIM2_IRQn,14); // Настройка приоритета прерывания Таймера 2 NVIC_EnableIRQ(TIM2_IRQn); // Разрешаем прерывание таймера 2 // Flush all Delays uint8_t index; for(index=0; index < DelayCOUNT; index++) // Обнуляем буфер. { delays100us[index].Message = NULL; delays100us[index].Time = 0; } } |
Добавляем функцию постановки задержки в массив:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | bool Delay_100us(uint16_t Time) { uint8_t index = 0; bool out=false; // Это выходная переменная. Она даст False если не удастся найти свободный слот. Или мы нотификацию не дождемся TaskHandle_t xMessage; xMessage = xTaskGetCurrentTaskHandle(); // Берем адрес текущей задачи. Чтобы не использовать API в критической секции. taskENTER_CRITICAL(); // Критическая секция, чтобы прерывание не вмешалось. for(index=0; index < DelayCOUNT; ++index) // Ищем свободную ячейку под задержку. { if (delays100us[index].Message == NULL) // Если хэндл пустой, значит нашли. { delays100us[index].Message = xMessage; // Кладем туда хэндл текущей задачи. Чтобы ОС Знала кого лягнуть. delays100us[index].Time = Time; // И на сколько тиков ложимся спать. out = true; // Говорим, что жизнь удалась. break; // Выход } } taskEXIT_CRITICAL(); // Конец критической секции if (out) { return ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Засыпаем пока не придет нотификация, по результату выдаем ответ. } else { return out; // Выходим с False так как не нашли пустой слот. } } |
ulTaskNotifyTake(pdTRUE, portMAX_DELAY) отдаст управление на время portMAX_DELAY (т.е. навечно) до прихода оповещения. Можно сделать не portMAX_DELAY, а сколько то там тиков таймера. И тогда, если извещение не придет, задача разморозится, но вернет False и мы будем знать, что все проебали. Либо потому, что не нашли ячейку, либо потому что не пришло извещение и мы выпали по таймауту. Тут уже простор для реакции.
Теперь дописываем наш обработчик:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | void TIM2_IRQHandler() { if(TIM2->SR & TIM_SR_UIF) // Проверяем, что это нас переполнение вызывало { TIM2->SR = ~TIM_SR_UIF; //сбросить флаг uint8_t index; BaseType_t xHigherPriorityTaskWoken = pdFALSE; // BaseType_t uxSavedInterruptStatus; // Переменная куда положим статус флагов прерываний для критической секции // uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR(); // Вход в критическую секцию. Но дается мне это зря. Приоритет фона же ниже. for(index=0; index < DelayCOUNT ;index++) // Прочесываем массив задержек { if(delays100us[index].Message == NULL) continue; // НА предмет не пустых ячеек if(delays100us[index].Time != 0) // Если время не равно нулю { delays100us[index].Time --; // Уменьшаем } else // А если в данном слоте время вышло. { vTaskNotifyGiveFromISR(delays100us[index].Message,&xHigherPriorityTaskWoken); // То отправляем уведомление задаче которая это выставила. delays100us[index].Message = NULL; // Обнуляем слот. Чтобы ничего не вышло там. } } // taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus); // Выход из критической секции. } } |
xHigherPriorityTaskWoken это возвращемое значение TRUE или FALSE о том, не разблокировали ли мы своим извещением (семафором, сообщением) более важную задачу чем та которую прервало это прерывание? Если для нас это критично, то можно выйти из прерывания не просто завершиф функцию, а с понтом — через функцию которая сразу отдаст управление передовикам производства. И лишь потом вернется к тому месту где прерывались. Если же забить, как я, то стахановцы подождут пока наша задача вернется с прерывания, а после только им дадут в руки баян. Вопрос лишь в скорости реакции.
Ну, а юзается функция просто и без понтов:
1 | Delay_100us(N); |
Сырки библиотечки High Density Delay:
Вы допустили в коде ужасную типовую ошибку, из-за которой рискуете провести ещё месяц-другой в борьбе с редко и случайно проявляющимся глюком. Правда, конкретно в этом примере ошибка не проявится, но так в принципе нельзя писать. Попробуйте найти сами:-).
Сломается будем искать. А пока работает.
Хорошо, тогда подскажу строчку, с которой кое-что не так, внимательно посмотрите описание регистра в мануале, и подумайте, что может случиться, если в приложении будут независимо использоваться несколько флагов прерываний:
TIM2->SR &= ~TIM_SR_UIF; //сбросить флаг
И что с ней не так? TIM_SR_UIF это вообще то не номер флага, а маска флага. А сам флаг UIF регистра RC в мануале значится как rc_w0 т.е. доступен для чтения и сбрасывается записью 0.
Ну, а дальше ~ инвертируем маску, получаем FFFFFE и записываем ее в регистр, сбрасывая только флаг UIF. Единственно, что & вот тут не нужна. Т.к. записи в регистр нет, только сброс. Но она ни на что не влияет. В чем проблема то?
Например, третий канал будет использоваться для захвата времени переключения внешнего сигнала. Обрабатывая событие от UIF вы прочитаете из SR значение SR_UIF. И далее запишете в SR нуль. Однако между чтением и записью SR может произойти событие захвата по третьему каналу, бит SR_CC3IF установится, и вы тут же его сбросите, так и не узнав, что возникло ещё какое-то событие. В результате получаем пропуск обработки прерывания. Поэтому нули в SR надо записывать только в те флаги, которые вы уже собрались обрабатывать и их надо немедленно сбросить, и в зарезервированные биты.
А где вы увидели, что я записываю в SR нуль? Ну где? Я нуль записываю ТОЛЬКО в бит UIF. Если в CR будут другие биты, то ничего с ними не случится. Они как стояли так и будут стоять.А, понял о чем вы. Да, если в текущем виде, с & то будет чтение вначале. А это лишнее и можно между этими двумя тактами словить. Учитывая, что там clear by 0, то & не нужен от слова совсем.
Не про return в bool Delay_100us ?
&& на выходе дает чистый bool
Я вот что имел в виду (если я правильно понял механизм):
return (ulTaskNotifyTake(pdTRUE, portMAX_DELAY) && out);
действительно даст чистый bool или зависнет
если out = false (нету слота)
и компилятор вызовет сначала ulTaskNotifyTake() для вычисления выражения и не дождется «сапога» из прерывания,
а я бы не положил ничего на рельсы что будет наоборот.
Это типичная ситуация с побочным эффектом в выражении.
Кроме того стандарт С99 прямо заявляет:
(4). Unlike the bitwise binary & operator, the && operator guarantees left-to-right evaluation; there is a sequence point after the evaluation of the first operand. If the first operand compares equal to 0, the second operand is not evaluated.
Другими словами, для операции && выражение вычисляется слева направо и т.д.
Пока запас слотов избыточен проблем не будет, однако проверка на свободный слот может просто не отработать.
Я бы написал так (тупо но надежно):
if (out)
{
return ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
else
{
return out;
}
Да. Вы правы, она точно встанет. Так лучше будет.
Если «&& guarantees left to right execution», то можно сделать return (out && ulTaskNotifyTake(pdTRUE, portMAX_DELAY)); — по сути будет то же самое, как и в «тупом» варианте.
Но в «тупом» варианте всё же более очевидно, пожалуй.
переменная xHigherPriorityTaskWoken в обработчике не используется дальше. по идеи, должно быть переключение контекста: portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
Не это не влияет. Это вопрос лишь в переключении контекста сейчас или потом. Даже если я разблокирую более приоритетную и не передам ей управление, то она все равно ее получит когда провернется диспетчер. Это вопрос скорости реакции и только.
Как это не влияет? Например, до вызова функции ulTaskNotifyTake(pdTRUE, portMAX_DELAY) более низкоприоритетная задача была в состоянии «готова», после вызова управление перейдет к ней. Без portYIELD_FROM_ISR(xHigherPriorityTaskWoken) после выхода из прерывания таймера управление так и останется у низкоприоритетной, а к «уведомленной» задаче управление перейдет только в следующем системном тике. Т.о. низкоприоритетная задача помешала верно отсчитать задержку.
«xHigherPriorityTaskWoken это возвращемое значение TRUE или FALSE о том, не разблокировали ли мы своим извещением (семафором, сообщением) более важную задачу чем та которую прервало это прерывание?», — тут не совсем верно. Верно: «не является ли разблокированная нашим извещением (семафором, сообщением) более важной, чем та что выполняется сейчас»
Ну и структура delays100us[DelayCOUNT] таки все равно не упакована по всей видимости и все равно будет занимать 8 байт. Так что можно и 32-битное время сделать.
Не влияет ,в том смысле, что ничего от этого не сломается и это не является некорректным выходом. Пострадает только время реакции разблокированных высокоприоритетных задач. У меня там кооперативный режим, поэтому все эти приоритеты мало чего значат. Управление все равно вручную передается.
= тут не совсем верно. Вы то же самое сказали, только другими словами. Один в один смысл.
Настройте assert на вывод в консоль отладчика или карта и будете видеть предупреждения об ошибках в уровнях — фриртос их проверяет
Хм… надо попробовать.
delays100us c функцией ulTaskNotifyTake(pdTRUE, portMAX_DELAY) в прерывании?
ошибся, извините.
>TIM2->PSC = 36; // Шина на 36мгц, делим. Получаем 1Мгц
The counter clock frequency CK_CNT is equal to fCK_PSC / (PSC[15:0] + 1).
ARR тоже -1 надо.
Абсолютно верно! Таймер считает от нуля до указанного значения N включительно, поэтому получается коэффициент деления N + 1. Это позволяет, указав 65535, получить делитель на 65536 (бывает нужно), в то же время, указав 0 получаем делитель на 1 (как бы отключаем деление вообще).
Ну да, логично. Поправил.
Помните, что когда Вы используете функцию loop() Arduino вместе с библиотекой Arduino_FreeRTOS, функция loop() никогда не должна блокироваться (переходить в состояние Block), или переходить в пустое занятое ожидание с использованием функции delay(), или иметь в своем теле какую-либо другую функцию задержки, так как функция loop() вызывается задачей Idle Task системы FreeRTOS (эта задача никогда не должна получать блокировку).
А при чем тут ардуино вообще?
С Critical Section в ISR нужно поосторожнее:
https://www.freertos.org/taskENTER_CRITICAL_FROM_ISR_taskEXIT_CRITICAL_FROM_ISR.html
…FreeRTOS API functions must not be called from within a critical section…
А у тебя вызывается vTaskNotifyGiveFromISR внутри нее.
Да я видел это предостережение. Но сколько не смотрел исходники так и не понял с чем оно связано. Т.к. по сути это просто запрет прерываний и все. Может конечно какой нибудь семафор переклинить изза этого, но только до момента выхода из прерывания.
Это больше связано с тем, что реализация ISR CS в теории может привести к dead lock. В твоем случае я бы блокировал только копирование одного айтема из статического массива в локальную стековую переменну, а дальше все действия производил над локальной переменной. Ну и в конце так же обновил бы айтем статического массива под защитой торого вызова CS. Это защитит от того, что в будущих релизах freeRTOS что-то поменяется. А дебажить dead lock’и в ISR без exclusive monitor то еще занятие…
Не, ну понятно , что концептуально апи функция с потенциально ждущей функцией, типа раздачи в очередь, может локнуть при занятой очереди. Тк отдаст управление шедулеру, а тот повиснет. Но вот тупо раздающие мессаги или функции возврата хэндла никакого потенциального криминала в себе иметь не должны. Им походу было просто лень описывать все случаи :)
Я думаю, они подстраховались на предмет возможных изменений в будущем. Ну и блочить прерывания на время прокрутки цикла у которого есть выходы в ядро сильно накладно. А без CS опасно — пока цикл крутится есть вероятность изменения статичсекого массива из прерываний с высшим приоритетом так как блокировки на вызов из прерывания в функциях задержки нету. Если же все фунции установки задержек работают только из unprivileged, то и смысла в CS нет. Но тогда надо блочить установку задержки из прерывания, что бы случайно не вызвать их оттуда.
Ну выход в ядро там только при совпадении. Т.е. статистически хорошо если один из будет. А чаще и без него. Но с другой стороны, вот да, если так подумать, то фон всегда ниже, а если не дергать эту функцию из прерывания, то и пофиг на КС. Проще действительно блочить запуск из прерывания, чем морочиться с критической секцией там.
Тут есть один тонкий момент:
Delay_100us()
{
….
delays100us[index].Message = xMessage;
!!! А тут фигась и прерывание!
delays100us[index].Time = Time; // <- а это уже может быть бесполезно, в прерывании Message = NULL;
….
А тут повиснем на всегда, так как ответ уже пришел, и мы будем ждать следующего:
return ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Засыпаем пока не придет нотификация, по результату выдаем ответ.
}
Так что надо механизм блокировки именно твоего прерывания на время добавления айтема.
Откуда там прерывание если там критическая секция.
Так это если от них (CS) отказаться. Ибо нафига блочить все прерывания, мне не очень понятно.
А вот маскировать исключительно прерывание таймера, используемого для счета задержек — уже более кошерно.
Так отказаться только в прерывании, где приоритет и так выше фона и вряд ли что ее перебьет. А в фоне КС нужна т.к. другая задача может использовать ту же функцию и записать в пустой слот совместно с первой. И получится баг. Поэтому доступ к массиву выдержек должен быть зафиксирован. Правда это критично только на вытесняющей многозадачности.
Шедулер (планировщик) отвечает за принятие решения о переключении контекста выполнения (т. е. какой задаче передать ресурс процессора). Решение это принимается на основе приоритета задач, а также на основе подсчета времени, которое задача провела в состоянии ожидания (чем дольше задача бездействовала, тем больше у неё прав на запуск). Приоритет назначается при создании задачи (uxPriority, упомянутый в статье), и может быт изменен или считан в ходе выполнения программы через специальные функции API FreeRTOS ( vTaskPrioritySet() , uxTaskPriorityGet() ). Моменты времени, когда происходят переключения задач с использованием шедулера, бывают в точках окончания каждого тика времени (для этого используется прерывание по таймеру), а также в момент вызова функций API FreeRTOS, и при наступлении специальных событий (окончание аппаратного прерывания, окончание времени блокировки задачи, наступление момента обновления очереди или семафора). Задачи, которые имеют равный приоритет, имеют одинаковые права на процессор, и переключаются на выполнение по очереди. Задачи, которые имеют более высокий приоритет, вытесняют (preempt) все задачи, которые имеют приоритет меньше. Высокоприоритетные задачи могут принудительно отдать свое процессорное время (вызовом taskYIELD() , если они завершили свою нужную работу), или заблокироваться в ожидании специального события, позволяя тем самым менее приоритетным задачам возможность запуститься. Таким образом во FreeRTOS для переключения задач используется алгоритм приоритетного (с фиксацией приоритета) планирования запуска задач с вытеснением, вытесняющая многозадачность ( Fixed Priority Preemptive Scheduling ). Однако имеется возможность применить и другой алгоритм переключения задач — кооперативная многозадачность ( Co-operative Scheduling ). У каждого алгоритма свои достоинства и недостатки, и соответственно разная область применения. Для обмена данными задача-задача, задача-прерывание, прерывание-задача во FreeRTOS имеются специально предусмотренные объекты — очереди. При обмене данными или событиями предусмотрена синхронизация высокоприоритетной задачи (или прерывания) и менее приоритетной задачи. Синхронизация происходит с помощью блокировки на очереди или семафоре, при этом можно задать время блокировки (таймаута процесса передачи). Для атомарных операций есть возможность создавать критические секции кода.
И к чему эта копипаста описания из мануала?
Вопрос по выбору таймера. А что мешает настроить SysTick на 100 кГц,в нём организовать задержку, а диспетчер вызывать каждое десятое прерывание?
планировщик будет съедать в 100 раз больше ресурсов, на сколько это будет больше в % соотношении нужно мерять, но не всегда их (ресурсов) хватит на такой трюк.