Я имел ввиду не какие-то конкретные решения, не правильность/неправильность работы с периферией микроконтроллера или окружения, а общие принципы построения, структуру кода. Некоторые несложные принципы я изложил ранее. Это не только мое мнение, это мнение множества грамотных, образованных программистов, которые не только умеют битики в регистрах щелкать. Эти и другие принципы изложены в книгах и учебниках, обсуждаются на тренировочных задачах (кейсах).
Почему следует придерживаться принципов структурированного написания кода? Во-первых, хорошо структурированный код легче редактировать в процессе написания. Во-вторых проще находить баги, проще отлаживать. В-третьих, (и на мой взгляд, это самое главное) программные модули кода можно без их изменения использовать в других проектах, ускоряя их разработку. Чтобы это было возможно, следует соблюдать остальные описанные ранее принципы.
Принцип единственной ответственности - программный модуль (класс, функция) затрагивает только то, для чего предназначен. Для примера, функция WritePort(data) не должна делать ничего, кроме записи в выходной порт переменной data. Она не должна вызывать внутри себя никаких там Dma, никаких посторонних обращений к какой-то PingMain(). Если такие обращения нужны по алгоритму работы, то сделайте эти вызовы ВНЕ функции WritePort(). Если нужны какие-то доп.условия выполнения, запишите их вне функции. Если нужны вычисления посторонних переменных, вычислите их снаружи. С другой стороны, если для записи в порт нужно прибегнуть к помощи иных функций, таких как WritePin(value), то эти функции могут вызываться, поскольку цель у них та же самая. Так же внутри WritePort(data) можно например разложить переменную data побитно для WritePin(value). Это нормально. Но НЕ нормально накладывать посторонние условия и вычисления, типа if(data==3) PingMain() и тому подобное. И тогда это функцию вы сможете использовать в другом проекте или даже в другом контексте этого же проекта. В показанном ранее примере кода нарушен принцип единственной ответственности, поэтому я и написал, что сиё поделие пишется либо новичками, либо безграмотными погромистами, которые просто ничего не знают.
Есть другие принципы, они чуть более сложные и они ближе к объектно-ориентированному программированию.
КотПротон, ну, я в микроконтроллерах вообще предпочитаю отделение мух от котлет, хоть это и более расточительно (иногда) для размера программы. Все взаимодействие с периферией я стараюсь вынести в отдельный модуль. Из которого наружу торчат интерфейсы уже не уровня "записать бит в порт Х", а "отправить N байт в дисплей", "получить состояние кнопок"... А уже как подключены кнопки, дисплей и т.д. никого не волнует. Делить отправку данных в дисплей на отдельные под-функции - не вижу смысла. Под капотом - 2 функции - инициализация периферии, обслуживающей дисплей - GPIO/SPI/I2C/DMA, и отправка данных - засылка массива в дисплей. Причем, как это будет, DMA, блокирующий цикл, отправка по байту из буфера по таймеру - дело внутреннее, в основной программе это не должно быть видно и должно быть без разницы. Я отправила из основной программы блок данных и должна забыть, как оно там дальше... Точно так же опрос кнопок - не волнует, как они подключены, на выходе я должна получить их состояние. Если в основной программе что то таймерное - то в модуль железа должна быть передана коллбек-функция... И никаких обработчиков прерываний в основной программе. Либо флаги, либо коллбеки.
Это позволяет легко мигрировать проекты между разными МК и даже между разными семействами. И делать реюз кода в других проектах.
Ну и в основной программе тоже нужно все структурировать. Но в МК несколько сложнее, нежели на ПК. Событийность нужно делать ручками. И, по большому счету , получается либо один большой суперцикл, в котором или структурно, или отдельными функциями реализован функционал, либо какой то менеджер очереди событий с обработчиками. Ну или накатывать какую то RTOS, которая на себя возьмет всю "борьбу под одеялом".... Но зачастую RTOS - несколько расточительно, особенно для мелких МК.
Но вот такой подход может увеличить объем кода - те же коллбеки, например.... Но удобство написания/отладки - несоизмеримо выше. (Сейчас из темы про АВРки прибегут взрослые люди и скажут, что простым переписыванием сишного кода на ассемблере они добились оптимизации размера в 270%.... Вот только в том асме, скорее всего, сам черт ногу сломит...)
При включении оптимизации -O3 исключаются излишние вызовы функций, заменяясь на непосредственное встраивание кода функций. Код ускоряется в несколько раз при сохранении структурированности исходника. И не надо писать ничего на ассемблере.
Цитата:
Под капотом - 2 функции - инициализация периферии, обслуживающей дисплей - GPIO/SPI/I2C/DMA,
Если по правилам, то в функции работы с дисплеем не должно быть функции инициализации GPIO/SPI/I2C. Почему? Да потому, что например на один физический I2C можно повесить несколько самых разных устройств, это нормально. (На один SPI так же можно повесить разные устройства, снабдив разными CS каждое). А у вас инит I2C зависит от дисплея. Нелогично. Либо писать один и тот же инит I2C и в дисплее, и во всех остальных устройствах, либо обращаться к иниту дисплея, чтобы настроить I2C, либо просто вынести инит I2C за пределы модуля дисплея. Об этом и говорят приведенные ранее принципы - программный модуль дисплея не должен делать ничего более, кроме как общаться с дисплеем. Настройка интерфейсов ввода/вывода - это не забота модуля дисплея. Ничего не изменится, если настройку I2C и пинов сделать вне модуля дисплея. Зато модуль дисплея станет чище, и другие модули, зависящие от I2C, перестанут зависеть от модуля дисплея.
Задача стоит не столько в "мигрировании" между разными МК, сколько в построении логики взаимодействия на одном МК в рамках одного проекта. И когда логика будет верно выстроена, задача мигрирования и повторного использования кода решится сама собой. То есть, когда в программном модуле дисплея не будет непосредственно прописан какой-то конкретный I2C, а будет лишь отсылка к функциям модуля интерфейса дисплея, тогда этот модуль дисплея вы можете без изменений перенести на другой микроконтроллер и даже на другой интерфейс - SPI вместо I2C. На чистом Си это решается чуть хуже, чем на С++ с его шаблонами и абстрактными классами.
Цитата:
Но в МК несколько сложнее, нежели на ПК.
Ничуть! Если из ПК выкинуть операционку с её драйверами, то вы получите оооочень большой микроконтроллер. Однако, разработчики операционки позаботились о программистах прикладного ПО, написав стандартные функции ввода-вывода и установив правила выделения ресурсов в рамках ОС.
При включении оптимизации -O3 исключаются излишние вызовы функций, заменяясь на непосредственное встраивание кода функций. Код ускоряется в несколько раз при сохранении структурированности исходника.
Константные выражения вообще вычисляются во время компиляции, а для переменных выкинут не имеющий смысла вызов функции. Решение о встраивании кода или вызове функции принимается компилятором на основе его правил.
Если по правилам, то в функции работы с дисплеем не должно быть функции инициализации GPIO/SPI/I2C. Почему? Да потому, что например на один физический I2C можно повесить несколько самых разных устройств, это нормально. (На один SPI так же можно повесить разные устройства, снабдив разными CS каждое).
Не, у модуля работы с периферией (условный HAL) один инит на всё. Который дергает иниты каждой периферии. И ясное дело, если SPI или I2C шарится между несколькими периферийными устройствами, то инит у него отдельный... А если еще и разные конфиги, то их смена возможна, например, в функции активации того или иного девайса, перед поднятием CS, например. Ваша парадигма мне понятна и близка. Я давно стараюсь ее придерживаться.
КотПротон писал(а):
При включении оптимизации -O3 исключаются излишние вызовы функций
Да то понятно, что лесенку вызовов компилятор срежет...
sc0rpy, а про какую мееостанцию речь? Можно ссылочку, глянуть код на предмет читаемости? ну и на станцию посмотреть, мож идеи какие можно будет в копилку отложить...
Добавлено after 3 minutes 34 seconds: КотПротон, часто в начале пути не видно что на финишэ, а код нужэн вчера. и жыть хочеца сейчас и не только красивыми теориями.
_________________ глаза баяца, руки из жопы, но я не здаюсь
Говнокод (быстрое решение "в лоб") мы все хоть раз писали. Но это - одноразовый код, не предполагающий дальнейшего сопровождения. А для того, чтобы видеть, что на финише, для этого существует планирование - постановка целей и задач и поиск алгоритмов их решения. И чем больше у программиста есть готового багажа, который ПРАВИЛЬНО написан, с возможностью повторного использования без изменения, тем быстрее программист приходит к цели.
Да это не условный. HAL - Hardware Abstraction Level (Layer) - это совершенно реальный уровень взаимодействия внутри программы, отвечает за работу с хардваром, с железом то есть. И это совершенно нормально.
В представлении STM структурная организация демо-проектов выглядит вот так:
Таким образом, функционал работы с дисплеем относится к подуровню BSP, и работает через функционал LL или HAL. STM предлагает два варианта низкоуровневых драйверов - LL и HAL, отсюда и такое задвоение.
Это фуфло. Синтетический код. Очевидное подталкивание компилятора примитивной функцией с одним арифметическим выражением. Фактически мимикрия под простейшие функции HAL.
Я блондинко™, я иногда я пользуюсь.... Когда атмегу8 уже запаяла, а код при -О1 немножко не влазит... Да, знаю, это неправильная оценка ресурсов и выбор камня..... Но после восьмой меги в таком корпусе у меня следующая есть только мега328 - ее жалко ставить.... их у меня уже мало осталось.... Они еще нормальные, доковидные, по низким ценам куплены...
А под STM-ки таки да, -О1, не приходилось в них код "трамбовать", как в атмеги...
JackSmith, я слышал только что отладка под -ОЗ не идет.
Стэковерфлоу говорит, что никаких фундаментальных проблем нет, это байки из прошлого.
Цитата:
На заре gcc (2.8 и т.д.), а также во времена egcs и RedHat 2.96 -O3 иногда был довольно глючным. Но это было более десяти лет назад (пост 2012 года), и -O3 не сильно отличается от других уровней оптимизации (по уровню глючности).
Однако он, как правило, выявляет случаи, когда разработчики полагаются на неопределённое поведение, поскольку более строго следуют правилам языка (языков), и особенно его граничным случаям.
От себя могу сказать, что я уже много лет использую -O3 для разработки программного обеспечения в финансовом секторе и ещё не встречал ни одной ошибки, которая бы отсутствовала при использовании -O2.
Отладка возможна на всех уровнях оптимизации, другое дело, что можно, например, удивиться невозможности поставить breakpoint - это место может исчезнуть в результате оптимизации. Да и вообще, мне кажется странным отлаживание в одном уровне, а затем его изменить и получить иную версию кода.
странно, что на сайте arm рекомендуют использовать "-O2", вместо "прогрессивного -O3", да?
в качестве эксперимента, собрал USB CDC драйвер для F1 с "-O3", и он даже работал, но прошивка весила 8 КБ вместо 7 КБ при сборке с флагом "-Os". я в свое время дизассемблировал код получаемый с "-O2" и "-Os", и решил для себя что последний дает самый лучший результат. но какбы не навязываю.
с "-O3", и он даже работал, но прошивка весила 8 КБ вместо 7 КБ
Так и должно быть, вызовы заменяются инлайном, что в некоторых случаях, позволяет повысить быстродействие. Никакого криминала тут нет.
JackSmith писал(а):
на сайте arm рекомендуют использовать "-O2", вместо "прогрессивного -O3", да?
Да вроде нет? Только для Отладки, рекомендуется более низкий уровень оптимизации (то что я выше писал), для работы можно использовать любой, подходящий под задачу. Не надо демонизировать -O3. Реальных причин для этого нет. Особенно на МК, особенно в real-time приложениях.
Цитата:
-O3
Maximum optimization. When debugging is enabled, this option typically gives a poor debug view. Arm recommends debugging at lower optimization levels.
_________________ При решение наиболее сложных задач, большинство, как правило, ошибается...
Отладка на всем, что отлично от None - работает, но специфисиссськи, и нужно переключить режим "шагания" на Instrucrion Stepping Mode, хотя это не всегда помогает в отладке. И вообще, я не говорил об отладке при включенной оптимизации. Я говорил о том, что без оптимизации получается весьма медленный код из-за того, что генерируется он "топорно" - что написано, то и делается, без всяких там сокращений. Отсюда и родился миф, что якобы написание на ассемблере дает лучший код, нежели на С/С++. Поэтому, рабочий код очень желательно включать на оптимизацию, иначе он будет тормознутым. Конечно, для этого надо знать, как правильно писать, чтобы оптимизация не повыбрасывала всю ту писанину, которую вы так гордо писали
PS. Кстати, мало кто знает, но разным файлам и разным функциям можно назначать разные уровни отладки. Это прописывается через флаги компилятора и атрибуты ф-ций.
Последний раз редактировалось КотПротон Пн авг 11, 2025 14:39:48, всего редактировалось 2 раз(а).
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 18
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения