本章参考资料: 《STM32F10X-中文参考手册》 GPIO 章节和 RCC 章节。
虽然我们上面用寄存器点亮了 LED,乍看一下好像代码也很简单,但是我们别侥幸以后就可以一直用寄存器开发。在用寄存器点亮 LED 的时候,我们会发现 STM32 的寄存器都是 32 位的,每次配置的时候都要对照着《STM32F10X-中文参考手册》中寄存器的说明,然后根据说明对每个控制的寄存器位写入特定参数,因此在配置的时候非常容易出错,而且代码还很不好理解,不便于维护。所以学习 STM32 最好的方法是用固件库,然后在固件库的基础上了解底层,学习寄存器。
9.1 什么是STM32函数库
函数库可以使开发人员脱离寄存器操作,当我们调用库API时不需要去了解底层的寄存器操作,实际上函数库是在寄存器和用户驱动层之间的代码,向下处理与寄存器直接相关的配置,向上提供配置寄存器的接口,库开发与寄存器开发方式的区别如图:
9.2 为什么采用库开发及学习
在以前 8 位机时代的程序开发中, 一般直接配置芯片的寄存器,控制芯片的工作方式,如中断,定时器等。配置的时候,常常要查阅寄存器表,看用到哪些配置位,为了配置某
功能,该置 1 还是置 0。这些都是很琐碎的、机械的工作,因为 8 位机的软件相对来说较简单,而且资源很有限,所以可以直接配置寄存器的方式来开发。
对于STM32,因为外设资源丰富,带来的必定是寄存器的数量和复杂度的增加,这是直接配置寄存器存在很大缺陷。
- 开发速度慢
- 程序可读性差
- 维护复杂
这些缺陷直接影响了开发效率,程序维护成本,交流成本。库开发方式则正好弥补了这些缺陷。而坚持采用直接配置寄存器的方式开发的程序员,会列举以下原因:
- 具体参数更直观
- 程序运行占用资源少
相对于库开发的方式,直接配置寄存器方式生成的代码量的确会少一点,但因为STM32 有充足的资源,权衡库的优势与不足,绝大部分时候,我们愿意牺牲一点 CPU 资源,选择库开发。一般只有在对代码运行时间要求极苛刻的地方,才用直接配置寄存器的方式代替,如频繁调用的中断服务函数。
9.3 实验:构建库函数雏形
接下来,我们在寄存器点亮 LED 的代码上继续完善,把代码一层层封装,实现库的最初的雏形,相信经过这一步的学习后,您对库的运用会游刃有余。这里我们只讲如何实现GPIO 函数库,其他外设的我们直接参考 ST 标准库学习即可,不必自己写。下面请打开本章配套例程“构建库函数雏形”来阅读理解,该例程是在上一章的基础上修改得来的。
9.3.1 外设寄存器的结构体定义
在前面中我们操作的的寄存器都是操作绝对地址,如果每一个寄存器我们都这样子去操作会非常麻烦,因为外设寄存器的地址都是基于外设基地址的偏移地址,都是基于外设基地址的基础上连续递增,每一个寄存器占32位,这种方式与结构体里面的成员相似,所以我们可以定义一个结构体,结构体的地址等于外设的基地址,结构体的成员相当于寄存器,成员的排列顺序也和寄存器的排列顺序一样,这样我们就不用每次都找到绝对地址,只需要知道寄存器的基地址就可以操作外设的全部寄存器,即操作结构体里面的成员即可。
在“stm32f10x.h”文件中,我们使用了结构体封装GPIO和RCC的外设寄存器,结构体成员的顺序按照寄存器的偏移地址从低到高,成员类型跟寄存器类型一样。
“volatile”关键字表示这个变量是易变的,要求编译器不要优化,这些结构体的成员,都代表寄存器,而寄存器很多时候是由外设或者STM32芯片状态是修改的,就是说即使CPU不执行代码修改这些变量,变量的值也会被外设修改,所以每次使用这些变量时,我们都要求CPU去该变量的地址重新访问。如果没有这个关键字,在某些情况下,编译器会认为没有代码修改该变量,就直接从 CPU 的某个缓存获取该变量值,这时可以加快执行速度,但该缓存中的是陈旧数据,与我们要求的寄存器最新状态可能会有出入。