Как стать автором
Обновить

Комментарии 65

… и собирать ошибки при ручном наборе всех этих
twoWaitStates = 0b010

Нет, так делать нельзя ни в коем случае. Либо писать обёртку над CMSIS, либо генератор классов из данных SVD, о чём упоминалось в habr.com/ru/post/459642

Интересная статья. Странно, что не видел её у себя в ленте.
Никогда не получалось настолько глубоко погрузиться в шаблоны, всё время приходится искать компромис между сложностью реализации (читай — временем на разработку) и качеством получаемого результата. Автору — респект!


С SVD файлами не всё так просто. Например, у того же ST в них есть некоторое количество ошибок, которые в неизменном виде кочуют в reference manual, видимо, его каркас генерируется автоматически. Нет там и признака R/W у поля регистра, и допустимых значений. Ручного труда по описанию периферии со всеми его ошибками не миновать, как ни крути.


Плохо пока понимаю, как можно красиво написать обёртку над CMSIS. Слишком разные подходы.

Да ST SVD делает не законченный, биты полей нужно руками прописывать.
А поля R/W у них есть.


Плохо пока понимаю, как можно красиво написать обёртку над CMSIS

Можно через указатель на член структуры https://m.habr.com/ru/post/459204/?_ga=2.146897137.773613979.1584720929-1127692827.1563622228


См метод 10 и 10.5

В ARMCC 0b010 ошибка будет.
Она походу везде будет. Очень приятно за QT, что он умеет с таким представление работать.

Пользуйтесь компиляторами, которые умеют C++14

Пользуюсь головой, которая умеет всё и без С+14.
НЛО прилетело и опубликовало эту надпись здесь
Затем что бы можно было использовать не только под gcc, но и с armcc например. Хотя предвижу вопрос — зачем armcc когда есть gcc.
НЛО прилетело и опубликовало эту надпись здесь
Я знаю, но не gcc единым живу.

На вкус и цвет…
Вот есть в Кейле отличный отладчик с просмотром содержимого регистров по полям. И монитор реального времени. Завидую. Однако, те возможности, что есть в gcc, для меня перевешивают всё это. Плюс возможность писать под любой операционкой, для меня это важно.

Можно использовать Clion от JetBrains с GDB и OpenOCD, подключить SVD и отладка будет тоже нормальная, с просмотром регистров по полям.

Я мимокрокодил но вот тут kuzulis вроде бы прикрутил отладчик keil к QtCreator (4.12 версия, по идее)
Просмотр регистров периферии уже есть в QtC 4.11, но только для GDB. Но кейловский отладчик прикручен в QtC 4.12 (пока только для симулятора и STLink для STM32, т.к оч много работы, не успеваю физически), но там пока без просмотра регистров (просмотр регистров с кейловским отладчиком уже закоммичен в QtC > 4.12).

Зачем armcc, когда его поддержка прекращена в пользу armclang? :)

И все эти выкрутасы нужны, чтобы включать лампочку по расписанию два раза в сутки…

У меня эти выкрутасы работают при чтении 16 каналов 24-разрядного внешнего АЦП с вычислением амплитуд и фаз сигнала по нескольким частотам, параллельным чтением акселерометра, гироскопа и GPS, и передачей всей этой радости в реальном времени по USB HS.
Есть за что бороться, не находите?

Просто уточнить, это всё на «так любимом китайцами STM32f103»?

Нет, крутится всё на stm32f429. stm32f103 для более лёгких применений. И как пример здесь. Но никто же не мешает так и с AVR работать. Благо там тоже gcc.

