先看代码
stm32f10x.h
//用来存放STM32寄存器映射的代码
//外设 perirhral
#define PERIPH_BASE ((unsigned int)0x40000000)
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE+0x10000)
#define AHBPERIPH_BASE (PERIPH_BASE+0x20000)
#define RCC_BASE (AHBPERIPH_BASE+0x1000)
#define GPIOB_BASE (APB2PERIPH_BASE+0x0C00)
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18)
#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE+0x00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE+0x04)
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
#define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE+0x10)
以下是STM32F103的存储器映像(存储器在产家制作完成后是一片没有任何信息的物理存储器,而CPU要进行访存就涉及到内存地址的概念,因此存储器映射就是为物理内存按一定编码规则分配地址的行为。值得注意,存储器映射一般是由产家规定,用户不能随意更改。)
寄存器映射
寄存器映射是在存储器映射的基础上进行的。
以STM32为例,操作硬件本质上就是操作寄存器。在存储器片上外设区域,四字节为一个单元,每个单元对应不同的功能。当我们控制这些单元时就可以驱动外设工作,我们可以找到每个单元的起始地址,然后通过C 语言指针的操作方式来访问这些单元。但若每次都是通过这种方式访问地址,不好记忆且易出错。这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名实质上就是寄存器名字。给已分配好地址(通过存储器映射实现)的有特定功能的内存单元取别名的过程就叫寄存器映射。
首先取TIM2定时器的地址,作为起始地址,也是所有外设的基地址0x4000 0000让我们逐一分析
#define PERIPH_BASE ((unsigned int)0x40000000) //这是一个外设基地址
这段代码 #define PERIPH_BASE ((unsigned int)0x40000000)
是C语言中的一个宏定义。我们来分解一下这段代码,理解其作用:
#define
: 这是C语言中的一个预处理指令,用于定义宏。它允许给定一个符号名称,将其定义为一个常量值或代码片段。预处理器在编译代码之前会用宏的值替换所有相应的出现。
PERIPH_BASE
: 这是正在定义的宏的名称。它充当一个符号常量,表示内存中某个外设的基地址。使用宏来表示这样的地址可以使代码更易读、易维护。
((unsigned int)0x40000000)
: 这是分配给PERIPH_BASE
宏的值。它代表外设映射的具体内存地址。在这里,数值 0x40000000
被强制转换为 unsigned int
类型。将地址强制转换为 unsigned int
类型通常是为了避免意外的符号扩展。
在实际应用中,这个宏可能被用在嵌入式系统或底层编程中,其中硬件外设(比如GPIO、UART等)被映射到特定的内存地址。使用宏可以让程序员使用符号名称 PERIPH_BASE
而不是原始的十六进制值,从而提高代码的可读性和可维护性。
#define APB1PERIPH_BASE PERIPH_BASE
这里 PERIPH_BASE
已经定义为硬件寄存器的基地址(比如 0x40000000
),而 APB1PERIPH_BASE
是属于第一个高性能总线 (APB1) 的寄存器的基地址,那么通过这样的宏定义,你可以在代码中使用 APB1PERIPH_BASE
,而不必关心具体的硬件寄存器基地址。
#define APB2PERIPH_BASE (PERIPH_BASE+0x10000)
这里APB2PERIPH_BASE为第二个高性能总线(APB2)的寄存器基地址,它在STM32F103的存储器映射表中,在基地址的基础上(PERIPH_BASE
)偏移了0x10000(AFIO开始当作APB2总线基地址)这样可以在代码中使用APB2PERIPH_BASE,而不必关心具体的硬件寄存器基地址。
#define AHBPERIPH_BASE (PERIPH_BASE+0x20000)
通过这种方式你可以得到 AHBPERIPH_BASE
,其值为 0x40000000 + 0x20000
,表示 AHB 总线上的寄存器基地址。(DMA1开始当作AHB总线基地址)
由于之前已经宏定义过#define PERIPH_BASE ((unsigned int*)0x40000000)
所以这里可以不写0x40000000而是使用PERIPH_BASE来替换,提高代码可读性
#define RCC_BASE (AHBPERIPH_BASE+0x1000)
复位和时钟控制(RCC)在AHBPERIPH_BASE的基础上偏移了0x1000
#define GPIOB_BASE (APB2PERIPH_BASE+0x0C00)
GPIOB端口的基地址在APB2PERIPH_BASE的基础上偏移了0x0C00
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18)
*(unsigned int*)(RCC_BASE + 0x18)
是一个将地址 RCC_BASE + 0x18
转换为无符号整数指针,然后通过指针解引用访问该地址的值的表达式。这通常用于访问嵌入式系统中的硬件寄存器,特别是在使用寄存器映射的方式时。
让我们逐步解释这个表达式:
RCC_BASE
:是一个基地址,通常表示某个寄存器或寄存器组的基地址。在这里, RCC_BASE
是时钟控制寄存器(RCC)的基地址。
RCC_BASE + 0x18
:表示基地址偏移了 0x18
字节,即指向了寄存器组中的某个具体寄存器。在这是 RCC_APB2ENR 寄存器,用于控制和配置微控制器上的某些外设的时钟。
(unsigned int*)
:将地址转换为指向无符号整数 (unsigned int
) 的指针类型。
*(unsigned int*)(RCC_BASE + 0x18)
:通过指针解引用操作,访问 RCC_BASE + 0x18
地址上的值。
这种操作通常用于读取或写入硬件寄存器的值。在嵌入式系统中,这样的寄存器用于配置和控制设备的各种功能,例如时钟频率、外设使能等。
需要注意的是,直接访问硬件寄存器需要小心,确保了解硬件规格,避免不正确的配置,以及确保不会导致系统不稳定。在实际使用中,通常会使用芯片厂商提供的头文件或文档,以确保对寄存器的正确访问。
最后通过#define宏定义成RCC_APB2ENR,通过RCC_APB2ENR来读取或写入硬件寄存器的值
#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE+0x00)
将地址(GPIOB_BASE+0x00)强制类型转换为指针使用 * 过指针解引用操作,访问 GPIOB_BASE+0x00地址上的值。
最后通过#define宏定义成GPIOB_CRL,通过GPIOB_CRL来读取或写入硬件寄存器的值
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE+0x04)
将地址(GPIOB_BASE+0x04)强制类型转换为指针使用 * 过指针解引用操作,访问 GPIOB_BASE+0x04地址上的值。
最后通过#define宏定义成GPIOB_CRH,通过GPIOB_CRH来读取或写入硬件寄存器的值
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
将地址(GPIOB_BASE+0x0C)强制类型转换为指针使用 * 过指针解引用操作,访问 GPIOB_BASE+0x0C地址上的值。
最后通过#define宏定义成GPIOB_ODR,通过GPIOB_ODR来读取或写入硬件寄存器的值
#define GPIOB_BSRR *(unsigned int*)(GPIOB_BASE+0x10)
将地址(GPIOB_BASE+0x10)强制类型转换为指针使用 * 过指针解引用操作,访问 GPIOB_BASE+0x10地址上的值。
最后通过#define宏定义成GPIOB_BSRR,通过GPIOB_BSRR来读取或写入硬件寄存器的值