RT-Thread RTOS |
![]() |
Добавил(а) microsin | ||||||||||||||||||
Операционная система для микроконтроллеров RT-Thread начала свое развитие в 2006 году. Это открытая, нейтральная, основанная на широком комьюнити разработчиков операционная система реального времени (real-time operating system, RTOS). RT-Thread приветствует поддержку всех разработчиков, и если у вас есть любые идеи, советы по развитию или вопросы по использованию RT-Thread, то можете связаться с разработчиками RT-Thread, информация доступна на официальном сайте RT-Thread, Github, Twitter, LinkedIn, Youtube, Facebook, Medium. Вопросы можно публиковать в секции решения проблем (раздел issue) репозитория или форума RT-Thread. Website | Github | Twitter | LinkedIn | Youtube | Facebook | Medium Если вы заинтересованы в RT-Thread, хотите присоединиться к команде разработки RT-Thread, либо предоставить свой код, то посетите страничку Code Contribution Guide site:rt-thread.io. Основные среды разработки и компиляторы (IDE), поддерживаемые RT-Thread: MDK KEIL Скрипты и утилиты конфигурирования и компиляции работают на Python-скриптах SCons [2]. RT-Thread написана в основном на языке C и придерживается стандартов API POSIX [3], что упрощает понимание её интерфейса и портирование на различные архитектуры микроконтроллеров (MCU) и модули/платы разработчика на их основе. Разработчики стараются придерживаться объектно-ориентированных методов программирования при создании систем реального времени, что делает код элегантным, структурированным, модульным и легко адаптируемым под требоывания пользователя. У RT-Thread есть версии Standard и Nano. Для MCU-систем с ограниченными ресурсами версия ядра NANO потребует только 3 килобайта Flash и 1.2 килобайта RAM. Многофункциональные устройства IoT с большим объемом памяти могут использовать версию Standard, онлайн-утилиту управления пакетами и утилиты конфигурирования системы. Это упрощает применение богатого набора различных библиотек для создания таких сложных функций, как графический интерфейс в стиле Android со слайдинг-эффектами тачскрина, голосовое управление и т. д. [Архитектура RT-Thread] RT-Thread это не только ядро реального времени, но также и множество различных функциональных компонентов. Показанная на рисунке выше архитектура включает: RT-Thread kernel: слой ядра, основная часть RT-Thread, где реализованы объекты операционной системы, такие как модуль поддержки потоков (multi-threading) и распределение между ними процессорного времени (scheduling, планировщик), а также модули семафоров (semaphore), очередей сообщений (message queue), управление памятью, программные таймеры и т. д. Библиотеки libcpu и BSP (Chip Migration Related Files/Board Support Package) осуществляют привязку к конкретной аппаратуре и состоят из драйверов периферийных устройств и низкоуровневых подпрограмм поддержки особенностей портирования CPU. Components and Service Layer: слой компонентов и служб реализует промежуточный высокоуровневый интерфейс поверх RT-Thread kernel. Здесь представлены виртуальный файловые системы (virtual file system, VFS [4]), интерфейс командной строки FinSH [5], поддержка сетевых коммуникаций и протоколов (network frameworks) и многое другое. Описание некоторых других компонентов см. в статье [6]. RT-Thread software package: программный пакет общего назначения, предназначенный для запуска на основе RT-Thread устройств IoT различного назначения. Пакет включает в себя документацию, исходный код или библиотечные файлы. RT-Thread предоставляет открытую платформу для пакетов, доступных официально или от сторонних разработчиков. Пакеты составляют важную функциональную составляющую экосистемы RT-Thread, значительно сокращая время разработки конечных устройств. RT-Thread поддерживает более 370 программных пакетов. Примечание переводчика: к сожалению некоторые программные пакеты, такие как например стек BLE для микроконтроллеров BEKEN или библиотека OTA для загрузчика с поддержкой шифрования, могут поставляться без исходного кода (например, в виде файла библиотеки *.a). Исходный код для таких библиотек может быть предоставлен по дополнительному соглашению с разработчиками (например на платной основе). [Общая структура каталогов файлов RT-Thread] Пакет исходного кода RT-Thread содержит следующие директории:
RT-Thread портирована на больше чем 200 плат разработчика. Большинство библиотек BSP поддерживают популярные среды разработки (IDE), такие как MDK (Keil), IAR, и почти все BSP поддерживают компилятор GCC. Обычно также предоставляются готовые примеры приложений, на основе которых пользователи могут создать свой собственный проект. Каждый имеет унифицированную структуру каталогов и файлов с файлом README.md, содержащим базовое описание BSP, как можно быстро начать его использовать. [Поддерживаемые архитектуры] ARM Cortex-M0/M0+: чипы наподобие тех, что поставляет компания ST Microelectronics. [Поддерживаемые IDE и компиляторы] Основные компиляторы/среды разработки, поддерживаемые RT-Thread: RT-Thread Studio IDE (Windows, на основе Eclipse) [7]. [Env Tool] На ранних стадиях разработки команда RT-Thread также создала дополнительный инструмент конфигурирования Env. Это утилита работает на основе тексто-графического интерфейса TUI (Text-based user interface). Разработчики могут использовать утилиту Env для конфигурирования и создания проектов GCC, Keil MDK и IAR. См. также руководство пользователя [8] и вводное видео [9]. [Быстрый старт] Примеры основаны на платах STM32F103 BluePill [10] и Raspberry Pi Pico, см. репозитории [11, 12], вводное обучающее видео [13]. Симулятор. RT-Thread BSP может быть напрямую загружен и скомпилирован для любой поддерживаемой платы или чипа. Однако также есть возмож запуска симуляции RT-Thread на основе qemu-vexpress-a9 BSP, без использования аппаратного устройства. Симулятор работает на основе QEMU, что доступно на Windows и Ubuntu [14]. Исходный код и утилиты RT-Thread можно скачать на Github [15]. Env Tool. Утилита Env [8] может помочь в конфигурировании и создании шаблонов проектов (скрипты для Linux/MacOS/Windows). Встроенная система menuconfig [2] обеспечивает простое конфигурирование ядра, компонентов и программных пакетов [6]. На Windows также доступна графическая утилита конфигурирования проекта и управления пакетами RT-Thread. [Лицензия] RT-Thread это открытое ПО, лицензируемое под Apache License Version 2.0 since v3.1.1. License. Эту информацию лицензирования можно в основном увидеть в начале модулей кода: /* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
* ...
*/
Базовые вопросы Q1. Загрузил SDK, все в порядке, компиляция проходит успешно. Однако не могу запустить программу, в чем проблема? Если используете Keil + RT-Thread Env, то первое, что вам следует сделать после загрузки исходного кода, это пройти процесс конфигурирования через menuconfig. Если используете RT-Studio IDE, то нужно открыть Settings перед созданием проекта; пройдите через все элементы конфигурации, отмените ненужное, оставив только ядро (kernel). Сначала убедитесь, что система нормально запускается в минимальной конфигурации, используя для этого простейшую программу наподобие мигания светодиодом. После этого пошагово добавляйте нижележащие устройства или другие пакеты, проверяя работу на каждом шаге (menuconfig -> компиляция -> проверка). Q2. Загрузил SDK, все в порядке, компиляция проходит успешно. Однако программа показывает Hard Fault on Thread, в чем проблема? Сделайте все то же самое, что перечислено в ответе на Q1. Q3. Загрузил SDK, но есть проблемы с компиляцией. Сделайте все то же самое, что перечислено в ответе на Q1. Kernel Q1. Как правильно определить RT_NAME_MAX? Чем меньше RT_NAME_MAX, тем меньше будет использоваться памяти. Например, если существует 100 объектов kernel, и имя объекта занимает 8, то всего на это будет потрачено 800 байт. В структуре rt_object находится определение объекта, где есть определение имени длиной RT_NAME_MAX байт и две переменные типа rt_uint8_t. RT_NAME_MAX может быть определено как 2n + 2. Q2. RT_DEBUG Не включайте отладку ядра (kernel debugging), если не понимаете, необходимо ли это в вашей ситуации. Q3. Как правильно определить размер стека потока? Этот вопрос имеет большое отношение к приложениям. и если это только система с минимальным ядром (minimal kernel system), потоком ожидания (idle thread), без разрешенных прерываний и работающих программ, то 256 для стека достаточно. Если добавляется код программы, вместе с механизмами блокировки, очередей, передачи сообщений, то лучше размер стека потока увеличить до 1024 или даже больше (подбирается опытным путем). Q4. Как быстро вычислить значение, возвращаемое GET_PIN? Мы знаем, что группирование GPIO чипов обычно начинается с PA, потом идут PB, PC, PD, PE, ... PZ. Также каждая такая группа портов содержит либо 16 бит, либо 8 бит (соответствует 16 IO и 8 IO). Упрощенная формула для GET_PIN для 16-битных портов: (X - A) * 16 + n Т. е. для A10 получится 10. Для C9 получается 2*16+9=41. H1 будет 7*16+1=113. Для 8-битных портов формула будет: (X - A) * 8 + n. Q5. Не понимаю, чем отличаются hard timers, soft timers и hardware timers. RT-Thread kernel определяет программные таймеры (software timer). Они, в отличие от аппаратных таймеров (hardware timer), не требуют задействования отдельного периферийного устройства timer, как и для реализации разных других функций типа сравнение интервалов (comparison) и захват сигнала (capture). Программный таймер просто задает интервал времени таймаута, и запускает callback-функцию при истечении этого интервала. Программный таймер, определяемый RT-Thread, также делится на два типа "hard timer" и "soft timer". Первый тип выполняет свою callback-функцию в прерываниях SysTick. Этот тип используется для встроенных таймеров потока, и может использоваться также на уровне приложения, однако следует иметь в виду, что callback-функции вызываются из контекста прерываний. Второй тип, который работает в потоке, может использоваться, когда слой приложения не требует высокой точности отслеживания интервала времени. Однако следует отметить, что поток, который определяет таймер, и поток, который выполняет callback-функцию таймера, являются двумя разными потоками. Q6. Сколько памяти достаточно запросить для пула очереди сообщений (Message Queue)? Очередь сообщений может создаваться разными способами. rt_err_t rt_mq_init (rt_mq_t mq, const char *name, void *msgpool, rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag); rt_mq_t rt_mq_create (const char *name, rt_size_t msg_size, rt_size_t max_msgs, rt_uint8_t flag); Если вы создаете очередь сообщений с помощью rt_mq_create, то пул очереди сообщений будет вычислен автоматически по размеру тела сообщения msg_size и максимальной длине очереди max_msgs. Если для инициализации очереди сообщений используется rt_mq_init, то память пула msgpool для сообщений должна быть предоставлена пользователем. В этом случае следует обратить внимание на размер структуры pool_size. Необходимый объем пула памяти можно вычислить по формуле: (RT_ALIGN(msg_size, RT_ALIGN_SIZE) + sizeof(struct rt_mq_message*)) * max_msgs Здесь msg_size это размер тела сообщения, и max_msgs это максимальная емкость очереди сообщений. Q7. Замечания по использованию очереди сообщений (message queue). Хотя несколько API-функций наподобие rt_mq_send, rt_mq_urgent и rt_mq_recv имеют параметры размера size, обратите особое внимание, что передаете строго одинаковые значения аргумента, соответствующие msg_size, в rt_mq_init и rt_mq_create. Не следует менять размер параметра size. Т. е. не используйте очередь сообщений, чтобы напрямую посылать данные слишком большого размера. Q8. Объяснение смысла макроса INIT_xxx_EXPORT. Отличительная особенность RT-Thread в том, что в функции main не делается никакая начальная конфигурация, для этого существует отдельный поток. Другие потоки автоматически запускаются через INIT_APP_EXPORT. Поначалу это слегка запутывает пользователя. Всего RT-Thread определяет 6 процессов запуска (startup processes). 1. Подпрограммы инициализации платы, вызываемые в функции board_init(). Определение: #define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
2. Подпрограммы инициализации pre/device/component/env/app, вызываемые в init_thread (чисто программная инициализация). Определение: #define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2")
3. Инициализация устройства. Определение: #define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3")
4. Инициализация компонента (dfs, lwip, ...). Определение: #define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4")
5. Инициализация окружения (монтирование диска, ...). Определение: #define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5")
6. Инициализация приложения (приложение rtgui, и т. п. ...). Определение: #define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")
INIT_BOARD_EXPORT запускается перед запуском планировщика (scheduler). Здесь происходит процесс начальной инициализации конфигурации периферии. Остальные процессы запускаются основным потоком (в standard-версии RT-Thread, если используется поток main) после того, как запущен планировщик. Эти процессы не полностью фиксированы, и кое-что можно подстроить. Например, можно перенести инициализацию LCD из DEVICE в BOARD, и сделать инициализацию emwin в PREV. Некоторые очереди сообщений также инициализируются в процессе ENV. В большинстве случаев все инициализации можно провести в описанных выше процессах 1 .. 6. Однако существует вероятность возникновения конфликта. Этой теме посвящен топик PR #5194 в RT-Thread Github Repository, где находятся некоторые полезные ссылки, и многие разработчики находят здесь советы по решению своих проблем. Рационально сделать запуск устройств и компонентов на одном уровне, разрешая их зависимости с помощью мьютексов. В этом случае нужно подстроить порядок кода. Q9. Как реализовать пробуждение потока с помощью rt_thread_suspend и rt_thread_resume? Лучше всего избегать таких сценариев. В RT-Thread может быть 2 возможности, возникающие при переходе потока в состояние приостановки (suspend): в одном случае он сразу получит процессорное время, в другом случае он будет ждать освобождения ресурса, пока ресурс заблокирован. Не существует полного и прозрачного способа, чтобы потоки знали текущее взаимное состояние. Предположим, что поток A хочет явно приостановить поток B, но A не знает, отказался ли в настоящий момент B от CPU, или он покидает ресурс для приостановки, или ресурc доступен и B пробуждается, выходя из состояния приостановки. Так или иначе, опасно переводить в приостановку другой поток, не зная его текущее состояние. Единственный допустимый вариант может быть, когда поток B загружен больше всего, и сам не будет проявлять инициативу для блокировки, чтобы оказаться от процессорного времени. Кроме того, приоритет B относительно низкий, и тогда высокоприоритетный поток A может приостановить B при определенных условиях. Но тогда поток B должен повлиять на поток ожидания (idle thread). Фактически необходимого функционала можно достичь более безопасными методами, используя традиционные механизмы синхронизации потоков - ожидание на семафоре или очереди. Поток B приостанавливает сам себя, ожидая поступления сигнала от A; поток A тогда разбудит поток B с помощью сигнала (публикуя семафор или ставя объект в очередь). Q10. Команды list_thread (или ps) не могут проверить состояние потока? Ошибки потока в столбце Error не могут иметь какой-либо ссылки. Здесь 0 это нормальное состояние, -2 показывает таймаут, и когда вы выполните rt_thread_mdelay, он превратится в -2. Однако это не означает ошибку. Столбец status представляет текущее состояние потока. Однако из-за того, что команда list_thread (или ps) выполняется в контексте потока tshell, поток tshell по определению работает (состояние running). Поток ожидания (idle thread) не может быть приостановлен, он показывает состояние готовности (ready). Другие потоки могут быть в состоянии ready, но в большинстве случаев при нормальном сценарии они могут быть в состоянии приостановки (suspend), ожидая публикации семафора или очереди. Однако это не означает, что другие потоки приостановлены, и не управляются планировщиком. Q11. Может ли таймер выполнять долгие операции? Как упоминалось выше, существует 3 разновидности таймеров в RT-Thread, и у каждого свои характеристики. Hardware timer: его callback-функция вызывается в прерывании, так что не рекомендуется, чтобы она выполняла какие-либо длительные действия. Hard timer: также выполняет свои callback-функции в прерываниях, и не рекомендуется в них напрямую выполнять длительные операции. Soft Timer: выполняется потоком таймера (timer thread), который вызовет callback-функции, и теоретически здесь могут выполняться долгие операции. Timer thread также является обычным потоком, у него есть собственный стек потока, свой приоритет, и т. п. Если некоторые операции независимы, то размещение их в определенном потоке совпадет с выполнением в потоке таймера. Однако способ, которым текущий timer thread обрабатывает soft-таймеры, не подходит для выполнения длительных операций. Для этого требуются некоторые изменения. Необходимо соответствующим образом подстроить приоритет timer thread относительно других потоков. Следует иметь в виду, что если создано несколько soft-таймеров, то длительное выполнение callback-функции одного из этих soft-таймеров может повлиять на работу других soft-таймеров, чего избежать не получится. [Ссылки] 1. RT-Thread document center site:rt-thread.io. . RT-Thread FAQ site:club.rt-thread.io. |