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

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

Спасибо на добром слове. ) Обоих застал и даже успел с zltigo обменятся любезностями. Спорить люблю. Но предпочитаю побеждать не умением спорить, а силой знаний. )

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


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

12 hours ago, Kabdim said:

Бит-филды мне по прежнему не нравятся большим

Прочитал всё изложенное всеми участниками. Да, я с таким не сталкивался. Битовые поля всегда определял классически в столбик

struct {
  uint32_t a     : 1;
  uint32_t b     : 1;
  uint32_t c     : 4;
  uint32_t rsrvd : 26;
}BitField;

Проблем ни разу не возникало, поэтому искренне удивился, что у вас они появились. К сожалению, действительно есть такое, что если что=то в своё время не получилось, то потом возникает на долгое время недоверие. Тем не менее, когда у вас появится свободное время и желание, рекомендую всё же разобраться с битовыми полями. Это очень мощное средство управления данными, предоставляемое языком. И совершенно неправильно не использовать его) Этот подход удобен хотя бы для прямого отображения на регистры управления периферией. Или вот, на глаза попался драйвер SD-карты, где битовые поля используются в достатке


    union spi_r2_response_cmd_t {
        __packed struct fields_t {
            uint16_t card_is_locked : 1;
            uint16_t wp_erase_skip : 1;
            uint16_t error : 1;
            uint16_t cc_error : 1;
            uint16_t card_ecc_failed : 1;
            uint16_t wp_violation : 1;
            uint16_t erase_param : 1;
            uint16_t out_of_range : 1;
            uint16_t in_idle_state : 1;
            uint16_t erase_reset : 1;
            uint16_t illegal_command : 1;
            uint16_t com_crc_error : 1;
            uint16_t erase_sequence_error : 1;
            uint16_t address_error : 1;
            uint16_t parameter_error : 1;
            uint16_t : 1;
        }
        fields;
        std::array<uint8_t, 2> data;
        uint16_t word;
    };

На классических масках такое делать неудобное (ИМХО).

12 hours ago, Arlleex said:

Это Вы еще @zltigo не застали,

Эх, было время)))

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


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

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

Или вот, на глаза попался драйвер SD-карты, где битовые поля используются в достатке...

А вот я не очень оценил битовые поля при описании регистров периферии:to_take_umbrage: Тот или иной подход всяко диктуется стремлением повысить качество кода в плане лексической читаемости. Вот для описания битов периферии существует два основных способа: за-define-нные имена битов/масок и, собственно, битовые поля структур. По-хорошему, эти способы должны сосуществовать как бы независимо, на мой взгляд. Т.е. для полноценной работы необходимо и достаточно только первого или второго способа описания. Если перечислять только минусы этих способов, то у способа с define-ми это потенциальная громоздкость и глобальность ("замусоривание" глобального пространства имен) идентификаторов, например, DBGMCU_IDCODE_DEV_ID; у способа с битовыми полями "видимым" глобальным символом было бы только определение DBGMCU, а внутреннее устройство "наружу" не видно: DBGMCU.IDCODE.DEVID. Однако, я поднимал вопрос относительно недавно, как изменить несколько полей в структуре через битовые поля одновременно. В итоге понял, что или инициализировать промежуточную структуру с последующей записью исходной (что с синтаксической стороны не очень красиво), или держать рядом все те же старые добрые define-ы, а структуры переименовать в union, в которых будет битовое описание + регистр в целом. Но тогда мы потеряем все преимущество битовых полей (а их основное преимущество в сокрытии полезных идентификаторов регистров). То, что в битовых полях не нужно думать куда что двигать и насколько - не аргумент, т.к. легким движением руки прекрасно пишутся тривиальные макросы для облегчения и сведения в ноль такой необходимости. Поэтому, на сегодняшний день, битовые поля у себя использую лишь для удобочитаемой реализации каких-либо частей в самопальных протоколах и (реже) бизнес-логике драйверов.

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


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

17 minutes ago, Arlleex said:

А вот я не очень оценил битовые поля при описании регистров периферии:to_take_umbrage: Т

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

struct ChannelSetupQuick {
        union Ctrl {
            struct {
                uint32_t reserved0           : 5;
                uint32_t isCircularMode      : 1;
                uint32_t isPeriphIncEnabled  : 1;
                uint32_t isMemIncEnabled     : 1;
                uint32_t reserved1           : 24;
            }fields;
            uint32_t ccr_reg;
            Ctrl() {
                fields.isMemIncEnabled = 1;
                fields.isPeriphIncEnabled = 0;
                fields.isCircularMode = 0;
            }
        }ctrl;
        std::uint16_t   dataCount;
        std::uint32_t   peripheralAddress;
        std::uint32_t   memoryAddress;
        ChannelSetupQuick()
            : dataCount(0)
            , peripheralAddress(0)
            , memoryAddress(0) {}
    };

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

    static const uint32_t TIEF_ALL_CH_MASK = ( 1UL << ( 4 * 0 + 3 ) )
                                             | ( 1UL << ( 4 * 1 + 3 ) )
                                             | ( 1UL << ( 4 * 2 + 3 ) )
                                             | ( 1UL << ( 4 * 3 + 3 ) )
                                             | ( 1UL << ( 4 * 4 + 3 ) )
                                             | ( 1UL << ( 4 * 5 + 3 ) )
                                             | ( 1UL << ( 4 * 6 + 3 ) );

