Проектирование встроенных систем подразумевает создание аппаратного обеспечения (печатной платы) и встроенного ПО. На каждом из этих направлений разработчики сталкиваются с неочевидными трудностями – неочевидными прежде всего для заказчиков. Здесь я хотел бы перечислить самые частые сложности, с которыми мне и моей команде приходится иметь дело при разработке встроенной электроники. Для ускорения проектирования аппаратное и программное обеспечение разрабатываются параллельно. Но чтобы никого не запутать, я буду рассматривать их и связанные с ними трудности отдельно. Создание нового продукта начинается с возникновения и проработки идеи. Однако наша команда занимается контрактной разработкой электроники, поэтому с готовыми идеями к нам приходят заказчики. Соответственно, на первом этапе мы собираем требования к будущей системе: Функциональные требования, которые описывают что именно должно делать устройство; Нефункциональные требования, которые описывают важные свойства и ограничения будущего устройства (габариты, производительность, надежность, условия работы, законодательные ограничения и т.п.). И здесь мы сталкиваемся с первой типовой сложностью. Технически подкованные заказчики хорошо объясняют, что они хотят получить, предоставляют цифры и точные описания. А вот клиенты, у которых нет технического образования, в основном описывают функционал устройства, но не понимают техническую сторону вопроса. Нередко им сложно оценить и реализуемость проекта. Поэтому самое важное на данном этапе – это вытянуть из клиента все релевантные требования: что должно делать устройство, сколько должно весить, какое будет питание, в каких условиях должно работать и т.д. Некоторым заказчикам кажется, что мы задаем слишком много вопросов о мелочах. Но такой подход позволяет собрать максимально полные требования к продукту, не упустить важную информацию, а значит, избежать проблем на последующих этапах.  Классическая проблема заключается в том, что заказчики не сообщают команде какие-то детали, которые им кажутся очевидными, само собой разумеющимися.  Так, когда мы работали над системой умного дома, заказчик предположил, что при срабатывании пожарной сигнализации система будет оповещать пользователя. Однако он ни разу не проговорил это требование и подписал ТЗ, в котором эта функция также не упоминалась. А когда дело дошло до тестирования, удивился отсутствию этого функционала. Именно поэтому на данном этапе важно задавать самые разные вопросы. На основе собранных требований к продукту мы формируем техническое задание. Кроме того, на этом этапе не стоит обещать заказчику больше, чем команде по силам выполнить. Если запрашиваемое клиентом решение нереализуемо, выгоднее честно в этом признаться, выделить решаемую задачей проблему и предложить какую-то альтернативу или компромисс, чем вызвать у заказчика завышенные ожидания. Если ТЗ содержит в себе перечень требований к продукту, то техническое предложение содержит перечень решений стоящих перед разработчиками задач, т.е. описывает архитектуру создаваемой встроенной системы. В рамках проектирования аппаратного обеспечения на этом этапе команда выбирает компоненты для будущей платы. Сложность здесь заключается в том, чтобы учесть различные ограничения: А) Технические характеристики: объем памяти и производительность микроконтроллера, энергопотребление, тип питания, габариты, вес и т.д. Б) Условия эксплуатации: влажность, экстремально высокие или низкие температуры, вибрация и т.п. В) Стоимость и качество компонентов, которые определяют себестоимость конечного продукта. Г) Доступность компонентов, которая определяет возможность производить продукт серийно (если перед заказчиком стоит такая задача). Приступая непосредственно к проектированию платы, разработчики должны учесть массу тонкостей. Вот некоторые из них: Необходимо придерживаться правил проектирования печатных плат, чтобы продукт получился надежным, долговечным и безопасным. Много внимания уделяется отведению тепла. Должного охлаждения можно добиться за счет корректного размещения компонентов, добавления переходных отверстий, увеличения площади меди, установки радиаторов. Опыт научил нас, что, имея дело с каким-то компонентом впервые, сперва стоит его протестировать, потому что реальные характеристики иногда отличаются от тех, что заявлены в документации. Разработчики должны предусмотреть защиту от электростатических разрядов, электромагнитных помех, неблагоприятных условия среды и других факторов. Если продукт предполагается выпускать серийно, необходимо придерживаться принципов DFM (учитывать требования производства). Если продукт предполагается сертифицировать, плату нужно изначально проектировать с учетом соответствующих требований. Документация к компонентам может содержать ошибки, неточности и недокументированные возможности, поэтому их работу стоит проверять на ранних этапах проектирования. Кроме того, если команда предлагает какое-то нестандартное решение и нет уверенности, что оно сработает как надо, это тоже следует проверять заранее. Поэтому в некоторых проектах команда сперва создает макет для проверки концепции. Такой прототип далек от финального продукта, имеет множество недостатков, но позволяет проверить работу компонентов, убедиться в реализуемости того или иного решения и выявить различные проблемы в самом начале разработки. Иногда приходится создавать прототип не только платы, но и какого-то внешнего оборудования. Так, когда мы работали над алгоритмом компьютерного зрения для сельскохозяйственного робота, чтобы проверить работу встроенного ПО, мы заказали простенькую руку-манипулятор. Но одна его часть была слишком короткой, поэтому мы удлинили руку с помощью профильной трубы и созданных на 3D-принтере деталей для соединения. Проектируемый продукт тестируется на каждом этапе работы. Это позволяет выявлять и устранять проблемы до того, как команда перейдет к завершающим испытаниям, которые проверяют, работает ли устройство, как задумано, и соответствует ли оно сертификационным требованиям. Здесь нам часто приходится создавать специальную тестовую прошивку, а также прошивку для заводских испытаний. В отличие от прикладного ПО, которое устанавливается на мощные ПК, смартфоны, планшеты или серверы, встроенное ПО ставится на оборудование, ограниченное по ресурсам. Это первая трудность, с которой сталкиваются разработчики. Благодаря большому опыту наши специалисты могут оценить, сколько ресурсов нужно для будущего встроенного ПО, еще на этапе подготовки технического предложения. Конечно, проще всего разрабатывать прошивку под мощные вычислители с большим объемом памяти, поскольку такое железо не потребует значительной оптимизации кода. Когда имеешь дело с незнакомыми аппаратными средствами, возникает другая проблема – документация на компоненты может отсутствовать или содержать ошибки и неточности. Так, для одного из проектов команда выбрала модуль, который, согласно документации, мог получать координаты GPS и передавать данные по GSM. Но испытания показали, что делать это одновременно он не может. Мы связались с разработчиком, и тот подтвердил, что модуль может одновременно работать только в одном режиме, а для переключения в другой режим модуль требуется переконфигурировать и перезагрузить. В документации об этом не было ни слова. Такое встречается не так уж редко – особенно в случае дешевых компонентов. Как правило, получить нужную информацию можно связавшись с разработчиком. Когда это невозможно, команда изучает железо самостоятельно: обычно это довольно простые компоненты, так что проанализировать их работу нетрудно. Язык выбирается исходя из того, какой тип ПО нужно разработать и какие аппаратные средства нам доступны. Так, встроенное ПО для устройств на базе Android пишется на Java, а ПО для одноплатных компьютеров – преимущественно на C или C++. Язык С используется для низкоуровневого программирования. Для высокоуровневого можно использовать C++, Python, Java, Rust и другие языки. Наша команда специализируется на С/С++. Это позволяет экономить на найме сотрудников, т.к. программист с опытом разработки на С++ может создавать как встроенное, так и прикладное десктопное ПО с помощью фреймворка Qt. Выбор операционной системы определяется требованиями к функционалу устройства и опять же доступными аппаратными ресурсами. Если функционал простой, достаточно будет bare-metal прошивки. Сложные программы обычно требуют операционной системы. Здесь выбор довольно широк (Embedded Linux, Android, Windows IoT, RTOS, RTEMS и др.) и зависит от таких факторов, как: Цели проекта; Производительность железа; Стоимость разработки. Мощное железо может поддерживать требовательные к ресурсам ОС. Например, в Ubuntu много сервисов, драйверов и пакетов, зато разработка ПО под нее не займет много времени. Если аппаратных ресурсов мало, потребуется больше времени на оптимизацию кода, а значит, разработка станет дороже. Кроме того, разработка обходится дешевле и идет быстрее, когда имеешь дело со свободно распространяемыми ОС. А если проект еще и открытый, то при обнаружении в нем ошибки, мы можем быстро ее устранить. Перед непосредственной разработкой встроенного ПО необходимо продумать архитектуру будущего решения. И здесь мы снова сталкиваемся с ограниченными аппаратными ресурсами, из-за чего команда не может использовать универсальный шаблон архитектуры для каждого проекта. Мы вынуждены выбирать наиболее оптимальный вариант из ограниченного числа шаблонов, который подойдет конкретному проекту. Кроме того, если проект подразумевает создание ПО прикладного уровня, работу нужно начать со спецификации внешних интерфейсов встраиваемого ПО, чтобы другая команда могла приступить к своей части работы. Иногда мы также создаем mock-сервис, который эмулирует работу встроенного ПО и генерирует для высокоуровневого ПО синтетические данные. Такой подход ускоряет разработку и является обычной практикой в разработке встроенных решений. В отличие от прикладного ПО встраиваемое программное обеспечение, как правило, не обладает сложной логикой. Однако оно должно корректно взаимодействовать с оборудованием, и добиться этого – самая трудная часть. Здесь разработчики сталкиваются со следующими сложностями. Недостаток информации Встроенные программы всегда разрабатываются под нестандартное аппаратное обеспечение. Специалист должен понимать, как работает оборудование, поэтому большая часть времени тратится на изучение компонентов и их тестирование. Так, в одном из проектов мы должны были создать решение, которое отправляло бы команды в виде наборов бит на компьютеры MacBook через USB-порт. Сама по себе задача простая, но в официальных документах Apple нигде не раскрывается, какими наборами бит кодируются те или иные команды. Нужную информацию пришлось по крупицам собирать на различных форумах и других ресурсах. Шифрование данных Если устройство передает данные по сети, а сами данные содержат конфиденциальную информацию, то их необходимо шифровать. Например, устройство, над которым мы сейчас работаем, передает биометрические данные через Интернет, и заказчик потребовал обезопасить соединение. Однако шифрование данных требует дополнительных ресурсов, а значит, повышает требования к железу. Ограниченность памяти и производительности Пару десятилетий назад проблема нехватки памяти во встроенных системах стояла особенно остро. Сейчас характеристики железа значительно выросли. С другой стороны, требования к производительности встроенных устройств продолжают расти, а сами устройства становятся все меньше. Так что проблема сохраняется. Если устройство питается от батареи, мы также сталкиваемся с ограничениями по мощности. Программное обеспечение для такой электроники должно минимизировать энергопотребление. Добиться этого можно уменьшив количество инструкций для процессорного ядра или запрограммировав переход ядра и периферии в спящий режим, когда его работа не требуется. Стабильность Еще одна сложность – добиться стабильной работы встроенного ПО. Нестабильная работа в большинстве случаев вызвана неправильной работой с памятью – например, когда один процесс пытается забрать переменную до того, как другой процесс эту переменную посчитал. Разработчики должны уметь предвидеть подобные ситуации и снабжать процессы соответствующими инструкциями. Новые требования Бывает так, что уже на этапе написания кода заказчик выдвигает новые требования к функционалу устройства или ПО. Может оказаться, что ресурсов выбранных аппаратных средств не хватает и проект приходится значительно перерабатывать. Оптимизация Встроенное ПО можно сделать быстрым, но требовательным к памяти или нетребовательным к памяти, но медленным. И здесь важно найти баланс. Помогают в этом современные компиляторы, в арсенале которых множество типов оптимизации. Протестировать встроенное ПО, не отходя от компьютера, нельзя. Нужны тестовые стенды, а часто и полевые испытания. И здесь тестировщикам приходится проявлять изобретательность. Так, в рамках одного проекта мы разработали устройство с модулем, измеряющим дистанцию до препятствий. С помощью 3D-принтера мы создали стойку, к которой крепилось изделие, а сама стойка фиксировалась на капоте машины с помощью магнита. Затем разработчики отправились на парковку и долго катались среди автомобилей, следя за работой устройства. Бывает так, что команде приходится разрабатывать встроенное ПО для устройства, которого у нас нет. Тогда тестировать решение приходится удаленно. Обычном мы используем TeamViewer. Программа устанавливается на удаленном компьютере. К нему также подключаются камера и тестируемое устройство. Подключившись к ПК, разработчик дает команды устройству и наблюдает за его реакцией по камере. Еще одна сложность связана с тем, что при тестировании встроенного устройства сложно определить, что именно вызывает ошибку – железо или ПО. Определить это особенно трудно во время полевых испытаний, когда у команды ограниченный набор диагностических инструментов. Единственный способ проверить работу ПО – записывать логи на ноутбук в режиме реального времени. Железо же проверяется осциллографом, вольтметром, мультиметром и другими инструментами. В рамках одного из проектов мы тестировали устройство, состоящее из одноплатника и двух печатных плат. Компьютер должен был передавать на одну из плат пакет Heartbeat. Так плата понимает, что одноплатник все еще работает. Если плата не получала пакет, она должна была перезагружать компьютер. На испытаниях перезагрузки следовали одна за другой. Мы проверили шину логическим анализатором. Он показал, что компьютер отправлял корректный сигнал. Следовательно, причину неисправности следовало искать в прошивке платы. Впоследствии так и оказалось: ПО некорректно обрабатывало пакет. Здесь я перечислил трудности проектирования встроенных системы, которые мне показались наиболее типовыми. На практике же я и моя команда сталкиваемся с самыми разными сложностями, и перечислить все вряд ли возможно.  CTO Кедр Solutions