2

我是嵌入式系统编程的新手,并试图走自己的路。使用带有数据表的Stellaris LM4F120 LaunchPad 评估板LM4F120H5QR Microcontroller,我发现可以获得一些寄存器的完整地址,您必须始终添加偏移量!我不明白它的重要性,因为我们可以直接使用完整地址!

例如配置端口 F(从0x4002.5000到 开始0x4002.5FFF)及其引脚(使用 APB 总线)

  1. 通过在寄存器中将(位 5)设置为 1 来激活 clk 到该端口RCGCGPIO ,它的基地址是0x400F.E000偏移量,0x608所以完整地址是0x400FE608
  2. 配置GPIODIR它的基地址0x4002.5000 与偏移量的reg,0x400因此完整地址是0x4002.5400
  3. 配置GPIODEN它的基地址0x4002.5000 与偏移量的reg,0x51C因此完整地址是0x4002.551C
  4. 配置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 个寄存器,可以让我们单独配置每个引脚,甚至只需向基地址添加偏移量即可配置它们的组合,例如,如果我们要配置打开的红色 LED,Port F我们直接写入地址base address 0x4002.5000 + offset 0x008

4

4 回答 4

3

你可以写#define GPIO_PORTF_DATA 0x400253FC它给你端口F的数据寄存器的绝对地址。它只是一个宏,作为程序员,你更容易知道你在谈论某个端口的数据寄存器。

在我作为嵌入式程序员的工作中,您使用偏移量以尽可能少地写入绝对地址。

我能想到的一些原因是当您发现地址错误,或者您获得了新版本的硬件,或者您必须编写具有新地址的新驱动程序时发生了什么,并假设寄存器没有改变,但只有地址,偏移量你只需要改变基地址,而不是代码中的所有寄存器。

于 2019-05-19T14:55:05.533 回答
3

这是因为您从中复制这些定义的标头是从CMSIS 系统视图描述格式自动生成的。芯片制造商使用这种格式以标准化的方式描述其微处理器的核心和外围元件。通常,您可以在某个存储库或制造商主页上下载那些所谓的“.svd”文件。

LM4F120H5QR 的这些外设之一将是通用 IO 端口 F (GPIOF)。.svd 文件将包含一个带有一些基地址的端口元素,然后是一个外设具有一些偏移量的每个寄存器的子元素。

于 2019-05-19T16:29:54.840 回答
0

您发布的特定代码没有多大意义。但在一般情况下,你会做这样的事情来处理同一芯片上的多个硬件外围设备:

#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 等编写驱动程序的常用方法,您可能在片上拥有多个相同的外围设备,并希望使用相同的代码来处理它们,而无需重复代码。

不利的一面是执行开销很小,因为地址必须在运行时计算。

于 2019-05-20T10:50:54.510 回答
0
  1. 因为文档是这样写的。

在此处输入图像描述

  1. 但事实上,没有人(除了重新发明轮子的人)这样做。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;
于 2019-05-20T10:57:27.050 回答