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

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

12 minutes ago, Variant99 said:

BSP - это всего лишь условное видение, опять же предложенное авторами от ST. 

BSP термин появившийся намного раньше чем сам куб и использующийся далеко не только в нем.

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


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

...так же как и HAL, соппсна 🙂 И в принципе то, BSP и HAL - это синонимы. Нет необходимости именно разделять их. Представленный писателями ST вариант HAL-а является дополнительной прослойкой, попыткой унификации работы с регистрами-битами микроконтроллера.  

Вот, про тот самый пример со светофором, о котором я говорил:

class Light {
public:
	Light(GPIO_TypeDef *port, uint8_t pin)
	{
		this->port = port;
		this->pin = pin;
	}

	void Init()
	{
		port->MODER &= ~(0x2 << (2 * pin));
	}

	void On()
	{
		port->BSRRL = 1 << pin;
	}

	void Off()
	{
		port->BSRRH = 1 << pin;
	}

private:
	GPIO_TypeDef *port;
	uint8_t pin;
};

class TrafficLight {
public:
	TrafficLight(int redTime, int yellowTime, int greenTime)
	{
		this->redTime = redTime; this->yellowTime = yellowTime; this->greenTime = greenTime;
	}

	void Init()
	{
		red.Init(); yellow.Init(); green.Init();
	}

	void Stop()
	{
		red.On(); yellow.Off(); green.Off();
	}

	void Ready()
	{
		red.Off(); yellow.On(); green.Off();
	}

	void Go()
	{
		red.Off(); yellow.Off(); green.On();
	}

private:
	Light red {GPIOB, 14}, yellow {GPIOE, 1}, green {GPIOB, 0};
	int redTime, yellowTime, greenTime;
};

---------------------------------------------------------
  	TrafficLight trl(10, 3, 15);
	trl.Init();
	trl.Go();

То есть, в классе Light прописаны операции с портами на уровне регистров. или если хотите, можете вставить туда ST HAL-обертки. В классе TrafficLight в приватной его области прописаны имена портов и номера пинов для каждого светика. А дальнейшая работа уже ведется не с именами портов/номерами пинов, а с именами сигналов светофора. То есть, выше этого класса от вас уже скрыты аппаратные подробности расположения светодиодов, вы уже абстрагировались от них, завершив написание Hardware Abstraction Level (или Board Support Package, если хотите так называть).

Инициализацию хардвара, как я понял, в конструктор класса лучше все-таки не вставлять. Хотя идея заманчивая, но как многие говорят, не самая лучшая.

Изменено пользователем Variant99
добавил код

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


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

3 hours ago, Variant99 said:

То есть, в классе Light прописаны операции с портами на уровне регистров. или если хотите, можете вставить туда ST HAL-обертки. В классе TrafficLight в приватной его области прописаны имена портов и номера пинов для каждого светика. А дальнейшая работа уже ведется не с именами портов/номерами пинов, а с именами сигналов светофора. То есть, выше этого класса от вас уже скрыты аппаратные подробности расположения светодиодов, вы уже абстрагировались от них, завершив написание Hardware Abstraction Level (или Board Support Package, если хотите так называть).

Инициализацию хардвара, как я понял, в конструктор класса лучше все-таки не вставлять. Хотя идея заманчивая, но как многие говорят, не самая лучшая.

 

Ну смотрите, у вас захардкожены пины в прикладном классе и инициализация их привинчена намертво к некому API.
Это не переносимо без изменений прикладных классов, не тестируемо и грязновато. Причем грязновато наверно краеугольный камень, я хз, может тут все столманы, что начисто пишут идеальные модуля и потом не переписывают\вставляют костыли, когда вспоминают, что нужно еще и (некое действие) производить. 

Я предполагаю, что-то вроде:

Spoiler
// BSP Library
// Публичный класс. Можно сделать его приватным и сделать интерфейс,
// но вой же о производительности подниметься =).
// Поэтому в теории да, его можно сконструировать в самом приложении, но сразу станет ясно, что чет потекло. 
//
class Led
{
public:
	Led(void* port, int pin) { /* HAL initialize call; */ }
	void Enable() { /* HAL enable call; */ }
	void Disable() { /* HAL disable call; */ }
};

