Замена битовых полей бит-смещением во встроенной структуре регистра - PullRequest
0 голосов
/ 02 февраля 2019

Я пытаюсь немного увлечься написанием драйверов для периферийных устройств во встроенных приложениях.

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

Иногда я хочу записать весь регистр, а иногда я хочу манипулировать подмножеством битов в этом регистре.В последнее время я прочитал кое-что, что предлагает создать объединение, содержащее один тип uintX, достаточно большой, чтобы вместить весь регистр (обычно 8 или 16 бит), а также структуру, в которой есть коллекция битовых полей.чтобы представить конкретные биты этого регистра.

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

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

Мой вопрос:

  1. Как мне взять что-то вродеэто:

    typedef union data_port
    {
        uint16_t CCR1;
        struct
            {
                data1 : 5;
                data2 : 3;
                data3 : 4;
                data4 : 4;
            }
    }
    

    И избавиться от битовых полей и преобразовать в схему сдвига бит в здравом смысле?

  2. Часть 3 этого сообщения парней здесь описывает то, о чем я говорю в общем ... Обратите внимание, в конце он помещает все регистры (обернутые как объединения) в структуру и затем предлагает сделать следующее:

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

    #define CAN0 (*(CAN_REG_FILE *)CAN_BASE_ADDRESS)
    

    Какого черта этот милый маленький ход?все о?CAN0 - указатель на указатель на функцию с ... числом, которое # определено как CAN_BASE_ADDRESS?Я не знаю ... Он потерял меня на этом.

Ответы [ 2 ]

0 голосов
/ 02 февраля 2019

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

Использование типов, больших чем unsigned char, также имеет эффекты, определяемые реализацией, но на практике это гораздо более переносимо.В реальном мире для uintNN_t есть только два варианта: с прямым порядком байтов или с прямым порядком байтов, и обычно для данного ЦП все используют один и тот же порядок, потому что это тот порядок, который ЦП использует изначально.(Некоторые архитектуры, такие как mips и arm, поддерживают оба порядковых номера, но обычно люди придерживаются одного порядкового номера в широком диапазоне моделей ЦП.) Если вы обращаетесь к собственным регистрам ЦП, его порядковый номер в любом случае может быть частью ЦП.С другой стороны, если вы обращаетесь к периферийному устройству, вам нужно позаботиться.

В документации к устройству, к которому вы обращаетесь, будет указано, какой объем памяти нужно адресовать одновременно (очевидно, 2байт в вашем примере) и как расположены биты.Например, в нем может быть указано, что регистр является 16-битным регистром, доступ к которому осуществляется с помощью 16-битных инструкций загрузки / сохранения независимо от того, что является порядковым номером процессора, что data1 охватывает 5 младших битов, data2 охватывает следующие 3, data3 следующие 4 и data4 следующие 4. В этом случае вы должны объявить регистр как uint16_t.

typedef volatile uint16_t data_port_t;
data_port_t *port = GET_DATA_PORT_ADDRESS();

Адреса памяти в устройствах почти всегда должны быть объявлены volatile, потому что важно, чтобы компилятор читал и записывал их в нужное время.

Чтобы получить доступ к частям регистра, используйте операторы bit-shift и bit-mask.Например:

#define DATA2_WIDTH 3
#define DATA2_OFFSET 5
#define DATA2_MAX (((uint16_t)1 << DATA2_WIDTH) - 1) // in binary: 0000000000000111
#define DATA2_MASK (DATA2_MAX << DATA2_OFFSET) // in binary: 0000000011100000
void set_data2(data_port_t *port, unsigned new_field_value)
{
    assert(new_field_value <= DATA2_MAX);
    uint16_t old_register_value = *port;
    // First, mask out the data2 bits from the current register value.
    uint16_t new_register_value = (old_register_value & ~DATA2_MASK);
    // Then mask in the new value for data2.
    new_register_value |= (new_field_value << DATA2_OFFSET);
    *port = new_register_value; 
}

