Перейти к содержанию
    

Правильное отделение BSP от кода приложения

В продолжении темы про архитектуру, более конкретный вопрос.

Во архитектурах многих проектов, начиная от какого нибудь CubeMX, заканчивая всякими кастомами, я наблюдаю четкое разделение на прикладной код приложения и код BSP (board support package). Как я понимаю, штука эта тащиться с всяких линуксов-юниксов, но там это чутка более сложная штука, тут хочу ее рассмотреть в контексте bare-metal или не сложных РТОС, не имеющих своей прослойки драйверов, типа FreeRTOS. Так же обсуждение пойдет в контексте С++, т.к. в С можно в принципе все тоже самое сделать, но костылей поболее будет.

 

Ниже утверждение, поправьте, если я не прав:

И так, у нас есть BSP, который является неким библиотечным модулем кода, который содержит прослойку для работы с "конкретной" конфигурацией железа и предоставляет некий интерфейс\API для кода приложения. Именно работа с "конкретной" конфигурацией железа в целом отличает его от сортов всяких HALов. Там где HAL предоставляет API дрыганья ногой, BSP предоставляет интерфейс включения\отключения LED.

Подобное разделение позволяет произвести инверсию зависимостей и выдвинуть требование API от приложения, после чего реализовать BSP и возможно не один. Например для отладочного комплекта и реального устройства. В том числе какой-нибудь mock-bsp, позволяющий производить тестирование прикладного кода.

 

Давайте для примера возьмем обычный ногодрыг, драйвер светодиода. 
Какие требования могут быть к этому коду:
  1) Возможность первоначальной конфигурации кодом приложения. Т.е. приложение должно мочь задать некие параметры при инициализации этого модуля.
  2) Наличие интерфейса (в контексте ЯП). Если у нас 100 драйверов светодиодов, хочется один общий тип на них все, а не LedOne, LedThree, ... 
  3) Независимость от других сущностей. Если какой-либо модуль приложения использует этот драйвер, он должен знать только об этом драйвере и иметь доступ только к нему, а не о ко всему интерфейсу BSP.
  4) В контексте светодиода не очень наглядно, но некоторые драйвера могут желать иметь свои таски или хотеть быть потокобезопасными. А значит должны иметь доступ к RTOS, каким либо образом. 

 

Собственно вопрос - как "правильно" это реализовать?

И тут сразу несколько вариантов:

//**********************************************************************
// Конструирование в коде приложения
// Плюсы: настоящий RAII
// Минусы: BSP никак не сможет контролировать че приложение делает с его драйверами. И если в BSP есть какой-то emergency функционал вроде ОТРУБАЕМ ВСЕ НА..., то он будет сделан через костыли. 
//// Где то в BSP
class LedDriver
{
      typedef enum
      {
          A, B, C, D
      }LedEnum;

      LedDriver(LedEnum led_enum, uint8_t brightness)
}

//// Где то в приложении
#include <bsp/led_driver.h>
BSP::LedDriver led_a = { BSP::LedDriver::A, 0xFF };
  
//**********************************************************************
// Конструирование в BSP и возврат ссылки/указателя
// Плюсы: почти что RAII. И BSP и приложение могут контролировать драйвер.
// Минусы: RAII то не настоящий, таскание ссылок по приложению может быть не очень удобно. 
//// Где то в BSP
class LedManager
{
    static LedDriver led_driver_a; // Не сконструированы до запроса
    static LedDriver led_driver_b;
    static LedDriver led_driver_c;
    static LedDriver led_driver_d;

    static LedDriver& Initialize(LedDriver::LedEnum led_enum, uint8_t brightness);
}
  
//// Где то в приложении
#include <bsp/led_driver.h>
BSP::LedDriver& led_a = BSP::LedManager::Initialize(BSP::LedDriver::A);
  
//**********************************************************************
// Приложение не владеет драйвером, только управляет. (HALо-образно, императивный стиль)
// Плюсы: очень легко накодить, внутри BSP можно любую грязь делать.
// Минусы: RAII не выполняется, это императивный стиль и возможно будут проблемы с передачей интерфейсов. 
//// Где то в BSP
class LedManager
{
    static void Init(BSP::LedDriver::A, uint8_t brightness);
    static void Enable(BSP::LedDriver::A);		
    static void Disable(BSP::LedDriver::A);
    static void Toggle(BSP::LedDriver::A);
}

//// Где то в приложении
#include <bsp/led_driver.h>
const BSP::LedDriver::LedEnum app_led = BSP::LedDriver::A;
BSP::LedManager::Init(app_led, 0xFF);
BSP::LedManager::Enable(app_led);

  

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

13 minutes ago, Solonovatiy said:

Собственно вопрос - как "правильно" это реализовать?

Надо всегда делать так, как потом ВАМ будет удобно этим пользоваться.

Чтобы это понять, следует начать какой-нибудь реальный проект, на котором будете отлаживать этот "концепт".

Вся сложность в том, что вы заранее не знаете как вам будет удобнее и быстрее, поэтому я пошел именно по такому пути - допиливание по ходу под реальные проекты.

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

В будущем такой ВИЗУАЛЬНЫЙ подход поможет избежать или значительно сократить многократные переделки. 

 

Ну и на последок - смотрите и изучайте как это сделано у других ))

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

9 minutes ago, Forger said:

Надо всегда делать так, как потом ВАМ будет удобно этим пользоваться.

Чтобы это понять, следует начать какой-нибудь реальный проект, на котором будете отлаживать этот "концепт".

Вся сложность в том, что вы заранее не знаете как вам будет удобнее и быстрее, поэтому я пошел именно по такому пути - допиливание по ходу под реальные проекты.

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

В будущем такой ВИЗУАЛЬНЫЙ подход поможет избежать или значительно сократить многократные переделки. 

 

Ну и на последок - смотрите и изучайте как это сделано у других ))

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

Собственно тема для этого и создана - может кто расскажет о своем опыте и\или тыкнет в проблемы концепций, что я описал.

Сам я склоняюсь ко 2му варианту, т.к. тогда и BSP и приложение будут иметь доступ к объектам класса драйвера. С другой стороны, возможно сама концепция "активного" BSP не верна и он должен быть полностью пассивным (чистой библиотекой), а вся активность должна быть описана внутри кода приложения.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

55 minutes ago, Solonovatiy said:

что удобно может быть до определенного момента

Для этого как раз и следует сразу внедрять подход в реальный проект, а не ждать пока родится некий "идеальный" BSP, который по факту может оказаться огромным фэйлом

 

Quote

, а потом поедут костылища, что все проклянешь.

Костыли не появляются из ниоткуда, из создает сам прогер: где-то поленился, где-то перемудрил, где-то решил повыпендриваться....

 

 

55 minutes ago, Solonovatiy said:

 тыкнет в проблемы концепций, что я описал.

вы их увидите только при попытках применения в реальном коде, с виду мое лично первое впечатление - сильно перемудрено ))

посмотрите для примера весьма тяжеловесную концепцию от mbed

 

тут на электрониксе не раз проскакивала дискуссия на подобную этой теме, пройдите поиском

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

 

вот тут показательно, выдержка из реального проекта

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

36 minutes ago, Forger said:

тут на электрониксе не раз проскакивала дискуссия на подобную этой теме, пройдите поиском

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

 

вот тут показательно, выдержка из реального проекта

Abstract pin это уже что-то внутриBSPшное, т.к. как раз код приложения никаких абстрактных пинов знать не должен, он вообще не должен знать ни про какие пины.  UART1\UART2 по идее тоже не должно быть, может COM\RS485 какой, как некий драйвер интерфейса. Это все должно быть скрыто в BSP. Я к тому, что это не совсем релевантно.  Там все таки обсуждается внутрянка самого BSP, тут я хочу обсудить связь BSP и юзер кода.

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

