const 1 26 апреля, 2023 Опубликовано 26 апреля, 2023 · Жалоба Привет. Не могу сам разобраться, в плюсах не силен. Как правильно (принято) реализовывать взаимодействие между объектами? Мой случай, условно (пишу на 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 (как было ранее) все получится. Но хотелось бы узнать какие еще варианты взаимодействия объектов имеются. Или в моем случае надо полностью менять подход в построении программы? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
tonyk_av 31 26 апреля, 2023 Опубликовано 26 апреля, 2023 · Жалоба 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__ //////////////////////////////////////////////////////////////////////////////// Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
xvr 12 26 апреля, 2023 Опубликовано 26 апреля, 2023 · Жалоба 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. Оставшийся процент лучше написать с нуля 🙂 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
tonyk_av 31 26 апреля, 2023 Опубликовано 26 апреля, 2023 · Жалоба 4 minutes ago, xvr said: Для МК вполне, для ПС + Qt слишком - 90% этого берёт на себя ОС Фишка моего предложения в том, что и в МК, и на ПК, и в свободно программируемых контроллерах используется один и тот же класс, в хэдере это заметно. Кому как, но лично мне лень писать несколько разных классов делающих одно и то же, но под разные платформы. И протокольные дела проще отладить на ПК, а потом просто пересобрать программу под другую платформу. 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Сергей Борщ 119 26 апреля, 2023 Опубликовано 26 апреля, 2023 · Жалоба 4 часа назад, const сказал: 1. Прием реализован через call-back ф-ию: 4 часа назад, const сказал: 2. Реализация передачи: Ужас. Почитайте про виртуальные функции. Каждый протокол в своем классе, у всех классов протоколов один общий абстрактный предок. При выборе протокола создается объект нужного класса и его адрес присваивается указателю. Далее все обращения идут через этот указатель и весь огород if (protocol == магическим образом пропадает. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
EdgeAligned 53 27 апреля, 2023 Опубликовано 27 апреля, 2023 · Жалоба Ну ваапервых, у вас тут совсем не объекты. Просто функции с именами в стиле С++, с расширением области видимости через оператор :: Вафтарых, чисто логически, вот эта конструкция неверна в смысле задачи селектора. 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;} Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
EdgeAligned 53 27 апреля, 2023 Опубликовано 27 апреля, 2023 · Жалоба Потому что иначе если вариантов будет штук 10, то после срабатывания первого в списке, алгоритм будет впустую проверять по if оставшиеся 9, хотя уже ясно, что отработал именно if(...==1) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
const 1 27 апреля, 2023 Опубликовано 27 апреля, 2023 · Жалоба 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_х (те же), но объекты правда были реализованы по другому. Но видимо не до конца понял этот функционал раз возникают вопросы, вот поэтому я тут. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
xvr 12 27 апреля, 2023 Опубликовано 27 апреля, 2023 · Жалоба 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 порта уже это обеспечивает, тогда можете вызывать методы напрямую. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Сергей Борщ 119 27 апреля, 2023 Опубликовано 27 апреля, 2023 · Жалоба 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); } } примерно так. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
tonyk_av 31 27 апреля, 2023 Опубликовано 27 апреля, 2023 · Жалоба 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. Получается, по-умному выражаясь, высокий коэффициент повторного использования кода. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
EdgeAligned 53 27 апреля, 2023 Опубликовано 27 апреля, 2023 · Жалоба А быть может, следует почитать книжки и посмотреть видеоуроки поС+ +? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
const 1 27 апреля, 2023 Опубликовано 27 апреля, 2023 (изменено) · Жалоба Ух, ппц, нет слов. Писал сообщение около часа, нажал на какую-то ссылку, вернулся и все пропало. 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 за видео спс. Найду время и внимательно его посмотрю. Изменено 27 апреля, 2023 пользователем const Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
const 1 27 апреля, 2023 Опубликовано 27 апреля, 2023 · Жалоба 12 hours ago, tonyk_av said: От показанного мной выше клаccа AbstractTransport порождены, например, классы последовательных портов, работающие через UART, Ethernet и USB, при этом программа, читающая-пищущая в такой порт, абсолютно не знает, через какой физический интерфейс идёт обмен. В моей реализации объекты тоже ничего не знают о физ. интерфейсе. Данные передаются от объекта как в сом-порт так и, при наличии соединения, в сокет по сети такой же программе подключенной как клиент. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
tonyk_av 31 28 апреля, 2023 Опубликовано 28 апреля, 2023 · Жалоба 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. ); Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться