Pull to refresh

Comments 28

Я очень извиняюсь, но вы изобрели CMSIS. Там все уже есть

В CMSIS всё это есть, но не в том виде как это представлено в статье.

В периферийных библиотеках для PIC16/PIC24 структуры с регистрами описаны именно битовыми полями. Там это имеет явную пользу, так как ядро умеет атомарно манипулировать битами.

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

Было бы круто если регистры периферии были представлены в виде структур с методами из с++ хотя-бы для инициализации, но так почему-то никто не делает.

У этого способа еще есть недостаток: когда нужно поменять значения нескольких полей в одном регистре, компилятор сгенерирует отдельные операции чтения/модификации/записи для каждого поля, вместо одной когда оперируем целым регистром

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

И если уж на то пошло, то неатомарые операции с битами в памяти... Ну вы поняли...

А вот избавление от потенциальных граблей и ошибок класса "опечатка" или "copy-paste" это безусловное благо. Не просто же так в том же Linux настоятельно рекомендуют использовать подсистему regmap для работы с регистрами. Проще контролировать это в одном месте, чем раскиданным по всему коду. Особенно когда это часть компилятора. Вопрос же одновременного изменения нескольких полей (как правильно пишут немного дальше) решается union'ом.

А вот если говорить о недостатках которые серьезнее - то это в первую очередь никакая переносимость битовых полей между BE и LE системами. Приходится городить множество #ifdef'ов. Потому для кода, специфичного для процессора оно хорошо и допустимо, а вот для переносимого... Стоит подумать.

Ну, и уж совсем в качестве оффтопика - мне безумно не хватает типов в стиле le16_t и be16_t. Да с поддержкой битовых полей в них. Как и реально переносимых атрибутов big_endian и little_endian для структур и объединений. В лучшем случае эти моменты есть у конкретно взятого компилятора, но это очень не хорошее решение. В первую очередь в плане переносимости.

Рабочий тестовый проект наверно можно было-бы выложить на файлообменник какой нибудь... или git пока живой еще...

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

Вы чуть чуть недоизобрели этот велосипед. Следующий шаг это внутри структуры каждого регистра сделать union, где объединить доступ по байтам, словам или по двойным словам. Тогда будет уже точно, как в либах от TI или NXP.

Зачем вы используете DMA на приеме, если принимаете по 1 байту?

rDMA2->HIFCR.CHTIF5 = 0; // Clear Half-transfer interrupt flags
rDMA2->HIFCR.CTCIF5 = 0; // Clear Transfer complete interrupt flags
rDMA2->HIFCR.CTEIF5 = 0; // Clear Transfer error interrupt flags
rDMA2->HIFCR.CDMEIF5 = 0; // Clear Direct mode error interrupt flags

Флаги сбрасываются записью 1, а не 0.

Во что эта вот конструкция превращается компилятором не смотрели? В классическом варианте сброса флагов, вроде такого:

DMA2->HIFCR = (DMA_HIFCR_CDMEIF5|DMA_HIFCR_CFEIF5|DMA_HIFCR_CHTIF5|DMA_HIFCR_CTCIF5|DMA_HIFCR_CTEIF5);

Все битовые операции происходят еще на стадии сборки и в коде только три инструкции будет, LDR адреса, MOV значения в регистр и затем STR в регистр.

Спасибо, действительно ошибся со сбросом флага.

Ваш вариант конечно выполниться гораздо быстрее, всего 3 инструкции.

 74       	DMA2->HIFCR = (DMA_HIFCR_CDMEIF5|DMA_HIFCR_CHTIF5|DMA_HIFCR_CTCIF5|DMA_HIFCR_CTEIF5);
