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

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

В чем смысл, Бэрримор?!
Одноразовый вызов функции или "две строчки", кроме повышенной когнитивной нагрузки как на писателя, так и на будущего читателя — разницы нет.
Так ради чего?

Как человек, недавно писавший bootloader, скажу, что тянуть STM32 HAL в загрузчик — форменное самоубийство. Я в итоге конфигурацию пинов свел последовательности записей в регистры.


Кстати да, очень жаль, что у ST есть утилита генерации кода, но она не умеет в гиперминималистичный формат "лишь бы заработало"

Недавно так же писал загрузчик для STM32F4. НА HAL он занял 5 килобайт. Вроде бы много, но вот самый минимальный сектор в начале памяти STM32F4 — 16 килобайт. А меньше 1 сектора — не стереть. Ну и зачем спрашивается экономить?

У STM32F030C8T6 размер страницы — 1 килобайт и 64к всего.
Так что 5 кило HAL это овердохрена для загрузчика. Я влез в работу с I2C EEPROM, флешкой и tinyAES в 4к и там еще осталось место.


Ну и зачем спрашивается экономить?

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


Ну и дескрипторы периферии отдельный прикол, когда они порядком занимают в оперативке, но все то же самое вычитывается из регистров статуса

Всё же суть HAL в переносимости, а не идиоматической абстрактности.

Переносимость путем лишения (порой очень нужного) функционала. К примеру прием по DMA, когда мы заранее не знаем объем пакета, который придет — без HAL это сделать куда проще.


Ну и о какой переносимости кода работы с Flash можно говорить, когда у разных контроллеров разный размер страницы — насколько я помню, HAL так далеко не прячет периферию.


Я сам пользуюсь HAL (когда другого нет), но это не тот опыт, которым я наслаждаюсь. STM32-LLL другое дело, ты знаешь как работает периферия и просто указываешь ей, что надо сделать

Не знаю, то ли я совсем балбес, то ли что, но когда мне потребовалось настроить таймер не самым тривиальным образом (PWM, но с тонкостями типа правильно настроенного preload и прерываний), то я потратил кучу времени на анализ кода HAL, чтобы правильно настроить регистры. Причём я так и не нашёл магическую комбинацию вызовов, которая бы сделала ровно то, что мне нужно.

В итоге, на тот момент остался совершенно нечитаемый вариант, где половина через HAL, а половина через регистры. Понять, на какое поведение рассчитывает код, исходя из кода инициализации, было решительно невозможно.

Переносимость? Я какое-то время метался между F0 и F103 — довольно сильные различия, HAL не сильно помогает. Даже банальные GPIO отличаются.

На мой взгляд, важнее иметь сильно типизированный API к регистрам периферии. Учитывая, что производитель публикует SVD файлы, которые (в идеале, конечно) описывают все нужные детали, этот типизированный API можно просто генерировать из этих SVD. При этом за счёт сильной типизации уже на этом уровне будут ловиться ошибки типа неправильных битовых масок, и.т.д, т.к все разрешённые значения будут иметь имена и привязку к конкретным позициям в конкретных регистрах.

