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

С++ взаимодействие объектов

Привет.

Не могу сам разобраться, в плюсах не силен. Как правильно (принято) реализовывать взаимодействие между объектами?

Мой случай, условно (пишу на QT, ранее программа была написана на CBuilder6).

 

Реализация на CBuilder 6.

Программа на ПК обменивается через COM-PORT с МК и отображает его состояние.

Протоколов обмена два (может быть больше). Реализованы протоколы через объекты protocol_1 и protocol_2, оба они имеют методы rcv(u8* byte, u16 sz) через которые принимают "сырые" данные.

Этот метод реализует байт-стаффинг, подсчет crc и разбивает поток байт на сообщения вида typedef struct {u16 cmd; u8 data[];} msg_t,

которые в свою очередь можно получить с помощью метода get_msg(msg_t * msg). Так же объекты имеют методы для передачи сообщений put_msg(msg_t * msg), которые их "готовят" (байт-стаффинг, crc) и передают.

Далее, объект коммуникации comm, который содержит объекты comport, protocol_1 и protocol_2, реализует обмен с МК:

1. Прием реализован через call-back ф-ию:

void com_rcv_cb(u8 * b, u16 sz)
{
  if (protocol == 1) protocol_1.rcv(b, sz);
  if (protocol == 2) protocol_2.rcv(b, sz);
}

ф-ия передается объекту comport через его метод set_rcv_cb(com_rcv_cb). Выбор протокола: comm.select_protocol(int r){ protocol = r; }

2. Реализация передачи:

void comm::send(msg_t * msg)
{
  if (protocol == 1) protocol_1.put_msg(msg);
  if (protocol == 2) protocol_2.put_msg(msg);
}

Собственно передачу осуществляют объекты protocol_1 и protocol_2, для этого им передается указатель на ф-ию отправки данных в comport /* void comport::send(u8* byte, u16 sz) */: protocol_1.set_sender(comport.send), protocol_2.set_sender(comport.send).

Все три объекта (comport, protocol_1 и protocol_2) объявляются в файле comm.cpp, но, как бы сказать, не в самом объекте comm. Все работает. Вопросов нет.

 

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

Объекты protocol_1 и protocol_2 взял как есть. Для работы с COM-PORT взял объект QSerialPort. Но в этот раз все три объекта (serialport, protocol_1 и protocol_2) создаю в объекте comm.

С приемом проблем нет, функцию приема прикрутил с помощью connect(serialport, &QSerialPort::readyRead, comm, comm::com_rcv_cb), упуская детали. Работает.

А вот передать указатель на ф-ию отправки serialport::write в объекты protocol_1 и protocol_2 не получается. Видимо от того, что serialport создается в самом объекте comm.

Думаю, если вынесу объявление объектов serialport, protocol_1 и protocol_2 в пространство файла comm.cpp (как было ранее) все получится.

 

Но хотелось бы узнать какие еще варианты взаимодействия объектов имеются. Или в моем случае надо полностью менять подход в построении программы?

 

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


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

12 minutes ago, const said:

Протоколов обмена два (может быть больше).

Логично, что оба протокола должны иметь общего предка.

12 minutes ago, const said:

объекты protocol_1 и protocol_2, оба они имеют методы rcv(u8* byte, u16 sz) через которые принимают "сырые" данные

Значит, предок должен иметь виртуальный метод rcv(u8* byte, u16 sz).

13 minutes ago, const said:

Прием реализован через call-back ф-ию

Зачем она? Ведь есть виртуальный метод rcv().

15 minutes ago, const said:

Все три объекта (comport, protocol_1 и protocol_2) объявляются в файле comm.cpp

Ужас. Каждый класс в своём файле.

Как возможный вариант реализации базового класса и базового для UART.

Spoiler
////////////////////////////////////////////////////////////////////////////////
//
//      AbsTrans.h
//
////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __ABSTRACT_TRANSPORT_H__
#define __ABSTRACT_TRANSPORT_H__

#include "MyTypes.h"
#include "string.h"

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

#define TRANSPORTS_NAME_LEGHT   16

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

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

class AbstractTransport
{
    public:
    
        enum MODE
        {
            BLOCK     = 0,
            NON_BLOCK = 1
        };

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

        //----------------------------------------------------------------------
        //      Очистка буферов приёмника и передатчика.
        virtual void purgeRxBuff( void ) = 0;
        virtual void purgeTxBuff( void ) = 0;

        //----------------------------------------------------------------------
        //      Копирует принятые байты из буфера приёмника физического порта
        // в буфер buff размером buffSize принятые байты. Возвращает число
        // скопированных байтов.
        //      В случае ошибки возвращает отрицательное число.
        virtual int16
        receive
        (
            void*   buff,
            int16   buffSize,
            MODE    mode        = NON_BLOCK
        )
        = 0;

        //----------------------------------------------------------------------
        //      Записывает в буфер передатчика физического порта из буфера
        // buff пользователя buffSize байт. Метод не проверяет наличие
        // в буфере передатчика не отправленных с прошлого вызова send байтов!
        // Метод не дожидается окончания отправки всех байтов из buff,
        // а возвращает управление в точку вызова сразу же, как только
        // последний байт из числа buffSize будет записан в буфер передатчика!
        virtual int16   
        send
        (
            void*   buff,
            int16   buffSize,
            MODE    mode    = NON_BLOCK
        )
        = 0;

        //----------------------------------------------------------------------
        //      Метод реализует отправку сообщения из буфера и приём ответа
        // в этот же буфер. Во время работы метода будут недоступны
        // и передатчик, и приёмник.
        //      Метод предназначен для реализации эффективного поллинга
        // по проотоколу Modbus и ему подобным протоколам типа ведущий-ведомый.
        virtual int16
        sendAndReceive
        (
                void*   buff,   // Рабочий буфер
                int16   tx_n,   // Количество байт для отправки.
                int16   rx_n,   // Количество байт для приёма.
                int16   timeout // Таймаут для приёма ответа
        )
        { return( 0 ); };

        //----------------------------------------------------------------------
        //      Возвращает количество оставшихся в буфере физического
        // передатчика неотправленных байт.
        //      Реализация наследников должна обеспечивать правильную работу
        // следующей конструкции:
        //
        //  if( getAvailableTxBytes() == 0 )
        //      send( buff, buffSize );
        //
        virtual int16 getAvailableTxBytes( void ) = 0;

        //----------------------------------------------------------------------
        //      Возвращают количество байт в буфере приёмника.

        virtual int16 getAvailableRxBytes( void ) = 0;

        //----------------------------------------------------------------------
        //      Методы-заглушки для поддержки посимвольного ввода-вывода.

        virtual int16 putChar( int16 c ) = 0;
        virtual int16 getChar( void ) = 0;
        virtual bool  peekChar( int16* c ){ return( false ); }

        virtual int16 putString( const char* str ) = 0;

        //----------------------------------------------------------------------
        //      Возвращает код ошибки последнего вызова метода класса.
        //  0  - нет ошибки, любое другое значение указывает на ошибку, код
        // которой зависит от платформы.
        virtual int  getLastError( void ){ return( 0 ); };

        //----------------------------------------------------------------------
        //

        virtual void enableReceiving( void ){};
        virtual void disableReceiving( void ){};

        //----------------------------------------------------------------------
        //      Печать диагностической информации об объекте.
        
        inline const char* getName( void ){ return( name ); };
        //----------------------------------------------------------------------
        
    protected:

        AbstractTransport( const char* name );
        virtual ~AbstractTransport( void );