С дма такой код будет выглядеть бесконечной строкой. А сам код дма переносить с одного мк на другой — не имеет ни малейшего смысла, потому как он железный. По этому гораздо проще написать статические структуры под конкретный мк, которые перепроцессор преобразует в одну запись.
Чем вам CMSIS не угодил?
void __attribute__ ((weak)) EXTI9_5_IRQHandler(void)
{
EXTI->PR = EXTI_PR_PR7;
if (ls_tik == 0){
EXTI->EMR &= ~EXTI_IMR_MR7;
EXTI->IMR &= ~EXTI_IMR_MR7;
EXTI->PR = EXTI_PR_PR7;
DMA2_Stream1->NDTR = 51200;
DMA2_Stream1->M0AR = (uint32_t) &image_buf[0];
DMA2_Stream1->FCR = DMA_SxFCR_DMDIS | sDMA_SxFCR_FTH.full_FIFO_4_4;
ls_tik = 1;
DMA2_Stream1->CR = sdma_line.dma2.stream_1.ch7_TIM8_UP|
sDMA_SxCR_DIR.memory_to_peripheral|
sDMA_SxCR_MBURST.incremental_burst_of_4_beats|
sDMA_SxCR_PBURST.single_transfer|
_VAL2FLD( DMA_SxCR_PL, 3)|
sDMA_SxCR_MSIZE.t32_bit|
sDMA_SxCR_PSIZE.t16_bit|
DMA_SxCR_MINC|
DMA_SxCR_PINC|
DMA_SxCR_TCIE|
DMA_SxCR_EN;
TIM8->CR1 |= TIM_CR1_CEN;
};
};
а для чего переопределять обработчик вектора повторно как слабый?
они ж итак стартапе описаны с этим атрибутом.

и да, маленькая очепятка у Вас:
EXTI->EMR &= ~EXTI_IMR_MR7;
EXTI->IMR &= ~EXTI_IMR_MR7;

явно должно быть:
EXTI->EMR &= ~EXTI_EMR_MR7;

не смертельно, так как маски в данном случае одинаковы, но внимательности это не отменяет.
mctMaks, слабая реализация функции совместно со слабым объявлением и одновременно с запретом на удаление адреса функции «KEEP» — автоматически разрешает её использование в изолированном виде. Точно так-же, как это требуется для прерываний, но теперь ничего изобретать уже не требуется.
То-есть можно написать реализацию прерывания в любом Си файле "*.c", без дополнительных объявлений и блокировок в хидере.
Что само по себе удобно и практично.
а что будет, если таких описаний будет несколько в разных файлах?

первый раз такое встречаю за свою практику, поэтому хочу больше подробностей знать)
Реализаций, объявление всех функций уже фиксировано.
Ничего страшного не будет, всё будет работать, но сами функции в прошивке будут не по порядку (1,2,3..) -а разбросаны по всей флешке.
И ещё, Си файлы (каталоги) включенные в проект, даже без подключенного хидера — будут автоматом использовать прерывания. По этому тут уже не обойтись условной компиляцией — файлы придётся удалять физически. Иначе либо закончится свободное место, или будет конфликт адресов. То-есть сделать из проекта помойку на десяток гигабайт чистого кода (на все случаи жизни) — уже не получится.
сложно как-то. Обработчик вектора все равно только один должен быть. Если есть необходимость использовать разные реализации в зависимости от типа проекта (ака солянка из исходников для сборки разных проектов), то проще настроить разные виды сборок, куда включены только нужные файлы.

В общем, либо не до конца понял эту идею, либо эта идея для слишком специфичных задач.

без включения хедера все равно не обойтись, банально по причине объявления в онном битовых масок для сброса\установки флагов.
В общем, либо не до конца понял эту идею

+1
У меня серьезные подозрения, что вышеприведённая конструкция работает только по совершенной случайности — потому что из двух weak'ов первым линкеру подсовывается правильный (не стандартный-пустой).

Меня всегда интересовало, почему регистры в микроконтроллерах до сих пор называют какими-то мозгодробительными аббревиатурами. Ну возможно в древние времена, когда 640К хватало всем, и IDE в современном понимании как таковых не было, это и имело смысл ради экономии места в памяти/на диске. Но современные микроконтроллеры — и все то же самое. Назвали бы нормально, длинным названием из нескольких слов, чтобы из названия было понятно что за регистр…
Регистры имеют названия только для IDE, для процессора и программы это просто ячейки ОЗУ в массиве памяти. Поэтому их называют максимально коротко, чтобы программисту приходилось нажимать меньше клавиш.
Регистры имеют названия прежде всего для программиста и определены в официальной документации на микроконтроллер (Reference Manual). И именно эти названия первичны, а дальше они определяются в библиотеке (всяких HAL и CMSIS) как именованные ячейки ОЗУ, и в идеале они должны совпадать с тем что в документации. Хотя и это выполняется далеко не всегда:(
А экономить нажатия клавиш при современном автокомпилите — глупо.
Оно и так все понятно, для тех, кто в теме, а не для залётных. Более того, схожие биты регистров конфигурации и флагов — схоже называются.

Если ARM поднялась, можно сказать, из неоткуда, значит они все делают правильно, а дальше каждый сам подберет, что ему удобно: битовые поля из си, сборная солянка, шаблоны.

Шаблоны интересны тем, что на них можно сделать декранч регулируемого размера, но для этого надо вылезти из зоны комфорта обычного Си и начать думать по крестовски.

А как такое компилируется?


INLINE constexpr static volatile Regs* rg()
{
    return reinterpret_cast<volatile Regs*>(base);
}

reinterpret_cast в constexpr запрещен же. Ну точнее, это не должно быть constexpr выражением, а значит, по стандарту — невозможно на этапе компиляции получить указатель на адрес регистра. В этом вся *опа то и зарылась…


An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract
machine (4.6), would evaluate one of the following expressions:
(2.15) — a reinterpret_cast (8.2.10);

И скорее всего на другом компиляторе это не сработает.

Как ни странно, компилируется. Даже gcc 9.2.1 не выдаёт по этому поводу никаких предупреждений. Тот же Clang, который работает в QtCreator в качестве статического анализатора кода тоже молчит.


Однако, эта конструкция мне самому не нравится. На этапе отладки не всегда можно через неё получить значения регистров, gdb её плохо переваривает. Буду думать, чем заменить.

Она без оптимизации должна быть рантайме. При оптимизации компилятор волен оптимизировать все. Но в общем случае, все это дело будет выполняться в рантайме.
А потом по цепочке и все остальные функции тоже.
Для проверки, можете написать


constexpr volatile Regs* test = rg();
Получите ошибку, так как rg(); не может вычислить я на этапе компиляции.

НЛО прилетело и опубликовало эту надпись здесь

Да, написать можно, но она перестаёт быть constexpr в этом случае, и в общем случае, указатель будет вычислен в рантайме и все остальное тогда тоже…

НЛО прилетело и опубликовало эту надпись здесь

Она используется потом для передачи указателя, который якобы рассчитан на этапе компиляции в constexpr функцию, что может создать ложное впечатление о том, что функция getRegField() или setRegister() тоже constexpr.

НЛО прилетело и опубликовало эту надпись здесь

Ага. Я про это и говорю, поскольку по сути в параметр constexpr функции передаётся reinterpret_cast.

НЛО прилетело и опубликовало эту надпись здесь

Извините если вопросы наивны, я об этом языке имею самое поверхностное представление.


Получается, что Ада умеет оптимизировать работу с битовыми полями, и эти две операции будт выполнены как одно чтение-модификация-запись?


Если да, то как написать код, чтобы значения устанавливались последовательно, например, сначала необходимо включить периферию, а затем установить какое-то конфигурационное значение?

НЛО прилетело и опубликовало эту надпись здесь
Я наверно слишком старый.
Мне кажется, что
FLASH->ACR = (FLASH->ACR &
(~(FLASH_ACR_LATENCY_Msk
| FLASH_ACR_PRFTBE_Msk )))
| FLASH_ACR_LATENCY_1
| FLASH_ACR_PRFTBE;

это очень просто и очень наглядно.
Гораздо проще, чем темплейты и все эти пляски с бубном метапрограммированием.

Я не от хорошей жизни туда полез. Когда поддерживаешь прошивки шести железок, похожих, но не совсем, всё время что-то в них докручивая, остро встаёт вопрос повторного использования кода и его удобочитаемости, а также невнесения ошибок при модификации.


Попробовал делать на шаблонах — понравилось, стало легче. В свою веру не обращаю, кому как удобнее.

Автор занимается бессмысленной работой, сначала для каждого мк объявляет кучу структур, потом прячет код внутри setLatency(), хотя можно было просто написать:


INLINE static void setLatency(Flash::Latency latency, bool prefetchBufferEnable = false)
{
    FLASH->ACR = FLASH->ACR & ~(FLASH_ACR_LATENCY | FLASH_ACR_PRFTEN) | 
        uint32_t(latency) | prefetchBufferEnable << FLASH_ACR_PRFTEN_Pos;
}

В итоге весь код будет завернут в классы или просто функции, пользователь либы с регистрами напрямую все равно не пересекается, какая разница что там внутри...

Возможно. Не настаиваю на своём подходе, тем более у уважаемого lamerok всё гораздо лучше проработано в этом отношении.
Однако, это то, чем я пользуюсь в реальных проектах, мне так удобно. Возможно кому-то ещё пригодится, если нет — тоже не беда, по итогам обсуждения, спасибо сообществу, появилось множество новых идей, значит уже писал не зря.

INLINE static void setLatency(Flash::Latency latency, bool prefetchBufferEnable = false)
{
FLASH->ACR = FLASH->ACR & ~(FLASH_ACR_LATENCY | FLASH_ACR_PRFTEN) | 
uint32_t(latency) | prefetchBufferEnable << FLASH_ACR_PRFTEN_Pos;
}

А вы уверены, что у вас в этом коде ошибки нет? :)

