2012-11-20 34 views
4

想知道关于I²C寄存器映射的最佳实践是什么,或者更喜欢其他人使用的。最佳常见问题I2C寄存器映射图

到目前为止,我通常会做很多的定义,每个寄存器一个,所有的位,掩码,移位等。 但是,最近我看到一些驱动程序使用(可能打包)结构的定义。我认为这些是Linux内核模块。

无论如何,他们会

struct i2c_sensor_fuu_registers { 

    uint8_t id; 
    uint16_t big_register; 
    uint8_t another_register; 
    ... 

} __attribute__((packed)); 

然后,他们会使用offsetof(或宏)来获得I2C寄存器和使用sizeof对要读取的字节数。

我发现,这两种方法都有其优点:

结构的方法:

  • (+)寄存器的偏移都在逻辑上载,而不必拼出一个定义每个注册了一个结构里面。
  • (+)使用适当大小的数据类型明确声明条目大小。 ( - )这没有考虑到广泛使用的位域
  • ( - )这不包括没有字节映射的寄存器映射(例如LM75),其中一个从偏移量读取2个字节n + 0x00,而n + 0x01是另一个寄存器,而不是寄存器n的高/低字节0x00
  • ( - )这不占地址空间中的大间隙(例如寄存器0x00,0x01,0x80, 0xAA,没有in-betweens ......)和(我认为?)依靠编译器优化来摆脱结构。

定义的方法:

  • (+)与其比特沿所述寄存器中的每一个通常是在一个块中定义,使得找到正确的符号容易和依靠的命名约定。
  • (+)透明/不知道地址空间差距。
  • ( - )即使没有间隙,每个寄存器都必须单独定义
  • ( - )因为定义通常是全局性的,所以名称通常很长,有点乱七八糟的源代码长符号名称。
  • ( - )要读取的数据的大小通常是硬编码的幻数或(可能长符号名称的(结束 - 开始+ 1)型计算)。
  • (o)透明/不知道数据大小与地图中的地址。

基本上,我正在寻找更聪明的方式来处理这些情况。我经常发现自己为每一个寄存器和每一位都输入了很多和很多令人费解的长符号名称,并且可能会掩盖和转换(后两种取决于数据类型),最终只使用其中的一些(但是讨厌稍后重新定义缺失的符号,这就是为什么我在一个会话中输入所有符号的原因)。 不过,我注意到要读取/写入的字节大小主要是幻数,通常需要并排读取数据表和源代码才能理解最基本的交互。

我想知道别人怎么处理这种情况?我在网上找到了一些例子,其中人们还艰苦地输入每一个单一的寄存器,等等在一个大头,但没有什么非常明确的......但是,上面的两个选项都不是太聪明在这一点:(

回答

2

警告:这里描述的方法使用位域,它们在内存中的排列是特定于实现的,如果你这样做,确保你知道你的编译器在这方面的工作原理如下:

正如你指出的那样,每种方法我喜欢混合方法你可以定义寄存器偏移量然后使用一个结构体作为内容,并使用一个联合体来指定位或整个寄存器在联合体内,使用正确的大小变量来表示regi的大小(正如你所提到的,它们有时不是字节可寻址的)。你不需要太多定义,而且你不太可能搞乱位移,也不需要掩码。例如:

#define unsigned char u8; 
#define unsigned short u16; 

#define CTL_REG_ADDR 0x1234 
typedef union { 
    struct { 
    u16 not_used:10; //top 10 bits ununsed 
    u16 foo_bits:3; //a multibit register 
    u16 bar_bit:1; //just one bit 
    u16 baz_bits:2; //2 more bits 
    } fields; 
    u16 raw; 
} CTL_REG_DATA; 

#define STATUS_REG_ADDR 0x58 
typedef union { 
    struct { 
    u8 bar_bits:4; //upper nibble 
    u8 baz_bits:4; //lower nibble 
    } fields; 
    u8 raw; 
} STATUS_REG_DATA; 

//use them like the following 
u16 readregister(u16); 
void writeregister(u16,u16); 

CTL_REG_DATA reg; 
STATUS_REG_DATA rd; 
rd = readregister(STATUS_REG_ADDR); 
if (rd.fields.bar_bit) { 
    reg.raw = 0xffff;  //set every bit 
    reg.fields.bar_bit = 0; //but clear this one bit 
    writeregister(CTL_REG_ADDR, reg); 
} 
+1

警告:http://stackoverflow.com/questions/6043483/why-bit-endianness-is-an-issue-in-bitfields和http://stackoverflow.com/questions/1490092/cc-force-bit字段顺序和对齐 –

+0

ouch。这是杀手。我会在答案的最上面写一张便条。 – engineerC

+0

感谢您的警告。我在想我自己:D – FRob

1

在我的理想世界中,硬件设计人员将提供一个与C++,C和ASM兼容的头文件。一个是基于实际硬件寄存器自动生成的。通过#define(用于ASM)和typedef'd结构(用于C和C++)定义每个寄存器和位/字段的方法。一个表示每个位和字段的访问属性(只读,只写,写清除等)。其中包括定义每个寄存器及其位/字段的用途和用途的注释。它还需要考虑目标的字节顺序和编译器,以确保任何寄存器和位域的顺序都是正确的。

我尽可能接近这个理想,因为我可以在以前的工作。我编写了一个脚本来解析一个寄存器描述文件(我定义的格式)并自动生成一个完整的头文件(结构体和#define)以及一个函数来转储所有可读的寄存器以用于调试目的。我在其他公司看到了类似的方法,但没有一个将其纳入这一程度。

我会指出,如果您使用typedef结构来定义您的寄存器布局,那么您可以轻松解决定义中较大的寄存器间隙。例如只需添加一个“保留[80]”或“未使用[94]”或“未实现[2044]”或“间隙[42]”数组元素来定义间隙。总是使用struct定义作为指向硬件基地址的指针,所以它不会占用内存中任何位置的实际结构大小。

希望有所帮助。

+0

是的,我认为我夸大了差距问题(因此忘记它不会占用任何内存)。这只是结构与许多条目reserved1,reserved2,...,reservedN感觉不对:/ – FRob

+1

@FRob这也困扰我,所以我最终根据保留范围命名它们。例如reserved_0x40_to_0x80 [0x40的]。然后很明显,我正在尝试保留一个范围,而不是只添加一个保留字节/字段。 –