        char name[ TRANSPORTS_NAME_LEGHT + 1 ];
};

////////////////////////////////////////////////////////////////////////////////
#endif  // __ABSTRACT_TRANSPORT__
////////////////////////////////////////////////////////////////////////////////
Spoiler
////////////////////////////////////////////////////////////////////////////////
//
//      UARTbase.h
//
////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __UART_BASE_H__
#define __UART_BASE_H__

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

#include "targetver.h"
#include "MyTypes.h"
#include "AbsTrans.h"


#ifdef _WIN32_

    #include "StdAfx.h"
    #include <windows.h>

    #pragma comment(lib, "kernel32.lib")

#endif

#ifdef _MINIOS7_
    #include "I-7188XA.h"
#endif

#ifdef __STM32__

    #ifdef __STM32F407__
        #include "system_stm32f4xx.h"
        #include "stm32f407xx.h"
    #endif

    #ifdef __STM32F411__
        #include "system_stm32f4xx.h"
        #include "stm32f411xe.h"
    #endif

    #ifdef __STM32F446__
        #include "system_stm32f4xx.h"
        #include "stm32f446xx.h"
    #endif

    #ifdef __STM32F745__
        #include "system_stm32f7xx.h"
        #include "stm32f745xx.h"
    #endif

    #ifdef __STM32F767__
        #include "system_stm32f7xx.h"
        #include "stm32f767xx.h"
    #endif

    #include <stdlib.h>
    #include "OS.h"
    #include "kTask.h"      // Собирается только под FreeRTOS!

    #include "IRQ.h"

#endif

//------------------------------------------------------------------------------
#ifdef __MINIOS7__

    #define MAX_SERIAL_PORTS    4 // Количество последовательных портов.

    enum ComPort
    {
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT3    = 3,
        COMPORT4    = 4,
        UNKNOWNPORT = -1
    };

#endif
//------------------------------------------------------------------------------
#ifdef __WIN32__

    #define MAX_SERIAL_PORTS    32  // Количество последовательных портов.

    enum ComPort
    {
        COMPORT0    = 0,
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT3    = 3,
        COMPORT4    = 4,
        COMPORT5    = 5,
        COMPORT6    = 6,
        COMPORT7    = 7,
        COMPORT8    = 8,
        COMPORT9    = 9,
        COMPORT10   = 10,
        COMPORT11   = 11,
        COMPORT12   = 12,
        COMPORT13   = 13,
        COMPORT14   = 14,
        COMPORT15   = 15,
        COMPORT16   = 16,
        COMPORT17   = 17,
        COMPORT18   = 18,
        COMPORT19   = 19,
        COMPORT20   = 20,
        COMPORT21   = 21,
        COMPORT22   = 22,
        COMPORT23   = 23,
        COMPORT24   = 24,
        COMPORT25   = 25,
        COMPORT26   = 26,
        COMPORT27   = 27,
        COMPORT28   = 28,
        COMPORT29   = 29,
        COMPORT30   = 30,
        COMPORT31   = 31,
        UNKNOWNPORT = -1
    };

#endif
//------------------------------------------------------------------------------
#ifdef __STM32__ 

    typedef struct 
    {
        GPIO_TypeDef*   port;
        uint8           aBit;
    }
    AnPin;
    
    typedef struct
    {
        AnPin Tx, Rx, dirControl;
    }
    UARTpin;

#endif

#ifdef __STM32F407__

    // Количество аппаратных последовательных портов.
    #define MAX_SERIAL_PORTS    6
    #define MAX_VCP             2   // Количество виртуальнх портов

    enum ComPort
    {
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT3    = 3,
        COMPORT4    = 4,
        COMPORT5    = 5,
        COMPORT6    = 6,
        COMPORT100  = 100,  // Virtual COM-port over USB
        COMPORT200  = 200,  // Virtual COM-port over Ethernet
        UNKNOWNPORT = -1
    };

#endif

#ifdef __STM32F411__

    // Количество аппаратных последовательных портов.
    #define MAX_SERIAL_PORTS    5
    #define MAX_VCP             1   // Количество виртуальнх портов

    enum ComPort
    {
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT6    = 6,
        COMPORT100  = 100,  // Virtual COM-port over USB
        COMPORT200  = 200,  // Virtual COM-port over Ethernet
        UNKNOWNPORT = -1
    };

#endif

#ifdef __STM32F446__

    #define MAX_SERIAL_PORTS    10  // Количество последовательных портов (UART)
    #define MAX_VCP             1   // Количество виртуальнх портов

    enum ComPort
    {
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT3    = 3,
        COMPORT4    = 4,
        COMPORT5    = 5,
        COMPORT6    = 6,
        COMPORT7    = 7,
        COMPORT8    = 8,
        COMPORT100  = 100,  // Virtual COM-port over USB
        COMPORT200  = 200,  // Virtual COM-port over Ethernet
        UNKNOWNPORT = -1
    };

#endif

#ifdef __STM32F745__

    #define MAX_SERIAL_PORTS    10  // Количество последовательных портов (UART)
    #define MAX_VCP             1   // Количество виртуальнх портов

    enum ComPort
    {
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT3    = 3,
        COMPORT4    = 4,
        COMPORT5    = 5,
        COMPORT6    = 6,
        COMPORT7    = 7,
        COMPORT8    = 8,
        COMPORT100  = 100,  // Virtual COM-port over USB
        COMPORT200  = 200,  // Virtual COM-port over Ethernet
        UNKNOWNPORT = -1
    };

#endif

#ifdef __STM32F767__

    #define MAX_SERIAL_PORTS    10  // Количество последовательных портов (UART)
    #define MAX_VCP             1   // Количество виртуальнх портов

    enum ComPort
    {
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT3    = 3,
        COMPORT4    = 4,
        COMPORT5    = 5,
        COMPORT6    = 6,
        COMPORT7    = 7,
        COMPORT8    = 8,
        COMPORT100  = 100,  // Virtual COM-port over USB
        COMPORT200  = 200,  // Virtual COM-port over Ethernet
        UNKNOWNPORT = -1
    };

#endif

//----------------------------------------------------------------------
enum StopBits
{
    ONE_STOP_BIT,
    ONE_AND_HALF_STOP_BIT,
    TWO_STOP_BITS
};
//------------------------------------------------------------------------------
enum Parity
{
    NO_PARITY = 0,
    ODD,            // Нечётность
    PARITY          // Чётность
};
//------------------------------------------------------------------------------

enum UART_USE_AS
{
    SIMPLE  = 0,
    MODBUS  = 1
};
////////////////////////////////////////////////////////////////////////////////

class UART_base : public AbstractTransport, protected IRQ
{
    public:

        enum UART_ERROR
        {
            OK                          = 0,
            INVALID_PORT                = 1,
            INVALID_BAUDRATE            = 2,
            UNSUPPORTED_DATA_BITS_VALUE = 3,
            UNSUPPORTED_STOP_BITS_VALUE = 4,
            MAPPING_NOT_SUPPORTED       = 5,
            ZERO_POINTER                = 6,
            INVALID_SIZE                = 7,
            IRQ_HANDLER_NOT_INSTALLED   = 8,
            DEVICE_IS_BUSY              = 9,

            // Ошибка операции ввода-вывода.
            IO_FAULT                    = 10,

            // Превышено время ожидания окончания операции.
            TIMEOUT                     = 11,

            // В буфере передатчика нет места для новых данных.
            NO_ROOM_FOR_DATA            = 12
        };

