STM32 и FreeRTOS. 1. Развлечение с потоками

  • Tutorial
Данный цикл из 5 статей рассчитан на тех, кому стало мало возможностей привычных «тинек» и ардуинок, но все попытки перейти на более мощные контроллеры оканчивались неудачей или не приносили столько удовольствия, сколько могли бы. Все ниженаписанное проговаривалось мной много раз на «ликбезе» программистов нашей студии (которые часто сознавались, что переход с «тинек» на «стмки» открывает столько возможностей, что попадаешь в ступор, не зная за что хвататься), поэтому смею надеяться, что польза будет всем. При прочтении подразумевается, что читающий — человек любопытный и сам смог найти и поставить Keil, STM32Cube и понажимать кнопки «ОК». Для практики я использую оценочную плату STM32F3DISCOVERY, ибо она дешевая, на ней стоит мощный процессор и есть куча светодиодиков.

Каждая статья рассчитана на «повторение» и «осмысление» где-то на один околовечерний час, ибо дом, семья или отдых…





Очень часто (да что там часто, практически всегда) микроконтроллеры применяют в условиях, когда необходимо отслеживать сразу несколько параметров. Или наоборот, управлять одновременно несколькими устройствами.

Вот задача для примера: у нас есть 4 выхода, на которых необходимо выводить импульсы разной длительности с разными паузами. Все, что у нас есть – это системный таймер, который считает в миллисекундах.

Усложняем задачу в духе “сам себя замучаю на ардуино”. Таймеры заняты другим, PWM не подходит, ибо не на всех ножках он работает, да и не загонишь его на нужные режимы обычно. Немного подумав, садимся и пишем примерно такой код

// инициализация
int time1on=500; // Время, пока выход 1 должен быть включен
int time1off=250; // Время, пока выход 1 должен быть выключен
unsigned int now=millis();
....
// где-то в цикле
if(millis()<now+time1on)
{
  port1=ON;
}
else
{
port1=OFF;
if(millis()>now+time1on+time1off)
  {
  now=millis();
  }
}


И так или примерно так для всех 4 портов. Получается приличная портянка на несколько экранов, но эта портянка работает и работает довольно быстро, что для микроконтроллера важно.

Потом внезапно программист замечает, что при каждом цикле дергается порт, даже если его состояние не меняется. Правит всю портянку. Потом число портов с такими же потребностями увеличивается в два раза. Программист плюет и переписывает все в одну функцию типа PortBlink(int port num).

Почти наступило счастье, но внезапно потребовалось что бы на каком-то порту вместе с управлением “на выход” что-то предварительно считывалось и уже на основе этого считанного управлялся порт. Программист снова матерится и делает еще одну функцию, специально под порт.

Счастье? А вот фигу. Заказчик что-то этакое прицепил и это считанное может легко тормознуть процесс на секунды … Начинается стенания, программисты правят в очередной раз код, окончательно превращая его в нечитаемый треш, менеджеры выкатывают дикие прайсы заказчику за добавление функционала, заказчик матерится и решает больше никогда не связываться со встроенными решениями.

(типа реклама и восхваление) А все почему? Потому что изначально было принято неправильное решение о платформе. Если есть возможность, мы предлагаем навороченную платформу даже для примитивных задач. По опыту стоимость разработки и поддержки потом оказываются гораздо ниже. Вот и сейчас для управления 8мю выходами я возьму STM32F3, который может работать на 72МГц. (шепотом) На самом деле просто у меня под рукой демоплата с ним (смаил). Была еще с L1, но мы ее нечаянно использовали в одном из проектов.

Открываем STM32Cube, выбираем плату, включаем галочку около FreeRTOS и собираем проект как обычно. Нам ничего этакого не надо, поэтому оставляем все по умолчанию.

Что такое FreeRTOS? Это операционная система почти реального времени для микроконтроллеров. То есть все, что вы слышали про операционные системы типа многозадачности, семафоров и прочих мутексов. Почему FreeRTOS? Просто ее поддерживает STM32Cube ;-). Есть куча других подобных систем – та же ChibiOS. По своей сути они все одинаковые, только различаются командами и их форматом. Тут я не собираюсь переписывать гору книг и инструкций по работе с операционными системами, просто пробегусь широкими мазками по наиболее интересным вещам, которые очень сильно помогают программистам в их нелегкой работе.

