STM32学习笔记--寄存器地址名称映射

基于正点原子mini开发板、STM32RCT6、库函数

目录:

  • 一、 C语言相关知识复习
  • 二、STM32寄存器地址名称映射

一、 C语言相关知识复习

1.位操作
STM32学习笔记--寄存器地址名称映射_第1张图片
2. define 宏定义
  define 是 C 语言中的预处理命令,它用于宏定义,可以提高源代码的可读性,为编程提供方便。常见的格式:

#define 标识符 字符串

  “标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。例如:

#define SYSCLK_FREQ_72MHz 72000000

  定义标识符 SYSCLK_FREQ_72MHz 的值为 72000000。
3. ifdef 条件编译
  单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。条件编译命令最常见的形式为:

#ifdef 标识符
程序段 1
#else
程序段 2
#endif

  它的作用是:当标识符已经被定义过(一般是用#define 命令定义),则对程序段 1 进行编译,否则编译程序段 2。 其中#else 部分也可以没有,即:

#ifdef
程序段 1
#endif

  这个条件编译在MDK里面是用得很多的,在stm32f10x.h这个头文件中经常会看到这样的语句:

#ifdef STM32F10X_HD
大容量芯片需要的一些变量定义
#end

  而 STM32F10X_HD 则是我们通过#define 来定义的,条件编译也是 c 语言的基础知识。
4. extern 变量申明
  C 语言中 extern 可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。这里面要注意,对于 extern 申明变量可以多次,但定义只有一次。在我们的代码中你会看到看到这样的语句:

extern u16 USART_RX_STA;

  这个语句是申明 USART_RX_STA 变量在其他文件中已经定义了,在这里要使用到。所以,你肯定可以找到在某个地方有变量定义的语句:

u16 USART_RX_STA;

  例如:
  在 Main.c 定义的全局变量 id,id 的初始化都是在 Main.c 里面进行的。 Main.c 文件如下:

u8 id;//定义只允许一次
main()
{
     
id=1;
printf("d%",id);//id=1
test();
printf("d%",id);//id=2
}

  但是我们希望在test.c的 changeId(void)函数中使用变量id,这个时候我们就需要在test.c里面去申明变量 id 是外部定义的了,因为如果不申明,变量 id 的作用域是到不了 test.c 文件中。看下面 test.c 中的代码:

extern u8 id;//申明变量 id 是在外部定义的,申明可以在很多个文件中进行
void test(void){
     
id=2;
}

  在 test.c 中申明变量 id 在外部定义,然后在 test.c 中就可以使用变量 id 了。

5.typedef 类型别名
  typedef 用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。typedef 在 MDK 用得最多的就是定义结构体的类型别名和枚举类型了。

struct _GPIO
{
     
 __IO uint32_t CRL;
 __IO uint32_t CRH;};

  定义了一个结构体 GPIO,这样我们定义变量的方式为:

struct _GPIO GPIOA;//定义结构体变量 GPIOA

 但是这样很繁琐,MDK 中有很多这样的结构体变量需要定义。这里我们可以为结体定义一个别名 GPIO_TypeDef,这样我们就可以在其他地方通过别名 GPIO_TypeDef 来定义结构体变量了。方法如下:

typedef struct
{
     
 __IO uint32_t CRL;
 __IO uint32_t CRH;} GPIO_TypeDef;

 Typedef 为结构体定义一个别名 GPIO_TypeDef,这样我们可以通过GPIO_TypeDef 来定义结构体变量:

GPIO_TypeDef _GPIOA,_GPIOB;

  这里的 GPIO_TypeDef 就跟 struct _GPIO 是等同的作用了。
6.结构体
  声明结构体类型:

Struct 结构体名{
     
成员列表;
}变量名列表;

  例如:

Struct U_TYPE {
     
Int BaudRate
Int WordLength;
}usart1,usart2;

  在结构体申明的时候可以定义变量,也可以申明之后定义,方法是:

Struct 结构体名字 结构体变量列表 ;

  例如:

struct U_TYPE usart1,usart2;

  结构体成员变量的引用方法是:

结构体变量名字.成员名

  比如要引用 usart1 的成员 BaudRate,方法是:

usart1.BaudRate;

  结构体指针变量定义也是一样的,跟其他变量没有啥区别。例如:

struct U_TYPE *usart3;//定义结构体指针变量 usart1;

  结构体指针成员变量引用方法是通过“->”符号实现,比如要访问 usart3 结构体指针指向的结构体的成员变量 BaudRate,方法是:

Usart3->BaudRate;

二、STM32寄存器地址名称映射

  STM32 因为寄存器太多太多,通过结构体来将寄存器组织在一起,修改结构体成员变量的值就可以达到操作对应寄存器的值,这样可以简化操作,方便开发。
  以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”选中之后右键选中“Go to definition of ”,便可一查看 GPIOA_BASE的宏定义:

#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)

  依次类推,可以找到最顶层:

#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define PERIPH_BASE ((uint32_t)0x40000000)

  所以我们便可以算出 GPIOA 的基地址位:

GPIOA_BASE= 0x40000000+0x10000+0x0800=0x40010800

  通过官方参考手册提供的存储器映射表我们可以看到,GPIOA 的起始地址也就是基地址确实是 0x40010800:
STM32学习笔记--寄存器地址名称映射_第2张图片
  通过GPIOA 的各个寄存器对于 GPIOA 基地址的偏移地址,可以算出来每个寄存器的地址。

GPIOA 的寄存器的地址=GPIOA 基地址+寄存器相对 GPIOA 基地址的偏移值

  这个偏移值在上面的寄存器地址映像表中可以查到。
  结构体存储的成员他们的地址是连续的, GPIOA 是指向GPIO_TypeDef 类型的指针,又由于GPIO_TypeDef 是结构体,所以自然而然我们就可以算出 GPIOA 指向的结构体成员变量对应地址了:
STM32学习笔记--寄存器地址名称映射_第3张图片
  把 GPIO_TypeDef 的定义中的成员变量的顺序和 GPIOx 寄存器地址映像对比可以发现,他们的顺序是一致的,如果不一致,就会导致地址混乱了。这就是为什么固件库里面:GPIOA->BRR=value;就是设置地址为 0x40010800+0x014(BRR 偏移量)=0x40010814 的寄存器 BRR 的值了。它和 51 里面 P0=value 是设置地址为 0x80 的 P0 寄存器的值是一样的道理。

你可能感兴趣的:(单片机,STM32,嵌入式,嵌入式,c语言,单片机,stm32,寄存器)