一.前言
单片机的本质其实就是在操作寄存器,其实在学习51单片机的时候更多的操作单片机实质上就是操作寄存器,而STM32的库函数其实就是将众多寄存器分装成一个个库函数去调用。因此了解并学习寄存器是有必要的,能知道单片机内部每一步操作是在干嘛,在后期对单片机的学习会更加得心应手。在前期学习STM32时,只要能知道怎么去使用库函数,配置库函数,能在单片机应用层上熟练运用,不过也要去了解一下寄存器,等后期单片机技术相对成熟时,返回过来学习寄存器,会对单片机底层更加透彻。
二.STM32的系统框架
1.四个驱动单元(CUP)
Cortex-M3内核DCode总线(D-bus)
Cortex-M3内核系统总线System(S-bus)
通用DMA1
通用DMA2
2.四个被动单元(外设)
内部SRAM
内部闪存存储器FLASH
FSMC
AHB到APB的桥,它连接所有的APB外设
3.驱动单元
ICode 总线
ICode 中的 I 表示 Instruction,即指令。内核通过ICode 总线读取内部FLASH代码指令来执行程序,专门用来取指
DCode 总线
DCode中的D表示 Data,即数据,那说明这条总线是用来取数的。我们在写程序的时候,数据有常量和变量两种,常量就是固定不变的,用C语言中的const关键字修饰,是放到内部的FLASH当中的,变量是可变的,不管是全局变量还是局部变量都放在内部的SRAM。因为数据可以被 Dcode总线和 DMA总线访问,所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数
系统总线System
系统总线主要是访问外设的寄存器,我们通常说的寄存器编程,即读写寄存器都是通过这根系统总线来完成的
DMA 总线
DMA 总线与DCode总线一样主要是用来传输数据,但Dcode总线传输数据要占用内核(cpu)的资源,而DMA总线相当于独立于内核cpu但帮助内核cpu传输数据而不用占用内核(cpu)的资源,就是在DMA传输数据的同时内核cpu可以干别的事情比如点亮一个LED灯
总线矩阵
总线矩阵协调内核系统总线和DMA主控总线之间的访问仲裁,仲裁利用轮换算法。因为数据可以被 Dcode 总线和 DMA 总线访问,数据可以是在某个外设的数据寄存器,可以在SRAM,可以在内部的 FLASH。所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数
4. 被动单元
内部FLASH
我们写好的程序编译之后都是一条条指令(二进制代码),存放在 FLASH 中,我们常量或常变量C 语言中的 const 关键字修饰也存放在FLASH
内部SRAM
就是我们常说的电脑内存条,程序函数内部的局部变量和全局变量,堆(malloc分配)栈(局部变量)等的开销都是基于内部的SRAM。内核通过 DCode 总线来访问它
FSMC
FSMC 的英文全称是 Flexible static memory controller,叫灵活的静的存储器控制器,是 STM32F10xx 中一个很有特色的外设通过FSMC我们可以扩展内存,如外部的SRAM,NANDFLASH 和 NORFLASH。但有一点我们要注意的是,FSMC 只能扩展静态的内存,即名称里面的 S:static,不能是动态的内存,比如 SDRAM 就不能扩展
AHB 到 APB 的桥
两个AHB/APB桥在AHB和2个APB总线间提供同步连接。APB1操作速度限于36MHz,APB2操作于全速(最高72MHz),上面挂载着 STM32 各种各样的特色外设。我们经常说的 GPIO、串口、I2C、SPI 这些外设就挂载在这两条总线上,这个是我们学习 STM32 的重点,就是要学会编程这些外设去驱动外部的各种设备
三.存储器映射
存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射
1.STM32的存储空间
存储空间的大小是由芯片内CPU内的地址总线的数量来决定,而stm32芯片内部的总线为32根
内存被划分为一个个的内存单元,每个内存单元的大小是一个字节,为了能有效的访问到内存的每个单元就给内存单元进行编号,编号就被称为该内存单元的地址
分配好地址后,被控单元的 FLASH,RAM,FSMC 和 AHB 到 APB 的桥(即片上外设),这些功能部件共同排列在一个 4GB 的地址空间内。我们在编程的时候,可以通过他们的地址找到他们,然后来操作他们( C语言里的将地址解引用操作 * 取出内容对他们进行数据的读和写)
2.存储区区域功能划分 ![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/1da75a988b7901814aef7f45fd36e99c.png)
在这8 个 Block 里面,最关心的有三块:Block0、Block1、Block2。 Block0 用来设计成内部 FLASH, Block1 用来设计成内部 RAM, Block2 用来设计成片上的外设
四.寄存器映射
1.简单介绍:
加上以下例子更简单清晰:
2.STM32寄存器地址映射
片上外设区分三条总线,根据外设速度的不同,不同总线挂在着不同的外设,APB1挂载低速外设,APB2和AHB挂载高速外设。相应的总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。其中APB1总线的地址最低,片上外设从这里开始,也叫外设基地址。
APB1总线的地址最低,也叫做外设基地址
表格总线基地址的“相对外设基地址偏移”即该总线地址与“片上外设”基地址0x4000 0000的差值。
以GPIO这个外设来讲解外设的基地址,GPIO属于高速的外设,挂载到APB2总线上
GPIO端口A的基地址就是在总线APB2的基地址上偏移地址OX0800所以GPIO端口A的基地址为总线
APB2的基地址+偏移OX0800=OX4001 0800
GPIO有很多个寄存器,每一个都有特定的功能。每个寄存器为32bit,占4个字节,在改外设的基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描述
这里我们以GPIOB端口为例,来说明GPIO都有哪些寄存器,具体见表格GPIOB端口的寄存器地址列表
各个寄存器的地址=外设基地址+寄存器相对于外设基地址的偏移
这里我们以“GPIO端口置位/复位寄存器为例”,教大家如何理解寄存器的说明,具体见GPIO端口置位_复位寄存器说明
1.名称
寄存器说明中首先列出了该寄存器中的名称,“(GPIOx_BSRR)(x=A…E)”这段的意思是该寄存器名为”GPIOx_BSRR“其中的x可以为”A…E“,也就是说这个寄存器说明适用于GPIOA、GPIOB至GPIOE,这些端口都有这样一个寄存器。
2.偏移地址
偏移地址是指本寄存器相对于这个外设的基地址的偏移。本寄存器的偏移地址是0x10,从手册可以查到GPIOA这个外设的基地址为0x4001 0800,我们就可以算出GPIOA这个GPIOA_BSRR寄存器地址为:0x4001 0800+0x10。
3.寄存器位表
紧接着的就是本寄存器的位表,表中列出了0-31位的名称及权限。表上方的数字为位编号,中间为位名称,最下方为读写权限,其中w表示只写,r表示只读,rw表示可读写。本寄存器中的位权限都是w,所以只能写,如果读本寄存器,是无法保证读取到它真正内容的。而有的寄存器位只读,一般是用于STM32外设的某种工作状态的,由STM32硬件自动生效,程序通过读取那些寄存器位来判断外设的工作状态。
4.位功能说明
位功能是寄存器说明中最重要的部分,它详细介绍了寄存器每一个位的功能。后续根据具体部分详细解答
四.C语言对寄存器的封装
1.封装总线和外设基地址
小小例子:
2.封装寄存器列表
3.操作寄存器(通过结构体指针)
直接使用宏定义好 GPIO_TypeDef 类型的指针,而且指针指向各个 GPIO 端口的首地址,使用时我们直接用GPIOA这个指针对结构体成员寄存器进行访问
具体例子:
五.总结
这里我们仅是以GPIO这个外设为例,给大家讲解了C语言对寄存器的封装。以此类推,其他外设也同样可以用这种方法来封装。
好消息是,这部分工作都由固件库帮我们完成了,这里我们只是分析了下这个封装的过程,让大家知其然,也只其所以然。尽管现在看到寄存器不太明白,在后续学习当种可以步步深入,我与在座的一起在未来的道路上并肩学习讨论,祝早日在嵌入式单片机领域更上一层楼