5 存储器映射和寄存器

文章目录

  • 5.3 芯片内核
    • 5.3.1 ICache
    • 5.3.2 DCache
    • 5.3.3 FlexRAM
  • 5.4 存储器映射
    • 5.4.1 存储器功能划分
      • 5.4.1.1 存储器 Block0 内部区域功能划分
      • 5.4.1.2 储存器 Block1 内部区域功能划分
      • 5.4.1.3 储存器 Block2 内部区域功能划分
  • 5.5 寄存器映射
    • 5.5.1 GPIO1的输出数据寄存器
    • 5.5.2 RT1052的外设地址映射
      • 5.5.2.1 外设基地址
      • 5.5.2.2 外设寄存器
      • 5.5.2.3 寄存器说明
      • 5.5.2.4 C语言对寄存器的封装
      • 5.5.2.5 封装寄存器列表
      • 5.5.2.6 修改寄存器的位操作方法

5.3 芯片内核

RT1052 采用的是 Cortex-M7 内核,内核即 CPU,由 ARM 公司设计。
5 存储器映射和寄存器_第1张图片

  • 芯片内核和外设之间通过各种总线连接
  • 内核通过 TCM 总线访问芯片内部的 SRAM 存储器,从中加载代码指令执行
  • 内核通过 AHBP 总线跟芯片上的 GPIO、UART 等外设进行交互
  • 另外内核还可以通过 AXIM 总线接口连接芯片外部的存储器,扩展存储空间。

该芯片并没有像传统的 MCU 一样集成内部 FLASH 存储器,因此该芯片必须依靠一个外部 FLASH 长期保存程序代码。

  • 在芯片上电后,它可以直接执行在外部 FLASH 中的代码

5.3.1 ICache

Instruction Cache 指令缓存,i.MX RT 系列芯片中其大小为32KB,内核访问该存储器有着极高的速度。

  • 作用是缓存要执行的指令
  • 内核根据代码的运行情况,预先从其它存储器(如外部 Flash,外部 SDRAM 等)加载可能会被执行的代码存储至 ICache
  • 据官方统计,ICache 指令缓存的命中率能达到 98% 以上,这就是为什么 i.MX RT 系列芯片代码存储在外部的 FLASH,运行速度依然这么快。

5.3.2 DCache

Data Cache 数据缓存,在 i.MX RT 系列芯片中其大小为 32KB,它与 ICache 的功能类似,起到缓存的作用,区别只是 ICache 专用于存储指令,DCache 专用于存储数据。

5.3.3 FlexRAM

灵活RAM。在 i.MX RT 系列芯片中其大小为 512KB,可以把它理解成传统MCU 的内部 SRAM 存储器。

  • 它附加了可划分功能区域的配置,分别可以把这内部 SRAM分为专用于存储指令的 ITCM,专用于存储数据的 DTCM 以及通用功能的 OCRAM。

内核使用不同的总线访问这些不同的存储器,因而访问速度有差异:
5 存储器映射和寄存器_第2张图片
ICache 和 DCache 是内核自动使用,用户无法访问的。而 ITCM、DTCM 及 OCRAM 是用户可根据具体地址进行访问,开发程序时应根据它们的特性加以利用。

5.4 存储器映射

SRAM、片上外设及外部存储器,这些功能部件共同排列在一个4GB 的地址空间内。

  • 存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射

Cortex-M7 存储器映射:
5 存储器映射和寄存器_第3张图片

5.4.1 存储器功能划分

在这 4GB 的地址空间中,ARM 已经粗线条的平均分成了 8 个块,,每个块也都规定了用途
5 存储器映射和寄存器_第4张图片大部分块的大小都有 512MB 以上,显然这是非常大的。在这 8 个 Block 里面,有这 3 个块非常重要

  • Block0 主要用于存储程序代码,一般采用 FLASH 存储器
  • Block1 主要用于运行时的内存,一般采用 SRAM 存储器,
  • Block2 用来设计成片上的外设,内核通过相应的地址访问片上外设。

5.4.1.1 存储器 Block0 内部区域功能划分

根据 ARM 内核的设计,Block0 主要用于存储程序代码,在 i.MX RT1052 芯片内部又把这部分划分了几个类型。
5 存储器映射和寄存器_第5张图片ITCM 是 Instruction Tightly-Coupled Memory 的缩写,译为指令紧耦合内存。所谓紧耦合是指该内存与内核连接紧密,有非常高的访问速度。

  • 该内存专用于缓存指令。
  • 希望有着极高执行速度的代码,可以要求内核上电后把相应的代码从外部 FLASH 加载至 ITCM

ROMCP,这是一小段 ROM 空间,用于存储芯片启动时的加载代码,即 bootloader,bootloader 负责把指令从外部存储器加载至 ITCM。

SEMC 及 FlexSPI 是 RT1052 可用于控制外部并行及串行 NorFlash 的两个外设,此处把它们映射到此代码空间,是为了支持 XIP 功能(即指令直接在 NorFlash 上运行,不需要加载到内部的 ITCM)。

5.4.1.2 储存器 Block1 内部区域功能划分

Block1 用于设计片内的 SRAM,也就是芯片运行时的内存,在 i.MX RT1052 芯片内部把这部分划分了两种 RAM 类型
5 存储器映射和寄存器_第6张图片第一种类型为 DTCM,是 Data Tightly-Coupled Memory 的缩写,译为数据紧耦合内存,它跟 ITCM类似,有着极高的访问速度,不过它是专门用来存储程序数据的,即代码中变量的存储位置。
第二种类型为 OCRAM,它是 On-chip RAM 的缩写,即片上内存,可以完全把它理解为传统 MCU的内部 SRAM,它没有像 ITCM 和 DTCM 的专用限制,可用于存储指令和数据(通用目的)。

  • ITCM、DTCM 及 OCRAM 地址范围均分配了 512KB,但这并不是说这三种存储器都有 512KB 大小。
  • 实际上这三种存储器共享内部 FlexRAM 的空间,而这个内部 FlexRAM 空间在 RT1052 芯片中为 512KB,在 RT1060 芯片中为 1MB。
  • 这三种存储器的空间是可以动态调整的。T1052 中,默认 ITCM 和 DTCM 各占 128KB,OCRAM占 256KB。
  • 一共有 16 种配置方式,具体可参考《Using the i.MX RT FlexRAM》文档。

5.4.1.3 储存器 Block2 内部区域功能划分

Block2 用于设计片内的外设,在 RT1052 芯片中,它的外设使用 4 条总线与内核进行连接:
5 存储器映射和寄存器_第7张图片AIPS 是 ARM IP Bus 的缩写,它一边与内核 AHB 总线连接,另一边与片上的各种外设连接

  • AIPS1~4 即连接了各种各样的外设,此处每条总线划分的地址范围各为 1MB,内核根据地址可以访问相应总线下的外设

5 存储器映射和寄存器_第8张图片最右一栏是挂载在 AIPS-2 总线上的外设名称。
阴影处为例,它表示一个名为 GPIO1 的外设的内存地址分配情况

  • GPIO1 外设下又包含了 RT1052 芯片中 32 个相关的引脚
  • 被分配的内存地址为 0x401B8000~0x401BBFFF
  • 通过访问这些地址就可以控制这 32 个引脚了

5.5 寄存器映射

在上述存储器 Block2 这块区域,设计的是片上外设。

  • 在相应的地址空间内它们以四个字节为一个单元,共 32bit
  • 每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作

我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元。

  • 以功能为名给这个内存单元取一个别名,这个别名对应的内存区就是我们经常说的寄存器。
  • 给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。

5.5.1 GPIO1的输出数据寄存器

GPIO1 端口的输出数据寄存器 DR 的地址是 0x401B 8000。

  • DR 寄存器是 32bit,对应着 32 个外部 IO

C语言访问:

// GPIO1 端口全部输出 高电平
*(unsigned int*)(0x401B8000) = 0xFFFFFFFF;
  • 0x401B 8000 在我们看来是 GPIO1 端口数据输出寄存器 DR 的地址,但是在编译器看来,这只是一个普通的变量,是一个立即数
  • 要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针,即 (unsigned int *)0x401B 8000
  • 再对这个指针进行 * 操作

寄存器访问:

// GPIO1 端口全部输出 高电平
#define GPIO1_DR (unsigned int*)(0x401B8000)
*GPIOF_DR = 0xFFFFFFFF;

为了方便操作,我们干脆把指针操作“*”也定义到寄存器别名里面

// GPIO1 端口全部输出 高电平
#define GPIO1_DR *(unsigned int*)(0x401B8000)
GPIOF_DR = 0xFFFFFFFF;

5.5.2 RT1052的外设地址映射

片上外设区的四条 AIPS 总线挂载着不同的外设,相应总线的最低地址我们称为该总线的基地址。
5 存储器映射和寄存器_第9张图片

5.5.2.1 外设基地址

总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为“XX外设基地址”,也叫 XX 外设的边界地址。

  • 有关 RT1052 外设的边界地址请参考《IMXRT1050RM》(参考手册)的第 2 章中的存储器映射的表。

以 GPIO 这个外设来讲解外设的基地址:
5 存储器映射和寄存器_第10张图片可见:

  • GPIO1~GPIO4 的外设基地址都位于 AIPS-2 总线地址的范围
  • GPIO5 比较特殊,它属于 AIPS-1 总线

5.5.2.2 外设寄存器

GPIO 是通用输入输出端口的简称,简单来说就是 RT1052 可控制的引脚。

  • GPIO 有很多个寄存器,每一个都有特定的功能。每个寄存器为 32bit,占 4 个字节
  • 寄存器的位置可以用相对该外设基地址的偏移地址来描述

GPIO1 端口的寄存器地址列表:
5 存储器映射和寄存器_第11张图片数据寄存器GPIO1_DR是GPIO1中的首个寄存器,所以它的寄存器地址与GPIO1的外设基地址相同,为0x401B8000。相对GPIO1基地址的偏移为 0。

紧挨着的是方向寄存器GPIO1_GDIR,它的地址也相对 GPIO1_DR 增加了 4,

GPIO2 基地址:0x401B 8000+(2-1)×0x4000 = 0x401B C000
IMR 寄存器相对基地址的偏移为 0x14,所以:
GPIO2_IMR 寄存器地址:0x401B C000 + 0x14 = 0x401B C014

5.5.2.3 寄存器说明

以“GPIO 中断配置寄存器 GPIO_ICR1”为例
5 存储器映射和寄存器_第12张图片偏移地址

  • 偏移地址是指本寄存器相对于这个外设的基地址的偏移。
  • 本寄存器的偏移地址是 0x0C,从参考手册中我们可以查到 GPIO1 外设的基地址为 0x401B 8000 ,我们就可以算出GPIO1 的这个 GPIO1_ICR1 寄存器的地址为:0x401B 8000+0x0C

寄存器功能说明。
寄存器位表:

  • 表上方的数字为位编号,中间为位名称,侧面为读写权限(其中 W 表示只写,R 表示只读,RW 表示可读写)
  • 表中最下一栏是对应寄存器位复位后的默认值,每位的值用 0 或 1 表示,本寄存器的复位值为全 0。

配置域功能说明:
详细介绍了寄存器每一个位的功能。

5.5.2.4 C语言对寄存器的封装

为了方便理解和记忆,我们把外设基地址和寄存器地址都以相应的宏定义起来

/* GPIO - 外设基地址 */
/** GPIO1 外设基地址 */
#define GPIO1_BASE (0x401B8000u)
/** GPIO2 外设基地址 */
#define GPIO2_BASE (0x401BC000u)
/** GPIO3 外设基地址 */
#define GPIO3_BASE (0x401C0000u)
/** GPIO4 外设基地址 */
#define GPIO4_BASE (0x401C4000u)
/** GPIO5 外设基地址 */
#define GPIO5_BASE (0x400C0000u)

在外设基地址上加入各寄存器的地址偏移,得到特定寄存器的地址。
一旦有了具体地址,就可以用指针进行读写操作:

1 /* 控制 GPIO1 引脚 6 配置为高电平引起中断
2 (GPIO1_ICR1 寄存器的 ICR6 设置为 01b ,即 0x01) */
3 /* 先对配置域 ICR6 的 2 个数据位清 0 */
4 *(unsigned int *)GPIO1_ICR1 &= ~(0x3<<(2*6));
5 /* 给配置域 ICR6 的 2 个数据位赋值 01b */
6 *(unsigned int *)GPIO1_ICR1 |= (0x01<<(2*6));
7
8 /* 控制 GPIO1 引脚 6 配置为上升沿引起中断
9 (GPIO1_ICR1 寄存器的 ICR6 设置为 10b ,即 0x02) */
10 /* 先对配置域 ICR6 的 2 个数据位清 0 */
11 *(unsigned int *)GPIO1_ICR1 &= ~(0x3<<(2*6));
12 /* 给配置域 ICR6 的 2 个数据位赋值 10b */
13 *(unsigned int *)GPIO1_ICR1 |= (0x02<<(2*6));
14
15 unsigned int temp;
16 /* 控制 GPIO1 端口所有引脚的电平 ( 读 DR 寄存器 ) */
17 temp = *(unsigned int *)GPIO1_DR;

5.5.2.5 封装寄存器列表

引入 C 语言中的结构体语法对寄存器进行封装:

1 typedef unsigned int uint32_t; /* 无符号 32 位变量 */
2 typedef unsigned short int uint16_t; /* 无符号 16 位变量 */
3
4 /** GPIO - 寄存器列表 */
5 typedef struct {
6 uint32_t DR; /**< GPIO 数据寄存器 , 地址偏移 : 0x0 */
7 uint32_t GDIR; /**< GPIO 方向寄存器 , 地址偏移 : 0x4 */
8 uint32_t PSR; /**< GPIO 状态寄存器 , 地址偏移 : 0x8 */
9 uint32_t ICR1; /**< GPIO 中断配置寄存器 1, 地址偏移 : 0xC */
10 uint32_t ICR2; /**< GPIO 中断配置寄存器 2, 地址偏移 : 0x10 */
11 uint32_t IMR; /**< GPIO 中断掩码寄存器 , 地址偏移 : 0x14 */
12 uint32_t ISR; /**< GPIO 中断状态寄存器 , 地址偏移 : 0x18 */
13 uint32_t EDGE_SEL; /**< GPIO 边沿选择寄存器 , 地址偏移 : 0x1C */
14 } GPIO_Type;

通过结构体指针访问寄存器:

1 GPIO_Type * GPIOx; // 定义一个 GPIO_Type 型结构体指针变量 GPIOx
2 GPIOx = GPIO1_BASE; // 把指针地址设置为宏 GPIO1_BASE 地址
3 GPIOx->DR = 0xFFFF; // 通过指针访问并修改 GPIO1_DR 寄存器
4 GPIOx->GDIR = 0xFFFFFFFF; // 修改 GPIO1_GDIR 寄存器
5 GPIOx->ICR1 =0xFFFFFFFF; // 修改 GPIO1_ICR1 寄存器
7 uint32_t temp;
8 temp = GPIOx->DR; // 读取 GPIOF_DR 寄存器的值到变量 temp 中

先 用 GPIO_Type 类 型 定 义 一 个 结 构 体 指 针 GPIOx, 并 让 指 针 指 向 地 址GPIO1_BASE(0x401B 8000)
GPIO 端口基地址址针:

 1 /* 使用 GPIO_Type 把地址强制转换成指针 */
2 #define GPIO1 ((GPIO_Type *) GPIO1_BASE)
3 #define GPIO2 ((GPIO_Type *) GPIO2_BASE)
4 #define GPIO3 ((GPIO_Type *) GPIO3_BASE)
5 #define GPIO4 ((GPIO_Type *) GPIO4_BASE)
6 #define GPIO5 ((GPIO_Type *) GPIO5_BASE)

5.5.2.6 修改寄存器的位操作方法

把变量 a 的某一位清零,且其它位不变

// 定义一个变量 a = 1001 1111 b ( 二进制数 )
2 unsigned char a = 0x9f;
3
4 // 对 bit2 清零
5
6 a &= ~(1<<2);
7
8 // 括号中的 1 左移两位, (1<<2) 得二进制数: 0000 0100 b
9 // 按位取反, ~(1<<2) 得 1111 1011 b
10 // 假如 a 中原来的值为二进制数: a = 1001 1111 b
11 // 所得的数与 a 作”位与 & ”运算, a = (1001 1111 b)&(1111 1011 b),
12 // 经过运算后, a 的值 a=1001 1011 b
13 // a 的 bit2 位被被零,而其它位不变

要设置寄存器的位参数:

1 //a = 1000 0011 b
2 // 此时对清零后的第 2 组 bit4 、 bit5 设置成二进制数“ 01 b ”
3
4 a |= (1<<2*2);
5 //a = 1001 0011 b ,成功设置了第 2 组的值,其它组不变

对变量的某位取反:

//a = 1001 0011 b
2 // 把 bit6 取反,其它位不变
3
4 a ^=(1<<6);
5 //a = 1101 0011 b

你可能感兴趣的:(嵌入式开发,嵌入式硬件)