【问题标题】:Do Struct-style register definitions consume RAM结构式寄存器定义是否消耗 RAM
【发布时间】:2021-02-04 12:05:37
【问题描述】:

所以我一直在互联网上寻找答案,但没有得到任何答案。

我想知道的是微控制器(ARM mcu、AVR mcu)的结构样式寄存器定义是否消耗 RAM。我知道如果实例化结构的对象,它将消耗 RAM(在堆栈上或其他地方)。

但是据我所知,ARM 在其 CMSIS 中使用的寄存器定义类似于下面的寄存器定义,类似于新的 ATTiny 系列用于其寄存器定义的寄存器定义。

这些是否专门消耗 RAM 内存。我很确定它们会在一定程度上消耗闪存/程序空间,但是 RAM?

#define PORT              ((Port     *)0x41008000UL) /**< \brief (PORT) APB Base Address */
typedef struct {
       PortGroup                 Group[4];    /**< \brief Offset: 0x00 PortGroup groups [GROUPS] */
} Port;
typedef struct {
  __IO PORT_DIR_Type             DIR;         /**< \brief Offset: 0x00 (R/W 32) Data Direction */
  __IO PORT_DIRCLR_Type          DIRCLR;      /**< \brief Offset: 0x04 (R/W 32) Data Direction Clear */
  __IO PORT_DIRSET_Type          DIRSET;      /**< \brief Offset: 0x08 (R/W 32) Data Direction Set */
  __IO PORT_DIRTGL_Type          DIRTGL;      /**< \brief Offset: 0x0C (R/W 32) Data Direction Toggle */
  __IO PORT_OUT_Type             OUT;         /**< \brief Offset: 0x10 (R/W 32) Data Output Value */
  __IO PORT_OUTCLR_Type          OUTCLR;      /**< \brief Offset: 0x14 (R/W 32) Data Output Value Clear */
  __IO PORT_OUTSET_Type          OUTSET;      /**< \brief Offset: 0x18 (R/W 32) Data Output Value Set */
  __IO PORT_OUTTGL_Type          OUTTGL;      /**< \brief Offset: 0x1C (R/W 32) Data Output Value Toggle */
  __I  PORT_IN_Type              IN;          /**< \brief Offset: 0x20 (R/  32) Data Input Value */
  __IO PORT_CTRL_Type            CTRL;        /**< \brief Offset: 0x24 (R/W 32) Control */
  __O  PORT_WRCONFIG_Type        WRCONFIG;    /**< \brief Offset: 0x28 ( /W 32) Write Configuration */
  __IO PORT_EVCTRL_Type          EVCTRL;      /**< \brief Offset: 0x2C (R/W 32) Event Input Control */
  __IO PORT_PMUX_Type            PMUX[16];    /**< \brief Offset: 0x30 (R/W  8) Peripheral Multiplexing */
  __IO PORT_PINCFG_Type          PINCFG[32];  /**< \brief Offset: 0x40 (R/W  8) Pin Configuration */
       RoReg8                    Reserved1[0x20];
} PortGroup;

注意:以上代码块中提供的所有代码都是来自 EEVBlog 用户的直接引用。它与 CMSIS 中提供的寄存器定义有关。链接是here。

编辑:我知道 MCU 有寄存器并且访问这些寄存器不会消耗 RAM。但我的困惑在于这些寄存器被引用的方式。例如:

// if a register address is 0x50

#define address 0x50    // This consumes no RAM as it is resolved during compilation

uint8_t addr = 0x50;    // This consumes RAM because it is now a variable


// So what about this??

typedef struct {
    uint8_t addr = 0x50;
} address_group;

所以访问寄存器本身不会占用 RAM 空间,但我们用来轻松引用这些地址的方法,在这种情况下使用结构,是我感到困惑的地方。

所有寄存器都可以一个一个地#define'd,这不会消耗RAM,但是选择以结构格式进行......?

【问题讨论】:

  • 不,它们不消耗任何内存
  • 它们也不消耗闪存。
  • 不需要内存。
  • 哇,3 个人说他们不消耗 RAM。那么它们在编译过程中都解决了吗?还是在预处理器阶段?
  • 通常您定义一个指向该数据类型的指针并将内存映射寄存器块的地址分配给该指针。通过取消引用指针,您可以直接访问硬件寄存器。这就是第一行对PORT 宏的作用,它甚至省略了指针变量本身。

标签: c avr cmsis


【解决方案1】:

