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

Плавный переход C -> C++ под МК

4 hours ago, Arlleex said:

Вы используете какие-то архитектурные паттерны проектирования?

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

У меня исключительно модульная структура проекта, где каждый модуль ничего друг о друге не знает. Для связи модулей используется схема slot-signal, позаимствованная из Qt, но тут на плюсах используется для их реализации делегаты.

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

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

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

В такой схеме не нужно с нуля проектировать структуру любого нового проекта перед его созданием, поскольку база уже есть и достаточно лишь подключить к проекту ее исходники, лежащие в общей для всех проектов папке Common/Application.

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

Вот, например, как выглядит главный по сути исходник Application.cpp одного из проектов:

Spoiler
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Project:		xxxxxx
// Description:	Application
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include <AbstractApplication.hpp>
#include <Memory.hpp>
#include <RCC.hpp>

// Project
#include "ProjectSettings.hpp"
#include "Inputs.hpp"
#include "UserInterface.hpp"
#include "Settings.hpp"
#include "Service.hpp"

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

constexpr auto kHeapSize = 122 * 1024;
constexpr auto kStackSize = 1024;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Application
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class SomeApplication final : public Application<kHeapSize, kStackSize>
{
	virtual void initializeCore() final;
	virtual void connectModules() final;
	virtual void installModules() final;
};

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

void SomeApplication::connectModules()
{
	UserInterface::getInstance().signal.inputs.configurate 			= Inputs::getInstance().slot.configurate;
	UserInterface::getInstance().signal.inputs.synchronize			= Inputs::getInstance().slot.synchronize;
	UserInterface::getInstance().signal.inputs.getWarningCounter	= Inputs::getInstance().slot.getWarningCounter;
	
	UserInterface::getInstance().signal.settings.getInt 			= Settings::getInstance().slot.getInt;
	UserInterface::getInstance().signal.settings.getString 			= Settings::getInstance().slot.getString;

  ......
	
	Inputs::getInstance().signal.settings.getInt 					= Settings::getInstance().slot.getInt;
	Inputs::getInstance().signal.settings.getString 				= Settings::getInstance().slot.getString;
	Inputs::getInstance().signal.settings.isDemoModeEnabled 		= Settings::getInstance().slot.isDemoModeEnabled;

 ....
  
	Settings::getInstance().signal.getUsbVoltage					= Inputs::getInstance().slot.getUsbVoltage;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

void SomeApplication::installModules()
{
	install(Settings::getInstance());
	install(Service::getInstance());
	install(Inputs::getInstance());
	install(UserInterface::getInstance());
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

void SomeApplication::initializeCore()
{
	Hardware::Power::initialize();
	Hardware::Flash::initialize();
	Hardware::Clock::initialize();

	Hardware::Clock::HSE::setQuartzFrequencyHz(HSE_FREQUENCY_HZ);
	Hardware::Clock::HSE::enable();
	Hardware::Clock::HSE::waitForReady();
	Hardware::Clock::HSE::setAsClockSource();

	Hardware::Clock::AHB::setPrescaler(Hardware::Clock::AHB::Prescaler::NONE);
	Hardware::Clock::APB1::setPrescaler(Hardware::Clock::APB1::Prescaler::PRE_4); // Max 42 MHz
	Hardware::Clock::APB2::setPrescaler(Hardware::Clock::APB2::Prescaler::PRE_2); // Max 84 MHz

	Hardware::Clock::PLL::setClockSource(Hardware::Clock::PLL::HSE);
	Hardware::Clock::PLL::setPrescaler(HSE_FREQUENCY_MHZ / PLL_INPUT_FREQUENCY_MHZ);
	Hardware::Clock::PLL::setMultiplier(PLL_OUTPUT_FREQUENCY_MHZ / PLL_INPUT_FREQUENCY_MHZ);
	Hardware::Clock::PLL::setSysClockDevider(PLL_OUTPUT_FREQUENCY_MHZ / CORE_FREQUENCY_MHZ);
	Hardware::Clock::PLL::setupUsbClock(); // 48 MHz only
	Hardware::Clock::PLL::enable();
	Hardware::Clock::PLL::waitForReady();
	Hardware::Clock::PLL::setAsClockSource();
  
	Hardware::Flash::setLatency();
	Hardware::Flash::Cache::enable();

	// Setup OS settings
	osSettings.coreFrequencyHz 			= Hardware::Clock::getSystemFrequencyHz();
	osSettings.systemTimerFrequencyHz	= RTOS_SYSTEM_TIMER_FREQUENCY_HZ;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// MAIN
//
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

int main()
{
	static SomeApplication application;
	application.initialize();
	application.run();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////

 

Там кстати внизу исходника весь main(), причем он практически одинаковый для всех проектов.

В этом проекте используется нештатная работа с кучей (на базе TLSF, кто знает), показала себя довольно неплохо.

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

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

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

 

Заранее скажу хейтерам, который любят экономить каждый байт, каждый бит где надо и не надо.

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

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

 

4 hours ago, Arlleex said:

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

А для чего это нужно? Какой-нибудь наглядный пример

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


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

23 hours ago, Forger said:

А для чего это нужно?

Больше букв - больше денег.

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


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

...это признак "индусского кода" - по древнему преданию, индусские программисты получали оплату пропорционально количеству строк.

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


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

1 hour ago, EdgeAligned said:

это признак "индусского кода" - по древнему преданию

а нынче это "придание" уже не работает? ))

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


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

2 hours ago, Forger said:

а нынче это "придание" уже не работает? ))

Конечно работает.

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


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

15 часов назад, x893 сказал:

Больше букв - больше денег.

Как то, о чем я писал, связано с "больше букв"?
 

В 15.09.2023 в 21:03, Forger сказал:

А для чего это нужно? Какой-нибудь наглядный пример

Чтобы, например, писать обработчики очень быстрых и частых прерываний в ООП-стиле.

У меня есть пара-тройка проектов, где по наследству достались, например ногодрыжные UART-ы с одновременными программными ШИМ-ами.

Дергать лапы и считывать с них уровни виртуальными функциями на 8-битной ATMega сломает текущую возможность реализации проекта вовсе.

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

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


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

Мы раньше на 8-битках на ассемблере писали, а нынче С×× мастрячат на них... 

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


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

8 hours ago, Arlleex said:

Чтобы, например, писать обработчики очень быстрых и частых прерываний в ООП-стиле.

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

оверхед крайне ничтожный, это - вызов делегата обработчика прерывания:

Delegate.jpg.5c8d0bd268c522610da355a3ae553720.jpg

Обычное, классическое прерываний чуть короче (на две инструкции).

Вот например как я объявляю некоторые примитивные обработчики как лямбда-функции:


class SettingsLib : public Settings
{
	virtual void connectSlots() final;
	virtual void run() final;
....
	class ThreadUSB : public OS::Timer<kStackSize>
	{
	private:
		virtual void initialize() final;
		virtual void body() final;
....
      
	private:

		Hardware::Interrupt usbInterrupt;
.......
      
      



extern "C" void OTG_FS_IRQHandler();

void SettingsLib::ThreadUSB::initialize()
{
	static auto interrupt = [this]() { OTG_FS_IRQHandler(); };
	Delegate<void()> vector = interrupt;
	usbInterrupt.installVector(vector);
	usbInterrupt.initialize(OTG_FS_IRQn);
	usbInterrupt.enable();
      ...
      

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

но самый цимус таких обработчиков прерываний - все они являются по сути обычными методами класса и поэтому имеют все нужные преемущества,

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

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

Такое решение прекрасно устраивает, использую даже в самых убогих и тщедушных МК ))

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


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

On 9/15/2023 at 8:19 PM, Arlleex said:

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

Компилятор может вывести и применить девиртуализацию только двумя способами.
1. Виртуальный метод или весь класс помечен final.
2. Класс определен в анонимном пространстве имен и не имеет производных классов в единице трансляции.

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


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

10 hours ago, Forger said:

у меня для этой цели используются делегаты

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

class IRQ
{
	.....
    // Обработчик прерывания.
    virtual void IRQ_Handler( void ) = 0;

    IRQ* installInterruptHandler
    (
    #ifdef __STM32__
    IRQn_Type   IRQn    // See "stm32fxxx.h", enum IRQn_Type.
    #endif
    );

    IRQ* uninstallInterruptHandler
    (
    #ifdef __STM32__
    IRQn_Type   IRQn    // See "stm32fxxx.h", enum IRQn_Type.
    #endif
    );
	.....
};


class SWTimer : protected IRQ
{
	.....

    protected:
    
        // Обработчик прерывания.
        virtual void IRQ_Handler( void );
	
	.....
};

 

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


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

3 hours ago, tonyk_av said:

поэтому обработчик прерывания это виртуальная функция

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

посмотрите disasm, результат может расстроить, ибо это работает намного дольше, чем через делегат

у вас должна быть где-то большая таблица оберток вокруг каждого обработчика, которая будет класть туда нужный this соотв. класса,

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

ибо статическое связывание (на этапе компиляции) вообще не имеет смысла и мало чем отличается от обычных c-обработчиков

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

 

3 hours ago, tonyk_av said:

А я предпочитаю удобство

удобство, это когда просто объявляешь самый обычный не виртуальный метод в полях класса и где-то помечаешь что это обработчик,

но в МК к сожалению аппаратно предусмотрена таблица прерываний только на С-функции, без доп. таблицы для this с++ методов,

т.е. без костылей никак ((

 

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


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

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

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


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

5 minutes ago, EdgeAligned said:

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

о чем выше и написал, это таблица указателей на С-функции, а для плюсового кода нужны указатели с++ функции, 

это и создает существенные проблемы при использовании обработчиков прерываний в качестве методов классов

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

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

если у кого есть другое решение, публикуйте )

кому как, но мне вот было бы очень интересно изучить )

 

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


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

On 9/18/2023 at 6:53 AM, EdgeAligned said:

...а как всё просто было на Си 🙂 

До некоторого уровня - да. А далее - "обманчивая красота подобных систем, однако, сильно воздействует на неискушенных пользователей " - и далее по тексту ))

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


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

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

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

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

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

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

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

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

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

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