Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Переносимость

Во встраиваемых системах переносимость — очень важная тема: каждый производитель и даже каждое семейство одного производителя предлагают разную периферию и возможности, и способы взаимодействия с периферией тоже различаются.

Распространённый способ сгладить эти различия — ввести слой аппаратной абстракции, или HAL (Hardware Abstraction Layer).

Аппаратные абстракции — это наборы программных подпрограмм, которые эмулируют некоторые детали, специфичные для платформы, давая программам прямой доступ к аппаратным ресурсам.

Часто они позволяют программистам писать не зависящие от устройства высокопроизводительные приложения, предоставляя стандартные вызовы операционной системы (OS) для работы с аппаратурой.

Википедия: Hardware Abstraction Layer

Встраиваемые системы в этом отношении немного особенные: у нас обычно нет операционной системы и устанавливаемого пользователем программного обеспечения, а есть образы прошивки, которые собираются целиком, и ряд других ограничений. Поэтому традиционный подход, описанный в Википедии, потенциально может работать, но, вероятно, не является самым продуктивным способом обеспечить переносимость.

Как же это сделать в Rust? На помощь приходит embedded-hal…

Что такое embedded-hal?

Вкратце, это набор трейтов, которые определяют контракты реализации между реализациями HAL, драйверами и приложениями (или прошивками). Эти контракты включают как возможности (то есть если трейт реализован для определённого типа, то реализация HAL предоставляет определённую возможность), так и методы (то есть если вы можете создать тип, реализующий трейт, то гарантированно доступны методы, указанные в трейте).

Типичная схема слоёв может выглядеть так:

Некоторые трейты, определённые в embedded-hal:

  • GPIO (входные и выходные выводы);
  • последовательная передача данных;
  • I2C;
  • SPI;
  • таймеры и отсчёт времени (countdown);
  • аналого-цифровое преобразование.

Основная причина, по которой существуют трейты embedded-hal и крейты, которые их реализуют и используют, — сдерживание сложности. Если учесть, что приложению может понадобиться реализовать использование периферии в аппаратуре, а также само приложение и, возможно, драйверы для дополнительных аппаратных компонентов, то нетрудно понять, что возможность повторного использования кода очень ограничена. Математически: если M — количество реализаций HAL для периферии, а N — количество драйверов, то, если заново изобретать колесо для каждого приложения, в итоге получится M×N реализаций, тогда как использование API, предоставляемого трейтами embedded-hal, делает сложность реализации близкой к M+N. Конечно, есть и дополнительные преимущества, например меньше проб и ошибок благодаря хорошо определённым и готовым к использованию API.

Пользователи embedded-hal

Как сказано выше, у HAL есть три основных пользователя:

Реализация HAL

Реализация HAL обеспечивает взаимодействие между аппаратурой и пользователями трейтов HAL. Типичная реализация состоит из трёх частей:

  • одного или нескольких типов, специфичных для аппаратуры;
  • функций, которые создают и инициализируют такой тип, часто предоставляя различные параметры конфигурации (скорость, режим работы, используемые выводы и т. д.);
  • одной или нескольких реализаций трейтов embedded-hal (impl) для этого типа.

Такая реализация HAL может быть разного вида:

  • через низкоуровневый доступ к аппаратуре, например через регистры;
  • через операционную систему, например через sysfs в Linux;
  • через адаптер, например мок-типы для модульного тестирования;
  • через драйвер аппаратных адаптеров, например мультиплексора I2C или расширителя GPIO.

Драйвер

Драйвер реализует набор специфичной функциональности для внутреннего или внешнего компонента, подключённого к периферии, которая реализует трейты embedded-hal. Типичные примеры таких драйверов — различные датчики (температуры, магнитометр, акселерометр, освещённости), устройства отображения (матрицы светодиодов, ЖК-дисплеи) и исполнительные механизмы (моторы, передатчики).

Драйвер должен инициализироваться экземпляром типа, который реализует определённый трейт embedded-hal; это обеспечивается ограничением трейта (trait bound), и драйвер предоставляет собственный экземпляр типа с набором методов для взаимодействия с управляемым устройством.

Приложение

Приложение связывает различные части вместе и обеспечивает достижение нужной функциональности. При переносе между разными системами именно эта часть требует наибольших усилий по адаптации, поскольку приложение должно правильно инициализировать реальное оборудование через реализацию HAL, а инициализация разного оборудования отличается, иногда кардинально. Также часто большую роль играет выбор пользователя: компоненты могут физически подключаться к разным выводам, аппаратные шины иногда требуют внешнего оборудования для согласования конфигурации, а при использовании внутренней периферии приходится выбирать между разными компромиссами (например, доступно несколько таймеров с разными возможностями или периферия конфликтует с другой периферией).