为了控制GPIO和UART等外设,外设提供了映射到MCU内存地址空间的寄存器。

STM32 MCUS 上此类寄存器的典型地址范围是从 0x40000000 向上。这与闪存和 RAM 是分开的。

例如:USART1 的波特率寄存器可能位于地址 0x41006008。因此,通过从该地址读取一个字(32 位),可以读取波特率。并且通过写入地址,可以更改。

在 C 中,它可能看起来像:

*(volatile uint32_t*)0x41006008 = 115200;

但是,如果它看起来像这样,它的可读性和效率会更高:

USART1.BRR = 115200;

您的问题中显示的所有 defines 和 typedef 都声明了数据类型(例如 PortGroup)和伪变量(例如 PORT)。我称它们为 pseudo,因为它们不是分配在 RAM 中的常规变量,而是内置于硬件中的内存映射结构。 (而且数据类型声明永远不会消耗内存。)

这种方法的巧妙之处在于:代码更易于编写和阅读,并且仍然与神秘代码一样大小。它在编译时解决,因为编译器可以计算出USART1.BRR 的绝对地址。

【讨论】:

    【解决方案2】:

    严格来说,内存映射寄存器是 RAM,在某种程度上,尽管它们可以通过硬件和软件进行更新。它们位于内存映射中不允许链接器乱来的位置。编译器一般不知道寄存器区域的存在。

    通过使用此处描述的方法:How to access a hardware register from firmware? ,或使用供应商提供的预制“寄存器映射”,我们告诉编译器访问它不知道存储什么的内存区域。

    例如,您可以告诉编译器“在此位置有一个 PortGroup 结构,来自地址 0x41008000UL 及以后”。然后编译器盲目地信任程序员并使用结构的声明类型通过提供的指针访问该区域。如果这些类型与硬件寄存器匹配,那么一切都会像您在那里分配变量一样工作。但是你实际上不需要让链接器在那个区域分配内存,因为硬件已经提供了一切。

    访问寄存器,或访问任何其他形式的变量,不一定占用任何 RAM。给定任何随机变量int x;,然后x 本身被分配到某个地方,但是使用x=0; 等访问它的行为不太可能占用任何RAM。


    最后还有一些误解:#define address 0x50 不消耗任何 RAM,不是因为它是在编译时解析的,而是因为它消耗的是 ROM。程序使用的所有数字都将分配在可执行文件的某个位置。

    如果你有uint8_t addr = 0x50; ... if(addr == something),优化编译器不一定要为addr分配RAM空间。它可以优化掉整个变量。请参阅此 x86 示例:https://godbolt.org/z/KroKhq。分配变量没有任何意义,因此编译器将幻数 0x50 (80 dec) 作为 cmp 指令的一部分存储在机器代码中。如果您在同一示例中使用#define,您将获得相同的机器代码。

    【讨论】:

    • #define address 0x50 不消耗 ROM。 define 声明本身不生成任何代码或分配任何内容。只有像x.y = address;这样使用才有效果,然后会生成代码(包括常量)。
    • @Codo 好吧,我有点假设它会在程序中使用,并相信读者有一些常识。如果你也不在MCU中下载程序,代码不会消耗任何ROM...
    • 通常一个程序包含带有数千个define的头文件,并且只会使用其中的几个。了解 define 是否以及何时生成代码非常重要。
    【解决方案3】:

    不,这些结构不占用 RAM。实际定义变量时会消耗 RAM,代码如下

    unsigned int myInteger;  // takes RAM
    struct foo myStruct;     // takes RAM
    

    上述行告诉您的编译器/链接器生成名为 myInteger 和 myStruct 的符号,并在微控制器的 RAM 区域(可能在您的堆栈上)为这些符号分配空间。

    另一方面,当你定义一个struct类型或者创建一个typedef时,这和定义一个变量是不同的,它不占用任何RAM:

    struct foo {  // Does not take RAM, just defines a type.
      ...
    };
    

    当您使用以下代码定义指向结构的指针时,您只是定义了一个表达式,该表达式的计算结果为指向硬件中已存在的结构的指针。你没有做任何应该消耗 RAM 的事情:

    // Does not take up RAM, just defines an expression.
    #define PORTA ((struct GPIO *)0x1230)
    

    当您在代码中使用 PORTA 时,使用它的代码行将占用一些代码空间。这些行在执行时可能会占用一些堆栈空间,但它们不应永久占用任何 RAM。

    【讨论】:

      猜你喜欢
      • 2018-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多