Exc что значит
1 exc
2 exc
3 exc
4 EXC
5 exc
6 exc.
7 exc
8 exc
9 EXC
10 EXC-T
11 Exc
12 exc
13 exc m/c
14 exc.
15 exc
16 exc
17 EXC
18 exc
19 exc
20 exc
См. также в других словарях:
exc. — exc. 〈Abk. für lat.〉 excudit * * * exc. = excudit. * * * exc., excud., Abkürzung für excudit [lateinisch »hat (es) geprägt«], Vermerk auf Kupferstichen hinter dem Namen des Verlegers (Adresse); ist dieser mit dem Stecher identisch, steht oft… … Universal-Lexikon
Exc. — Exc., Abkürzung für excudit (lat., »hat s gestochen, verfertigt«), vom 16.–18. Jahrh. auf Kupferstichen, Holzschnitten etc. Zusatz zu dem Namen des Druckers und Verlegers … Meyers Großes Konversations-Lexikon
Exc. — Exc. = Excūdit (lat., d.i. hat es gestochen), auf Kupferstichen Abkürzung hinter dem Namen des Stechers … Kleines Konversations-Lexikon
Exc. — Exc. s. Exz … Kleines Konversations-Lexikon
Exc. — Exc., excudit, lat., bei Kupferstichen: hat gestochen … Herders Conversations-Lexikon
exc — abbrev. except * * * … Universalium
exc. — exc. 〈Abk. für〉 excudit … Lexikalische Deutsches Wörterbuch
exc. — exc., excud. = excudit … Die deutsche Rechtschreibung
exc — abbrev. except … English World dictionary
Exc. — Excellency. * * * abbrev Excellency * * * exc., 1. excellent. 2. a) except. b) excepted. 3. exception. Exc., Excellency. * * * abbr. Excellency … Useful english dictionary
exc. — 1. excellent. 2. except. 3. exception. 4. excudit. 5. excursion. * * * abbrev Excellency * * * exc., 1. excellent. 2. a) except. b) excepted … Useful english dictionary
Перевод "exc" на русский
Ваш текст переведен частично.
Вы можете переводить не более 999 символов за один раз.
Войдите или зарегистрируйтесь бесплатно на PROMT.One и переводите еще больше!
Словарь
Контексты
Бот-переводчик
PROMT Master NMT
Скачайте мобильное приложение PROMT.One
Откройте возможности нейронного машинного перевода PROMT
PROMT.One — это облачное приложение – бесплатный онлайн-переводчик для перевода с языка на язык на основе нейронных сетей (Neural Machine Translation), словарь с транскрипцией, разговорники и многое другое. Наслаждайтесь правильным и точным переводом на английский, немецкий и еще 20+ языков.
Смотрите перевод слов и устойчивых выражений, транскрипцию и произношение в онлайн cловаре. Словари PROMT для английского, немецкого, французского, русского, испанского, итальянского и португальского языков включают миллионы слов и словосочетаний, самую современную разговорную лексику, которая постоянно отслеживается и пополняется нашими лингвистами.
Учите употребление слов и выражений в разных Контекстах. Миллионы реальных примеров на английском, немецком, испанском, французском помогут вам в изучении иностранных языков и подготовке домашних заданий.
Переводите в любом месте и в любое время с помощью бесплатного мобильного переводчика PROMT.One для iOS и Android. Попробуйте голосовой и фотоперевод.
Установите языковые пакеты для офлайн-перевода на мобильных устройствах и универсальный плагин PROMT АГЕНТ для Windows с подпиской PREMIUM.
Exc что значит
Link to this page:
- ▲
- EXC
- ▼
- ▲
- EXC
- ▼
All content on this website, including dictionary, thesaurus, literature, geography, and other reference data is for informational purposes only. This information should not be considered complete, up to date, and is not intended to be used in place of a visit, consultation, or advice of a legal, medical, or any other professional.
Переключение контекста и простой вытесняющий планировщик для CortexM
С каждым годом курсовые для моих студентов становятся все объемнее. Например, в этом году одним из заданий была разработка метеостанции, ведь только ленивый не делает метеостанции, а студенты они по определению не ленивые, поэтому должны её сделать. Её можно быстро накидать в Cube или собрать на Ардуино, но задача курсового не в этом. Основная задача — самостоятельно, с нуля разобраться с модулями микроконтроллера, продумать архитектуру ПО и, собственно, закодировать все на С++, начиная от регистров и заканчивая задачами РТОС. Кому интересно, здесь пример отчета по такому курсовому
Так вот, появилась небольшая проблема, а именно, бесплатный IAR позволяет делать ПО размером не более 30 кБайт. А это уже впритык к размеру курсового в неоптимизированном виде. Анализ кода студентов выявил, что примерно 1/4 часть их приложения занимает FreeRtos — около 6 кБайт, хотя для того, чтобы сделать вытесняющую переключалку и управлялку задачами хватило бы, наверное… да байт 500 причем вместе с 3 задачами (светодиодными моргунчиками).
Эта статья будет посвящена тому, как можно реализовать Очень Простой Планировщик(он же SST), описанный в статье аж 2006 года и сейчас поддерживаемый Quantum Leaps в продукте Qp framework.
С помощью этого ядра очень просто реализовать конечный автомат, и оно очень хорошо может использоваться в небольших проектах студентами (и не только), которые могут получить дополнительно 5 кБайт в свое распоряжение.
Я попробую показать как можно реализовать такой планировщик самому. Чтобы не сильно перегружать статью, рассмотрю переключение контекста на CortexM0 у которого нет аппаратного модуля с плавающей точкой.
Все кто заинтересовался и хочет понять как можно переключать контекст, добро пожаловать под кат.
Небольшое отступление
Изначально я хотел описать, как работает планировщик в «нормальных» РТОС и потом уже описать, как он сделан в Простом Планировщике и показать пример такого планировщика на ядре CortexM4, но статья получалась довольно большой и непонятной, поэтому я решил её упросить (не уверен, что она стала понятнее, но точно меньше, хотя все равно большой). Поэтому я ввел небольшие ограничения и начальные условия:
- Рассматриваем ядро CortexM0, ну или микроконтроллеры с ARM архитектурой, поддерживающие только
- Thumb набор команд
- Имеющие только привилегированный режим
- Не имеющие аппаратного блока с плавающей точкой
И хотя такой планировщик в принципе можно запустить и на CortexM3 и даже на CortexM4 (с отключенным FPU блоком), для нормальной их поддержки, нужно будет внести небольшие изменения в обработчике PendSV и SVC исключений.
В общем делать его мы будем по-модному, на С++17, без указателей, интерфейсов, создания задач в рантайме и прочей «ерунды», а полагаться только на соmpile-time, чтобы всё-всё было определено, а по возможности проверено на этапе компиляции.
Введение
Собственно, в качестве введения наверное лучше всего подойдет цитата из выше указанной статьи 2006 года
По большому счету, вся программа — это один большой или небольшой конечный автомат. И наши старшие братья в мире ПО под «еще более нормальные» операционные системы давно уже имеют кучу механизмов для реализации конечных автоматов — потоки, корутины, фиберы — тому подтверждение.
В ПО же для микроконтроллеров каждый раз приходится либо использовать совсем неоптимальные вещи обычных операционных систем реального времени (передача событий от задачи к задаче, со всеми вытекающими (долгие переключения контекста, создание новых задач с большими стеками)), либо городить что-то свое, либо по старинке пользоваться обычным switchом.В случае же с SST ядро и планировщик очень просты и ему не нужно управлять несколькими стеками. И основное отличие этого ядра является то, что оно требует чтобы все задачи выполнялись до завершения (Run to completion), используя один стек.
А это кстати решает одну из «вечных» возможных проблем с бесконечным циклом, ведь бесконечный цикл в С++ это вообще-то Undefined/Unspecified Behaviour (UB).
Спасибо Dubovik_a за уточнение: не все бесконечные циклы UB, в соответствии со стандартом, если внутри цикла есть одно из нижесказанного, то это уже не UB- terminate,
- make a call to a library I/O function,
- access or modify a volatile object, or
- perform a synchronization operation or an atomic operation
Но в любом случае, нет таких циклов — нет UB, а заодно сделаем наш планировщик без единого указателя, чтобы, еще меньше UB проникли в код (не уверен, что код на С++ можно вообще написать без UB, но вдруг).
Перед тем как начинать статью, я хотел вначале найти простое объяснение, как переключить контекст на CortexM в интернете на русском языке, из более менее понятного и простого, нашел вот эту статью. Но я не уверен, что без дополнительного заглядывания в руководство по ядру CortexM3 из этого текста можно сразу все понять.
Есть еще статья на Хабре: Как сделать context switch на STM32.
Но даже если вы и прочитали эти статьи, все равно все выглядит как рисование совы.Поэтому давайте вначале разберемся с алгоритмом переключения контекста, как это вообще происходит. И первым делом займемся изучением некоторых необходимых для создания планировщика понятий.
Команды CortexM микроконтроллеров
У CortexM бывает три набора команд:
- ARM — Основной 32 битный набор команд.
- Thumb — Сокращённая система 16 битных команд.
- Thumb-2 — 16 битный Thumb набор + немного 32 битных команд, эдакая смесь ARM и Thumb, чтобы получить преимущества обоих систем команд.
Так вот наш CortexM0 поддерживает только Thumb набор, ну не считая парочки команд из Thumb-2, но закроем на это глаза.
На всякий случай, CortexM3 поддерживает Thumb-2 полностью.Режимы работы процессора
Cortex-M имеет два режима работы: режим процесса (Thread) и режим обработчика (Handle):
- Режим Handle используется при обработке исключений(все обработчики прерываний работают в этом режиме, хотя прерывания — это лишь подмножество исключений) и работает только с основным MSP стеком
- Режим Thread используется для выполнения пользовательского кода и может работать с основным стеком(MSP) или стеком процесса (PSP)
Переключение из одного режима в другой происходит автоматически в момент входа или выхода из исключения.
Про стеки узнаем немного позже, а пока это вся информация по режимам, которую нужно знать для переключения контекста. И да, мы будет использовать только основной стек MSP.
CortexM0 регистры
CortexM0 имеет 16 регистров общего назначения:
- Младшие регистры (r0-r7)
- Старшие регистры (r8-r12)
- Регистр указателя стека SP (r13) для текущего контекста (r8-r12)
И ряд регистров специального назначения:
- Регистр состояния xPSR, он содержит в себе флаги результатов выполнения арифметических действий, состояние выполнение программы и номер обрабатываемого в данный момент исключения. Доступ к полям регистра может осуществляться через три псевдорегистра, позволяющие обращаться к определенным областям xPSR:
- Регистр состояния приложения APSR содержит флаги результатов выполнения арифметических операций
- Регистр состояния прерывания EPSR содержит номер обрабатываемого исключения
- Регистр состояния выполнения IPSR содержит бит показывающий в каком режиме исполняются команды микроконтроллера Thumb или ARM, а так как, мы выяснили, что
CortexM0 может работать только в Thumb режиме, то этот бит всегда должен быть равен 1, иначе микроконтроллер допустит недопустимое.
Регистр указателя стека (r13/SP)
Я не буду подробно описывать что такое стек, есть множество статей на эту тему. Но для того, чтобы понять как он работает на CortexM архитектуре необходимо знать несколько моментов.
- Указатель стека всегда выравнен по слову и его два младшие бита должны быть равны 0.
- Стек всегда двигается от старших адресов к младшим.
- Указатель стека используется для доступа к стеку с помощью инструкций POP и PUSH.
- Указатель стека может быть модифицирован с помощью инструкций LDR, STR, SUB, ADD и так далее
- Имеет двойное назначение и может являться:
- MSP(Main Stack Pointer) — указателем на основной стек,
- PSP (Program Stack Pointer) — указателем на стек процесс PSP.
И хотя в нашей задаче нам не нужен стек процесса, для общего образования все таки уточню, что в каждый момент доступен только один из этих указателей. В режиме Handle указатель SP всегда указывает на MSP, а вот в режиме Thread указатель может указывать как на основной стек MSP, так и на стек процесса PSP. Какой именно сейчас стек используется, можно определить с помощью CONTROL регистра.
Выходя из режима Handle можно поменять стек указав волшебное значение при возврате из исключения или в регистре связи. Встречаем регистр связи.
Регистр связи (r14/LR)
У регистра связи две функции. Одна прямая — хранение адреса возврата:
- Регистр связи используется для хранения адреса возврата из подпрограмм и функций, вызванных командой BL.
И вторая не менее важная:
- Во время входа и возврата из исключения в LR сохраняется EXC_RETURN код, который указывает какой режим и какой стек нужно использовать после возврата из исключения.
EXC_RETURN Что значит 0xFFFFFFF1 Возвращаемся в Handle режим, используем основной стек MSP 0xFFFFFFF9 Возвращаемся в Thread режим, используем основной стек MSP 0xFFFFFFFD Возвращаемся в Thread режим, используем стек процесса PSP Исключение
Исключение в ARM, это такой механизм, который позволяет прервать безмятежное течение программы. Исключение может быть вызвано программно с помощью инструкции вызова исключения или же вызвано в ответ на поведение системы, такое как прерывание, ошибка выравнивания или ошибка системы памяти.
Исключения бывают синхронные и асинхронные. Прерывания являются асинхронными исключениями. А вот например, ошибки связанные с доступом к памяти или выполнения инструкций — синхронные исключения.И в целом разделяют две основные стадии исключения:
- Генерация исключения
Момент, когда в микроконтроллере происходит некое важное событие, которое связано с исключением
- Обработка или активация исключения
Это когда микроконтроллер начинает выполнять определенную последовательность для входа в исключение, потом выполняет код обработчика исключения и в конце последовательность выхода из исключения. И в общем-то переход от состояния генерации исключения до состояния обработка исключения может быть мгновенным.
А теперь давайте поймем как происходит вход и выход из исключения, но для полноты картины прежде, посмотрим на кадр исключения.
Кадр исключения
Кадр исключения (Exception Frame). Так вот, это набор регистров, которые автоматически сохраняются при входе в исключение и восстанавливается из него при выходе из исключения. Кадр выглядит так:
В кадре исключения сохраняются регистры R0-R3, R12 и LR, PC, xPSR.
Остальные регистры R4-R11 не могут использоваться (в соответствии с C/C++ standard Procedure Call Standard for the ARM Architecture) в обработчике исключения и поэтому не входят в данный кадр.
Вход в Исключение
Это важный момент для понимания того, что происходит во время вхождения и выхода из прерывания.
Вход в прерывание возникает тогда, когда появляется ожидающее исключение с необходимым приоритетом и:- Микроконтроллер находится в Thread режиме
- Исключение имеет приоритет выше, чем обрабатывающееся в данный момент исключение. В таком случае исключение с высшим приоритетом вытесняет текущее исключение, по другому это называется вложенными исключениями.
Когда микроконтроллер начинает обработку исключения он сохраняет кадр исключения в стеке. Эта операция по английски называется «stacking». По русски звучит странно, поэтому не буду переводить. При этом указатель стека перемещается на размер кадра исключения.
Как было уже сказано выше, стек исключения содержит кадр из 8 слов данных и подчиняется простым правилам.
Стек выравнен по 8 байтовому адресу (двум словам).
Стек содержит адрес возврата из исключения — адрес следующей инструкции в прерванной исключением подпрограмме. Это значение восстанавливается и загружается в PC во время возврата из исключения.
Микроконтроллер, а точнее контроллер прерывания считывает стартовый адрес обработчика исключения из таблицы векторов прерываний и когда «stacking» завершен, запускает выполнение обработчика этого прерывания. В то же время микроконтроллер записывает специальный код возврата — EXC_RETURN в регистр LR, как мы уже выяснили этот код показывает тип указателя стека (MSP или PSP) и в каком режиме был микроконтроллер до входа в исключение.
Если во время входа в исключение не произошло более высоко-приоритетного прерывания, процессор запускает выполнение обработчика исключения. Микроконтроллер автоматически изменяет статус исключения на активное.
Если более высокоприоритетное исключение произошло во время входа в исключение, то статус текущего исключения будет «ожидание». Так называемое «позднее прибытие».
В общем-то и все, исключение обработали, теперь надо из него выйти.
Возврат из исключения
Возврат из исключения происходит когда микроконтроллер находится в Handle режиме и выполняется одна и следующих инструкций, пытающихся установить PC в специальное EXC_RETURN значение :
- POP инструкция которая загружает значение из стека в PC.
- BX инструкция, использующая любой регистр
Микроконтроллер сохраняет значение EXC_RETURN в LR при входе в исключение
Механизм исключений полагается на это значение, чтобы определить когда микроконтроллер завершит обработку исключения.Биты[31:4]
- EXC_RETURN значения должны быть установлены в 0xFFFFFFF. Когда микроконтроллер загружает эти биты в PC, это дает понять ядру, что операция не является обычной, а означает завершение обработки прерывания. Как результат такого «оповещения» запускается последовательность возврата из исключения.
Биты[3:0]
- EXC_RETURN значения указывают на требуемый стек возврата и режим процессора.
При возврате из исключения происходит обратная операция — unstacking, еще более странно переводящаяся на русский язык. При этом микроконтроллер загружает в PC адрес следующей инструкции из кадра исключения, и собственно переходит на её исполнение.
Я тут попытался нарисовать залипающую картинку, получилось не очень, но не пропадать же 2-часову труду зря.
Но я люблю статику, поэтому вот обычная картинка:
Переключение контекста
В «нормальных» RTOS, идея работы с задачами состоит в том, чтобы PSP стек использовался отдельными задачами, а MSP стек использовался обработчиками исключений и ядром. Когда возникает исключение, контекст задачи помещается в текущий активный указатель стека PSP, а затем переключается на использование MSP для обработки исключения.
С одной стороны это хорошо — это подразумевает некое разделение между стеками обработчика исключений и задач, ваша задача всегда работает со стеком PSP и доступа к MSP нет.
С другой стороны, переключение контекста не такое быстрое, а из-за того, что каждая задача имеет свой стек, который обычно делают с запасом, возможен непреднамеренный расход ОЗУ.
Итак, контекст у нас должен переключаться по какому-то событию. Пусть это будет любое событие происходящее в прерывании, например, по таймеру, или приходу символа в UART, или любому другому, которое должно инициировать обработку чего-то. Как только произошло такое событие мы должны запустить планировщик, который найдет подходящую задачу и запустит её, при этом вытеснив уже запущенные менее приоритетные.
Логично, что такие события могут происходить из прерываний, т.е в режиме Handle, а вот планировщик и задачи должны быть запущены в режиме Thread. Как это сделать?
Каждый раз при выходе из любого прерывания в котором генерируется событие для переключения контекста мы будем генерировать исключение PendSV, и уже в нем делать магию по переключению контекста: в упрощенном виде это будет выглядеть примерно так:
Т.е. вместо того, чтобы в прерывании вызывать планировщик, мы сгенерируем исключение PendSV и уже при выходе из него запустим планировщик, который будет заниматься переключением задач.
Сразу же после выхода из прерывания, сгенерировавшего событие для какой-либо задачи, мы попадем в PendSV исключение, в котором должны:
- Скинуть флаг генерации исключения PendSV
- Запретить все прерывания
- Вызвать планировщик
На последнем пункте давайте остановимся поподробнее, потому что легко сказать, да как это сделать.
Вызов планировщика
Нам нужно вызвать планировщик из исключения PendSV, так чтобы он запустился в режиме Thread, но чтобы попасть в этот режим нужно выйти из PendSV.
Как вы помните при входе в исключение, микроконтроллер сохранил кадр исключения текущей задачи на стеке.
А если указатель стека так и останется на вершине этого кадра, то при вызове планировщика, т.е. выходе из прерывания, этот кадр пропадет, так как при выходе из исключения сделается unstacking.Значит нам надо сделать так, чтобы, при вызове планировщика мы работали с другим кадром, не испортив при этом кадр вытесненной задачи. Т.е. к текущему указателю стека нужно добавить (а поскольку стек растет в сторону уменьшения адресов, то убавить) стек на размер еще одного такого же кадра, но с данными для вызова Планировщика.
И в этом кадре в PC мы положим адрес планировщика, в LR адрес возврата после работы планировщика, а в xPSR надо поставим 1 в бит T, который говорит о том, что мы работает с набором команд Thumb, а то выйдет исключение по ошибке выполнения инструкций.
Вот так мы руками поменяем наш стек в обработчике исключения PendSV для вызова планировщика:
Возврат из планировщика
Как только планировщик выполнил свою работу, нам нужно вернуться куда-то, где надо будет разрешить прерывания, а также сделать, что-то, что позволит вернуться к текущей прерванной задаче. Т.е. мы опять должны будем сгенерировать какое-то исключение, и в нем удалить тот кадр исключения, что мы добавили в предыдущем пункте. И уже при выходе из исключения, у нас сделается правильный unstacking с переходом на прерванную задачу.
Для лучшего понимания, я нарисовал целую картину, могут быть ошибки, но честно старался и в общем-то посыл передал верно.
- Вначале работает SomeTask
- Какое-то прерывание посылает HighPriorityTask задаче событие и вызывает PendSV
- Начинается стадия входа в исключение, выполняется stacking, формируется кадр исключения и сохраняется на MSP стеке
- Запускается обработчик исключения PendSV
- В нем мы добавляем к стеку еще один рукотворно-созданный кадр исключения, куда записываем адрес адрес планировщика, адрес возврата после работы планировщика и набор команд Thumb
- Выходим из исключения, выполняем стадию unstacking, в которой микроконтроллер вытаскивает наши подменные PC, LR и xPSR, и в соответствии с ними переходит на планировщик.
- Планировщик делает свою работу уже в режиме Thread — ищет новую высокоприоритетную задачу, и если нашел — просто запускает ее, также в режиме Thread, она тоже может вытесниться еще более высокоприоритетной.
- Выполняется высокоприоритетная задача
- По завершению возвращается в планировщик
- А планировщик возвращается в место, где вместе с разрешением прерываний атомарно генерируется SVC исключение
- И снова начинается процесс входа в исключение SVC
- В обработчике исключения SVC мы убираем со стека тот фиктивный кадр исключения, что добавили в PendSV и оставляем только кадр от вытесненной задачи
- Выходим из SVC, выполняем unstacking и попадаем обратно в вытесненную задачу
Опа и все должно работать… Теперь тоже самое на lisp (не нашел, как вставить код на ассемблере, чтобы он корректно отображался, и были видны комментарии, поэтому вставил разметку как на lisp) ассемблере.
Планировщик
Ну а теперь посмотрим, как устроен Ооочень простой планировщик. Чтобы показать насколько он простой — сразу покажу картинку, она много прояснит: Всего 4 публичных метода, остальное скрыто от пользователя от греха подальше.
Как вы понимаете, вся суть тут заложена в методе Schedule() и он должен быть экстремально простым. Поэтому мы сделаем так, чтобы приоритет задачи определялся её положением в списке задач. Ну т. е., чтобы если мы задали бы задачи так:
То это бы означало, что приоритет HighPriorityTask — самый высокий, а idleTask — самый низкий. Это нам решит кучу проблем с сортировкой списка задач. Задачи всегда расположены в порядке уменьшения приоритета.
Тогда наш планировщик будет совсем-совсем простым.
Функция запуска задачи тоже проста как пять копеек:
Как видно, задача должна реализовывать метод OnEvent().
И да, мы же не хотели использовать указатели, поэтому задачи передаем через ссылки, как параметр шаблона.И очень просто пробегаемся по этому списку, например, чтобы найти первую (самую высокоприоритетную) активную задачу:
Заметьте, никаких массивов указателей на задачи, а поэтому не существует даже теоретической возможности на выход за пределы массива
Собственно и запускаем на исполнение по такому же принципу:
Чтобы задача активировалась ей надо просигналить, ну например, случился таймаут канального уровня у какого-нибудь протокола (в Modbus RTU аж два таймера на 3,5 символа и 1,5 символ) и надо обработать событие по приему сообщения — да ради бога — посылаем из таймера задаче, обработчику приема сообщения, событие.
Выше я уже указывал, что нельзя просто так взять и запустить планировщик из прерывания, нужно из этого прерывания как-то выйти вначале, а потом уже запустить — и это мы делаем путем вызова PendSV.
В примере я сделал события от таймеров, которые построил на основе системного таймера. Обработчик прерывания системного таймера показан ниже:
А таймеры просто постят события
Задачи
Задачи должны наследоваться от TaskBase. В него я запихнул атрибут событие, чтобы у каждой задачи был такой атрибут, но так как он статический, то чтобы этот атрибут был разный для каждой задачи, применил странно-рекурсивный шаблон.
В коде это будет так:
Также я сделал 3 , нет 4 задачи, 3 из которых моргают светодиодами, а одна ничем не моргает, хотя должна, но её все время вытесняют .
Задачи сами просто так не запустятся, нужно, чтобы кто-то им запостил событие, а такими сущностями в моем примере являются таймеры. Их надо настроить для каждой задачи индивидуально.
Ну и все запускаем.
.
Все лежит в Github Исходный код. Можно просто папку открыть в Clion.Заключение
4 задачи моргания светодиодом + сам планировщик занимает 564 байт кода + 14 байт константных данных и 17 байт ОЗУ без оптимизации.