Написание слоев аппаратной абстракции (HAL) на C
ДомДом > Блог > Написание слоев аппаратной абстракции (HAL) на C

Написание слоев аппаратной абстракции (HAL) на C

Nov 22, 2023

Джейкоб Бенинский | 19 мая 2023 г.

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

Сравнительно часто встречаются встроенные модули приложений, которые имеют прямой доступ к оборудованию. Хотя это упрощает написание приложения, это также является плохой практикой программирования, поскольку приложение становится тесно связанным с аппаратным обеспечением. Вы можете подумать, что это не имеет большого значения — в конце концов, кому действительно нужно запускать приложение более чем на одном аппаратном обеспечении или портировать код? В этом случае я бы направил вас ко всем, кто недавно столкнулся с нехваткой чипов и был вынужден вернуться и не просто перепроектировать свое оборудование, но и переписать все свое программное обеспечение. Существует принцип, известный многим специалистам объектно-ориентированного программирования (ООП) как принцип инверсии зависимостей, который может помочь решить эту проблему.

Принцип инверсии зависимостей гласит, что «модули высокого уровня не должны зависеть от модулей низкого уровня, но оба должны зависеть от абстракций». Принцип инверсии зависимостей часто реализуется в языках программирования с использованием интерфейсов или абстрактных классов. Например, если бы мне пришлось написать интерфейс цифрового ввода/вывода (dio) на C++, поддерживающий функции чтения и записи, он мог бы выглядеть примерно так:

класс dio_base {

публика:

виртуальный ~dio_base() = по умолчанию;

// Методы класса

виртуальная недействительная запись (порт dioPort_t, вывод dioPin_t, состояние dioState_t) = 0;

виртуальное чтение dioState_t (порт dioPort_t, вывод dioPin_t) = 0;

}

Те из вас, кто знаком с C++, могут видеть, что мы используем виртуальные функции для определения интерфейса, что требует от нас предоставления производного класса, реализующего детали. С помощью этого типа абстрактного класса мы можем использовать динамический полиморфизм в нашем приложении.

Из кода трудно понять, как была инвертирована зависимость. Вместо этого давайте посмотрим на краткую UML-диаграмму. На диаграмме ниже модульled_io зависит от dio-интерфейса посредством внедрения зависимостей. Когда создается объектled_io, ему предоставляется указатель на реализацию цифрового ввода/вывода. Реализация dio для любого микроконтроллера также должна соответствовать интерфейсу dio, определенному dio_base.

Глядя на приведенную выше диаграмму классов UML, вы можете подумать, что, хотя это отлично подходит для разработки приложения на языке ООП, таком как C++, это не применимо к C. Однако на самом деле вы можете получить такой тип поведения в C, который инвертирует зависимости. Есть простой трюк, который можно использовать в C с использованием структур.

Сначала спроектируйте интерфейс. Вы можете сделать это, просто написав сигнатуры функций, которые, по вашему мнению, должен поддерживать интерфейс. Например, если вы решили, что интерфейс должен поддерживать инициализацию, запись и чтение цифрового ввода/вывода, вы можете просто перечислить функции примерно следующим образом:

void write(dioPort_t const port, dioPin_t const pin, dioState_t const state);

dioState_t read (dioPort_t const port, dioPin_t const pin);

Обратите внимание, что это очень похоже на функции, которые я определил ранее в абстрактном классе C++, только без ключевого слова virtual и определения чистого абстрактного класса (= 0).

Затем я могу упаковать эти функции в структуру typedef. Структура будет действовать как пользовательский тип, содержащий весь интерфейс dio. Исходный код будет выглядеть примерно так:

read(port, pin) == dio->HIGH) ? dio->LOW : dio->HIGH);/p>

write(port, pin, state};/p>