Уважаемые жители Habr‑а, В данной статье, речь пойдет о доступе к GPIO, и другим регистрам, используя C++.  Несколько лет назад, я подготовил статью, о том, как можно используя constexpr‑клаcсы, серьезно оптимизировать доступ к GPIO‑порту, таким образом, чтобы команда «PA0.set()» — превращалась в одну‑три ассемблерных инструкции, в зависимости от обстоятельств. Этот класс лишь выглядел обычным, который оптимизировался компилятором. Фактически, это была высокоуровневая оптимизация низкоуровневого кода. Теоретически возможно было перегрузить оператор равенства и писать просто PA0=1 или PA0=0, фактически это вызывало inline инструкцию, позволяющую добавить еще и барьерную инструкцию. Больше года назад, я потерял доступ к аккаунту Habr, и не мог восстановить его. Однако, спустя некоторое время, эксперименты по оптимальному доступу к полям регистров продолжились. И это привело к использованию нехитрых конструкций, построенных на шаблонах. Сегодня, я расскажу к чему привели эти эксперименты. Итак. Задача состояла в том, чтобы максимально описать любой регистр, и получить доступ к нему, удобным способом. Я несколько раз пытался различными способами формализовать описание, и мои попытки привели меня к следующему коду.  Описание регистров привожу на примере регистров микросхемы hi3516ev200. И описания, найденного на китайском языке, где то на просторах сети Internet. Итак, для примера, выше, представлено опсиание битовых полей регистра PllConfig0, отвечающего за конфигурацию PLLA и расположенного по адресу 0×12 010 000. Существует аналогичный регистр PllConfig6, отвечающий за конфигурацию PLLV и расположенный по адресу 0×12 010 018. Его описание ничем не отличается, за исключением адреса, и выглядит следующим образом. Итак, что представляют из себя указанные выше регистры? Это всего лишь три битовых поля: Postdiv2, Postdiv1, Frac. Поле Frac, занимает младшие 24 бита, поле Postdiv1 биты 24 25 и 26, а поле Postdiv2, биты с 28 по 30. Каждое поле, описывается при помощи typedef, или using. Я выбрал первый вариант, поскольку это заставляет вызываться проверки на этапе компиляции для всех полей, а не только для тех, которые используются. Был еще вариант, наследоваться от Field, но он тоже показался менее удобным. т. е. люое поле регистра может быть описано следующим образом: Возможность указать тип доступа, позволяет не инстанцировать некоторые функции, и таким образом, для поля только для чтения, будет отсутствовать функция записи. Описание зарезервированных полей, не сложнее, просто оно состоит из вложенных типов структур, и записывается во одну шаблонную строчку. Забегая вперед, замечу, что данное, поле, позволило, определять зарезервированные биты, и в случае доступа к отдельным полям регистра, определять все ли поля регистра используются. Так, если, Вы хотите записать Postdiv2, сформируется код по принципу: чтение => модификация => запись. Если Вы пожелаете записать все три поля: Postdiv2, Postdiv1, Frac. То чтение регистра, не требуется, т.к. все три поля будут перезаписаны. Однако, если не описать зарезервированные биты, то класс, не узнает о их существовании, и попытвается прочитать, изменить три поля, и записать регистр. Добавление зарезервированных полей, укажет о том, что Вы изменяете только три поля, а оставшиеся биты — зарезервированы, а значит можно не читать регистр, а сразу сформировать значение и записать это значение в регистр. Помимо экономии транзакций доступа к памяти, это позволяет избегать добавления барьерных инструкций чтения, если это необходимо.  Итак, как можно обращаться к указанным полям регистра? Если поставить Visual Studio Code, с IntelliSense, она подскажет формат достуа к каждому полю. Вероятно STM32CubeIde, сделает то же самое. Перейдем к использованию.  Использвание непосредстваенно описания, и адреса по умолчанию: Использование шаблонных функций для доступа не скольким полям одновременно: Использование модифицированного адреса:  Строгая типизация. Преимущества строгой типизации соложно недооценить. Она, с одной стороны, уменьшает количество ошибок, а с другой, повышает читабельность кода. Я сейчас говорю, не только о типизации в целом, но и о применении enum class. Для того чтобы понять, это. Давайте посмотрим на более строгое описание регистра с типами, и доступ к нему. Если попытаться скомпилировать код, используя несоответствующие типы, или другие обратиться к полям соседнего регистра, подрузумевая текущий, — код так же не скопмпилируется, поскольку осществляется проверка адресов. Позже подумаю как добавить проверку типов полей. Если вы неверно укажате размерности полей, выйдите за границу регистра или перепутаете младший и старший бит, код так же не скомпилируется.  Есть еще одна фишка. Вы можете создать пересекающиеся поля. Например, описать два бита на порту GPIO, отвечающие за цвет двухцветного светодиода, назвать их, например Led1Red, Led1Green. А еще одним полем указать enum class, который будет использовать те‑же два поля, и описать три цвета и состояние выключено. Выглядеть это булет как то так: Осталось дописать, что я еще не до конца решил вопросы, касающиеся множественного доступа к полям, которые не являются RW (ReadWrite), однако, это дело техники, и вопрос времени.  Репозиторий доступен по ссылке: https://github.com/hwswdevelop/MemoryMappedRegAccess https://github.com/hwswdevelop/MemoryMappedRegAccess Cсылка на предыдущую публикацию: Хабр (habr.com)    Программист встраиваемых систем