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

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

Есть способ включить защиту чуть проще и без использования нескольких библиотек:
для STM32F1
if (!(FLASH->OBR & FLASH_OBR_RDPRT))
{
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;

FLASH->OPTKEYR = 0x45670123;
FLASH->OPTKEYR = 0xCDEF89AB;

FLASH->CR |= FLASH_CR_OPTER;
FLASH->CR|= FLASH_CR_STRT;
while ((FLASH->SR & FLASH_SR_BSY) != 0 );

FLASH->CR |= FLASH_CR_LOCK;
}

для STM32F4 включает первый уровень защиты
if ((FLASH->OPTCR & 0x0000FF00) == 0xAA00)
{
while ((FLASH->SR & FLASH_SR_BSY) != 0 );
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;

while ((FLASH->SR & FLASH_SR_BSY) != 0 );
FLASH->OPTKEYR = 0x08192A3B;
FLASH->OPTKEYR = 0x4C5D6E7F;

while ((FLASH->SR & FLASH_SR_BSY) != 0 );
FLASH->OPTCR &= 0xFFFF00FF;
FLASH->OPTCR |= FLASH_OPTCR_OPTSTRT;

while ((FLASH->SR & FLASH_SR_BSY) != 0 );
FLASH->OPTCR |= FLASH_OPTCR_OPTLOCK;
FLASH->CR |= FLASH_CR_LOCK;
}
Согласен, что можно, но мне так не нравится.

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

А при использовании функций HAL, эта работа уже сделана другими разработчиками.
Может быть там написано и не оптимально, но как говорится, «лучшее враг хорошего».
Так привязка есть, просто она в библиотеке куда вы, видимо, не заглядываете. Тут будет вопрос для каждого программиста по его задаче — нужно ли ему лезть под капот всего этого и разбираться, нужно ли ему обходиться без сторонних библиотек для экономии места и оптимизации кода, или не нужно. Учитывая размеры флешки у STM32 не так уж часто приходится беспокоится о экономии.

Это тогда надо весь код на регистрах писать. Нафиг надо

Эта сторонняя библиотека (HAL) сделана и поддерживается производителем самого железа. И я обоснованно считаю, что они лучше знают тонкости аппаратной реализации, чем кто либо другой.
Судя по всему тонкости аппаратной реализации знают одни люди, а вот HAL пишут другие, я тоже пользуюсь HAL, но некоторые функции в ней реализованы крайне неудачно, например работа с различными портами через HAL это отдельное извращение.
Да, такое тоже бывает. И ошибки в HAL, это одна из причин, когда можно словить проблему на ровном месте. К счастью, разобраться с такими косяками гораздо легче, т.к. с ним уже могли сталкиваться другие разработчики и решение легко гуглится.

Проблема с HAL в том, что она слабо коррелирует с Reference Manual. То есть, если в мануале есть какая-то функция, обычно трудно понять, как она реализована в HAL, а по HAL полного писания нет. Начинаются танцы с анализом кода самой библиотеки и т.д. Уж лучше ардуино.
Лучше смотря для чего. Если для пет-проекта, то можно и Ардуино взять.
А вот если разрабатывает прошивку для конкретного оборудования или ты его же сам и разрабатываешь, то тут без вариантов.
Никому ничего не советую, но сам я использую регистры через ардуино. Это позволяет делать быстрый критичный код, и не заморачиваться со стандартными функциями там, где нет ограничений по производительности. СТМ32, 3кВт источник питания с цифровым управлением и анализом частотного отклика.
На мой взгляд (!), HAL, даже будучи разработанным самим производителем не удобен для серьезных разработок. Да, пет проект накидать в HAL можно, а когда речь идет о каких-то более серьезных функциях, влияющих на безопасность, всё равно нужно обращаться в RM, проверить как именно работает та или иная функция. Но как оттуда перенести на HAL, однозначного решения нет. То есть сперва всё-равно нужно разобраться с RM, а потом еще дополнительно искать в библиотеке, как они там это реализовали. Ну вот честно, ни быстрее, ни надежнее это не выглядит. Ну и портянки структур с отдельными параметрами утомляют.
А я не буду с вами спорить, т.к. согласен :-)
Критичный код я и сам предпочитаю лишний перепроверить, и это если не вспоминать про наличие errat.
И я обоснованно считаю, что они лучше знают тонкости аппаратной реализации
Вы очень сильно ошибаетесь, этот HAL писали индусы на аутсорсе и ошибок там хватает. Про оптимальность реализации многих моментов вообще молчу.
Вполне возможно. Но меня не сильно беспокоит абстрактная «оптимальность». Для меня важно, что бы код работал так как ожидается, а на его поддержку тратилось как можно меньше времени и сил.
Если HAL работает так как нужно, то зачем мне переписывать эту же функциональность, добавляя лишних проблем с последующей поддержкой этого кода?
Эти магические цифры одинаковы для всех СТМ32, и, во-вторых, они задефайнены в .h-файле, поэтому Pelemeshka рекомендовал бы использовать дефайны вместо констант.
нужно хорошо разбираться во внутренностях микроконтроллера каждой серии
А разве разработчик не должен разбираться априори в том с чем работает?)) К тому же данные «магические числа» достаточно лишь оформить как переменные с понятными именами. Так же эти числа прекрасно ищутся в том же RM-е, что сильно упрощает их понимание.

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

