【问题标题】:How to create bitfield out of existing variables in C如何从 C 中的现有变量创建位域
【发布时间】:2017-01-13 05:00:15
【问题描述】:

我正在开发 CodeWarrior V10.6 中的 Motorola HCS08 µCU,我正在尝试创建一个 extern 位域,其中包含来自现有寄存器的位。 µCU 标头中创建位域的方式类似于

typedef unsigned char byte;
typedef union {
  byte Byte;
  struct {
    byte PTAD0       :1;
    byte PTAD1       :1;                                     
    byte PTAD2       :1;                                     
    byte PTAD3       :1;                                     
    byte PTAD4       :1;                                     
    byte PTAD5       :1;                                     
    byte PTAD6       :1;                                     
    byte PTAD7       :1;                                     
  } Bits;
} PTADSTR;
extern volatile PTADSTR _PTAD @0x00000000;
#define PTAD                            _PTAD.Byte
#define PTAD_PTAD0                      _PTAD.Bits.PTAD0
#define PTAD_PTAD1                      _PTAD.Bits.PTAD1
#define PTAD_PTAD2                      _PTAD.Bits.PTAD2
#define PTAD_PTAD3                      _PTAD.Bits.PTAD3
#define PTAD_PTAD4                      _PTAD.Bits.PTAD4
#define PTAD_PTAD5                      _PTAD.Bits.PTAD5
#define PTAD_PTAD6                      _PTAD.Bits.PTAD6
#define PTAD_PTAD7                      _PTAD.Bits.PTAD7

例如,这将允许通过PTAD = 0x01PTAD_PTAD0 = 1 更改寄存器值。这个定义对于 PTAD, PTBD, PTCD, ... PTGD 基本相同,唯一改变的是地址。

我尝试从以前的现有变量中创建自定义位域

typedef union {
  byte Byte;
  struct {
    byte *DB0;
    byte *DB1;
    byte *DB2;
    byte *DB3;
    byte *DB4;
    byte *DB5;
    byte *DB6;
    byte *DB7;
  } Bits;
} LCDDSTR;

我会将位域创建并初始化为LCDDSTR lcd = {{&PTGD_PTGD6, &PTBD_PTBD5, ...}},因为由于某种原因,LCDSTR lcd = {*.Bits.DB0 = &PTGD_PTGD6, *.Bits.DB1 = &PTBD_PTBD5, ...} 之类的初始化(将其视为结构,请再次纠正我)How to initialize a struct in accordance with C programming language standards 中的建议不适用于此编译器(它确实适用于在线编译器)。

但是,正如您可能看到的那样,我正在对位进行分组,并且(如果可以的话)我可以通过执行 *lcd.Bits.DB0 = 1 或类似的操作来更改实际寄存器的值,但是如果我做lcd.Byte = 0x00,我会更改lcd.Bits.DB0 中包含的内存地址的最后一个(我认为)字节,你知道,因为结构实际上并不包含数据,而是包含指针。

我将如何继续实现能够包含和修改来自多个寄存器的位的结构? (我想这里的问题是,在内存中,这些位不是一个接一个的,我想这会更容易)。甚至可能吗?我希望是的。

【问题讨论】:

  • 是否有映射到特定寄存器的内存地址?例如,内存地址 0x00000000 映射到寄存器 0,内存地址 0x00000001 映射到寄存器 1(假设 1 字节或 8 位寄存器)?我希望使用相同的PTADSTR 结构进行定义,然后创建一个指向寄存器所在内存区域开头的指针,并将它们作为PTADSTR 结构的数组引用。
  • 你不能有一个指向位域的指针。看stackoverflow.com/questions/13547352/…
  • @RichardChambers 我可以说,在定义中进一步将 PTADDSTR 结构定义为 0x00000001,然后将 PTBDSTR 定义为 0x00000002,然后将 PTBDDSTR 定义为 0x00000003 等等,是的 8 位寄存器,我想答案你的第一个问题,但我不明白你的想法,创建一个数组,其中包含指向每个结构位的指针,你说?
  • 指针不是指向结构的每一位,而是指向映射到 8 位寄存器的内存区域开头的指针,这将是 PTADSTR 结构的数组。然后使用_PTAD[0].Byte = 4;_PTAD[0].Bits.PTAD1 = 1;volatile PTADSTR (* const _PTAD) = 0x00000000;,我的理解是类似于extern volatile PTADSTR _PTAD @0x00000000; 的含义。您可能希望使用 _PTAD 以外的其他名称,因为系统标头已经使用了它。
  • 但是我可能会误解您的问题以及您想要完成的工作,因为我并不完全清楚您想要做什么。

