【问题标题】:Understanding #define preprocessor directive macro syntax了解#define 预处理器指令宏语法
【发布时间】:2019-07-03 12:01:56
【问题描述】:

以下代码取自LPC54618.h头文件:

typedef struct {
     //...structure elements
     __IO uint32_t SDIOCLKSEL;
     //...more elements

} SYSCON_Type;

#define SYSCON_BASE         (0x40000000u)
#define SYSCON              ((SYSCON_Type *)SYSCON_BASE)
  1. 据我所知,这句话背后的意思

    #define SYSCON ((SYSCON_Type *)SYSCON_BASE)

我假设它创建了一个名为SYSCON 的指针,该指针指向一个类型为SYSCON_Type 的变量,该变量存储在地址0x40000000u 中。这真的会发生吗?是否有任何资源可以解释这里使用的语法(即在宏中定义指针)?

  1. 当我尝试直接更改 SDIOCLKSEL 的值时,即:

    SYSCON->SDIOCLKSEL = some value;

    我收到一个错误:

    error: expected ')' error: expected parameter declarator error: expected ')' error: expected function body after function declarator

但如果我在函数中使用它,例如:

void foo(void)
{
   SYSCON->SDIOCLKSEL = some value;  
}

没有错误。这是为什么?为什么我不能直接写入结构?

任何答案将不胜感激!

【问题讨论】:

  • 您需要显示在上下文中无法编译的代码。一行代码本身并不能告诉我们任何事情。
  • 您不能在函数之外使用通用语句。任何书籍、教程或课程都应该教过你。
  • 就预处理器而言,#define 表示SYSCON 现在是一个扩展为((SYSCON_Type *)0x40000000u) 的常量,所以写SYSCON->SDIOCLKSEL = some value; 完全等同于写((SYSCON_Type *)0x40000000u)->SDIOCLKSEL = some value;。其余的只是 C 语法,与预处理器无关。
  • 请记住,您也可以让编译器向您显示预处理器的输出,以帮助了解正在发生的事情。检查编译器的文档以了解如何执行此操作。
  • 预处理器对指针一无所知。因此,宏中没有指针定义的语法。它与没有宏的语法相同。

标签: c pointers c-preprocessor lpc


【解决方案1】:
#define SYSCON_BASE         (0x40000000u)

这只是列出物理地址0x40000000

#define SYSCON ((SYSCON_Type *)SYSCON_BASE)

这通过强制转换将整数常量0x40000000u 转换为指向结构的指针。它实际上并没有分配任何东西——实际的寄存器已经被分配为内存映射硬件。

简单地说,它说“在地址 0x40000000 有一个硬件外围设备SYSCON”(不管是什么,一些计时器?)。一个常见的情况是,您在 MCU 中拥有多个相同类型的硬件外围设备(许多 SPI、ADC 等),每个外围设备都具有相同的寄存器布局,但位于不同的地址。我们可以为每个这样的外设使用相同的结构类型,也可以使用相同的驱动代码。

结构本身将有一个内存映射,它 100% 对应于寄存器布局。在这里确保填充/对齐不会搞砸很重要,但希望 MCU 制造商已经想到了这一点(尽管不要认为这是理所当然的)。

假设SDIOCLKSEL的寄存器偏移量为0x10,那么当你输入SYSCON->SDIOCLKSEL = some value;时,你会得到这样的机器码(伪汇编代码):

LOAD 0x40000000 into index register X  
LOAD 0x10 into register A
ADD A to X
MOVE some value into the address of X

(ARM 有特殊指令可以根据偏移量移动等,因此实际机器代码中的指令可能更少。随后的寄存器访问可以保持“X”不变并重复使用该基地址,以获得有效的代码。)

__IO 限定符只是隐藏volatile 的代码膨胀。

尝试“直接写入结构”时出现错误的原因很简单,您无法在所有函数之外执行代码,它与此结构无关。

【讨论】:

    【解决方案2】:

    这很容易。

    它创建了一个名为 SYSCON 的指针,指向一个变量 类型 SYSCON_Type 存储在地址 0x40000000u。这是 真的会发生什么?

    是和不是。当你使用宏 SYSCON

    void foo(uint32_t value)
    {
       SYSCON->SDIOCLKSEL = value;
    }
    

    预处理器转换为:

    void foo(uint32_t value)
    {
      ((SYSCON_Type *)0x40000000u)->SDIOCLKSEL = value;
    }
    

    将 32 位无符号 value 写入地址 0x40000000u + 结构成员偏移量的内存位置。

    通常用于访问映射到内存地址空间的硬件寄存器。

    你需要在函数内部完成(所有代码都是 C 语言)

    【讨论】:

    • 我想这是我当时最缺乏理解的原因。到目前为止,我学到的用于声明指针的语法是简单的int *foo_ptr = &foo;((SYSCON_Type *)0x40000000u) 如何翻译成我仍然无法理解的内容
    • @Vincent,这里没有指针 variable 的声明。只有一个整数常量转换为指针value,然后解除引用。这种方法的成功取决于对目标硬件的了解(以便可以选择正确的常数)以及使用中的 C 实现的合作(因为效果是实现定义的)。话虽如此,在嵌入式设备编程中这是相对普遍的做法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多