08026682:   ldr     r3, [pc, #84]   ; (0x80266d8 <UART1_Received_DMA+140>)
08026684:   mov.w   r2, #3840       ; 0xf00
08026688:   str     r2, [r3, #12]

С битовыми полями на одну операцию 4 инструкции + если операции производить по очереди, в данном случае 4 то 4*4 = 16 инструкций. Что гораздо медленнее.

 67       	rDMA2->sHIFCR.CHTIF5  = 1; // Clear Half-transfer interrupt flags
08026652:   ldr     r2, [pc, #132]  ; (0x80266d8 <UART1_Received_DMA+140>)
08026654:   ldr     r3, [r2, #12]
08026656:   orr.w   r3, r3, #1024   ; 0x400
0802665a:   str     r3, [r2, #12]

Почему у вас каждое поле volatile вместо обычной структуры и

typedef volatile MyStructImpl MyStruct; ?

Более того, можно так

typedef volatile struct {
	int x;
} hehe;

Да, можно и так, как бы если использовать volatile для структуры, действие спецификатора будет распространяться на все содержимое структуры (нам это и нужно). Если этого не нужно, то можно применить спецификатор volatile к отдельным элементам структуры.

Давно уже не использую битовые структуры для работы с регистрами.
В целом себя не оправдывает.
Когда регистры были 8-и битными, простыми и редкими, в 8-и битниках типа Z86 - это ещё что-то упрощало.
Но теперь главный источник ошибок не в том, что бит не туда попал, а в том, что этот бит делает не то что надо.
У производителей теперь видна гибридизация. BSP пишут команды, где хаотично могут сочетаться и битовые структуры и макросы с масками, смещениями и позициями битов.
Это проистекает, думается, из-за предельной запутанности регистров периферии и методами обращения с ними.

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

Много времени уходит на то чтобы удостовериться в правильности позиции бита. Для это всегда открыт мануал на чип. Готовым хидерам от производителя даже доверять нельзя. Они хидеры пишут оптом для всех ревизий и типов сразу заворачивая в каскады макросов. Особо тестированием не заморачиваются.

Размещая в битовых структурах всегда есть риск ошибиться с позицией. Т.е. применение битовых структур требует последующего тестирования. Объявление же битов явно по их позиции значительно упрощает написание кода. Поскольку можно проигнорить и не объявлять ненужные биты, можно легко проверить по мануалу правильность позиции бита, и можно легко поменять позицию бита.
А то как у автора позиция бита объявляется в комментарии - это самый коварный способ выстрелить себе в ногу. Нет ничего опаснее забытых и неверных комментариев.

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

Да и STM32 не скоро ещё вернётся в продажу. С начала кризиса нам пришлось поменять три семейства микроконтроллеров разных производителей по причине прекращения поставок. Метод объявления и использования битов явно и по номерам себя вполне оправдывает, он устойчив к вариабельности платформ, компиляторов, версия языков, отладчиков, библиотек и проч.

"STM32, в котором все регистры имеют 32 бита. "

Это совсем не так.

А какие регистры в STM32 не имеют 32 бита?

Тут тонкость есть. Да, у того таймера предделитель например в референсе указан как 16 битный:

Но в той же карте регистров он обозначен как 32 бита, у которого 16 старших зарезервировано. Так что да, все регистры 32 битные.

С другой стороны, никто не запрещает использовать разные типы адресации (байт, полуслово, слово) при обращении к регистру. Для STM32 это не имеет значения и будет выполнено за одно и тоже время.

Можете пояснить за работу DMA в данном случае? Я никак не пойму. насколько такой способ переносим, а сколько тут нарушение стандарта и MISRA C, как тут. Насколько я понимаю, DMA просто берет память по адресу и копирует её 1к1 по другому адресу, никакого переупорядочивания не происходит. То есть для того, чтобы всё было как задумано и по стандарту (пусть и мимо misra), надо, чтобы соблюдались следующие условия:

  1. структура данных порта

utypedef union portStruct {uint32_t val;

struct {uint32_t pin1:1;

uint32_t pin2:1;

...

uint32_t pin32:1;

};

} portStruct_t;

union для "обмана" компилятора типобезопасностью.

  1. sizeof(uint32_t) == sizeof(portStruct_t)

  2. \_AlignOf(uint32_t) == \_AlignOf(portStruct_t)И всё

И все равно мы можем получить неправильное поведение из-за endianess, которую на этапе компиляции не получишь, если для платформы не определен <endianness.h>. Правильно ли я понимаю, что если мы используем структуру выше как источник копирования (16 битный вариант, с нулями в последних 16 битах), а GPIO->ODR в качестве цели, она ляжет "задом наперед", поскольку m3 le? Является ли использование меппинга структуры UB по стандарту? Что говорит про такой код MISRA C?

Случайно отклонил комментарий по поводу:

	do{
  ...
	} while(0U);

Здесь происходить сброс флага чтением регистров DR и RXNE перед включением запроса DMA. Этот кусок кода я взял из библиотеки HAL, когда изучал как она устроена.

У stm есть прерывание юарт по символу (любому, в ТЧ окончания строки). Благодаря этому прерыванию можно не проверять каждый байт

Действительно, только лет существует CMSIS и вы думаете что никто не догадальься использовать битовые поля в структурах? А знаете почему?

1) они запрещены во многих стандартах таких как misra и т.п.

2) теперь у вас не uin32_t для накпример регистра SR а структура... Как мне из него или в него записать сразу несколько бит (реторический вопрос, хочу на ответ посмотреть)? Учитывая что вы используете FreeRTOS да и в принципе еще есть прерывания... обычно нужно сначало сформировать конкретную маску (например для установки битов) и одной инструкцией биты регистра установить. В вашем методе это не возможно.

П.С. CMSIS давно внедрили макросы для ститывания бита/битов из регистра, на читабельности никак не сказывается.

1) они запрещены во многих стандартах таких как misra и т.п.

Можете привести ссылки на правила в этих стандартах? Нагуглить такое не получается

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

2) Как на счет атомарного доступа к нескольким битам? :) Только не говорите что обернете все это через union...

