[英]what is the purpose of using offset to get register full address in micro-controller?
我是嵌入式系统编程的新手,正在尝试自己的方式。 我将Stellaris LM4F120 LaunchPad评估板与数据表LM4F120H5QR Microcontroller
我发现要获得某些寄存器的完整地址,而您必须始终添加一个偏移量! 我不了解它的重要性,因为我们可以直接使用完整的地址!
例如,配置端口F(从
0x4002.5000
到0x4002.5FFF
开始)及其引脚(使用APB总线)
RCGCGPIO
寄存器中的(位5)设置为1(基地址为0x400F.E000
,偏移0x608
为0x400F.E000
,使完整地址为0x400FE608
)来激活clk到该端口 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)))
使用偏移量是否会增加可读性,并使它变得简单易用,因为我们只需编写偏移量即可获得所需的寄存器?
我发现基地址比获得寄存器的完整地址有更多用途。
例如:
GPIODATA
控制0-7 pins
,它具有255个寄存器,可以让我们仅通过向基地址添加偏移量就可以单独配置每个引脚,甚至可以配置它们的组合,例如,如果我们要配置Port F
上的红色LED我们直接写入地址base address 0x4002.5000 + offset 0x008
。
您可以编写#define GPIO_PORTF_DATA 0x400253FC
,它为您提供端口F数据寄存器的绝对地址。它只是一个宏,对于程序员来说,它更容易使您知道您在谈论某个端口的数据寄存器。
在我作为嵌入式程序员的工作方式中,您使用offset来尽可能少地写入绝对地址。
我能想到的一些原因是,当您发现地址错误,或者您获得了新版本的硬件,或者曾经发生过必须使用新地址编写新驱动程序并假定结构为寄存器没有改变,只有地址,带有偏移量,您只需要更改基地址,而不是代码中的所有寄存器。
这是因为您从CMSIS系统视图描述格式中自动复制了这些定义的标头。 芯片制造商使用此格式以标准化方式描述其微处理器的核心和外围元素。 通常,您可以在某些存储库或制造商的主页上下载那些所谓的“ .svd”文件。
LM4F120H5QR所描述的那些外设之一将是通用IO端口F(GPIOF)。 .svd文件将包含具有一些基地址的端口元素,然后是外设具有一些偏移量的每个寄存器的子元素。
您发布的特定代码没有多大意义。 但是在一般情况下,您将执行以下操作:在同一芯片上处理多个硬件外设:
#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等编写驱动程序的常用方法,在这些驱动程序中,您可能在片上拥有多个相同的外设,并希望使用相同的代码来处理它们,而无需重复代码。
缺点是执行开销很小,因为该地址必须在运行时计算。
#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;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.