        //----------------------------------------------------------------------
        //      Связывает объект SerialPort с физическим портом port,
        // устанавливая для порта параметры приёма-передачи.
        //      Возвращает номер порта. Зачение UNKNOWNPORT указывает на ошибку.
        virtual ComPort
        open
        (
            ComPort     port,
            uint32      baudRate,
            uint8       dataBits,
            Parity      parity,
            StopBits    stopBits
        );

        //----------------------------------------------------------------------
        //      Отвязывает физический порт от объекта SerialPort.
        //      Возвращает номер отвязанного порта. Значение UNKNOWNPORT
        // указывает на ошибку.
        virtual ComPort close( void );

        //----------------------------------------------------------------------
        //      Возвращает номер физического порта, к которому выполнена
        // привязка. Если привязка не выполнена или завершилась неудачей,
        // то вернёт UNKNOWNPORT.
        ComPort getPort( void );

        //----------------------------------------------------------------------
        //      Возвращает код ошибки последнего вызова метода класса.
        //  0  - нет ошибки, любое другое значение указывает на ошибку, код
        // которой зависит от платформы.
        virtual int  getLastError( void );

        //----------------------------------------------------------------------
        //      Принудительная очистка буферов. Количество байт для приёма-
        // передачи обнуляется! Оставшиеся в передатчике байты не передаются!

        virtual void purgeRxBuff( void ) = 0;
        virtual void purgeTxBuff( void ) = 0;

        //----------------------------------------------------------------------
        //      Читает в буфер buff размером buffSize байты из буфера приёмника.
        //      Работает в неблокирующем режиме!
        //      Возвращает количество прочитанных байт или ноль в случае
        // отсутствия доступных для чтения данных.
        virtual int16
        receive
        (
            void*   buff,
            int16   buffSize,
            MODE    mode        = NON_BLOCK
        ) 
        = 0;

        //----------------------------------------------------------------------
        //      Записывает buffSize байт из буфера buff в буфера передатчика.
        //      Работает в неблокирующем режиме!
        //      Возвращает количество записанных байт или ноль в случае
        // невозможности записи. lastError указывает на ошибку.
        //      Перед вызовом send(..) во избежании склеивания отправляемых
        // посылок, необходимо вызывать getAvailableTxBytes(..) для проверки
        // того, что буфер передатчика пуст.
        virtual int16
        send
        (
            void*   buff,
            int16   buffSize,
            MODE    mode     = NON_BLOCK
        )
        = 0;

        //----------------------------------------------------------------------
        //  Возвращает количество байт, находящихся в буфере передатчика.
        virtual int16 getAvailableTxBytes( void ) = 0;
        virtual int16 getAvailableRxBytes( void ) = 0;

        //----------------------------------------------------------------------
        //  Сервисные функции для мониторинга и диагностики.
        //virtual ostream& print( ostream& os );
        
    protected:

        //----------------------------------------------------------------------
        //      Создаёт объект, через который будет осуществляться работа
        // с последовательным портом. Может создаваться различная
        // обвязка для удобства работы: очереди, семафоры и т.п.
        UART_base
        ( 
            const char* name 
        );

        //----------------------------------------------------------------------
        //      Уничтожает объект с высвобождением всех занятых ресурсов.
        virtual ~UART_base( void );

        //----------------------------------------------------------------------

        ComPort     port;                       // Привязаный порт.
        int         lastError;                  // Код ошибки последнего вызова.
        
        uint32      baudRate;
        uint8       dataBits;
        Parity      parity;
        StopBits    stopBits;

        #ifdef __STM32__
        
            uint32          APB_divider;
            uint8           APB_n;
            USART_TypeDef*  usart;
            IRQn_Type       usart_IRQn;
            
            //------------------------------------------------------------------
            // Устанавливает события, по которым будет формироваться прерывания.
            // Не забудь установить UE и определись с RE и TE!
            virtual void setEvents( void ) = 0;

        #endif

    private:

        // Закрываем конструктор копирования и оператор присваивания.
        UART_base( const UART_base& );
        UART_base& operator= ( UART_base& );

        #ifdef _WIN32_

            HANDLE  m_hFile;
            uint32  numOfBytesToWrite,  // Количество байт для отправки.
                    bytesWrittens;      // Количество отправленных байт.

        #endif
};

extern UART_base *serialPort[ MAX_SERIAL_PORTS ];

////////////////////////////////////////////////////////////////////////////////
#endif  // __UART_H__
////////////////////////////////////////////////////////////////////////////////

 

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


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

1 hour ago, const said:

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

 

 

В Qt принят другой подход, они используют не виртуальные функции а слоты и сигналы.

Ваш обьект последовательного порта, вместе с обработчиком протокола, заворачиваете в класс (назовём его SerialClass), наследник QObject. В нём создаёте сигналы для отправки обработанных данных. Для приёма данных создаёте слоты.

 

Ответную часть оформляете так же в виде класса (назовём его ClientClass), так же наследник QObject, так же со слотами и сигналами. Далее запускаете SerailClass в отдельном потоке (QThread), клиента в потоке GUI и соединяете их с помощью connect

1 hour ago, tonyk_av said:

Как возможный вариант реализации базового класса и базового для UART.

Для МК вполне, для ПС + Qt слишком - 90% этого берёт на себя ОС, 9% - Qt. Оставшийся процент лучше написать с нуля 🙂

 

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


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

4 minutes ago, xvr said:

Для МК вполне, для ПС + Qt слишком - 90% этого берёт на себя ОС

Фишка моего предложения в том, что и в МК, и на ПК, и в свободно программируемых контроллерах  используется один и тот же класс, в хэдере это заметно.

Кому как, но лично мне лень писать несколько разных классов делающих одно и то же, но под разные платформы. И протокольные дела проще отладить на ПК, а потом просто пересобрать программу под другую платформу.

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


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

4 часа назад, const сказал:

1. Прием реализован через call-back ф-ию:

4 часа назад, const сказал:

2. Реализация передачи:

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

if (protocol == 

магическим образом пропадает.

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


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

Ну ваапервых, у вас тут совсем не объекты. Просто функции с именами в стиле С++, с расширением области видимости через оператор ::

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

if (protocol == 1) protocol_1.rcv(b, sz);
if (protocol == 2) protocol_2.rcv(b, sz);

вместо второго if нужно поставить else. Либо, написать через switch(protocol) { case 1:  ... break; case 2: ... break;}

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


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

Потому что иначе если вариантов будет штук 10, то после срабатывания первого в списке, алгоритм будет впустую проверять по if оставшиеся 9, хотя уже ясно, что отработал именно if(...==1)

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


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

16 hours ago, tonyk_av said:

Логично, что оба протокола должны иметь общего предка.

1. Да, логично. В моем случае объекты протокола были "созданы" из си-исходников для МК - ф-ции и переменные запихнул в один объект, ничего не меняя.

2. Какое преимущество в функциональном плане даст мне переписывание этих объектов с наследованием от предка.

Значит, предок должен иметь виртуальный метод rcv(u8* byte, u16 sz).

3. При описании объектов наследованных от предка мне нужно будет этот метод переопределить. В итоге получу два объекта с одинаковыми (по имени и аргументам) метода с разной реализацией. То что у меня есть и сейчас. Отсюда возникает вопрос п. 2.

Зачем она? Ведь есть виртуальный метод rcv().

4. Как в этот метод перенаправить поток байт с другого объекта - comport?

5. Если объектов protocol_х будет много и принимать данные из comport необходимо будет сразу нескольким таким объктам, что будет определяться, например, битовым полем

которое зависит от настройки ПО (меняются при открытом COM-порте, с помощью checkbox, активной вкладки pagecontrol, выбора radiobutton) как быть в таком случае?

6. Все вишеописанное касается и передачи, в зависимости от настроек ПО и действий пользователя отправка может происходить из нескольких protocol_x.

Ужас. Каждый класс в своём файле.

7. Описание каждого объекта в отдельных файлах. Для взаимодействия с остальным ПО (UI) был создан объект comm. И вот уже в его файле comm.cpp были объявлены др. объекты (не в методах, а в файле, см. ниже):

#include "comm.h"

ComPort comport;
Protocol_1 protocol_1;
Protocol_2 protocol_2;

... /* далее идут методы comm::, которые используют вышеобъявленные объекты */

Да у меня бы и не возникало вопросов, пусть и неправильно с точки зрения языка написано ПО, но когда я переместил объявление объектов в конструктор comm (см. ниже) передавать указатели на методы одних объектов в другие объекты у меня пропала возможность - выдает ошибку (что то про то, что методы должны быть static).

#include "comm.h"

Comm::Comm () {
  comport = new ComPort;
  protocol_1 = new Protocol_1;
  protocol_2 = new Protocol_2;
}

 

 

15 hours ago, xvr said:

В Qt принят другой подход, они используют не виртуальные функции а слоты и сигналы.

Ваш обьект последовательного порта, вместе с обработчиком протокола, заворачиваете в класс (назовём его SerialClass), наследник QObject. В нём создаёте сигналы для отправки обработанных данных. Для приёма данных создаёте слоты.

Ответную часть оформляете так же в виде класса (назовём его ClientClass), так же наследник QObject, так же со слотами и сигналами. Далее запускаете SerailClass в отдельном потоке (QThread), клиента в потоке GUI и соединяете их с помощью connect

Думал пойти таким путем. Но во первых, такой подход непереносим на другой фрамеворк, хотелось бы реализовать на "чистом" с++. Ну а во вторых, не много ли ресурсов такой подход будет тратить по сравнению с вызовом ф-ции по указателю? Могут часто отправлятся сообщения по, допустим 5-7 байт, каждый раз дергать slot через signal это норма?

 

13 hours ago, Сергей Борщ said:

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

Прочитал и даже реализовывал на Java (Android Studio). Правда там поток байт шел через сокеты. Методы обработки байт-потока брал из protocol_х (те же), но объекты правда были реализованы по другому.

Но видимо не до конца понял этот функционал раз возникают вопросы, вот поэтому я тут.

 

 

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


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

1 hour ago, const said:

2. Какое преимущество в функциональном плане даст мне переписывание этих объектов с наследованием от предка.

 

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

1 hour ago, const said:

4. Как в этот метод перенаправить поток байт с другого объекта - comport?

 

Протокол должен вызывать метод (что то типа recv) от comport'а

1 hour ago, const said:

Если объектов protocol_х будет много и принимать данные из comport необходимо будет сразу нескольким таким объктам, что будет определяться, например, битовым полем

которое зависит от настройки ПО (меняются при открытом COM-порте, с помощью checkbox, активной вкладки pagecontrol, выбора radiobutton) как быть в таком случае?

Если одновременно может быть активен только один протокол, то проще простого - делаете поле типа указатель на бызовый тип для всех протоколов, потом (в зависимости от checkbox) создаёте конкретный класс протокола (наследник) и присваиваете его в это поле. Всё, заботу о дальнейшем берёт на себя С++ полиморфизм.

Если надо одновременно несколько протоколов, то встаёт вопрос - как они будут делить трафик из comport'а. напрашивается некий арбитр, который сам по себе является протоколом (таким же как остальные), и который внутри себя держит массив указателей на обслуюиваемые им протоколы. Вот тут уже в полный рост полиморфизм.

1 hour ago, const said:
  protocol_1 = new Protocol_1;
  protocol_2 = new Protocol_2;

Этого быть не должно - поле protocol должно быть одно. И создавать надо один из Protocol_1 или Protocol_2

1 hour ago, const said:

выдает ошибку (что то про то, что методы должны быть static).

Описание Comm покажите

1 hour ago, const said:

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

У вас GUI программа. Так же у вас взаимодействие с COM портом. Это вещь асинхронная и требующая либо отдельного потока либо встраивания его обслуживания в цикл обработки оконных сообщений (чего никто не делает, по причине сложности). Общение отдельного потока с GUI потоком требует синхронизации через оконные сообщения, механизм слотов/сигналов это обеспечивает автоматически. На 'чистом С++' вам придётся с нуля эти самые слоты и сигналы написать самому.

1 hour ago, const said:

Ну а во вторых, не много ли ресурсов такой подход будет тратить по сравнению с вызовом ф-ции по указателю?

Примой вызов функци  по указателю (а равно как и виртаульного метода) приведёт к трудно уловимым глюкам в вашем GUI. Синхронизация ОБЯЗАНА быть.

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

 

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


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

2 часа назад, const сказал:

Но видимо не до конца понял этот функционал раз возникают вопросы, вот поэтому я тут.

 

Спойлер
class generic_protocol
{
public:
    generic_protocol()  {}
    virtual ~generic_protocol() {}
    
    virtual bool recv(u8* byte, u16 sz) = 0;
};

class protocol_1 : public generic_protocol
{
public:
    protocol_1(uint32_t param);
    ~protocol_1();

    bool recv(u8* byte, u16 sz);
}

class protocol_2 : public generic_protocol
{
public:
    protocol_2(char const * param);
    ~protocol_2();

    bool recv(u8* byte, u16 sz);
}


generic_protocol * Protocol;


bool start(uint8_t protocol_number)
{
    if(Protocol)
        delete Protocol;    // destroy previous protocol
        
    switch(protocol_number)
    {
    case 0:     Protocol = new protocol_1(1);
                return true;
                
    case 1:     Protocol = new_protocol_2("Cлава мне, победителю драконов!");
                return true;
                
    case 2:     Protocol = new protocol_1(100500);
                return true;

    default:    Protocol = nullptr;
    }
    
    return false;
}


void process()
{
    for(;;)
    {
        u8_t Buffer[512];
        u16_t Size;
        ....
    
        Protocol->rcv(Buffer, Size);
    }
}

 

 

2 часа назад, const сказал:

2. Какое преимущество в функциональном плане даст мне переписывание этих объектов с наследованием от предка

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

2) При добавлении нового протокола кроме самого класса протокола в основной код нужно будет добавить лишь одну строчку создания нужного объекта. 

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

else if (protocol == 3)
2 часа назад, const сказал:

Если объектов protocol_х будет много и принимать данные из comport необходимо будет сразу нескольким таким объктам, что будет определяться, например, битовым полем

которое зависит от настройки ПО (меняются при открытом COM-порте, с помощью checkbox, активной вкладки pagecontrol, выбора radiobutton) как быть в таком случае?

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

Спойлер
class protocol_absent : public generic_protocol
{
public:
    protocol_absent()   {}

    bool recv(u8*, u16) {};
}

generic_protocol * Protocol[5] = 
{
    new Protocol_absent,
    new Protocol_absent,
    new Protocol_absent,
    new Protocol_absent,
    new Protocol_absent,
};

bool start(uint8_t protocol_number, uint8_t position)
{
    if(Protocol[position])
        delete Protocol[position];    // destroy previous protocol
        
    switch(protocol_number)
    {
    case 0:     Protocol[position] = new protocol_1(1);
                return true;
                
    case 1:     Protocol[position] = new_protocol_2("Cлава мне, победителю драконов!");
                return true;
                
    case 2:     Protocol[position] = new protocol_1(100500);
                return true;

    default:    Protocol = new Protocol_absent;
    }
    
    return false;
}


