Что такое embedded разработка
Embedded systems: что это? Коротко про встраиваемые системы
Embedded программист — это уникальный специалист по работе со встраиваемыми системами управления приложениями в реальном времени. Данные системы (Embedded systems) состоят из 3-х основных вещей:
Решение поставленных задач на прикладном уровне. В этом случае нужно просто найти эффективные методы и инструкции без их детальной разработки.
Само программирование. При этом необходимо внедрять все полученные решения из прикладного уровня и корректировать, беря во внимание аппаратное обеспечение устройства.
Реализация. Когда вся команда, участвующая в разработке, выполняет все сформулированные требования к продукту, такие как соблюдение точной функциональности, защищенность и надежность в эксплуатации, точные технические характеристики и др.
Embedded System — специальная система подобранных аппаратных и программных компонентов, которая отвечает за точное выполнение приложением всей возложенной на него функциональности. Часто такие системы разрабатывают для конкретных приложений или устройств. Embedded-программист — это специалист, который разрабатывает, тестирует и обслуживает эти системы.
Embedded system — что это?
Embedded System — это системы, которые выстраиваются на уровне микропроцессоров и микроконтроллеров. Они отвечают за какие-то специальные функции приложения или устройства и являются частью более крупных систем приложения, а не самостоятельной частью.
Где используются Embedded System?
Embedded System применяются во многих областях человеческой жизни. Так как IT-сфера постоянно развивается, то и применение встроенных систем также расширяет свою сферу деятельности. На данный момент Embedded System можно найти в:
бортовом компьютере автомобиля;
системах безопасности и сигнализации;
Как работают Embedded System?
ASIC — интегральные схемы;
FPGA — программируемые логические матрицы;
прочие компоненты, предназначенные для наладки взаимодействия с интерфейсом пользователя.
Как программируют Embedded System?
Программирование Embedded System не ограничивается только знаниями самого языка программирования, также нужно понимание электроники, информатики, автоматизации процессов, робототехники и друго го — многое зависит от области применения встраиваемых систем. Поэтому можно сказать, что Embedded-программист — это не просто программист, а специалист широкой направленности.
Чтобы встраиваемая система получилась максимально успешной, к ее разработке нужно подходить очень ответственно и обязательно хорошо продумать архитектуру и функциональность. Очень часто небольшие ошибки приводят к тотальному провалу систем, поэтому программирование должно быть аккуратным, а тестирование — очень тщательным.
Иногда Embedded System бывают настолько сложными, что их разработка превращается в целое событие, которое управляется несколькими командами инженеров и программистов.
Заключение
технологий дополненной и виртуальной реальности;
Поэтому стоит рассмотреть Embedded-программирование как род своей будущей деятельности.
Мы будем очень благодарны
если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.
Embedded-программист
Embedded-программист — это специалист, занимающийся разработкой, сопровождением, тестированием встроенного программного обеспечения. Кстати, в 2021 году центр профориентации ПрофГид разработал точный тест на профориентацию. Он сам расскажет вам, какие профессии вам подходят, даст заключение о вашем типе личности и интеллекте.
Краткое описание
Представители этой профессии являются узкоспециализированными специалистами, оплата труда которых очень высокая. Они могут работать в офисе, но часто их труд сопряжен с командировками, во время которых специалист проводит тюнинг, настройку или тестирование оборудования и ПО в филиалах компании.
Чаще всего работодатели требуют, чтобы у разработчика был стаж работы не менее 3-5 лет, а также важно наличие поверхностных знаний об особенностях того оборудования, для которого ему придется разрабатывать ПО.
Особенности профессии
Опытный embedded-программист высоко ценится работодателем, ведь не каждый человек, получивший профильное образование, выполняет свою работу качественно. Специалист может работать с разным программным обеспечением, но чаще всего он выбирает для себя оборудования одного типа, которым занимается в течение всей своей карьеры.
В обязанности представителя этой профессии входит:
Этот специалист работает в команде людей, которые занимаются разработкой встроенного ПО, поэтому он должен уметь подчиняться руководителю, точно выполняя его требования и поставленные задачи.
Специалист должен иметь высшее техническое образование, отлично знать специфику оборудования и программного обеспечения.
Заметим, что если вы страдаете от заболеваний сердца, органов зрения, аллергии, тремора, то эту специальность лучше не выбирать в качестве профильной.
Разработка встроенного ПО: введение
Привет, Хабр! Представляю вашему вниманию перевод статей Chris Svec, оригинал здесь. Публикуется с разрешения автора по лицензии CC-A-NC-ND.
Embedded software engineering 101: введение
Я запускаю цикл статей по обучению разработке встроенного программного обеспечения. Мы начнем с описания простого микроконтроллера, и после того, как Вы поймете, как он работает, разовьем это до понимания работы относительно сложных систем, таких как Fitbit или Nest.
Я назвал эту серию Embedded Software Engineering 101, и она начинается с этой недели прямо здесь в этом блоге.
Продолжайте читать для дальнейших объяснений и подробностей.
Одни строительные блоки на других строительных блоках.
Я работаю со встроенными системами и разрабатываю микросхемы уже более 14 лет. Мне нравятся встроенные системы — железо, софт и ограничения, которые связывают их вместе.
Любительская электроника и такие идеи, как Arduino, Adafruit и Sparkfun дали возможность легко накидать что-то из железа и софта за выходные (или месяц, или семестр), создав что новое, интересное и может быть даже полезное.
Это здорово! Предоставление людям возможности созидать — изумительная штука; если бы я хотел выражаться выспренно, то с придыханием назвал бы это «демократизирующей технологией».
Большая часть любительских проектов единовременные. Вы собираете нечто, делаете это настолько хорошим, насколько хватает времени или энергии, и двигаетесь дальше.
Я провел свою карьеру на противоположном конце спектра — создавая продукцию, которая выпускается в сотнях тысяч или миллионах или больше экземпляров — и это требует совсем другого образа мышления и системного подхода.
Я хочу учить людей, как писать встроенное ПО для такого рода систем. Я уже давно вынашивал эту идею курса/руководства/книги/блога «Embedded Software Engineering 101», и благодаря блогу Embedded.fm начинаю ее реализацию сейчас.
Я человек фундаментального типа, так что мой план — начать с основ, с простого описания простого микропроцессора, и развивать эту основу, пока вы не поймете, как работает относительно сложная встроенная система.
Моя цель — чтобы к концу этого цикла вы могли разобраться как работает Fitbit, термостат Nest или подобная встроенная система. Вы сможете начать работать со встроенными программными системами используя профессиональный опыт.
Embedded Software Engineering 101 предназначен для:
Так вот, я не Фейнман, но я уверен, что лучший способ понять систему — это начать с основ. Вооруженные этим пониманием, вы сможете создавать простые встроенные системы с простым софтом. И поняв сначала очень простую программу, вы сможете развивать это, создавая более сложное ПО по мере роста опыта.
Основы в первую очередь — это конечно только мое личное убеждение. Множество людей сделали полезные штуки с Ардуино без понимания чего бы то ни было из основ. Этот цикл статей для тех, кто все-таки хочет понимать основы и все, что на них построено.
Конечно мы должны задаться вопросом — где правильный уровень чтобы начать с этих самых «основ»? Транзисторы и логические вентили? Нет, это слишком низкий уровень для старта со встроенным ПО. Подключение к распространенным датчикам? Нет, это слишком высокий уровень, требуется слишком много знаний чтобы начать с этого.
Я думаю правильный уровень основ это встроенный микропроцессор. Не обязательно понимать физику или электронику чтобы использовать встроенный микропроцессор, также не обязательно быть экспертом в программировании.
Так что с этого мы и начнем в следующей статье.
Предупреждение о предвзятости: в прошлой жизни я был архитектором/разработчиком процессоров. Начать этот цикл с понимания как работает ЦПУ может быть не лучшим способом для понимания встроенных систем, но именно так работает мой мозг. Обязательно попробуйте другие курсы/руководства и т.д., если не станете понимать этот после нескольких статей.
Embedded software engineering 101: основы микроконтроллера
Мы начнем наше путешествие Embedded Software Egineering 101 со скромного микроконтроллера. Микроконтроллер (или микропроцессор) это основной строительный блок всех вычислительных систем, встроенных и прочих.
МК кажется довольно сложным, но он состоит из трех простых вещей: инструкции, регистры и память. Инструкции это те штуки, которые микроконтроллер знает как выполнять. Простой МК умеет выполнять не так уж много — у него может быть например 20 или 30 инструкций. В дальнейшем в этом цикле я буду использовать микроконтроллер MSP430 от Texas Instruments, у которого только 27 инструкций.
Просто фотография МК (TI MSP430F5529)
Эти 27 инструкций — единственное, что MSP430 умеет делать. Он может сложить два числа, вычесть из одного числа другое, переместить числа с одного места в другое или выполнить 24 другие простые операции. 27 операций может показаться недостаточно чтобы сделать что-либо полезное, но на самом деле их хватит с избытком, чтобы выполнить любую мыслимую программу.
Хорошо, значит у микроконтроллера есть инструкции, которые делают что-то с числами. Но где находятся эти числа? Регистры и память! Инструкции оперируют числами, которые хранятся в регистрах и памяти.
Регистры это очень быстрое хранилище, содержащее числа, которыми оперируют инструкции. Можно думать о них, как об используемом инструкциями блокноте. МК содержит немного регистров, обычно 8-32. Например, у MSP430 16 регистров.
Память это тоже хранилище для чисел, но она гораздо объемнее и медленнее чем регистры. У микроконтроллера может быть 64 кБ, 256 кБ или даже более 1 МБ памяти. У MSP430F5529 около 128 кБ памяти; это более чем в 8000 раз превосходит количество его регистров!
Прежде чем мы начнем рассматривать примеры, я призываю вас достать лист бумаги и ручку или карандаш и прорабатывать эти примеры по мере чтения. Прорабатывать их на бумаге сложнее, чем просто читать, что я написал. Таким образом вы внимательнее подойдете к процессу, и шансы на запоминание изученного будут выше.
Давайте рассмотрим вымышленный, но характерный пример микроконтроллера.
Пусть скажем у нашего МК 4 регистра и 8 ячеек памяти. Регистры обычно называют как-нибудь креативно, например «R0», «R1» и т.д., поступим и мы так же. На ячейки памяти обычно ссылаются по их номерам, также называемым адресами памяти, начиная нумерацию с 0. Вот так будут выглядеть наши регистры и память:
И теперь я помещу в них некоторые значения:
Теперь нашему вымышленному микроконтроллеру нужны какие-нибудь инструкции.
Совокупность инструкций, которые знает МК, называется его набором инструкций. Пусть скажем в наборе будет три инструкции: ADD (сложить), SUB (сокращение от «subtract» — вычесть) и MOVE (переместить). Инструкции должны получать откуда-то числа, которыми они оперируют, и также помещать куда-то свои результаты, так что некоторые из них содержат информацию о том, где находятся входные и выходные данные.
Пусть, например, у нашей инструкции ADD два источника и один приемник данных, и все они должны быть регистрами. Руководство может описывать эту инструкцию примерно так:
ADD регИст, регПрм
Инструкция ADD добавляет значение регистра «регИст» к значению регистра «регПрм» и сохраняет результат в регистре «регПрм»
Резюме: регПрм = регИст + регПрм
Пример: ADD R1, R2 выполняет операцию R2 = R1 + R2
Это общепринято в инструкциях — использовать один из источников также в роли приемника, как делает инструкция ADD, используя регПрм в качестве и источника и приемника данных.
«ADD R1, R2» — это язык ассемблер для микроконтроллера, это нативный язык программирования МК.
Давайте определим SUB в том же стиле:
SUB регИст, регПрм
Инструкция SUB вычитает значение регистра «регИст» из значения регистра «регПрм» и сохраняет результат в регистре «регПрм»
Резюме: регПрм = регПрм — регИст
Пример: SUB R3, R0 выполняет операцию R0 = R0 — R3
И наконец пусть у инструкции MOVE один источник и один приемник, и либо:
1. MOVE регИст, регПрм
2. MOVE памИст, регПрм
3. MOVE регИст, памПрм
Инструкция MOVE копирует данные из аргумента Ист в аргумент Прм.
Резюме: есть три типа инструкции MOVE
1. регПрм = регИст
2. регПрм = мемИст
3. мемПрм = регИст
Пример: я покажу примеры инструкции MOVE ниже в этом посте.
Одно замечание о слове «move», используемом для этой инструкции: большая часть наборов инструкций используют именно его, хотя в действительности данные копируются, а не перемещаются.
Название «move» может создать впечатление, что операнд-источник инструкции уничтожается или очищается, но на самом деле он остается в покое, модифицируется только приемник.
Давайте пройдемся по нескольким примерам используя наш вымышленный микроконтроллер.
На старте наши регистры и память выглядят так:
Теперь выполним на МК следующую инструкцию:
Она берет значение R1, складывает его со значением R2 и сохраняет результат в R2. Процессор выполняет большую часть инструкций за одну операцию, но я разобью выполнение каждой инструкции ADD, SUB и MOVE на несколько шагов стрелкой «=>» ведущей через замены (регистр/память => значение):
После выполнения этой инструкции память неизменна, но регистры теперь выглядят следующим образом, с изменившимся значением написанным красным:
Обратите внимание, что R1 неизменен; изменился только регистр-приемник R2.
Следующей давайте попробуем инструкцию SUB:
Она берет значение R3, вычитает его из значения R0, и сохраняет результат в R0:
После выполнения этой инструкции память неизменна, но регистры теперь выглядят таким образом:
И наконец давайте попробуем пару версий инструкции MOVE:
Эта инструкция MOVE копирует в R0 значение R2:
И теперь регистры выглядят так:
Дальше мы скопируем регистр в память:
Эта инструкция MOVE копирует в ячейку памяти 3 значение R3. Квадратными скобками в нашем наборе инструкций обозначаются ячейки памяти.
Регистры неизменны, но память меняется:
И для нашего последнего примера мы скопируем значение из памяти в регистр:
Здесь значение ячейки памяти 6 копируется в регистр R0:
Память неизменна, а регистры теперь выглядят следующим образом:
Верите или нет, но если вы поняли большую часть того, что мы только что обсудили насчёт инструкций, регистров и памяти, то вы понимаете основы микроконтроллеров и языка ассемблер.
Конечно я опустил множество деталей. Например, как МК получает инструкции для выполнения?
Есть ли более интересные инструкции, чем только простые математические и инструкции копирования? Память это то же самое, что RAM или флэш, или нет?
Мы ответим на эти вопросы в следующей статье.
Кто такой Embedded-разработчик. Обзор изнутри от Вадима Егораева
О профессии рассказывает Вадим Егораев, Software Engineering Team Leader в ЕРАМ, 10 лет в ИТ, развивает программы тренингов для Embedded-разработчиков.
Продолжаем цикл материалов про ИТ-специальности. Каждую из них описывает «типичный представитель» — опытный специалист. Надеемся, что цикл поможет школьникам, студентам, переквалификантам, джуниорам и сочувствующим выбрать специальность в ИТ, оценить свои перспективы или просто сверить часы с авторитетным коллегой. Можно обсуждать и дополнять материал в комментариях, чтобы сделать его ещё полезней. Спикер и автор материала поддержат дискуссию и ответят на вопросы.
О профессии рассказывает Вадим Егораев, Software Engineering Team Leader в ЕРАМ, 10 лет в ИТ, развивает программы тренингов для Embedded-разработчиков.
Продолжаем цикл материалов про ИТ-специальности. Каждую из них описывает «типичный представитель» — опытный специалист. Надеемся, что цикл поможет школьникам, студентам, переквалификантам, джуниорам и сочувствующим выбрать специальность в ИТ, оценить свои перспективы или просто сверить часы с авторитетным коллегой. Можно обсуждать и дополнять материал в комментариях, чтобы сделать его ещё полезней. Спикер и автор материала поддержат дискуссию и ответят на вопросы.
Что такое Embedded-разработка?
Это разработка встроенного программного обеспечения. Я определяю Embedded-разработку как «практически всё, что делается не под компьютер, мобильный телефон и сервер».
Встроенное ПО есть во многих привычных вещах. Например, беспроводные наушники — передача данных на них идёт при помощи софта. Веб-камеры — в звуковой карте есть ПО, которое управляет громкостью, входами, выходами, переключает их. Ещё люди ездят на машинах, смотрят телевизор, фотографируют на камеру и т. д. — процессоры сейчас есть практически везде, и Embedded-разработчики пишут для них ПО.
Как развивалась отрасль?
Активное развитие устройств со встраиваемыми системами началось, когда процессоры стали низкопотребляющими, достаточно дешёвыми в выпуске и внедрении. Личный пример: если в 1988 году у моего Audi 100 не было внутри никакой цифровой электроники, то в середине 90-х появились первые устройства, которые управляли системой антиблокировки тормозов (ABS) на основе процессора. А с 2000-х цифровой электроникой стали контролироваться стёкла, дворники, фары и всё, что угодно.
Последние 5 лет в тренде — умные устройства вроде интеллектуального чайника, роботов-пылесосов, смарт-ламп и область IoT в целом. Развитие направления происходит, в том числе, благодаря таким платформам, как AWS, Azure, Microsoft и GCP, к которым можно быстро и качественно подключать сотни тысяч умных устройств, а затем обрабатывать данные с них.
Чем занимается Embedded-разработчик?
Рассказать легче в сравнении. У разработчика, который пишет на Java, Python или других высокоуровневых языках, 70–80% времени уходит, чтобы создать само ПО. То есть бизнес-логику — разработку того, что приходит из требований заказчика.
У Embedded-инженеров наоборот: они используют 70% времени, чтобы заставить платформу работать. Мы создаем все условия, чтобы ПО запустилось, можно было принимать данные от сенсоров, управлять мотором, работать с драйверами и загрузчиками, светить светодиодами и так далее в зависимости от контекста. Делаем то, что не связано с непосредственными задачами устройства.
Типичные задачи Embedded-разработчиков: написать что-то, что даёт данные или куда-то их пересылает. Например, принять данные от температурного датчика и передать их в облако. Другой задачей может быть написание, скажем, драйвера сенсора.
Какие знания пригодятся?
Завершу список упорством и постоянным саморазвитием. Элементная база непрерывно обновляется, как и наборы инструментов, и среды разработки — нужно изучать, что происходит в отрасли и около неё, чтобы быть «на гребне волны».
В каких направлениях можно работать?
Самые разные сферы, диапазон проектов большой. В ЕРАМ я занимался и рулевым управлением автомобиля, и кофеваркой, и зарядкой для электромобилей, которую используют в частных домах, и преобразователями интерфейсов, и сетевым ускорителем DNS запросов и многим другим. Всё зависит от специфики компании, в которой вы работаете, и доменов заказчиков. В любом случае, Embedded-разработка всегда очень интересная и разноплановая, особенно в больших компаниях, куда приходят разные клиенты.
Возможность смены профессии
Вариантов много. У нас даже шутка есть: главное требование для разработчиков встроенного ПО — не убежать в Java. 😊 Потому, что на Java сегодня пишутся много интересных проектов для самых разных областей. Например, в нашем департаменте Embedded, Standalone & IoT Delivery есть и Java, и Python, и Ruby, и Go, и еще много разных стеков. Что привлекает и держит меня в своей области? Возможность видеть вживую и трогать руками то, что делаешь (плату, например), а потом сразу наблюдать за результатом работы. Твой продукт не просто где-то там на сервере крутится на другой стороне планеты, а лежит у тебя на столе, светится, издает звуки и т. д.
Где учиться?
Если с нуля, то лучший вариант базы — электроника (сам так начинал). Ребята, которые закончили кафедры БГУИР, БНТУ и региональных технических университетов по специальностям промышленной электроники, автоматизации или общей электроники, имеют подходящий набор скиллов и знаний (например, понимание цифровых схем и азов программирования), чтобы стать хорошим Embedded-разработчиком. Потом им остается прокачаться в области ПО. Кстати говоря, мы так и делаем: доучиваем студентов-электронщиков, чтобы потом взять их в команду.
Переквалификация. В Embedded проще прийти из C и С++. Это лучший вариант: так как нам довольно часто приходится работать с памятью, то очень важными оказываются знания в логике работы указателей, которые есть далеко не во всех языках программирования.
Для самостоятельного обучения из онлайн-курсов могу посоветовать Coursera и LinkedIn Learning. Там есть и про Linux, и про операционные системы, и про разработку встроенного ПО, и про криптографию:
П. С. Про работу и хобби
Помимо разработки я увлекаюсь музыкой, сейчас активно учусь игре на электрогитаре. Моя работа и хобби пересекаются и дополняют друг друга, ведь электронная музыка и встроенное ПО сильно связаны. Вообще я и начинал изучать электронику со звуковой и звуковоспроизводящей электроники. Знаю, как работают микрофоны, динамики, звуковые карты, цифровые сигнальные процессоры, как создаются звуковые эффекты — это помогает мне развиваться как музыканту, видеть мир звуковоспроизведения и звукозаписи с нескольких сторон. Например, я понимаю, какие звуковые волны происходят из моего музыкального инструмента, как они распространяются, почему что-то идет не так или не работает. А различные звуковые эффекты мне представляются цифровыми фильтрами определенного рода.
Среди моих знакомых Embedded и IoT-инженеров есть много ребят с очень интересными и необычными хобби, они постоянно пробуют что-то новое. Пожалуй, это отлично поддерживает креативность и вдохновляет мыслить outside the box — советую!
Хотите сообщить важную новость? Пишите в Телеграм-бот.
А также подписывайтесь на наш Телеграм-канал.
Два подхода к проектированию ПО для embedded
Хочу немного рассказать о двух подходах проектирования ПО в embedded. Два подхода эти – c использованием суперцикла или же с использованием RTOS (Real-Time Operation System, операционная система реального времени).
Думаю, что по ходу рассказа будет понятно также, в каких именно случаях стоит применять первый, а в каких не обойтись без второго.
Надеюсь, будет интересно всем тем, кто хочет заглянуть в мир разработки для встраиваемых систем. Для тех, кто в embedded уже собаку съел, скорее всего, не будет ничего нового.
Совсем немного теории (для тех, кто делает самые первые шаги).
Есть у нас микроконтроллер, представляющий из себя собственно процессор, немного памяти и различную периферию, например: аналого-цифровые преобразователи, таймеры, Ethernet, USB, SPI – все это сильно зависит от контроллера и решаемых задач.
Ко входу АЦП можно, например, подключить какой-нибудь датчик, скажем, температурный сенсор, который при подаче на него питания преобразует температуру в напряжение, измеряемое этим АЦП.
А к выходу контроллера, называемому GPIO (General Purpose Input-Output) можно, к примеру, подключить светодиод (или же что-нибудь более мощное вроде моторчика, но уже через усилитель).
Через SPI, RS232, USB и т.п. контроллер может связываться с внешним миром более сложным способом – получая и отсылая сообщения по заранее заданному протоколу.
В 90% случаев ПО пишется на С, иногда может использоваться С++ или ассемблер. Хотя все чаще появляются возможности писать на чем-нибудь более высокоуровневом, если это не касается непосредственной работы с периферией и не требуется максимально возможное быстродействие.
Чтобы лучше представить, с чем приходится иметь дело, вот пара примеров окружений, с которыми приходится работать: размер FLASH контроллера (аналог жесткого диска) – 16-256 килобайт, размер RAM – 64-256 килобайт! И в таком окружении реально запустить не только приложение, а еще и операционную систему реального времени с полноценной поддержкой многозадачности!
Примеры ниже – на псевдокоде, местами очень похожем на С. Без подробностей реализации там, где это несущественно для понимания.
Итак, подход «суперцикл».
Программа в этом подходе выглядит проще простого:
Бесконечный цикл, в котором контроллер последовательно делает все, что он должен делать.
Самое интересное, конечно же, во встраиваемых системах – это работа с периферией (теми самыми АЦП, SPI, GPIO и т.д.). С внешней периферией контроллер может работать двумя способами: опрашивая или используя прерывания. В первом случае, если мы хотим, например, прочитать символ из RS232 консоли, то мы будем периодически проверять, нет ли там символа, до тех пор, пока его не получим. Во втором же случае мы настраиваем RS232 контроллер так, чтобы он генерировал прерывание в тот момент, когда появится новый символ.
Демонстрация первого подхода. Например, хотим мы следить за температурой, а если она превысит установленный лимит – зажечь светодиод. Выглядеть это будет как-то так:
Пока все должно быть просто и понятно. (Функции чтения температуры и манипуляций со светодиодом приводить не буду – это не цель данной статьи).
Но что, если нам нужно делать что-то с заданной периодичностью? В примере выше температура будет проверяться с максимально возможной частотой. А если нам, например, нужно мигать светодиодом раз в секунду? Или опрашивать датчик строго с интервалом в 10 миллисекунд?
Тогда на помощь приходят таймеры (которые есть практически у любого микроконтроллера). Работают они так, что генерируют прерывание с заданной частотой. Мигание светодиода тогда будет выглядеть как-то так:
Особенность работы с прерываниями такова, что обработчик прерывания (код, который будет вызван непосредственно в тот момент, когда прерывание произойдет) должен быть как можно более коротким. Поэтому наиболее часто встречаемое решение – в обработчике установить глобальную переменную-флаг (да-да, без глобальных переменных никуда, увы), а в основном цикле ее проверять, и когда она изменится, выполнить уже основную работу, требуемую для обработки возникшего события.
Эта самая глобальная переменная обязательно должна быть объявлена с идентификатором volatile – иначе оптимизатор может банально «выбросить» неиспользуемый с его точки зрения код.
А если нужно будет мигать двумя светодиодами, так чтобы один мигал раз в секунду, а второй – три раза? Можно, конечно, использовать два таймера, но с таким подходом таймеров нам надолго не хватит. Вместо этого сделаем так, чтобы таймер работал с гораздо более высокой частотой, а в программе будем использовать делитель.
Заметьте, нам не нужно следить за переполнением счетчика миллисекунд, так как применяется беззнаковый тип.
Представим теперь, что у нас есть отладочная консоль, реализованная поверх интерфейса RS232 (самое распространенное решение в мире embedded!). И мы хотим выводить туда отладочные сообщения (которые будут видны, если наш контроллер подключить к компу через COM-порт). А одновременно с этим нам нужно со строго заданной (при этом высокой) частотой опрашивать датчик, подключенный к контроллеру.
И вот здесь возникнет вопрос – как реализовать такую банальную вещь, как вывод строки в консоль? Очевидное решение вроде
будет в данном случае недопустимым. Строку-то оно выведет, но при этом необратимо нарушит требование опрашивать датчик со строго заданной частотой. Мы же все это делаем в одном большом цикле, где все действия выполняются последовательно, помните? А консоль – устройство медленное, и вывод строки может занять гораздо больше времени, чем требуемый интервал между последовательными опросами датчика. Пример ниже — то, как делать не надо!
Еще пример – захотите вы реализовать программную защиту от перегрузки. Добавите измеритель тока, подключите его к АЦП контроллера, управление предохранительным реле заведете на один из пинов входа-выхода. И естественно захотите, чтобы защита срабатывала как можно быстрее после наступления события перегрузки (а иначе все просто сгорит). А у вас – все тот же общий цикл, в котором все действия выполняются строго по порядку. И гарантированное время реакции на событие никак не может быть меньше, чем время выполнения одной итерации цикла. И если в этом цикле будут операции, требующие для своего завершения длительного времени – то собственно все, именно они и будут задавать время реакции системы на все остальное.
А если вдруг где-то в этом цикле закрадется ошибка – то «ляжет» вся система. В том числе и реакция на перегрузку (чего допускать очень не хотелось бы, не так ли?).
Хотя с первой проблемой теоретически можно еще что-то сделать. Например, заменить простейшую, но долгую функцию печати строки на что-то вроде:
А простой вызов этой функции на что-то вроде:
Мы в результате сократили время прохода одного цикла со времени, необходимого для печати целой строки, до времени, необходимого для печати одного символа. Но для этого нам пришлось вместо примитивной и всем понятной с первого взгляда функции вывода строки в консоль добавить в код две машины состояний – одну для печати (чтоб запоминать позицию), а вторую – собственно для печати, чтобы помнить, что мы сейчас печатаем строку на протяжении нескольких следующих циклов. Да здравствуют глобальные переменные, «грязные» функции, хранящие состояния, и тому подобные замечательные штуки, которые запросто и очень быстро способны превратить код в несопровождаемое спагетти.
А теперь представьте себе, что система должна одновременно опрашивать с десяток датчиков, реагировать на несколько критических событий, требующих немедленной реакции, обрабатывать команды, «прилетающие» от пользователя или компьютера, выводить отладочные сообщения, управлять десятком индикаторов или манипуляторов. И для каждого из действий заданы свои ограничения по времени реакции и частота опроса или управления. И попробуйте все это запихнуть в один последовательный общий цикл.
Безусловно, это все реально. Но вот тому, кому придется это все сопровождать спустя хотя бы год после написания, я не позавидую.
Еще одна проблема дизайна «общий цикл» — сложность измерения загруженности системы. Предположим, у вас есть код:
Система как-то реагирует на прерывание, приходящее извне. И вопрос – сколько таких прерываний в секунду система сможет обработать? Насколько будет занят процессор при обработке 100 событий в секунду?
Вам будет очень сложно измерить, сколько времени было потрачено на обработку событий, а сколько – на опрос переменной «А не произошло ли прерывание?». Ведь все выполняется в одном цикле!
И вот здесь на помощь приходит второй подход.
Применение операционной системы реального времени.
Проще всего ее применение проиллюстрировать на том же примере: одновременный опрос датчика с заданной частотой и вывод на консоль длинной отладочной строки.
Как видите, в главной функции больше нет одного главного бесконечного цикла. Вместо него – отдельный бесконечный цикл в каждой задаче. (Да-да, функция os_start_sheduler(); никогда не вернет управление!). И что самое главное – у этих задач есть приоритеты. Операционная система сама обеспечит то, что нам нужно – чтобы задача с высоким приоритетом выполнялась прежде всего, а с низким – только лишь тогда, когда ей остается время.
И если время реакции на, например, прерывание в дизайне с суперциклом будет равно в худшем случае времени выполнения всего цикла (прерывание-то случится, конечно же, сразу же, но далеко не всегда необходимые действия можно сделать непосредственно в обработчике), то время реакции в случае ОС реального времени будет равно времени переключения между задачами (которое достаточно мало, чтобы считать, что это происходит сразу же!). Т.е. прерывание произойдет в одной задаче, а сразу по его завершению мы переключимся на другую задачу, ожидающую события, «запущенного» из прерывания.
Что касается измерения загрузки процессора – то и эта задача с применением ОС становится тривиальной. По умолчанию каждая ОС имеет самую прожорливую (но и самую низкоприоритетную) задачу Idle, которая выполняет пустой бесконечный цикл и получает управление лишь тогда, когда все остальные задачи неактивны. И подсчет времени, проведенного в Idle, обычно тоже уже реализован. Остается лишь его вывести в консоль.
Также если вдруг вы «не заметите» какую-нибудь ошибку, то «упадет» только та задача, в которой будет присутствовать ошибка (возможно также, что и все задачи с более низким приоритетом тоже), но задачи с более высоким приоритетом продолжат выполняться, обеспечивая хотя бы минимальные жизненно важные функции устройства, например, защиту от перегрузки.
И подводя итог: если система очень простая и нетребовательная ко времени реакции, ее проще сделать по образцу «суперцикл». Если же система собирается стать большой, соединяющей в себе много разных действий и реакций, которые к тому же критичны ко времени – то альтернативы использования ОС реального времени нет.
Кроме этого, плюс использования ОС – более простой и понятный код (поскольку мы можем группировать код по задачам, избегая глобальных переменных, машин состояний и прочего мусора, необходимого при использовании дизайна с суперциклом).
Минус же использования ОС – для ее использования требуется больше места, памяти, опыта и знаний (хотя ничего сложного там и нет, все же многозадачность априори сложнее и непредсказуемее, чем последовательно выполняющийся код). Обязательно хорошее понимание принципов работы в многозадачной среде, принципов потокобезопасного кода, синхронизации данных и многого другого.
Для «поиграться» можно взять FreeRTOS – бесплатный проект с открытым кодом, при этом достаточно стабильно работающий и простой в освоении. Хотя не редкость и коммерческие проекты с использованием именно этой операционки.