Может быть там написано и не оптимально, но как говорится, «лучшее враг хорошего»
В чем сложность написать самому один раз хорошо и потом просто переиспользовать данный кусок кода? Вроде как временных затрат на это практически нет, а в случае проблемы не придется копаться в потрохах HAL с мыслями «что за дерьмо тут сделали»
А разве разработчик не должен разбираться априори в том с чем работает?))
Тут ключевое слово разбираться может иметь разное значения для каждого разработчика читающего. Для одного будет достаточно уровня базовых функциональных возможностей, а для другого, нужны тонкости настройки всей периферии.
Я придерживаюсь подхода «по необходимости». Если функциональность HAL работает как заявлено, то мне не интересно залезать в её потроха и медитировать над исходниками.
Вроде как временных затрат на это практически нет
А вот с этим я категорически не соглашусь. Может быть временных затрат на копипаст действительно нет, вот только «разработка» и «поддержка», это совершенно разные процессы и второй вы совершенно не принимаете в расчет. А именно он является самым важным и дорогим.
«поддержка», это совершенно разные процессы и второй вы совершенно не принимаете в расчет
Как влияет на стоимость поддержки наличие одного задокументированного метода?

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

относительно это. Иногда изменение настройки периферии существенно меняет «бизнес-логику» проекта в целом. Даже при однократной настройке по включению устройства.

В добавок, сопровождение проекта это ведь не только про устранение найденных багов, но и иногда добавление новой «фичи». А это в свою очередь может привести к пересмотру задействованной периферии и её переконфигурации. В итоге: да, казалось бы 10% от всего кода, но пришлось большую часть переписывать.

Я думаю, что у каждого есть такой случай в практике, а то и не один.

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

ИМХО, Больше удивление вызывает, когда немного поковырявшись с настройками периферии реализуешь аппаратно большую часть того, что было реализовано программно. Опять же, способ настройки может сказаться и при разработке схемы. А это уже экономия на компонентах. Так что, учитывать надо все :)
А у меня вызывает удивление считать настройку периферии отдельно от разработки. Какая разница заказчику или пользователю, по какой причине его устройство не работает? Хоть ошибка в ПО, хоть в настройке оборудования (которая по большому счету тоже часть ПО)?

Да и бюджет на разработку обычно не подразумевает выделенной части «настройка периферии». Все это одна сплошная разработка, причем затраты разовые. А вот поддержка, точнее затраты на поддержку — постоянные.
Все это одна сплошная разработка
То есть про архитектуру проекта не стоит говорить? Я почему-то всегда считал, что уровень драйверов и уровень логики в эмбеде это сильно разные слои абстракции, которые как минимум не смешаны в «сплошную разработку». Драйвера в принципе не должны быть привязаны к проекту, это штука которая поддерживается отдельно, легко портируется на разные МК и затраты на них присутствуют лишь на старте.

А вот поддержка, точнее затраты на поддержку — постоянные.
А что вы поддерживать собрались? Однажды написанную оптимальную функцию? Ну так она пишется один раз, тестируется и используется во всех проектах.

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

А вот CMSIS родной от ARM и сам reference manual на МК уже никак особо не изменится, можно взять однажды условный F401 камень, написать функцию доступа к памяти и переиспользовать ее 5+ лет с нулевыми издержками за содержание.

Сколько видов библиотек сменилось у ST за последние 10 лет? А вот RM и CMSIS не изменились. Вот вам и поддержка…
Это хорошо когда есть возможность отделить бизнес-логику от железа.
Причем отделить до уровня «пох на чем запускать — на ПК i7-9900 c виндой или stm32f100»
Мы с вами говорим про разные уровни абстракции. Под словом «разработка» вы понимаете только процесс написание кода (драйвера, бизнес логики и т.д.), а я вам пытаюсь пояснить про финансирование данного процесса, т.е. про расходы на заплату этого самого разработчика.

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

К сожалению, у меня за все время работы, таких проектов не было ни разу. Да и сам бизнес стремится к тому, чтобы привязать пользователя и заставить его платить постоянно.
А для этого требуется всегда исправлять баги (случайные и не очень), поддерживать новые технологии или протоколы, расширить функционал по просьбе заказчика или обновлять саму аппаратную платформу. И вот это все и есть поддержка, т.е. постоянные затраты.
В таком случае получаем классику — делаем херню на коленке, продаем за недорого, а потом всю жизнь сосем деньги с заказчика на поддержку. В принципе вы правы, сейчас так работают многие, а все остальные стремятся к этому. Если рассматривать разработку как бизнес в целом, то вы пожалуй правы, но с точки зрения разработки как техники — «осуждаю»)))
Вы передергиваете мои слова. Я не говорил про «херню на коленке», хотя быстрая поставка MVP фактически поощряет такой подход. Путь это будет на совести Product Owner`ов и владельцев бизнеса.

Я на это смотрю именно как разработчик/архитектор, который понимает движение денежных потоков. Ведь именно мне/моим сотрудникам придется закрывать данные задачи, а бюджет и время, которое отводится на их решение не безгранично.
А для встроенного загрузчика (тот что системный и через разные интерфейсы доступен) как правильно обходить данную защиту? Или он дефакто её отключает?
К сожалению имею пробел в данной области, хотелось бы восполнить
Неблокирующий вывод в первом примере тут же становится блокирующем при повторном вызове.
Только если уже идет передача по DMA или размер добавляемых данных превысит размер остатка буфера. Но тут уже ничего не сделаешь, чудес не бывает ;-(
А зачем здесь вообще DMA? Подобные задачи (низкоскоростной вывод) куда проще решаются через кольцевой буфер и прерывание по пустому буферу передачи. Никаких блокировок.
Копируем данные в кольцевой буфер и смотрим — свободен ли сейчас буфер UART. Если свободен — кидаем туда первый байт.
Дальше по прерыванию просто кидаем следующий байт и всё. Дошли до указателя последнего байта — остановились.
Согласен, так получается очень красиво!
1 Как правильно заметил An_private буфер должен быть кольцевой. Дополнительный трюк- размер буфера 0x100 а голова и хвост uint8_t. Тогда даже при какой нибудь проблеме у Вас не будет наезда по памяти. И код проще.
простите, случайно ткнул в минус. на самом деле плюсую!
Бывает. Плюсани тогда компании ;-)
В этом случае придется лишний раз дергать ядро МК прерываниями!
Самый идеальный вариант — двойная буферизация. Добавляем помаленьку в буфер, если набрели на '\n' и передатчик свободен — передаем данные средствами DMA, тем временем заполняя второй буфер.
Когда я отлаживал USB (пришлось самому писать CDC и HID, т.к. готовых вменяемых в открытом доступе нет), отладочный выхлоп гонял по UART на трех мегабодах. Правда, пришлось размеры буферов увеличить до 256Б. Но затыков не было.

А, скажем, у STM32F0 есть возможность настроить прерывание по символу. В этом случае можно и прием вести по DMA вплоть до полного заполнения буфера (в этом случае сбрасываем буфер и выставляем флаг buffer overflow), а если получаем символ '\n', вызывается прерывание, в котором прекращаем прием по DMA, копируем нужный кусок буфера в другой буфер, выставляем флаг rx_rdy и начинаем сначала. Весьма удобно и не напрягает и без того хилое ядро МК.

P.S. В приличном обществе не стоит выкладывать кал! Калокуб — удел абдуринщиков!..
PS: На всякий случай хочу заметить, что даже при использовании DMA чудеса ещё как бывают.
Стандартный приём при использовании DMA — наличие ДВУХ буферов — один сейчас «в эфире», второй копит данные. При окончании данных в первом буфере — натравливаем DMА на второй буфер, а данные льём в первый. И так они по очереди и работают. В таком случае мы избегаем любых блокировок или потерь данных.
В таком случае мы избегаем любых блокировок

Не совсем. Если пихать больше чем пролезает, все равно придется или выкидывать данные или блокировать писателя или динамически заказывать новые буфера, а потом все равно блокировать.
Бывает и не такое. Прошлый год делал проигрыватель звука с плавным воспроизведение ровно по этой же схеме (два буфера ДМА, которые попеременно меняются).
А один буфер с прерыванием по полутрансферу не вариант? :)
Получается тоже самое, только с боку ;-)
На самом деле, я хотел написать про другое.
Мне пришлось задействовать третий буфер для отдельной процедуры старта проигрывания нового файла, если воспроизведение уже работает. Без этого не получалось сделать слитное воспроизведение, т.к. между концом уже проигрывающегося файла и воспроизведением нового иногда проскакивали артефакты, заметные на слух.
Если есть JLink и есть не много свободного места, то можно использовать RTT Viewer. Данные идут по SWD в неблокируемом режиме.
+1. Удобная вещь, учитывая что:
  • умеет в цветовую дифференциацию штанов уровней логгирования;
  • в ней есть простенький способ отображения переменных на графике
  • она берет на себя организацию буферизации
  • ну и бонусом, достаточно высокая скорость работы
  • чуть не забыл, аппаратный детектор наличия отладчика. если его нет, то данные начинают скипаться и лог автоматически отключается.


При этом платим только за вызов функции конфигурации, отдаем немного ОЗУ под буфера и несколько сотен микроампер добавочного потребления.
Блокирующий отладочный вывод? Да вы наверное шутите, мистер Фейнман! Лично мне такого даже в голову не приходило почти что никогда.
Как минимум, кольцевой буфер и из него уже отправка по прерываниям.

как максимум (до которого на сейчас додумался) кольцевой буфер, по которому стартует дма и дальше подбирает.
логика примерно такая:
прерывания дма запрещены. работаем на прерываниях уарта.
приходит первый сивол — отправляем как обычно.
первый символ отправился, в прерывании смотрим буфер.
буфер не пуст — заряжаем передачу дма (до конца сообщения или до конца буфера).
в прерывании дма практически то же самое что и в прерывании от уарта.
Если ничего нет — запрещаю дма и дальше см.п.1
Но и с дма есть вопросы. Например, количество и приоритеты дма.
Блокирующий отладочный вывод?
Что может быть проще printf()?
Вопрос только в реализации этой функции. Хоть через CAN
Можно и через ethernet, но обычно начитают с самого простого HAL_UART_Transmit
Видел один раз такое. Основная программа была на ПК и работала с регистрами через Ethernet. На МК была шаблонная программа которые гоняла данные туда сюда.
А поправить putchar как нужно религия не позволяет?
Не понял вопроса
printf для вывода символа вызывает putchar. Вот его и правим как нужно.
В данном случае (STM32CubeIDE), переопределять нужно не putchar, а __io_putchar, но я самого вопроса не понял.
А если речь идет о том, что и printf сделать не блокирующим, да пожалуйста.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.