Я велосипедов тоже порядком успел написать. Все говно = ) Они работают только сразу после рефакторинга на них, а как только начинаешь расширять - колеса отваливаются.  

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

Изменено пользователем Solonovatiy

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

20 minutes ago, Solonovatiy said:

Abstract pin это уже что-то внутриBSPшное, т.к. как раз код приложения никаких абстрактных пинов знать не должен,

Почитайте пример внимательнее. Создать экземпляр AbstractPin невозможно, т.к. он абстрактный. Но создать можно просто Pin, AnalogPin, LedPIN и т.д.

Видать у вас какое-то свое видение границы bsb и кода. Поэтому мой совет прежний - начните какой-нибудь проект на базе своего "велосипеда". Многие вопросы отпадут сами собой ))

 

20 minutes ago, Solonovatiy said:

UART1\UART2 по идее тоже не должно быть, может COM\RS485 какой, как некий драйвер интерфейса.

Вы путает теплое с мокрым. COM\RS485 к вашему сведению - это аппаратная "надстройка" над обычными USART. В код эту надстройку кроме названия никак не засунуть. Поэтому в коде фигурирует USART.

Далее по коду его можно "обвесить" неким протоколом, но к некому BSP это уже не имеет никакого отношения.

 

20 minutes ago, Solonovatiy said:

Я велосипедов тоже порядком успел написать.

Однако в первом посте я вижу как раз очередной велосипед )))

Поэтому и посоветовал обратить внимание на уже существующие решения.

21 minutes ago, Solonovatiy said:

я вот про это велосипедостроение с абстрактными фабриками абстрактных пинов уже много где читал видел смотрел.

Я не использую фабрики для создания тех или иных сущностей для аппаратнозависимых частей проекта. Это здесь не требуется.

Например, при слове abstract я не вижу сразу некую фабрику. Вижу просто полностью абстрактный класс, экземпляр которого создать невозможно. Т.к. это по сути базовый класс, нужен только для ссылок (указателей).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

 

29 minutes ago, Forger said:

Почитайте пример внимательнее. Создать экземпляр AbstractPin невозможно, т.к. он абстрактный. Но создать можно просто Pin, AnalogPin, LedPIN и т.д.

Это очевидно. Вопрос про то, что PIN вообще на хрен не надо в юзер код, сейчас это пин, завтра это расширитель GPIO на SPI или вообще удаленное устройство. В юзер коде по идее должны быть LED-лампочка-огонек-светодиод, который зажжется, когда его попросят, а дрыганьем ноги или пакетом данных на шине - юзер коду должно быть пофигу. Разве нет?

(ну лан, с удаленным устройством может и перегнул, но Pin пусть и простой, но не очень хороший пример, с уартом ниже - лучше).

29 minutes ago, Forger said:

Вы путает теплое с мокрым. COM\RS485 к вашему сведению - это аппаратная "надстройка" над обычными USART. В код эту надстройку кроме названия никак не засунуть. Поэтому в коде фигурирует USART.

Как раз нет. Если у вас на плате к USART1 прицеплен MAX485 - вы как его не перенастраивайте, че с ним не делайте он в ИК-порт не превратиться. По факту есть только одна его правильная конфигурация, причем не как USART1, а как USART1 + пара пинов, для управления MAX485. Любые другие конфигурации будут ошибочными.(на некой данной плате)

С другой стороны в пользовательском коде, модуль который использует 485 - хочет использовать его, а точнее просто некий потоковый IO, но явно не ковырять биты его ног. 

PS кстати в этом посте родился небольшой ответ. Пользовательский код не должен вообще как либо выбирать оборудование или драйверы, т.е. никаких конструкций Led(LED:A), какой дали тот и жрет. Это не его ответственность. Че с этим делать пока не ясно. 

 

