Многозадачность важна в наши дни. Интересно, как нам этого добиться в микроконтроллерах и встроенном программировании. Я разрабатываю систему, основанную на микроконтроллере PIC. Я разработал его прошивку в MplabX IDE, используя C, а затем разработал приложение для него в Visual Studio, используя C #.
Поскольку я привык использовать потоки в программировании на C # на рабочем столе для реализации параллельных задач, есть ли способ сделать то же самое в моем коде микроконтроллера? MplabX IDE предоставляет, pthreads.h
но это просто заглушка без реализации. Я знаю, что есть поддержка FreeRTOS, но использование этого делает ваш код более сложным. На некоторых форумах говорится, что прерывания также можно использовать как многозадачные, но я не думаю, что прерывания эквивалентны потокам.
Я разрабатываю систему, которая отправляет некоторые данные в UART, и в то же время необходимо отправить данные на веб-сайт через (проводной) Ethernet. Пользователь может контролировать выход через веб-сайт, но выход включается / выключается с задержкой 2-3 секунды. Так что это проблема, с которой я сталкиваюсь. Есть ли решение для многозадачности в микроконтроллерах?
источник
Ответы:
Существует два основных типа многозадачных операционных систем: упреждающие и кооперативные. Оба позволяют определять несколько задач в системе, разница в том, как работает переключение задач. Конечно, с одним ядром-процессором одновременно выполняется только одна задача.
Оба типа многозадачных ОС требуют отдельного стека для каждой задачи. Таким образом, это подразумевает две вещи: во-первых, процессор позволяет размещать стеки в любом месте ОЗУ и, следовательно, имеет инструкции для перемещения указателя стека (SP), то есть нет аппаратного стека специального назначения, как на младшем конце ПОС. Это исключает серии PIC10, 12 и 16.
Вы можете написать операционную систему почти целиком на C, но переключатель задач, на котором перемещается SP, должен быть в сборке. В разное время я писал переключатели задач для PIC24, PIC32, 8051 и 80x86. Кишки все сильно различаются в зависимости от архитектуры процессора.
Второе требование - наличие достаточного объема ОЗУ для нескольких стеков. Обычно для стека требуется не менее пары сотен байт; но даже при 128 байтах на задачу для восьми стеков требуется 1 КБ ОЗУ - хотя вам не нужно выделять один и тот же размер стека для каждой задачи. Помните, что вам нужно достаточно стека для обработки текущей задачи и любых вызовов ее вложенных подпрограмм, но также и стекового пространства для вызова прерывания, так как вы никогда не знаете, когда это произойдет.
Есть довольно простые методы, чтобы определить, сколько стека вы используете для каждой задачи; Например, вы можете инициализировать все стеки к определенному значению, скажем, 0x55, и запустить систему на некоторое время, а затем остановить и исследовать память.
Вы не говорите, какой PIC вы хотите использовать. Большинство PIC24 и PIC32 будут иметь достаточно места для запуска многозадачной ОС; PIC18 (единственный 8-битный PIC, имеющий стеки в ОЗУ) имеет максимальный размер ОЗУ 4K. Так что это довольно сомнительно.
При совместной многозадачности (более простой из двух) переключение задач выполняется только тогда, когда задача «отдает» свой контроль обратно ОС. Это происходит всякий раз, когда задаче необходимо вызвать подпрограмму ОС для выполнения некоторой функции, которую она будет ожидать, например, запрос ввода-вывода или таймер. Это облегчает ОС переключение стеков, так как нет необходимости сохранять все регистры и информацию о состоянии, SP можно просто переключить на другую задачу (если нет других готовых к запуску задач, пустой стек данный контроль). Если текущая задача не требует выполнения вызова ОС, но выполняется некоторое время, она должна добровольно отказаться от управления, чтобы система реагировала.
Проблема с совместной многозадачностью состоит в том, что если задача никогда не откажется от управления, она может перегружать систему. Только он и любые подпрограммы прерывания, которым назначено управление, могут запускаться, поэтому ОС будет казаться заблокированной. Это «кооперативный» аспект этих систем. Если реализован сторожевой таймер, который сбрасывается только при переключении задач, то можно отловить эти ошибочные задачи.
Windows 3.1 и более ранние версии были совместными операционными системами, отчасти поэтому их производительность не была такой высокой.
Упреждающая многозадачность сложнее реализовать. Здесь задачи не обязаны отказываться от управления вручную, но вместо этого каждой задаче может быть предоставлен максимальный промежуток времени для выполнения (скажем, 10 мс), и затем выполняется переключение задачи на следующую выполняемую задачу, если она есть. Для этого необходимо произвольно остановить задачу, сохранить всю информацию о состоянии, а затем переключить SP на другую задачу и запустить ее. Это делает переключатель задач более сложным, требует большего количества стеков и немного замедляет работу системы.
Как для кооперативной, так и для упреждающей многозадачности прерывания могут происходить в любое время, что временно прерывает выполнение задачи.
Как указывает суперкат в комментарии, одним из преимуществ многозадачности, связанной с кооперативом, является то, что легче распределять ресурсы (например, аппаратное обеспечение, такое как многоканальный АЦП, или программное обеспечение, например, изменение связанного списка). Иногда две задачи хотят получить доступ к одному и тому же ресурсу одновременно. При упреждающем планировании ОС могла бы переключать задачи в середине одной задачи с использованием ресурса. Поэтому блокировки необходимы для предотвращения входа другой задачи и доступа к тому же ресурсу. При совместной многозадачности это не нужно, поскольку задача контролирует, когда она самостоятельно вернется в ОС.
источник
void foo(void* context)
которые логика контроллера (ядро) извлекает из пары указателей и указателей на функции и вызывает их по одному. Эта функция использует контекст для хранения своих переменных и тому подобного, а затем может добавить отправку продолжения в очередь. Эти функции должны быстро возвращаться, чтобы позволить другим задачам их момент в процессоре. Это основанный на событиях метод, требующий только одного стека.Потоки обеспечиваются операционной системой. Во встроенном мире у нас обычно нет ОС («голый металл»). Так что это оставляет следующие варианты:
Я бы посоветовал вам использовать простейшие из вышеперечисленных схем, которые будут работать для вашего приложения. Из того, что вы описываете, у меня будет основной цикл генерации пакетов и помещения их в кольцевые буферы. Затем используйте драйвер на основе UART ISR, который запускается всякий раз, когда предыдущий байт завершает отправку до тех пор, пока не будет отправлен буфер, а затем ожидает дополнительного содержимого буфера. Подобный подход для Ethernet.
источник
Как и в любом одноядерном процессоре, в реальном программном обеспечении многозадачность невозможна. Таким образом, вы должны позаботиться о переключении между несколькими задачами в одну сторону. Об этом позаботятся разные ОСРВ. У них есть планировщик, и на основе системного тика они будут переключаться между различными задачами, чтобы дать вам возможность многозадачности.
Понятия, связанные с этим (сохранение и восстановление контекста), довольно сложны, поэтому выполнение этого вручную, вероятно, будет трудным и сделает ваш код более сложным, и, поскольку вы никогда не делали этого раньше, в нем будут ошибки. Мой совет здесь заключается в том, чтобы использовать протестированную RTOS точно так же, как FreeRTOS.
Вы упомянули, что прерывания обеспечивают уровень многозадачности. Это своего рода правда. Прерывание прервет вашу текущую программу в любой момент и выполнит там код, это сравнимо с системой с двумя задачами, где у вас есть 1 задача с низким приоритетом и другая с высоким приоритетом, которая заканчивается в одном временном интервале планировщика.
Таким образом, вы можете написать обработчик прерывания для повторяющегося таймера, который будет отправлять несколько пакетов через UART, а затем выполнить остальную часть вашей программы в течение нескольких миллисекунд и отправить следующие несколько байтов. Таким образом, вы получаете ограниченную возможность многозадачности. Но у вас также будет довольно длительное прерывание, что может быть плохо.
Единственный реальный способ одновременно выполнять несколько задач на одноядерном MCU - это использовать DMA и периферийные устройства, поскольку они работают независимо от ядра (DMA и MCU используют одну и ту же шину, поэтому они работают немного медленнее, когда оба активны). Таким образом, пока DMA перетасовывает байты в UART, ваше ядро может свободно отправлять данные в Ethernet.
источник
В других ответах уже описаны наиболее часто используемые параметры (основной цикл, ISR, RTOS). Вот еще один вариант в качестве компромисса: протопотоки . Это в основном очень легкая библиотека для потоков, которая использует основной цикл и некоторые макросы C, чтобы «эмулировать» ОСРВ. Конечно, это не полная ОС, но для «простых» потоков это может быть полезно.
источник
Мой базовый дизайн для минимальной временной RTOS не сильно изменился за несколько микросемей. Это в основном прерывание по таймеру за рулем конечного автомата. Процедура обслуживания прерываний - это ядро ОС, а оператор switch в основном цикле - задачи пользователя. Драйверы устройств являются процедурами обслуживания прерываний для прерываний ввода-вывода.
Основная структура выглядит следующим образом:
Это в основном кооперативная многозадачная система. Задачи написаны так, чтобы никогда не входить в бесконечный цикл, но нам все равно, потому что задачи выполняются внутри цикла событий, поэтому бесконечный цикл неявный. Это стиль программирования, подобный событиям / неблокирующим языкам, таким как javascript или go.
Вы можете увидеть пример этого стиля архитектуры в моем программном обеспечении передатчика RC (да, я фактически использую его для полета самолетов RC, так что это несколько критично для безопасности, чтобы предотвратить падение самолетов и потенциально убивать людей): https://github.com / slebetman / pic-txmod . В основном это 3 задачи - 2 задачи в реальном времени, реализованные в виде драйверов устройств с состоянием (см. Ppmio) и 1 фоновая задача, реализующая логику микширования. Так что в основном он похож на ваш веб-сервер в том, что он имеет 2 потока ввода / вывода.
источник
Хотя я понимаю, что вопрос конкретно касается использования встроенной ОСРВ, мне кажется, что более широкий вопрос, который задают, - это «как добиться многозадачности на встроенной платформе».
Я настоятельно советую вам забыть об использовании встроенной ОСРВ хотя бы на время. Я советую это, потому что считаю необходимым сначала узнать о том, как достичь «параллелизма» задачи с помощью чрезвычайно простых методов программирования, состоящих из простых планировщиков задач и конечных автоматов.
Чтобы предельно кратко объяснить концепцию, каждый модуль работы, который необходимо выполнить (т. Е. Каждое «задание»), имеет определенную функцию, которую необходимо периодически вызывать («ставить галочку»), чтобы этот модуль выполнял некоторые действия. Модуль сохраняет свое текущее состояние. Затем у вас есть основной бесконечный цикл (планировщик), который вызывает функции модуля.
Грубая иллюстрация:
Подобная однопотоковая структура программирования, при которой вы периодически вызываете функции основного конечного автомата из цикла главного планировщика, является повсеместной во встроенном программировании, и поэтому я настоятельно рекомендовал бы OP сначала ознакомиться с ним и освоиться с ним, прежде чем углубляться в использование. RTOS задачи / темы.
Я работаю на типе встроенного устройства, которое имеет аппаратный ЖК-интерфейс, внутренний веб-сервер, почтовый клиент, DDNS-клиент, VOIP и многие другие функции. Хотя мы используем RTOS (Keil RTX), количество используемых отдельных потоков (задач) очень мало, и большая часть «многозадачности» достигается, как описано выше.
Чтобы дать несколько примеров библиотек, которые демонстрируют эту концепцию:
Сетевая библиотека Keil. Весь стек TCP / IP может быть запущен однопоточным; Вы периодически вызываете main_TcpNet (), который выполняет итерацию стека TCP / IP и любой другой сетевой параметр, скомпилированный из библиотеки (например, веб-сервер). См. Http://www.keil.com/support/man/docs/rlarm/rlarm_main_tcpnet.htm . Следует признать, что в некоторых ситуациях (возможно, выходящих за рамки этого ответа) вы достигаете точки, когда становится полезным или необходимо использовать потоки (особенно при использовании блокирующих сокетов BSD). (Далее: новое V5 MDK-ARM фактически порождает выделенный поток Ethernet - но я просто пытаюсь привести иллюстрацию.)
Библиотека Linphone VOIP. Сама библиотека linphone является однопоточной. Вы вызываете
iterate()
функцию с достаточным интервалом. См. Http://www.linphone.org/docs/liblinphone-javadoc/org/linphone/core/LinphoneCore.html#iterate () . (Немного плохой пример, потому что я использовал это на встроенной платформе Linux, и библиотеки зависимостей linphone, несомненно, порождают потоки, но опять-таки, чтобы проиллюстрировать это.)Возвращаясь к конкретной проблеме, описанной OP, проблема, по-видимому, заключается в том, что связь UART должна происходить одновременно с некоторыми сетями (передача пакетов по TCP / IP). Я не знаю, какую сетевую библиотеку вы на самом деле используете, но я предполагаю, что она имеет основную функцию, которую нужно часто вызывать. Вам нужно было бы написать свой код, который имеет дело с передачей / приемом данных UART, чтобы он был структурирован аналогичным образом, как конечный автомат, который может повторяться при периодических вызовах основной функции.
источник