使用偏移量获取微控制器中寄存器完整地址的目的是什么?
what is the purpose of using offset to get register full address in micro-controller?
我是嵌入式系统编程的新手,正在努力走自己的路。
使用带有数据表 LM4F120H5QR Microcontroller
的 Stellaris LM4F120 LaunchPad 评估板 我发现要获取某些寄存器的完整地址,您必须始终添加偏移量!我不明白它的重要性,因为我们可以直接使用完整地址!
For example to configure Port F (which starts from 0x4002.5000
to
0x4002.5FFF
)and it's pins (using APB bus)
- 通过在
RCGCGPIO
中将(位 5)设置为 1 来激活此端口的时钟
注册它的基地址是 0x400F.E000
和偏移量 0x608
所以
完整地址是 0x400FE608
- 配置
GPIODIR
reg,它的基地址是 0x4002.5000
偏移量 0x400
所以完整地址是 0x4002.5400
- 配置
GPIODEN
reg,它的基地址是 0x4002.5000
偏移量 0x51C
所以完整地址是 0x4002.551C
- 配置
GPIODATA
reg,它的基地址是 0x4002.5000
0x3FC
所以完整地址是 0x4002.50x3FC
如果我能猜到的话,这里的偏移量是用来减少出错的,因为我们可以这样写:
#define GPIO_PORTF_BASE 0x40025000
#define GPIO_PORTF_DATA (*((volatile unsigned long *)(GPIO_PORTF_BASE + 0x3FC)))
#define GPIO_PORTF_DIR (*((volatile unsigned long *)(GPIO_PORTF_BASE + 0x400)))
#define GPIO_PORTF_DEN (*((volatile unsigned long *)(GPIO_PORTF_BASE + 0x51C)))
使用偏移量是否会增加可读性并使其变得更简单和简单,因为我们只需写入偏移量即可获得所需的寄存器?
更新
I found that Base address has more usage than obtaining the full address of a register.
for example : GPIODATA
controls 0-7 pins
and it has 255 registers that can allow us to configure each pin individually and even their combination just by adding an offset to the base address e.g. If we want to configure the Red Led which is on Port F
we write to the address base address 0x4002.5000 + offset 0x008
directly.
你可以写#define GPIO_PORTF_DATA 0x400253FC
,它给你端口F的数据寄存器的绝对地址。它只是一个宏,作为一个程序员,你更容易知道你在谈论数据寄存器一些港口。
在我作为一名嵌入式程序员的工作中,你使用偏移量的方式是为了尽可能少地写绝对地址。
我能想到的一些原因是当你发现地址有误,或者你得到了新版本的硬件,或者你必须用新地址编写新驱动程序,然后让假设寄存器的结构没有改变,只有地址,有了偏移量,你只需要改变基地址,而不是代码中的所有寄存器。
那是因为您从中复制这些定义的 header 是 CMSIS System View Description format 中的 auto-generated。芯片制造商使用这种格式以标准化方式描述其微处理器的核心和外围元件。通常您可以在某些存储库或制造商主页上下载那些所谓的“.svd”文件。
描述的 LM4F120H5QR 外设之一是通用 IO 端口 F (GPIOF)。 .svd 文件将包含一个带有一些 base-address 的端口元素,然后是一个 sub-element 用于外设具有一些偏移量的每个寄存器。
你贴的具体代码意义不大。但在一般情况下,你会做这样的事情来处理同一芯片上的多个硬件外设:
#define PORTF 0x40025000ul
...
#define GPIO_PORT_DATA(base) (*((volatile unsigned long *)(base + 0x3FCul)))
#define GPIO_PORT_DIR(base) (*((volatile unsigned long *)(base + 0x400ul)))
#define GPIO_PORT_DEN(base) (*((volatile unsigned long *)(base + 0x51Cul)))
鉴于所有外围设备都具有相同的内存映射,您现在可以编写一个驱动程序来处理多个外围设备。 GPIO 可能不是最好的例子,因为在 GPIO 上编写抽象层通常只会增加混乱。但理论上我们可以拥有这个驱动程序:
void gpio_set (volatile unsigned long* port, uint8_t pin);
...
gpio_set (PORTF, 5);
其中 gpio
不知道它正在处理哪个特定端口,无论如何它都通过访问宏来完成相同的工作。
这是为 SPI、UART、CAN、ADC 等编写驱动程序的常用方法,您可能有几个相同的外设 on-chip 并希望使用相同的代码来处理所有这些,而无需代码重复。
缺点是执行开销很小,因为地址必须在 run-time 中计算。
- 因为文档是这样写的。
- 但事实上没有人(除了重新发明轮子的人)这样做。 STM CMSIS 文件定义结构,编译器自行计算偏移量:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
我是嵌入式系统编程的新手,正在努力走自己的路。
使用带有数据表 LM4F120H5QR Microcontroller
的 Stellaris LM4F120 LaunchPad 评估板 我发现要获取某些寄存器的完整地址,您必须始终添加偏移量!我不明白它的重要性,因为我们可以直接使用完整地址!
For example to configure Port F (which starts from
0x4002.5000
to0x4002.5FFF
)and it's pins (using APB bus)
- 通过在
RCGCGPIO
中将(位 5)设置为 1 来激活此端口的时钟 注册它的基地址是0x400F.E000
和偏移量0x608
所以 完整地址是0x400FE608
- 配置
GPIODIR
reg,它的基地址是0x4002.5000
偏移量0x400
所以完整地址是0x4002.5400
- 配置
GPIODEN
reg,它的基地址是0x4002.5000
偏移量0x51C
所以完整地址是0x4002.551C
- 配置
GPIODATA
reg,它的基地址是0x4002.5000
0x3FC
所以完整地址是0x4002.50x3FC
如果我能猜到的话,这里的偏移量是用来减少出错的,因为我们可以这样写:
#define GPIO_PORTF_BASE 0x40025000
#define GPIO_PORTF_DATA (*((volatile unsigned long *)(GPIO_PORTF_BASE + 0x3FC)))
#define GPIO_PORTF_DIR (*((volatile unsigned long *)(GPIO_PORTF_BASE + 0x400)))
#define GPIO_PORTF_DEN (*((volatile unsigned long *)(GPIO_PORTF_BASE + 0x51C)))
使用偏移量是否会增加可读性并使其变得更简单和简单,因为我们只需写入偏移量即可获得所需的寄存器?
更新
I found that Base address has more usage than obtaining the full address of a register.
for example :
GPIODATA
controls0-7 pins
and it has 255 registers that can allow us to configure each pin individually and even their combination just by adding an offset to the base address e.g. If we want to configure the Red Led which is onPort F
we write to the addressbase address 0x4002.5000 + offset 0x008
directly.
你可以写#define GPIO_PORTF_DATA 0x400253FC
,它给你端口F的数据寄存器的绝对地址。它只是一个宏,作为一个程序员,你更容易知道你在谈论数据寄存器一些港口。
在我作为一名嵌入式程序员的工作中,你使用偏移量的方式是为了尽可能少地写绝对地址。
我能想到的一些原因是当你发现地址有误,或者你得到了新版本的硬件,或者你必须用新地址编写新驱动程序,然后让假设寄存器的结构没有改变,只有地址,有了偏移量,你只需要改变基地址,而不是代码中的所有寄存器。
那是因为您从中复制这些定义的 header 是 CMSIS System View Description format 中的 auto-generated。芯片制造商使用这种格式以标准化方式描述其微处理器的核心和外围元件。通常您可以在某些存储库或制造商主页上下载那些所谓的“.svd”文件。
描述的 LM4F120H5QR 外设之一是通用 IO 端口 F (GPIOF)。 .svd 文件将包含一个带有一些 base-address 的端口元素,然后是一个 sub-element 用于外设具有一些偏移量的每个寄存器。
你贴的具体代码意义不大。但在一般情况下,你会做这样的事情来处理同一芯片上的多个硬件外设:
#define PORTF 0x40025000ul
...
#define GPIO_PORT_DATA(base) (*((volatile unsigned long *)(base + 0x3FCul)))
#define GPIO_PORT_DIR(base) (*((volatile unsigned long *)(base + 0x400ul)))
#define GPIO_PORT_DEN(base) (*((volatile unsigned long *)(base + 0x51Cul)))
鉴于所有外围设备都具有相同的内存映射,您现在可以编写一个驱动程序来处理多个外围设备。 GPIO 可能不是最好的例子,因为在 GPIO 上编写抽象层通常只会增加混乱。但理论上我们可以拥有这个驱动程序:
void gpio_set (volatile unsigned long* port, uint8_t pin);
...
gpio_set (PORTF, 5);
其中 gpio
不知道它正在处理哪个特定端口,无论如何它都通过访问宏来完成相同的工作。
这是为 SPI、UART、CAN、ADC 等编写驱动程序的常用方法,您可能有几个相同的外设 on-chip 并希望使用相同的代码来处理所有这些,而无需代码重复。
缺点是执行开销很小,因为地址必须在 run-time 中计算。
- 因为文档是这样写的。
- 但事实上没有人(除了重新发明轮子的人)这样做。 STM CMSIS 文件定义结构,编译器自行计算偏移量:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;