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

Низкоуровневое программирование STM32: от включения питания до «Hello, World»

Блог компании RUVDS.comНенормальное программированиеПрограммирование микроконтроллеровDIY или Сделай сам
Перевод
Всего голосов 49: ↑39 и ↓10 +29
Просмотры17.8K
Комментарии 19

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

Отлично! новый велосипед, да еще какой!
Задавать номер пина GPIO для чтения как число, а не как битовую маску — сразу видно почерк не bare metal кодера. Да и все равно, в конкретном проекте пины МК зачастую выполняют предельно конкретные функции и имеют четкие имена, из за чего их все равно лучше обьявлять константами или прости господи дефайнами.
Даже во всеми ругаемом медленном и глючном st HAL так не делают.
Ну, чо тут такого, скажете вы, всего на пару машинных операций больше.
Но поверьте, даже в наше время когда самый вшивый МК мощнее железок, что летали на луну бывает необходимость экономии на вот таких вот мелочах.
Так в Arduino делают.
А от статьи с названием «низкоуровневое программирование» ожидаешь скорее вот такого habr.com/ru/post/490474 чем подробного описания очередного фреймворка.
всю жизнь считал низкоуровневым программированием — програмирование как минимум на ассемблере…
хотя сейчас асм не в моде :-)
Мне кажется, что это немного «странный» фреймворк. С одной стороны, написано на c++, но используется только ключевое слово class и оператор :: — разрешение контекста.

Очень много дублирования кода и неоптимального обращения к регистрам контроллера, например:
Исходник
  if (pin < 8) {
    // Установим регистр CRL (CNF & MODE).
    uint8_t pinmode = pin * 4;
    uint8_t pincnf = pinmode + 2;
    
    if (speed == GPIO_LOW) {    instance.regs->CRL |= (0x2 << pinmode); }
    else if (speed == GPIO_MID) {  instance.regs->CRL |= (0x1 << pinmode);  }
    else if (speed == GPIO_HIGH) {  instance.regs->CRL |= (0x3 << pinmode);  }
                                  // Две записи подряд в один и тот же регистр
    if (type == GPIO_PUSH_PULL) {    instance.regs->CRL &= ~(0x1 << pincnf);  }
    else if (type == GPIO_OPEN_DRAIN) {  instance.regs->CRL |= (0x1 << pincnf);  }
  }
  else {
    // Установим регистр CRH.
    uint8_t pinmode = (pin - 8) * 4;
    uint8_t pincnf = pinmode + 2;
    
    if (speed == GPIO_LOW) {    instance.regs->CRH |= (0x2 << pinmode); }
    else if (speed == GPIO_MID) {  instance.regs->CRH |= (0x1 << pinmode);  }
    else if (speed == GPIO_HIGH) {  instance.regs->CRH |= (0x3 << pinmode);  }
  
    if (type == GPIO_PUSH_PULL) {    instance.regs->CRH &= ~(0x1 << pincnf);  }
    else if (type == GPIO_OPEN_DRAIN) {  instance.regs->CRH |= (0x1 << pincnf);  }
  }


Как минимум, можно сократить в 3 раза:
Модификация
volatile uint32_t * const reg = pin < 8 ? &instance.regs->CRL : &instance.regs->CRH;

uint8_t pinmode = (pin & 7) * 4;
uint8_t pincnf = pinmode + 2;
uitn32_t mask = 0xF << pinmode;
uint32_t speed = (uint32_t)speed << pinmode;
uint32_t type = (uint32_t)type << pincnf;

*reg = (*reg & (~mask)) | speed | type;  


Очень много проверок в рантайме, наподобие таких:
...
if (pin > 15){}
...
if (pin < 8){}
...

которых, думаю, лучше избегать.
Относительно, зависит от того с чем сравнивать. Ну и от цели, которую перед собой поставили. Настройка и работа с периферией в МК это процентов 5-10% от объема всего кода. Остальное логика, обработка данных. При проектировании да, стоит подумать над тем как эффективней использовать, а не настраивать.

