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

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

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

У меня есть факультативная задача - написать CAN-UART-преобразователь на STM32. Наши гуру линукса, конечно же, просят SLCAN. Я бы мог взять свой любой проект и адаптировать исходник под новый МК (в силу дефицита обкатанные камни купить не смогли). Там все на Си. Ну и довольно "топорная" работа с входным потоком UART. Захотел перетащить под C++, да чтобы было красиво как в коде, так и быстро в железе (малый оверхед). Фиг с ним с классом SLCAN - я уже понял как я сделаю его реализацию. Чтобы абстрагировать класс парсинга и обработки протокола SLCAN от приема и передачи данных по UART/CAN, последние я реализую совершенно независимо. Еще по разумным соображениям мне нужен FIFO как для передачи по UART, так и для приема. Полностью программная реализация, которая у меня уже есть, мне не нравится, т.к. я хочу использовать DMA на прием и на передачу. Соответственно, почему нельзя сделать просто 2 разных реализации классов FIFO? Типа такого

namespace FIFO {
  class cSoftImpl { // программная реализация
    ...
    u32 write(u8 buf[], u32 len);
    u32 read(u8 buf[], u32 len);
    ...
  };
  
  class cDMAImpl { // реализация с DMA-контроллером
    ...
    u32 write(u8 buf[], u32 len);
    u32 read(u8 buf[], u32 len);
    ...
  };
}

В программе создается объект любого из FIFO и весь исходник, работающий с этим объектом, не меняется. Правда, если не использовать указатели на такие классы...
Либо наследовать cDMAImpl от cSoftImpl и переопределить его методы.

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


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

13 minutes ago, Arlleex said:

В программе создается объект любого из FIFO и весь исходник, работающий с этим объектом, не меняется. Правда, если не использовать указатели на такие классы...
Либо наследовать cDMAImpl от cSoftImpl и переопределить его методы.

либо создать ABC:
 

class cFifoIf {            // реализация с DMA-контроллером
    ...
    virtual u32 write(u8 buf[], u32 len) = 0;
    virtual u32 read(u8 buf[], u32 len) = 0;
    ...
  };

И именно его использовать на более высоком уровне.

А классы софтовой или DMA реализации - унаследовать от него. Тогда замена интерфейса FIFO не потребует никакого изменения в коде, который использует этот интерфейс. Более того, вы можете закрыть их от дальнейшего наследования квалификатором final.

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

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


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

11 минут назад, one_eight_seven сказал:

...либо создать ABC:

С виртуальными как раз понятно - но так я стал бы делать где-нибудь на ПК-шном приложении.
У меня то МК:smile: Хочется без динамического полиморфизма (потому что это довольно затратно).

Прошу извинить за ламерские хотелки - но я сейчас именно на острие лезвия между качеством исходника и производительностью.

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


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

3 hours ago, jcxz said:

то тогда надо создать такой базовый класс для всех устройств ввода/вывода - UART, USB, SPI и т.п.

У меня так и сделано. И есть общий абстрактный класс, в котором определены виртуальные функции open(), close() (эта не реализуется, т.к. во встраиваемой системе мне нет нужды закрывать драйвера), имеется член m_open, который сохраняет результат открытия драйвера.

3 hours ago, jcxz said:

и чем оно лучше простого UartWrite(), SpiWrite() если кроме имён методов оно ничего не объединяет.

Лучше или хуже - это понятия субъективные, поэтому отвечу за себя только. Мне лучше тем, что древовидная структура классов драйверов позволяет при необходимости добавлять сущности не к конкретным драйверам, например последовательных портов, а в их базовый класс от которого они наследуются. Или даже в общий базовый класс, если это нужно для всех всех имеющихся драйверов. Данная концепция ни раз себя оправдывала, мне так удобно. Если Вам удобно делать по-другому, то, пожалуй, дискуссию можно закрыть) Каждый останется при своём мнение, а возможно и изменит его в будущем.

3 hours ago, jcxz said:

Так "обязан" или "может"? По Вашим же постам: Если у вас в одном проекте нужна функция Close() для UART, а вдругом - не нужна, то даже там где она не нужна, всё равно нужно её реализовывать? Или нет?