标签: c struct microcontroller bit-fields codewarrior


【解决方案1】:

我将如何继续实现能够包含和修改来自多个寄存器的位的结构? (我想这里的问题是,在内存中,这些位不是一个接一个的..

我认为你不能用结构来做到这一点。这是因为根据定义,位域必须占用相同或连续的地址。

但是宏在这里可能有用

#define DB0  PTGD_PTGD6
#define DB1  PTBD_PTBD5
....

要将位清除为全 0 或设置为全 1,您可以使用多行宏

#define SET_DB(x) do { \
    PTGD_PTGD6 = x;    \
    PTBD_PTBD5 = x;    \
    ......             \
} while(0) 

【讨论】:

  • 非常感谢您的回答。我喜欢这种方法,正如我的问题所述,我期待某种DB = 0xAB,但我想我可以编写一个宏SET_DB(x),这样我就可以访问x中的每个位并将其分配给每个BD位,例如,让我试试看它是否按预期工作。我还必须看看这是否足够(我记得出于某种原因我需要包含在变量中的位)。
  • 进行了大量的测试并且工作得非常好,非常感谢。
【解决方案2】:

我将如何继续实现能够包含和修改来自多个寄存器的位的结构?

你不能。

一个结构必须代表一个单一的、连续的内存块——否则,像使用sizeof结构这样的操作,或者对指向一个指针的操作都没有意义。

如果你想置换一个值的位,你需要找到一些明确的方法。如果您的位顺序相对简单,则可以通过一些按位操作来实现;如果它更奇怪,您可能需要使用查找表。

除此之外:C 中的位域非常有限。对于包含位域的结构最终将如何布局在内存中,该语言并没有做出很多保证。对于可移植代码,通常最好避免使用它们。 (这在此处不适用,因为您正在为特定的编译器/微控制器组合编写代码,但总的来说值得牢记。)

【讨论】:

  • 伙计,非常感谢您的回答。真的很有帮助。
【解决方案3】:

不幸的是,您的联合没有任何意义,因为它形成了一个 byte 和 8 个 byte* 的联合。由于 HCS08 上的指针是 16 位的,因此最终结果为 8*2 = 16 字节的数据,无法以任何有意义的方式使用。

  • 请注意,名为bit-fields 的C 结构在标准中的规定非常差,因此在任何程序中都应避免使用。 See this
  • 请注意,Codewarrior 寄存器映射与 C 标准(或 MISRA-C)的距离并不遥远。
  • 请注意,结构通常对于硬件寄存器映射是有问题的,因为结构可以包含填充。你在 HCS08 上没有这个问题,因为它不需要对齐数据。但大多数 MCU 都要求这样做。

因此,如果您有该选项,最好在标准 C 中推出您自己的寄存器映射。端口 A 的数据寄存器可以简单地定义如下:

#define PTAD    (*(volatile uint8_t*)0x0000U)
#define PTAD7   (1U << 7)
#define PTAD6   (1U << 6)
#define PTAD5   (1U << 5)
#define PTAD4   (1U << 4)
#define PTAD3   (1U << 3)
#define PTAD2   (1U << 2)
#define PTAD1   (1U << 1)
#define PTAD0   (1U << 0)

