18

我正在为没有内存保护的嵌入式系统编写系统级代码(在 ARM Cortex-M1 上,使用 gcc 4.3 编译)并且需要直接读取/写入内存映射寄存器。到目前为止,我的代码如下所示:

#define UART0     0x4000C000
#define UART0CTL  (UART0 + 0x30)

volatile unsigned int *p;
p = UART0CTL;
*p &= ~1;

有没有不使用指针的更短的方法(我的意思是代码更短)?我正在寻找一种方法来编写如此短的实际分配代码(如果我不得不使用更多#defines 就可以了):

*(UART0CTL) &= ~1;

到目前为止,我尝试的任何事情都以 gcc 抱怨它无法将某些东西分配给左值而告终...

4

6 回答 6

21
#define UART0CTL ((volatile unsigned int *) (UART0 + 0x30))

:-P

编辑添加:哦,针对所有关于如何将问题标记为 C++ 和 C 的评论,这里有一个 C++ 解决方案。:-P

inline unsigned volatile& uart0ctl() {
    return *reinterpret_cast<unsigned volatile*>(UART0 + 0x30);
}

这可以直接卡在头文件中,就像 C 风格的宏一样,但是您必须使用函数调用语法来调用它。

于 2010-03-10T13:34:39.553 回答
17

我想成为一个挑剔的人:我们在谈论 C 还是 C++ ?

如果是 C,我愿意听从 Chris 的回答(并且我希望删除 C++ 标签)。

如果是 C++,我建议不要使用那些讨厌的 C-Cast #define

惯用的 C++ 方式是使用全局变量:

volatile unsigned int& UART0 = *((volatile unsigned int*)0x4000C000);
volatile unsigned int& UART0CTL = *(&UART0 + 0x0C);

我声明了一个类型化的全局变量,它将遵守范围规则(与宏不同)。

它可以轻松使用(无需使用*()),因此更短!

UART0CTL &= ~1; // no need to dereference, it's already a reference

如果您希望它是指针,那么它将是:

volatile unsigned int* const UART0 = 0x4000C000; // Note the const to prevent rebinding

但是使用const不能为 null 的指针有什么意义呢?从语义上讲,这就是为其创建引用的原因。

于 2010-03-10T14:21:48.383 回答
2

如果你想让硬件寄存器看起来像普通的旧变量,你可以比克里斯的回答更进一步:

#define UART0     0x4000C000
#define UART0CTL (*((volatile unsigned int *) (UART0 + 0x30)))

UART0CTL &= ~1;

这是一个口味问题,可能更可取。我曾在团队希望寄存器看起来像变量的情况下工作,并且我处理过添加的取消引用被认为“隐藏太多”的代码,因此寄存器的宏将作为指针保留被明确取消引用(如克里斯的回答)。

于 2010-03-10T16:04:27.817 回答
1
#define UART0  ((volatile unsigned int*)0x4000C000)
#define UART0CTL (UART0 + 0x0C)
于 2010-03-10T13:40:08.763 回答
1

我喜欢在结构中指定实际的控制位,然后将其分配给控制地址。就像是:

typedef struct uart_ctl_t {
    unsigned other_bits : 31;
    unsigned disable : 1;
};
uart_ctl_t *uart_ctl = 0x4000C030;
uart_ctl->disable = 1;

(抱歉,如果语法不太正确,我实际上已经有一段时间没有用 C 编写代码了......)

于 2010-03-10T16:51:04.927 回答
1

我有点喜欢嵌入式应用程序的另一个选项是使用链接器为您的硬件设备定义部分并将您的变量映射到这些部分。这样做的好处是,如果您面向多个设备,即使来自同一供应商(例如 TI),您通常也必须逐个设备更改链接器文件。即同一系列中的不同设备具有不同数量的内部直接映射内存,并且板对板您可能在不同位置也有不同数量的内存和硬件。这是 GCC 文档中的一个示例:

通常,编译器将它生成的对象放在数据和 bss 等部分中。但是,有时您需要额外的部分,或者您需要某些特定变量出现在特殊部分中,例如映射到特殊硬件。section 属性指定变量(或函数)位于特定的部分中。例如,这个小程序使用了几个特定的​​节名:

      struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
      struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };
      char stack[10000] __attribute__ ((section ("STACK"))) = { 0 };
      int init_data __attribute__ ((section ("INITDATA")));

      main()
      {
        /* Initialize stack pointer */
        init_sp (stack + sizeof (stack));

        /* Initialize initialized data */
        memcpy (&init_data, &data, &edata - &data);

        /* Turn on the serial ports */
        init_duart (&a);
        init_duart (&b);
      }

将 section 属性与全局变量一起使用,而不是局部变量,如示例中所示。

您可以将 section 属性与已初始化或未初始化的全局变量一起使用,但链接器要求每个对象定义一次,但未初始化的变量暂时位于 common(或 bss)部分中并且可以多次“定义”。使用 section 属性将更改变量进入的部分,如果未初始化的变量有多个定义,则可能导致链接器发出错误。您可以强制使用 -fno-common 标志或 nocommon 属性初始化变量。

于 2010-03-10T18:50:10.197 回答