将位域结构映射到易失性寄存器

时间:2013-10-09 20:42:14

标签: c embedded bit-fields

我正在编写一个与VHDL中定义的许多寄存器接口的应用程序。寄存器为32位宽,并分配成组。我为该组的每个成员提供了组的基址和32位偏移量。以下是一个组的示例,组内的寄存器和寄存器的结构。

Group 1 | base addr | offset | data_port

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

目前,使用以下位字段结构

处理I / O.
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)

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

希望,一旦我达到这一点,一切都将在32位内完全对齐。我想要做的一个方法是创建相同的data_port结构,但是删除位打包,然后右边一个函数专门为每个寄存器将位移位到unsigned int,然后可以使用它传递给寄存器易失性指针。

类似的东西,

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

我不确定语法是否正确,但我们的想法是在不必担心编译器或平台的情况下转移值。这是否因为?这种方法是否存在可移植性问题?

3 个答案:

答案 0 :(得分:6)

虽然位字段非常依赖于实现,但您可以使用宏来识别寄存器:

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 

答案 1 :(得分:1)

用于访问硬件寄存器中的字段的典型的与实现无关的方法是使用移位(和掩码)。例如:

#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. 不要将bitfields用于此类事情。它们依赖于实现,因此可以显示意外行为。上述方法适用于任何平台。
  2. 注册表的#define应使用与uint32_t类似的与实现无关的类型,以便明确显示其大小。

答案 2 :(得分:0)

最好的选择似乎是完全消除位域结构的使用。因此,要处理寄存器的输入,而不是分离位,只需使用寄存器的组件创建一个结构。

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

虽然此结构不直接管理硬件接口,但它是处理应用程序层数据的有用方法。可以使用宏或指向易失性const 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结构的值,以确保它们不会超出其边界。否则,写入寄存器的数据将产生意外结果。