正如我们所知,定义位掩码无论如何都是多余的,因为PTAD |= 1 &lt;&lt; 7;PTAD |= PTAD7; 具有同样的可读性。这是因为这是一个纯 I/O 端口。另一方面,为状态和控制寄存器定义文本位掩码可显着提高代码的可读性。


如果您想修改多个寄存器中的位,您可以执行以下操作:

假设我们有一个 RGB(红-绿-蓝)LED,共阴极,3 种颜色连接到 3 个不同端口上的 3 个不同引脚。您可以这样做,而不是殴打 PCB 设计师:

#define RGB_RED_PTD     PTAD
#define RGB_RED_PTDD    PTADD
...
#define RGB_BLUE_PTD    PTBD
#define RGB_BLUE_PTDD   PTBDD
...
#define RGB_GREEN_PTD   PTDD
#define RGB_GREEN PTDD  PTDDD

#define RGB_RED_PIN    1
#define RGB_BLUE_PIN   5
#define RGB_GREEN_PIN  3

您现在可以独立于它们在硬件上的位置进行设置:

void rgb_init (void)
{
  RGB_RED_PTDD   |= (1 << RGB_RED_PIN);
  RGB_BLUE_PTDD  |= (1 << RGB_BLUE_PIN);
  RGB_GREEN_PTDD |= (1 << RGB_GREEN_PIN);
}

void rgb_yellow (void)
{
  RGB_RED_PTD    |=  (1 << RGB_RED_PIN);
  RGB_BLUE_PTD   &= ~(1 << RGB_BLUE_PIN);
  RGB_GREEN_PTD  |=  (1 << RGB_GREEN_PIN);
}

等等。示例以 HCS08 为例,但当然可以在任何具有直接端口 I/O 的 MCU 上普遍使用。

