STM32简介
1、STM32 分类
STM32 有很多系列,可以满足市场的各种需求,从内核上分有 Cortex-M0、 M3、 M4和 M7 这几种,每个内核又大概分为主流、高性能和低功耗。单纯从学习的角度出发,可以选择 F1 和 F4, F1 代表了基础型,基于 Cortex-M3 内核,主频为 72MHZ,F4 代表了高性能,基于 Cortex-M4 内核,主频F407 168M,F429 180M。至于 F1, F4(429 系列以上)除了内核不同和主频的提升外,升级的明显特色就是带了 LCD 控制器和摄像头接口,支持 SDRAM,这个区别在项目选型上会被优先考虑。
2、STM32的命名规则
3、引脚分类
4、芯片内部架构
我们看到的 STM32 芯片已经是已经封装好的成品,主要由内核和片上外设组成。若与电脑类比,内核与外设就如同电脑上的 CPU 与主板、内存、显卡、硬盘的关系。
STM32F407 采用的是 Cortex-M4 内核,内核即 CPU,由 ARM 公司设计。 ARM 公司并不生产芯片,而是出售其芯片技术授权。芯片生产厂商(SOC)如 ST、 TI、 Freescale,负责在内核之外设计部件并生产整个芯片,这些内核之外的部件被称为核外外设或片上外设。如 GPIO、 USART(串口)、 I2C、 SPI 等都叫做片上外设。具体见下图:
芯片内核和外设之间通过各种总线连接,其中主控总线有 8 条,被控总线有 7 条,具体见图 6-4。主控总线通过一个总线矩阵来连接被控总线, 总线矩阵用于主控总线之间的访问仲裁管理,仲裁采用循环调度算法。总线之间交叉的时候如果有个圆圈则表示可以通信,没有圆圈则表示不可以通信。比如 S0: I 总线只有跟 M0、 M2 和 M6 这三根被控总线交叉的时候才有圆圈,就表示 S0 只能跟这三根被控总线通信。从功能上来理解, I 总线是指令总线,用来取指,指令指的是编译好的程序指令。我们知道 STM32 有三种启动方式,从 FLASH 启动(包含系统存储器),从内部 SRAM 启动,从外部 RAM 启动,这三种存储器刚好对应的就是 M0、M2 和 M6 这三条总线。
图 6-4 STM32F05xx 和 STM32F407xx 器件的总线接口
5、存储器映射
在图 6-4 中,连接被控总线的是 FLASH, RAM 和片上外设,这些功能部件共同排列在一个 4GB 的地址空间内。我们在编程的时候,操作的也正是这些功能部件。
存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射,具体见图 6-5。 如果给存储器再分配一个地址就叫存储器重映射。
图 6-5 STM32F407 存储器映射
5.1、 存储器区域功能划分
在这 4GB 的地址空间中, ARM 已经粗线条的平均分成了 8 个块,每块 512MB,每个块也都规定了用途,具体分类见表格 6-1。每个块的大小都有 512MB,显然这是非常大的,芯片厂商在每个块的范围内设计各具特色的外设时并不一定都用得完,都是只用了其中的一部分而已。
表格 6-1 存储器功能分类
在这 8 个 Block 里面,有 3 个块非常重要,也是我们最关心的三个块。 Boock0 用来设计成内部 FLASH, Block1 用来设计成内部 RAM, Block2 用来设计成片上的外设,下面我们简单的介绍下这三个 Block 里面的具体区域的功能划分。
(1)存储器 Block0 内部区域功能划分
Block0 主要用于设计片内的 FLASH, F407 系列片内部 FLASH 最大是 1MB,我们使用的 STM32F407ZGT6 的 FLASH 就是 1MB。要在芯片内部集成更大的 FLASH 或者SRAM 都意味着芯片成本的增加,往往片内集成的 FLASH 都不会太大, ST 能在追求性价比的同时做到 1MB 以上,实乃良心之举。 Block0 内部区域的功能划分具体见表格 6-2。
表格 6-2 存储器 Block0 内部区域功能划分
(2)储存器 Block1 内部区域功能划分
Block1 用于设计片内的 SRAM。 F407 内部 SRAM 的大小为 128KB,其中 SRAM1 为112KB, SRAM2 为 16KB。 Block1 内部区域的功能划分具体见表格 6-3。
表格 6-3 存储器 Block1 内部区域功能划分
(3)储存器 Block2 内部区域功能划分
Block2 用于设计片内的外设,根据外设的总线速度不同, Block 被分成了 APB 和 AHB两部分,其中 APB 又被分为 APB1 和 APB2, AHB 分为 AHB1 和 AHB2,具体见表格 6-4。还有一个 AHB3 包含了 Block3/4/5, AHB3 包含的 3 个 Block 用于扩展外部存储器,如SRAM, NORFLASH 和 NANDFLASH 等。
表格 6-4 存储器 Block2 内部区域功能划分
6、寄存器映射
我们知道,存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么叫寄存器映射?寄存器到底是什么?
在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
比如,我们找到 GPIOF 端口的输出数据寄存器 ODR 的地址是 0x4002 1414(至于这个地址如何找到可以先跳过,后面我们会有详细的讲解), ODR 寄存器是 32bit,低 16bit 有效,对应着 16 个外部 IO,写 0/1 对应的的 IO 则输出低/高电平。现在我们通过 C 语言指针的操作方式,让 GPIOF 的 16 个 IO 都输出高电平,具体见代码 6-1。
代码 6-1 通过绝对地址访问内存单元
// GPIOF 端口全部输出 高电平 *(unsigned int*)(0x4002 1414) = 0xFFFF;
0x4002 1414 在我们看来是 GPIOF 端口数据输出寄存器 ODR 的地址,但是在编译器看来,这只是一个普通的变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针,即(unsigned int *)0x4002 1414,然后再对这个指针进行 *操作。
刚刚我们说了,通过绝对地址访问内存单元不好记忆且容易出错,我们可以通过寄存器的方式来操作,具体见代码 6-2。
代码 6-2 通过寄存器别名方式访问内存单元
// GPIOF 端口全部输出 高电平 #define GPIOF_BASE 0x40021400 #define GPIOF_ODR (unsigned int*)(GPIOF_BASE+0x14) *GPIOF_ODR = 0xFF;
为了方便操作,我们干脆把指针操作“*”也定义到寄存器别名里面,具体见代码 6-3。
代码 6-3 通过寄存器别名访问内存单元
// GPIOF 端口全部输出 高电平 #define GPIOF_BASE 0x40021400 #define GPIOF_ODR *(unsigned int*)(GPIOF_BASE+0x14) GPIOF_ODR = 0xFF;