×
单片机 > 单片机程序设计 > 详情

再造STM32---第六部分:自己写库—构建库函数雏形

发布时间:2020-06-10 发布时间:
|

       本章参考资料: 《STM32F4xx 中文参考手册》 、 《STM32F429 规格书》


       虽然我们上面用寄存器点亮了 LED,乍看一下好像代码也很简单,但是我们别侥幸以后就可以一直用寄存器开发。在用寄存器点亮 LED 的时候,我们会发现 STM32 的寄存器都是 32 位的,每次配置的时候都要对照着《STM32F4xx 参考手册》中寄存器的说明,然后根据说明对每个控制的寄存器位写入特定参数,因此在配置的时候非常容易出错,而且代码还很不好理解,不便于维护。所以学习 STM32 最好的方法是用软件库,然后在软件库的基础上了解底层,学习遍所有寄存器。


 


6.1 什么是 STM32 函数库:

       以上所说的软件库是指“STM32 标准函数库”,它是由 ST 公司针对 STM32 提供的函数接口,即 API (Application Program Interface),开发者可调用这些函数接口来配置 STM32的寄存器,使开发人员得以脱离最底层的寄存器操作,有开发快速,易于阅读,维护成本低等优点。


       当我们调用库 API 的时候不需要挖空心思去了解库底层的寄存器操作,就像当年我们刚开始学习 C 语言的时候,用 prinft()函数时只是学习它的使用格式,并没有去研究它的源码实现, 但需要深入研究的时候,经过千锤百炼的库 API 源码就是最佳学习范例。


       实际上, 库是架设在寄存器与用户驱动层之间的代码,向下处理与寄存器直接相关的配置,向上为用户提供配置寄存器的接口。 库开发方式与直接配置寄存器方式的区别见图6-1。


6.2 为什么采用库来开发及学习?

       在以前 8 位机时代的程序开发中, 一般直接配置芯片的寄存器,控制芯片的工作方式,如中断,定时器等。配置的时候, 常常要查阅寄存器表,看用到哪些配置位,为了配置某功能,该置 1 还是置 0。这些都是很琐碎的、机械的工作,因为 8 位机的软件相对来说较简单,而且资源很有限,所以可以直接配置寄存器的方式来开发。

       对于 STM32,因为外设资源丰富,带来的必然是寄存器的数量和复杂度的增加,这时直接配置寄存器方式的缺陷就突显出来了:

(1) 开发速度慢

(2) 程序可读性差

(3) 维护复杂

       这些缺陷直接影响了开发效率,程序维护成本,交流成本。库开发方式则正好弥补了这些缺陷。

       而坚持采用直接配置寄存器的方式开发的程序员,会列举以下原因:

(1) 具体参数更直观

(2) 程序运行占用资源少

       相对于库开发的方式,直接配置寄存器方式生成的代码量的确会少一点,但因为STM32 有充足的资源,权衡库的优势与不足,绝大部分时候,我们愿意牺牲一点 CPU 资源,选择库开发。一般只有在对代码运行时间要求极苛刻的地方,才用直接配置寄存器的方式代替,如频繁调用的中断服务函数。

       对于库开发与直接配置寄存器的方式,就好比编程是用汇编好还是用 C 好一样。在STM32F1 系列刚推出函数库时引起程序员的激烈争论,但是,随着 ST 库的完善与大家对库的了解,更多的程序员选择了库开发。 现在 STM32F1 系列和 STM32F4 系列各有一套自己的函数库,但是它们大部分是兼容的, F1 和 F4 之间的程序移植,只需要小修改即可。而如果要移植用寄存器写的程序,我只想说:“呵呵”。

       用库来进行开发,市场已有定论,用户群说明了一切,但对于 STM32 的学习仍然有人认为用寄存器好,而且汇编不是还没退出大学教材么?认为这种方法直观,能够了解到是配置了哪些寄存器,怎样配置寄存器。事实上,库函数的底层实现恰恰是直接配置寄存器

方式的最佳例子,它代替我们完成了寄存器配置的工作,而想深入了解芯片是如何工作的话,只要直接查看库函数的最底层实现就能理解,相信你会为它严谨、优美的实现方式而陶醉, 要想修炼 C 语言,就从 ST 的库开始吧。 所以在以后的章节中,使用软件库是我们的重点,而且我们通过讲解库 API 去高效地学习 STM32 的寄存器,并不至于因为用库学习,就不会用寄存器控制 STM32 芯片。


6.3 实验:构建库函数雏形:

       虽然库的优点多多,但很多人对库还是很忌惮,因为一开始用库的时候有很多代码,很多文件,不知道如何入手。不知道您是否认同这么一句话:一切的恐惧都来源于认知的空缺。我们对库忌惮那是因为我们不知道什么是库,不知道库是怎么实现的。

       接下来,我们在寄存器点亮 LED 的代码上继续完善,把代码一层层封装,实现库的最初的雏形,相信经过这一步的学习后,您对库的运用会游刃有余。这里我们只讲如何实现GPIO 函数库,其他外设的我们直接参考 ST 标准库学习即可,不必自己写。

       下面请打开本章配套例程“构建库函数雏形”来阅读理解,该例程是在上一章的基础上修改得来的。



6.3.1 修改寄存器地址封装:


        上一章中我们在操作寄存器的时候,操作的是都寄存器的绝对地址,如果每个外设寄存器都这样操作,那将非常麻烦。我们考虑到外设寄存器的地址都是基于外设基地址的偏移地址,都是在外设基地址上逐个连续递增的,每个寄存器占 32 个或者 16 个字节,这种方式跟结构体里面的成员类似。所以我们可以定义一种外设结构体,结构体的地址等于外设的基地址,结构体的成员等于寄存器,成员的排列顺序跟寄存器的顺序一样。这样我们操作寄存器的时候就不用每次都找到绝对地址,只要知道外设的基地址就可以操作外设的全部寄存器,即操作结构体的成员即可。

       在工程中的“stm32f4xx.h”文件中,我们使用结构体封装 GPIO 及 RCC 外设的的寄存器,见代码清单 6-1。结构体成员的顺序按照寄存器的偏移地址从低到高排列,成员类型跟寄存器类型一样。如不理解 C 语言对寄存器的封的语法原理,请参考《C 语言对寄存器的封装》 小节。


代码清单6-1 封装寄存器列表


//volatile 表示易变的变量,防止编译器优化

#define __IO volatile

typedef unsigned int uint32_t;

typedef unsigned short uint16_t;

/* GPIO 寄存器列表 */

typedef struct {

__IO uint32_t MODER; /*GPIO 模式寄存器 地址偏移: 0x00 */

__IO uint32_t OTYPER; /*GPIO 输出类型寄存器 地址偏移: 0x04 */

__IO uint32_t OSPEEDR; /*GPIO 输出速度寄存器 地址偏移: 0x08 */

__IO uint32_t PUPDR; /*GPIO 上拉/下拉寄存器 地址偏移: 0x0C */

__IO uint32_t IDR; /*GPIO 输入数据寄存器 地址偏移: 0x10 */

__IO uint32_t ODR; /*GPIO 输出数据寄存器 地址偏移: 0x14 */

__IO uint16_t BSRRL; /*GPIO 置位/复位寄存器低 16 位部分 地址偏移: 0x18 */

__IO uint16_t BSRRH; /*GPIO 置位/复位寄存器 高 16 位部分地址偏移: 0x1A */

__IO uint32_t LCKR; /*GPIO 配置锁定寄存器 地址偏移: 0x1C */

__IO uint32_t AFR[2]; /*GPIO 复用功能配置寄存器 地址偏移: 0x20-0x24 */

} GPIO_TypeDef;

/*RCC 寄存器列表*/

typedef struct {

__IO uint32_t CR; /*!< RCC 时钟控制寄存器,地址偏移: 0x00 */

__IO uint32_t PLLCFGR; /*!< RCC PLL 配置寄存器,地址偏移: 0x04 */

__IO uint32_t CFGR; /*!< RCC 时钟配置寄存器,地址偏移: 0x08 */

__IO uint32_t CIR; /*!< RCC 时钟中断寄存器,地址偏移: 0x0C */

__IO uint32_t AHB1RSTR; /*!< RCC AHB1 外设复位寄存器,地址偏移: 0x10 */

__IO uint32_t AHB2RSTR; /*!< RCC AHB2 外设复位寄存器,地址偏移: 0x14 */

__IO uint32_t AHB3RSTR; /*!< RCC AHB3 外设复位寄存器,地址偏移: 0x18 */

__IO uint32_t RESERVED0; /*!< 保留, 地址偏移: 0x1C */

__IO uint32_t APB1RSTR; /*!< RCC APB1 外设复位寄存器,地址偏移: 0x20 */

__IO uint32_t APB2RSTR; /*!< RCC APB2 外设复位寄存器,地址偏移: 0x24*/

__IO uint32_t RESERVED1[2]; /*!< 保留,地址偏移: 0x28-0x2C*/

__IO uint32_t AHB1ENR; /*!< RCC AHB1 外设时钟寄存器,地址偏移: 0x30 */

__IO uint32_t AHB2ENR; /*!< RCC AHB2 外设时钟寄存器,地址偏移: 0x34 */

__IO uint32_t AHB3ENR; /*!< RCC AHB3 外设时钟寄存器,地址偏移: 0x38 */

/*RCC 后面还有很多寄存器,此处省略*/

} RCC_TypeDef;

        这段代码在每个结构体成员前增加了一个“__IO”前缀,它的原型在这段代码的第一行,代表了 C 语言中的关键字“volatile”,在 C 语言中该关键字用于表示变量是易变的,要求编译器不要优化。这些结构体内的成员,都代表着寄存器,而寄存器很多时候是由外设或 STM32 芯片状态修改的,也就是说即使 CPU 不执行代码修改这些变量,变量的值也有可能被外设修改、更新,所以每次使用这些变量的时候,我们都要求 CPU 去该变量的地址重新访问。若没有这个关键字修饰,在某些情况下,编译器认为没有代码修改该变量,就直接从 CPU 的某个缓存获取该变量值,这时可以加快执行速度,但该缓存中的是陈旧数据,与我们要求的寄存器最新状态可能会有出入。



6.3.2 定义访问外设的结构体指针:

以结构体的形式定义好了外设寄存器后


关键字:STM32  库  库函数 

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
ARM 汇编的必知必会