【讨论】:

    【解决方案4】:

    听起来像下面这样的方法是沿着你想要解决方案的方向走的。

    我没有测试过这个,因为我没有硬件但是这应该提供一个替代方案。

    这假设您想要打开特定引脚或关闭特定引脚,但不会出现您希望在单个操作中打开某些引脚并关闭特定设备的其他引脚的情况。如果是这种情况,我会考虑将RegPinNo 的类型设为无符号短,以便为每个寄存器/引脚号组合包含一个操作码。

    这还假设操作时间不是关键约束,并且硬件具有足够的马力,因此小循环不会对吞吐量造成太大负担,也不会占用其他事情所需的 CPU 时间。因此,如果考虑到这一点,可能需要更改此代码以改进优化。

    我假设您需要某种易于阅读的方式来表达命令,该命令将打开和关闭分散在多个内存区域中的一系列位。

    首先要提出这样一个命令的表示形式,在我看来,从char 数组中借用来表示一个字符串就足够了。

    typedef byte RegPinNo;    // upper nibble indicates register number 0 - 7, lower nibble indicates pin number 0 - 7
    
    const byte REGPINNOEOS = 0xff;   // the end of string for a RegPinNo array.
    

    这些将用于定义寄存器/引脚编号数组,如下所示。

    RegPinNo myLed[] = { 0x01, 0x12, REGPINNOEOS };  // LED is addressed through Register 0, Pin 0 and Register 1, Pin 1 (zero based)
    

    因此,在这一点上,我们有一种方法可以描述特定设备(在本例中为 LED)是通过一系列寄存器/引脚编号项来寻址的。

    接下来让我们创建一个小型函数库,该库将使用此表示来实际修改特定寄存器中的特定引脚,方法是遍历此寄存器/引脚编号数组并对其执行操作,例如设置寄存器中的位或清除寄存器中的位。

    typedef unsigned char byte;
    typedef union {
        byte Byte;
        struct {
            byte PTAD0 : 1;
            byte PTAD1 : 1;
            byte PTAD2 : 1;
            byte PTAD3 : 1;
            byte PTAD4 : 1;
            byte PTAD5 : 1;
            byte PTAD6 : 1;
            byte PTAD7 : 1;
        } Bits;
    } PTADSTR;
    
    // Define a pointer to the beginning of the register area. This area is composed of
    // 8 different registers each of which is one byte in size.
    // We will address these registers as Register 0, Register 1, ... Register 7 which just happens
    // to be how C does its zero based indexing.
    // The bits representing pins on the PCB we will address as Pin 0, Pin 1, ... Pin 7.
    extern volatile PTADSTR (* const _PTAD)  = 0x00000000;
    
    void SetRegPins(RegPinNo *x)
    {
        byte pins[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
        int i;
        for (i = 0; x[i] != REGPINNOEOS; i++) {
            byte bRegNo = (x[i] >> 4) & 0x07;  // get the register number, 0 - 7
            byte bPinNo = x[i] & 0x07;         // get the pin number, 0 - 7
            _PTAD[bRegNo].Byte |= pins[bPinNo];
        }
    }
    
    void ClearRegPins(RegPinNo *x)
    {
        byte pins[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
        int i;
        for (i = 0; x[i] != REGPINNOEOS; i++) {
            byte bRegNo = (x[i] >> 4) & 0x07;  // get the register number, 0 - 7
            byte bPinNo = x[i] & 0x07;         // get the pin number, 0 - 7
            _PTAD[bRegNo].Byte &= ~pins[bPinNo];
        }
    }
    
    void ToggleRegPins(RegPinNo *x)
    {
        byte pins[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
        int i;
        for (i = 0; x[i] != REGPINNOEOS; i++) {
            byte bRegNo = (x[i] >> 4) & 0x07;  // get the register number, 0 - 7
            byte bPinNo = x[i] & 0x07;         // get the pin number, 0 - 7
            _PTAD[bRegNo].Byte ^= pins[bPinNo];
        }
    }
    

    您可以使用上面的内容,如下所示。不确定时间延迟函数在您的环境中会是什么样子,所以我使用了一个函数Sleep(),它接受关于延迟或睡眠的毫秒数的参数。

    void LightLed (int nMilliSeconds)
    {
        RegPinNo myLed[] = { 0x01, 0x12, REGPINNOEOS };  // LED is addressed through Register 0, Pin 0 and Register 1, Pin 1 (zero based)
    
        SetRegPins(myLed);    // turn on the LED
        Sleep(nMilliSeconds); // delay for a time with the LED lit
        ClearRegPins(myLed);  // turn the LED back off
    }
    

    编辑 - 改进

    允许同时在特定寄存器中设置多个引脚的更有效的实现是将RegPinNo 的使用定义为无符号短`,高字节是寄存器号,低字节byte 是作为字节位掩码操作的引脚。

    使用这种方法,您将拥有一个如下所示的SetRegPins() 函数。其他功能也需要进行类似的更改。

    void SetRegPins(RegPinNo *x)
    {
        int i;
        for (i = 0; x[i] != REGPINNOEOS; i++) {
            byte bRegNo = (x[i] >> 8) & 0x07;  // get the register number, 0 - 7
            byte bPinNo = x[i] & 0xFF;         // get the pin mask
            _PTAD[bRegNo].Byte |= bPinNo;
        }
    }
    

    typedef 看起来像:

    typedef unsigned short RegPinNo;    // upper byte indicates register number 0 - 7, lower byte provides pin mask
    
    const byte REGPINNOEOS = 0xffff;   // the end of string for a RegPinNo array.
    

    这些元素的使用方式如下:

    void LightLed (int nMilliSeconds)
    {
        RegPinNo myLed[] = { 0x0002, 0x0103, REGPINNOEOS };  // LED is addressed through Register 0, Pin 1 and Register 1, Pin 0 and Pin 1 (zero based)
    
        SetRegPins(myLed);    // turn on the LED
        Sleep(nMilliSeconds); // delay for a time with the LED lit
        ClearRegPins(myLed);  // turn the LED back off
    }
    

    【讨论】:

      猜你喜欢
      • 2023-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多