Хотя признаюсь, что данный вариант не очень хорош в плане понимания.

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


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

51 minutes ago, Arlleex said:

битовые поля у себя использую лишь для удобочитаемой реализации каких-либо частей в самопальных протоколах и (реже) бизнес-логике драйверов

У меня вообще если в коде где-то есть битовые поля, то это означает, что этот код я откуда-то взял ☺

По собственной воле никогда битовые поля не использовал и использовать не собираюсь. Макросы и булева логика намного удобней. А с некоторыми компиляторами (вроде sdcc) вообще веселуха с битовыми полями…

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


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

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

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

Поэтому такую структуру передаю целиком, как массив. 

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


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

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

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

Да, да, именно читаемость, ради нее и стоит использовать битовые поля. Да и не надо их так бояться )) 

Если не писать все поля в одну строку, то число ошибок c ними стремиться к нулю ))

 

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


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

1 час назад, Forger сказал:

Да, да, именно читаемость, ради нее и стоит использовать битовые поля. Да и не надо их так бояться...

Читаемость да, но

Цитата

...если не писать все поля в одну строку, то число ошибок c ними стремиться к нулю...

есть такие регистры в периферии, в которые нельзя писать раздельными командами. И тут явно придется юзать define-ы битов. А значит для унификации их все (все биты периферии) придется определить (описать) в заголовочном файле (или взять готовый), таким образом дублировав работу по определению структур с битовыми полями и define-альтернатив. Получается, бессмысленная какая-то работа, проще оставить что-то одно, и пока define-ам нет альтернатив по применимости и универсальности, для описания битов периферии самым гибким вариантом остаются именно они.

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


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

3 часа назад, Arlleex сказал:

у способа с define-ми это потенциальная громоздкость и глобальность ("замусоривание" глобального пространства имен) идентификаторов, например, DBGMCU_IDCODE_DEV_ID; у способа с битовыми полями "видимым" глобальным символом было бы только определение DBGMCU, а внутреннее устройство "наружу" не видно

А зачем пользоваться #define? Я в таких случаях всегда использую список битовых полей определённых через enum {} внутри структуры. И нет "замусоривания глобального пространства".

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


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

20 minutes ago, Arlleex said:

Читаемость да, но

есть такие регистры в периферии, в которые нельзя писать раздельными командами. И тут явно придется юзать define-ы битов. А значит для унификации их все (все биты периферии) придется определить (описать) в заголовочном файле (или взять готовый), таким образом дублировав работу по определению структур с битовыми полями и define-альтернатив. Получается, бессмысленная какая-то работа, проще оставить что-то одно, и пока define-ам нет альтернатив по применимости и универсальности, для описания битов периферии самым гибким вариантом остаются именно они.

Для регистров периферии уже существуют готовые h-файлы от производителя камня. Вполне годные для всей низкоуровневой "ботвы".

Я лишь создал вокруг этих регистров свои библиотеки на плюсах, которые обобщают таймера, ацп, can. Т.е. вся "тряхомуть" с дейфайнами отлажена в отдельных файлах, зафиксена в базе и забыта на время что там внутри.

Битовые поля это уже для юзер-кода, так сказать (протоколы там всякие, где это реально оч удобно). А дефайны в юзер коде лишь его захламляют. Избегаю максимально.

 

зы. Опять-таки что не тема, то разговор уходит в личную вкусовщину в том или ином виде :boredom:

Но суть такова - битовые поля в прямых руках не создают никаких проблем.

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

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


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

3 часа назад, haker_fox сказал:

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


    static const uint32_t TIEF_ALL_CH_MASK = ( 1UL << ( 4 * 0 + 3 ) )
                                             | ( 1UL << ( 4 * 1 + 3 ) )
                                             | ( 1UL << ( 4 * 2 + 3 ) )
                                             | ( 1UL << ( 4 * 3 + 3 ) )
                                             | ( 1UL << ( 4 * 4 + 3 ) )
                                             | ( 1UL << ( 4 * 5 + 3 ) )
                                             | ( 1UL << ( 4 * 6 + 3 ) );

Хотя признаюсь, что данный вариант не очень хорош в плане понимания.

Откройте для себя enum! Вместо 0, 1, 2, 3, ... И будет и читаемо и не менее оптимально.

 

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

И тут явно придется юзать define-ы битов.

Честно говоря - совершенно не понимаю вашей тяги к #define...  :unknw:

Классическая структура с бит-картами у меня:

