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

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

Набор команд для работы железяки с сервисной программной на ПК весьма обширный и требует различных структур при обмене. И нередки ситуации, когда ортогональные группы команд имеют одинаковый формат структур, но совершенно разное предназначение. А ломать голову "подходит ли мне эта структура для отправки такого-то запроса" совершенно не продуктивно. В случае с кучей псевдонимов интуитивно понятно, для какого типа запроса какую структуру заполнять.

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

enum eUID : u32 {
  // системные команды и запросы
  SYS_CMD_RST_MCU = 0, // сброс МК
  SYS_REQ_GET_INFO,    // запрос общесистемной инфы (версии ПО, железки и т.д.)
  ...
  
  // команды и запросы для работы с CAN
  WORK_CMD_CLEAR_RX_FIFO_CAN = 10000,
  WORK_CMD_CLEAR_TX_FIFO_CAN,
  WORK_REQ_RECV_CAN,
  WORK_CMD_SEND_CAN,
  ...

  // команды и запросы для работы с логами
  WORK_REQ_READ_FLASH_LOG,
  ...
};


В кадре обмена одним из первых обязательных полей служит идентификатор команды/запроса: eUID uid. Почти все вышеперечисленные UID (кроме WORK_CMD_SEND_CAN) весьма просты, т.е. не требуют аргументов, их структура одинакова, и, казалось бы, можно было их упрятать в одну какую-нибудь

struct sTxSimpleCmd {
  eUID uid;
  u32  crc;
};


Но в самом прикладном коде нужно напрягать голову и думать, а подходит ли эта структура для той или иной UID, особенно когда этих команд уже под сотню. Потому что некоторые UID требуют формата структур совершенно другого "наполнения", например, для WORK_CMD_SEND_CAN

struct sTxSendCAN {
  eUID      uid;
  sCANFrame frame;
  u32       crc;
};

и, как видно, уже образуются отдельные структуры sTxSimpleCmd и sTxSendCAN. Потом окажется, что sTxSendCAN по наполнению подходит какой-нибудь другой команде для другого сервиса, и что, в точке использования заводить эту структуру? Нет конечно. Вот я и создаю псевдонимы структурам, чтобы сразу по названию желаемой команды было понятно, какую структуру создавать

// команды и запросы без аргументов
typedef struct {
  eUID uid;
  u32  crc;
} sSysCmd, sSysReq,         // для UID из списка SYS_...
  sWorkCANCmd, sWorkCANReq, // для UID работы с CAN
  sWorkLogCmd;              // для UID работы с логами

// для команды WORK_CMD_SEND_CAN
struct sWorkCANSendCmd {
  eUID      uid;
  sCANFrame frame;
  u32       crc;
};


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

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


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

В 21.02.2024 в 22:03, Arlleex сказал:

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

Почему "нет конечно". Конечно да:)

Потому что потом, при необходимости изменить структуру sTxSendCAN не придётся искать все её псевдонимы и дорабатывать их.

Лучше уж сразу для каждой команды прописать подходящую структуру. Можно использовать плюсовый механизм traits. Типа такого:

// перечень всех команд
enum Command : uint32_t {
    Reset = 0,
    GetInfo = 1,
    SendData = 2,
};

// шаблон структуры, содержащей информацию о команде
template<Command cmd> struct CommandInfo;

// специализации шаблона для каждой команды (можно туда добавлять всё что угодно: тайм-аут обработки, структуры-параметры и так далее)
template <> struct CommandInfo<Command::Reset>
{
    struct Params {};
};

template <> struct CommandInfo<Command::GetInfo>
{
    struct Params {
        uint32_t param1;
    };
};

template <> struct CommandInfo<Command::SendData>
{
    struct Params {
        uint32_t param1;
        uint32_t param2;
    };
};

void send(...) { }

// и вот как будет выглядеть выбор нужной структуры для заданной команды:
void sendData()
{
    using CommandData = CommandInfo<Command::SendData>;
    using Params = typename CommandData::Params;
    Params params;
    send(Command::SendData, &params, sizeof(Params));
}

При добавлении новой команды для неё сразу же создаём специализацию шаблона, содержащего инфу о команде. И далее оперируем этой информацией (на обоих концах протокола обмена).

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


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

33 минуты назад, AHTOXA сказал:

При добавлении новой команды для неё сразу же создаём специализацию шаблона, содержащего инфу о команде. И далее оперируем этой информацией (на обоих концах протокола обмена).

А каковы преимущества специализаций тут? Ведь и в Вашем, и в моем варианте так или иначе появляются эти "отдельные" структурки:

struct CommandReset {
  ...
};

struct CommandGetInfo {
  ...
};

struct CommandSendData {
  ...
};

которые, если они одинаковые, можно описать один раз и раздать псевдонимы:

typedef struct {
  ...
} CommandReset,
  CommandGetInfo,
  CommandSendData;


Смотрите, насколько запись короче стала🙂 При этом не потеряла однозначностей: ну да, тут надо всегда "следить" за логической связью между структурой и командой. Зато представьте, а вдруг таких команд с одинаковым логическим смыслом будет не одна, не две, а три десятка:

enum Command {
  CMD_RESET_MCU,
  CMD_RESET_RTC,
  CMD_RESET_SDRAM,
  CMD_RESET_EXPANDER,
  ...
};


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

А в случае с обыкновенной структурой все это "схлопнется" в одну единственную структуру struct CommandReset {...}.

Ну, тут можно, конечно, возразить: разбить enum с командами на отдельные энумы, но вот только это весьма громоздко. Придется следить за согласованностью значений всех команд в разных местах - не красиво.

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

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


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

28 минут назад, Arlleex сказал:

А каковы преимущества специализаций тут? Ведь и в Вашем, и в моем варианте так или иначе появляются эти "отдельные" структурки:

Я как раз и агитирую за отдельные структурки. Чтобы для каждой команды было чётко указано, какова структура параметров. Всё это можно вынести в один *.h - файл, и получить самодокументированный код.

Кстати, и в этом подходе можно вынести одинаковые структуры "за скобки":

struct OneParam
{
	uint32_t param1;
}

template <> struct CommandInfo<Command::GetInfo>
{
    using Params = OneParam;
};

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


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

Если уж наделять структуры такими заумными возможностями, то, что мешает их надели уже неким "функционалом", некими действиями? 😉

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

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

Таким образом рождается некая иерархия команд: есть базовые, а есть те, которые построены на их основе и дают больше возможностей.

Такие команды должны уметь работать с итераторами (списками), чтобы не "перебирать" их вручную.

Другой вопрос - насколько это нужно в том или ином проекте. Чем плоха самая обычная структура с безымянным union и т.п? Зачем так усложнять? Ведь должна быть цель, оправдывающая такие усложнения )

 

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


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

24 минуты назад, Forger сказал:

Другой вопрос - насколько это нужно в том или ином проекте. Чем плоха самая обычная структура с безымянным union и т.п? Зачем так усложнять? Ведь должна быть цель, оправдывающая такие усложнения )

То, что вы описали, почти всегда не нужно. А нужно - чёткое и консистентное описание протокола обмена. Можно составить документ, таблицу, типа

код команды - структура параметров запроса - структура параметров ответа.

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

А можно сделать самодокументирующийся код, как я описал выше. Там всё сразу в одном месте. И никак не получится при добавлении команды "забыть" добавить реализацию шаблона. Будет ошибка компиляции. Меня такой подход многократно выручал. Особенно он удобен, когда приходится возвращаться к проекту после длительного перерыва, и всё уже полностью выветрилось из головы.

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


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

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

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

Только что, AHTOXA сказал:

А можно сделать самодокументирующийся код, как я описал выше.

Меня в этом подходе устраивает все, кроме раздувания специализаций:

template <> struct CommandInfo<Command::GetInfo>
{
    using Params = OneParam;
};


Вот было бы здорово, если в этих скобках <> помимо Command::GetInfo можно было через запятую добавлять и другие команды.

Иначе получается для каждой команды нужно писать одну и ту же 4-строчную текстулю. Это единственный минус.

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


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

7 минут назад, Arlleex сказал:

Вот было бы здорово, если в этих скобках <> помимо Command::GetInfo можно было через запятую добавлять и другие команды.

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

struct CommandInfoTwoParam
{
    struct Params {
        uint32_t param1;
        uint32_t param2;
    };
};

template <> struct CommandInfo<Command::SendData> : public CommandInfoTwoParam {};

Это почти идеально: лишнее дублирование (почти) отсутствует, но тем не менее каждая команда явно описана.

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


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

16 минут назад, AHTOXA сказал:

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

Нет-нет, я не об этом🙂 Я об этом (это не скомпилится, но так это выглядит в моей голове):

template <> struct CommandInfo<Command::Reset, Command::GetInfo, Command::SendData>
{
    using Params = OneParam;
};


Т.е. одному телу шаблона назначить сразу 3 команды списком, через запятую. И пусть этим самым знает, что для всех трех этих команд нужно инстанцировать структуру OneParam.

А то щас получается один и тот же шаблон 3 раза - просто для разных команд. Глазами потом бегать по описаниям - такое себе🙂

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


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

Продолжая на эту же тему...

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

Пример: комп шлет команду "вычитать CAN-сообщения из входящего FIFO". И железяка должна выгрести FIFO, и сколько там смогла выгрести - столько и отправить в ответном кадре.

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

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


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

On 3/1/2024 at 12:44 PM, Arlleex said:

А можно ли в C++ каким-то образом создать что-то наподобие FAM в Си?

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

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


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

24 минуты назад, dimka76 сказал:

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

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

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


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

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

Компилятор конечно сыплет предупреждения

Я ставлю массив размером 0. Тогда предупреждений нет.

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


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

37 минут назад, EdgeAligned сказал:

Указатель на буфер?

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

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

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

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


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

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

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

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

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

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

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

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

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

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