Какая тут ошибка? С приоритетами операций все нормально...

Это хорошо, что вы их помните, но ИМХО, для того, кто будет код этот смотреть, все не так очевидно. Все таки, ИМХО опять, что-то типа этого понятнее выглядит, хотя делает тоже самое:


  FLASH::ACRPack<
    FLASH::ACR::LATENCY::Six,
    FLASH::ACR::PRFTEN::Enable
  >::Set() ;

Хотя с другой стороны, вы же один раз напишите эту функцию и потом ей будете пользоваться всегда. А как она реализована, мало должно волновать.

А зачем в этот код смотреть тому, кто нетверд в приоритетах операций? Вы хотите, чтобы на C++ могла писать та самая кухарка, которая, по слухам, может управлять государством? И сколько реальных пользователей у Вашей шаблонной библиотеки?
Как по мне — выигрыш в читаемости от Вашего шаблона минимален, веры ему не больше, чем «сишному» варианту, а значит надо куда-то лезть смотреть, что там enable_if'ами нагорожено. А там уж точно нечитаемо.
Не поймите превратно, мне шаблонное метапрограммирование по душе. Писать их интересно. Но не все шаблоны одинаково полезны.

Насчет кухарки — да. Но дело в другом, дело в том, что в случае с С, много ручной работы, даже Flash::Latency — это кто задает этот енум? И потом, тут на С++, потому что, на сколько я помню, bool в Си нет. А раз нет bool, то в функцию можно передать, что угодно… Поэтому потенциально просто — тут ошибок можно наделать больше.
Задача минимизировать количество ошибок программистом прикладного уровня. А тут огромное поле для этого.
Шаблон не щаблон, это не важно — важно не дать делать пользователю вашей функции, то, чего делать не надо. Как миниму, там должны быть assert вставлены, на проверку входных параметров. А если это на С++ писано, то зачем так вообще писать, где явное преобразование из bool в uint32_t, например? Тут какой-то симбиоз Си и С++. Я даже не уверен даже, что там все по стандарту сделано С++. Статический анализатор выдает 25 предупреждений на одной этой строчке. Такой код никто не пропустит в продакшен.

