将位字段读取强制为32位

时间:2009-12-04 13:34:58

标签: c++ c bit bit-fields

我正在尝试通过PCI总线读取低于32位的VME桥接芯片(Tundra Universe II),然后进入VME总线并由目标接收。

目标VME应用程序只接受D32(读取的数据宽度为32位),并忽略其他任何内容。

如果我使用在VME窗口上映射的位字段结构(nmap'd到主存储器中),我可以读取> 24位的位字段,但是任何不足都会失败。即: -

struct works {
    unsigned int a:24;
};

struct fails {
    unsigned int a:1;
    unsigned int b:1;
    unsigned int c:1;
};

struct main {
    works work;
    fails fail;
}
volatile *reg = function_that_creates_and_maps_the_vme_windows_returns_address()

这表明 struct works 被读取为32位,但是通过读取 结构 a ,例如 reg - > fail.a 被分解为X位读取。 (其中X可能是16或8?)

所以问题是:
a)这缩小了哪里?编译器? OS?还是Tundra芯片?
b)执行的读操作的实际大小是多少?

我基本上想排除芯片以外的一切。关于它的文档是在网上,但如果可以证明通过PCI总线请求的数据宽度是32位,那么问题可归咎于Tundra芯片!

编辑: -
具体的例子,代码是: -

struct SVersion
{
    unsigned title         : 8;
    unsigned pecversion    : 8;
    unsigned majorversion  : 8;
    unsigned minorversion  : 8;
} Version;

所以现在我把它改成了这个: -

union UPECVersion
{
    struct SVersion
    {
        unsigned title         : 8;
        unsigned pecversion    : 8;
        unsigned majorversion  : 8;
        unsigned minorversion  : 8;
    } Version;
    unsigned int dummy;
};

基本主结构: -

typedef struct SEPUMap
{
    ...
    ...
    UPECVersion PECVersion;

};

所以我仍然需要更改所有基线代码

// perform dummy 32bit read
pEpuMap->PECVersion.dummy;

// get the bits out
x = pEpuMap->PECVersion.Version.minorversion;

我怎么知道第二次读取是否真的再次真正读取,就像我的原始代码那样? (而不是通过联合使用已读取的位!)

9 个答案:

答案 0 :(得分:6)

您的编译器正在将结构的大小调整为其内存对齐设置的倍数。几乎所有现代编译器都这样做。在某些处理器上,变量和指令从内存地址开始,这些内存地址是某些内存对齐值的倍数(通常为32位或64位,但对齐取决于处理器体系结构)。大多数现代处理器不再需要内存对齐 - 但几乎所有处理器都看到实质性性能优势。因此,编译器会为您的数据调整以提高性能。

但是,在许多情况下(例如你的),这不是你想要的行为。由于各种原因,您的结构大小可能变得非常重要。在这些情况下,有各种方法解决问题。

一种选择是强制编译器使用不同的对齐设置。执行此操作的选项因编译器而异,因此您必须检查文档。它通常是某种#pragma。在某些编译器(例如Microsoft编译器)上,可以仅为非常小的代码段更改内存对齐。例如(在VC ++中):

#pragma pack(push)      // save the current alignment
#pragma pack(1)         // set the alignment to one byte
// Define variables that are alignment sensitive
#pragma pack(pop)       // restore the alignment

另一种选择是以其他方式定义变量。内部类型不会根据对齐进行调整,因此,不是24位位域,另一种方法是将变量定义为字节数组。

最后,你可以让编译器按照他们想要的大小制作结构,并手动记录你需要读/写的大小。只要你没有将结构连接在一起,这应该可以正常工作。但请记住,编译器会为您提供填充结构,因此如果您创建一个更大的结构,包括 works failed 结构,那么将在它们之间填充可能会导致问题的位。

在大多数编译器中,创建小于8位的数据类型几乎是不可能的。大多数架构都不这么认为。这应该不是一个大问题,因为大多数使用小于8位数据类型的硬件设备最终会以这样的方式安排它们的数据包,它们仍然是8位多路复用,所以你可以进行位操作来提取或在数据流离开或进入时对其进行编码。

由于上面列出的所有原因,许多适用于此类硬件设备的代码都使用原始字节数组,只是对数组中的数据进行编码。尽管失去了现代语言结构的许多便利,但它最终会变得更容易。

答案 1 :(得分:2)

我想知道sizeof(struct fails)的价值。是1吗?在这种情况下,如果通过取消引用指向struct fails的指针来执行读取,则在VME总线上发出D8读取是正确的。

您可以尝试将字段unsigned int unused:29;添加到struct fails

答案 2 :(得分:2)

struct的大小不等于其字段大小的总和,包括位字段。编译器允许按C和C ++语言规范插入在struct中的字段之间填充。通常会插入填充以用于对齐目的。

嵌入式系统编程中的常用方法是将数据读取为无符号整数,然后使用位屏蔽来检索有趣的位。这是由于我所述的上述规则以及结构中“打包”字段没有标准编译器参数的事实。

我建议创建一个对象( classstruct)以与硬件连接。让对象读取数据,然后将这些位提取为bool成员。这使得实现尽可能靠近硬件。剩下的软件不应该关心如何这些位的实现。