void process()
{
    for(;;)
    {
        u8_t Buffer[512];
        u16_t Size;
        ....
    
        for(auto pProto : Protocol)
        	pProto->rcv(Buffer, Size);
    }
}

 

примерно так.

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


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

1 hour ago, const said:

2. Какое преимущество в функциональном плане даст мне переписывание этих объектов с наследованием от предка.

Огромное.

От показанного мной выше клаccа AbstractTransport порождены, например, классы последовательных портов, работающие через UART, Ethernet и USB, при этом программа, читающая-пищущая в такой порт, абсолютно не знает, через какой физический интерфейс идёт обмен.

Аналогичная ситуация и с протоколами, например, Модбас, когда создаётся базовый класс, реализующий логику протокола:

Spoiler
////////////////////////////////////////////////////////////////////////////////
//
//      MB_Slave_base.h
//
////////////////////////////////////////////////////////////////////////////////

#ifndef __MB_Slave_base_H__
#define __MB_Slave_base_H__

#include "MyTypes.h"
#include "kTask.h"
#include "AbsTrans.h"
#include "ModbusCRC.h"
#include "MB.h"

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

struct MB_SlaveStatistics  // Статистика работы слэйва
{
    int16
        speed,      // Частота опроса, запросов в секунду
        errCnt;     // Счётчик повреждённых запросов.
};

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

class MB_Slave_base
{
    public:

        MB_Slave_base
        (
            const char*         name,
            MB_ADU_manipulator* manipulator,
            AbstractTransport*  transport,
            const int16*        node,
            const int16*        inputRegisters,
            const int16*        Nir,
            int16*              holdingRegisters,
            const int16*        Nhr,
            const int16*        discrets,
            const int16*        Nd,
            int16*              coils,
            const int16*        Nc,
            MB_SlaveStatistics* stat
        );

        virtual ~MB_Slave_base( void );

        virtual void poll( void );

        //----------------------------------------------------------------------


    protected:

        //----------------------------------------------------------------------
        // Реализованные комманды протокола.

        // Возвращают количество прочитанных регистров.
        // Отрицательное значение содержит код ошибки MB_ERROR_хххх
        virtual int8 readInputRegisters( int16 from, int16 N, int16* data );    // 0x04
        virtual int8 readHoldingRegisters( int16 from, int16 N, int16* data);   // 0x03
        virtual int8 writeHoldingRegisters( int16 from, int16 N, int16* data ); // 0x10

        //----------------------------------------------------------------------

        AbstractTransport
            *tr;

        const int16
            *node,
            *ir,
            *Nir,
            *Nhr,
            *disc,
            *Nd,
            *Nc;

        int16
            *hr,
            *coils;

        MB_SlaveStatistics
            *stat;

        MB_ADU_manipulator
            *manipulator;

        MB_ADU
            *adu;
};

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

#endif  // __MB_Slave_base_H__

////////////////////////////////////////////////////////////////////////////////
//
//  the end of MB_Slave_base.h
//
////////////////////////////////////////////////////////////////////////////////

Разница между Modbus/TCP и Modbus/RTU заключается в обработке ADU. Вынесем обработку в отдельный класс и получим, что дальнейшая обработка запросов в Модбас ничем отличаться не будет.

Spoiler
////////////////////////////////////////////////////////////////////////////////
//
//  MB_TCP_ADU_manipulator.cll
//
////////////////////////////////////////////////////////////////////////////////

#include <MB_TCP_ADU_manipulator.h>

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

bool
MB_TCP_ADU_manipulator::isOurID( uint8 _deviceID, uint8 _IDfromADU )
{
    return( true );
}

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

uint16
MB_TCP_ADU_manipulator::getTransactionID( const MB_ADU* _adu )
{
    uint16
        val = _adu -> tcp.transactionID ;

    val = MB2host( val );

    return( val );
}

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

uint16
MB_TCP_ADU_manipulator::getProtocolID( const MB_ADU* _adu )
{
    uint16
        val = _adu -> tcp.protocolID;

    val = MB2host( val );

    return( val );
}

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

MB_PDU*
MB_TCP_ADU_manipulator::getPDU( MB_ADU* _adu )
{
    return( &_adu -> tcp.pdu );
}

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

uint16
MB_TCP_ADU_manipulator::getLenght( const MB_ADU* _adu )
{
    return( _adu -> tcp.lenght );
}

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

uint8
MB_TCP_ADU_manipulator::getNode( const MB_ADU* _adu )
{
    uint8
        *ptr = ( uint8* )( ( std::ptrdiff_t )_adu + 6 );

    return( *ptr );
}

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

uint16
MB_TCP_ADU_manipulator::getCRC( const MB_ADU* _adu, int16 _len )
{
    return( 0 );
}

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

int16
MB_TCP_ADU_manipulator::putTransactionID( MB_ADU* _adu, uint16 _id )
{
    //_id = host2MB( _id );
    //_adu -> tcp.transactionID = _id;
    ( void )_id;
    ( void )_adu;

    return( 2 );
}

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

int16
MB_TCP_ADU_manipulator::putProtocolID( MB_ADU* _adu, uint16 _id )
{
    //_id = host2MB( _id );
    //_adu -> tcp.protocolID = _id;

    ( void )_id;
    ( void )_adu;

    return( 2 );
}

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

int16
MB_TCP_ADU_manipulator::putNode( MB_ADU* _adu, uint8 _node )
{
    _adu -> tcp.node = _node;

    return( 1 );
}

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

int16
MB_TCP_ADU_manipulator::putLenght( MB_ADU* _adu, uint16 _len )
{
    _adu -> tcp.lenght = host2MB( _len );

    return( 2 );
}

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

int16
MB_TCP_ADU_manipulator::putCRC( MB_ADU* _adu, int16 _ans_len, uint16 _crc )
{
    return( 0 );
}

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

uint16
MB_TCP_ADU_manipulator::calculateCRC( const MB_ADU* _adu, int16 _len )
{
    return( 0 );
}

////////////////////////////////////////////////////////////////////////////////
//
//  The end of MB_TCP_ADU_manipulator.cpp
//
////////////////////////////////////////////////////////////////////////////////
Spoiler
////////////////////////////////////////////////////////////////////////////////
//
//  MB_RTU_ADU_manipulator.cll
//
////////////////////////////////////////////////////////////////////////////////

#include <MB_RTU_ADU_manipulator.h>

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

bool
MB_RTU_ADU_manipulator::isOurID( uint8 _deviceID, uint8 _IDfromADU )
{
    bool
        result = ( _deviceID == _IDfromADU );

    return( result );
}

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

uint16
MB_RTU_ADU_manipulator::getTransactionID( const MB_ADU* _adu )
{
    ( void )_adu;

    return( 0 );
}

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

uint16
MB_RTU_ADU_manipulator::getProtocolID( const MB_ADU* _adu )
{
    ( void )_adu;

    return( 0 );
}

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

uint16
MB_RTU_ADU_manipulator::getLenght( const MB_ADU* _adu )
{
    ( void )_adu;

    return( 0 );
}

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

MB_PDU*
MB_RTU_ADU_manipulator::getPDU( MB_ADU* _adu )
{
    return( &_adu -> rtu.pdu );
}

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

uint8
MB_RTU_ADU_manipulator::getNode( const MB_ADU* _adu )
{
    return( _adu -> rtu.node );
}

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

uint16
MB_RTU_ADU_manipulator::getCRC( const MB_ADU* _adu, int16 _len )
{
    uint16
        const *ptr = ( uint16* )( ( uint8* )_adu + _len - sizeof( uint16 ) );

    uint16
        val = *ptr;

    val =  MB2host( val );

    return( val );
}

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

int16
MB_RTU_ADU_manipulator::putTransactionID( MB_ADU* _adu, uint16 _id )
{
    ( void )_adu;
    ( void )_id;

    return( 0 );
}

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

int16
MB_RTU_ADU_manipulator::putProtocolID( MB_ADU* _adu, uint16 _id )
{
    ( void )_adu;
    ( void )_id;

    return( 0 );
}

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

int16
MB_RTU_ADU_manipulator::putLenght( MB_ADU* _adu, uint16 _len )
{
    ( void )_adu;
    ( void )_len;

    return( 0 );
}

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

int16
MB_RTU_ADU_manipulator::putNode( MB_ADU* _adu, uint8 _node )
{
    _adu -> rtu.node = _node;

    return( 1 );
}

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

int16
MB_RTU_ADU_manipulator::putCRC( MB_ADU* _adu, int16 _ans_len, uint16 _crc )
{
    _adu-> rtu.pdu.data[ _ans_len - 2 ] = ( _crc >> 8 ) & 0xFF;
    _adu-> rtu.pdu.data[ _ans_len - 1 ] = _crc & 0xFF;

    return( 2 );
}

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

uint16
MB_RTU_ADU_manipulator::calculateCRC( const MB_ADU* _adu, int16 _len )
{
    uint16
        crc = calculateModbusCRC( ( uint8* )_adu, _len );

    return( crc );
}

////////////////////////////////////////////////////////////////////////////////
//
//  The end of MB_RTU_ADU_manipulator.cll
//
////////////////////////////////////////////////////////////////////////////////

А потом просто собираем нужного мастера:

Spoiler
void CommTask::run( void )
{
    if( firstLoop )
    {
        firstLoop = false;

        // Инициализируем подсистему ввода-вывода.
        init_io();

        // Создаём два сокета для работы с двумя подключениями по Модбас.
    	mb_sock_1 = new SockIO( "MBTCP_502", wiz, SockIO::TCP, SockIO::SERVER, 0, 0 );
    	mb_sock_2 = new SockIO( "MBTCP_5002", wiz, SockIO::TCP, SockIO::SERVER, 0, 0 );

        // Открваем два сокета для двух подключений по Модбас по портам 502 и 5002.
        mb_sock_1 -> open( 502 );
        mb_sock_2 -> open( 5002 );

        // Создаём манипулятор для работ с TCP ADU.
        mb_tcp_manipulator = new MB_TCP_ADU_manipulator();

        // Создаём двух ТСР-слэйвов.
        mb_tcp_slave_1 = new MB_TCP_Slave
                            (
                                "MB_Slave_502",
                                mb_tcp_manipulator,
                                mb_sock_1,
                                &mb_node,
                                &ir[ 0 ], &Nir,
                                &hr[ 0 ], &Nhr,
                                0, 0,
                                0, 0,
                                &mb_stat_1
                            ),
        mb_tcp_slave_2 = new MB_TCP_Slave
                            (
                                "MB_Slave_5002",
                                mb_tcp_manipulator,
                                mb_sock_2,
                                &mb_node,
                                &ir[ 0 ], &Nir,
                                &hr[ 0 ], &Nhr,
                                0, 0,
                                0, 0,
                                &mb_stat_2
                            );
    }
    //--------------------------------------------------------------------------

    // Обязательно вызываем поллеры для Модбас-клиентов, которые
    // обработают запросы!
    mb_tcp_slave_1 -> poll();
    mb_tcp_slave_2 -> poll();

    loop++;

    Task::yield();
}

Для построения Modbus/RTU-слэйва нужно передать классу слэйва в качестве транспорта указатель на MB_RTU_UART и и обработчик ADU MB_RTU_ADU_manipulator. Получается, по-умному выражаясь, высокий коэффициент повторного использования кода.

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


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

Ух, ппц, нет слов. Писал сообщение около часа, нажал на какую-то ссылку, вернулся и все пропало.

 

12 hours ago, xvr said:

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

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

12 hours ago, xvr said:

Протокол должен вызывать метод (что то типа recv) от comport'а

Когда он должен его вызывать? Откуда он знает, что пришла очередная порция байт? Полинг? По моему, логичней объекту comport вызывать call-back ф-ию передавая в нее принятые данные. Так сказать, событийная модель.

12 hours ago, xvr said:

Если одновременно может быть активен только один протокол, то проще простого - делаете поле типа указатель на бызовый тип для всех протоколов, потом (в зависимости от checkbox) создаёте конкретный класс протокола (наследник) и присваиваете его в это поле. Всё, заботу о дальнейшем берёт на себя С++ полиморфизм.

Если надо одновременно несколько протоколов, то встаёт вопрос - как они будут делить трафик из comport'а. напрашивается некий арбитр, который сам по себе является протоколом (таким же как остальные), и который внутри себя держит массив указателей на обслуюиваемые им протоколы. Вот тут уже в полный рост полиморфизм.

Если один - да, согласен. Не мой случай.

Делят трафик легко и непринужденно.

Объект comport при открытии физ. порта создает два потока - прием и передача. Прием данных реализован через call-back ф-ию, которая в свою очередь передает всем «нуждающемся» объектам принятые байты через .rcv(...) и тут же, в call-back, объекты протоколов опрашиваются на наличие собранных сообщений методом get_msg(...). Если таковые имеются, они складываются в кольцевой буфер, после чего пинается поток UI, который выгребает из кольцевого буфера эти сообщения и «отображает». Для передачи используется кольцевой буфер объекта comport в который методом comport.send(…) помещаются данные и пинается поток передачи. Поток выгребает данные и передает их в физ. порт. Указатель на этот метод передан всем протокольным объектам, что бы они могли отправлять сообщения своими собственными методами. Так как протоколы находятся в одном потоке, они не перекрывают друг друга. Операции «положить-достать» с кольцевыми буферами атомарны, обеспечиваются реализацией. Так было в CBuilder6.

12 hours ago, xvr said:

Этого быть не должно - поле protocol должно быть одно. И создавать надо один из Protocol_1 или Protocol_2

См. выше.

12 hours ago, xvr said:

Описание Comm покажите

#ifndef XCOMM_H
#define XCOMM_H

#include "xcomport.h"
#include "slip.h"

class xComm : public QObject
{
public:
    xComm() {
        cp  = new xComPort;
        cp->setDebug(true);

        slp = new Slip;
    }

    ~xComm() {
        delete cp;
    }

    bool open(QString & name) {
        auto res = cp->openComPort(name, 256000);
        if (res) QObject::connect(cp, &QSerialPort::readyRead, this, &xComm::read);
        return res;
    }

private:
    xComPort * cp;
    Slip     * slp;

private slots:
    void read() {
        //auto a = cp->readAll();
        //slp.rcv((Data*)a.data(), a.size());
        //while(slp.getSize()) {

        //}
    }
};

#endif // XCOMM_H

 

12 hours ago, xvr said:

У вас GUI программа. Так же у вас взаимодействие с COM портом. Это вещь асинхронная и требующая либо отдельного потока либо встраивания его обслуживания в цикл обработки оконных сообщений (чего никто не делает, по причине сложности). Общение отдельного потока с GUI потоком требует синхронизации через оконные сообщения, механизм слотов/сигналов это обеспечивает автоматически. На 'чистом С++' вам придётся с нуля эти самые слоты и сигналы написать самому.

12 hours ago, xvr said:

Примой вызов функци  по указателю (а равно как и виртаульного метода) приведёт к трудно уловимым глюкам в вашем GUI. Синхронизация ОБЯЗАНА быть.

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

Вся «синхронизация» обеспечивается очередями, см. выше.

to Сергей Борщ

Реализация с наследованием понятна, спс за пример. Но вопрос был в другом.

Откуда берутся данные в Buffer[512]? Интересует реализация передачи этих данных из объекта comport протоколу(ам).

12 hours ago, Сергей Борщ said:

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

2) При добавлении нового протокола кроме самого класса протокола в основной код нужно будет добавить лишь одну строчку создания нужного объекта. 

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

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

А в общем, конечно не поспоришь.

12 hours ago, Сергей Борщ said:

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

Несомненно, ваш вариант лучше (правильней) моей реализации вызова методов объектов в call-back: protocol_1.rcv(…), protocol_2.rcv(…), …, protocol_x.rcv(…). Но главный вопрос во взаимодействие объектов, повторюсь: Откуда берутся данные в Buffer[512]?

Вопрос по вашей реализации: «пустые» указатели обязательно забивать объектами-заглушками? При обращении к объекту нет автоматической проверки его адреса на nullptr? Может проще обрабатывать исключение чем создавать заглушки?

to tonyk_av

В объекте class UART_base : public AbstractTransport, protected IRQ

переменная ComPort это номер порта, а сам порт у вас оформлен в виде объекта, где он объявлен, в каком файле (объекте)? Имеют к нему доступ другие объекты программы? Проясните пожалуйста.

to EdgeAligned

за видео спс. Найду время и внимательно его посмотрю.

 

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

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


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

12 hours ago, tonyk_av said:

От показанного мной выше клаccа AbstractTransport порождены, например, классы последовательных портов, работающие через UART, Ethernet и USB, при этом программа, читающая-пищущая в такой порт, абсолютно не знает, через какой физический интерфейс идёт обмен.

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

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


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

6 hours ago, const said:

переменная ComPort это номер порта, а сам порт у вас оформлен в виде объекта, где он объявлен, в каком файле (объекте)? Имеют к нему доступ другие объекты программы? Проясните пожалуйста.

Поясняю.

Такой подход к работе с UART когда-то давно я видел у Advantech, где это было сделано в виде библиотеки на С. Нашёл это удобным для себя. Написал подобное на С++. Вот с тех пор этот класс и обрастает поддержкой разных ОС и МК. Класс реализует работу как с посимвольной записью-чтением через putChar()-getChar(), так и с блочной через send()-receive(). При работе с физическим UART используются DMA и кольцевые буферы.

Spoiler
////////////////////////////////////////////////////////////////////////////////
//
//      UARTbase.h
//
////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __UART_BASE_H__
#define __UART_BASE_H__

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

#include "targetver.h"
#include "MyTypes.h"
#include "AbsTrans.h"
#include "stdint.h"


#ifdef _WIN32_

    #include "StdAfx.h"
    #include <windows.h>

    #pragma comment(lib, "kernel32.lib")

#endif

#ifdef _MINIOS7_
    #include "I-7188XA.h"
#endif

#ifdef __STM32__

    #ifdef __STM32F411__
        #include "system_stm32f4xx.h"
        #include "stm32f411xe.h"
    #endif

    #ifdef __STM32F446__
        #include "system_stm32f4xx.h"
        #include "stm32f446xx.h"
    #endif

    #ifdef __STM32F745__
        #include "system_stm32f7xx.h"
        #include "stm32f745xx.h"
    #endif

    #ifdef __STM32F767__
        #include "system_stm32f7xx.h"
        #include "stm32f767xx.h"
    #endif

    #include <stdlib.h>
    #include "OS.h"
    #include "kTask.h"      // Собирается только под FreeRTOS!

    #include "IRQ.h"

#endif

//------------------------------------------------------------------------------
#ifdef __MINIOS7__

    #define MAX_SERIAL_PORTS    4 // Количество последовательных портов.

    enum ComPort
    {
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT3    = 3,
        COMPORT4    = 4,
        UNKNOWNPORT = -1
    };

#endif
//------------------------------------------------------------------------------
#ifdef __WIN32__

    #define MAX_SERIAL_PORTS    32  // Количество последовательных портов.

    enum ComPort
    {
        COMPORT0    = 0,
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT3    = 3,
        COMPORT4    = 4,
        COMPORT5    = 5,
        COMPORT6    = 6,
        COMPORT7    = 7,
        COMPORT8    = 8,
        COMPORT9    = 9,
        COMPORT10   = 10,
        COMPORT11   = 11,
        COMPORT12   = 12,
        COMPORT13   = 13,
        COMPORT14   = 14,
        COMPORT15   = 15,
        COMPORT16   = 16,
        COMPORT17   = 17,
        COMPORT18   = 18,
        COMPORT19   = 19,
        COMPORT20   = 20,
        COMPORT21   = 21,
        COMPORT22   = 22,
        COMPORT23   = 23,
        COMPORT24   = 24,
        COMPORT25   = 25,
        COMPORT26   = 26,
        COMPORT27   = 27,
        COMPORT28   = 28,
        COMPORT29   = 29,
        COMPORT30   = 30,
        COMPORT31   = 31,
        UNKNOWNPORT = -1
    };

#endif
//------------------------------------------------------------------------------
#ifdef __STM32__ 

    typedef struct 
    {
        GPIO_TypeDef*   port;
        uint8           aBit;
    }
    AnPin;
    
    typedef struct
    {
        AnPin Tx, Rx, dirControl;
    }
    UARTpin;

#endif

#ifdef __STM32F411__

    // Количество аппаратных последовательных портов.
    #define MAX_SERIAL_PORTS    5
    #define MAX_VCP             1   // Количество виртуальнх портов

    enum ComPort
    {
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT6    = 6,
        COMPORT100  = 100,  // Virtual COM-port over USB
        COMPORT200  = 200,  // Virtual COM-port over Ethernet
        UNKNOWNPORT = -1
    };

#endif

#ifdef __STM32F446__

    #define MAX_SERIAL_PORTS    10  // Количество последовательных портов (UART)
    #define MAX_VCP             1   // Количество виртуальнх портов

    enum ComPort
    {
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT3    = 3,
        COMPORT4    = 4,
        COMPORT5    = 5,
        COMPORT6    = 6,
        COMPORT7    = 7,
        COMPORT8    = 8,
        COMPORT100  = 100,  // Virtual COM-port over USB
        COMPORT200  = 200,  // Virtual COM-port over Ethernet
        UNKNOWNPORT = -1
    };

#endif

#ifdef __STM32F745__

    #define MAX_SERIAL_PORTS    10  // Количество последовательных портов (UART)
    #define MAX_VCP             1   // Количество виртуальнх портов

    enum ComPort
    {
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT3    = 3,
        COMPORT4    = 4,
        COMPORT5    = 5,
        COMPORT6    = 6,
        COMPORT7    = 7,
        COMPORT8    = 8,
        COMPORT100  = 100,  // Virtual COM-port over USB
        COMPORT200  = 200,  // Virtual COM-port over Ethernet
        UNKNOWNPORT = -1
    };

#endif

#ifdef __STM32F767__

    #define MAX_SERIAL_PORTS    10  // Количество последовательных портов (UART)
    #define MAX_VCP             1   // Количество виртуальнх портов

    enum ComPort
    {
        COMPORT1    = 1,
        COMPORT2    = 2,
        COMPORT3    = 3,
        COMPORT4    = 4,
        COMPORT5    = 5,
        COMPORT6    = 6,
        COMPORT7    = 7,
        COMPORT8    = 8,
        COMPORT100  = 100,  // Virtual COM-port over USB
        COMPORT200  = 200,  // Virtual COM-port over Ethernet
        UNKNOWNPORT = -1
    };

#endif

//----------------------------------------------------------------------
enum StopBits
{
    ONE_STOP_BIT,
    ONE_AND_HALF_STOP_BIT,
    TWO_STOP_BITS
};
//------------------------------------------------------------------------------
enum Parity
{
    NO_PARITY = 0,
    ODD,            // Нечётность
    PARITY          // Чётность
};
//------------------------------------------------------------------------------

enum UART_USE_AS
{
    SIMPLE  = 0,
    MODBUS  = 1
};
////////////////////////////////////////////////////////////////////////////////

class UART_base : public AbstractTransport, protected IRQ
{
    public:

        enum UART_ERROR
        {
            OK                          = 0,
            INVALID_PORT                = 1,
            INVALID_BAUDRATE            = 2,
            UNSUPPORTED_DATA_BITS_VALUE = 3,
            UNSUPPORTED_STOP_BITS_VALUE = 4,
            MAPPING_NOT_SUPPORTED       = 5,
            ZERO_POINTER                = 6,
            INVALID_SIZE                = 7,
            IRQ_HANDLER_NOT_INSTALLED   = 8,
            DEVICE_IS_BUSY              = 9,

            // Ошибка операции ввода-вывода
            IO_FAULT                    = 10,

            // Превышено время ожидания окончания операции
            TIMEOUT                     = 11
        };

        //----------------------------------------------------------------------
        //      Связывает объект SerialPort с физическим портом port,
        // устанавливая для порта параметры приёма-передачи.
        //      Возвращает номер порта. Зачение UNKNOWNPORT указывает на ошибку.
        virtual ComPort
        open
        (
            ComPort     port,
            uint32      baudRate,
            uint8       dataBits,
            Parity      parity,
            StopBits    stopBits
        );

        //----------------------------------------------------------------------
        //      Отвязывает физический порт от объекта SerialPort.
        //      Возвращает номер отвязанного порта. Значение UNKNOWNPORT
        // указывает на ошибку.
        virtual ComPort close( void );

        //----------------------------------------------------------------------
        //      Возвращает номер физического порта, к которому выполнена
        // привязка. Если привязка не выполнена или завершилась неудачей,
        // то вернёт UNKNOWNPORT.
        ComPort getPort( void );

        //----------------------------------------------------------------------
        //      Возвращает код ошибки последнего вызова метода класса.
        //  0  - нет ошибки, любое другое значение указывает на ошибку, код
        // которой зависит от платформы.
        virtual int  getLastError( void );

        //----------------------------------------------------------------------
        //      Принудительная очистка буферов. Количество байт для приёма-
        // передачи обнуляется! Оставшиеся в передатчике байты не передаются!

        virtual void purgeRxBuff( void ) = 0;
        virtual void purgeTxBuff( void ) = 0;

        //----------------------------------------------------------------------
        //      Читает в буфер buff размером buffSize байты из буфера приёмника.
        //      Работает в неблокирующем режиме!
        //      Возвращает количество прочитанных байт или ноль в случае
        // отсутствия доступных для чтения данных.
        virtual int16
        receive
        (
            void*   buff,
            int16   buffSize,
            MODE    mode        = NON_BLOCK
        ) 
        = 0;

        //----------------------------------------------------------------------
        //      Записывает buffSize байт из буфера buff в буфера передатчика.
        //      Работает в неблокирующем режиме!
        //      Возвращает количество записанных байт или ноль в случае
        // невозможности записи. lastError указывает на ошибку.
        //      Перед вызовом send(..) во избежании склеивания отправляемых
        // посылок, необходимо вызывать getAvailableTxBytes(..) для проверки
        // того, что буфер передатчика пуст.
        virtual int16
        send
        (
            void*   buff,
            int16   buffSize,
            MODE    mode     = NON_BLOCK
        )
        = 0;

        //----------------------------------------------------------------------
        //  Возвращает количество байт, находящихся в буфере передатчика.
        virtual int16 getAvailableTxBytes( void ) = 0;
        virtual int16 getAvailableRxBytes( void ) = 0;

        //----------------------------------------------------------------------
        //  Сервисные функции для мониторинга и диагностики.
       // virtual ostream& print( ostream& os );
        
    protected:

        //----------------------------------------------------------------------
        //      Создаёт объект, через который будет осуществляться работа
        // с последовательным портом. Может создаваться различная
        // обвязка для удобства работы: очереди, семафоры и т.п.
        UART_base
        ( 
            const char* name 
        );

        //----------------------------------------------------------------------
        //      Уничтожает объект с высвобождением всех занятых ресурсов.
        virtual ~UART_base( void );

        //----------------------------------------------------------------------

        ComPort     port;                       // Привязаный порт.
        int         lastError;                  // Код ошибки последнего вызова.
        
        uint32      baudRate;
        uint8       dataBits;
        Parity      parity;
        StopBits    stopBits;

        #ifdef __STM32__
        
            uint32          APB_divider;
            uint8           APB_n;
            USART_TypeDef*  usart;
            IRQn_Type       usart_IRQn;
            
            //------------------------------------------------------------------
            // Устанавливает события, по которым будет формироваться прерывания.
            // Не забудь установить UE и определись с RE и TE!
            virtual void setEvents( void ) = 0;

        #endif

    private:

        // Закрываем конструктор копирования и оператор присваивания.
        UART_base( const UART_base& );
        UART_base& operator= ( UART_base& );

        #ifdef _WIN32_

            HANDLE  m_hFile;
            uint32  numOfBytesToWrite,  // Количество байт для отправки.
                    bytesWrittens;      // Количество отправленных байт.

        #endif
};

extern UART_base *serialPort[ MAX_SERIAL_PORTS ];

////////////////////////////////////////////////////////////////////////////////
#endif  // __UART_H__
////////////////////////////////////////////////////////////////////////////////

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

Spoiler
////////////////////////////////////////////////////////////////////////////////
//
//      ConiIO.h
//
//      Консольный ввод-вывод.
//
////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __CONIO_H__
#define __CONIO_H__

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

#include <stdio.h>
#include "MB_RTU_UART.h"
#include "RingBuffer.h"
#include "Semaphore.h"
#include "SWTimer.h"

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

class ConIO : public MB_RTU_UART, SWTimer
{
    public:

        //----------------------------------------------------------------------
        //      Создаёт объект, через который будет осуществляться работа
        // с последовательным портом. Ни какой привязки к физическому порту
        // на этом шаге не выполняется, зато может создаваться различная
        // обвязка для удобства работы: очереди, семафоры и т.п.
        ConIO
        (
            const char* name,
            uint16      size_of_tx_buff,    // размер кольцевого буфера передатчика;
            uint16      size_of_rx_buff,    // размер колльцевого буфера приёмника;
            uint16      size_of_dma_tx_buff,// размера буфера DMA для передачи;
            uint16      size_of_dma_rx_buff,// размера буфера DMA для приёма;
            uint16      Nagles_timeout      // задержка для алгоритма Нэйгла, ms.
        );

 

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


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

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

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

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

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

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

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

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

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

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