Это написано на C++, bool неявно приводится к int, приводить его к uint32_t в данном случае не обязательно, т.к. даже несмотря на возможное смешение знаковых и беззнаковых типов бинарно результат все равно будет одинаковым. У меня обычно параноидальные настройки ворнингов не выставлены, но сейчас добавил для Gcc -Wall -Wextra -Wpedantic и получил только предупреждение касательно скобок, причем если их добавить, то их выводит серым цветом, как необязательные :) VS и Resharper тоже молчат, хотя там часть предупреждений заблокирована, так что даже не знаю что нужно сделать чтобы на одной строке получить 25 предупреждений...

Я особо не разбирался, вот скриншот



А если проверить PC Lint, то тот вообще наверное с ума сойдет :)

Я взял функцию из статьи и по-быстрому переделал, естественно имена старался не менять, потому если анализатору не нравится сбивающее с толку имя, в этом не моя вина :) Далее, все предупреждения где встречаются PERIPH_BASE и т.д., которых нет в моем коде, а так же все ворнинги с приведением кроме одного моего "old style cast" — это все предупреждения для стандартных хедеров. Причем местами не понятно что оно в принципе хочет, например, у меня FLASH_ACR_LATENCY_Msk задефайнен как (0x7UL << FLASH_ACR_LATENCY_Pos), а в предупреждении речь идет про underlying type unsigned 8-bit int… И где оно 8 бит увидело? Ну ладно, я то как-то переживу, а многие использовали CMSIS даже не подозревая, что в продакшен с ним код не пустят :)

Ну ладно, я то как-то переживу, а многие использовали CMSIS даже не подозревая, что в продакшен с ним код не пустят :)

Я имею ввиду для надежных промышленных систем, где код проверяют и сертифицируют, ну или медицина, например. Вообще, у ST есть специальная версия Cube и HAL с сертификатом, но так просто вы её не получите, только через NDA, там подобные вещи убраны, упрощены и пофиксины.
st cube SIL3


FLASH_ACR_LATENCY_Msk задефайнен как (0x7UL << FLASH_ACR_LATENCY_Pos)

у меня так вот задефайнено, без L… У вас какая версия CMSIS и для какого процессора? :)


#define FLASH_ACR_LATENCY_Pos          (0U)                                    
#define FLASH_ACR_LATENCY_Msk          (0xFU << FLASH_ACR_LATENCY_Pos)         /*!< 0x0000000F */

Я проверял на STM32G0, а к нему старых хедеров не может быть в принципе, но и для всех остальных серий стоит UL, кроме древних хедеров от SPL:


#define  FLASH_ACR_LATENCY                   ((uint8_t)0x03) 

Там еще и многие регистры объявлены 16-ти битными, но это было давно, сейчас скачал для проверки ST-й пак STM32CubeF4 и там все в точности как у меня в VisualGDB, никаких 8-ми битных констант в хедерах от производителя уже нет много лет.

НЛО прилетело и опубликовало эту надпись здесь
На вкус и цвет,…
Как по мне, в данном подходе есть необходимость помнить какие биты надо ставить и как они переопределены программистом.

Пользовался подобным описание для регистров внешних чипов, тот же акселерометр например. Не очень удобно оказалось. Особенно когда коллега перепутал порядок бит при описании регистров…
Поблемы возможны производительностью с… (С) Магистр Йода
НЛО прилетело и опубликовало эту надпись здесь
Вроде по классике либо load+mask+shift либо load+mask+shift+or+store, и так для каждого поля. Возможно, компиляция шагнула далеко, я могу отстать от тенденций.
И еще: прямо запись в несколько битовых полей объединяет в две ассемблерные инструкции? Или Вы про запись в одно поле?

У меня, когда я работал через битовые поля, каждое обращение к полю компилировалось в отдельную операцию чтение-модификация-запись. Собирал тогда GCC 7

Так и будет, если все поля, как и положено для регистров, объявлены как volatile, потому что порядок установки может иметь значение.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории