Создание приложений на базе RISC-V и FreeRTOS

FreeRTOS

Разбить на страницы
Показывать лекцию целиком

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

Далее мы объясним различные компоненты FreeRTOS и то, как они работают и взаимодействуют друг с другом, чтобы обеспечить инфраструктуру для создания встраиваемых приложений. После понимания процесса создания приложений мы завершим лекцию демонстрацией запуска примера приложения в симуляторе QEMU.

Ознакомившись с этой лекцией, читатель сможет:

  • понимать, что такое FreeRTOS;
  • описать особенности FreeRTOS;
  • объяснить содержание и структуру дистрибутива FreeRTOS;
  • перечислить компоненты FreeRTOS и объяснить, как они работают;
  • создавать приложения на базе FreeRTOS;
  • запускать пример приложения на QEMU.
  • Презентация по лекции

    Введение во FreeRTOS

    Что такое FreeRTOS?

    FreeRTOS - одна из наиболее часто используемых операционных систем реального времени (RTOS), распространяемая под открытой лицензией MIT. FreeRTOS - это RTOS, ориентированная на микроконтроллеры и небольшие микропроцессоры. Она включает ядро и растущий набор библиотек IoT, подходящих для использования во всех отраслях промышленности. FreeRTOS создана с акцентом на надежность и простоту использования.

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

    Требования приложений

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

    Технические требования

    Модульность

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

    Абстрагирование информации о времени

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

    Управление питанием

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

    Гибкое управление прерываниями

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

    Требования к смешанной обработке

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

    Управление временем простоя системы

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

    Требования к обслуживанию и эффективности

    Тестируемость

    Одним из ключевых аспектов при разработке системы является возможность её обслуживания и быстрого и эффективного устранения любых поломок. Для обеспечения этого требования важно иметь возможность тестирования на любые потенциальные сбои во время разработки системы, а также воспроизведения любых сбоев, выявленных во время эксплуатации системы. Поэтому, когда система разрабатывается, тестируемость должна быть встроенной частью дизайна, включая определение того, что должно быть протестировано и как это должно быть протестировано.

    Удобство обслуживания и расширяемость

    Система должна быть разработана таким образом, чтобы её можно было легко обслуживать как в полевых условиях, так и вне их, как с точки зрения аппаратного, так и программного обеспечения. Расширяемость также является ключевым фактором; система должна быть разработана таким образом, чтобы упростить добавление функций после завершения первоначальной версии системы.

    Разработка приложений в команде

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

    Повторное использование кода

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

    Эффективность кода

    Наконец, код должен быть разработан таким образом, чтобы эффективно использовать память, и разработчик должен помнить об ограничениях производительности небольших контроллеров, на которых строятся встраиваемые системы.

    Особенности FreeRTOS

    Ниже перечислены стандартные возможности FreeRTOS (взятые из справочного руководства FreeRTOS):

  • вытесняющие или совместные операции;
  • очень гибкое назначение приоритетов задач;
  • гибкий, быстрый и легкий механизм оповещения о задачах;
  • очереди;
  • бинарные семафоры;
  • счётные семафоры;
  • мьютексы;
  • рекурсивные мьютексы;
  • программные таймеры;
  • группы событий;
  • функции перехвата, вызываемые прерыванием тика (tick hook functions);
  • функции перехвата, вызываемые задачей ожидания (idle hook functions);
  • проверка переполнения стека;
  • запись трассировки;
  • сбор статистики выполнения задач;
  • дополнительное коммерческое лицензирование и поддержка;
  • модель полной вложенности прерываний (full interrupt nesting model) для некоторых архитектур;
  • таймер без прерываний (прерывания таймера не происходят через регулярные промежутки времени, а доставляются только по мере необходимости) для приложений с чрезвычайно низким энергопотреблением;
  • программно управляемый стек прерываний, когда это необходимо (это может помочь сэкономить оперативную память).
  • Содержимое и структура дистрибутива FreeRTOS

    FreeRTOS распространяется через Git-репозиторий в виде одного сжатого zip-файла со всем исходным кодом, поэтому пользователи могут свободно использовать его в своих приложениях, а также при необходимости вносить в код любые изменения. Дистрибутив также содержит демонстрационные приложения и демоверсии портов (ports) для различных контроллеров, поддерживаемых FreeRTOS. Эти демоверсии помогают пользователям FreeRTOS быстро адаптировать её к своим требованиям. Многие поставщики микроконтроллеров поставляют RTOS как часть своего набора инструментов, что еще больше облегчает разработку с FreeRTOS.

    Структура каталогов дистрибутива FreeRTOS

    Последнюю версию FreeRTOS можно загрузить с веб-сайта FreeRTOS. FreeRTOS также размещен на GitHub, и пользователи могут получить последнюю версию исходного кода из каталога Git.

  • FreeRTOS - содержит файлы, относящиеся к FreeRTOS
  • Demo - содержит демоверсии портов
  • License - содержит лицензионные файлы
  • Source - содержит исходный код FreeRTOS
  • Test - содержит тесты
  • FreeRTOS-Plus - содержит файлы FreeRTOS и расширенные библиотеки приложений
  • Demo - содержит демонстрационные версии приложений FreeRTOS-Plus
  • Source - содержит исходный код библиотек FreeRTOS-Plus
  • Test - содержит тесты приложений FreeRTOS-Plus
  • ThirdParty - содержит сторонние контрибуции (third party contributions)
  • Содержимое дистрибутива

    Ключевая часть ядра (core of the kernel) FreeRTOS содержится в трёх файлах, которые находятся в подкаталоге FreeRTOS\FreeRTOS\Source. Это файлы tasks.c, queue.c и lists.c. Для простых приложений этих трёх файлов достаточно. Папка Source содержит подпапку include, в которой находятся файлы include, необходимые для установки.

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

    Каталог Source также содержит подкаталог portable, который содержит подкаталоги с файлами, специфичными для определенных компиляторов и программных инструментов. Код, находящийся в этих папках, может быть использован в качестве отправной точки для создания портов в соответствии с требованиями пользователя.

    Папка Demo содержит примеры приложений, портированных на конкретные контроллеры. Пользователи, планирующие использовать контроллер определенного типа, могут использовать один или несколько примеров, представленных в этой папке, в качестве основы для создания приложений для своего контроллера.

    Компоненты FreeRTOS и их применение

    Ключевые компоненты FreeRTOS

    Ниже перечислены ключевые компоненты FreeRTOS:

  • управление памятью;
  • задачи;
  • очереди, семафоры и мьютексы;
  • прямые уведомления о задачах;
  • буферы потоков и сообщений;
  • таймеры.
  • Мы опишем их более подробно в последующих разделах.

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

    Все возможности FreeRTOS управляются из файла конфигурации FreeRTOS, FreeRTOSConfig.h. Каждое приложение должно иметь этот файл как часть системы. Он содержит параметры конфигурации для включения функций FreeRTOS, необходимых для данного приложения. Образец конфигурационного файла может быть скопирован из демонстрационного порта, который лучше всего подходит для контроллера, используемого для данного приложения.

    Управление памятью: методы распределения памяти

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

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

    Схема распределения памяти во FreeRTOS по умолчанию динамическая. В случаях, когда разработчику приложения требуется больший контроль над управлением памятью для определенных компонентов приложения, он может выбрать статическое распределение памяти для этих задач. FreeRTOS предоставляет отдельный набор функций для компонентов, которым требуется статическое распределение памяти. Эти функции доступны, когда configSUPPORT_STATIC_ALLOCATION имеет значение 1 в файле настроек конфигурации.

    Ниже перечислены некоторые преимущества использования динамического распределения памяти (адаптировано из руководства пользователя FreeRTOS):

  • при создании объекта требуется меньше параметров функции;
  • выделение памяти происходит автоматически, в рамках функций API RTOS;
  • разработчику приложения не нужно заботиться о выделении памяти;
  • оперативная память, используемая объектом RTOS, может быть повторно использована при удалении объекта, что потенциально уменьшает максимальный объем оперативной памяти приложения;
  • функции API RTOS позволяют возвращать информацию об использовании кучи, что позволяет оптимизировать её размер.
  • Ниже перечислены некоторые преимущества использования статического распределения памяти (адаптировано из руководства пользователя FreeRTOS):

  • объекты RTOS могут быть размещены в определенных местах памяти;
  • максимальный объем оперативной памяти может быть определен во время компоновки, а не во время выполнения;
  • разработчику приложения не нужно заботиться о корректной обработке сбоев при выделении памяти;
  • это позволяет использовать RTOS в приложениях, которые просто не допускают динамического распределения памяти (хотя FreeRTOS включает схемы распределения, которые могут преодолеть большинство возражений).
  • Управление памятью

    Управление памятью: схемы управления памятью во FreeRTOS

    FreeRTOS определяет пять схем управления памятью. Они содержатся в отдельных файлах: heap_1.c, heap_2.c, heap_3.c, heap_4.c и heap_5.c, которые находятся в каталоге Source/Portable/MemMang. Пользователи могут добавлять свои собственные реализации по мере необходимости, но хотя бы одна из этих реализаций должна быть включена при компиляции исходного текста FreeRTOS в приложении.

    Ниже приводится простое описание каждой из этих реализаций.

  • heap_1.c

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

  • heap_2.c

    heap_2 использует алгоритм наибольшего соответсвия (best-fit) для выделения памяти, и пространство, которое больше не используется, освобождается для дальнейшего использования. Он не объединяет свободные места в один блок перед перераспределением. Эта схема может быть использована, когда во время выполнения приложения происходит многократное удаление и создание задач или других компонентов RTOS. Не рекомендуется использовать эту схему, если освобождаемые и перераспределяемые блоки памяти имеют произвольный размер, так как это может привести к фрагментации памяти. Кроме того, распределение не является детерминированным, но оно более эффективно, чем реализация malloc в языке Си.

  • heap_3.c

    heap_3 - это простая, потокобезопасная обертка вокруг стандартных функций malloc() и free() языка Си. Эта схема требует, чтобы компоновщик настроил кучу, а библиотека компилятора предоставила функции malloc() и free(). Она не является детерминированной и может привести к увеличению размера кода ядра.

  • heap_4.c

    heap_4 использует "первый подходящий" алгоритм (first-fit) для выделения памяти. В отличие от heap_2, он объединяет соседние свободные пространства в более крупный блок, а затем выделяет память; он включает алгоритм коалесценции, который поддерживает эту возможность. Эта схема может быть использована в системах, требующих многократного создания и удаления задач и других компонентов. Поскольку эта реализация объединяет области памяти в более крупные блоки памяти, она с меньшей вероятностью приведет к неправильной фрагментации памяти.

  • heap_5.c

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

  • Более подробные объяснения и примеры использования вышеперечисленных схем можно найти в руководстве FreeRTOS.

    Задачи

    Задачи - это базовые компоненты FreeRTOS. Они позволяют разработчикам приложений определять конкретные части функциональности, которые должны быть выполнены в определенное время выполнения приложения. Внутри приложения может быть определено любое количество задач. Задачи можно понимать как небольшие подпрограммы, которые доступны для выполнения в течение всего времени работы основного приложения.

    Планировщик (scheduler) RTOS отвечает за контроль над тем, какая задача должна быть выполнена в любой момент времени. В одноядерных системах только одна задача может быть активна в приложении в любой момент времени. Поэтому планировщик также отвечает за безопасное включение и выключение каждой задачи, а также за сохранение состояния, чтобы при повторном включении каждая задача возвращалась в свое предшествующее состояние. Это достигается планировщиком FreeRTOS за счёт ведения индивидуального стека для каждой задачи.

    Задачи: состояния

    Задача может находиться в одном из следующих четырёх состояний:

  • Готова к выполнению.

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

  • Выполняется.

    В этом состоянии задача выполняется на процессоре. Если система имеет только одно ядро, то в каждый момент времени может выполняться только одна задача.

  • Заблокирована.

    В этом состоянии задача не готова к выполнению, так как ожидает входных данных от внешних источников, других задач или временных событий (например, события таймера или задержки). Для каждой заблокированной задачи существует тайм-аут, по достижении которого задача переходит из заблокированного состояния в состояние готовности. После истечения тайм-аута задаче не нужно ждать наступления события, которое её блокировало.

  • Приостановлена.

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

  • Задачи: приоритеты

    Задачам можно назначать приоритеты по мере необходимости. FreeRTOS позволяет пользователю определить переменное количество уровней приоритетов. Уровни начинаются с 0, а максимальный уровень определяется в файле FreeRTOSConfig.h. Это максимальное значение должно быть разумным, чтобы минимизировать использование оперативной памяти.

    Планирование задач осуществляется планировщиком. Планировщик гарантирует, что задачи в состоянии готовности с более высоким приоритетом будут выполняться перед задачами с более низким приоритетом, которые также находятся в состоянии готовности. FreeRTOS может быть настроена на выполнение задач с одинаковым приоритетом в режиме "нарезания времени" (time slicing), для чего в конфигурационном файле задается параметр configUSE_TIME_SLICING. Разделение между задачами с равным приоритетом осуществляется с помощью схемы арбитража round-robin.

    Задачи: реализация

    Задачи создаются с помощью функции xTaskCreate() или xTaskCreateStatic() и могут быть удалены с помощью функции xTaskDelete().

    Параметры могут быть переданы в задачу для дальнейшей обработки с помощью указателя параметров.

    Примерная структура реализации задачи представлена ниже.

    Шаг 1: Создание двух задач

    xTaskCreate( prvQueueReceiveTask, "Rx", configMINIMAL_STACK_SIZE * 2U, NULL, mainQUEUE_RECEIVE_TASK_PRIORITY, NULL );
    xTaskCreate( prvQueueSendTask, "Tx", configMINIMAL_STACK_SIZE * 2U, NULL, mainQUEUE_SEND_TASK_PRIORITY, NULL ); 

    Шаг 2: Определение задачи 1 (prvQueueReceiveTask)

    static void prvQueueReceiveTask( void *pvParameters )
    {
         unsigned long ulReceivedValue;
         const unsigned long ulExpectedValue = 100UL;
         const char * const pcMessage1 = "Blink1";
         const char * const pcMessage2 = "Blink2";
         const char * const pcFailMessage = "Unexpected value received\r\n";
    
         int f = 1;
    
         /* Remove compiler warning about unused parameter. */
         ( void ) pvParameters;
    
         for( ;; )
         {....
    

    Очереди

    Очереди - основной механизм межзадачного взаимодействия. Задачи могут использовать их для обмена информацией друг с другом. Очереди реализованы как потокобезопасные FIFO (first in first out). Задачи добавляют информацию в конец очереди, а другие задачи, которым нужны данные из очереди, забирают их из начала и обрабатывают. При необходимости задачи также могут перемещать данные не в конец очереди, а в её начало.

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

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

    Очереди: доступ из нескольких задач

    В очередях может быть несколько задач, которые пишут в них или читают из них. Обычно в очередь записывают несколько задач, и реже из нее читают несколько задач.

    Очереди: механизм блокировки и разблокировки

    Когда задача пытается читать из пустой очереди, она переходит в состояние "заблокирована", пока либо данные не станут доступны в очереди, либо не будет достигнут тайм-аут блокировки.

    Когда задача пытается записать данные в заполненную очередь, она переходит в состояние "заблокирована" до тех пор, пока в очереди не освободится место или не будет достигнут тайм-аут блокировки.

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

    Если несколько задач блокируются на одной и той же очереди, то задача с наивысшим приоритетом будет разблокирована первой.

    Очереди: реализация

    В следующем примере показано, как можно реализовать и использовать очереди между двумя задачами.

    Шаг 1. Создание очереди

    /* Create the queue. */
    
    xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );
    

    Шаг 2: Использование очереди

    /* Send a value to the queue, causing the task receiving this data from
    the queue to unblock and toggle the LED. 0 is used as the block time so
    that the sending operation will not block; it shouldn't need to block, as
    the queue should always be empty at this point in the code. */
    
    xQueueSend( xQueue, ulValueToSend, 0U );
    

    Семафоры и мьютексы

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

    Прямые уведомления о задачах

    Прямые уведомления о задачах (direct task notifications) - это события, отправляемые непосредственно задаче без промежуточного механизма, такого как очередь или семафор. Это ускоряет обмен данными и занимает гораздо меньше места в памяти. Задача блокируется, когда в массиве событий уведомления установлен бит уведомления. Заблокировать задачу может только одно уведомление; если бы произошло другое событие, оно не повлияло бы на состояние задачи.

    Буферы потоков и сообщений

    Потоковые буферы предлагают механизм связи "один к одному" в следующих случаях:

  • связь между задачами;
  • связь между прерываниями и задачами.
  • Эти буферы оптимизированы для сценариев однократной записи и однократного чтения. Буферы потоков способны передавать байты, а буферы сообщений - дискретные сообщения переменного размера. Буферы сообщений строятся поверх буферов потоков.

    Эти буферы очень полезны для следующих типов коммуникационных сценариев:

  • передача данных из подпрограммы обслуживания прерываний в задачу;
  • передача данных от одного ядра микроконтроллера к другому на двухъядерных процессорах.
  • Данные передаются посредством копирования, то есть они копируются в буфер отправителем и выводятся из буфера операцией чтения.

    Таймеры

    Таймеры могут быть реализованы программно в RTOS, поэтому их также можно назвать программными таймерами. Они не используют аппаратные ресурсы и не потребляют процессорное время. Таймер позволяет запускать задачи или события, которые должны произойти в определенный момент в будущем. Будущее время выполнения контролируется настройками таймера. Задача, которая должна быть выполнена, называется функцией обратного вызова таймера. Функция обратного вызова (callback) таймера выполняется по истечении времени таймера или периода таймера.

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

    Таймеры: соображения эффективности при реализации программных таймеров

    Функциональность программного таймера, в общем, легко реализовать, но трудно реализовать эффективно.

    Реализация таймера в RTOS обладает следующими свойствами:

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

    Таймеры: важная информация о написании функций обратного вызова таймера

    Функции обратного вызова таймера выполняются в контексте задачи обслуживания таймера, поэтому важно, чтобы функции обратного вызова таймера никогда не пытались блокировать. Например, функция обратного вызова таймера не должна вызывать vTaskDelay() или vTaskDelayUntil(), а также не должна указывать ненулевое время блокировки при обращении к очереди или семафору.

    Таймеры: типы

    Два типа таймеров могут быть определены и использованы в приложении:

  • Однократные таймеры.

    Однократный таймер выполняется только один раз. По истечении срока действия таймера его обратный вызов вызывается и выполняется один раз.

  • Таймеры автозагрузки.

    Таймер автозагрузки выполняется неограниченное время, пока работает приложение. Каждый раз, когда таймер истекает, выполняется обратный вызов, и таймер сбрасывается; таймер снова работает до следующего истечения срока его действия, что приводит к выполнению обратного вызова. Этот процесс повторяется, что приводит к периодическому выполнению обратного вызова.

  • Создание FreeRTOS приложений

    С чего начать

    Лучше всего начать создание нового приложения, использующего FreeRTOS, с демонстрационного приложения для выбранного процессора. Рекомендуется модифицировать демо-версию в соответствии с текущими требованиями. Это обеспечит хорошую отправную точку для приложения и устранит многие проблемы портирования (porting), которые могут возникнуть при создании нового приложения с использованием FreeRTOS.

    Затем разработчик должен указать следующую предварительную информацию, необходимую для создания чистого приложения RTOS:

  • Количество требуемых задач

    Каждому приложению потребуется управлять различными частями функциональности в разные моменты времени. Эти функциональные компоненты называются задачами; перед созданием приложения необходимо понять и определить необходимое количество задач для системы.

  • Функциональность каждой задачи

    Функциональность каждой задачи также должна быть определена, понята и подробно описана.

  • Зависимость между задачами

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

  • Механизм связи между задачами с зависимостями

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

  • Прерывания и зависимости от внешних событий

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

  • Ограничения памяти

    Ограничения памяти системы необходимо понимать и определять, чтобы гарантировать, что система будет работать эффективно.

  • Требования к производительности и приоритету для каждой задачи в системе

    Наконец, перед внедрением приложения следует указать требования к производительности для каждой задачи, а также порядок приоритета среди задач.

  • После перечисления приведенных выше деталей пользователь может начать со следующих шагов по реализации приложения FreeRTOS.

    Шаг 1: Настройка потока инструментов для контроллера

    В качестве первого шага настройте поток инструментов (tool flow) для контроллера, на котором будет выполняться приложение RTOS. Используя процесс установки, запустите базовый тест Hello world, чтобы убедиться в следующем.

  • Приложение написано.
  • Необходимый стартовый код для контроллера, файлы компоновщика, файлы конфигурации компоновщика и другие связанные файлы уже готовы.
  • Приложение компилируется в тулчейне (toolchain) для выбранного контроллера.
  • Пользователь может запустить приложение на плате или эмулировать функциональность контроллера для проверки программного обеспечения.
  • В качестве альтернативы пользователь может выбрать демонстрационное приложение FreeRTOS и запустить его в потоке инструментов, чтобы убедиться, что установка выполнена правильно, а затем использовать его в качестве отправной точки для разработки приложения.

    Шаг 2: Включение исходных файлов FreeRTOS

    Необходимые файлы

    Ниже перечислены основные файлы, которые должны быть включены в любое приложение FreeRTOS:

  • FreeRTOS/Source/tasks.c
  • FreeRTOS/Source/queue.c
  • FreeRTOS/Source/list.c
  • FreeRTOS/Source/portable/[compiler]/[architecture]/port.c, где [compiler] - используемый компилятор, [architecture] - тип используемой архитектуры
  • FreeRTOS/Source/portable/MemMang/heap_x.c, где x - 1, 2, 3, 4 или 5
  • Если каталог, содержащий файл port.c, также содержит файл на языке ассемблера, то файл на языке ассемблера также должен быть включен.

    Необязательные файлы

  • Если требуется функциональность программного таймера, добавьте FreeRTOS/Source/timers.c в список исходных файлов проекта.
  • Если требуется функциональность группы событий, добавьте FreeRTOS/Source/event_group.c в список исходных файлов проекта.
  • Если требуется поток или буфер сообщений, добавьте FreeRTOS/Source/stream_buffer.c в список исходных файлов проекта.
  • Существует также функциональность сопрограмм (или корутин, coroutines), но её не рекомендуется использовать для новых разработок (эта функциональность устарела).
  • Шаг 3: Включение необходимых заголовочных файлов RTOS

    Следующие каталоги должны быть частью пути include сценария компиляции, чтобы компилятор мог найти заголовочные файлы RTOS:

  • FreeRTOS/Source/include
  • FreeRTOS/Source/portable/[compiler]/[architecture]
  • Директория, содержащая FreeRTOSConfig.h
  • В зависимости от того, на какой процессор портируется RTOS, эти пути могут потребоваться и в include пути включения ассемблера.

    Шаг 4: Обновление настроек файла конфигурации FreeRTOS

    Каждый проект FreeRTOS требует наличия файла конфигурации FreeRTOSConfig.h. Это файл, который определяет настройки для ядра RTOS, подстраивая ядро под конкретное создаваемое приложение.

    Этот файл зависит от пользователя или приложения и должен быть размещен в области кода приложения, а не вместе с исходным кодом ядра.

    Подробнее о различных настройках, доступных в этом файле, можно ознакомиться в документации FreeRTOS.

    Если в ваш проект включена куча heap_1, heap_2, heap_4 или heap_5, то значение параметра configTOTAL_HEAP_SIZE будет определять размер кучи FreeRTOS. Если для configTOTAL_HEAP_SIZE задано слишком большое значение, приложение не будет связываться, поэтому нужно устанавливать разумный размер кучи.

    Параметр configMINIMAL_STACK_SIZE определяет размер стека, используемого бездействующей задачей. Если для configMINIMAL_STACK_SIZE установлено слишком малое значение, бездействующие задачи будут генерировать переполнение стека. Рекомендуется скопировать параметр configMINIMAL_STACK_SIZE из официальной демонстрации FreeRTOS, предоставленной для архитектуры микроконтроллера, используемой приложением. Однако некоторые демонстрационные проекты не были обновлены и могут не иметь всех необходимых параметров конфигурации; в этих случаях пользователь должен добавить их вручную по мере необходимости.

    Шаг 5: Установка необходимых векторов прерывания

    Каждый порт RTOS использует как минимум один таймер. Он используется для генерации периодического тикового прерывания. В зависимости от порта могут потребоваться дополнительные таймеры для управления переключением контекста и других связанных с этим задач. Прерывания, которые требуются RTOS, обслуживаются исходным файлом RTOS port.c.

    В зависимости от порта и используемого компилятора, способ установки обработчиков прерываний также различается. Пользователи могут скопировать официальное демо-приложение для используемого порта из каталогов демо-версий RTOS.

    После выполнения всех вышеперечисленных шагов пользователь сможет скомпилировать приложение для выбранного им контроллера. Затем пользователь может улучшить свое приложение в соответствии со своими требованиями и запустить его на выбранном им оборудовании.

    Запуск демо-приложения

    Чтобы понять, как FreeRTOS работает с демонстрационным приложением, мы продемонстрируем, как запустить демонстрационное приложение с минимальной установкой программного обеспечения на вашем компьютере с Windows. Эту настройку можно использовать в качестве экспериментальной платформы, чтобы получить представление о FreeRTOS, прежде чем переходить к её использованию на выбранной вами встраиваемой платформе.

    Далее в этом курсе мы рассмотрим, как запускать FreeRTOS на других платформах, включая платформы RISC?V, и как моделировать ваши приложения без использования физической аппаратной системы.

    Настройка, необходимая для запуска демо-версии Windows, выглядит следующим образом:

  • Eclipse - можно скачать на сайте www.eclipse.org
  • Cygwin и GCC - можно скачать на сайте www.cygwin.com
  • FreeRTOS package - можно скачать на сайте www.freertos.org
  • Ниже приведены шаги, которые необходимо выполнить для запуска демо-приложения, после установки инструментов (Eclipse и Cygwin + GCC).

    Шаг 1: Импорт проекта в Eclipse

    Выполните следующие шаги, чтобы импортировать проект в Eclipse:

  • Откройте Eclipse.
  • В строке меню перейдите: File > Import > General > Existing Projects into Workspace.
  • Выберите Next, как показано на снимке экрана ниже:
  • В следующем окне выберите путь к демоверсии FreeRTOS, а затем выберите уже определенный там проект.
  • Нажмите Finish, чтобы открыть проект, как показано на снимке экран ниже:
  • После открытия проекта все связанные файлы отображаются в окне Project Explorer слева. Это показано на скриншоте ниже:
  • Шаг 2: Понимание демонстрационных файлов

    Теперь давайте посмотрим на доступные демонстрационные файлы.

  • В этой демонстрации есть три основных файла:
  • main.c - основной код для выполнения.
  • main_blinky.c - это простое двухзадачное приложение, которое дает представление о том, как могут быть построены приложения FreeRTOS.
  • main_full.c - более полный список приложений.
  • Давайте запустим пример main_blinky, установив define, как показано на скриншоте ниже, в файле main.c:
  • Затем скомпилируйте этот пример с помощью команды build в Eclipse (Eclipse будет использовать GCC от Cygwin для компиляции кода).
  • Шаг 3: Запуск приложения

    Теперь, когда ваше приложение готово к запуску в Windows через Eclipse, выполните следующие шаги:

  • Приложение можно запустить как внутри Eclipse, так и из командной строки:
  • Вы должны увидеть следующий вывод в консоли Eclipse:
  • Запустите его из интерпретатора Cygwin или командной строки Windows:
  • Перейдите в место, где находятся файлы FreeRTOS: \FreeRTOS\FreeRTOS\Demo\WIN32-MingW\Debug.
  • Запустите RTOSDemo.exe
  • Вы должны увидеть следующий результат:
  • Поздравляем! Вы запустили свое первое приложение FreeRTOS!

    Демо-видео

    Примечание переводчика

    Представленное в курсе демонстрационное видео фактически повторяет шаги, описанные в предыдущем разделе. Ниже приведён транскрипт этого видео с кадрами его основных моментов.

    Шаг первый - импортировать проект. Откройте из меню "File", "Import projects" и выберите имя проекта. Нажмите "Finish". Это приведет к импорту проекта в Eclipse.

    Теперь вы можете собрать проект. Перейдите в раздел "Project" и нажмите "Clean"; таким образом, все существующие файлы будут очищены.

    Затем выполните команду "Build All".

    После выполнения команды "Build all" компилятор скомпилирует все необходимые файлы в проект, и вы получите исполняемый файл.

    Видите, компилятор компилирует необходимый файл. И в конце компиляции вы можете увидеть, что исполняемый файл создан. Теперь компиляция завершена. Процесс сборки завершен.

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

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

    Контрольные вопросы

  • Допускает ли лицензия, под которой распространяется FreeRTOS, безвозмездное копирование, изменение и распространение ПО?
  • Как модульность влияет на разработку программного обеспечения для встраиваемых систем?
  • Почему важно использовать абстракцию информации о времени при разработке ПО для встраиваемых систем?
  • Каким образом управление временем простоя системы влияет на эффективность работы встраиваемой системы?
  • Где в дистрибутиве находятся файлы, специфичные для конкретных компиляторов?
  • За что отвечает файл FreeRTOSConfig.h?
  • Правда ли, что динамическое распределение предоставляет больший контроль над управлением памятью?
  • Какая из пяти схем управления памятью, определённых FreeRTOS, скорее приведёт к фрагментации?
  • За что отвечает планировщик FreeRTOS?
  • Что такое задача в контексте FreeRTOS?
  • Какие механизмы межзадачного взаимодействия доступны во FreeRTOS?
  • Какие шаги нужно выполнить для создания нового приложения с использованием FreeRTOS?
  • Какие файлы необходимо включить в проект для работы с FreeRTOS?
  • Какие настройки необходимо обновить в файле FreeRTOSConfig.h для нового проекта?
  • Страницы:

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

    Далее мы объясним различные компоненты FreeRTOS и то, как они работают и взаимодействуют друг с другом, чтобы обеспечить инфраструктуру для создания встраиваемых приложений. После понимания процесса создания приложений мы завершим лекцию демонстрацией запуска примера приложения в симуляторе QEMU.

    Ознакомившись с этой лекцией, читатель сможет:

  • понимать, что такое FreeRTOS;
  • описать особенности FreeRTOS;
  • объяснить содержание и структуру дистрибутива FreeRTOS;
  • перечислить компоненты FreeRTOS и объяснить, как они работают;
  • создавать приложения на базе FreeRTOS;
  • запускать пример приложения на QEMU.
  • Презентация по лекции

    Введение во FreeRTOS

    Что такое FreeRTOS?

    FreeRTOS - одна из наиболее часто используемых операционных систем реального времени (RTOS), распространяемая под открытой лицензией MIT. FreeRTOS - это RTOS, ориентированная на микроконтроллеры и небольшие микропроцессоры. Она включает ядро и растущий набор библиотек IoT, подходящих для использования во всех отраслях промышленности. FreeRTOS создана с акцентом на надежность и простоту использования.

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

    Требования приложений

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

    Технические требования

    Модульность

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

    Абстрагирование информации о времени

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

    Управление питанием

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

    Гибкое управление прерываниями

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

    Требования к смешанной обработке

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

    Управление временем простоя системы

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

    Требования к обслуживанию и эффективности

    Тестируемость

    Одним из ключевых аспектов при разработке системы является возможность её обслуживания и быстрого и эффективного устранения любых поломок. Для обеспечения этого требования важно иметь возможность тестирования на любые потенциальные сбои во время разработки системы, а также воспроизведения любых сбоев, выявленных во время эксплуатации системы. Поэтому, когда система разрабатывается, тестируемость должна быть встроенной частью дизайна, включая определение того, что должно быть протестировано и как это должно быть протестировано.

    Удобство обслуживания и расширяемость

    Система должна быть разработана таким образом, чтобы её можно было легко обслуживать как в полевых условиях, так и вне их, как с точки зрения аппаратного, так и программного обеспечения. Расширяемость также является ключевым фактором; система должна быть разработана таким образом, чтобы упростить добавление функций после завершения первоначальной версии системы.

    Разработка приложений в команде

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

    Повторное использование кода

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

    Эффективность кода

    Наконец, код должен быть разработан таким образом, чтобы эффективно использовать память, и разработчик должен помнить об ограничениях производительности небольших контроллеров, на которых строятся встраиваемые системы.

    Особенности FreeRTOS

    Ниже перечислены стандартные возможности FreeRTOS (взятые из справочного руководства FreeRTOS):

  • вытесняющие или совместные операции;
  • очень гибкое назначение приоритетов задач;
  • гибкий, быстрый и легкий механизм оповещения о задачах;
  • очереди;
  • бинарные семафоры;
  • счётные семафоры;
  • мьютексы;
  • рекурсивные мьютексы;
  • программные таймеры;
  • группы событий;
  • функции перехвата, вызываемые прерыванием тика (tick hook functions);
  • функции перехвата, вызываемые задачей ожидания (idle hook functions);
  • проверка переполнения стека;
  • запись трассировки;
  • сбор статистики выполнения задач;
  • дополнительное коммерческое лицензирование и поддержка;
  • модель полной вложенности прерываний (full interrupt nesting model) для некоторых архитектур;
  • таймер без прерываний (прерывания таймера не происходят через регулярные промежутки времени, а доставляются только по мере необходимости) для приложений с чрезвычайно низким энергопотреблением;
  • программно управляемый стек прерываний, когда это необходимо (это может помочь сэкономить оперативную память).
  • Содержимое и структура дистрибутива FreeRTOS

    FreeRTOS распространяется через Git-репозиторий в виде одного сжатого zip-файла со всем исходным кодом, поэтому пользователи могут свободно использовать его в своих приложениях, а также при необходимости вносить в код любые изменения. Дистрибутив также содержит демонстрационные приложения и демоверсии портов (ports) для различных контроллеров, поддерживаемых FreeRTOS. Эти демоверсии помогают пользователям FreeRTOS быстро адаптировать её к своим требованиям. Многие поставщики микроконтроллеров поставляют RTOS как часть своего набора инструментов, что еще больше облегчает разработку с FreeRTOS.

    Структура каталогов дистрибутива FreeRTOS

    Последнюю версию FreeRTOS можно загрузить с веб-сайта FreeRTOS. FreeRTOS также размещен на GitHub, и пользователи могут получить последнюю версию исходного кода из каталога Git.

  • FreeRTOS - содержит файлы, относящиеся к FreeRTOS
  • Demo - содержит демоверсии портов
  • License - содержит лицензионные файлы
  • Source - содержит исходный код FreeRTOS
  • Test - содержит тесты
  • FreeRTOS-Plus - содержит файлы FreeRTOS и расширенные библиотеки приложений
  • Demo - содержит демонстрационные версии приложений FreeRTOS-Plus
  • Source - содержит исходный код библиотек FreeRTOS-Plus
  • Test - содержит тесты приложений FreeRTOS-Plus
  • ThirdParty - содержит сторонние контрибуции (third party contributions)
  • Содержимое дистрибутива

    Ключевая часть ядра (core of the kernel) FreeRTOS содержится в трёх файлах, которые находятся в подкаталоге FreeRTOS\FreeRTOS\Source. Это файлы tasks.c, queue.c и lists.c. Для простых приложений этих трёх файлов достаточно. Папка Source содержит подпапку include, в которой находятся файлы include, необходимые для установки.

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

    Каталог Source также содержит подкаталог portable, который содержит подкаталоги с файлами, специфичными для определенных компиляторов и программных инструментов. Код, находящийся в этих папках, может быть использован в качестве отправной точки для создания портов в соответствии с требованиями пользователя.

    Папка Demo содержит примеры приложений, портированных на конкретные контроллеры. Пользователи, планирующие использовать контроллер определенного типа, могут использовать один или несколько примеров, представленных в этой папке, в качестве основы для создания приложений для своего контроллера.

    Компоненты FreeRTOS и их применение

    Ключевые компоненты FreeRTOS

    Ниже перечислены ключевые компоненты FreeRTOS:

  • управление памятью;
  • задачи;
  • очереди, семафоры и мьютексы;
  • прямые уведомления о задачах;
  • буферы потоков и сообщений;
  • таймеры.
  • Мы опишем их более подробно в последующих разделах.

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

    Все возможности FreeRTOS управляются из файла конфигурации FreeRTOS, FreeRTOSConfig.h. Каждое приложение должно иметь этот файл как часть системы. Он содержит параметры конфигурации для включения функций FreeRTOS, необходимых для данного приложения. Образец конфигурационного файла может быть скопирован из демонстрационного порта, который лучше всего подходит для контроллера, используемого для данного приложения.

    Управление памятью: методы распределения памяти

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

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

    Схема распределения памяти во FreeRTOS по умолчанию динамическая. В случаях, когда разработчику приложения требуется больший контроль над управлением памятью для определенных компонентов приложения, он может выбрать статическое распределение памяти для этих задач. FreeRTOS предоставляет отдельный набор функций для компонентов, которым требуется статическое распределение памяти. Эти функции доступны, когда configSUPPORT_STATIC_ALLOCATION имеет значение 1 в файле настроек конфигурации.

    Ниже перечислены некоторые преимущества использования динамического распределения памяти (адаптировано из руководства пользователя FreeRTOS):

  • при создании объекта требуется меньше параметров функции;
  • выделение памяти происходит автоматически, в рамках функций API RTOS;
  • разработчику приложения не нужно заботиться о выделении памяти;
  • оперативная память, используемая объектом RTOS, может быть повторно использована при удалении объекта, что потенциально уменьшает максимальный объем оперативной памяти приложения;
  • функции API RTOS позволяют возвращать информацию об использовании кучи, что позволяет оптимизировать её размер.
  • Ниже перечислены некоторые преимущества использования статического распределения памяти (адаптировано из руководства пользователя FreeRTOS):

  • объекты RTOS могут быть размещены в определенных местах памяти;
  • максимальный объем оперативной памяти может быть определен во время компоновки, а не во время выполнения;
  • разработчику приложения не нужно заботиться о корректной обработке сбоев при выделении памяти;
  • это позволяет использовать RTOS в приложениях, которые просто не допускают динамического распределения памяти (хотя FreeRTOS включает схемы распределения, которые могут преодолеть большинство возражений).
  • Управление памятью

    Управление памятью: схемы управления памятью во FreeRTOS

    FreeRTOS определяет пять схем управления памятью. Они содержатся в отдельных файлах: heap_1.c, heap_2.c, heap_3.c, heap_4.c и heap_5.c, которые находятся в каталоге Source/Portable/MemMang. Пользователи могут добавлять свои собственные реализации по мере необходимости, но хотя бы одна из этих реализаций должна быть включена при компиляции исходного текста FreeRTOS в приложении.

    Ниже приводится простое описание каждой из этих реализаций.

  • heap_1.c

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

  • heap_2.c

    heap_2 использует алгоритм наибольшего соответсвия (best-fit) для выделения памяти, и пространство, которое больше не используется, освобождается для дальнейшего использования. Он не объединяет свободные места в один блок перед перераспределением. Эта схема может быть использована, когда во время выполнения приложения происходит многократное удаление и создание задач или других компонентов RTOS. Не рекомендуется использовать эту схему, если освобождаемые и перераспределяемые блоки памяти имеют произвольный размер, так как это может привести к фрагментации памяти. Кроме того, распределение не является детерминированным, но оно более эффективно, чем реализация malloc в языке Си.

  • heap_3.c

    heap_3 - это простая, потокобезопасная обертка вокруг стандартных функций malloc() и free() языка Си. Эта схема требует, чтобы компоновщик настроил кучу, а библиотека компилятора предоставила функции malloc() и free(). Она не является детерминированной и может привести к увеличению размера кода ядра.

  • heap_4.c

    heap_4 использует "первый подходящий" алгоритм (first-fit) для выделения памяти. В отличие от heap_2, он объединяет соседние свободные пространства в более крупный блок, а затем выделяет память; он включает алгоритм коалесценции, который поддерживает эту возможность. Эта схема может быть использована в системах, требующих многократного создания и удаления задач и других компонентов. Поскольку эта реализация объединяет области памяти в более крупные блоки памяти, она с меньшей вероятностью приведет к неправильной фрагментации памяти.

  • heap_5.c

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

  • Более подробные объяснения и примеры использования вышеперечисленных схем можно найти в руководстве FreeRTOS.

    Задачи

    Задачи - это базовые компоненты FreeRTOS. Они позволяют разработчикам приложений определять конкретные части функциональности, которые должны быть выполнены в определенное время выполнения приложения. Внутри приложения может быть определено любое количество задач. Задачи можно понимать как небольшие подпрограммы, которые доступны для выполнения в течение всего времени работы основного приложения.

    Планировщик (scheduler) RTOS отвечает за контроль над тем, какая задача должна быть выполнена в любой момент времени. В одноядерных системах только одна задача может быть активна в приложении в любой момент времени. Поэтому планировщик также отвечает за безопасное включение и выключение каждой задачи, а также за сохранение состояния, чтобы при повторном включении каждая задача возвращалась в свое предшествующее состояние. Это достигается планировщиком FreeRTOS за счёт ведения индивидуального стека для каждой задачи.

    Задачи: состояния

    Задача может находиться в одном из следующих четырёх состояний:

  • Готова к выполнению.

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

  • Выполняется.

    В этом состоянии задача выполняется на процессоре. Если система имеет только одно ядро, то в каждый момент времени может выполняться только одна задача.

  • Заблокирована.

    В этом состоянии задача не готова к выполнению, так как ожидает входных данных от внешних источников, других задач или временных событий (например, события таймера или задержки). Для каждой заблокированной задачи существует тайм-аут, по достижении которого задача переходит из заблокированного состояния в состояние готовности. После истечения тайм-аута задаче не нужно ждать наступления события, которое её блокировало.

  • Приостановлена.

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

  • Задачи: приоритеты

    Задачам можно назначать приоритеты по мере необходимости. FreeRTOS позволяет пользователю определить переменное количество уровней приоритетов. Уровни начинаются с 0, а максимальный уровень определяется в файле FreeRTOSConfig.h. Это максимальное значение должно быть разумным, чтобы минимизировать использование оперативной памяти.

    Планирование задач осуществляется планировщиком. Планировщик гарантирует, что задачи в состоянии готовности с более высоким приоритетом будут выполняться перед задачами с более низким приоритетом, которые также находятся в состоянии готовности. FreeRTOS может быть настроена на выполнение задач с одинаковым приоритетом в режиме "нарезания времени" (time slicing), для чего в конфигурационном файле задается параметр configUSE_TIME_SLICING. Разделение между задачами с равным приоритетом осуществляется с помощью схемы арбитража round-robin.

    Задачи: реализация

    Задачи создаются с помощью функции xTaskCreate() или xTaskCreateStatic() и могут быть удалены с помощью функции xTaskDelete().

    Параметры могут быть переданы в задачу для дальнейшей обработки с помощью указателя параметров.

    Примерная структура реализации задачи представлена ниже.

    Шаг 1: Создание двух задач

    xTaskCreate( prvQueueReceiveTask, "Rx", configMINIMAL_STACK_SIZE * 2U, NULL, mainQUEUE_RECEIVE_TASK_PRIORITY, NULL );
    xTaskCreate( prvQueueSendTask, "Tx", configMINIMAL_STACK_SIZE * 2U, NULL, mainQUEUE_SEND_TASK_PRIORITY, NULL ); 

    Шаг 2: Определение задачи 1 (prvQueueReceiveTask)

    static void prvQueueReceiveTask( void *pvParameters )
    {
         unsigned long ulReceivedValue;
         const unsigned long ulExpectedValue = 100UL;
         const char * const pcMessage1 = "Blink1";
         const char * const pcMessage2 = "Blink2";
         const char * const pcFailMessage = "Unexpected value received\r\n";
    
         int f = 1;
    
         /* Remove compiler warning about unused parameter. */
         ( void ) pvParameters;
    
         for( ;; )
         {....
    

    Очереди

    Очереди - основной механизм межзадачного взаимодействия. Задачи могут использовать их для обмена информацией друг с другом. Очереди реализованы как потокобезопасные FIFO (first in first out). Задачи добавляют информацию в конец очереди, а другие задачи, которым нужны данные из очереди, забирают их из начала и обрабатывают. При необходимости задачи также могут перемещать данные не в конец очереди, а в её начало.

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

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

    Очереди: доступ из нескольких задач

    В очередях может быть несколько задач, которые пишут в них или читают из них. Обычно в очередь записывают несколько задач, и реже из нее читают несколько задач.

    Очереди: механизм блокировки и разблокировки

    Когда задача пытается читать из пустой очереди, она переходит в состояние "заблокирована", пока либо данные не станут доступны в очереди, либо не будет достигнут тайм-аут блокировки.

    Когда задача пытается записать данные в заполненную очередь, она переходит в состояние "заблокирована" до тех пор, пока в очереди не освободится место или не будет достигнут тайм-аут блокировки.

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

    Если несколько задач блокируются на одной и той же очереди, то задача с наивысшим приоритетом будет разблокирована первой.

    Очереди: реализация

    В следующем примере показано, как можно реализовать и использовать очереди между двумя задачами.

    Шаг 1. Создание очереди

    /* Create the queue. */
    
    xQueue = xQueueCreate( mainQUEUE_LENGTH, sizeof( uint32_t ) );
    

    Шаг 2: Использование очереди

    /* Send a value to the queue, causing the task receiving this data from
    the queue to unblock and toggle the LED. 0 is used as the block time so
    that the sending operation will not block; it shouldn't need to block, as
    the queue should always be empty at this point in the code. */
    
    xQueueSend( xQueue, ulValueToSend, 0U );
    

    Семафоры и мьютексы

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

    Прямые уведомления о задачах

    Прямые уведомления о задачах (direct task notifications) - это события, отправляемые непосредственно задаче без промежуточного механизма, такого как очередь или семафор. Это ускоряет обмен данными и занимает гораздо меньше места в памяти. Задача блокируется, когда в массиве событий уведомления установлен бит уведомления. Заблокировать задачу может только одно уведомление; если бы произошло другое событие, оно не повлияло бы на состояние задачи.

    Буферы потоков и сообщений

    Потоковые буферы предлагают механизм связи "один к одному" в следующих случаях:

  • связь между задачами;
  • связь между прерываниями и задачами.
  • Эти буферы оптимизированы для сценариев однократной записи и однократного чтения. Буферы потоков способны передавать байты, а буферы сообщений - дискретные сообщения переменного размера. Буферы сообщений строятся поверх буферов потоков.

    Эти буферы очень полезны для следующих типов коммуникационных сценариев:

  • передача данных из подпрограммы обслуживания прерываний в задачу;
  • передача данных от одного ядра микроконтроллера к другому на двухъядерных процессорах.
  • Данные передаются посредством копирования, то есть они копируются в буфер отправителем и выводятся из буфера операцией чтения.

    Таймеры

    Таймеры могут быть реализованы программно в RTOS, поэтому их также можно назвать программными таймерами. Они не используют аппаратные ресурсы и не потребляют процессорное время. Таймер позволяет запускать задачи или события, которые должны произойти в определенный момент в будущем. Будущее время выполнения контролируется настройками таймера. Задача, которая должна быть выполнена, называется функцией обратного вызова таймера. Функция обратного вызова (callback) таймера выполняется по истечении времени таймера или периода таймера.

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

    Таймеры: соображения эффективности при реализации программных таймеров

    Функциональность программного таймера, в общем, легко реализовать, но трудно реализовать эффективно.

    Реализация таймера в RTOS обладает следующими свойствами:

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

    Таймеры: важная информация о написании функций обратного вызова таймера

    Функции обратного вызова таймера выполняются в контексте задачи обслуживания таймера, поэтому важно, чтобы функции обратного вызова таймера никогда не пытались блокировать. Например, функция обратного вызова таймера не должна вызывать vTaskDelay() или vTaskDelayUntil(), а также не должна указывать ненулевое время блокировки при обращении к очереди или семафору.

    Таймеры: типы

    Два типа таймеров могут быть определены и использованы в приложении:

  • Однократные таймеры.

    Однократный таймер выполняется только один раз. По истечении срока действия таймера его обратный вызов вызывается и выполняется один раз.

  • Таймеры автозагрузки.

    Таймер автозагрузки выполняется неограниченное время, пока работает приложение. Каждый раз, когда таймер истекает, выполняется обратный вызов, и таймер сбрасывается; таймер снова работает до следующего истечения срока его действия, что приводит к выполнению обратного вызова. Этот процесс повторяется, что приводит к периодическому выполнению обратного вызова.

  • Создание FreeRTOS приложений

    С чего начать

    Лучше всего начать создание нового приложения, использующего FreeRTOS, с демонстрационного приложения для выбранного процессора. Рекомендуется модифицировать демо-версию в соответствии с текущими требованиями. Это обеспечит хорошую отправную точку для приложения и устранит многие проблемы портирования (porting), которые могут возникнуть при создании нового приложения с использованием FreeRTOS.

    Затем разработчик должен указать следующую предварительную информацию, необходимую для создания чистого приложения RTOS:

  • Количество требуемых задач

    Каждому приложению потребуется управлять различными частями функциональности в разные моменты времени. Эти функциональные компоненты называются задачами; перед созданием приложения необходимо понять и определить необходимое количество задач для системы.

  • Функциональность каждой задачи

    Функциональность каждой задачи также должна быть определена, понята и подробно описана.

  • Зависимость между задачами

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

  • Механизм связи между задачами с зависимостями

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

  • Прерывания и зависимости от внешних событий

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

  • Ограничения памяти

    Ограничения памяти системы необходимо понимать и определять, чтобы гарантировать, что система будет работать эффективно.

  • Требования к производительности и приоритету для каждой задачи в системе

    Наконец, перед внедрением приложения следует указать требования к производительности для каждой задачи, а также порядок приоритета среди задач.

  • После перечисления приведенных выше деталей пользователь может начать со следующих шагов по реализации приложения FreeRTOS.

    Шаг 1: Настройка потока инструментов для контроллера

    В качестве первого шага настройте поток инструментов (tool flow) для контроллера, на котором будет выполняться приложение RTOS. Используя процесс установки, запустите базовый тест Hello world, чтобы убедиться в следующем.

  • Приложение написано.
  • Необходимый стартовый код для контроллера, файлы компоновщика, файлы конфигурации компоновщика и другие связанные файлы уже готовы.
  • Приложение компилируется в тулчейне (toolchain) для выбранного контроллера.
  • Пользователь может запустить приложение на плате или эмулировать функциональность контроллера для проверки программного обеспечения.
  • В качестве альтернативы пользователь может выбрать демонстрационное приложение FreeRTOS и запустить его в потоке инструментов, чтобы убедиться, что установка выполнена правильно, а затем использовать его в качестве отправной точки для разработки приложения.

    Шаг 2: Включение исходных файлов FreeRTOS

    Необходимые файлы

    Ниже перечислены основные файлы, которые должны быть включены в любое приложение FreeRTOS:

  • FreeRTOS/Source/tasks.c
  • FreeRTOS/Source/queue.c
  • FreeRTOS/Source/list.c
  • FreeRTOS/Source/portable/[compiler]/[architecture]/port.c, где [compiler] - используемый компилятор, [architecture] - тип используемой архитектуры
  • FreeRTOS/Source/portable/MemMang/heap_x.c, где x - 1, 2, 3, 4 или 5
  • Если каталог, содержащий файл port.c, также содержит файл на языке ассемблера, то файл на языке ассемблера также должен быть включен.

    Необязательные файлы

  • Если требуется функциональность программного таймера, добавьте FreeRTOS/Source/timers.c в список исходных файлов проекта.
  • Если требуется функциональность группы событий, добавьте FreeRTOS/Source/event_group.c в список исходных файлов проекта.
  • Если требуется поток или буфер сообщений, добавьте FreeRTOS/Source/stream_buffer.c в список исходных файлов проекта.
  • Существует также функциональность сопрограмм (или корутин, coroutines), но её не рекомендуется использовать для новых разработок (эта функциональность устарела).
  • Шаг 3: Включение необходимых заголовочных файлов RTOS

    Следующие каталоги должны быть частью пути include сценария компиляции, чтобы компилятор мог найти заголовочные файлы RTOS:

  • FreeRTOS/Source/include
  • FreeRTOS/Source/portable/[compiler]/[architecture]
  • Директория, содержащая FreeRTOSConfig.h
  • В зависимости от того, на какой процессор портируется RTOS, эти пути могут потребоваться и в include пути включения ассемблера.

    Шаг 4: Обновление настроек файла конфигурации FreeRTOS

    Каждый проект FreeRTOS требует наличия файла конфигурации FreeRTOSConfig.h. Это файл, который определяет настройки для ядра RTOS, подстраивая ядро под конкретное создаваемое приложение.

    Этот файл зависит от пользователя или приложения и должен быть размещен в области кода приложения, а не вместе с исходным кодом ядра.

    Подробнее о различных настройках, доступных в этом файле, можно ознакомиться в документации FreeRTOS.

    Если в ваш проект включена куча heap_1, heap_2, heap_4 или heap_5, то значение параметра configTOTAL_HEAP_SIZE будет определять размер кучи FreeRTOS. Если для configTOTAL_HEAP_SIZE задано слишком большое значение, приложение не будет связываться, поэтому нужно устанавливать разумный размер кучи.

    Параметр configMINIMAL_STACK_SIZE определяет размер стека, используемого бездействующей задачей. Если для configMINIMAL_STACK_SIZE установлено слишком малое значение, бездействующие задачи будут генерировать переполнение стека. Рекомендуется скопировать параметр configMINIMAL_STACK_SIZE из официальной демонстрации FreeRTOS, предоставленной для архитектуры микроконтроллера, используемой приложением. Однако некоторые демонстрационные проекты не были обновлены и могут не иметь всех необходимых параметров конфигурации; в этих случаях пользователь должен добавить их вручную по мере необходимости.

    Шаг 5: Установка необходимых векторов прерывания

    Каждый порт RTOS использует как минимум один таймер. Он используется для генерации периодического тикового прерывания. В зависимости от порта могут потребоваться дополнительные таймеры для управления переключением контекста и других связанных с этим задач. Прерывания, которые требуются RTOS, обслуживаются исходным файлом RTOS port.c.

    В зависимости от порта и используемого компилятора, способ установки обработчиков прерываний также различается. Пользователи могут скопировать официальное демо-приложение для используемого порта из каталогов демо-версий RTOS.

    После выполнения всех вышеперечисленных шагов пользователь сможет скомпилировать приложение для выбранного им контроллера. Затем пользователь может улучшить свое приложение в соответствии со своими требованиями и запустить его на выбранном им оборудовании.

    Запуск демо-приложения

    Чтобы понять, как FreeRTOS работает с демонстрационным приложением, мы продемонстрируем, как запустить демонстрационное приложение с минимальной установкой программного обеспечения на вашем компьютере с Windows. Эту настройку можно использовать в качестве экспериментальной платформы, чтобы получить представление о FreeRTOS, прежде чем переходить к её использованию на выбранной вами встраиваемой платформе.

    Далее в этом курсе мы рассмотрим, как запускать FreeRTOS на других платформах, включая платформы RISC?V, и как моделировать ваши приложения без использования физической аппаратной системы.

    Настройка, необходимая для запуска демо-версии Windows, выглядит следующим образом:

  • Eclipse - можно скачать на сайте www.eclipse.org
  • Cygwin и GCC - можно скачать на сайте www.cygwin.com
  • FreeRTOS package - можно скачать на сайте www.freertos.org
  • Ниже приведены шаги, которые необходимо выполнить для запуска демо-приложения, после установки инструментов (Eclipse и Cygwin + GCC).

    Шаг 1: Импорт проекта в Eclipse

    Выполните следующие шаги, чтобы импортировать проект в Eclipse:

  • Откройте Eclipse.
  • В строке меню перейдите: File > Import > General > Existing Projects into Workspace.
  • Выберите Next, как показано на снимке экрана ниже:
  • В следующем окне выберите путь к демоверсии FreeRTOS, а затем выберите уже определенный там проект.
  • Нажмите Finish, чтобы открыть проект, как показано на снимке экран ниже:
  • После открытия проекта все связанные файлы отображаются в окне Project Explorer слева. Это показано на скриншоте ниже:
  • Шаг 2: Понимание демонстрационных файлов

    Теперь давайте посмотрим на доступные демонстрационные файлы.

  • В этой демонстрации есть три основных файла:
  • main.c - основной код для выполнения.
  • main_blinky.c - это простое двухзадачное приложение, которое дает представление о том, как могут быть построены приложения FreeRTOS.
  • main_full.c - более полный список приложений.
  • Давайте запустим пример main_blinky, установив define, как показано на скриншоте ниже, в файле main.c:
  • Затем скомпилируйте этот пример с помощью команды build в Eclipse (Eclipse будет использовать GCC от Cygwin для компиляции кода).
  • Шаг 3: Запуск приложения

    Теперь, когда ваше приложение готово к запуску в Windows через Eclipse, выполните следующие шаги:

  • Приложение можно запустить как внутри Eclipse, так и из командной строки:
  • Вы должны увидеть следующий вывод в консоли Eclipse:
  • Запустите его из интерпретатора Cygwin или командной строки Windows:
  • Перейдите в место, где находятся файлы FreeRTOS: \FreeRTOS\FreeRTOS\Demo\WIN32-MingW\Debug.
  • Запустите RTOSDemo.exe
  • Вы должны увидеть следующий результат:
  • Поздравляем! Вы запустили свое первое приложение FreeRTOS!

    Демо-видео

    Примечание переводчика

    Представленное в курсе демонстрационное видео фактически повторяет шаги, описанные в предыдущем разделе. Ниже приведён транскрипт этого видео с кадрами его основных моментов.

    Шаг первый - импортировать проект. Откройте из меню "File", "Import projects" и выберите имя проекта. Нажмите "Finish". Это приведет к импорту проекта в Eclipse.

    Теперь вы можете собрать проект. Перейдите в раздел "Project" и нажмите "Clean"; таким образом, все существующие файлы будут очищены.

    Затем выполните команду "Build All".

    После выполнения команды "Build all" компилятор скомпилирует все необходимые файлы в проект, и вы получите исполняемый файл.

    Видите, компилятор компилирует необходимый файл. И в конце компиляции вы можете увидеть, что исполняемый файл создан. Теперь компиляция завершена. Процесс сборки завершен.

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

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

    Контрольные вопросы

  • Допускает ли лицензия, под которой распространяется FreeRTOS, безвозмездное копирование, изменение и распространение ПО?
  • Как модульность влияет на разработку программного обеспечения для встраиваемых систем?
  • Почему важно использовать абстракцию информации о времени при разработке ПО для встраиваемых систем?
  • Каким образом управление временем простоя системы влияет на эффективность работы встраиваемой системы?
  • Где в дистрибутиве находятся файлы, специфичные для конкретных компиляторов?
  • За что отвечает файл FreeRTOSConfig.h?
  • Правда ли, что динамическое распределение предоставляет больший контроль над управлением памятью?
  • Какая из пяти схем управления памятью, определённых FreeRTOS, скорее приведёт к фрагментации?
  • За что отвечает планировщик FreeRTOS?
  • Что такое задача в контексте FreeRTOS?
  • Какие механизмы межзадачного взаимодействия доступны во FreeRTOS?
  • Какие шаги нужно выполнить для создания нового приложения с использованием FreeRTOS?
  • Какие файлы необходимо включить в проект для работы с FreeRTOS?
  • Какие настройки необходимо обновить в файле FreeRTOSConfig.h для нового проекта?
  • Вернуться к учебному плану