Jump to content

    
Arlleex

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

Recommended Posts

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

У меня есть факультативная задача - написать 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 и переопределить его методы.

Share this post


Link to post
Share on other sites
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.

Edited by one_eight_seven

Share this post


Link to post
Share on other sites
11 минут назад, one_eight_seven сказал:

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

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

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

Share this post


Link to post
Share on other sites
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:
    };
};

 

 

Share this post


Link to post
Share on other sites
42 minutes ago, Arlleex said:

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

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

Share this post


Link to post
Share on other sites

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

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

 

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

Share this post


Link to post
Share on other sites
16 minutes ago, repstosw said:

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

Началось :help:

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

 

 

Share this post


Link to post
Share on other sites
32 minutes ago, repstosw said:

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

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

Share this post


Link to post
Share on other sites
1 hour ago, repstosw said:

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

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

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites
18 minutes ago, turnon said:

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

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

 

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

Share this post


Link to post
Share on other sites
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:
    };
};

 

 

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

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

Share this post


Link to post
Share on other sites
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          }

 

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

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.