// BSP Library
// Не очень красивое решение на первый взгляд.
// По факту - вам один фиг размечать их руками и указывать какие используются.
// Однако тут если захотеть можно реализовать и доступ по массиву и возврат массивов.
//
class BoardLeds
{
public:
	BoardLeds()
	: green(GPIOA, 15)
	, yellow(GPIOB, 2)
	, red(GPIOD, 5)
	{}

	Led& AccessGreenLed() { return green; }
	Led& AccessYellowLed() { return yellow; }
	Led& AccessRedLed() { return red; }

private:
	Led green;
	Led yellow;
	Led red;
};

// Application library

class TrafficLight
{
public:
	struct Timings
	{
		int redTime;
		int yellowTime;
		int greenTime;
	};

	struct Leds
	{
		Led& red;
		Led& yellow;
		Led& green;
	};

	TrafficLight(Timings timings, Leds leds)
	: leds_(leds)
	, timings_(timings)
	{}

	void Stop()
	{
		leds_.red.Enable(); leds_.yellow.Disable(); leds_.green.Disable();
	}

	void Ready()
	{
		leds_.red.Disable(); leds_.yellow.Disable(); leds_.green.Disable();
	}

	void Go()
	{
		leds_.red.Disable(); leds_.yellow.Disable(); leds_.green.Enable();
	}

private:
	const Timings timings_;
	const Leds leds_;
};


// Application Main
int main()
{
	BoardLeds board_leds = {};

	TrafficLight::Leds leds
	{
		board_leds.AccessRedLed(),
		board_leds.AccessYellowLed(),
		board_leds.AccessYellowLed(),
	};

	TrafficLight::Timings timings
	{
		5,
		5,
		5
	};
	
	TrafficLight traffic_light = { timings, leds };

	traffic_light.Go();
}

 

А что это даст, кроме выполнения минимальных норм всяких клинкодов (ну на самом деле с натяжкой, но никто же не предлагает обмазываться интерфейсами)? 

Spoiler
// BSP x86
// Дописываем файлы *.cpp\*.c x86 BSP.
// *.h у нас лежат отдельно и их никто не трогает никогда, кроме добавления функционала.
// В пару щелчков мыши запускаем проект на хостовой машине в любой момент времени и отлаживать какие то вещи без доступа железа.
class Led
{
public:
	Led(void* port, int pin) { std::cout << "Initialized PIN" << std::endl; }
	void Enable() { std::cout << "Initialized PIN" << std::endl; }
	void Disable() { std::cout << "Initialized PIN" << std::endl; }
};

// BSP Microchip
// Дописываем файлы еще одно BSP *.cpp\*.c и можем запускать проект на 2х МК или устройствах.
// *.h у нас лежат отдельно и их никто не трогает никогда, кроме добавления функционала.
// 
class Led
{
public:
	Led(void* port, int pin) { /* Microchip MLA\ASF call; */ }
	void Enable() { /* Microchip MLA\ASF call; */ }
	void Disable() { /* Microchip MLA\ASF call; */ }
};

// Пишем тесты прикладного ПО и прогоняем на x86
// 
TEST_CASE("Traffic light", "Go method" ) 
{
	BoardLeds board_leds = {};
	
	TrafficLight::Leds leds
	{
		board_leds.AccessRedLed(),
		board_leds.AccessYellowLed(),
		board_leds.AccessYellowLed(),
	};

	TrafficLight::Timings timings
	{
		5,
		5,
		5
	};
	
	TrafficLight traffic_light = { timings, leds };

	traffic_light.Go();
	
	TEST_ASSERT(leds.green == ENABLED);
	TEST_ASSERT(leds.yellow != ENABLED);
	TEST_ASSERT(leds.red != ENABLED);
}

 

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

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

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


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

Для такой простой задачи у вас уж больно много писанины вышло 🙂 Может оное и "красиво", но с точки зрения микроконтроллера уж очень тяжеловесно. Да и в писанине конечно тоже выглядит не особо то. Хотя наверно можно и так. 

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

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


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

1 hour ago, Variant99 said:

Для такой простой задачи у вас уж больно много писанины вышло 🙂 Может оное и "красиво", но с точки зрения микроконтроллера уж очень тяжеловесно. Да и в писанине конечно тоже выглядит не особо то. Хотя наверно можно и так. 

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

1. А чего там тяжеловестного? Тут нет наследования, нет дин. аллокации, ну да, будет оверхед от ООП, но он и в вашем примере будет.
Меньшее кол-во текста обрубает все те плюсы, о которых я написал выше. Вернее как, попытка их достижения другим известным мне способом вызовет еще большее кол-во текста.

Про С и говорить нечего, там для подобного придется городить тот же самый псевдо-ооп с киданием структурками, который скорее всего будет чуть компактнее и чуть быстрее, но в 10 раз менее читаймее. А возможно и не меньше\быстрее даже. 

Ну жесче только на шаблонах это сделать, там наверно реально приблизиться уже к zero-cost. Но для такой простой задачи как раз - нафига и я не особо сторонник разводить шаблоны без реальной на то нужды. (Насколько мне известно, сейчас многие ультра-про С++ уже считают ошибкой шаблонизацию всего и вся).

2. Простая задача всегда пример. ИРЛ подобное никто не будет покрывать тестами и собирать под несколько платформ. Представьте, что у вас несколько вариантов железа, которые управляют какими релехами, хз ЛИФТА НА АРДУИНО лол. И уже станет понятнее нафига. 

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

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


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

Нет, на Си в конкретном случае решается ничуть не сложнее. ООП тут притянут за уши, это не тот случай. Тут всего лишь средствами С++ переписывается то, что было на С. Разве вы на С никогда не писали? Я писал, и немало. 

1 час назад, Solonovatiy сказал:

А чего там тяжеловестного?

Ну смотрите, что выходит в вашем варианте. Класс Led содержит HAL-функции для портов-пинов. Затем, класс BoardLeds собирает светики класса Led. И еще класс TrafficLight содержит внутри структуру Leds, которая содержит те же самые три разрозненные светика. Окей, а что тогда делает класс BoardLeds? Он же так же содержит три светика класса Led. 

В этом случае, в классе TrafficLight уже нужно уходить от названия led, связанного со светодиодами, и переходить к названию signal, означающему сигнал светофора и реализовывать в принципе то логику смены сигналов. Ну чтобы не дублировать одно и то же. На бумажке нарисуйте получившуюся у вас блок-схему, покрутите её, попереставляйте прямоугольнички и связи.

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

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


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

47 minutes ago, Variant99 said:

Нет, на Си в конкретном случае решается ничуть не сложнее. ООП тут притянут за уши, это не тот случай. Тут всего лишь средствами С++ переписывается то, что было на С. Разве вы на С никогда не писали? Я писал, и немало. 

Так ну для начала, что значит притянут за уши. Императивщина лучше ООП только когда надо все крохи производительности урвать. Иначе - хз. Но это вкусовщина, да. Если 20 лет долбишь С, возможно уже так приедаеться, что пофигу, я подолбил 3 года и мне на столько не хватало ООП, что я разводил это ООП в С. (и самое ужасное - мне нравилось).

52 minutes ago, Variant99 said:

Ну смотрите, что выходит в вашем варианте. Класс Led содержит HAL-функции для портов-пинов. Затем, класс BoardLeds собирает светики класса Led. И еще класс TrafficLight содержит внутри структуру Leds, которая содержит те же самые три разрозненные светика. Окей, а что тогда делает класс BoardLeds? Он же так же содержит три светика класса Led. 

BoardLEDs - агрегатор светодиодов железа. Он не только их собирает, он еще отвечает за развязку прикладного слоя от слоя железа и описывает их конфигурацию.

TrafficLight - всего лишь пользователь этих светодиодов. Увеличьте число светодиодов до 6 и добавьте 2ой TrafficLight. 
BoardLEDs останется один.

Так же BoardLEDs может выступить Mock объектом в тестах и легко симулировать работу железа для этого. 

Тут недопонимание в том, что по хорошему конечно класс LED должен быть интерфейсом iLED, а BoardLEDs конструировать какие LED реализующие интерфейс iLED, тут же по факту из-за рудиментарности примера ссылка на объект. Ну и я действительно стараюсь от такого воздерживаться пока не припрет. 

1 hour ago, Variant99 said:

В этом случае, в классе TrafficLight уже нужно уходить от названия led, связанного со светодиодами, и переходить к названию signal, означающему сигнал светофора и реализовывать в принципе то логику смены сигналов.

А это уже оффтоп. По феншую-то ДА. Но это дело как раз уже прикладного ПО. Сигнал - понятие прикладное, BSP предоставляет конкретное железо. Тут защита от того, что дергаться будут именно светодиоды платы, а не какое-нибудь реле. 

1 hour ago, Variant99 said:

дублировать одно и то же

Дублирование ввиду рудиментарности. 
Вот предположим, что у нас задача сложнее:
- 2 независимых трехцветных светофора
- 1 двуцветный
- 1 светодиод на heartbeat
- 2 светодиода на юзер интерфейс

И вот уже все светодиоды разнесены по туче прикладных классов. 
А класс TrafficLight имеет 6 аргументов в конструкторе, т.к. захардкодить их уже нельзя, надо передавать при конструировании. 

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


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

На Си сокрытие аппаратных подробностей происходит уже на первом уровне, передавая выше функции вида RedOn(), RedOff() или Red(ON), Red(OFF) или даже LED_Control(RED, ON). И всё! Далее не нужно никаких агрегаторов и наворачиваний вложенностей.

По-моему, вы уже слишком перемудрили. Я тоже через всё это проходил и всё такое пробовал. И понял, что оно просто того не стоит. Чем проще и прозрачнее пишешь, тем лучше это работает. 
У вас есть по крайней мере два лишних перемудривания - ST HAL обертки в классе Led и класс BoardLeds, содержащий такую же привязку к портам-пинам. И фактически то же самое содержится и в классе TrafficLight. Конечно, вам поначалу это кажется более правильным. Но со временем, работая с проектами, начнете убирать лишние сучности. Если конечно не завязните в этих сетях надолго.

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

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


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

45 minutes ago, Variant99 said:

На Си сокрытие аппаратных подробностей происходит уже на первом уровне, передавая выше функции вида RedOn(), RedOff() или Red(ON), Red(OFF) или даже LED_Control(RED, ON). И всё! Далее не нужно никаких агрегаторов и наворачиваний вложенностей.

По-моему, вы уже слишком перемудрили. Я тоже через всё это проходил и всё такое пробовал. И понял, что оно просто того не стоит. Чем проще и прозрачнее пишешь, тем лучше это работает. 
У вас есть по крайней мере два лишних перемудривания - ST HAL обертки в классе Led и класс BoardLeds, содержащий такую же привязку к портам-пинам. И фактически то же самое содержится и в классе TrafficLight. Конечно, вам поначалу это кажется более правильным. Но со временем, работая с проектами, начнете убирать лишние сучности. Если конечно не завязните в этих сетях надолго.

 

Возможно. Для обсуждения этого тема и поднята.

Насчет HAL оберток - это не утверждение и не необходимость. Просто так легче было пояснить че там происходит. Там вполне можно и регистры ковырнуть.  
Насчет BoardLeds - хз, у него вероятно дурноватый API, но это ввиду вырожденного примера скорее. 
С другой стороны, вот если представить более реальную историю с BoardRelays в кол-ве штук так 8 и требованием их динамически назначать в рантайме, оно выглядит вполне себе ничего.

Есть класс Motor жрущий релейный выход, есть BoardRelays и есть некий диспетчер, который в зависимости от ран-тайм настроек определенное реле сует в определенный класс, то уже вполне появляется цель, API ток поменять на принимающий энумератор. Хотя это тоже вырожденный пример с другим знаком. 

 