在定义位字段位置/命名常量时,我​​建议使用以下格式:

#define VALUE (1 << BIT POSITION)
// OR
const unsigned int VALUE = 1 << BIT POSITION;

此格式更具可读性,编译器可执行算法。计算在编译期间进行,并且在运行期间没有影响。

答案 3 :(得分:1)

Ian - 如果你想确定你正在读/写的东西的大小,我建议你不要使用这样的结构来做这件事 - 失败结构的sizeof可能只有1个字节 - 编译器可以根据优化等自由决定它应该是什么 - 我建议使用int显式读/写,或者通常你需要确保大小的东西,然后做其他事情,比如转换到union / struct没有这些限制。

答案 4 :(得分:1)

例如,Linux内核具有内联函数,可以显式处理内存映射的IO读写。在较新的内核中,它是一个很大的宏包装器,归结为内联汇编movl指令,但它的旧内核定义如下:

#define readl(addr) (*(volatile unsigned int *) (addr))
#define writel(b,addr) ((*(volatile unsigned int *) (addr)) = (b))

答案 5 :(得分:1)

编译器决定要发出的读取大小。要强制执行32位读取,可以使用union

union dev_word {
    struct dev_reg {
        unsigned int a:1;
        unsigned int b:1;
        unsigned int c:1;
    } fail;
    uint32_t dummy;
};

volatile union dev_word *vme_map_window();

如果通过volatile限定指针读取union不足以强制读取整个union(我认为它会 - 但这可能是编译器相关的),那么你可以使用一个函数来提供所需的间接:

volatile union dev_word *real_reg; /* Initialised with vme_map_window() */

union dev_word * const *reg_func(void)
{
    static union dev_word local_copy;
    static union dev_word * const static_ptr = &local_copy;

    local_copy = *real_reg;
    return &static_ptr;
}

#define reg (*reg_func())

...然后(为了与现有代码兼容)您的访问完成如下:

reg->fail.a

答案 6 :(得分:1)

前面描述的使用gcc标志-fstrict-volatile-bitfields并将位域变量定义为volatile u32的方法有效,但定义的总位数必须大于16。

例如:

typedef     union{
    vu32    Word;
    struct{
        vu32    LATENCY     :3;
        vu32    HLFCYA      :1;
        vu32    PRFTBE      :1;
        vu32    PRFTBS      :1;  
    };
}tFlashACR;
.
tFLASH* const pFLASH    =   (tFLASH*)FLASH_BASE;
#define FLASH_LATENCY       pFLASH->ACR.LATENCY
.
FLASH_LATENCY = Latency;

导致gcc生成代码

.
ldrb r1, [r3, #0]
.

这是一个字节读取。但是,将typedef更改为

typedef     union{
    vu32    Word;
    struct{
        vu32    LATENCY     :3;
        vu32    HLFCYA      :1;
        vu32    PRFTBE      :1;
        vu32    PRFTBS      :1;
        vu32                :2;

        vu32    DUMMY1      :8;

        vu32    DUMMY2      :8;
    };
}tFlashACR;

将结果代码更改为

.
ldr r3, [r2, #0]
.

答案 7 :(得分:0)

我相信唯一的解决方案是 1)编辑/创建我的主结构为所有32位整数(无符号长整数)
2)保持原始的位域结构
3)我要求的每次访问,
3.1)我必须将结构成员读取为32位字,并将其转换为位字段结构,
3.2)读取我需要的位域元素。 (对于写入,设置此位字段,然后将字写回!)

(1)这是相同的,因为那时我失去了“main / SEPUMap”结构的每个成员的内在类型。

结束解决方案: -
而不是: -

printf("FirmwareVersionMinor: 0x%x\n", pEpuMap->PECVersion);

这: -

SPECVersion ver = *(SPECVersion*)&pEpuMap->PECVersion;

printf("FirmwareVersionMinor: 0x%x\n", ver.minorversion);

我唯一的问题就是写作! (写入现在是读/修改/写入!)

// Read - Get current
_HVPSUControl temp = *(_HVPSUControl*)&pEpuMap->HVPSUControl;

// Modify - set to new value
temp.OperationalRequestPort = true;

// Write
volatile unsigned int *addr = reinterpret_cast<volatile unsigned int*>(&pEpuMap->HVPSUControl);

*addr = *reinterpret_cast<volatile unsigned int*>(&temp);

只需将代码整理成方法即可!

#define writel(addr, data) ( *(volatile unsigned long*)(&addr) = (*(volatile unsigned long*)(&data)) )

答案 8 :(得分:0)

我在使用GCC编译器的ARM上遇到了同样的问题,其中写入内存只是通过字节而不是32位字。

解决方案是使用 volatile uint32_t (或写入所需大小)来定义位域:

union {
    volatile uint32_t XY;
    struct {
        volatile uint32_t XY_A : 4;
        volatile uint32_t XY_B : 12;
    };
};

但是在编译时你需要添加gcc或g ++这个参数:

-fstrict-volatile-bitfields

更多在gcc文档中。