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

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

Но если необходимо сконфигурировать несколько пинов, а тем более динамически менять конфигурацию <...> то гораздо проще обернуть всю работу с регистрами в класс, и пользоваться методами типа setPin/resetPin.
Было бы не плохо продемонстрировать удобство вашего способа. Ну, скажем, вывод на семисегментник, у которого сегменты разбросаны по разным портам в произвольном порядке.
И однотипная настройка\установка нескольких битов в порте вместо одного прохода потребует несколько: на каждый бит по отдельности. Плюс «switch-case» сейчас не в моде?
Смотря какова конечная задача. Тем более, что люди продолжают по привычке считать, что код с несколькими слоями трансляции вызовов будет таким же в результирующем бинаре. Т.е., про оптимизацию компилятором успешно забывают, а потом кидаются экскрементами в код, облегчающий конфигурацию. Зато стоически молчат о том, как тяжко копаться в их «быстром» и «однопроходном» коде.
Так потому и просим продемонстрировать настройку и выхлоп ассемблера на какой-то практической задаче. Насколько подход очередного слоя абстракции лучше или хуже других.
Ну, примеры по теме статьи может привести только автор. Я же хотел бы обратить внимание, что оптимизирующий компилятор успешно складывает цепочки вызываемых функций и пересчитывает все константные значения. Т.е., условно говоря, port->setPin(13); превращается в GPIOx->RegName = (GPIOx->RegName & 0xFFFFEFFFU) | 0x00001000U; самим компилятором, а потому бит меняется за минимум операций. А единовременное переключение группы пинов вероятно разве что в том случае, когда вообще весь алгоритм управления периферией может уложиться в логику конечного автомата. Только редко когда проектам на слабых контроллерах нужна подобная скорострельность.
Скажем, я пишу библиотеку для дисплея на hd44780, нужно объявить 4 подряд идущие ноги для линии данных, одну под RS и одну под E. Две последние для удобства разводки могут оказаться на разных портах.
На макросах я могу сделать так:
#define LCD_44780_DATA C, 6 //PC6, PC7, PC8, PC9
#define LCD_44780_RS B, 15
#define LCD_44780_E A, 8
#include "lcd_44780.h"

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

Я конце 90-ых, имея под рукой IBM AT-286 с EGA-адаптером, для достижения скорострельности полез в системное программирование и стал управлять буфером кадров напрямую. Так там порядок байт бит-масок пикселей в буфере отличался от процессорного. К тому же, один байт — горизонтальная строка, длиной с ширину символьного сегмента (8x16: 80x25 для VGA и 8x14: 80x25 для EGA). У EGA-режима памяти хватало на два независимых кадра, а VGA — на 1,5 — соответственно один постоянно видимый.

Для отрисовки примитивов (это ещё никакого антиалиасинга) приходилось пересчитывать сдвиги битовых масок и накладывать их через смещения по коэффициенту. Но и то API выглядел примерно так:

graph->drowPix(x, y);
graph->drowLine(x1, y1, x2, y2);
graph->drowSquare(x1, y1, r);

И т.д.

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

Но то время ушло. Теперь даже жалкая AtMega тактуется выше, чем тогда процессор (8-12 МГц было). Теперь стало глупо заниматься побайтовой оптимизацией, хотя искренне жаль…
А какое отношение это имеет к назначению портов?
То есть внутри того же «lcd_44780.h» имеет смысл реализовать стандартные функции вывода текста (графику-то он не поддерживает). С этим никто не спорит. Но при разводке платы бывает удобно поменять порты, к которым он подключен. Не лезть же каждый раз внутрь библиотеки! И абстракции вокруг портов должны решать именно эту задачу, а вовсе не лезть во внутренности библиотек.
«И однотипная настройка\установка нескольких битов в порте вместо одного прохода потребует несколько: на каждый бит по отдельности. Плюс «switch-case» сейчас не в моде?»
Вся ветка обсуждения тянется от этого поста.
И? Вас постоянно куда-то в сторону уводит.
Да, слои абстракции могут не добавлять тормозов, но это надо показать.
абстракция она на то и абстракция, чтобы уйти от конкретики. В Вашем случае абстракция получилась над одним, уже теряющем популярность (ибо по практически той же цене и в том же форм-факторе доступна stm32f411/f401), stm32f103.