Вот, например, как выглядит использование подобного API на Rust. Да, этот код ни разу не переносим, но для одноразового переноса на другой чип мне быстрее будет прочитать документацию по новому чипу и переписать его (и компилятор мне в этом поможет), а для разных целевых платформ я просто сделаю свой HAL, на том уровне абстракции, на котором мне нужно. Как пример, HAL для LCD экрана.
Есть ещё вариант. Трудоёмкий в начале. Генерировать своим скриптом, да хоть на том же Python или tcl/tk. Можно с графикой: мышкой расставил что куда надо и оно само тебе исходник выработает. В средах проектирования CPLD/FPGa используется и такой подход.
А какая функция в HAL включает, например, пилу на выходе DAC-a?
Поддержу автора статьи. Разница в том что, одному богу известно, что делает HAL. Пример такой: инициализация происходила с помощью HAL, там есть ффункция System_Init(), вызываемая сразу по сбросу. В проекте использовалась RTOS, которая вешала свои обработчики на прерывания системного таймера, так вот этот систем инит инициализировал и запускал системный таймер, котороый уже начинал долбить до старта операционки, понятно, что ничего хорошего при вызове обработчика РТОС до её инициализации не получалось, и все падало. Понадобилось время, чтобы разобраться, что за… происходит. Смысл всего этого, насколько я понял, в том, что все под вашим контролем, вы инициализируете только то, что надо вам, и только так, как вам надо.
Спасибо за поддержку...:) Я тоже разбирался с System_Init() на 7 серии, пришлось самому конфигурировать регистры, чтобы установить требуемую тактовую частоту. HAL выдавал огромный код, который свелся к нескольким строчкам.
В CubeMX снять галочку "Сгенерировать обработчик прерывания" — делов-то.
Весть код открыт, полезть посмотреть что там делается — нет никакой проблемы. Это не ESP какой-нибудь где все закрыто и уж точно "одному богу известно" что там внутри библиотек делается.
Согласен. Открытость кода — это достоинство Куба…
Можно и через регистры, но какие будут плюсы?
Я вижу только минусы:
— Переносимость кода никакая, по сравнению с HAL. Вот прибежит завтра начальник и скажет: давай свой код, который ты писал по STM32F4 запусти на STM32F2, срок до вечера. И все, копай инициализацию по новой.
— Читаемость регистрового кода — никакая, нужно постоянно смотреть в ДШ
> — Переносимость кода никакая…
Иногда случается так, что кот в принципе нельзя впихнуть в кристалл поменьше. Как раз из-за этого самого HAL, который прилично раздувает код и достаточно сильно может его затормозить в критичных моментах. Ну и немного RAM.
> — Читаемость регистрового кода — никакая
Зависит от комментариев и предефайнов. Довольно неплохо использовать CMSIS + заголовочник от HAL'а, где значения регистров прописаны уже более понятно.
Например: FLASH->ACR |= FLASH_ACR_ACC64 | FLASH_ACR_LATENCY | FLASH_ACR_PRFTEN; // Разрешаем читать по 64 бита, увеличиваем латентность доступа и разрешаем работу предвыборки данных

> нужно постоянно смотреть в ДШ
Зачастую работа даже с библиотекой HAL не исключает частого окунания в DS.

Внезапно даже с HAL надо смотреть в даташит, чтобы знать, почему не работает чтение RTC из прерывания будильника, или почему передаяа с суффиксом _IT не запускается, пока принудительно не включишь NVIC_EnableIRQ(...).


Что до современных тенденций, то ST на хрензнаеткакойраз все-таки выкатила более-менее вменяемую библиотеку: STM32 Low-Level Library, которая почти полностью состоит из инлайновых функций модификаций регистровых флагов, которые позволят разрулить большую часть зависимостей на этапе компиляции. Вот только на STM32F4 я ее не нашел, обидно

Да. У меня сильно больше времени ушло на попытку «правильно» использовать HAL, чем я то же самое потом повторил просто на регистрах.

Простые вещи, типа GPIO, нормально переносятся (хотя я и там словил грабли, т.к не было привычки инициализировать структуры нулями), а вот сложная настройка PWM, master-slave у таймеров, да ещё чтоб правильные выходы были включены и прерывания — вот это ни разу не просто делается. Даже банальный энкодер у меня не завёлся чисто через HAL, какие-то не те параметры они выставляют.
Помнить регистры между семействами это ещё та задача, а они разные, да да разные регистры, не говоря уже про биты. Удобство подобной манипуляции очень сомнительно ввиду того, что стандартный хидер от стм не даёт никакой информации о том что это к примеру за биты: почему их 3, зачем мне нужны именно они? нет никакой информации о запрещённых комбинациях (а их к слову очень много, и порой можно сделать так и так, но нельзя сделать вот так и ещё 10 раз как).

Неудобно писать:
// Alternate function mode
GPIOA->MODER &= ~GPIO_MODER_MODER8_0; //0
GPIOA->MODER |= GPIO_MODER_MODER8_1; //1


