【问题标题】:#define vs. enums for addressing peripherals用于寻址外围设备的#define 与枚举
【发布时间】:2010-10-19 17:15:20
【问题描述】:

我必须在基于 ARM9 的微控制器中对外围寄存器进行编程。

例如,对于 USART,我将相关的内存地址存储在 enum 中:

enum USART
{
    US_BASE = (int) 0xFFFC4000,
    US_BRGR = US_BASE + 0x16,
    //...
};

然后,我在函数中使用指针来初始化寄存器:

void init_usart (void)
{
    vuint* pBRGR = (vuint*) US_BRGR;
    *pBRGR = 0x030C;
    //...
}

但是我老师说我最好用#defines,比如:

#define US_BASE (0xFFFC4000)
#define US_BRGR (US_BASE + 0x16)
#define pBRGR   ((vuint*) US_BRGR)

void init_usart (void)
{
    *pBRGR = 0x030C;
}

他说,就像这样,你没有在堆栈中分配指针的开销。

就个人而言,我不太喜欢#defines,也不喜欢其他预处理器指令。 所以问题是,在这种特殊情况下,#defines 真的值得使用而不是 enums 和堆栈分配的指针吗?


相关问题:Want to configure a particular peripheral register in ARM9 based chip

【问题讨论】:

  • 如果在非调试版本中编译器生成的代码有任何不同,我会感到惊讶。
  • 我讨厌那些只说:“用这个”的老师,所以学生们以后还要自己挖掘原因。通常这些老师自己都不知道答案。
  • 如果您将指针定义为静态,它们将不会在堆栈上分配。如果您还声明它们为 const,那么它们甚至可能不会导致在数据区域中分配内存(但这取决于您使用的特定编译器)
  • 我想你可能误解了你的导师。枚举很好,没有理由使用指向 int 的指针而不是 int。你可以做int i = *(int *) some_thing;

标签: c enums arm embedded c-preprocessor


【解决方案1】:

我一直喜欢的方法是首先定义一个反映外设寄存器布局的结构

typedef volatile unsigned int reg32; // or other appropriate 32-bit integer type
typedef struct USART
{
    reg32 pad1;
    reg32 pad2;
    reg32 pad3;
    reg32 pad4;
    reg32 brgr;
    // any other registers
} USART;

USART *p_usart0 = (USART * const) 0xFFFC4000;

然后在代码中我可以使用

p_usart0->brgr = 0x030C;

当您有多个相同类型外围设备的实例时,这种方法会更简洁:

USART *p_usart1 = (USART * const) 0xFFFC5000;
USART *p_usart2 = (USART * const) 0xFFFC6000;

用户 sbass 提供了 Dan Saks 的 an excellent column 链接,该链接提供了有关此技术的更多详细信息,并指出了其优于其他方法的优势。

如果您有幸使用 C++,那么您可以为外围设备上的所有常见操作添加方法,并很好地封装设备特性。

【讨论】:

  • 这样好多了。 TI 将这种方法用于他们的处理器(至少对于 28xx DSP 和 MSP430)。定位寄存器的方法有点不同;它将它们粘贴在特定部分并使用链接器文件将它们定位在特定地址。
  • 另外,你应该让整个结构是可变的,而不仅仅是单个组件。
  • @Jason,如果所有寄存器都被声明为 volatile,那么声明整个 struct volatile 不会有任何区别。在任何一种情况下,编译器都不允许优化对寄存器的任何显式读取或写入。
  • @Jason, Stephen:volatile 经常被错误编译:cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf 具体来说,即使在最新版本的 gcc 和 clang 上,volatile 结构也会被错误编译:blog.regehr.org/archives/274
  • @pkh 感谢您的参考。如果我可以编辑 cmets,我应该将我对 Jason 的回复从“声明整个 struct volatile 不会有任何区别”更改为“声明整个 struct volatile 不应该有任何区别”。
【解决方案2】:

恐怕enum 对于这样的任务来说是个死胡同。该标准将enum 常量定义为int 类型,因此通常它们与指针兼容。

有一天,在具有 32 位 int 和 64 位指针的架构上,您可能有一个不适合 int 的常量。没有明确定义会发生什么。

另一方面,enum 将在堆栈上分配某些东西的论点是无效的。它们是编译时常量,与函数堆栈无关,或者与您通过宏指定的任何常量无关。

【讨论】:

  • @Michael:但这可以通过在末尾添加 UL 来解决。
【解决方案3】:

Dan Saks 为嵌入式系统编程撰写了许多关于此的专栏。这是他的latest ones 之一。他讨论了 C、C++、枚举、定义、结构、类等,以及为什么你可能会这样。绝对值得一读,总是好的建议。

【讨论】:

【解决方案4】:

根据我的经验,将#define 用于此类事情的一个重要原因是它更像是嵌入式社区中使用的标准习语。

使用枚举而不是 #define 将向讲师(以及将来的同事)生成问题/cmets,即使使用其他技术可能具有其他优势(例如不踩踏全局标识符命名空间)。

我个人喜欢将枚举用于数字常量,但有时您需要根据您的工作内容和工作地点进行惯常的操作。

但是,性能应该不是问题。

【讨论】:

    【解决方案5】:

    答案总是按照老师的要求做,然后通过课程,然后根据您自己的问题,找出他们的理由是否有效,并形成您自己的意见。你赢不了学校,不值得。

    在这种情况下,很容易编译为汇编程序或反汇编程序以查看枚举和定义之间的差异(如果有的话)。

    我会推荐对枚举进行定义,因为编译器对枚举感到不适。我强烈反对以你使用指针的方式使用指针,我看到每个编译器都无法准确地生成所需的指令,这种情况很少见,但当它发生时,你会想知道你过去几十年的编码是如何工作的。指向结构或其他任何东西要糟糕得多。我经常为此感到愤怒,并期待这次。街区周围太多英里,修复了太多带有这些问题的损坏代码而忽略了根本原因。

    【讨论】:

      【解决方案6】:

      我不一定会说任何一种方式都更好。这只是个人喜好。至于你教授的论点,这确实是一个有争议的问题。在栈上分配变量是一条指令,不管有多少,通常是sub esp, 10h的形式。因此,如果您有一个本地或 20 个本地,则为所有这些分配空间仍然是一条指令。

      我想说#include 的一个优点是,如果出于某种原因您想更改访问该指针的方式,您只需在一个位置进行更改。

      【讨论】:

        【解决方案7】:

        我倾向于使用枚举,以便将来与 C++ 代码兼容。我这样说是因为在我的工作中,我们有很多项目之间共享的 C 头文件,其中一些使用 C 代码,一些使用 C++。对于那些使用 C++ 的人,我们通常希望将定义包装在命名空间中,以防止符号屏蔽,但您不能将 #define 分配给命名空间。

        【讨论】:

          【解决方案8】:

          推荐阅读这篇文章:https://blog.feabhas.com/2019/01/peripheral-register-access-using-c-structs-part-1/

          作者解释了访问外围设备的不同方式。如果您有多个具有相似布局的外围设备,我个人更喜欢使用基于结构的方法。

          【讨论】:

          • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-04-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-08-06
          • 1970-01-01
          相关资源
          最近更新 更多