Ладно, буду считать что прочитали в интернете и прониклись. Смотрим, что поменялось

Где-то в начале main.c

static void StartThread(void const * argument);


и после всех инициализаций

/* Create Start thread */
osThreadDef(USER_Thread, StartThread, osPriorityNormal, 0, configMINIMAL_STACK_SIZE);
osThreadCreate (osThread(USER_Thread), NULL);

/* Start scheduler */
osKernelStart(NULL, NULL);



И пустая StartThread с одним бесконечным циклом и osDelay(1);

Удивлены? А между тем перед вами практически 90% функционала, которые вы будете использовать. Первые две строки создают поток с нормальными приоритетом, а последняя строка запускает в работу планировщик задач. И все это великолепие укладывается в 6 килобайт флеша.

Но нам надо проверить работу. Меняем osDelay на следующий код

HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_RESET);
osDelay(500);
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_SET);
osDelay(500);


Компилируем и заливаем. Если все сделано правильно, то у нас должен замигать синий светодиодик (на STM32F3Discovery на PE8-PE15 распаяна кучка светодиодов, поэтому если у вас другая плата, то смените код)

А теперь возьмем и растиражируем полученную функцию для каждого светодиода.

static void PE8Thread(void const * argument);
static void PE9Thread(void const * argument);
static void PE10Thread(void const * argument);
static void PE11Thread(void const * argument);
static void PE12Thread(void const * argument);
static void PE13Thread(void const * argument);
static void PE14Thread(void const * argument);
static void PE15Thread(void const * argument);


Добавим поток для каждого светодиода
osThreadDef(PE8_Thread, PE8Thread, osPriorityNormal, 0, configMINIMAL_STACK_SIZE);
osThreadCreate (osThread(PE8_Thread), NULL);


И перенесем туда код для зажигания светодиода
static void PE8Thread(void const * argument)
{
for(;;)
  {
    HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_RESET);
    osDelay(500);
    HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_SET);
    osDelay(500);
  }
}


В общем все однотипно.

Компилируем, заливаем … и получаем фигу. Полную. Ни один светодиод не мигает.

Путем страшной отладки методом комментирования выясняем, что 3 потока работают, а 4 – уже нет. В чем проблема? Проблема в выделенной памяти для шедулера и стека.

Смотрим в FreeRTOSConfig.h

#define configMINIMAL_STACK_SIZE ((unsigned short)128)
#define configTOTAL_HEAP_SIZE ((size_t)3000)


3000 байт на все и каждой задаче 128 байт. Плюс еще где-то надо хранить информацию о задаче и прочем полезном. Вот поэтому, если ничего не делать, планировщик при нехватке памяти даже не стартует.

Судя по факам, если включить полную оптимизацию, то сам FreeRTOS возьмет 250 байт. Плюс на каждую задачу по 128 байт для стека, 64 для внутреннего списка и 16 для имени задачи. Считаем: 250+3*(128+64+16)=874. Даже до килобайта не дотягивает. А у нас 3 …

В чем проблема? Поставляемая с STM32Cube версия FreeRTOS слишком старая (7.6.0), что бы заиметь vTaskInfo, поэтому я захожу сбоку:

Перед и после создания потока я поставил следующее (fre – это обычный size_t)

fre=xPortGetFreeHeapSize();


Втыкаем брекпоинты и получаем следующие цифры: перед созданием задачи было 2376 свободных байт, а после 1768. То есть на одну задачу уходит 608 байт. Проверяем еще. Получаем цифры 2992-2376-1768-1160. Цифра совпадает. Путем простых логических умозаключений понимаем, что те цифры из фака взяты для какого-нибудь дохлого процессора, со включенными оптимизациями и выключенными всякими модулями. Смотрим дальше и понимаем, что старт шедулера отьедает еще примерно 580 байт.

В общем, принимаем для расчетов 610 байт на задачу с минимальным стеком и еще 580 байт для самой ОС. Итого в TOTAL_HEAP_SIZE надо записать 610*9+580=6070. Округлим и отдадим 6100 байт – пусть жирует.

Компилируем, заливаем и наблюдаем, как мигают все светодиоды разом. Пробуем уменьшить стек до 6050 – опять ничего не работает. Значит, мы подсчитали правильно :)