Зато какое удовольствие писать вот так (код взят из реального проекта, и на выходе по ассемблерному выхлопу он даёт тоже самое что и выше, при одинаковых флагах оптимизации):
// enable usart3 pins,
    // connect to alternative func 7 (usart)
    hal::gpiod::set_mode<hal::pin_mode::alt_func,
        hal::p8, hal::p9>();
    hal::gpiod::set_alt_func<hal::pin_alt::af7,
        hal::p8, hal::p9>();


Да большинство библиотек использует принцип лапшекода, не следуя хотя бы минимальным стандартам о переносимости и компактности. Но это не значит что все библиотеки таковы. В общем использование прямого обращения к регистрам хоть и хорошо с точки зрения выхлопа ассемблерного кода, но очень сильно заставляет человека следить за каждым шагом его действий, что порождает кучу ошибок.
По коду, по вашим двум примерам и там и там надо лезть в спецификацию и читать про регистры, как миниму надо знать, что af7 отвечает за USART3 вот если бы было бы что-то типа этого:
Gpio *pUart3TxPort =  new Gpio(USART3_TX_PORT, USART3_TX_PIN);
pUart3TxPort->SetMode(PM_Alternate);
pUart3TxPort->SetAlternateFunction(AF_Uart3Tx);
Ваш подход понятен, но мои проекты требуют более полного изучения внутренней структуры контроллера. Например: для формирования ШИМ сигналов заданной частоты и разрядности нужно знать частоту тактирования таймера, а она (частота) разная для разных таймеров (сидят на разных шинах). :( Поэтому в даташит приходится заглядывать очень часто… :)
Этот вариант никак не учитыват, что бывают ещё альтернативные порты. Например, USART3_TX может быть как PB10, так и PD8 (STM32F103).

Плюс, всё равно где-то (кроме спецификации) всё равно должна быть зафиксирована привязка к реальным номерам портов, чтобы удобнее было отслеживать соответствие кода и разрабатываемой аппаратной части.

Это для демо-платы пойдёт подход «а сконфигурируй мне USART3 и пофиг где — я потом куда надо провода воткну», а для аппаратной части, сделанной под задачу, важно знать этот пин. Причём, диктовать пин могут обе стороны — программной части надо, чтобы пин бы первым каналом «продвинутого» таймера TIM1 или TIM2 (их таких будет 4 варианта, а если соглашаться на инвертированный канал, то и все 8 вариантов), а аппаратной части какой-то пин может оказаться удобнее, чем другой.

В общем, моя мысль в том, что код настраивающий/работающий с периферией не должен быть «умным», так как проблема находится на более высоком уровне. Код не может знать, какой вариант был выбран «основной пин»/«второй пин». Не может знать, какой именно таймер был выбран «TIM1»/«TIM2». Не может знать, какой подвариант был выбран «CH1»/«CH1N». Поэтому, код должен просто принимать россыпь констант (ссылка на пин, на таймер, на конкретный USART) и предполагать, что эти выданные ему константы соотносятся правильным образом.

А потом, можно сделать какой-нибудь решатель, который бы верифицировал спецификацию (или выдавал разные опции) в зависимости от заданных ограничений и выдавал значения констант. За неимением такого решателя, пусть это будет просто конфигурационный файл с константами, который проверяет человек по спецификации.
Основной смысл библиотек SPL и HAL — не снизить порог вхождения (хотя и это тоже), а отделить друг от друга низкоуровневую и высокоуровневую части ПО.

Хорошо, железо вы проинициализировали прямой записью в регистры. А обращаться к этому железу в процессе работы так же будете? Нужно в программе поднять ногу порта — пишем
GPIOA->BSRR = (1<<15);
в сотне разных мест. А потом понадобилось сменить ногу с 15 на 16, например. Искать все эти 100 мест в коде и менять везде? Конечно, нет. Надо написать функцию-обёртку (или макрос) для управления портом. И ещё пару для UART. И еще штуки три для таймеров. И несколько для DMA…

