Комментарии 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
Пользуйтесь компиляторами, которые умеют C++14
На вкус и цвет…
Вот есть в Кейле отличный отладчик с просмотром содержимого регистров по полям. И монитор реального времени. Завидую. Однако, те возможности, что есть в gcc, для меня перевешивают всё это. Плюс возможность писать под любой операционкой, для меня это важно.
Можно использовать Clion от JetBrains с GDB и OpenOCD, подключить SVD и отладка будет тоже нормальная, с просмотром регистров по полям.
Зачем armcc, когда его поддержка прекращена в пользу armclang? :)
У меня эти выкрутасы работают при чтении 16 каналов 24-разрядного внешнего АЦП с вычислением амплитуд и фаз сигнала по нескольким частотам, параллельным чтением акселерометра, гироскопа и GPS, и передачей всей этой радости в реальном времени по USB HS.
Есть за что бороться, не находите?
Чем вам 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;
не смертельно, так как маски в данном случае одинаковы, но внимательности это не отменяет.
То-есть можно написать реализацию прерывания в любом Си файле "*.c", без дополнительных объявлений и блокировок в хидере.
Что само по себе удобно и практично.
первый раз такое встречаю за свою практику, поэтому хочу больше подробностей знать)
Ничего страшного не будет, всё будет работать, но сами функции в прошивке будут не по порядку (1,2,3..) -а разбросаны по всей флешке.
И ещё, Си файлы (каталоги) включенные в проект, даже без подключенного хидера — будут автоматом использовать прерывания. По этому тут уже не обойтись условной компиляцией — файлы придётся удалять физически. Иначе либо закончится свободное место, или будет конфликт адресов. То-есть сделать из проекта помойку на десяток гигабайт чистого кода (на все случаи жизни) — уже не получится.
В общем, либо не до конца понял эту идею, либо эта идея для слишком специфичных задач.
без включения хедера все равно не обойтись, банально по причине объявления в онном битовых масок для сброса\установки флагов.
А экономить нажатия клавиш при современном автокомпилите — глупо.
Если 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.
Извините если вопросы наивны, я об этом языке имею самое поверхностное представление.
Получается, что Ада умеет оптимизировать работу с битовыми полями, и эти две операции будт выполнены как одно чтение-модификация-запись?
Если да, то как написать код, чтобы значения устанавливались последовательно, например, сначала необходимо включить периферию, а затем установить какое-то конфигурационное значение?
Мне кажется, что
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() ;
Хотя с другой стороны, вы же один раз напишите эту функцию и потом ей будете пользоваться всегда. А как она реализована, мало должно волновать.
Как по мне — выигрыш в читаемости от Вашего шаблона минимален, веры ему не больше, чем «сишному» варианту, а значит надо куда-то лезть смотреть, что там 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-ми битных констант в хедерах от производителя уже нет много лет.
Как по мне, в данном подходе есть необходимость помнить какие биты надо ставить и как они переопределены программистом.
Пользовался подобным описание для регистров внешних чипов, тот же акселерометр например. Не очень удобно оказалось. Особенно когда коллега перепутал порядок бит при описании регистров…
И еще: прямо запись в несколько битовых полей объединяет в две ассемблерные инструкции? Или Вы про запись в одно поле?
С++, метапрограммирование и регистры микроконтроллера