struct FaultState {
  enum { //список причин аварий
    GF_swUL,          //авария драйвера нижнего силового ключа фазы U
    GF_swUH,          //авария драйвера верхнего силового ключа фазы U
    GF_swVL,          //авария драйвера нижнего силового ключа фазы V
    GF_swVH,          //авария драйвера верхнего силового ключа фазы V
    GF_swWL,          //авария драйвера нижнего силового ключа фазы W
    GF_swWH,          //авария драйвера верхнего силового ключа фазы W
    GF_sw,            //общая авария от платы драйверов (объединение всех GF_sw..)
    GF_emergency,     //нажатие кнопки аварийной остановки PIN_EMERGENCY
    GF_amperU,        //авария по току силовых ключей фазы U
    GF_amperV,        //авария по току силовых ключей фазы V
    GF_amperW,        //авария по току силовых ключей фазы W
    GF_amperDc,       //авария по току батареи
    GF_pedal,         //авария датчиков педалей (газа или тормоза)
    GF_rotor,         //авария датчика углового положения ротора
    GF_remPrnd,       //авария дистанционного управления направлением
    GF_remReduAccBrk, //авария дистанционного управления ослаблением разгона и торможения
    GF_remReduAcc,    //авария дистанционного управления ослаблением разгона
    GF_remReduBrk,    //авария дистанционного управления ослаблением торможения
    GF_voltDcL,       //авария по напряжению батареи, ниже допустимого
    GF_voltDcH,       //авария по напряжению батареи, выше допустимого
    GF_volt,          //аварии по каналам напряжения (VOLT_CH_MON_n штук)
    GF_temp = GF_volt + VOLT_CH_MON_n, //аварии по температурным каналам (TEMP_CH_n штук)
    GF_n = GF_temp + TEMP_CH_n,
    GF_hall = GF_rotor
  };
  enum {
    LATCH_DEF = 1ull << GF_swUL | 1ull << GF_swUH | 1ull << GF_swVL | 1ull << GF_swVH | 1ull << GF_swWL | 1ull << GF_swWH |
    1ull << GF_sw | 1ull << GF_emergency};
  u64 avar;       //биткарта текущих состояний аварий
  u64 avarMsk;    //биткарта текущих состояний аварий с наложенной (атомарно) маской
  u64 msk;        //биткарта маски аварий; биты==0 - игнорировать соотв.сигнал аварии
  u64 latch;      //биткарта защёлкиваемых аварий (каждый бит ==1 - соотв.авария должна защёлкиваться до сброса)
...
};

Как видно - #define-ами и не пахнет.  :unknw:

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


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

23 минуты назад, jcxz сказал:

Я в таких случаях всегда использую список битовых полей определённых через enum {} внутри структуры...

Можно пример? Я понимаю (примерно) что Вы имеете ввиду, но не понимаю где там гибкость как у define-ов.

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


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

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

Можно пример? Я понимаю (примерно) что Вы имеете ввиду, но не понимаю где там гибкость как у define-ов.

Я же привёл...

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


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

6 hours ago, jcxz said:

Откройте для себя enum! Вместо 0, 1, 2, 3, ... И будет и читаемо и не менее оптимально.

Да можно как угодно... Я не фанатик только одного или двух способов)

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


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

Прошу напутствия.

Решил я, значит, немного переделать свой парсер SLCAN на C++. Объявил пространство имен SLCAN, в нем определил класс cSlave. Объекты этого класса представляют из себя некие интерфейсы-преобразователи UART-CAN. Периодически я буду вызывать метод, который отвечает за чтение буфера UART, парсинг, и выполнение команд, закодированных в этом буфере сообщений. Интересно следующее: SLCAN дает способ своими UART-сообщениями управлять как самой транзакцией (UART <-> CAN), так и некоторыми настройками самого адаптера (например, задать битрейт CAN и т.д.). Я бы хотел отвязать класс от аппаратуры, вот прям совсем. Одно дело, когда внутри одного МК реализуется один мост UART-CAN, и совсем другое - когда несколько или больше. И вот здесь самое интересное: реализация методов класса (например, когда парсер увидел команду установки скорости CAN) должна вызвать пользователем предоставленный обработчик, который эту самую скорость установит для нужного периферийного CAN в МК. Ничего для себя понятнее, чем добавить в класс указатели на функции (да-да, старый добрый callback), я не придумал. Т.е. что-то типа

namespace SLCAN {
  class cSlave {
    public:
      cSlave(bool (*const setBaudRate)(u32)) : // для примера только один метод
             setBaudRate(setBaudRate) {}
     ~cSlave() {}
    
    void parseUART(...) {                      // парсер всего, что прилетает в UART (абстрактно)
      ...
      if(cmd == SET_BAUDRATE)
        setBaudRate(br);                       // вызовется для нужного CAN-модуля в МК
    }
    
    private:
      bool (*const setBaudRate)(u32 br);
  };
}

bool SetBaudRateCAN1(u32 br) {                 // функция установки скорости на CAN1
  ...
}

int main(void) {
  SLCAN::cSlave slcan(SetBaudRateCAN1);        // создали SLCAN-сокет, привязали к нему аппаратно-зависимые вещи
  
  while(1) {
    ...
    slcan.parseUART();                         // обслуживаем стек SLCAN
  }
  
  return 1;
}


Т.е. получилось некое подобие callback()-ов на как будто статические методы класса. Может, это колхоз? А как надо тогда?

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


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

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

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

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

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

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

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

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

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

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