Теперь можно побаловаться и позадавать для каждого светодиодика свои промежутки “импульса” и “паузы”. В принципе, если обновить FreeRTOS или поколдовать в коде, то легко дать точность на уровне 0,01мс (по умолчанию 1 тик – 1мс).

Согласитесь, работать с 8ю задачами поодиночке гораздо приятней, чем в одной с 8ю одновременно? В реальности у нас в проектах обычно крутится по 30-40 потоков. Сколько было бы смертей программистов, если всю их обработку запихать в одну функцию я даже подсчитать боюсь :)

Следующим шагом нам необходимо разобраться с приоритетами. Как и в реальной жизни, некоторые задачи “равнее” остальных и им необходимо больше ресурсов. Для начала заменим одну мигалку мигалкой же, но сделанной неправильно, когда пауза делается не средствами ОС, а простым циклом.

То есть вместо osDelay() вставляется вот такой вот ужас.

unsigned long c;
for(int i=0;i<1000000;i++)
{
  c++;
}


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

Заменяем, компилируем, запускаем. Светодиодики мигают по прежнему, но как-то вяло. Просмотр осциллографом дает понять, что вместо ровных границ (типа 50мс горим и 50мс не горим), границы стали плавать на 1-2мс (глаз, как ни странно, это замечает). Почему? Потому что FreeRTOS не система реального времени и может позволить себе такие вольности.

А теперь давайте поднимем приоритет этой задаче на один шажок, до osPriorityAboveNormal. Запустим и увидим одиноко мигающий светодиод. Почему?

Потому что планировщик распределяет задачи по приоритетам. Что он видит? Что задача с высоким приоритетом постоянно требует процессор. Что в результате? Остальным задачам времени на работу не остается.

А теперь понизим приоритет на один шаг от нормального, до osPriorityBelowNormal. В результате планировщик, дав поработать нормальным задачам, отдает оставшиеся ресурсы «плохой».

Отсюда можно легко вывести первое правило программиста: если функции нечего делать, то отдай управление планировщику.

В FreeRTOS есть два варианта «подожди»

Первый вариант «просто подожди N тиков». Обычная пауза, без каких либо изысков: сколько сказали подождать, столько и ждем. Это vTaskDelay (osDelay просто синоним). Если посмотреть на время во время выполнения, то будет примерно следующее (примем что полезная задача выполняется 24мс):

… [0ms] — передача управления — работа [24ms] пауза в 100мс [124ms] — передача управления — работа [148ms] пауза в 100мс [248ms]…

Легко увидеть, что из-за времени, требуемой на работу, передача управления происходит не каждые 100мс, как изначально можно было бы предположить. Для таких случаев есть vTaskDelayUntil. С ней временная линия будет выглядеть вот так

… [0ms] — передача управления — работа [24ms] пауза в 76мс [100ms] — передача управления — работа [124ms] пауза в 76мс [200ms]…

Как видно, задача получает управление в четко обозначенные временные промежутки, что нам и требовалось. Для проверки точности планировщика в одном из потоков я попросил делать паузы по 1мс. На картинке можете оценить точность работы с 9ю потоками (про StartThread не забываем)



На этом я обычно заканчиваю, ибо народ настолько погружается в игру с приоритетами и выяснением «когда оно сломается», что проще замолчать и дать поразвлекаться.

Полностью собранный проект со всеми исходниками можно взять по адресу kaloshin.ru/stm32/freertos/stage1.rar


Продолжение Часть 2. Про семафоры
Поделиться публикацией

Похожие публикации

