目录
一、什么是寄存器以及什么是寄存器映射
在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit, 每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到 每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通 过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的 不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个 给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
二、查找地址
如果我们需要找到某个寄存器的地址,那应该如何查找呢?我们在初学者时,通常采用查阅《STM32F10xxx参考手册》的方式。例如我们要查找GPIOA中的P1端口,通常有三步。
第一步 找到GPIOB的基地址
首先我们找到GPIOA所有端口所在的地址范围

我们看到GPIOA相关的寄存器所在的范围为0x4001 0800到 0x4001 0BFF
第二步 找到端口输入寄存器的地址偏移
继续查阅资料发现端口输入寄存器的地址偏移为0x08,也就是0x4001 0800 + 8=0x4001 0808

第三步 找到具体端口号
继续查表,发现GPIOA的p1口位于从右往左数的第二个

然后我们就可以直接访问这个地址
unsigned int *pGPIOA_IDR = (unsigned int *)0x40010808;
unsigned char PA1 = *pGPIOA_IDR & 0x2;//取出从右往左数的第1位
示例一 让 GPIOB 的 16 个 IO 都输出高电平
我们找到 GPIOB 端口的输出数据寄存器 ODR 的地址是 0x4001 0C0C,ODR 寄存器是 32bit,低 16bit 有效,对应着 16 个外部 IO,写 0/1 对应的的 IO 则输出低/高电平。现在我们通过 C 语言指 针的操作方式,让 GPIOB 的 16 个 IO 都输出高电平,具体代码如下
1 // GPIOB 端口全部输出 高电平
2 *(unsigned int*)(0x4001 0C0C) = 0xFFFF;
示例二 点亮led灯
通过访问寄存器地址的方式点亮led灯,我选择的端口是GPIOA的p8口
首先要配置时钟使能
通过查找参考手册发现时钟控制名字叫做RCC,属于AHB总线。所有GPIO都属于APB2总线。

GPIOA的地址是从0x4001 0800开始,复位和时钟控制RCC的地址从0x4002 1000开始,然后查询资料发现APB2外设时钟使能寄存器(RCC_APB2ENR)偏移地址为0x18,所以APB2的地址就是0x4002 1018

看手册RCC_APB2ENR,位2是IOPAEN,名字是IO端口A时钟使能,就是我们想要的。把RCC_APB2ENR的位2赋值为1,就是开启GPIOA时钟。
将GPIOA中的p1设置为输出,STM32中,用端口配置低寄存器(GPIOx_CRL)来配置引脚Px0-Px7, 用端口配置高寄存器(GPIOx_CRH)来配置引脚Px8-Px15。
配置引脚PA1,使用的寄存器是GPIOA_CRH。偏移地址为0x00,所以GPIOA_CRH,它的地址是0x4001 0804
找到需要操作的寄存器后,把它配置为通用输出。
配置为输出模式,通用推挽输出。速度暂时不关注,随便填写一个50MHz吧,其它速度当然也可以。所以设置GPIOB_CRH的MODE8与CNF8为0b0011,即0x3。此寄存器中其它的位暂时不做修改,使用默认值,也就是GPIOB_CRH设置为:0x4444 4443。
复位值是0x4444 4444,并不是0x0000 0000。所谓的复位值,就是指如果没有操作这个寄存器时,寄存器存放的默认值。复位值按位拆分0x4 = 0b0100,0x表示16进制,0b表示二进制,也就是默认CNF 01,MODE 00,是浮空输入。
推挽输出:可以输出高,低电平,连接数字器件;推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。
开漏输出:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行,适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内)。
要点亮LED灯我们需要把PA1口设为低电平,查询发现地址偏移为0x0C(中文版翻译有误,写成了0Ch,英文原版为0x0C),所以地址为0x4001 080c

做好所有准备工作后,开始编写程序
int main(void)
{
unsigned int *pRCC_APB2ENR = (unsigned int *)0x40021018;
//定义指针*pRCC_APB2ENR指向地址0x40021018
unsigned int *pGPIOA_CRH = (unsigned int *)0x40010804;
//定义指针*pGPIOA_CRH指向地址0x40010804
unsigned int *pGPIOA_ODR = (unsigned int *)0x4001080c;
//定义指针*pGPIOA_ODR指向地址0x4001080c
*pRCC_APB2ENR = 0x00000004;//把RCC_APB2ENR的位2赋值为1,就是开启GPIOA时钟
*pGPIOA_CRH = 0x44444443;//配置GPIOA为输出模式,通用推挽输出
*pGPIOA_ODR = 0x00000000;//将GPIOA端口高8位输出数据寄存器全部置为低电平
return 0;
}
效果如下

示例三 流水灯
//--------------APB2使能时钟寄存器------------------------
#define RCC_AP2ENR *((unsigned int*)0x40021018)
//----------------GPIOA配置寄存器 ------------------------
#define GPIOA_CRH *((unsigned int*)0x40010804)
#define GPIOA_ORD *((unsigned int*)0x4001080C)
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRH *((unsigned int*)0x40010C04)
#define GPIOB_ORD *((unsigned int*)0x40010C0C)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH *((unsigned int*)0x40011004)
#define GPIOC_ORD *((unsigned int*)0x4001100C)
int main()
{
RCC_AP2ENR|=1<<2; //APB2-GPIOA外设时钟使能
RCC_AP2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_AP2ENR|=1<<4; //APB2-GPIOC外设时钟使能
GPIOA_CRH&=0x0FFFFFF0; //设置位 清零
GPIOA_CRH|=0x10000000; //PA15推挽输出
GPIOA_ORD|=1<<15; //设置初始灯为灭
GPIOB_CRH&=0x0FFFFFFF; //设置位 清零
GPIOB_CRH|=0x10000000; //PB15推挽输出
GPIOB_ORD|=1<<15; //设置初始灯为灭
GPIOC_CRH&=0x0FFFFFFF; //设置位 清零
GPIOC_CRH|=0x10000000; //PC15推挽输出
GPIOC_ORD|=1<<15; //设置初始灯为灭
while(1)
{
GPIOA_ORD=0x0<<15; //PA15低电平
HAL_Delay(1000);
GPIOA_ORD=0x1<<15; //PA15高电平
GPIOB_ORD=0x0<<15; //PB15低电平
HAL_Delay(1000);
GPIOB_ORD=0x1<<15; //PB15高电平
GPIOC_ORD=0x0<<15; //PC15低电平
HAL_Delay(1000);
GPIOC_ORD=0x1<<15; //PC15高电平
}
}
烧录后实物如下

总结
学习了寄存器地址映射的相关知识,能够对寄存器地址进行赋值或修改数值,学会了修改寄存器输出模式,最后实现了使用寄存器实现流水灯,对寄存器操作有了一定的了解,为接下来的库函数理解打下了一定基础
本文详细介绍了STM32的寄存器及寄存器映射概念,通过查找地址的三个步骤展示了如何定位GPIO寄存器,并提供了让GPIOB全输出高电平、点亮LED及流水灯的示例,加深了对寄存器操作的理解。

5339

被折叠的 条评论
为什么被折叠?