Очевидно, вы можете сделать код намного короче.Я разделил его на отдельные крошечные шаги, чтобы за логикой было легко следовать.Я включаю более короткую версию ниже.Любой компилятор, достойный своей соли, должен компилировать тот же код, за исключением режима без оптимизации.Обратите внимание, что выше, я использовал промежуточную переменную вместо двух назначений для *port, потому что выполнение двух назначений для *port изменило бы поведение: это заставило бы устройство видеть промежуточное значение (и другое чтение, так как |=это и чтение, и запись).Вот более короткая версия и функция чтения:

void set_data2(data_port_t *port, unsigned new_field_value)
{
    assert(new_field_value <= DATA2_MAX);
    *port = (*port & ~(((uint16_t)1 << DATA2_WIDTH) - 1) << DATA2_OFFSET))
                   | (new_field_value << DATA2_OFFSET);
}
unsigned get_data2(data_port *port)
{
     return (*port >> DATA2_OFFSET) & DATA2_MASK;
}

#define CAN0 (*(CAN_REG_FILE *)CAN_BASE_ADDRESS)

Здесь нет функции.Объявление функции должно иметь тип возврата, за которым следует список аргументов в скобках.Это принимает значение CAN_BASE_ADDRESS, которое, по-видимому, является указателем некоторого типа, затем приводит указатель к указателю на CAN_REG_FILE и, наконец, разыменовывает указатель.Другими словами, он обращается к файлу регистрации CAN по адресу, указанному CAN_BASE_ADDRESS.Например, могут быть объявления типа

void *CAN_BASE_ADDRESS = (void*)0x12345678;
typedef struct {
    const volatile uint32_t status;
    volatile uint16_t foo;
    volatile uint16_t bar;
} CAN_REG_FILE;
#define CAN0 (*(CAN_REG_FILE *)CAN_BASE_ADDRESS)

, а затем вы можете делать такие вещи, как

CAN0.foo = 42;
printf("CAN0 status: %d\n", (int)CAN0.status);
0 голосов
/ 02 февраля 2019

1.Проблема при избавлении от битовых полей состоит в том, что вы больше не можете использовать простые операторы присваивания, но вы должны сдвинуть значение, чтобы записать, создать маску, сделать AND, чтобы стереть предыдущие биты, и использовать OR, чтобы записать новые биты.,Чтение похоже обратное.Например, давайте возьмем 8-битный регистр, определенный следующим образом:

val2.val1
0000.0000

val1 - младшие 4 бита, а val2 - верхний 4. Весь регистр называется REG.
Для чтения val1в tmp нужно ввести:

tmp = REG & 0x0F;

и прочитать val2:

tmp = (REG >> 4) & 0xF;   // AND redundant in this particular case

или

tmp = (REG & 0xF0) >> 4;

Но записать tmp в val2, например,вам нужно сделать:

REG = (REG & 0x0F) | (tmp << 4);

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

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

2.

#define CAN0 (*(CAN_REG_FILE *)CAN_BASE_ADDRESS)

Этот макрос определяет CAN0 как разыменованный указатель на базовый адресрегистров CAN, объявление функции не участвует.Предположим, у вас есть 8-битный регистр по адресу 0x800.Вы можете сделать:

#define REG_BASE 0x800     // address of the register
#define REG (*(uint8_t *) REG_BASE)

REG = 0;    // becomes *REG_BASE = 0
tmp = REG;  // tmp=*REG_BASE

Вместо uint_t вы можете использовать тип структуры, и все биты и, возможно, все байты или слова волшебным образом перейдут на правильное место с правильной семантикой.Конечно, используя хороший компилятор - но кто не хочет развертывать хороший компилятор?

Некоторые компиляторы имеют / имеют расширения для назначения заданного адреса переменной;например, старый турбо паскаль имел ключевое слово ABSOLUTE:

var CAN: byte absolute 0x800:0000;  // seg:ofs...!

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

...