Вот и написали собственную библиотеку для работы с периферией. От чего убегали, к тому и пришли.
Для управления пинами обычно использую такой дефайн:
#define Work_Led_ON GPIOB->BSRR = GPIO_BSRR_BS_2; //Port B Set bit 2
#define Work_Led_OFF GPIOB->BSRR = GPIO_BSRR_BR_2; //Port B Reset bit 2
А чем это лучше библиотеки? Ответ: ничем. Так как это мало того что макрос (кстати без изоляции от побочных эффектов). Так и ещё макрос с кучей зафиксированных значений, что опять же снижает понятность кода при отладке и превращает в лапшу итоговый код.
Не согласен.

В примере выше — это специфичный «HAL на коленке» под задачу. А HAL от STM тебе выдаст вон тот самый HAL_GPIO_WritePin, к которому ты ещё и номер пина будешь таскать.

И вот как раз использование такого макроса понятнее — «включить LED»/«выключить LED», всё ровно в терминах высокоуровневой задачи. И сам макрос тупее некуда.

И зафиксированные значения (по уму) будут локализованы в отдельном файле, где будет привязка к железу.

Не понимаю, как тут поможет HAL? Ну то есть можно прям в самом макросе HAL вызвать, но это не сильно поможет. Весь остальной код и так уже написан в терминах Work_Led_ON/Work_Led_OFF.
Совершенно верно. Все дефайны, определяющие доступ к железу, находятся в отдельном файле. :) И на мой взгляд, макроопределение и вызов функции, все таки разные вещи… :)
Хорошо, уплыл на другой порт ваш Work_Led, или сместился на другой пин? ваши дальнейшие действия?
В данном случае, идёшь и меняешь код макроса.

P.S. Плюс код инициализации, но это отдельная песня.
Причём не одного + порт инициализации…
Итого куча телодвижений. Если подходить к макросам более грамотно то можно добиться смены всего в паре мест, но тоже не то. В случае с библиотекой это будет ровно одно место.
Код на выхлопе будет аналогичным (сам проверял), но при этом не нужно думать, а всё ли я поправил и на те ли значения поправил. Ещё раз — я не советую использовать HAL или SPL заместо CMSIS, я советую использовать правильные библиотеки которые не дают того оверхеда который вы постоянно везде видите.
Довольно часто на форумах я вижу вопросы типа: "… я использую такую-то функцию HAL, она выдает не то… что делать?". Вот и ответ… почитать исходные документы, почитать про регистры… :)
«Первое что делает человек узнавший о фьюзах это случайно лочит Атмегу...» То что у людей часто возникают вопросы подобного типа может говорить о многом:
1) Человек не читал документацию на библиотеку (а она есть и есть аппноты).
2) Человек не внимательно читал документацию и упустил какой-то момент.
3) Человек затребовал от библиотеки невозможное (см п 1, 2).
и много чего другого.

Таких случаев бывает много, также как и случаев когда человек напрямую работает с регистрами. Вот к примеру DMA — он в отличие от таймера не допускает работы в половинами своих регистров, можно только целыми записывать. Где это написано? Правильный ответ нигде — однако если взять HAL или SPL то можно увидеть что там этой проблемы не возникает. Второе — без определённого флага в RTC нельзя просто так побитого менять данные в регистрах времени и даты. Третье — не все NVIC позволяют выставлять прерывания ядра. И таких примеров на низком уровне много.
Вы правы, конечно… Люди бывают разные...:)
Ой, да прям куча телодвижений.

Вот что будет с HAL.

Делаешь, например, настройку имя порта + пины для вывдов DATA, EN, R/S LCD экрана и всё хорошо. А потом оказывается, что EN и R/S на другом порту, а ты это не предусмотрел. Придётся вводить ещё один параметр.

А потом что? Правильно, DATA пины (4 штуки) оказываются разбиты между двумя портами. И опять вся вот эта идея «меняем в одном месте» летим к чертям.

А потом ещё и не забыть нужные подсистемы включить (и, насколько я помню, у разных чипов раскладка периферии по подсистемам разная и HAL это не абстрагирует).

Я эти грабли все на своём проекте собирал, пока метался между F0 и F103, разными платами, языками (C -> C++ -> Rust), библиотеками (StdPeriph -> HAL -> Rust), подходами (C++ с шаблонами <-> C++ с классами).

Самая жизнеспособная в данном случае абстракция — это «интерфейс» с тремя методами «выставить EN», «выставить R/S», «выставить DATA». А уже что оно там внутри будет делать — пофиг, хоть HAL, хоть не HAL, там кода будет — кот наплакал.

P.S. А правильные библиотеки — согласен, как я уже говорил, я переехал на Rust и весьма счастлив. :)
Весь вопрос в том что макросы не избавляют вас от тех же действий в вашем случае. Да вообще из знакомых мне вариантов есть один который избавляет, второй пишу я. Но там подход меняется достаточно сильно и скажем так порог вхождения программиста в этот код выше чем у лапши любым выше перечисленным способом.
Интересно! Может расскажете в 2-ух словах?
Если уж совсем в двух словах — «используй метапрограммирование». А если не в двух то использование шаблонов с вычислением масок и констант во время компиляции. Небольшой фрагмент кода я приводил выше.
Понятно :) Спасибо...:)
Тот кусочек кода не раскрывает, чем он принципиально лучше.

То есть я согласен, строгая типизация — это важно, я про это писал выше. По этому параметру он, конечно же, лучше.

Но меня это в меньшей степени беспокоит, и вот почему.

Например, я перенёс свой датчик Холла с TIM1 на TIM2. А порт взял такой, который на второй канал попадает. И может быть, это альтернативный порт для этого канала, и нужно переназначение каналов таймера настраивать. И поставил триггер Шмитта, то есть вход стал инвертированный.

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

И если мне и так приходится а) делать свой слой абстракции б) вникать, фактически, до уровня регистров, то в чём существенный плюс (кроме типизации, за которую я «за» обеими руками, о чём я уже писал)?
Ну для остальной периферии пока я пока ещё не реализовал настолько высокоуровневый API уж извините, к сожалению в компайл-тайм не влезает сильный ии. Тот API который я разрабатываю помогает случайно не записать не в тот регистр не те настройки, позволяет выбирать из заранее объявленных констант. Ну и самое главное не накладывает избыточного кода поверх того который может сгенерировать компилятор языка С. Пока более ничего сверхестественного моя библиотека не выполняет. Для части периферии отвязана от записи в регистры напрямую, а также настройки (EXTI, GPIO, USART), но от понимания того что такое USART оно не избавляет да и не должно. Компилятор за вас думать не будет. И не должен. А когда будет тогда не будете нужны вы. Кстати видел я ваш код, он нисколько не помогает оптимизировать ту работу которую делает программист. Потому, что сложные операции должны быть эффективными.
Я примерно про то и говорю. Я просто думаю в контексте Rust, где подобная библиотека генерируется автоматически по SVD файлам. И когда я ещё пытался делать свою прошивку на C/C++, мне именно такая библиотека и была нужна, а не вот этот HAL от STM, который пытается быть чуть более высокоуровневым, но у него это получается не особо.

Последний пункт, правда, не понял, про сложные операции. Код на Rust должен компилироваться в прямой доступ к регистрам.
Меня немного смущает фраза «должен компилироваться в прямой доступ к регистрам». Обычно битовые операции разделённые по времени не оптимизируются, поэтому я от них отказался в пользу свёртки во время компиляции набора битовых констант через вариативные шаблоны.
Оптимизироваться не будут операции с промежуточным записями в регистр (потому что регистры — это volatile память). А цепочки типа
tim2.ccer.write(|w| w
  .cc1p().set_bit()
  .cc1e().set_bit());

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

Думаю, в C++ аналогичный код будет ровно так же соптимизирован.
Корректируется макрос в файле и все, программа как работала так будет успешно работать...:)
if (Block_mode)
{
Power_Relay_OFF
}

Вот пример из реальной программы…
А чем не устраивает HAL_GPIO_WritePin(GPIOB, GPIO_PIN_2, GPIO_PIN_SET);
?
Под капотом
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  assert_param(IS_GPIO_PIN_ACTION(PinState));

  if(PinState != GPIO_PIN_RESET)
  {
    GPIOx->BSRR = GPIO_Pin;
  }
  else
  {
    GPIOx->BSRR = (uint32_t)GPIO_Pin << 16U;
  }
}
На мой взгляд, все таки есть разница между макроопределением и вызовом функции. Особенно там где хотелось бы минимальные задержки. :)
Проблема HAL, на мой взгляд, что он предлагает уровень абстракции, который идёт под углом к тому, что мне нужно.

HAL строит абстракции вокруг конкретной переферии. «Порты»/«Таймеры»/и.т.д.

Это может нормально работает для случаев, когда подсистема сильно отделена от всего остального. Не знаю, USB какой-нибудь, что-ли (хотя всегда есть DMA который на себя всё подряд замыкает). Я с такими крупными подсистемами не работал — не знаю. С FLASH работал, там HAL более-менее был удобен.

А вот когда у тебя подсистема — это хитрым образом сконфигурированный таймер и разные пины на разных портах, HAL тебе никак не поможет. Всё равно придётся писать свой слой абстракции, который будет предлагать API специфичный для подсистемы (например «выставить задержку на следующий цикл PWM»/«установить следующий цикл PWM последним»). И если ты такой слой пишешь, то по моему опыту, настроить регистрами проще и быстрее, чем пытаться найти комбинацию вызовов HAL, которая приведёт к нужному результату.

И которая, в 90% случаев всё равно не будет работать на другом устройстве, или из-за разницы в чипах, или банально из-за существенной разницы в раскладке на переферию/пины (тут выход PWM был основной пин, там — альтернативный, и.т.д).
Согласен :)
Когда по какой-либо причине не использую библиотеки, для улучшения читаемости кода использую макросы, как, к примеру, предлагает уважаемый ZiB: ziblog.ru/2012/09/09/linii-vvoda-vyivoda-stm32-chast-3.html
В этом случае мы работаем напрямую с регистрами, но пишем вполне прозрачный код:
#define PIN_LED	D, 12, LOW, MODE_OUTPUT_PUSH_PULL_PULL_DOWN, SPEED_2MHZ, AF_NO
// Здесь все просто:	D - порт, 12 - пин, режимы и т. д.

void main(void)
{
	PIN_ON(PIN_LED);
	// И т. д.
}

Ни в коем случае не хочу обидеть автора, но меня несколько удивляет методология такого подхода. Автор берет STM32F7 (внимание, контроллер с мощнейней перефирией), показывает, что через регистры можно изменять состоние пинов и управлять таймерами и делает вывод, что таким образом можно делать все. Но ведь это не так. Если я беру STM32F7, то, очевидно, мне нужно много больше, чем мигать светодиодом. Например, RTC (включая все зубодробильную логику работы с датами и временем в разных часовых поясах и високосных/невисокосных годах), SDIO со всей логикой общения с карточкой (включая CRC контроль данных), USB-стек, внешняя память, и все это через DMA. Именно этот функционал и является основной частью HAL, а поморгать светодиодом — это так, базовый уровень. Вы высокоуровневый протокол работы SD карты, FatFS или USB-стек тоже сами будете с нуля переписывать? А если Вам этот слой HAL неинтересен (то есть нет потребности в этом функционале), то зачем Вы берете F7, а, скажем, не Atmega16?

Проекты бывают разные. В моих проектах главная нагрузка на проц — это графический цветной интерфейс. F7 использую, прежде всего, из-за приличного размера памяти и неплохого быстродействия и очень жду H7 ;). На нем смогу увеличить разрешение экрана. RTC, Fat, USB Host & Device давно реализованы и не вызывают никаких проблем. Аппаратный CRC16 не получилось использовать. написал свой для Modbus.
Спасибо за развернутый комментарий. :)
Не по теме, но может кому то пригодится. В очередной рассылке от ST написано что Low-Layer (LL) API теперь доступно для всего STM32 семейства. По сути это тот же SPL, которого нету для STM32F7, а LL есть. Так что интересующимся можно обратить внимание как на современный SPL.
Более того, у них есть даже специальная утилита, которая берет файлы с кодом на SPL и переименовывает функции в LL аналоги. Недавно помогла в переносе старого SPL проекта с F4 на F7.
Только я было обрадовался выходу LL (учитывая, что SPL больше не поддерживается), как напоролся на баг в функции LL_GPIO_Init для F1. Т.е. даже светодиодом спокойно не помигать.
А багтрекера у STM так и не завелось.

Вот за утилитку спасибо, не знал!
Раз уж начали оптимизацию, то не ограничивайтесь половинчатыми решениями, используйте Asm вместо Си
А почему тогда не в машинных кодах? ;))) В свое время приходилось в машинных кодах для 8080 проца писать… :)
Да нормальная вещь CubeMX и HAL! Простые вещи — на ура. Нужно посложнее? Используй регистры! Причем регистры можно использовать вместе с HAL используя дескриптор: hadc1.instance->DR
Собственно, я всегда писал и пишу под STM32 без всяких HAL, SPL и Cube. Код получается понятнее и компактнее, а главное — предсказуемее. Уровень абстракции от оборудования у меня всегда самописный.
Что хорошего в HAL если уровень абстракции сводиться к такой же работе с периферией как и с регистрами. Лучше уж написать свои библиотеки на регистрах, чем такие же библиотеки на HAL. У меня библиотеки умеют сами настраивать прерывания и обрабатывать их, исходя из того какая периферия используеться, в будущем еще сделаю автоматическую настройку портов.
HAL — это не библиотеки и не драйвера. Это слой абстракции от железа. Типа «кинуть байт в UART» (SPI, etc). Без конкретизации, в какой регистр (который в разных семействах может и по-разному называться). Поверх, конечно же, нормальный слой драйверов надо писать.
В развитии этой темы — сразу писать на ассемблере!
Не нужно останавливаться на полпути — писать нужно в машинных кодах. ;)))
Простой пример из жизни. Был некий проект под дискавери 746. Перепаковка под дискавери 429 — щелчок по галочке. Далее — полдня рутинной работы по допиливанию под свою плату (тоже по мотивам диско-429). Без ХАЛа — до сих пор сидели бы, искали 10 различий в даташитах, вместо продуктивной работы. Вот и думайте.
На дворе 21 век, ребята. Все идет к тому, что писать на си — моветон. И уж тем более лазить ручками по регистрам, которые меняются быстрее, чем успеешь осилить документацию.
Вот только по регистрам тоже иногда полазить полезно бывает. Да и HAL'у доверять не всегда можно. Раз поимели "странную" проблему. Когда копался пытаясь выяснить в чем же дело, нашел примерно такой код в функции отправки-приема по SPI:
while(tx_len)
{
 if(tx_ready) send_byte();
 if(rx_ready) receive_byte();
}
И все работало хорошо — даже если мы послали следующий байт не вычитав предыдущий их приемника(что легко сделать при таком подходе) — никаких проблем нет, потому что пока идет отправка этого байта, мы все еще можем вычитать предыдущий… но система была сложная, с RTOS. И если RTOS переключала задачу аккурат между отправкой следующего байта и вычиткой предыдущего — принятый байт перезаписывался следующим принятым. В результате теряли один байт, пакет рвался и возникали другие проблемы.
А правильный код должен быть такой:
while(tx_len)
{
 while(!tx_ready);
 send_byte();
 while(!rx_ready);
 receive_byte();
}
Естественно, циклы должны быть с таймаутами и прочее. В следующих HAL это поправили, но кто его знает, что у них еще там "припасено"…
Этот подход понятен и, конечно, имеет место быть. :) особенно если используются платы дискавери.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории