存储器与寄存器(2)

本文深入解析STM32存储器的分区布局,特别是Block0、Block1和Block2的详细功能划分。同时讲解了寄存器的概念、寄存器映射以及如何通过C语言操作STM32的外设寄存器,包括总线地址计算和GPIO外设寄存器示例。
摘要由CSDN通过智能技术生成

目录

 

一、存储器映射

1.1、存储器映射

1.2、存储区域划分

1.2.1、Block0内部区域功能划分

1.2.2、 Block1内部区域功能划分

1.2.3、 Block2 内部区域功能划分

二、什么是寄存器和寄存器映射

2.1、概述

三、如何访问stm32寄存器内容

3.1、stm32外设地址映射

3.1.1、总线基地址

3.1.2、外设基地址

3.1.3、外设寄存器地址

3.2、使用c语言封装寄存器


一、存储器映射

1.1、存储器映射

存储器本身不具备地址信息,它的地址是由芯片厂商或用户分配。

给存储器分配地址的过程叫出存储器映射。如果在分配一个地址则叫重映射。

 

1.2、存储区域划分

开发版4G容量的ARM按功能划分表

Block0、Block1、Block2包含了STM32芯片内部Flash、RAM和片上外设。

Block0 主要用于设计片内的 FLASH,STM32F103 系列芯片内部 FLASH 最大是 512KB,STM32F103ZET6的FLASH也是512KB。

1.2.1、Block0内部区域功能划分

  • 0x0000 0000-0x0007 FFFF:取决于 BOOT 引脚,为 FLASH、系统存储器、SRAM 的别名。
  • 0x0008 0000-0x07FF FFFF:预留。
  • 0x0800 0000-0x0807 FFFF:片内 FLASH,我们编写的程序就放在这一区域(512KB)。
  • 0x0808 0000-0x1FFF EFFF:预留。
  • 0x1FFF F000-0x1FFF F7FF:系统存储器,里面存放的是 ST 出厂时烧写好的isp 自举程序,用户无法改动。使用串口下载的时候需要用到这部分程序。
  • 0x1FFF F800-0x1FFF F80F:选项字节,用于配置读写保护、BOR 级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。当芯片不小心被锁住之后,我们可以从 RAM 里面启动来修改这部分相应的寄存器位。
  • 0x1FFF F810-0x1FFF FFFF:预留

1.2.2、 Block1内部区域功能划分

Block1用于设计片内的SRAM,我们使用的 STM32F103ZET6 的 SRAM是64KB。

  • 0x2000 0000-0x2000 FFFF:SRAM,容量为 64KB。
  • 0x2001 0000-0x3FFF FFFF:预留。

1.2.3、 Block2 内部区域功能划分

Block2 用于设计片内外设,根据外设总线速度的不同,Block2 被划分为 AHB和 APB 两部分,APB 又被分成 APB1 和 APB2 总线

  • 0x4000 0000-0x4000 77FF:APB1 总线外设。
  • 0x4000 7800-0x4000 FFFF:预留。
  • 0x4001 0000-0x4001 3FFF:APB2 总线外设。
  • 0x4001 4000-0x4001 7FFF:预留。
  • 0x4001 8000-0x4002 33FF:AHB 总线外设。
  • 0x4002 4400-0x5FFF FFFF:预留。
  • 在Block3/4/5中还包含了FSMC扩展区域,这3个块可用于扩展外部存储器,比如 SRAM,NORFLASH 和 NANDFLASH 等。

二、什么是寄存器和寄存器映射

2.1、概述

  • ARM按功能划分为八个功能区域,Cortex-M3内核是32位、因此存储器内部是以四个字节为一个单元
  • 把每个单元的功能作为名,给这个内存取一个别名,这个别名就是我们经常说的寄存器。然后通过 C语言指针来操作这些寄存器即可。
  • 实现特定功能的内存即寄存器
  • 给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。

三、如何访问stm32寄存器内容

寄存器就是一些有特定功能的内存单元,所以要访问 STM32 寄存器也就是操作 STM32 的内存单元,根据 C 语言指针的特点,可以使用指针来操作STM32 的内存单元。

3.1、stm32外设地址映射

片上外设区分为四条总线,根据外设速度的不同,不同总线挂载着不同的外设,APB1 挂载低速外设APB2 和 AHB 挂载高速外设。相应总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。APB1总线的地址最低,因此片上外设就从这这个地址开始,也称外设基地址。

3.1.1、总线基地址

从存储器映射那张图的 Block2 可以看到,分为 4 大块,每块都有一个起始地址,这个起始地址就是基地址,然后到下一块起始地址的时候就会和前一块地址出现偏差,这个差值就是偏移量,即相对基地址的偏移量。

从上图可以看到 APB1 总线基地址是 0x4000 0000,相对外设基地址的偏移量是 0,所以此总线也是外设 Block2 的基地址。

3.1.2、外设基地址

每条总线上都会挂接着很多的外设、这些外设也会有自己的地址范围XXX外设的首个地址即最低地址就是 XXX 外设的基地址,也称作 XXX 边界地址。

GPIO外设地址

外设 GPIOx 都是挂接在 APB2 总线上的,属于高速的外设。而 APB2 总线的基地址是 0x4001 0000,故 GPIOA 的相对 APB2 总线的地址偏移是800。


3.1.3、外设寄存器地址

XXX 外设的寄存器就分布在其对应的外设地址范围内。以 GPIO 外设为例,GPIO 是通用输入输出端口的简称,可以通过软件来控制其输入和输出。GPIO 有很多个寄存器,每一个都有特定的功能。每个寄存器为 32bit,占四个字节,这些寄存器都是按顺序依次排列在外设的基地址上。寄存器的位置都以相对该外设基地址的偏移地址来描述。

  • 1号红色框表示的是相对 GPIOx 地址的偏移值,比如现在我们使用的是GPIOB 外设,其基地址是 0x4001 0C00,那么本寄存器 GPIOx_BSRR 地址=0x4001 0C00+0x10=0x4001 0C10。对于其他的 GPIO 外设也是一个原理。
  • 2号、3号红色框表示的是寄存器的位表。其中2 表示寄存器编号,因为一个寄存器是 32bit,所以范围是 0-31。3 表示的是相应位的权限,w:只写,r:只读,rw:可读可写。本寄存器位权限是 w,所以只能写,如果试图读本寄存器,是无法保证读取到它真正内容的。而有的寄存器位权限为只读,一般是用于表示STM32 外设的某种工作状态的,由 STM32 硬件自动更改,通过读取那些寄存器位来判断外设的工作状态。
  • 4号红色框  是寄存器位功能说明。这个也是寄存器说明中最重要的部分,它详细介绍了寄存器每一个位的功能。例如本寄存器中有两种寄存器位,分别为BRy 及 BSy,其中的 y 数值表示的是管脚号,可以是 0-15。如 BR0、BS0 用于控制 GPIOx 的第 0 个引脚若 x 表示 GPIOB,那就是控制 GPIOB 的第 0 引脚,而 BR1、BS1 就是控制 GPIOB 第 1 个引脚。其中 BRy 引脚的说明是“0:不会对相应的ODRx 位执行任何操作;1:对相应 ODRx 位进行复位”。这里的“复位”是将该位设置为 0 的意思,而“置位”表示将该位设置为 1;说明中的 ODRx 是另一个寄存器的寄存器位,我们只需要知道 ODRx 位为 1的时候,对应的引脚 x 输出高电平,为 0 的时候对应的引脚输出低电平即可(感兴趣的读者可以查询该寄存器 GPIOx_ODR 的说明了解)。所以,如果对 BR0 写入“1”的话,那么 GPIOx 的第 0 个引脚就会输出“低电平”,但是对 BR0 写入“0”的话,却不会影响 ODR0 位,所以引脚电平不会改变。要想该引脚输出“高电平”,就需要对“ BS0”位写入“ 1”,寄存器位 BSy 与 BRy 是相反的操作。

3.2、使用c语言封装寄存器

//定义外设基地址
#define PERIPH_BASE ((unsigned int)0x40000000) 

//定义 APB2 总线基地址
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) 

//定义 GPIOB 外设基地址
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00) 

//定义寄存器基地址  使用C语言宏定义对GPIO端口寄存器的地址进行定义

// 两个32位的配置寄存器
#define GPIOC_CRL   *(unsigned int*)(GPIOC_BASE+0x00)    //端口配置低位寄存器
#define GPIOC_CRH   *(unsigned int*)(GPIOC_BASE+0x04)    // 端口配置高位寄存器
//两个32位数据寄存器
#define GPIO_IDR    *(unsigned int*)(GPIOC_BASE+0x08)    // 端口输入数据寄存器
#define GPIOC_ODR   *(unsigned int*)(GPIOC_BASE+0x0C)    //端口输出数据寄存器
//一个32位置位/复位寄存器
#define GPIO_BSRR   *(unsigned int*)(GPIOC_BASE+0x10)    // 端口设置/清除寄存器
//复位寄存器
#define GPIO_BRR    *(unsigned int*)(GPIOC_BASE+0x14)    // 端口清除寄存器
//一个32位锁定寄存器
#define GPIO_LCKR   *(unsigned int*)(GPIOC_BASE+0x18)   // 端口配置锁定寄存器

无符号整型(unsigned  int)
(1)整型是4个字节(有些编译器不同,可能会是2个),即32位,无符号整型当然也为32位。
(2)既然是32位,无符号整型的取值是32个0~32个1,即:0~4294967295

//控制 GPIOB 第 5 管脚输出一个低电平
 *(unsigned int *)GPIOB_BSRR = (0x01<<(16+5));

该寄存器的 16-31 位是 BR0-BR15 用来设置对应 GPIO0-GPIO15 为低电平,写1 有效,写 0 无效。所以要控制 GPIO5 输出低电平即对该寄存器中的 BR5 位写 1,而 BR5 是在该寄存器高 16 位的第 5 位(从 0 开始数),因此才会有 0x01<<(16+5)。式中“16”表示操作的是该寄存器的高 16 位,“5”表示高 16 位中的哪一位。然后把 1 左移到对应的操作寄存器位上即可。

控制GPIOC引脚0输出低电平(即通过BSRR 寄存器的BR0置1)
*(unsigned int*)GPIOC_BSRR=(0x01<<(16+0))

控制GPIOC引脚0输出高电平(即通过BSRR 寄存器的BSO置1)
*(unsigned int *)GPIOC_BSRR=0x01<<0

读取GPIOC端口所有引脚的电平(读IDR 寄存器)
unsigned int temp;
temp =*(unsigned int *)GPIOC_IDR

 GPIOB_BSRR 的值是这个寄存器的地址,但是编译器不知道它是地址,而是把它当做立即数,所以我们必须要强制转换为(unsigned int *)指针类型才可以对其操作,这一点特别要注意。然后再在前面加上一个“*”作取指针操作,表示对该地址内内容进行写。

3.3、寄存器封装

typedef unsigned int uint32_t; //无符号 32位变量
typedef unsigned short int uint16_t; //无符号16位变量
//GPIO 寄存器列表
typedef struct
{
uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */
uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */
uint32_t IDR; /*GPIO 数据输入寄存器 地址偏移: 0x08 */
uint32_t ODR; /*GPIO 数据输出寄存器 地址偏移: 0x0C */
uint32_t BSRR; /*GPIO 位设置/清除寄存器 地址偏移: 0x10 */
uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */
uint16_t LCKR; /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */

}GPIO_TypeDef

引入C语言中的结构体对寄存器进行封装
  • 这段代码用 typedef 关键字声明了名为 GPIO_TypeDef 的结构体类型,结构体内有 7 个成员变量,变量名正好对应寄存器的名字。C 语言的语法规定,结构体内变量的存储空间是连续的,其中 32 位的变量占用 4 个字节,16 位的变量占用 2 个字节。
  • 我们定义的这个 GPIO_TypeDef ,假如这个结构体的首地址为0x4001 0C00(这也是第一个成员变量 CRL 的地址),那么结构体中第二个成员变量 CRH 的地址即为 0x4001 0C00 +0x04 ,加上的这个 0x04 ,正是代表 CRL所占用的 4 个字节地址的偏移量,其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给出。
  • 这样的地址偏移与 STM32 GPIO 外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存器了,比如我们还是将GPIOB5 输出低电平。
GPIO_TypeDef * GPIOx; //定义一个 GPIO_TypeDef 型结构体指针 GPIOx
GPIOx = GPIOB_BASE; //把指针地址设置为宏 GPIOB_BASE 地址
GPIOx->BSRR =(1<<(16+5)); //通过指针访问并修改 GPIOB_BSRR 寄存器

这段代码先用 GPIO_TypeDef 类型定义一个结构体指针 GPIOx,并让指针指向 GPIOB 基地址 GPIOB_BASE,地址确定下来,然后根据 C 语言访问结构体的内容,用 GPIOx->BSRR 写寄存器。为了操作更简便灵活,我们直接使用宏定义好GPIO_TypeDef 类型的指针,而且指针指向各个 GPIO 端口的首地址,使用时我们直接用该宏访问寄存器即可。

#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
GPIOB->BSRR = (1<<(16+5));
  • 以 GPIO 这个外设为例,何使用 C 语言对寄存器封装。
  • 使用 ST 公司提供的固件库,他们把 STM32 所有外设都已经封装好了,我们只需要调用即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旭日初扬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值