STM32. Можно ли читать/записывать единичные биты внутри регистров с помощью этих пользовательских структур?

Чтение и запись из регистров в STM32 кажется очень неуклюжим и неинтуитивным для чтения, например:

BRvalue = (SPI_CR1_BR & SPI1->CR1) >> SPI_CR1_BR_Pos; // Read current BR value
SPI1->CR1 &= ~SPI_CR1_BR; // Clear bits in BR position.
SPI1->CR1 |= 0b010 << SPI_CR1_BR_Pos; // Write new bits.

Исходя из программирования PIC с помощью XC8, у них есть эти хорошие структуры (внутри объединения, потому что иногда у них есть несколько структур, где они также определяют битовые поля как единичные биты или используют разные слова для именования битов и т. д.), которые позволяют читать и писать сингулярные биты внутри регистров. Мне понравился баланс простоты и читаемости, поэтому я написал, например, свой собственный для этого регистра:

typedef union {
    struct {
        volatile unsigned CPHA :1;
        volatile unsigned CPOL :1;
        volatile unsigned MSTR :1;
        volatile unsigned BR :3;
        volatile unsigned SPE :1;
        volatile unsigned LSBFIRST :1;
        volatile unsigned SSI :1;
        volatile unsigned SSM :1;
        volatile unsigned RXONLY :1;
        volatile unsigned CRCL :1;
        volatile unsigned CRCNEXT :1;
        volatile unsigned CRCEN :1;
        volatile unsigned BIDIOE :1;
        volatile unsigned BIDIMODE :1;
    };
} SPI1CR1bits_t;
SPI1CR1bits_t *SPI1CR1bits = (SPI1CR1bits_t*) &(SPI1->CR1);

Это позволяет выполнять операцию чтения/записи НАМНОГО более элегантным способом:

BRvalue = SPI1CR1bits->BR; // Read value
SPI1CR1bits->BR = 0b010; // Write value

Я протестировал его, и он, кажется, работает отлично. Он также работает для чтения битов состояния, которые меняет HW.

Однако я не нашел такой распространенной практики в STM32. Есть ли причина? С какими проблемами я могу столкнуться при чтении/записи с помощью этого метода? Есть ли вещи, которых мне следует опасаться?

Наверное, хорошо. Есть несколько способов определить аппаратные регистры, отображаемые в памяти: некоторые из них — ваш и stm32. Чтобы помочь тем из нас, кто не хочет копать, было бы полезно, если бы вы отредактировали свой вопрос и опубликовали определения stm32 для SPI_CR1_BR et. ал. которые полностью отражают то, что вы хотите заменить.

Craig Estey 23.06.2024 03:36

Одна вещь: если вы сопоставляете свою структуру непосредственно с аппаратным обеспечением, если вы измените два бита (например, str->SSI = 1; str->SSM = 0;), ее придется извлекать и сохранять дважды (например, tmp = reg; tmp |= SSI; reg = tmp; tmp = reg; tmp &= ~SSM; reg = tmp;). Доступ к регистрации может быть немного медленнее. Итак, вы можете удалить volatile из своей структуры и добавить поле: union { struct { unsigned SSI:1; unsigned SSM:1 } ; unsigned raw; }; Затем выполните: mystruct.raw = SPI_CR1_BR; mystruct.SSI = 1; mystruct.SSM = 0; SPI_CR1_BR = mystruct.raw; Затем вы извлекаете/сохраняете аппаратный регистр только один раз.

Craig Estey 23.06.2024 04:56

Некоторые аппаратные средства ожидают, что вы получите временную копию регистра, измените биты во временной переменной и запишите обратно все значение регистра. То есть аппаратное обеспечение может ожидать, что несколько бит будут изменены «атомарно» за одну запись в регистр. Зависит от данного аппаратного регистра и того, что сказано в документации.

Craig Estey 23.06.2024 04:59

Причина «почему», скорее всего, заключается в том, что предоставленный заголовок должен работать с несколькими компиляторами, которые не всегда могут вести себя одинаково. Microchip предоставляет свой собственный компилятор, и их заголовок должен работать только с ним. По крайней мере, для Cortex-M3/4 вы могли читать отдельные биты атомарно, используя побитовую обработку. Все ST обеспечивают более высокие уровни абстракции как в библиотеках Cube HAL или LL, так и в более старой (и лучшей IMO) SPL.

Clifford 23.06.2024 08:59

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

Clifford 23.06.2024 09:08

Расположение битовых полей не гарантируется стандартом и зависит от компилятора. Не используйте для этого битовые поля. Вместо этого создайте макросы GET_BIT и SET_BIT.

Fredrik 23.06.2024 09:28
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
108
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Использование битовых полей C будет работать лишь частично.

Основная проблема заключается в том, что компиляторы реализуют битовые поля, предполагая, что базовые целочисленные переменные ведут себя как обычная память, которую можно читать и записывать. Таким образом, они реализуют операции записи, сначала читая базовую целочисленную переменную, изменяя соответствующие биты и, наконец, записывая базовую целочисленную переменную. Это предположение справедливо не для всех регистров STM32.

Это работает, если все биты в регистре имеют тип rw, r или не используются. Но это не работает, если регистр содержит биты с другим поведением, например rc_w0, rc_w1, rc_r, t и т. д. В этих случаях запись в битовые поля в большинстве случаев приведет к непредусмотренным изменениям битов.

В справочных руководствах STM32 подробно описано поведение битов регистра, а в первой главе дано объяснение различного поведения битов.

Таким образом, использование битовых полей должно быть ограничено регистрами, содержащими только rw, r и неиспользуемые биты.

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

Однако это не должно влиять на регистры STM32, поскольку все они являются 32-битными целыми числами. Поэтому просто последовательно используйте uint32_t в качестве целочисленного типа данных и создайте отдельную структуру для каждого регистра, и вы будете в безопасности.

но не нашел такой распространенной практики в STM32. Есть ли причина?

IMO, потому что ARM обязала своих лицензиатов использовать стандарт кодирования во всем сопутствующем программном обеспечении (называемом CMSIS; к сожалению, это прозвище слишком часто встречается в рекламных материалах без надлежащего объяснения, поэтому его смысл был несколько потерян).

Итак, производители микроконтроллеров, такие как ST, производили, например, заголовки устройств, описывающие регистры и их биты/битовые поля согласно CMSIS. И CMSIS избегает битовых полей - именно по причинам, указанным @Codo в другом ответе (хотя, по моему мнению, основная причина - расплывчатое определение битовых полей в стандарте C, поскольку проблема с RMW в регистрах, не являющихся чисто памятью, не является битовым полем конкретно - вы будете укушены таким же образом, если будете использовать RMW через обычные операторы |= &= и даже использовать побитовую обработку на Cortex-M3/M4).

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

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

Более того, создание собственных битовых структур может быть подвержено ошибкам; легко забыть битовое поле или установить неправильный размер при копировании полей структуры. Я бы рекомендовал, если ваш компилятор позволяет, добавить, например. проверка утверждения, чтобы убедиться, что размер структуры или объединения больше ожидаемого. Очевидно, что эта проверка в любом случае не покроет всех ошибок, которые можно допустить при определении структуры.

// if using e.g. the STM32F76xxx series, the SPIx_CR1 registers can be accessed in 16-bit (half-words) or 32-bit (words) modes
// let'use a 16-bit structure
typedef struct {
    volatile unsigned CPHA :1;
    volatile unsigned CPOL :1;
    volatile unsigned MSTR :1;
    volatile unsigned BR :3;
    volatile unsigned SPE :1;
    volatile unsigned LSBFIRST :1;
    volatile unsigned SSI :1;
    volatile unsigned SSM :1;
    volatile unsigned RXONLY :1;
    volatile unsigned CRCL :1;
    volatile unsigned CRCNEXT :1;
    volatile unsigned CRCEN :1;
    volatile unsigned BIDIOE :1;
    volatile unsigned BIDIMODE :1;
} __attribute__((packed)) type_struct_register_SPICR1_bits;

// using C11 standard for assert
// checking if we added too many bits
_Static_assert(sizeof(type_struct_register_SPICR1_bits) != 2, "wrong size for type_struct_register_SPICR1_bits");

// union with the bits and the half-word view of the register
typedef union {
    type_struct_register_SPICR1_bits bits;
    uint16_t reg;
} type_union_16bits_register_SPICR1;
// using C11 standard for assert
// checking if the union has the expected size
_Static_assert(sizeof(type_union_register_SPICR1) != 2, "wrong size for type_union_16bits_register_SPICR1");

type_union_16bits_register_SPICR1 SPI1CR1;
// read without casting
SPI1CR1.reg = SPI1->CR1;
// if you need to make multiple assignments at once, you can do your assignments on the variable
SPI1CR1.bits.BR = 0b010;
SPI1CR1.bits.CPHA = 1;
// write without casting; commits the changes altogether
SPI1->CR1 = SPI1CR1.reg;

Другие вопросы по теме