они запрещены во многих стандартах таких как misra 

конкретно в MISRA запрета нет, есть лишь требования (описание правил MISRA от IAR, 2004г):

  • Правило 108: все члены структуры должны быть полностью специфированы;

  • Правило 111: тип битового поля только unsigned int или signed int;

  • Правило 112: битовое поле типа signed int должно иметь длину как минимум 2 бита;

  • Правило 113: Доступ к полям структуры или объединения только по именам;

А какие ваши доказательства?

CMSIS давно внедрили макросы для ститывания бита/битов из регистра, на читабельности никак не сказывается

не правда. CMSIS стандарт который стандартизирует описание регистров и полей в них для микроконтроллеров с ядрами ARM. Как правило это описание предоставляется в виде файла SVD, который уже потом производитель (как правило) конвертирует в заголовочный файл. На сайте ARM даже утилитка где-то обитает для конвертации. То что некоторые производители добавили туда несколько макросов, не означает что это включено в стандарт.

Так-то оно да, но статься исключительно об STM32... сответственно я и имею ввиду исходники CMSIS от ST ( а не LL, SPL или HAL)

хорошо, остановимся только на stm32. Опять же, макросы даже если и есть в исходниках (а в этом заголовке не то что макросы, там иногда отсутствует описание некоторых блоков периферии), сами ST не используют у себя. Например:

  • у STM32 для серий L0 и F0 есть замечательная вещь - snippets. Вот там примеры использования периферии только в формате использования CMSIS. но опять же, почему-то без использования этих макросов;

  • LL, кстати позиционируется как совсем уж низкоуровневый доступ. Функция в итоге превращается в строчку прямой модификации регистра. Но опять почему-то без макросов.

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

(сарказм on)ну да, действительно.. очень много и не понятно (сарказм off):

/** @addtogroup Exported_macro
  * @{
  */
#define SET_BIT(REG, BIT)     ((REG) |= (BIT)) 
#define CLEAR_BIT(REG, BIT)   ((REG) &= ~(BIT)) 
#define READ_BIT(REG, BIT)    ((REG) & (BIT)) 
#define CLEAR_REG(REG)        ((REG) = (0x0)) 
#define WRITE_REG(REG, VAL)   ((REG) = (VAL)) 
#define READ_REG(REG)         ((REG)) 
#define MODIFY_REG(REG, CLEARMASK, SETMASK)  WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))
#define POSITION_VAL(VAL)     (__CLZ(__RBIT(VAL))) 

П.С. Сниппиты к сожалению устаревшие

<занудство>

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

</занудство>

Сниппиты к сожалению устаревшие

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

Полностью поддерживаю автора публикации и согласен с ним.

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

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




Sign up to leave a comment.

Articles