Всем доброго здравия! В прошлой статье я обещал написать о том, как можно работать со списком портов.
Сразу скажу, что уже все было решено до меня аж в 2010 году, вот статья: Работа с портами ввода-вывода микроконтроллеров на Си++ . Человек написавший это в 2010 просто красавчик.  Мне было немного неловко, что я будут делать то, что уже сделано 10 лет назад, поэтому я решил не дожидаться 2020 года, а сделать это в 2019, чтобы повторить решение еще пока 9 летней давности, это будет не так стремно. В выше указанной статье работа со списками типов была сделана с помощью C++03, когда еще шаблоны имели фиксированное число параметров, а функции не могли быть constexpr выражениями. С тех пор С++ "немного изменился", поэтому давайте попробуем сделать тоже самое, но на С++17. Добро пожаловать под кат: Итак, стоит задача, установить или скинуть сразу несколько пинов процессора, которые объединены в список. Пины могут находиться на разных портах, не смотря на это такая операция должна быть сделана максимально эффективно.  Собственно, то, что мы хотим сделать, можно показать кодом: Для тех, кто не в курсе микроконтроллерных дел, GPIOA->BSRR регистр отвечает за атомарную установку или сброс значений на ножках микроконтроллера. Этот регистр 32 битный. Первые 16 бит отвечают за установку 1 на ножках, вторые 16 бит за установку 0 на ножках.  Например, для того, чтобы установить ножку номер 3 в 1, нужно в регистре BSRR установить третий бит в 1. Чтобы сбросить ножку номер 3 в 0 нужно в этом же регистре BSRRустановить 19 бит в 1. Обобщенная схема этапов решения этой задачи может быть представлен следующим образом:  Ну или другими словами: Чтобы компилятор сделал за нас:  А затем программа И сделать это нужно максимально эффективно, чтобы даже без оптимизации код был минимальным. Собственно это вся задача.  Начнем с первого пунктика: Проверка того, что список содержит уникальные Pin. Напомню, у нас есть список Pinов: Нечаянно можно сделать так: Хотелось бы, чтобы компилятор отловил такую оплошность и сообщил об этом пианисту. Будем проверять список на уникальность следующим образом: Для формирования нового списка без дубликатов, коллега посоветовал не изобретать велосипед и воспользоваться подходом из библиотеки Loki. У него я этот подход и спер. Почти то же самое что и в 2010 году, но с переменным числом параметров.  Как теперь можно этим пользоваться? Да очень просто: Ну т.е. если вы неправильно задали список пинов, и нечаянно два одинаковых пина указали в списке, то программа не откомпилируется, а компилятор выдаст ошибку: "Беда: Одинаковые пины в списке". Мы тут понаписали уже столько, но пока еще нет ни строчки реального кода, который попадет в микроконтроллер. Если все пины заданы правильно, то программа для прошивки выглядит так: Давайте добавим немного кода и попробуем сделать метод Set() для установки пинов в списке. Забежим немного вперед в самый конец задачи. В конечном итоге необходимо реализовать метод Set(), который автоматически, на основании Pinов в списке, определял бы какие значения в какой порт нужно установить.  Поэтому объявим класс, который будет содержать список Pinов, а в нем определим публичный статический метод Set().  Как видно, метод Set(size_t mask) принимает какое-то значение (маску). Эта маска есть число, которое нужно поставить в порты. По умолчанию она равна 0xffffffff, это означает, что мы хотим поставить все Pinы в списке (максимум 32). Если передать туда другое значение, например, 7 == 0b111, то установиться должны только первые 3 пина в списке и так далее. Т.е. маска накладываемая на список Pinов. Для того, чтобы вообще можно было что-то устанавливать в пины, нужно знать на каких портах эти пины сидят. Каждый Pin привязан к определенному порту и мы можем из класса Pin "вытащить" эти порты и сформировать список этих портов. Наши Pinы назначены на разные порты: У этих 5 Pinoв всего 3 уникальных порта (GPIOA, GPIOB, GPIOC). Если мы объявим список PinsPack<Pin1, Pin2, Pin3, Pin4, Pin5>, то из него нужно получить список из трех портов Collection<Port<GPIOA>, Port<GPIOB>, Port<GPIOC>> Класс Pin содержит в себе тип порта и в упрощенном виде выглядит так: Кроме того, еще нужно определить структуру для этого списка, это будет просто шаблонная структура, принимающая переменное количество аргументов шаблона Теперь определим список уникальных портов, а заодно проверим, что список пинов не содержит одинаковых пинов. Это сделать несложно : Идем дальше... Получив список портов, теперь нужно его обойти и что-то с каждым портом сделать. В упрощенном виде можно сказать, что мы должны объявить функцию, которая на входе получит список портов и маску для списка пинов. Поскольку мы должны обходить список, размер которого доподлинно не известен, функция будет шаблонной с переменным количеством параметров. Обходить будем "рекурсивно", пока в шаблоне еще есть параметры, будем вызвать функцию с этим же именем. Итак, обходить список портов научились, но кроме обхода нужно сделать какую-то полезную работу, а именно установить в порт что-то.  Этот метод будет выполняться в runtime, так как параметр mask передается в функцию из вне. А из-за того, что мы не можем гарантировать, что в метод SetPorts() будет передаваться константа, метод GetValue() тоже начнет выполняться во время исполнения.  И хотя, в статье  Работа с портами ввода-вывода микроконтроллеров на Си++  написано, что в подобном методе компилятор определил, что передалась константа и расчитал значение для записи в порт на этапе компиляции, мой компилятор сделал такой трюк только при максимальной оптимизации.
А хотелось бы, чтобы GetValue() выполнялся во время компиляции при любых настройках компилятора.  Я не нашел в стандарте, как в таком случае должен вести компилятор компилятор, но судя по тому, что компилятор IAR делает это только при максимальном уровне оптимизации, скорее всего это стандартом и не регламентировано, либо не должно восприниматься как constexpr выражение.
Если кто знает, пишите в комментариях.  Чтобы обеспечить явную передачу константного значения сделаем дополнительный метод с передачей mask в шаблоне: Таким образом мы теперь можем пробегаться по списку Pinов, вытаскивать из них порты и составлять уникальный список портов к которым они привязаны, а затем пробегаться по созданному списку портов и устанавливать в каждом порте нужное значение.
Осталось это значение рассчитать. У нас есть список портов, который мы получили из списка Pinов, для нашего примера это список: Collection<Port<GPIOA>, Port<GPIOB>, Port<GPIOC>>.
Нужно взять элемент этого списка, например, порт GPIOA, затем в списке Pinов найти все Pinы, которые привязаны к этому порту и рассчитать значение для установки в порт. А затем тоже самое сделать со следующим портом. Значит для порта GPIOA значение должно (1 << 1 ) | (1 << 3) = 10, а для порта GPIOC — (1 << 1) | (1 << 2) = 6, а для GPIOB (1 << 1 ) = 2  Функция для расчета принимает запрашиваемый порт и если Pin находится на том же порту, что и запрашиваемый порт, то она должна установить в маске бит, соответствующий позиции этого Pina в списке, единицу (1).
На словах объяснить не просто, лучше посмотрим сразу в код: Теперь мы знаем значение, которое нужно установить в каждом порту. Осталось доделать публичный метод Set(), который будет виден пользователю, чтобы все это хозяйство вызвалось: Как и в случае с SetPorts() сделаем дополнительный шаблонный метод, чтобы гарантировать передачу mask как константы, передав её в атрибуте шаблона.  В результате, всем этим дело можно воспользоваться следующим образом: Более полный пример, можно посмотреть тут:
https://onlinegdb.com/r1eoXQBRH Как вы помните мы хотели добиться, чтобы наш вызов преобразовался в 3 строки, в порт A установилось 10, в порт B — 2 и в порт С — 6 Давайте посмотрим, что у нас получилось при полностью отключенной оптимизации  Я подкрасил зеленым значения портов и вызовы установок этих значений в порты. Видно, что все сделано так как мы задумывали, компилятор для кадого из портов подстчитал значение и просто вызвал функцию для установки этих значений в нужные порты.
Если функции установки также сделать inline, то в конечном итоге получится один вызов записи значения в BSRR регистр для каждого порта.  Собственно это всё. Кому интересно, код лежит тут. Пример лежит тут. https://onlinegdb.com/ByeA50wTS Хоккеист — на микроконтроллерах программист