问题描述
所以我一直在互联网上寻找答案,但一无所获。
我想知道微控制器(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,但选择以struct格式进行...?
解决方法
为了控制 GPIO 和 UART 等外设,外设提供了映射到 MCU 内存地址空间的寄存器。
STM32 MCUS 上此类寄存器的典型地址范围是从 0x40000000 向上。这与闪存和 RAM 是分开的。
例如:USART1 的波特率寄存器可能位于地址 0x41006008。因此,通过从该地址读取一个字(32 位),可以读取波特率。并且通过写入地址,可以更改。
在 C 中,它可能看起来像:
*(volatile uint32_t*)0x41006008 = 115200;
但是,如果看起来像这样,它的可读性和效率会更高:
USART1.BRR = 115200;
问题中显示的所有 defines 和 typedef 声明数据类型(例如 PortGroup
)和伪变量(例如 PORT
)。我称它们为伪,因为它们不是分配在 RAM 中的常规变量,而是内置于硬件中的内存映射结构。 (而且数据类型声明永远不会消耗内存。)
这种方法的巧妙之处在于:代码更易于编写和阅读,并且仍然与神秘代码的大小相同。它在编译时解析,因为编译器可以计算出 USART1.BRR
的绝对地址。
严格来说,内存映射寄存器在某种程度上是 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
,您将获得相同的机器代码。
不,这些结构不占用 RAM。实际定义变量时会消耗RAM,代码如下
unsigned int myInteger; // takes RAM
struct foo myStruct; // takes RAM
类似上面的行告诉您的编译器/链接器创建名为 myInteger
和 myStruct
的符号,并在微控制器的 RAM 区域(可能在堆栈中)为这些符号分配空间。>
另一方面,当你定义一个结构体类型或创建一个typedef
时,这与定义一个变量不同,它不占用任何内存:
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。