Jump to content

    

Атомарная запись нескольких битовых полей структуры в Си

6 часов назад, Сергей Борщ сказал:

1 * (RCC_CFGR_PLLSRC & -RCC_CFGR_PLLSRC)

Спасибо Сергей !

Я не додумался до такого красивого способа сдвига значения до начала битовой маски !!!

Надо только наверное в макрос завернуть, чтобы не писать два раза одинаковую битовую маску ?

Share this post


Link to post
Share on other sites
7 часов назад, amaora сказал:

Если в объявлении нет слов volatile, то компилятор может свернуть запись нескольких битовых полей в одно чтение-модификация-запись. Не проверяли?

Объявлять регистры периферии без volatile - это стрелять себе в ногу.

Share this post


Link to post
Share on other sites
7 часов назад, AlexandrY сказал:

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

А у меня и есть по даташиту (только названия регистров изменены на адекватные).

 

7 часов назад, Сергей Борщ сказал:

Тоже вброшу свой вариант...

Вот мои макросы как раз делают то же самое

#define maskbits(src, mask)           ((src) & (mask))
...
#define presrv(src, sbits, rbits)     (maskbits((src) | (sbits), ~(rbits)))
...
#define fieldlsb(mask)                maskbits(mask, -(mask))
#define makefield(mask, val)          maskbits((val) * fieldlsb(mask), mask)
#define updatefield(reg, mask, val)   reg = maskbits(reg, ~(mask)) | makefield(mask, val)
...
#define assertpass(cond)              ((cond) ? 1 : 1 / (cond))
...
#define inrange(val, min, max)        ((val) >= (min) && (val) <= (max))
...

 

Пример макроса, устанавливающего в нужное поле периферийного регистра число от 1 до 16. Если число вне этого диапазона, компилятор сформирует предупреждение (или ошибку)

#define cpc_ClkOutDiv(x) updatefield(CPC->CLKOUTCR, CPC_CLKOUTCR_DIV, assertpass(inrange((x), 1UL, 16UL))*((x) - 1UL))

 

Пример макроса, устанавливающего или сбрасывающего флаг в периферийном регистре при выполнении или невыполнении условия, не используя оператора if() и связанного с ним инструкций перехода Bx

#define definebits(reg, bits, bcond) reg = maskbits(reg, ~(bits)) | maskbits(bits, -(bcond))

...

#define cpc_ClkOutCmd(s) definebits(CPC->CLKOUTCR, CPC_CLKOUTCR_OUTEN, (s))

...

cpc_ClkOutCmd(ENABLE); // установит бит CPC_CLKOUTCR_OUTEN в регистр CPC->CLKOUTCR

// а можно и в run-time
void CANCmd(const u32 cmd)
{
  definebits(CAN->CR1, CAN_CR1_EN, cmd);
  ...
}

 

Короче, выделил некоторое количество макросов (что-то около 50) для элементарных операций, зато не надо теперь каждый раз голову ломать и вспоминать, на сколько и куда мне чего надо сдвинуть, чтобы куда-то там попасть:blush:

 

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

Объявлять регистры периферии без volatile - это стрелять себе в ногу.

+1.

Share this post


Link to post
Share on other sites
1 minute ago, Arlleex said:

 зато не надо теперь каждый раз голову ломать и вспоминать.

Почему с ходу у меня сложилось как раз противоположное впечатление :mda:

Share this post


Link to post
Share on other sites
Только что, Forger сказал:

Почему с ходу у меня сложилось как раз противоположное впечатление

Я уже как-то притерся ко всем этим макросам и пользую их на ура.

Но тут уж кому как удобно:smile:

Share this post


Link to post
Share on other sites
41 minutes ago, Arlleex said:

Но тут уж кому как удобно:smile:

С этим не поспоришь ;)

Share this post


Link to post
Share on other sites
6 hours ago, jcxz said:

Объявлять регистры периферии без volatile - это стрелять себе в ногу.

Можно несколько объявлений иметь. Можно барьеры расставлять. Локальные копии делать.

То, что хочет ТС не получится с volatile. На каждую операцию с битовым полем будет и чтение и запись.

Share this post


Link to post
Share on other sites
Только что, amaora сказал:

То, что хочет ТС не получится с volatile. На каждую операцию с битовым полем будет и чтение и запись.

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

Я пишу под ARM Cortex-M3.

Под атомарностью в целом можно понимать несколько несвязанных между собой операций:

1. Атомарная работа с одним или несколькими регистрами (LDR/LDRD/STR/STRD).

2. Атомарная работа с переменными в разделяемой среде, например, в многопоточной вытесняющей ОС (команды LDREX/STREX и средства синхронизации самой ОС).

3. Атомарная работа с битовыми группами с точки зрения языка Си.

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

Share this post


Link to post
Share on other sites
17 минут назад, Arlleex сказал:

я хотел одной строкой Си-кода инициализировать несколько битовых полей структуры

Видимо многие "инициализировать" восприняли как "изменить".

Такое голые си кушают?

sPLL0СR = 
{
  12345,	// M
  0,		// __RESERVED__
  67,		// N
  1,		// EN
  .............
};

Еще в сях можно было с указанием имен полей структуры заполнять (в плюсах нельзя, поэтому не помню точно синтаксис, что-то вроде { .N = 67, .M = 12345, .EN = 1 } )

Share this post


Link to post
Share on other sites
Только что, Сергей Борщ сказал:

Видимо многие "инициализировать" восприняли как "изменить".

Еще в сях можно было с указанием имен полей структуры заполнять...

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

Правда, перед инициализатором нужно привести тип

typedef struct
{
  u32 a : 10;
  u32 b : 15;
  u32   :  7;
}sREG;

...

sREG a;
a = (sREG){.a = 5UL, b = 3UL};

 

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

Share this post


Link to post
Share on other sites
5 hours ago, Arlleex said:

Под атомарностью в целом можно понимать несколько несвязанных между собой операций:

То, что Вам нужно это называется оптимизация доступа к регистрам периферии, а не атомарность. У меня подобная задача возникала с обертками над GPIO. Несколько записей в один регистр можно свернуть в одну, но компилятор видя volatile в объявлении не может этого сделать. Но я пришел к тому, что вся эта возня не стоит затраченных усилий, лучше оставить неоптимальный но простой и надежный вариант. А при особой необходимости делать оптимизированные варианты кода на низком уровне, на примере GPIO это может быть чтение нескольких входов одной операцией.

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

Share this post


Link to post
Share on other sites
6 minutes ago, amaora said:

Во что это красиво завернуть в C, вот вопрос без ответа.

Да нефиг делать:

1. объявляем union, в которой объединяем структуру с битовыми полями и "сырое" (raw) значение регистра.

2. создаем локально экземпляр этого union

3. читаем нужный регистр в соотв. поле raw

4. меняем битовые поля в нашей структуре

5. пишем в регистр значение поля raw

Сам так делал, просто, эффективно, но требует описания всех этих union и структур.

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

 

 

Share this post


Link to post
Share on other sites
On 7/30/2019 at 8:14 PM, Arlleex said:

Может есть какой-нибудь трюк, все-таки позволяющий атомарно записать несколько битовых полей структуры?

Имеется, допустим,


// битовые представления регистров
typedef union
{
  struct
  {
    u32 M            : 15UL;
    u32 __RESERVE1__ :  1UL;
    u32 N            :  8UL;
    u32 EN           :  1UL;
    u32 SEL          :  1UL;
    u32 RDY          :  1UL;
    u32 __RESERVE2__ :  5UL;
  };
  u32 RAW;
}sPLL0СR;

...

// описание регистров периферии
typedef struct
{
  ...
  sPLL0СR СR;
  ...
}sPPL0;

 

Теперь я хочу одним махом каким-то образом сразу инициализировать поля M и N в структуре CR. Возможно ли такое средствами языка?

Ну, я бы не рекомендовал это для начинающих, но в IAR это вроде работает:

sPPL0 a;

a=(sPPL0){.CR.M=5,.CR.N=6};

Правда код там "оверкилл" - сперва затягивается вся структура целиком в регистры(служебную память), модифицируется нужное, а потом возвращается назад.

Share this post


Link to post
Share on other sites

artemkad, посмотрите мой ответ Сергею пару сообщениями выше. Повторюсь, этот код не сделает подобие a |= 5 << Mpos | 6 << Npos.

Share this post


Link to post
Share on other sites
8 hours ago, Arlleex said:

artemkad, посмотрите мой ответ Сергею пару сообщениями выше. Повторюсь, этот код не сделает подобие a |= 5 << Mpos | 6 << Npos.

И смысл такому уподабливаться? Тут-же хрен знает какой результат получится. Поля-то от старых значений не чистятся.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now