опять же, как часто Вам приходилось в устройстве с МК менять настройки пинов динамически? Я даже сходу пример-то не могу такой придумать. Максимум что приходилось делать, это переводить в состояние, при котором потребление будет минимальным. Опять же, однократно перед сном. Получается что динамика в данном случае избыточна. Шаблоны тема сложная, но интересная.

Вот собственно и все. Думаю в таком виде работать с портами ввода-вывода гораздо удобнее, чем напрямую с регистрами.

Относительно. Когда говорят «пишу на регистрах» не стоит считать что по всему коду разбросы строки вида:
 GPIOC->BSRR = 1 << pin; 

Это все так же оборачивается в функции. Поэтому и спорное утверждение об удобстве.
Мой вариант, правда для nrf52840, но суть та же. Настройка пина. При конфигурации происходит сборка по ИЛИ допустимых настроек.

  Gpio<port1, 3> enable_;
  enable_.configurate(output | noPull);
  enable_.set(); // разрешаем питание на драйвер светодиода


вот что порадовало, так это использование регистров «BRR» и «BSRR».

nomber? это от какого слова?
mode = Sarcasm;
можно предположить что от транскрипции [ˈnʌmbə®], достаточно «близко» )
абстрагируемся от регистров CMSIS при
Погодите, я правильно понял? Вы уже имеющийся слой абстракции (CMSIS) обернули еще одним слоем абстракции (вашим классом)?
Абстракция это хорошо, когда в ней есть нужда.
Наверное не совру, что каждый, кто пишет прошивки, так же пишет небольшие «абстракции», чтобы сделать гибкую настройку некоторых пинов, их прерываний и т.д., чтобы далее, в процессе добавления в код прошивки нужных фич, не терять время на переконфигурирование пинов/таймеров/ADC/DMA и т.д. Для каждого проекта, эти «абстракции» свои, своего рода маленький «велосипед». Если этот маленький «велосипед» для текущего проекта выполняет свои задачи, то ок.
У вас «велосипед» работает — это хорошо, но, в других проектах он будет не нужен (ИМХО).
У меня тоже есть свои «велосипеды», можете посмотреть в коде на гите (не по STM).
Я к чему, таких статей можно нагенерить множество (по каждому проекту, свои «велосипеды»), и, кстати ИМХО — это будет своего рода база знаний, что можно сделать при написании кода.

Я понимаю, что выражение «велосипед» выглядит в данном контексте не очень, хотел писать «фича», но решил оставить так.

Автору: плюс в карму, ноль за статью.
Я к чему, таких статей можно нагенерить множество (по каждому проекту, свои «велосипеды»), и, кстати ИМХО — это будет своего рода база знаний, что можно сделать при написании кода.

Идея интересная. Задумался над тем, чтобы показать свой «шаблонный» велосипед над freeRTOS и стеком BLE. Но терзают определенные сомнения ибо «вроде работает, если вот и тут не трогать» )

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

Да, но у меня достаточно часто при переносе велосипеда из проекта в проект он обрастает новыми деталями. Потому что предусмотреть все и сразу не возможно. Обязательно что-нибудь не обычное вылезет.

на тему развития велосипедов

Да, вот та же мысль — бесполезно писать статью вида «а тут я обернул вот так». Потому что обернуть можно тысячей разных способов.

Интересные статьи — про нетрадиционные методы «шоколадного обертывания», вроде тех, что пишет lamerok, либо напротив — критику этих методов (которая неоднократно была к комментариях, но так и не оформилась в виде статьи). А как в 100500-й раз обернуть что-то в класс…
lamerok, ага, его статьи зачитаны до дыр. Более того, по сути эти статьи и сподвигли меня на переход в с++ при разработке проектов.

Потому что обернуть можно тысячей разных способов.

Согласен, но не все одинаково удобны. у меня аллергия на динамическую работу с памятью в МК, поэтому делаю все по максимуму в статике. Счастье было, когда удалось статический полиморфизм (CRTP) реализовать у себя. Ну по крайней мере мне так кажется что удалось)
Ну, аллергии на динамическую память быть не должно вовсе. Аллергия должна быть на ее освобождение, а вот выделение оной — зачастую единственный возможный способ красиво решить задачу.