Смотря как объявлена виртуальная функция в базовом классе. Если это pure virtual (не помню русский термин), то обязан. В противном случае - нет.

3 hours ago, jcxz said:

Это с чего они "платформонезависимы"??? Вы ведь мой пример выше читали, забыли? Там где размер символа UART = 24 бита. Сможете Вы на STM32 или на LPC такую длину символа установить?

А Вы читаете внимательно то, что я пишу? Я, как я помню, написал "в общем случае". Фраза допускает и то, что есть исключения.

3 hours ago, jcxz said:

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

Так я Вас разве заставляю это делать? Мне, вот, нужно. Т.к. тот же графический интерфейс прибора позволяет задавать настройки порта. И где мне их прикажете хранить? В моём случае они как раз доступны и доступные через единый (т.е. для всех платформ) интерфейс. Для Вашего уникального последовательно порта, который, как мне кажется, является чем-то даже и не похожим на УАПП можно создать свой драйвер, даже не наследуя от базового класса. Здесь, я считаю, нужно выбирать, как проектировать. Тупо наследовать и перегружать я, вроде, пока ни кого не призывал)

3 hours ago, jcxz said:

Вот именно! А классо-поклонники я вижу просто не замечают этого. :biggrin:

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

3 hours ago, jcxz said:

Т.е. - указатель. Будет то, что сказал Arlleex.

Ну и что?

3 hours ago, jcxz said:

Жесть какая! Теперь ещё для инита порта, вместо простого BL, будет стоять кучка команд LDR с последующей BLX: чтение указателя на таблицу виртуальных методов, чтение указателя на конкретный виртуальный метод (Init()), а затем - BLX. И вся эта кухня - вместо простого BL! (которого может и не быть если компилятор заинлайнит его).

Вот код задачи из текущего проекта (личного)

Spoiler

void CmpSlave::task() {
    static Drv::Stm32f0x1UsartCmp<> usart;

    auto result = usart.open();

    for (;;) {
        wdgmngr.alive();
        //result = usart.readFrame(OsApi::Task::SLEEP_FOREVER);
        result = usart.readFrame();
        setLedStatus(result);
        if (result != RetVal::Ok)
            continue;
        if (usart.getRxFrame().getAddress() != Ffwk::Api::General::getCmpAddress())
            continue;
        usart.getTxFrame().setAddress(usart.getRxFrame().getAddress());
        executeCommand(usart.getRxFrame(), usart.getTxFrame());
        result = usart.writeFrame(100);
    }
}

 