45 minutes ago, Variant99 said:

На Си сокрытие аппаратных подробностей происходит уже на первом уровне, передавая выше функции вида RedOn(), RedOff() или Red(ON), Red(OFF) или даже LED_Control(RED, ON). И всё! Далее не нужно никаких агрегаторов и наворачиваний вложенностей.

Вот эта штука (раннее связывание) меня как раз сильно раздражает в С. Ввиду глобальности методов, в сложном приложении, без CTRL+F по проекту черт ногу сломит понимать где это ЕЩЕ может использоваться. С этим можно бороться только разве что стилем кода в проекте и самодисциплиной жесткой. Компилятор не запретит подключить "my_super_gpio.h" в любом месте и начать там дергать регистры. 

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

С ООП, если применять позднее связывание - намного легче, т.к. сворачивая\разворачивая вложенности можно отследить где это появилось и куда ушло. (хотя если дать волю рукам, можно тоже дичь понаписать). 

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

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


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

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

BoardRelays в кол-ве штук так 8 и требованием их динамически назначать в рантайме

Без разницы, реле или светодиоды. У меня и релюшки были, 10 штук. Но зачем их назначать в рантайме то? Они что, могут прыгать по плате и переподключаться на другие ножки МК? Навряд ли такой беспредел у вас там творится.

Стиль написания для микроконтроллеров отличается от стиля написания для больших компутеров, в том числе по причине фиксированности железа на всё время работы. А если вы переносите проект на другое железо, вы руками корректируете различия. И лучше это сделать в одном месте, поближе непосредственно к железу. Мы ж не будем реализовывать Plug-and-Play на микроконтроллере то 🙂 

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

Вот эта штука (раннее связывание) меня как раз сильно раздражает в С.

Чем? Нет, ну если хотите, вы и на С можете вытащить порты-пины на самый верх точно таким же способом, передавая указатели на регистры периферии. Только чето я не шибко то видел, чтобы кто-то этим очень пользовался. Хотя, конечно, кто как хочет, так и ... пишет. Пару раз видел, как в main.c пишут глобально распиновку, а потом её и передают во все низлежащие функции. Возможно, так тоже можно. Ну а че.

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


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

1 hour ago, Variant99 said:

Но зачем их назначать в рантайме то? Они что, могут прыгать по плате и переподключаться на другие ножки МК?

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

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


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

Да ну? Приведите реальный пример такого проекта 🙂 Если уж выход МК сгорел, то он сгорел, и не факт, что не сгорело еще че-нить, как в микроконтроллере, так и на самой плате. К тому же, как вы определите, что выход сгорел? Ну а кто будет дорожки на плате переводить автоматически во время работы? Нет уж, если выход погорел, то он погорел, и вся эта байда вместе нуждается в обследовании и в ремонте. Где вы видели, чтобы неисправный прибор продолжал работать как ни в чем не бывало 🙂 Если такое где и есть, то это скорее исключительное исключение из правил, нежели насущная необходимость. 

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

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

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


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

7 minutes ago, Variant99 said:

Приведите реальный пример такого проекта

Ребята, не выдумывайте того, чего навряд ли вообще возможно в реале.

Betaflight - открытый проект для полетных контроллеров FPV дронов.

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

 

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

Как раз над таким проектом работаю. Переназначение производится через соотв json файл. Но там нет никаких "пинов", ибо схема жестко завязана на аппаратную часть, но полно одинаковых каналов (channel), которые можно как угодно переназначать и изменять их функционал.

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


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

2 hours ago, Variant99 said:

Они что, могут прыгать по плате и переподключаться на другие ножки МК? Навряд ли такой беспредел у вас там творится.

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

22 minutes ago, AndyBig said:

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

Не понимаю изначально отрицательное отношение к абстракциям.
Наоборот же стоит резать, когда приходиться. Нужды городить ДЛЯ функционала - никогда нет и не будет. Абстракции всегда для поддержки и кач-ва. 

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


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

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

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

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

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

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

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

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

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

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