Пилю потихоньку библиотеку драйверов для МК () , являющуюся некой
агрегацией моих работ в эмбеддете. Так случилось, что уже
написанные библиотеки являются проприетарными, выложить я их не
могу, однако ничего плохого в этом не вижу, так как могу забабахать
что-нибудь новенькое. Ну и в последствии либа ляжет в основу моего
собственного проекта, о котором пока рано говорить. Это ещё далеко
не релиз, но некоторые подходы к работе уже можно обсудить. Сама
библиотека представляет
из себя
https://github.com/evgeniy1294/mpp/tree/develop набор модулей, разные таргеты (например, stm32f407) агрегируют в себе набор совместимых с ним модулей. Модули помогают работать с периферией, ядром, могут служить оберткой для сторонних библиотек (например, lwip), содержать в себе какой-нибудь алгоритм или просто упрощать жизнь пользователям библиотек.
Для проектов, использующих данную библиотеку, я предполагаю некий подход к их организации. Идея подхода стара как мир: специфические для устройства/ревизии вещи выносятся в отдельную группу файлов (я её называю target/board, не путать с таргетом в самой библиотеке): описание периферии платы (светодиоды, внешняя память и т.д.), задействованной периферии контроллера (gpio, spi, uart), клоки, профили питания, секции в памяти. Платформонезафисимая логика пользуется функционалом из board-файлов, которые подставляются скриптом сборки.
Описание периферии платы выглядит примерно вот так:
namespace board
{
// Systick and DWT
struct FakeClk {
constexpr static std::uint32_t kSysClkHz = 16'000'000u;
constexpr static std::uint32_t kSysTickClkHz = 16'000'000u;
};
using Systick = mpp::core::Systick < FakeClk >;
using ClockCounter = mpp::core::ClockCounter < FakeClk >;
// Leds
struct LedTrait final: mpp::gpio::LedTrait
{
constexpr static mpp::gpio::Inversion kInversion = mpp::gpio::Inversion::Off;
};
struct LedInvTrait final: mpp::gpio::LedTrait
{
constexpr static mpp::gpio::Inversion kInversion = mpp::gpio::Inversion::On;
};
using LedBlue = mpp::gpio::Gpio < mpp::gpio::PD15, LedTrait >;
using LedRed = mpp::gpio::Gpio < mpp::gpio::PD14, LedInvTrait >;
using LedOrange = mpp::gpio::Gpio < mpp::gpio::PD13, LedInvTrait >;
using LedGreen = mpp::gpio::Gpio < mpp::gpio::PD12, LedTrait >;
using Leds = mpp::gpio::IoGroup < LedBlue, LedRed, LedOrange, LedGreen >;
}// namespace board
Мне было лень описывать полную конфигурацию тактовой системы контроллера для простой программы-мигалки, поэтому я завел тип FakeClk, содержащий настройки клоков по-умолчанию.
Эта информация необходима для правильной настройки системного таймера, также классы Systick и ClockCounter (это DWT) содержат информацию о тиках в секунду.
Далее идет описание светодиодов, для работы с которыми используется шаблон Gpio. Для использования этот шаблон необходимо специализировать классами IO и Trait, содержащими информацию о ножке контроллера и её настройках соответственно.
Список IO для поддерживаемых контроллеров поставляется вместе с библиотекой (генерируется скриптами), Trait'ы поставляются только базовые - Input, Output, Analog и т.д Этой информации обычно недостаточно для настройки пина, поэтому нужно создать свои трейты, отнаследовав их от базовых.
Для сведодиода достаточно добавить информацию о необходимости логической инверсии.
Теперь мы можем использовать наши светодиоды:
LedBlue::Init();
LedRed::Init();
LedOrange::Init();
LedGreen::Init();
LedGreen::Set();
Инициализация выглядит страшненько, неправда ли? Для решение этой проблемы есть классы-агрегаторы:
* utils::DevSet - агрегирует любые классы, имеющие в своём составе статическую функцию Init, умеет последовательно её вызывать.
DevSet< LedBlue, LedRed, LedOrange, LedGreen >::Init();
Компилятор развернет код в тоже, что и выше
* utils::IoSet - наследник DevSet, агрегирует только IO. дополнительно имет функции Set, Reset, Toggle. Из-за принципа работы его использование не всегда оптимально. Если регистры, модифицируемые функциями различны - то все хорошо, лучше все равно не сделать. А если пины принадледжат одному порту?
* gpio::IoGroup - специализированный класс, служит для оптимальной настроки и использования группы IO. Все IO должны быть строго на одном порту, не должны повторятся (проверяется статически). Всегда генерирует оптимальный код.
using Leds = mpp::gpio::IoGroup < LedBlue, LedRed, LedOrange, LedGreen >;
Leds::Init();
О накладных расходах:
* Все классы являются полностью статическими, никаких объектов не создается. Соответственно, нет накладных расходов по памяти.
* Функции классов по большей части inline, класс выступает в роле кодогенератора. Ксения может считать это кубом, встроенным в код.
* Все расчеты constexpr, все проверки статические, т.е. выполняется это все только на этапе компиляции.
* За счет статических проверок, могут быть выкинуты некоторые операции, которые компилятор может оставить.
* Библиотека при кодогенерации учитывает ограничения, устанавливаемые архитектурой системы. Например, если вы запихнете в Gpio какой-то трэш, например пин с несуществующим номером - будет ошибка компиляции. Все настройки также проверяются.
По итогу какой-нибудь Leds::Init() вы и на ассемблере лучше не напишите.
От вас жду критики. Конечно, это пока не релиз, но некоторые цели, например образовательные (показать студентам живой пример использования fold-expression и метапрограммирования) уже вполне выполняются.