У драйвера последовательного порта перегружен метод open(), вот фрагмент

   class Stm32f0x1UsartCmp : public Stm32f0x1Usart<Num, RxSize, RxPin, RxAF, TxPin, TxAF> {
    public:
        Stm32f0x1UsartCmp() {}
        RetVal open() override {
            if (this->m_open == RetVal::Ok)
                return this->m_open;
            /// TODO automatically choose vector based on template args
            Nvic::install(USART1_IRQn, irqHandler);
            setupDirPin();
            setDirIn();
            this->setNvic();
            this->enableClock();
            BaseUsart::Settings settings;
            settings.baudrate = BAUDRATE;
            this->m_open = this->setSettings(settings);
            flush();
            return this->m_open;
        }

Видно, что драйвер отнаследован.

Вот листинг


   \                                 In section .text, align 4, keep-with-next
     11          void CmpSlave::task() {
   \                     _ZN4Link8CmpSlave4taskEv: (+1)
   \        0x0   0xB57C             PUSH     {R2-R6,LR}
   \        0x2   0x....             LDR      R0,??DataTable2_5
   \        0x4   0x7800             LDRB     R0,[R0, #+0]
   \        0x6   0x2800             CMP      R0,#+0
   \        0x8   0xD105             BNE      ??task_0
     12              static Drv::Stm32f0x1UsartCmp<> usart;
   \        0xA   0x2001             MOVS     R0,#+1
   \        0xC   0x....             LDR      R1,??DataTable2_5
   \        0xE   0x7008             STRB     R0,[R1, #+0]
   \       0x10   0x....             LDR      R0,??DataTable2_6
   \       0x12   0x.... 0x....      BL       _ZN3Drv17Stm32f0x1UsartCmpILNS_13BaseSerialBus6BusNumE1ELj1ENS_3Pin3PinILc66ELj7EEELNS3_8FunctionE0ENS4_ILc66ELj6EEELS6_0ENS4_ILc66ELj5EEEEC1Ev
     13          
     14              auto result = usart.open();
   \                     ??task_0: (+1)
   \       0x16   0x....             LDR      R0,??DataTable2_6
   \       0x18   0x.... 0x....      BL       _ZN3Drv17Stm32f0x1UsartCmpILNS_13BaseSerialBus6BusNumE1ELj1ENS_3Pin3PinILc66ELj7EEELNS3_8FunctionE0ENS4_ILc66ELj6EEELS6_0ENS4_ILc66ELj5EEEE4openEv
   \       0x1C   0x0005             MOVS     R5,R0
     15          
     16              for (;;) {
     17                  wdgmngr.alive();
   \                     ??task_1: (+1)
   \       0x1E   0x....             LDR      R4,??DataTable2_7

Нет BLX'ов. Более того, компилятор зайнлайнил вызов этой функции (остальные не стал проверять).

В том же листинге (фрагмент)

   \                                 In section .text, align 4
   \   __softfp RetVal Drv::Stm32f0x1UsartCmp<>::open()
   \                     _ZN3Drv17Stm32f0x1UsartCmpILNS_13BaseSerialBus6BusNumE1ELj1ENS_3Pin3PinILc66ELj7EEELNS3_8FunctionE0ENS4_ILc66ELj6EEELS6_0ENS4_ILc66ELj5EEEE4openEv: (+1)
   \        0x0   0xB5FE             PUSH     {R1-R7,LR}
   \        0x2   0x0004             MOVS     R4,R0
   \        0x4   0x88A0             LDRH     R0,[R4, #+4]
   \        0x6   0x2800             CMP      R0,#+0
   \        0x8   0xD101             BNE      ??open_4
   \        0xA   0x88A0             LDRH     R0,[R4, #+4]
   \        0xC   0xE02A             B        ??open_5
   \                     ??open_4: (+1)

 

3 hours ago, jcxz said:

А потом удивляемся, почему на казалось бы ещё вчера мощном CPU, сегодня наша программа еле ворочается.  :biggrin:

Я не помню, чтобы на форуме жаловался на медленную работу ПО,.

3 hours ago, jcxz said:

Вот это и есть - оверхед в полный рост!

Оверхэд есть всегда. И он не только в количестве машинных инструкций выражается. А, например, в потере времени на форуме))))

 

2 hours ago, jcxz said:

Таким образом - вместо улучшения читаемости (которое вроде должно давать применение ООП), получили прямо противоположный результат. Стало совершенно нечитаемо.

Так в чём же дело? В неправильном инструменте? Или в неправильном его использовании? Вы наверняка видели нечитваемые исходники на Си или Си++ без ООП. Я, вот, видел. Даже сегодня у коллеги, все прелести: магические числа, константы и переменные названы в одном стиле, строки сравниваются не с помощью strcmp, а примерно так

if( src[0] == dst[0] && src[1] == dst[1] и т.д.

И я не придумываю. Именно так строки он и сравнивает. Конечно, этот код не идёт в наши приборы. Он делает тестовые стенды для производства, и приборов не касается.

Тоже читаемость никакая?

 

1 hour ago, Arlleex said:

Либо наследовать cDMAImpl от cSoftImpl и переопределить его методы.

Я бы сделал абстрактный класс и оба этих наследовал от него. Ну если Вас не пугают VTABLE, которая может там появится. Если код описать в хидере, то с большой вероятностью он заинлайнится, как у меня в примере выше.

1 hour ago, one_eight_seven said:

либо создать ABC:

Ах, это и есть ABC. Т.е. класс только с интерфейсом (pure virtual functions). Полегчало мне)))

1 hour ago, Arlleex said:

(потому что это довольно затратно).

Ну вот смотрите, у меня все все все драйверы имеют этого родителя. Как я понял, это и есть ABC. И функция open() заинлайнилась. Да, инлайнится не 100%, но тут либо шашечки, либо ехать)) А если серьёзно, удобство окупается, ИМХО. Да и в листинг глянуть не проблема.

Spoiler

namespace Drv {
    class Base {
    public:
        typedef void ( *IrqHandlerPtr_t )();

        virtual RetVal open() = 0;
        virtual RetVal close() = 0;
    protected:
        RetVal m_open = RetVal::Failed;
    private:
    };
};

 

 

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


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

@haker_fox, у Вас что, open(), даж будучи виртуальным, встроился? Потому как я не вижу чтения VTable...

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


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

42 minutes ago, Arlleex said:

Потому как я не вижу чтения VTable...

Ну вот и я об том же... Я чуть выше неверно выразился, говоря о том, что метод заинлайнился. Торопился. Допустил опечатку. Конечно же не заинлайнился, а просто попал в этот же листинг. Переход BL на него есть. А так, если я всё корректно проверил, да, встроился. Но у меня драйвер описан полностью в хидере.

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


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

Классы ради классов?  ООП ради ООП?

Если Си устраивает, то зачем переходить на "плюсЫ" ?

 

ПлюсЫ в играх нужны. В МК от них головной боли больше, чем пользы.

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


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

16 minutes ago, repstosw said:

ПлюсЫ в играх нужны.  В МК от них головной боли больше, чем пользы.

Началось :help:

Пожалуй пора добавлять в правила запрет на разведение холиваров в стиле C vs C++, моветон.

 

 

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


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

32 minutes ago, repstosw said:

ПлюсЫ в играх нужны. В МК от них головной боли больше, чем пользы.

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

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


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

1 hour ago, repstosw said:

В МК от них головной боли больше, чем пользы.

Применяю плюсы в МК года с 2006. Темпалгин и цитрамон не глотаю.

Просто Вы не знаете Си++. Это единственно верный ответ.

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


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

Почему-то ембедеры осваивающие C++ пытаются сразу применить все его изученные возможности. И выходит то же самое что на С, но с кучей классов и виртуальных методов и разочарование в C++ и обвинение в "оверхеде". 

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


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

18 minutes ago, turnon said:

пытаются сразу применить все его изученные возможности

Это в целом нормально, потом научаться использовать инструмент  правильно )

 

А кто так и не научился - поливают плюсы грязью, тут в том числе )

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


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

20 часов назад, haker_fox сказал:

Т.к. тот же графический интерфейс прибора позволяет задавать настройки порта. И где мне их прикажете хранить?

Естественно - в единой структуре настроек прибора. Хранить настройки в разных местах - это мне кажется по меньшей мере странным.

20 часов назад, haker_fox сказал:

В моём случае они как раз доступны и доступные через единый (т.е. для всех платформ) интерфейс. Для Вашего уникального последовательно порта, который, как мне кажется, является чем-то даже и не похожим на УАПП можно создать свой драйвер, даже не наследуя от базового класса. Здесь, я считаю, нужно выбирать, как проектировать. Тупо наследовать и перегружать я, вроде, пока ни кого не призывал)

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

Ну и что?

Вот код задачи из текущего проекта (личного)

  Показать содержимое


void CmpSlave::task() {
    static Drv::Stm32f0x1UsartCmp<> usart;

    auto result = usart.open();

    for (;;) {
        wdgmngr.alive();
        //result = usart.readFrame(OsApi::Task::SLEEP_FOREVER);
        result = usart.readFrame();
        setLedStatus(result);
        if (result != RetVal::Ok)
            continue;
        if (usart.getRxFrame().getAddress() != Ffwk::Api::General::getCmpAddress())
            continue;
        usart.getTxFrame().setAddress(usart.getRxFrame().getAddress());
        executeCommand(usart.getRxFrame(), usart.getTxFrame());
        result = usart.writeFrame(100);
    }
}

 

Нет BLX'ов. Более того, компилятор зайнлайнил вызов этой функции (остальные не стал проверять).

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

Во-вторых: насколько могу судить по 

у Вас open() и не виртуальный. А вот интересно было бы взглянуть на вызовы монструозных методов read() и write() которые как раз виртуальные.

И вызовы не через указатель на их собственный класс, а через указатель на родительский класс.

А то, что у Вас там получилась BL (или даже заинлайнилось), так это видимо потому, что метод Вы вызывали или через оператор "." для объекта дочернего класса или через указатель на дочерний класс. Естественно в этом случае компилятор знает какой именно метод должен быть вызван в этом месте и ставит BL.

 

Покажите вызов виртуального метода через указатель на родительский класс. Вот тогда и посмотрим.

 

20 часов назад, haker_fox сказал:

Ну вот смотрите, у меня все все все драйверы имеют этого родителя. Как я понял, это и есть ABC. И функция open() заинлайнилась.

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

20 часов назад, haker_fox сказал:

Да, инлайнится не 100%, но тут либо шашечки, либо ехать)) А если серьёзно, удобство окупается, ИМХО. Да и в листинг глянуть не проблема.

  Показать содержимое


namespace Drv {
    class Base {
    public:
        typedef void ( *IrqHandlerPtr_t )();

        virtual RetVal open() = 0;
        virtual RetVal close() = 0;
    protected:
        RetVal m_open = RetVal::Failed;
    private:
    };
};

 

 

Ах, вон они где у вас объявлены виртуальными! В исходном посте  

этого не было.

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


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

9 minutes ago, jcxz said:

Хранить настройки в разных местах - это мне кажется по меньшей мере странным.

Не в разных, а в тех местах, которым эти настройки принадлежат. По-другому мне кажется странным...

11 minutes ago, jcxz said:

Покажите вызов виртуального метода через указатель на родительский класс. Вот тогда и посмотрим.

Вот добавил код снизу.

Spoiler

void CmpSlave::task() {
    static Drv::Stm32f0x1UsartCmp<> usart;

    auto result = usart.open();

    for (;;) {
        wdgmngr.alive();
        //result = usart.readFrame(OsApi::Task::SLEEP_FOREVER);
        result = usart.readFrame();
        setLedStatus(result);
        if (result != RetVal::Ok)
            continue;
        if (usart.getRxFrame().getAddress() != Ffwk::Api::General::getCmpAddress())
            continue;
        usart.getTxFrame().setAddress(usart.getRxFrame().getAddress());
        executeCommand(usart.getRxFrame(), usart.getTxFrame());
        result = usart.writeFrame(100);
        
        // Just for test
        uint8_t buff[10];
        Drv::BaseUsart * pbu = &usart;
        pbu->write(buff, sizeof buff);
    }
}

 

Вот фрагмент листинга.

Spoiler

   29                  // Just for test
     30                  uint8_t buff[10];
     31                  Drv::BaseUsart * pbu = &usart;
   \       0xDC   0x....             LDR      R4,??DataTable2_6
     32                  pbu->write(buff, sizeof buff);
   \       0xDE   0x2096             MOVS     R0,#+150
   \       0xE0   0x0040             LSLS     R0,R0,#+1
   \       0xE2   0x9000             STR      R0,[SP, #+0]
   \       0xE4   0x2300             MOVS     R3,#+0
   \       0xE6   0x220A             MOVS     R2,#+10
   \       0xE8   0xA901             ADD      R1,SP,#+4
   \       0xEA   0x0020             MOVS     R0,R4
   \       0xEC   0x6826             LDR      R6,[R4, #+0]
   \       0xEE   0x68B6             LDR      R6,[R6, #+8]
   \       0xF0   0x47B0             BLX      R6
   \       0xF2   0xE794             B        ??task_1
     33              }
     34          }

 

Ну всё, не применяем Си++ во встраиваемых устройствах) Видны обращения к "втабле")))

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


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

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

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

Гость
Ответить в этой теме...

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

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

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

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

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

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