Есесна запрет на освобождение рождает много демонов — например работа со строками становится «как в Си» (что не есть плохо, просто не идиоматично). Но что поделать.
динамическая память в Qt, для GUI приложения. Там во всю пользуюсь, а тут… с учетом того что SEGGER не понятно когда, где и как создает кучу, как-то не особо приятно.
Segger? А почему он кучу создает? Впрочем неважно, куча всегда создается в одном и том же месте, ваша задача — корректно рассчитать ее параметры, и отработать все сценарии выделения памяти. Повторюсь, иногда никак без выделения, вот освобождать — табу. Поэтому динамика в полном смысле слова невозможна/нежелательна. Ну а в Qt вы не то что вовсю пользуетесь, вы там ей просто _не можете_ не пользоваться ))
вот и мне было интересно почему он. В настройках проекта указал, что размер heap = 0. Но тем не менее при переопределении виртуальной функции в памяти образовался регион, дословно не помню, но сочетание seggerHeap там было. Искать концы было лень, так как это была «песочница» для обкатки шаблона.
Мммм, загадка… Интересно было бы найти концы, потому что в целом это забота лишь компоновщика, и ничья более. Я только не понял, вы исполняли код только в отладчике, или на железе тоже?
код запускался во встроенном симуляторе. В настройках проекта:
Heap Size = 0;
Main Stack Size = 2048;
Process Stack Size = 0;

Linker = SEGGER;
Ну вот например, тренировался на этом шаблоне:
class Singleton {
public:
  static Singleton* instance() { return inst_; }
  void run()  { }

private:
  Singleton() {}
  static Singleton* inst_;
};
Singleton* Singleton::inst_ =  new Singleton();


Расход памяти можно посмотреть тут:


То есть формально куча не создана, но указатель на нее у segger'а есть. Они относительно недавно, в этом году, меняли и линкера, и компилятора своего. Из-за этого перестали собираться проекты, созданные в ранних версиях. После выхода патча стало легче, но старые проекты восстанавливать все равно пришлось
Ах ты ж елки, я не понял, что вы делали проект в Segger Embedded Studio. И крайне удивлен, что там есть свой линкер, думал на ld сделано.

Разве Segger виноват в том, что программист задает нулевой размер кучи а потом создает экземпляр класса при помощи new, причем там, где это совершенно не нужно? Классический ленивый Singleton может так выглядеть:


class Singleton
{
public:
    static Singleton& instance()
    {
        static Singleton inst;
        return inst;
    }
    ...
};

Разве Segger виноват в том, что программист задает нулевой размер кучи а потом создает экземпляр класса при помощи new, причем там, где это совершенно не нужно?

проблема в другом. Программист явно указал не использовать кучу, но зачем-то решил это делать (забыл например или не знал что нельзя использовать кучу). Segger в данном случае создал кучу сам в некой области памяти. По map файлу это видно было, но нет данных о размере этой кучи.

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

 Классический ленивый Singleton может так выглядеть

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

template <typename T>
class Singleton {
public:
  static constexpr T& instance() { 
    return inst_;   
  }
  
  const Singleton<T>& operator=(const Singleton<T>&) = delete;

private:  
  inline static T inst_;
};

Каждый раз надеюсь, что тут что-то новое и каждый раз одно и то же.
Очередной джедай видит как бедные baremetal'исты ковыряются в своих регистрах и решает внести свои собственные абстракции. Тычет классами в несчастные регистры, моргает обернутым, как шаверма на вокзале, пином со светодиодом и уже готовит следующую статью с шаблонами для разных датчиков.
Писанина ради писанины.

а чего хочется увидеть, хоть примерно? Без сарказма вопрос.
Чего-то более оригинального, сложного и необычного.
Подробного описания периферии, принципов ее работы, эмуляцию и запуск на аппаратном модуле.
Красивого законченного устройства.
Полноценной библиотеки, которая бы действительно делала взаимодействие с контроллером более удобным.

Товарищ COKPOWEHEU прав. Мне ещё было бы интересно посмотреть на красивое решение на тех же плюсах какой-нибудь нудной проблемы(или проекта), которое добавило бы в копилку "за высокие абстракции".

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

Подробного описания периферии, принципов ее работы, эмуляцию и запуск на аппаратном модуле.

Могу рассказать про свой опыт работы с nrf52840. Достаточно интересная периферия с возможностью каскадирования внутри чипа через модуль PPI (Programmable peripheral interconnect — программируемое подключение периферии). Вроде занятная штука, но интересно ли будет?
В продолжение статьи требуем сравнение полученного ассемблерного кода в авторской интерпретации с аналогом, например, на макросах.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории