Добрый день жители Хабра.
Этот пост будет посвящен программированию на C++ и использованию объектов constexpr с целью повышения уровня удобства и одновременной оптимизации кода с точки зрения размера и производительности.
Работая над одним из проектов, я задумался: «можно ли сделать удобный доступ к портам GPIO на STM32, и в то же время сделать его оптимальным с точки зрения размера кода и производительности».
Что я хотел получить:
- Использование контекстных подсказок и автодополнения при работе с GPIO.
- Получение наиболее оптимального кода.
1-2 инструкции по сборке.
- Возможность добавления проверок на уровне компиляции, которые не повлияют на производительность.
Сначала порт ищется по индексу, и только потом осуществляется вызов.
Тогда мне в голову пришла идея использовать выражения и классы constexpr для реализации обоих пунктов сразу.
Итак, давайте начнем.
В моем случае код не будет кроссплатформенным, потому что.
мы можем считать это частью HAL (Hardware Abstraction Layer).
Код был написан для микроконтроллера STM32F103xxx. Для начала определимся с адресами портов.
статический constexpr const uint32_t GPIOA_BASE = 0x40010800; статический constexpr const uint32_t GPIOB_BASE = 0x40010C00; статический constexpr const uint32_t GPIOC_BASE = 0x40011000;Теперь определим настройки порта, которые будут нам доступны.
класс перечисления GpioMode: uint8_t { Входной аналоговый = 0x00, ВводПлавающий Входсподтягивание ВыходПушПулл = 0x04, ВыходOpenDrain, АльтернативныйPushPull, АльтернативныйОткрытыйСлив }; класс перечисления GpioOutputSpeed: uint8_t { Вход Макс10 МГц, Макс2МГц, Макс50 МГц };Давайте напишем класс порта.
Мы создадим класс в виде шаблона.
Мы определяем все функции класса как статические встроенные.
Это сделано для оптимизации кода.
Класс шаблона в данном случае используется для группировки функций и хранения параметров в виде значений constexpr. Те.
Эти параметры будут доступны только на этапе компиляции, а после компиляции код будет оптимизирован под минимальное количество инструкций.
В идеале — до одной или двух ассемблерных инструкций на доступ к порту, даже при компиляции с опцией «-O0».
Добавим инструкции барьера dsb в функции доступа к порту.
Небольшое отступление.
На собеседованиях часто задают вопрос про летучие, что, мягко говоря, уже приелось.
У меня большая просьба к тем, кто проводит собеседования во встроенных полях: «не могли бы вы сказать мне сразу, не подглядывая, своими словами, какие инструкции dmb, dsb и isb нужны в системе управления вооружением»? Думаю вопрос про волатильные отпадёт сам собой.
Теперь давайте объявим определения портов как адреса типов.template<uint32_t GpioAddr, uint8_t pinNo> struct GpioPin { static constexpr const uint32_t GpioAddress = GpioAddr; static constexpr const uint8_t GpioPinNo = pinNo; static constexpr const uint32_t GpioPinMask = (1 << pinNo); static constexpr const uint32_t GpioConfPerReg = 8; static inline void mode(const GpioMode mode, const GpioOutputSpeed oSpeed = GpioOutputSpeed::Input){ if constexpr (GpioPinNo < GpioConfPerReg){ static constexpr const uint32_t maskBitCount = 4; static constexpr const uint32_t maskOffset = (pinNo * maskBitCount) & 0x1F; static constexpr const uint32_t mask = (1 << (maskBitCount + 1)) - 1; reinterpret_cast<volatile GPIO* const>(GpioAddress)->CRL &= ~(mask << maskOffset); reinterpret_cast<volatile GPIO* const>(GpioAddress)->CRL |= ((static_cast<uint32_t>(mode) & 0x03) << maskOffset); reinterpret_cast<volatile GPIO* const>(GpioAddress)->CRL |= ((static_cast<uint32_t>(oSpeed) & 0x03) << (maskOffset + 2)); } else { // Error // TO DO: add error compile time error message. } } static inline bool get() { return (reinterpret_cast<volatile GPIO* const>(GpioAddress)->IDR & GpioPinMask); } static inline void set() { reinterpret_cast<volatile GPIO* const>(GpioAddress)->BSRR = GpioPinMask; asm volatile("dsb;"); } static inline void reset(){ reinterpret_cast<volatile GPIO* const>(GpioAddress)->BRR = GpioPinMask; asm volatile("dsb;"); } static inline void invert(){ reinterpret_cast<volatile GPIO* const>(GpioAddress)->ODR ^= GpioPinMask; asm volatile("dsb;"); } }
#define GPIOA (reinterpret_cast (GPIOA_BASE)) #define GPIOB (reinterpret_cast (GPIOB_BASE)) #define GPIOC (reinterpret_cast (GPIOC_BASE))Остается только объявить классы constexpr: constexpr const GpioPin ПА0; constexpr const GpioPin ПА1; constexpr const GpioPin ПА2; constexpr const GpioPin ПБ0; constexpr const GpioPin ПБ1; constexpr const GpioPin ПБ2; constexpr const GpioPin ПБ3; .
constexpr const GpioPin ПК0; constexpr const GpioPin ПК1; constexpr const GpioPin ПК2; Что можно улучшить? Внутри класса можно добавлять различные проверки, которые будут выполняться на этапе компиляции.
Например, проверка адресов.
И наконец пример использования.
Выглядит как обычный класс, но компилируется в 2-3 ассемблерные инструкции.
В этом случае автодополнение работает, по крайней мере, в eclipse. PA0.set(); PA0.инвертировать(); PA0.set(); PA0.reset(); PA0.инвертировать();
Код доступен по ссылке: |
https://github.com/hwswdevelop/BluePillUsbDebugPrototyping/blob/main/Template/System/Core/gpio.h |
-
Eee Pc От Asus
19 Oct, 24 -
Жестовая Коммуникация
19 Oct, 24 -
Создание Сайта Для Вашей Компании
19 Oct, 24 -
Эффективные И Неэффективные Веб-Сайты
19 Oct, 24 -
Китайцы Предлагают Каждому 10 Тб
19 Oct, 24 -
Компьютерное Будущее 40 Лет Назад
19 Oct, 24 -
Сколько У Нас Хороших Карточек...
19 Oct, 24