Отображение структуры битового поля в энергозависимый регистр

Я пишу приложение, которое взаимодействует с рядом регистров, определенных в VHDL. Регистры имеют ширину 32 бита и распределены по группам. Мне предоставляется базовый адрес группы и 32-битные смещения для каждого члена группы. Вот пример одной группы, регистра внутри группы и структуры регистра.

Группа 1 | базовый адрес | смещение | порт_данных

data_port | alt_u32 data0: 12; | alt_u32 data1: 1; | ....

В настоящее время ввод-вывод обрабатывается с использованием следующей структуры битового поля:

typedef struct
{
   uint32_t   data0 : 12;
   uint32_t   data1 : 1;
   ...
}volatile data_port;

и изменяя поля с помощью указателя на адреса,

data_port *const p_data = (data_port *)0xc006380;

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

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

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

#define PeripheralBase ((uint32_t volatile *)BASE)

or

uint32_t volatile *const peripheral_base  = (uint32_t *) BASE;

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

Что-то вроде,

static inline uint32_t struct_to_uint(data_port *data)
{
   return data->data0
        + ((uint32_t)data->data1 << 12)
        + ((uint32_t)data->data2 << 13)
        + .....;
}

Я не уверен, что синтаксис правильный, но идея состоит в том, чтобы сдвинуть значения, не беспокоясь о компиляторе или платформе. Это так с тех пор? Есть ли проблемы с переносимостью при таком подходе?


person dsell002    schedule 09.10.2013    source источник
comment
Вам также следует взглянуть на упакованный атрибут, если вы читаете данные непосредственно из области памяти, как в data_port *const p_data = (data_port *)0xc006380;. Если компилятор изменится (например, на архитектуру x64), он может добавить байты заполнения для оптимизации доступа к членам структуры.   -  person fvdalcin    schedule 10.10.2013
comment
Подумайте о том, чтобы убрать volatile из typedef. Поместите это в volatile data_port *const p_data = (volatile data_port *) 0xc006380;. Структура является изменчивой только тогда, когда находится по этому специальному адресу, а не когда она скопирована в какое-то другое место. Попробуйте uint32_t вместо alt_u32.   -  person chux - Reinstate Monica    schedule 10.10.2013
comment
@chuck, хотя я понимаю, что структура не представляет собой изменчивый адрес, ее единственное использование связано с изменчивым регистром. Он используется для явного указания, что все указатели этого типа будут указывать на изменчивый адрес. Я согласен с использованием uint32_t. alt_u32 указан в API, который я использую, но если я говорю о переносимости, то с тех пор используются стандартные марки.   -  person dsell002    schedule 10.10.2013
comment
@ dsell002 Просто мысль: не имеет прямого отношения к вашему сообщению, но иногда к таким побитовым регистрам требуется доступ сразу, чтобы получить / установить согласованный образец. Таким образом, объединение битовой структуры с помощью uint32_t позволяет производить выборку / установку всего регистра и сохранять результат в другой энергонезависимой переменной типа data_port для последующих операций чтения и записи битового поля. Кстати: я согласен с вашим подходом на другом уровне.   -  person chux - Reinstate Monica    schedule 10.10.2013
comment
@chux yep, объединение беззнакового int и 32-битной структуры определенно является вариантом, и я вижу, что он часто используется для таких вещей, как копирование меньших типов, char и short, в слово или длинное слово. Единственная проблема - битовые поля в структуре. Я просто ищу портативную работу для них.   -  person dsell002    schedule 10.10.2013
comment
Связано: stackoverflow. com / questions / 1797345 / и stackoverflow.com/questions/17541370/.   -  person Steve Melnikoff    schedule 23.10.2013


Ответы (3)


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

typedef struct
{
   uint32_t data0 : 12;
   uint32_t data1 : 1;
   ...
} data_port;

#define DATA_PORT (*(volatile data_port *) 0xc006380) 

затем получите доступ к биту таким образом:

 DATA_PORT.data0 = 1;  // set data0 bit of DATA_PORT to 1 
person ouah    schedule 09.10.2013
comment
Есть ли разница между тем, как работает этот указатель макроса, по сравнению с использованием константного указателя на data_port? - person dsell002; 10.10.2013
comment
Я предпочитаю это решение, более элегантное, и компилятор будет жаловаться с предупреждением при переполнении данных. - person vgonisanz; 13.06.2016

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

#define DATA0_SHIFT   0
#define DATA0_MASK    0x3FF
#define DATA1_SHIFT   12
#define DATA1_MASK    0x1
#define DATA2_SHIFT   13
#define DATA2_MASK    0x1

// ...

uint32_t data = 0
   | ((data0 & DATA0_MASK) << DATA0_SHIFT)
   | ((data1 & DATA1_MASK) << DATA1_SHIFT)
   | ((data2 & DATA2_MASK) << DATA2_SHIFT);

Для самого реестра примерно так:

#define DATA_PORT_ADDR  0xc006380
#define DATA_PORT_REG  (*(volatile uint32_t *)(DATA_PORT_ADDR))

Это означает, что вы можете сделать это:

DATA_PORT_REG = data; // Value from above.

Также:

  1. Не используйте для этого битовые поля. Они зависят от реализации и поэтому могут демонстрировать неожиданное поведение. Вышеупомянутый метод должен работать на любой платформе.
  2. #define для регистра должен использовать независимый от реализации тип, например uint32_t, чтобы явно показать его размер.
person Steve Melnikoff    schedule 23.10.2013
comment
@ dsell002: Вы ничего не добавили к этому разговору. Этот ответ добавляет ряд вещей, в том числе: использование #defines для сдвигов полей и масок, чтобы улучшить читаемость и избежать магических чисел в коде; использование масок, чтобы избежать влияния слишком больших значений на соседние поля; причина важности использования uint32_t; использование #define для самого регистра, чтобы обеспечить простое присвоение в коде. - person Steve Melnikoff; 24.10.2013
comment
я не собирался показаться грубым. Я думал, что был безразличен, поэтому прошу прощения, если так вышло. В тексте все-таки сложно передать тон :) Однако, я считаю, что важно делать дополнения. В исходных вопросах я рассмотрел проблемы с битовыми полями, а в своем ответе представил маскировку. Использование макросов для доступа к реестру - вполне законное решение, но было бы хорошо, если бы вы сказали, чем он отличается от того, что я предлагал. Опять же, извините, если я показался грубым, и спасибо за помощь. Ваше здоровье! :) - person dsell002; 24.10.2013

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

typedef struct data_port
{
   uint32_t   data0;
   uint32_t   data1;
   ....
}data_port;

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

uint32_t volatile *const peripheral_base  = (uint32_t *) BASE;

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

static inline uint32_t struct_to_uint(data_port *data)
{
   return data->data0
    + (data->data1 << 12)
    + (data->data2 << 13)
    + .....;
}

Затем запись в регистр может выполняться с помощью вызова функции.

*peripheral_base = stuct_to_uint(&data_port);

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

person dsell002    schedule 10.10.2013