Комментарии 35
    +2
    Для желающих более подробно разобраться в отдельных моментах посоветую ещё вот эту книгу: FreeRTOS — операционная система для микроконтроллеров
      0
      Жду продолжения!
        +1
        Будет. Еще 4 части, в конце «из чего-то там и палок» сделаем аппаратный сниффер UART :)
        +2
        Вот мне всегда было интересно, зачем процессу с тупым дерганьем ножки аж ~200 байт оперативки?
          +2
          Ну вообще-то ему столько не надо. Если именно просто дергать ножку, то вроде 20-25 байт хватает за глаза. Но простое дерганье ножки встречается очень редко :)
          0
          Спасибо за статью! Я думаю, было бы здорово, если бы Вы добавили кратко список из раздела «Обзор FreeRTOS» из статьи, ссылку на которую привел Disasm в первом комментарии.
          Вообще, ОС для МК — неоднозначная штука. Например, если написать что-то вроде
          static void thread1(const void * arg)
          {
             while (1) {
               write_port(1, SET);
               write_port(1, RESET);
             }
          }
          static void thread2(const void * arg)
          {
             while (1) {
               write_port(2, SET);
               write_port(2, RESET);
             }
          }
          

          и запустить оба потока, то мы увидим не непрерывную смену сигналов, а попеременно чередующуюся. Даже сам процесс смены задачи повлияет и, скорее всего, появится участок времени, когда ни один из портов не переключается.
          На мой взгляд, ОС нужна, когда пишешь что-то гораздо более сложное.

            0
            Ну у меня не стояло задачи переписывать документацию к FreeRTOS. И по-моему, любой программер должен сам раскопать и прочитать более новые описания.

            У меня регулярно стоит задача показать «ардуинщикам» или «олдскульным» что под stm32 можно писать так же быстро, но возможностей гораздо больше и бояться не надо. Ибо ардуино, не смотря на всю красивость, очень сильно портит мозги навязываемым стилем программирования.

            Что касается кода, то не вижу причин, почему сигналы обязательно будут чередоваться. Все зависит от процессора и ОС. Например, берем двухядерный процессор и распределяем потоки по ядрам.

            А участок, когда порты не будет переключаться, будет всегда и везде, ибо между вызовами write_port всегда будут другие вызовы (типа обновления счетчика времени работы, прерывания, регенерация памяти и так далее и тому подобное). Главное тут — понять, насколько важен этот участок времени и насколько он критичен. Но по моему опыту, еще не разу не было необходимости сделать одиночный импульс длительностью около нано- или пико-секунды (лень вспоминать величину сколько будет занимать 1 такт на 140МГц)
              0
              Одиночный импульс на 140 МГц это около 7 нс. Для сравнения — время распространения радиосигнала на 3 метра — 10 нс. Т.е. если вам задали сделать обработку в локаторе с разрешением по дальности 1.5 метра, то 10 нс для вас рабочий период. Другой пример, обработка временных интервалов низкочастотных процессов с высокой точностью. Скажем, есть процесс с максимальной частотой следования импульсов всего 10 кГц, но измерять время между импульсами необходимо с точностью 0.01% — получим тот рабочий период 10 нс. Примеры живые, это 2 реальных задачи из двух реальных приборов. Первый пришлось решать на рассыпухе 1554 серии, второй товарищ сейчас пытается допилить на отечественном аналоге Cortex M3 методом усреднения или еще чем-то подобным, но пока не слишком получается.
                0
                Когда в ТЗ стоит пунктик про отечественную элементную базу, любая задача внезапно на порядок усложняется :)
                Про импульсы помнится еще несколько лет назад коллеги, используя Cyclone III, получали разрешение по времени в районе 1-2нс.
                  0
                  Отечественный ключ 590кн8, выпускающийся с начала 80-х, имеет время включения 3 нс. Импортные ключи примерно такой же стоимости сейчас имеют быстродействие вроде бы около 1 нс. STM интересна в плане что на них есть отечественные аналоги, Миландр делает. Сортех M3 у них уже идет серийно, М4 обещают скоро запустить. Отладочные платы у Миландра дорогие, за 30 тыр, дороже чем от STM на порядок и более.
            0
            А я вот замучался отлаживать трудноуловимый баг с потоками, но система ThreadX + arm926ejs (Cypress FX3 и их SDK). OpenOCD из коробки не поддерживает примитивы ThreadX для этого CPU, попытался добавить сам, посидел пару дней с дизассемблером, что бы понять как контекст сохраняется, вроде реализовал, треды отображаются, а вот стектрейсы какие-то эпические. Вот и не могу понять, где лыжи стоят. Если кому интересно, наработки: secure.cypress.com/?app=forum&id=167&rID=106353
              +1
              Тут я извинюсь. По меркам хабра и программистов микроконтроллеров, я еще не настоящий сварщик :)
              +2
              Потому что FreeRTOS не система реального времени и может позволить себе такие вольности.

              Я дико извиняюсь, но FreeRTOS — это Free Real Time Operating System. И «вольности» она себе позволяет исключительно с вашего допущения.

              От себя добавлю, что FreeRTOS достаточно удобная штука, но некоторые вещи там сделаны несколько странно.
              И соглашение об именах лично мне не нравится, бесполезная венгерская нотация только мешает автоподстановке. А уж про имена макросов в стиле configCONFIG_SOMETHING режут глаз.

              Но плюшек много.
                0
                Если подходить с такой точки зрения, то FreeRTOS да, имеет право на RT в названии. Но для «настоящей» RT ей не хватает предсказуемости. В том примере время переключение на задачу «плавает» от загрузки. Это объясняется бОльшими объемами для сохранения контекстов и прочего (мне реально не охота лезть туда).

                И про странности согласен :)
                  0
                  Я может быть не совсем уловил, но вы изначально запускали OS в кооперативном режиме, круговом или вытесняющем?
                  В том примере, где время переключение начало плавать.
                    0
                    по умолчанию в FreeRTOS — вытесняющий.

                    Что бы сразу отсечь «так не бывает», могу рассказать, как быстро доказать «реалтаймовость FreeRTOS» на практике.

                    1. Ставим 1 тик на 1мс (ну это по умолчанию)
                    2. Берем STM32Lx и ставим ему минимальную тактовую частоту (хотя вроде 8МГц хватит уже)
                    3. Просим FreeRTOS дергать нас каждую миллисекунду.
                    4. Смотрим на осциллографе, как гуляют периоды.

                    Хинт: на такой частоте процессор успевает сделать примерно 80 тактов и время переключения задача-планировщик и обратно больше 1мс

                      0
                      Ну, эм, а чего вы хотели тогда, если время переключения больше 1 миллисекунды и тик каждую миллисекунду?
                        +2
                        Ничего. Просто следовал анекдоту про русских мужиков и японскую бензопилу :)
                          0
                          Ы. Ладно.
                          Просто если я правильно понимаю, ни одна RTOS вам не обеспечит периодического процесса с периодом меньше, чем гарантированное время переключения контекста. Так что мне ваше доказательство показалось очень странным.
                            0
                            Ну да :) Но (ИМХО) настоящая RTOS просто не должна допускать такое. То есть в данном случае я должен был получить отлуп в создании задачи/таймера/ещечего и свалиться в обработку ошибок
                              0
                              Если вы знаете такую RTOS, то, пожалуйста, расскажите :)
                                0
                                По этим меркам я «ненастоящий сварщик» :) Но вроде по слухам какую-то из подверсий QNX можно заставить делать такое.

                                Но нам пока хватает и такого поведения. Хвала ST — простой сменой чипа увеличивается память/скорость. Даже плату не надо переразводить :)
                +1
                Порт FreeRTOS на STM32 работает с памятью не байтами, а 32-битными словами, см. тип portSTACK_TYPE файл portmacro.h для выбранной архитектуры, размер данного типа используется при выделении памяти под стек. Поэтому строчки
                #define configMINIMAL_STACK_SIZE ((unsigned short)128)
                #define configTOTAL_HEAP_SIZE ((size_t)3000)
                Означают что на таск выделяется 128*4=512 байт, а на всех 3000*4=12000 байт.

                Когда сначала просматривал статью, первой мыслью было «О ужас! В новых FreeRTOS зачем-то изменили API». А оказалось это ST опять выпендрились и зачем-то понаделали оберток для функций FreeRTOS. Лучше этим чудом не пользоваться, во избежании проблем в будущем, а задействовать нормальный, свежий релиз FreeRTOS.
                  0
                  Вот тут не соглашусь. Да, действительно
                  #define portLONG		long
                  #define portSHORT		short
                  #define portSTACK_TYPE	unsigned portLONG
                  


                  Но в самом коде

                  typedef struct xTASK_PARAMTERS
                  {
                  	pdTASK_CODE pvTaskCode;
                  	const signed char * const pcName;
                  	unsigned short usStackDepth;
                  	void *pvParameters;
                  	unsigned portBASE_TYPE uxPriority;
                  	portSTACK_TYPE *puxStackBuffer;
                  	xMemoryRegion xRegions[ portNUM_CONFIGURABLE_REGIONS ];
                  } xTaskParameters;
                  
                  


                  И дальше с ним работа идет примерно так

                  pxNewTCB->pxStack = ( portSTACK_TYPE * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( portSTACK_TYPE ) ), puxStackBuffer ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
                  


                  То есть просто нет смысла просить стек в некратное sizeof( portSTACK_TYPE ) число байт.

                  А про выпендривание ST. Ну по крайней мере они честно предупреждают, что ломают (ака SPL > HAL), а не втихушку что-то делают.

                  Что касается более свежих версий FreeRTOS — согласен. Но STM32Cube (вернее его firmware) пока использует старые. А надо стартануть сразу и быстро, иначе разбегутся :)
                    0
                    Поясните, с чем вы не соглашаетесь? Сами же нашли строчку:
                    ( portSTACK_TYPE * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( portSTACK_TYPE ) ), puxStackBuffer );

                    usStackDepth это параметр, который вы передаете в xTaskCreate, т.е. в данном случае 128
                    portSTACK_TYPE = unsigned long, а sizeof(unsigned long) = 4
                    128 * 4 =?

                    Про не кратность ничего не понял.
                      0
                        0
                        Ндас, признаю. Закопался в не ту сторону. Ибо в определении треда четко прописано, что

                        uint32_t               stacksize;    /* stack size requirements in bytes; 0 is default stack size */
                        


                        Плюс я ориентировался на показания xPortGetFreeHeapSize(), а он сообщает именно байты, а не слова

                        /* A few bytes might be lost to byte aligning the heap start address. */
                        #define heapADJUSTED_HEAP_SIZE	( configTOTAL_HEAP_SIZE - portBYTE_ALIGNMENT )
                        ...
                        #if portBYTE_ALIGNMENT == 4
                        	#define portBYTE_ALIGNMENT_MASK	( 0x0003 )
                        #endif
                        ....
                        /* Keeps track of the number of free bytes remaining, but says nothing about
                        fragmentation. */
                        static size_t xFreeBytesRemaining = ( ( size_t ) heapADJUSTED_HEAP_SIZE ) & ( ( size_t ) ~portBYTE_ALIGNMENT_MASK );
                        
                        size_t xPortGetFreeHeapSize( void )
                        {
                        	return xFreeBytesRemaining;
                        } 
                        


                        В общем, перепроверю и сделаю тесткейс, что бы других в заблуждение не вводить.
                          0
                          А я тоже немного наврал. configTOTAL_HEAP_SIZE задается в байтах. Т.к. например в heap_1.c имеем
                          unsigned char ucHeap[ configTOTAL_HEAP_SIZE ];
                          

                          В мозгах как-то отложилось, что стек в лонгах.
                          В общем сие есть потенциальные грабли, про которые просто помнить надо.
                            0
                            (мрачно) тут этих грабель… :)
                    0
                    Интересно, если взять более-менее реальный проект, где есть сложные вычисления, есть ли оценки эффективности? Если поток1 работает непрерывно (не привлекая delay) 100мкс, поток2 тоже 100мкс, то как ОС распеределит время между ними? Они досчитают одновременно или по очереди? И сколько пройдет времени в сумме (т.е. какой оверхеод от вмешательства RTOS?)
                      +1
                      Вот хоть убей не помню, где народ сравнивал оверхед от ОС. Но навскидку, по памяти — где-то 3-4% «просадка» по сравнению с «чистым» железом.

                      Что касается распределения времени, то если ничего не делать, то делится честно и поровну.
                      0
                      Не могли бы вы описать алгоритм как правильно общаться с периферией в потоках, где требуется отмерять точные промежутки времени? Например имеем цифровой датчик влажности, датчик давления и, пусть будет термометр с интерфейсом 1-ware. К примеру, на опрос каждого датчик у нас есть поток (это наверное не очень правильно и лучше опрос организовать таймерами с прерываниями, но пусть будет для нашего примера). Так вот, как правильно организовать общение с внешним миром, чтоб быть уверенным, что поток не прервется на середине процедуры чтения и данные не будут потеряны?
                        0
                        У любой RTOS есть макросы/команды в духе «не сметь прерывать»

                        Для FreeRTOS это taskENTER_CRITICAL и taskDISABLE_INTERRUPTS

                        www.freertos.org/a00020.html
                        0
                        А могли при создании таска указать больший размер, а не копипастить пример и потом править конфиг?
                          0
                          Когда писалась эта статья, кубик не умел в это.

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                        Самое читаемое