0

我正在使用 CodeWarrior V10.6 中的摩托罗拉 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 = 0x01、 或更改PTAD_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, ...}},因为由于某种原因,如何根据 C 编程语言标准初始化结构中LCDSTR lcd = {*.Bits.DB0 = &PTGD_PTGD6, *.Bits.DB1 = &PTBD_PTBD5, ...}的初始化(将其视为结构,请再次纠正我)中的建议不适用于此编译器(它确实适用于在线编译器)。

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

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

4

4 回答 4

1

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

  • 请注意,标准中对称为位域的 C 结构的规定非常差,因此在任何程序中都应避免使用。看到这个
  • 请注意,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 << 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 上普遍使用。

于 2017-01-13T10:59:00.703 回答
1

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

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

但是宏在这里可能很有用

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

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

#define SET_DB(x) do { \
    PTGD_PTGD6 = x;    \
    PTBD_PTBD5 = x;    \
    ......             \
} while(0) 
于 2017-01-13T05:44:44.887 回答
1

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

你不能。

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

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

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

于 2017-01-13T06:25:17.023 回答
0

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

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

这假设您想要打开特定引脚或关闭特定引脚,但不会出现您希望在单个操作中打开某些引脚并关闭特定设备的其他引脚的情况。如果是这种情况,我会考虑将类型设置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为无符号短型`,高字节是寄存器号,低字节是要操作的引脚作为字节的位掩码。

使用这种方法,您将拥有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
}
于 2017-01-13T14:10:51.527 回答