29 minutes ago, Forger said:

Однако в первом посте я вижу как раз очередной велосипед )))

Да это просто С++ обертка над тем что я видел в проектах. Там ничего нового или велосипедного. Просто реализовать можно разными способами. 

 

Изменено пользователем Solonovatiy

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

23 minutes ago, Solonovatiy said:

Вопрос про то, что PIN вообще на хрен не надо в юзер код, сейчас это пин, завтра это расширитель GPIO на SPI или вообще удаленное устройство.

 

Вам решать, где у вас там некий "юзер-код", а где его нет. Границу вы сами себе рисуете. Никто не отругает, если нарисуете не там ))

Я лишь привел для примера свой подход для изоляции железа (МК) от софта. Меня он полностью устраивает. Позволяет легко менять камни, переносить модули из одного проекта в другой.

Так как код пишет программер (чаще всего я сам, в основном в одиночку), то я его проектирую так, как мне удобно прежде всего мне.

Если подразумевается конфигурирование самого устройства, то там все совсем иначе и никаких BSP и т.п. сущностей в принципе нет. Для этого существуют другие инструменты.

 

23 minutes ago, Solonovatiy said:

Если у вас на плате к USART1 прицеплен MAX485 - вы как его не перенастраивайте,

Повторюсь: программная реализация некого протокола вешается ПОВЕРХ порта. Не наследуется от него. Порт (usart/can и т.п.) является лишь полем (данными) в классе, который работает с этим портом.

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

Мне кажется, как раз после таких чудовищных надстроек и появляются костыли.

Код прежде всего должен быть простым и легко читаемым.

 

23 minutes ago, Solonovatiy said:

Пользовательский код не должен вообще как либо выбирать оборудование или драйверы,

Этот "подход" отлично создает костыли на ровном месте. Мне это кажется крайне неразумным для кода, который пишет один человек. Некое масло масляное.

Существует такой термин user friendy код. Он подразумевает, что проектировщик кода не станет сам себе умышленно вредить в коде ))

 

23 minutes ago, Solonovatiy said:

Да это просто С++ обертка над тем что я видел в проектах.

Обертка призвана упрощать код, его применение и сопровождение. А здесь - все в точности наоборот ))

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

HAL = Hardware Abstraction Level, то есть как раз то, что отделяет уровень "железа" (или "платы") от уровня "софта", то есть общей логики работы. Единых правил на все случаи жизни нет. Если у вас один-два светика, то можно написать GreenLEDOn, а если их стотыщщ и особенно если они выведены на какой-нить расширитель порта, то лучше завести номер (идентификатор) светика. И лучше, чтобы эти номера были не просто цифрами, а имели понятные текстовые имена констант.

В 01.01.2023 в 20:34, Solonovatiy сказал:

Если какой-либо модуль приложения использует этот драйвер, он должен знать только об этом драйвере и иметь доступ только к нему, а не о ко всему интерфейсу BSP.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

В 01.01.2023 в 20:34, Solonovatiy сказал:
И если в BSP есть какой-то emergency функционал вроде ОТРУБАЕМ ВСЕ НА..., то он будет сделан через костыли.

Так и напишите такой функционал. Ничего страшного, вы же программист, а не юзер.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В 01.01.2023 в 20:34, Solonovatiy сказал:

Там где HAL предоставляет API дрыганья ногой, BSP предоставляет интерфейс включения\отключения LED.

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

 

В 01.01.2023 в 20:47, Forger сказал:

тобы это понять, следует начать какой-нибудь реальный проект

Абсолютно верно! 🙂 Только так вы поймете, как с чем лучше работать. Я вот тоже в теории вроде как нашел кучу разных вариантов, а чтото болееменее понял только на реальном проекте. Не обязательно хвататься сразу за сложный, пусть это будет светофор или показомер.

В 01.01.2023 в 20:34, Solonovatiy сказал:

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

Наоборот, это RTOS имеет доступ к драйверам устройств. А потокобезопасность доступа делается с помощью мьютекса, который выдает право доступа, или с помощью Gatekeeper-задачи, которая работает непосредственно с драйвером, а все остальные задачи обращаются к ней.

В 01.01.2023 в 22:21, Solonovatiy сказал:

Конфигурация железа пишется один раз на проект,

...а потом переписывается в процессе разводки платы или доводки кода/устройства 🙂

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В 01.01.2023 в 23:05, Solonovatiy сказал:

Пользовательский код не должен вообще как либо выбирать оборудование или драйверы, т.е. никаких конструкций Led(LED:A), какой дали тот и жрет.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

27 minutes ago, Variant99 said:

или с помощью Gatekeeper-задачи, которая работает непосредственно с драйвером, а все остальные задачи обращаются к ней.

Использую именно такой подход (ранее не знал что он даже имеет название ))).

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

Поэтому порт является полем классе, который уже является протокольной настройкой над портом или портами.

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

Очень упрощает отладку кода, перенос его в другие проекты, особенно  это заметно с применением модульного тестирования кода.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 hours ago, Variant99 said:

HAL = Hardware Abstraction Level, то есть как раз то, что отделяет уровень "железа" (или "платы") от уровня "софта", то есть общей логики работы.

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

Она сами по себе ничего не говорят, как периферия настроена и как будет использоваться, это задает пользователь. 
BSP - это уровень выше, из свободно конфигурируемой периферии делает вполне себе конкретную

 

3 hours ago, Variant99 said:

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

Это как раз максимально распространенный случай. Уровни светодиодов это вершина айсберга. 
А MMC какой? В каком он режиме подключен, SPI\1B\4B - это не определяет HAL, но при этом это не надо тащить в пользовательский код. Т.к. для пользователя это некий DiskIO, ну или хотя бы просто SD Card, как там подключены проводки - вообще не зона интересов прикладной программы.

У меня в одном проекте в итоге так и было, что в отладочном комплекте SD висит на SPI, а в проде на 4bit. Тогда я это решал тупо #ifdef NODEBUG, но оно уже почти не возможно использовать, когда отладочных комплектов становиться 2+ и появляются ревизии.

3 hours ago, Variant99 said:

Наоборот, это RTOS имеет доступ к драйверам устройств. А потокобезопасность доступа делается с помощью мьютекса, который выдает право доступа, или с помощью Gatekeeper-задачи, которая работает непосредственно с драйвером, а все остальные задачи обращаются к ней.

Какая нибудь FreeRTOS не знает, что такое драйвера - вообще. Всякие chibi и мбеды не в счет.
А то, что вы взяли таску и на основе ее сделали драйвер крутящийся в таске - не делает это частью RTOS.

Это все та-же прослойка, которая обеспечивает связь прикладного кода и железа. И именно она имеет доступ и к RTOS и железу. 

Изменено пользователем Solonovatiy

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

9 минут назад, Solonovatiy сказал:

HAL это просто обертки над регистрами,

Это они такие в видении писателей CubeMX. Хотя они уже выполняют свою функцию - изолируют от управления регистрами и их битами. BSP - это всего лишь условное видение, опять же предложенное авторами от ST. На деле, всё это входит в Hardware Abstraction Level, назначение которого - скрытие аппаратных особенностей (различий). 

15 минут назад, Solonovatiy сказал:

Какая нибудь FreeRTOS не знает, что такое драйвера - вообще.

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

Я ж говорю - попробуйте написать какой-либо реальный простой проект, типа того же светофора. Воткните в макетку пять светодиодов и сделайте циклическую работу трехсигнального светофора с двумя доп.секциями. За этим занятием и поймете, что есть как. Там будут все необходимые уровни абстракции. В том числе можно и RTOS смонтировать.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...