Если сравнивать настройку через SPL\HAL (без оптимизации), то препроцессор может дать выигрыш в производительности. Т.к. большая часть кода будет запускаться не в рантайме.

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

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

ИМХО, Ну и сейчас тенденция миграции на С++ при программирование под МК стала более заметной. Так что использование препроцессора, это скорее как один из этап развития.
Поздравляю, вы только презентовали очередную надстройку над CMSIS. Да, все как и было обещано:
В этом материале я хочу рассказать о том, как писать программы для микроконтроллеров (Microcontroller Unit, MCU) Cortex-M, вроде STM32, используя лишь набор инструментов ARM и документацию, подготовленную STMicroelectronics.


Но, если уж на то пошло. Почему тогда полноценно не брать готовые битовые маски\значения для битовых полей регистров? Диссонанс прям возникает от такого:
uint8_t pin2 = pin * 2;
instance.regs->MODER &= ~(0x3 << pin2);
instance.regs->MODER |= (0x1 << pin2);


Структуру с описанием порта значит из стандарта берем (обращение к полю MODER структуры GPIOx), а маску и битовое значение посчитаем сами. Иначе видимо не получится «низкоуровневое программирование»

Опять же, есть определенный нюанс с порядком настройки GPIO. Он описан не совсем явно (это уже к авторам вопрос), но регистр MODER прописывается в последнюю очередь, уже после всех основных настроек. Пример представлен в секции с описанием настройки альтернативной функции GPIO. Вы же пишите универсальную функцию инициализации? Значит это нужно учитывать. Вот фрагмент reference Manual, раздел GPIO, с указанием (неявно, но подтверждено на практике) порядка инициализации:
Peripheral alternate function:
– Connect the I/O to the desired AFx in one of the GPIOx_AFRL or GPIOx_AFRH
register.
– Select the type, pull-up/pull-down and output speed via the GPIOx_OTYPER,
GPIOx_PUPDR and GPIOx_OSPEEDER registers, respectively.
– Configure the desired I/O as an alternate function in the GPIOx_MODER register.

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

Аналогично выглядит и использование ODR (Output Data Register, регистр выходных данных), с помощью которого осуществляется вывод данных на пин:

Для вывода лучше использовать BSRR. Это опять же рекомендовано в документации. В добавок доступ будет атомарным, код проще, время исполнения меньше. Что-то типа этого (упрощенно, чтобы показать смысл):
port->BSSR = 1 << pin; // для установки 1
port->BSSR = 1 << pin << 16; // для сброса в 0

Это позволит, в том числе, работать и с битовой маской, если нужно изменить сразу несколько пинов одного порта. Назначение регистра ODR ровно такое же, как и IDR — узнать состояние порта. Т.е. его следует использовать только для контроля, что значение действительно выставило в выходном регистре.

Конечно, разрабатывать программы для MCU STM32 можно с помощью существующих фреймворков. Это может быть ST HAL, обычный CMSIS, или даже что-то, более близкое к Arduino. Но… что тут увлекательного?

Использование фреймворка без чтения документации вредно. Все равно придется рано или поздно лезть в потроха для исправления ошибок авторов фреймворка. HAL именно за это и ругают чаще всего. Те ошибки, что я указал вам, были (а может и до сих пор есть) в HAL от ST. Даже в CMSIS от arm попадаются ошибки, от этого никто не застрахован. Не верите, посмотрите их github.

И, с другой стороны, если документация к STM32 кажется кому-то, работающему с этой платформой, так сказать, бредом сивой кобылы, то можно ли говорить о том, что этот человек по-настоящему понимает данную платформу?

Простите, но вы сами видимо не совсем понимаете данную платформу. Заметьте, это всего лишь GPIO, простой как 3 копейки модуль, но и в нем у вас ошибки.
А что говорить про I2C\SPI, где таких мелочей вагон и маленькая тележка?

uint8_t pin2 = pin * 2;
instance.regs->MODER &= ~(0x3 << pin2);
instance.regs->MODER |= (0x1 << pin2);

У меня вообще от такого происходит «короткое зависание». Вы правильно заметили, что для удобства, тогда уж, лучше заранее «задефайнить» битовые маски и где надо, подставлять…

Низкоуровневое программирование МК на крестах. Рука-лицо.

Писать комментарии к переводу в надежде на то что автор их прочтет довольно странное занятие. Комментируйте оригинал

Во-первых плашку перевод надо сделать позаметней, слишком много кто её не замечает при первом прочтении.

Во-вторых, люди то будут читать перевод. Сам перевод критиковать не буду, ибо я, как говорится, в данной теме «не копенгаген». Но указать на наличествующие ошибки я думаю стоит. Хотя бы для тех, кто решит пойти тем же путем что и автор.

edit: посмотрел комментарии под оригиналом. Большая часть восторженная, статья волшебна, автор маг. Но есть и полезные, со справедливой критикой. Например, вот фрагмент ветки:

-> Looks like your example is “How to write your own GPIO library” instead of writing a code in bare metal. i.e. If you need to call a function with a dozen lines of code trying to parse what your I/O function is, it is by definition not bare metal.

-> Well, still, at some point doing JUST “register transfer” gets rather … tricky.


¯\_(ツ)_/¯

У меня закрались сомнения в самом начале статьи, поэтому я пошел в комментарии, чтоб посмотреть, как автора ругают. Как видите, комментарии не лишние.

Maya Posch довольно специфичный автор и, по моему мнению, к ее произведениям стоит относиться скептически. (черт меня дернул купить ее книжку Hands-On Embedded Programming with C++17, правда немного успокаивает, что я это сделал с огромной скидкой :) )
Дон Бокс (один из авторов DCOM, SOAP, WCF и еще кучи технологий) как-то сказал по поводу своего очередного ухода из Майкрософт — «я с удивлением обнаружил, что писать книги гораздо выгоднее и приятнее чем программы».
мне кажется не важно перевод это или нет
критика адресована не лично в адрес автору статьи, а тому, кто по неопытности подумает что это полезные знания, не разберется в плюсах и минусах и слепо будет применять это на практике
И в отличие от ошибок при рендеринге сайта или другого «высокоуровневого» айти, это может быть программист умной розетки, ошибка работы которой может дорого обойтись.
Автор, вы что такое говорите? По вашему CMSIS это высокоуровневый «фреймворк» (в компании ARM все поперхнулись кофем), а ваше «изобретение» на «плюсах» это чистейший хардкор? Побойтесь Бога.

Зачем вводить людей в заблуждение подобными заголовками? Хочется вам порекламировать свой велосипед своё творение, ну и озаглавили бы — «Еще один „фреимворк“ для stm32». И ещё хорошо бы без долгих и нудных прологов.
По-моему, здесь ошибка:
// Если кнопка нажата (переход от высокого состояния к низкому), то 'button_down' будет в низком состоянии в том случае, если кнопка будет нажата.
// Если кнопка не нажата (переход от низкого состояния к высокому, к Vdd), то 'button_down' будет в высоком состоянии в том случае, если кнопка будет нажата.

Та же ошибка в оригинале статьи:
// If the button pulls down to ground (high to low), 'button_down' is low when pushed.
// If the button is pulled up to Vdd (low to high), 'button_down' is high when pushed.

«Если кнопка не нажата… если кнопка будет нажата» — такого быть не может.
Тьху, ты. Видя заголовок: «Низкоуровневое программирование STM32» ожидаешь ну хотябы асеблер и написание своего загрузчика, а тут тебе подсовывают самопальный CMSIS
Это вообще будет работать — при входе кнопки в «плавающем режиме»? Или автор бездумно скопировала это из документации, не разобравшись — типа «экономит энергию, это хорошо для меня (с)» — а наводки она предлагает фильтровать благодарным читателям?
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.