Для таких случаев в го надо писать конструктор, который будет принимать все обязательные параметры. А создавать структуру напрямую при имеющемся конструкторе нехорошо. В любом языке можно стрельнуть себе в ногу.
Да, тут я немного перемудрил, действительно эти трейты и не нужны вовсе. Думал про фичу из другого языка. В PS немного переписал, используя вашу идею хранения маски в самом перечислении.
Я не развивал особо мысль как это всё на разные периферии делать. Первое, что в мысль приходит, описывать их в отдельных неймспесах, а при работе просто выбрать нужный.
using namespace stm32f4;
А те поля, которые отсутствуют в конкретном микроконтроллере — просто делать функции пустышки. В целом опыт подсказывает, что всё можно обобщить и даже проверять, поддерживает конкретный экземпляр периферии определённый функционал или нет на этапе компиляции. Делал такое для uarta, если в конструктор передали пины, которые не поддерживают uart, срабатывал static_assert, да ещё и с описанием: какие пины подходят.
Вот пример: https://github.com/slonegd/mculib3/blob/b5a9142dfac4f2b24b4746dfe198b33d3df35ac9/src/periph/usart_f1_f4.h#L225
Это где? Вся суть в том, что каждый параметр — отдельный тип, не обязательно enum, кстати, просто в примере только enum. По типу в constexpr контексте и определяется куда этот параметр записать. А что записать, уже не constexpr, хотя, как правило, тоже известно на этапе компиляции, но не всегда.
Под безопасностью я имел ввиду, что значения запишутся туда куда нужно просто по факту своего существования. В си очень легко перепутать параметр и записать не то.
Вот пример регистров периферии. Там есть как флаги, так и перечисления, так и просто данные. А апи мы все тут сами и изобретаем, кому что в голову придёт. Нет некого стандарта. Я предложил лишь один из вариантов.
Не исключают, но приведенный от балды пример как раз и показывает, что связанность вышла через чур, из-за чего юнит тестирование уже не поможет. Разбивайте задачи на слабосвязанные, тестируете через юнит, и дебаг вам не понадобится. Полагаться на интеграционные тесты — такая себе практика.
Наткнулись вы дебагером на вот такую функцию set, как в статье. Зачем вам заходить внутрь неё, если она со всех сторон протестирована и делает ровно то, что написано? step over и дальше смотрим.
О, вы знаете __attribute__((packed)), круто. Как раз так и сделано в моей последней библиотеке, потому что поставленная в статье задача была слишком сложной для меня.
Есть ещё одна проблема проблема с битовыми полями, на которую можно наткнуться. Некоторые регистры не позволяют побайтовое обращение, а компилятор с битовым полем может сделать так:
Вот в месте где стрелочка, stm32 уходит в hardfault. Нашел флаг компиляции, который запрещает байтовый доступ: -fno-strict-volatile-bitfields, и он даже помог. Только вот нашлось место, где он проигнорировал и сгенерировал байтовый доступ. В общем работа с битовыми полями — дело глючное.
Опять же присвоение к конкретному регистру, а подход из статьи позволяет не думать о регистрах при использовании, абстрагироваться от конкретики. На самом деле, какая разница в какой конкретно регистр записывается определенное свойство периферии, главное что оно пишется. Чуть выше я подробнее ответил на этот вопрос.
Очень много пишите сразу, потому легко пропустить на что ответить.
DMA_SxCR_DIR_0 не перечисление, это просто дефайн в CMSIS. Допустим, мы из них делаем перечисление и тогда валидацию на уровне enum можно делать, как вы предлагаете. Но это единый enum на регистр, что мешает сделать названия понятнее, по типу direction::to_periph или memory_size::byte16. Мелочь, а неприятно.
Зачем писать в несколько регистров? Для унификации интерфейса. Я, как пользователь периферии, не очень то хочу помнить в какой конкретно регистр устанавливается определенное значение. Я знаю, что у периферии есть свойство, для dma то же самое направление и размер данных, я его и задаю, а в какой регистр это делается в CR или CRL или даже CR[1] не важно. У разных версий микроконтроллера по разному. А поскольку мне важно задать свойство, я могу задать параметры, которые записываются в разные регистры, и это отработает. Хороший же интерфейс как раз, позволяет меньше задумываться о низкоуровневой реализации.
А что конкретно непонятно в поставленной задаче? Записать данные в регистры периферии из некого списка параметров (хоть как у меня, хоть через оператор или билдер), при этом чтобы обращения к регистрам были минимизированы. Если параметры относятся к одному регистру, то они собираются по или и пишутся за одно обращение к регистру. При этом сделать так, чтобы было невозможно ненароком записать параметр, который не относится к данной периферии. Но всё это написано в шапке статьи, я просто повторил.
Ну попробуйте реализовать без списка типов обобщённо. Я с удовольствием почитаю, а то только можно да можно, без конкретики. Ту ссылку, что вы привели не имеет никакой валидации, а чтобы её прикрутить, на скорый взгляд, придётся делать всё тоже самое. Как минимум надо будет хранить в constexpr контексте информацию о регистрах и как они связаны с перечислениями, при этом количество этих регистров и перечислений в обобщённом алгоритме неизвестно. А там посмотрим, какой подход лаконичнее и понятнее.
Да нет же, printf ещё хуже. Всё должно быть покрыто тестами, тем более для большого проекта. Вы при отладке никогда не проверите все возможные варианты событий.
Сам залезаю в отладчик иногда, но только когда совсем не могу понять в какой части проблема, когда выясняю, изолирую проблему и пишу тесты, которые сначала падают, исправляю ошибку, чтобы тесты не падали. Отладчик просто необходим, если вы пишите на CMSIS, в упор не видите, где ошиблись с маской из дефайна, так как они все на одно лицо, и только в отладке смотрите на перефирию, чтобы увидеть, что нужный бит где-то не выставлен. Строгая типизация с++ позволяет не заботится о том, что вы ошиблись с передачей не того аргумента в функцию, а значит и отладчик для проверки регистров периферии не нужен.
То есть одно и тоже перечисление с разными масками. Если маску хранить в traits, то можно от базового перечисления наследоваться, и новому типу делать любую другую маску.
Александреску крут, но его подход я и сам не поддерживаю по причине того, что найти разработчика, который поймёт что написано, нереально. Все эти новые фишечки добавляются комитетом как раз для того, чтобы убрать боли, связанные с метапрограммированием. Подход с constexpr функциями ускоряет компиляцию в разы по сравнению со списками Александреску. Посмотрите доклад по ссылке в начале, там чуть подробнее.
В микроконтроллер debug info никогда не пишется. Отладчик берёт её из других файлов. Уж не знаю как можно скомпилировать прогу под микро, чтобы она была гиг. Попробуйте в годболте измините оптимизацию на без, увидите, что он там накомпилировал, только вот зачем компилировать без оптимизации?
Ну и, если вам понадобилась отладка, значит вы делаете что-то не так. Не пишите тесты, например.
Забавно, что мой подход назвали из джавистким, и сами же приводите пример ооп-паттерна, которые в джаве на уровне языка есть. Опять же билдер придётся писать для каждой периферии для валидации. Хотя может и можно обобщённый алгоритм тоже написать. Можно подумать.
Плюс моего подхода в том, что алгоритм написан один раз, протестирован и далее просто нужно выполнить концепт работы с перечислениями и периферией.
Наследовать так же лучше как-то так:: enum_list<Enum1, Enum3>
Может и лучше. Моё мнение, что лучше вообще агрегировать как это сделано с регистрами в периферии, чтобы было единообразно, а вместо std::enable_if гаписать свою простенькую функцию. Просто показал, что можно так.
Суть поста была в том, чтобы показать, что со списками типов стало работать куда проще и можно решать задачи, которые раньше были не по силам среднестатистическому разработчику. Детали реализации не так важны.
template <auto Value>
using type_t = typename decltype(Value)::type;
Это первое, что я попытался сделать, но компилятор меня сразу осадил. Value не может быть аргументом шаблона, так как пришла сюда в качестве аргумента функции.
Если я правильно понял вопрос, то тут обсуждается запись в регистры управления периферии микроконтроллера, которые имеют фиксированный адрес, согласно спецификации. Но тут я подсунул динамический адрес просто для того, чтобы можно было запустить на любой машине.
На самом деле дело ещё хуже, непосредственно запись тоже внутри set с reinterpret_cast сделана, поэтому он и в случае статического адреса без оптимизаций сделает много лишнего кода. Возможно, стоит непосредственно запись вынести в отдельную функцию, а расчет адресов и значений в constexpr. Хотя, чтобы результат расчёта constexpr сделать, придётся параметры передавать в качестве аргументов шаблона, а мне это не очень нравится. К тому же в 20 стандарте появится consteval и можно будет передавать через аргументы функции.
В выходные попробую переделать так, чтоб запись отдельно, и расчёт отдельно в constexpr, выложу результат в PS.
Вместо возни с флагами оптимизации, проще просто переменную t объявить volatile, тогда будет работать при любых флагах, так как это прямое указание компилятору, что переменную нельзя оптимизировать.
Для таких случаев в го надо писать конструктор, который будет принимать все обязательные параметры. А создавать структуру напрямую при имеющемся конструкторе нехорошо. В любом языке можно стрельнуть себе в ногу.
Да, тут я немного перемудрил, действительно эти трейты и не нужны вовсе. Думал про фичу из другого языка. В PS немного переписал, используя вашу идею хранения маски в самом перечислении.
Вот эта часть, которая глючила:
https://github.com/slonegd/mculib3/blob/b5a9142dfac4f2b24b4746dfe198b33d3df35ac9/src/periph/dma_f1.h#L40-L51
Вот описание структуры:
https://github.com/slonegd/mculib3/blob/develop/src/bits/bits_dma_f1.h
Обычное битовое поле, а не работает.
Я не развивал особо мысль как это всё на разные периферии делать. Первое, что в мысль приходит, описывать их в отдельных неймспесах, а при работе просто выбрать нужный.
А те поля, которые отсутствуют в конкретном микроконтроллере — просто делать функции пустышки. В целом опыт подсказывает, что всё можно обобщить и даже проверять, поддерживает конкретный экземпляр периферии определённый функционал или нет на этапе компиляции. Делал такое для uarta, если в конструктор передали пины, которые не поддерживают uart, срабатывал static_assert, да ещё и с описанием: какие пины подходят.
Вот пример:
https://github.com/slonegd/mculib3/blob/b5a9142dfac4f2b24b4746dfe198b33d3df35ac9/src/periph/usart_f1_f4.h#L225
Это где? Вся суть в том, что каждый параметр — отдельный тип, не обязательно enum, кстати, просто в примере только enum. По типу в constexpr контексте и определяется куда этот параметр записать. А что записать, уже не constexpr, хотя, как правило, тоже известно на этапе компиляции, но не всегда.
Под безопасностью я имел ввиду, что значения запишутся туда куда нужно просто по факту своего существования. В си очень легко перепутать параметр и записать не то.
Вот пример регистров периферии. Там есть как флаги, так и перечисления, так и просто данные. А апи мы все тут сами и изобретаем, кому что в голову придёт. Нет некого стандарта. Я предложил лишь один из вариантов.
Не исключают, но приведенный от балды пример как раз и показывает, что связанность вышла через чур, из-за чего юнит тестирование уже не поможет. Разбивайте задачи на слабосвязанные, тестируете через юнит, и дебаг вам не понадобится. Полагаться на интеграционные тесты — такая себе практика.
Наткнулись вы дебагером на вот такую функцию set, как в статье. Зачем вам заходить внутрь неё, если она со всех сторон протестирована и делает ровно то, что написано? step over и дальше смотрим.
О, вы знаете
__attribute__((packed))
, круто. Как раз так и сделано в моей последней библиотеке, потому что поставленная в статье задача была слишком сложной для меня.Есть ещё одна проблема проблема с битовыми полями, на которую можно наткнуться. Некоторые регистры не позволяют побайтовое обращение, а компилятор с битовым полем может сделать так:
Вот в месте где стрелочка, stm32 уходит в hardfault. Нашел флаг компиляции, который запрещает байтовый доступ: -fno-strict-volatile-bitfields, и он даже помог. Только вот нашлось место, где он проигнорировал и сгенерировал байтовый доступ. В общем работа с битовыми полями — дело глючное.
Опять же присвоение к конкретному регистру, а подход из статьи позволяет не думать о регистрах при использовании, абстрагироваться от конкретики. На самом деле, какая разница в какой конкретно регистр записывается определенное свойство периферии, главное что оно пишется. Чуть выше я подробнее ответил на этот вопрос.
Очень много пишите сразу, потому легко пропустить на что ответить.
DMA_SxCR_DIR_0 не перечисление, это просто дефайн в CMSIS. Допустим, мы из них делаем перечисление и тогда валидацию на уровне enum можно делать, как вы предлагаете. Но это единый enum на регистр, что мешает сделать названия понятнее, по типу direction::to_periph или memory_size::byte16. Мелочь, а неприятно.
Зачем писать в несколько регистров? Для унификации интерфейса. Я, как пользователь периферии, не очень то хочу помнить в какой конкретно регистр устанавливается определенное значение. Я знаю, что у периферии есть свойство, для dma то же самое направление и размер данных, я его и задаю, а в какой регистр это делается в CR или CRL или даже CR[1] не важно. У разных версий микроконтроллера по разному. А поскольку мне важно задать свойство, я могу задать параметры, которые записываются в разные регистры, и это отработает. Хороший же интерфейс как раз, позволяет меньше задумываться о низкоуровневой реализации.
А что конкретно непонятно в поставленной задаче? Записать данные в регистры периферии из некого списка параметров (хоть как у меня, хоть через оператор или билдер), при этом чтобы обращения к регистрам были минимизированы. Если параметры относятся к одному регистру, то они собираются по или и пишутся за одно обращение к регистру. При этом сделать так, чтобы было невозможно ненароком записать параметр, который не относится к данной периферии. Но всё это написано в шапке статьи, я просто повторил.
Ну попробуйте реализовать без списка типов обобщённо. Я с удовольствием почитаю, а то только можно да можно, без конкретики. Ту ссылку, что вы привели не имеет никакой валидации, а чтобы её прикрутить, на скорый взгляд, придётся делать всё тоже самое. Как минимум надо будет хранить в constexpr контексте информацию о регистрах и как они связаны с перечислениями, при этом количество этих регистров и перечислений в обобщённом алгоритме неизвестно. А там посмотрим, какой подход лаконичнее и понятнее.
Да нет же, printf ещё хуже. Всё должно быть покрыто тестами, тем более для большого проекта. Вы при отладке никогда не проверите все возможные варианты событий.
Сам залезаю в отладчик иногда, но только когда совсем не могу понять в какой части проблема, когда выясняю, изолирую проблему и пишу тесты, которые сначала падают, исправляю ошибку, чтобы тесты не падали. Отладчик просто необходим, если вы пишите на CMSIS, в упор не видите, где ошиблись с маской из дефайна, так как они все на одно лицо, и только в отладке смотрите на перефирию, чтобы увидеть, что нужный бит где-то не выставлен. Строгая типизация с++ позволяет не заботится о том, что вы ошиблись с передачей не того аргумента в функцию, а значит и отладчик для проверки регистров периферии не нужен.
Это связано со спецификой некоторых регистров. Условно может быть так:
То есть одно и тоже перечисление с разными масками. Если маску хранить в traits, то можно от базового перечисления наследоваться, и новому типу делать любую другую маску.
Александреску крут, но его подход я и сам не поддерживаю по причине того, что найти разработчика, который поймёт что написано, нереально. Все эти новые фишечки добавляются комитетом как раз для того, чтобы убрать боли, связанные с метапрограммированием. Подход с constexpr функциями ускоряет компиляцию в разы по сравнению со списками Александреску. Посмотрите доклад по ссылке в начале, там чуть подробнее.
В микроконтроллер debug info никогда не пишется. Отладчик берёт её из других файлов. Уж не знаю как можно скомпилировать прогу под микро, чтобы она была гиг. Попробуйте в годболте измините оптимизацию на без, увидите, что он там накомпилировал, только вот зачем компилировать без оптимизации?
Ну и, если вам понадобилась отладка, значит вы делаете что-то не так. Не пишите тесты, например.
Тут или нет валидации параметров, или нужно будет переопределять оператор | для каждого параметра и сразу навскидку я не соображу как это провернуть.
Забавно, что мой подход назвали из джавистким, и сами же приводите пример ооп-паттерна, которые в джаве на уровне языка есть. Опять же билдер придётся писать для каждой периферии для валидации. Хотя может и можно обобщённый алгоритм тоже написать. Можно подумать.
Плюс моего подхода в том, что алгоритм написан один раз, протестирован и далее просто нужно выполнить концепт работы с перечислениями и периферией.
Может и лучше. Моё мнение, что лучше вообще агрегировать как это сделано с регистрами в периферии, чтобы было единообразно, а вместо std::enable_if гаписать свою простенькую функцию. Просто показал, что можно так.
Суть поста была в том, чтобы показать, что со списками типов стало работать куда проще и можно решать задачи, которые раньше были не по силам среднестатистическому разработчику. Детали реализации не так важны.
Это первое, что я попытался сделать, но компилятор меня сразу осадил. Value не может быть аргументом шаблона, так как пришла сюда в качестве аргумента функции.
Если я правильно понял вопрос, то тут обсуждается запись в регистры управления периферии микроконтроллера, которые имеют фиксированный адрес, согласно спецификации. Но тут я подсунул динамический адрес просто для того, чтобы можно было запустить на любой машине.
На самом деле дело ещё хуже, непосредственно запись тоже внутри set с reinterpret_cast сделана, поэтому он и в случае статического адреса без оптимизаций сделает много лишнего кода. Возможно, стоит непосредственно запись вынести в отдельную функцию, а расчет адресов и значений в constexpr. Хотя, чтобы результат расчёта constexpr сделать, придётся параметры передавать в качестве аргументов шаблона, а мне это не очень нравится. К тому же в 20 стандарте появится consteval и можно будет передавать через аргументы функции.
В выходные попробую переделать так, чтоб запись отдельно, и расчёт отдельно в constexpr, выложу результат в PS.