在51单片机中:
首先我们看看 51 中是怎么做的。51 单片机开发中经常会引用一个 reg51.h 的头文件,下面我们看看他是怎么把名字和寄存器联系起来的:
sfr P0 =0x80;
sfr 也是一种扩充数据类型,点用一个内存单元,值域为 0~255。利用它可以访问 51 单片
机内部的所有特殊功能寄存器。如用 sfr P1 = 0x90 这一句定义 P1 为 P1 端口在片内的寄存
器。然后我们往地址为 0x80 的寄存器设值的方法是:P0=value;
在STM32中:
在 STM32 中,同样也可以这样做,但是 STM32 因为寄存器太多太多,如果以这样的方式一一列出来,那要好大的篇幅,既不方便开发,也显得太杂乱无序的感觉。所以 MDK 采用的方式是通过结构体来将寄存器组织在一起。下面我们就讲解 MDK 是怎么把结构体和地址对应起来的,为什么我们修改结构体成员变量的值就可以达到操作对应寄存器的值。这些事情都是在stm32f10x.h文件中完成的。我们通过 GPIOA 的几个寄存器的地址来讲解。
(1)寄存器地址映射表
从这个表我们可以看出,GPIOA 的 7 个寄存器都是 32 位的,所以每个寄存器占有 4个地址,一共占用 28 个地址,地址偏移范围为(000h~01Bh)。这个地址偏移是相对 GPIOA的基地址而言的。GPIOA 的基地址是怎么算出来的呢?因为 GPIO 都是挂载在 APB2 总线之上,所以它的基地址是由 APB2 总线的基地址+GPIOA 在 APB2 总线上的偏移地址决定的。同理依次类推,我们便可以算出 GPIOA 基地址了。下面我们打开 stm32f10x.h 定位到 GPIO_TypeDef 定义处:
typedef struct
{__IO uint32_t CRL;__IO uint32_t CRH;__IO uint32_t IDR;__IO uint32_t ODR;__IO uint32_t BSRR;__IO uint32_t BRR;__IO uint32_t LCKR;
} GPIO_TypeDef;
然后定义到:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
GPIOA 是将 GPIOA_BASE 强制转换为 GPIO_TypeDef 指针,这句话的意思是,GPIOA 指向地址GPIOA_BASE,GPIOA_BASE 存放的数据类型为 GPIO_TypeDef。
然后查看 GPIOA_BASE的宏定义:
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
可知GPIOA 的基地址是 APB2 总线的基地址+GPIOA 在 APB2 总线上的偏移地址
依次类推,可以找到最顶层:
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define PERIPH_BASE ((uint32_t)0x40000000)
所以我们便可以算出 GPIOA 的基地址位:
GPIOA_BASE= 0x40000000+0x10000+0x0800=0x40010800
(2)GPIOA 的 7 个寄存器的地址如何计算:
GPIOA 的寄存器的地址=GPIOA 基地址+寄存器相对 GPIOA 基地址的偏移值这个偏移值在上面的寄存器地址映像表中可以查到。
(3)结构体里面这些寄存器又是怎么与地址一一对应的:
这里就涉及到结构体的一个特征,那就是结构体存储的成员他们的地址是连续的。上面讲到 GPIOA 是指向GPIO_TypeDef 类型的指针,又由于 GPIO_TypeDef 是结构体,所以自然而然我们就可以算出 GPIOA 指向的结构体成员变量对应地址了。
我们可以把 GPIO_TypeDef 的定义中的成员变量的顺序和 GPIOx 寄存器地址映像对比可以发现,他们的顺序是一致的,如果不一致,就会导致地址混乱了。这就是为什么固件库里面:GPIOA->BRR=value;就是设置地址为 0x40010800+0x014(BRR 偏移量)=0x40010814 的寄存器 BRR 的值了。它和 51 里面 P0=value 是设置地址为 0x80 的 P0 寄存器的值是一样的道理。