嵌入式开发五:认识寄存器开发与标准库开发

本文介绍了STM32常用的两种开发方式:寄存器开发和标准库开发。对比了二者的优缺点,指出库开发可提高开发效率、降低维护成本。还通过实验构建库函数雏形,并介绍了STM32官方标准固件库,包括库文件功能、文件关系及使用帮助文档等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      通过前面的学习,我们对 STM32 有了个比较清晰的了解,本节我们将分别简单介绍常用的两种开发方式:第一种标准库开发、第二种:标准库(固件库)开发,请务必认真学习,让大家知其然,也只其所以然。后期外设的学习均是基于标准库开发。

目录

一、自己实现库—构建库函数的雏形

1.1 库开发与寄存器开发的关系(认识)

1.2 什么是STM32固件库?

1.3 为什么采用库来开发和学习?

1.4 实验:构建库函数雏形

1.4.0 创建文件

1.4.1 修改寄存器地址封装

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

1.4.3 定义位操作函数

1.4.4 定义初始化结构体 GPIO_InitTypeDef

1.4.5 定义引脚模式的枚举类型

 1.4.6 定义 GPIO 初始化函数

1.4.7 全新面貌,使用函数点亮 LED 灯

1.5 总结

二、初识STM32官方标准固件库

2.1 STM32 固件库与 CMSIS 标准讲解

2.2 STM32F4 官方库包介绍

2.2.1 库目录、文件简介

2.2.2 主要文件介绍

2.3 固件库各个文件的关系(重点理解)

2.4 简单总结

2.5 固件库使用帮助文档

2.5.1 常用官方资料

2.5.2 初识库函数


一、自己实现库—构建库函数的雏形

        学过51单片机的应该都知道51单片机采用的是寄存器开发,此时,我们正在学习一种更加强大的基于ARM内核的STM32单片机,从开发实现的方式讲,它仍然可以用寄存器开发,但是我们别侥幸以后就可以一直用寄存器开发。在用寄存器的方式实现点亮 LED (后面简单讲解),我们会发现 STM32 的寄存器都是 32 位的,每次配置的时候都要对照着《STM32F4xx 中文参考手册》 中寄存器的说明,然后根据说明对每个控制的寄存器位写入特定参数,因此在配置的时候非常容易出错,而且代码还很不好理解,不便于维护,此外STM32的寄存器数量是非常多的,如果使用寄存器开发,那必然会耽误开发进度,提升了开发的难度,于是,ST(意法半导体)为了方便用户开发程序,提供了一套丰富的 STM32F4 固件库。到底什么是固件库?它与直接操作寄存器开发有什么区别和联系?很多初学用户很是费解,这一节,我们将讲解 STM32 固件库相关的基础知识,希望能够让大家对 STM32F4 固件库有一个初步的了解。

学习 STM32 最好的方法是用官方提供的固件库来开发,然后在固件库的基础上了解底层,学遍所有寄存器。

1.1 库开发与寄存器开发的关系(认识)

       很多用户都是从学 51 单片机开发转而想进一步学习 STM32 开发,他们习惯了 51 单片机 的寄存器开发方式,突然一个 ST 官方库摆在面前会一头雾水,不知道从何下手。下面我们将通过一个简单的例子来告诉 STM32 固件库到底是什么,和寄存器开发有什么关系?其实一句话就可以概括:固件库就是函数的集合,固件库函数的作用是向下负责与寄存器直接打交道, 向上提供用户函数调用的接口(API)。 在 51 的开发中我们常常的作法是直接操作寄存器,比如要控制某些 IO 口的状态,我们直接操作寄存器如下:

  P0=0x11;  //51单片机的寄存器是8位的,此代码的作用是给P0寄存器写入0x11

 而在 STM32 的开发中,我们同样可以操作寄存器如下:

GPIOF->BSRRL=0x0001; //这里是针对 STM32F4 系列

 对于51单片机这种方法当然可以,但是这种方法的劣势是你需要去掌握每个寄存器的用法,你才能正确使用 STM32,而对于 STM32 这种级别的 MCU,数百个寄存器记起来又是谈何容易。于是 ST(意法 半导体)推出了官方固件库,固件库将这些寄存器底层操作都封装起来,提供一整套接口(API) 供开发者调用,大多数场合下,你不需要去知道操作的是哪个寄存器,你只需要知道调用哪些函数即可。比如上面的控制 BSRRL 寄存器实现电平控制,官方库封装了一个函数:

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
 GPIOx->BSRRL = GPIO_Pin;
}

       这个时候你不需要再直接去操作 BSRRL 寄存器了,你只需要知道怎么使用 GPIO_SetBits ()这个函数就可以了。在你对外设的工作原理有一定的了解之后,你再去看固件库函数,基本上函 数名字能告诉你这个函数的功能是什么,该怎么使用,这样是不是开发会方便很多? 任何处理器,不管它有多么的高级,归根结底都是要对处理器的寄存器进行操作。

提醒:

       但是固件库不是万能的,您如果想要把 STM32 学透,光读 STM32 固件库是远远不够的。你还是要了解一下 STM32 的原理,了解 STM32 各个外设的运行机制。只有了解了这些原理,你在进行固件库开发过程中才可能得心应手游刃有余。只有了解了原理,你才能做到“知其然知其所以然”, 所以大家在学习库函数的同时,别忘了要了解一下寄存器大致配置过程。 

1.2 什么是STM32固件库?

        以上所说的固件库是指“STM32 标准函数库”,它是由 ST 公司针对 STM32 提供的函数接口,即 API (Application Program Interface),开发者可调用这些函数接口来配置 STM32 的寄存器,使开发人员得以脱离最底层的寄存器操作,有开发快速,易于阅读,维护成本低等优点。 当我们调用库 API 的时候不需要挖空心思去了解库底层的寄存器操作,就像当年我们刚开始学习 C 语言的时候,用 prinft()函数时只是学习它的使用格式,并没有去研究它的源码实现,但需要深入研究的时候,经过千锤百炼的库源码就是最佳学习范例。 实际上,库是架设在寄存器与用户驱动层之间的代码,向下处理与寄存器直接相关的配置,向上为用户提供配置寄存器的接口。库开发方式与直接配置寄存器方式的区别见下图 。

1.3 为什么采用库来开发和学习?

       在以前 8 位机时代(比如51单片机)的程序开发中,一般直接配置芯片的寄存器,控制芯片的工作方式, 如中断,定时器等。配置的时候,常常要查阅寄存器表,看用到哪些配置位,为了配置某功能,该置 1 还是置 0。这些都是很琐碎的、机械的工作,因为 8 位机的软件相对来说较 简单,而且资源很有限,所以可以直接配置寄存器的方式来开发。 对于 STM32,因为外设资源丰富,带来的必然是寄存器的数量和复杂度的增加这时直接配置寄存器方式的缺陷就突显出来了:

(1) 开发速度慢

(2) 程序可读性差

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

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

(1) 具体参数更直观

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

        相对于库开发的方式,直接配置寄存器方式生成的代码量的确会少一点,但因为 STM32 有充足的资源,权衡库的优势与不足,绝大部分时候,我们愿意牺牲一点 CPU 资源,选择库开发。一般只有在对代码运行时间要求极苛刻的地方,才用直接配置寄存器的方式代替,如频繁调用的中断服务函数。 对于库开发与直接配置寄存器的方式,就好比编程是用汇编好还是用 C 好一样。在 STM32F1 系列刚推出函数库时引起程序员的激烈争论,但是,随着 ST 库的完善与大家对库的了解,更多的程序员选择了库开发。现在 STM32F1 系列和 STM32F4 系列各有一套自己的函数库,但是它们大部分是兼容的,F1 和 F4 之间的程序移植,只需要小修改即可。 用库来进行开发,市场已有定论,用户群说明了一切,但对于 STM32 的学习仍然有人认为用寄存器好,而且汇编不是还没退出大学教材么?认为这种方法直观,能够了解到是配置了哪些寄存器,怎样配置寄存器。

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

1.4 实验:构建库函数雏形

      虽然库的优点多多,但很多人对库还是很忌惮,因为一开始用库的时候有很多代码, 很多文件,不知道如何入手。不知道您是否认同这么一句话:一切的恐惧都来源于认知的空缺。我们对库忌惮那是因为我们不知道什么是库,不知道库是怎么实现的。接下来,我们在寄存器点亮 LED 的代码上继续完善,把代码一层层封装,实现库的最初的雏形,相信经过这一步的学习后,您对库的运用会游刃有余。这里我们只讲如何实现 GPIO 函数库,其他外设的我们直接参考 ST 标准库学习即可,不必自己写。 

1.4.0 创建文件

      这里是模仿ST官方的标准固件库,因为我们点灯操作的是GPIO外设,这里新建三个文件,文章后面会详细讲解ST官方的标准固件库各个文件的功能。

stm32f4xx.h->实现内核之外的外设寄存器映射(也就是给有特定功能的内存单元取个别名),以及一些外设确定函数的参数。
stm32f4xx_gpio.c->实现的配置GPIO外设的寄存器的所有函数,包括本文的重点,GPIO_Init->GPIO初始化函数。
stm32f4xx_gpio.h->与stm32f4xx_gpio.c成对存在,主要存放外设的初始化结构体,外设初始化结构体成员的参数列表,外设固件库函数的声明。

1.4.1 修改寄存器地址封装

        直接操作寄存器的时候,操作的都是寄存器的绝对地址,如果每个外设寄存器都这样操作,那将非常麻烦。我们考虑到外设寄存器的地址都是基于外设基地址的偏移地址,都是在外设基地址上逐个连续递增的,每个寄存器占 4个字节,这种方式跟结构体里面的成员类似。所以我们可以定义一种外设结构体,结构体的地址等于外设的基地址, 结构体的成员等于寄存器,成员的排列顺序跟寄存器的顺序一样。这样我们操作寄存器的时候就不用每次都找到绝对地址,只要知道外设的基地址就可以操作外设的全部寄存器, 即操作结构体的成员即可。在工程中的“stm32f4xx.h”文件中,我们使用结构体封装 GPIO 及 RCC 外设的的寄存器,具体见代码清单 9-1。结构体成员的顺序按照寄存器的偏移地址从低到高排列,成员类型跟寄存器类型一样,也是uint32_t。

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

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

     以结构体的形式定义好了外设寄存器后,使用结构体前还需要给结构体的首地址赋值, 才能访问到需要的寄存器。为方便操作,我们给每个外设都定义好指向它地址的结构体指针,具体见代码清单 9-2。

      把外设的基地址强制类型转换成相应的外设寄存器结构体指针,然后再把该指针替换成外设名,然后外设名就是该外设类型的寄存器结构体指针,指向了该外设的结构体,也就是说该外设结构体的首地址变成的外设的基地址,通过该指针可以直接操作该外设的全部寄存器这些宏通过强制类型转换把外设的基地址转换成 GPIO_TypeDef 类型的指针,从而得到 GPIOA、GPIOB 等直接指向对应外设的指针,通过对结构体指针的操作,即可访问对应外设的寄存器。 利用这些指针访问寄存器,我们把 main 文件里对应的代码修改掉,具体见代码清单 9-3。 

使用寄存器点亮 LED 灯
 int main(void)
{
   //开启 GPIOF 时钟,使用外设时都要先开启它的时钟
   RCC_AHB1ENR |= (1<<5);

   // LED 端口初始化  
  
  GPIOF_MODER &= ~( 0x03<< (2*6));   //GPIOF MODER6 清空

  GPIOF_MODER |= (1<<2*6);     //PF6 MODER6 = 01b 输出模式
 
  GPIOF_OTYPER &= ~(1<<1*6);  //GPIOF OTYPER6 清空

  GPIOF_OTYPER |= (0<<1*6);   //PF6 OTYPER6 = 0b 推挽模式

  GPIOF_OSPEEDR &= ~(0x03<<2*6);  //GPIOF OSPEEDR6 清空

  GPIOF_OSPEEDR |= (0<<2*6);    //PF6 OSPEEDR6 = 0b 速率 2MHz
 
  GPIOF_PUPDR &= ~(0x03<<2*6);  //GPIOF PUPDR6 清空

  GPIOF_PUPDR |= (1<<2*6);  // PF6 PUPDR6 = 01b 上拉模式

  GPIOF_BSRR |= (1<<16<<6); //PF6 BSRR 寄存器的 BR6 置 1,使引脚输出低电平

  //GPIOF_BSRR |= (1<<6);    //PF6 BSRR 寄存器的 BS6 置 1,使引脚输出高电平

  while (1);
 
}
自己写库—构建库函数雏形 

 int main(void)
{
/*开启 GPIOF 时钟,使用外设时都要先开启它的时钟*/
 RCC->AHB1ENR |= (1<<5);
 
 /* LED 端口初始化 */

 GPIOF->MODER &= ~( 0x03<< (2*6));  //GPIOF MODER6 清空
 
 GPIOF->MODER |= (1<<2*6);   //PF6 MODER6 = 01b 输出模式
 
 GPIOF->OTYPER &= ~(1<<1*6);   //GPIOF OTYPER6 清空

 GPIOF->OTYPER |= (0<<1*6);  //PF6 OTYPER6 = 0b 推挽模式
 
 GPIOF->OSPEEDR &= ~(0x03<<2*6); //GPIOF OSPEEDR6 清空

 GPIOF->OSPEEDR |= (0<<2*6); //PF6 OSPEEDR6 = 0b 速率 2MHz

 GPIOF->PUPDR &= ~(0x03<<2*6);  //GPIOF PUPDR6 清空

 GPIOF->PUPDR |= (1<<2*6); //PF6 PUPDR6 = 01b 上拉模式

 GPIOF->BSRRH |= (1<<16<<6); //PF6 BSRR 寄存器的 BR6 置 1,使引脚输出低电平
 
 //GPIOF->BSRRL |= (1<<6);  //PF6 BSRR 寄存器的 BS6 置 1,使引脚输出高电平
 
 while (1);

 }

      乍一看,除了把寄存器名字的“-”改成了->之外,其他都没有变,但就是这个小变化里面蕴藏这大变化,带“-”的操作是直接操作内存,需要对寄存器的地址一个个进行定义, 带“->”的操作是使用外设结构体指针的方式来操作,这为我们继续编写库函数打下了基础。 还有一个不同是我们把 BSRR 寄存器分成 BSRRH 和 BSRRL 两段,各 16 位,高 16 位控制复位,低 16 位控制置位,都是写 1 有效。 打好了地基,下面我们就来建高楼。接下来使用函数来封装 GPIO 的基本操作,方便以后应用的时候不需要再查询寄存器,而是直接通过调用这里定义的函数来实现。我们把针对 GPIO 外设操作的函数及其宏定义分别存放在“stm32f4xx_gpio.c”和 “stm32f4xx_gpio.h”文件中。

1.4.3 定义位操作函数

      在“stm32f4xx_gpio.c”文件定义两个位操作函数,分别用于控制引脚输出高电平和低 电平,见代码清单 9-4。这里就是操作BSRR,BRR寄存器间接操作ODR寄存器来控制引脚的电平,将操作BSRR,BRR寄存器封装成一个函数,如果要控制引脚电平调用函数即可。

 

这里解释一下为啥BRR寄存器与BSRR寄存器高16位功能一样,还搞个BRR寄存器,这里纯属是为了照顾不同程序员的使用习惯,不用太纠结。

总结:要配置引脚输出高电平就往BSRR相应低16位写1就行了,若要配置引脚输出低电平就往BRR寄存器相应位写1就ok了。或者只使用BSRR寄存器。

这两个函数体内都是只有一个语句,对 GPIOx 的 BSRRL 或 BSRRH 寄存器赋值,从而设置引脚为高电平或低电平。其中 GPIOx 是一个指针变量,通过函数的输入参数我们可以修改它的值,如给它赋予 GPIOA、GPIOB、GPIOH 等结构体指针值,这个函数就可以控制相应的 GPIOA、GPIOB、GPIOH 等端口的输出。 对比我们前面对 BSRR 寄存器的赋值,都是用“|=”操作来防止对其它数据位产生干扰的,为何此函数里的操作却直接用“=”号赋值,这样不怕干扰其它数据位吗?见代码 清单 9-5。 

      根据 BSRR 寄存器的特性,对它的数据位写“0”,是不会影响输出的,只有对它的数 据位写“1”,才会控制引脚输出。对低 16 位写“1”输出高电平,对高 16 位写“1”输出低电平。也就是说,假如我们对 BSRRH(高 16 位)直接用“=”操作赋二进制值“0000 0000 0000 0001 b”,它会控制 GPIO 的引脚 0 输出低电平,赋二进制值“0000 0000 0001 0000 b”,它会控制 GPIO 引脚 4 输出低电平,而其它数据位由于是 0,所以不会受到干扰。同理,对 BSRRL(低 16 位)直接赋值也是如此,数据位为 1 的位输出高电平。代码清单 9-6 中 的两种赋值方式,功能相同。

代码清单 9-6 BSRR 寄存器赋值等效代码
 /*使用 “|=” 来赋值*/
 GPIOF->BSRRH |= (uint16_t)(1<<6);
 /*直接使用“=” 来赋值,二进制数(0000 0000 1000 0000)*/
 GPIOF->BSRRH = (uint16_t)(1<<6);

     这两行代码功能等效,都把 BSRRH 的 bit6 设置为 1,控制引脚 6 输出低电平,且其它 引脚状态不变。但第二个语句操作效率是比较高的,因为“|=”号包含了读写操作,而“=” 号只需要一个写操作。因此在定义位操作函数中我们使用后者。 利用这两个位操作函数,就可以方便地操作 GPIO 的引脚电平了,控制各种端口引脚 的范例见代码清单 9-7。

使用以上函数输入参数,设置引脚号时,还是很不方便,且可读性差,为此我们把表 示 16 个引脚号的操作数都定义成宏,具体见代码清单 9-8。做这个的目的是为了更加方便,增加可读性,不用将代码写成:GPIOX->BSRR | = (1<<引脚号);引脚参数转化为二进制就是对应的引脚号位为1其余位为0,这样直接让寄存器位或这个宏,就将对应的引脚的电平配置完成。

 这些宏代表的参数是某位置“1”其它位置“0”的数值,其中最后一个 “GPIO_Pin_ALL”是所有数据位都为“1”,所以用它可以一次控制设置整个端口的 0-15 所有引脚。利用这些宏, GPIO 的控制代码可改为代码清单 9-9。

 代码清单 9-9 使用位操作函数及宏控制 GPIO
 
 GPIO_SetBits(GPIOH,GPIO_Pin_10);   /*控制 GPIOH 的引脚 10 输出高电平*/

 GPIO_ResetBits(GPIOH,GPIO_Pin_10); /*控制 GPIOH 的引脚 10 输出低电平*/

/*控制 GPIOH 的引脚 10、引脚 11 输出高电平,使用“|”,同时控制多个引脚*/
 GPIO_SetBits(GPIOH,GPIO_Pin_10|GPIO_Pin_11);

 /*控制 GPIOH 的引脚 10、引脚 11 输出低电平*/
 GPIO_ResetBits(GPIOH,GPIO_Pin_10|GPIO_Pin_11);

 /*控制 GPIOH 的所有输出低电平*/
 GPIO_ResetBits(GPIOH,GPIO_Pin_ALL);

 /*控制 GPIOA 的引脚 8 输出高电平*/
 GPIO_SetBits(GPIOA,GPIO_Pin_8);

 /*控制 GPIOB 的引脚 9 输出低电平*/
 GPIO_ResetBits(GPIOB,GPIO_Pin_9);

使用以上代码控制 GPIO,我们就不需要再看寄存器了,直接从函数名和输入参数就 可以直观看出这个语句要实现什么操作(英文中―Set‖表示“置位”,即高电平,“Reset” 表示“复位”,即低电平)。 

1.4.4 定义初始化结构体 GPIO_InitTypeDef

       定义位操作函数后,控制 GPIO 输出电平的代码得到了简化,但在控制 GPIO 输出电平前还需要初始化 GPIO 引脚的各种模式,这部分代码涉及的寄存器有很多,我们希望初始化 GPIO 也能以如此简单的方法去实现。为此,我们先根据 GPIO 初始化时涉及到的初始化参数以结构体的形式封装起来,声明一个名为 GPIO_InitTypeDef 的结构体类型,具体 见代码清单 9-10。

这个结构体中包含了初始化 GPIO 所需要的信息,包括引脚号、工作模式、输出速率、 输出类型以及上/下拉模式。

设计这个结构体的思路是:初始化 GPIO 前,先定义一个这样的结构体变量,根据需要配置 GPIO 的模式,对这个结构体的各个成员进行赋值,然后把这个变量作为“GPIO 初始化函数”的输入参数,该函数能根据这个变量值中的内容去配置寄存器,从而实现 GPIO 的初始化。

1.4.5 定义引脚模式的枚举类型

      代码清单 9-10 定义的结构体很直接,美中不足的是在对结构体中各个成员赋值时还需要看具体哪个模式对应哪个数值,如 GPIO_Mode 成员的“输入/输出/复用/模拟”模式对应二进制值“00 、01、 10、 11”,我们不希望每次用到都要去查找这些索引值,所以使用 C 语言中的枚举语法定义这些参数,具体见代码清单 9-11。

有了这些枚举定义,我们的 GPIO_InitTypeDef 结构体也可以使用枚举类型来限定输入 了,具体见代码清单 9-12。

      定义一个这样的结构体变量,根据需要配置 GPIO 的工作模式,对这个结构体的各个成员进行赋值,然后把这个变量的地址作为“GPIO 初始化函数”的输入参数,该函数能访问这个变量值中的内容去配置寄存器,从而实现 GPIO 的初始化。如果不使用枚举类型,仍使用“uint8_t”类型来定义结构体成员,那么成员值的范围 就是 0-255 了,而实际上这些成员都只能输入几个数值。所以使用枚举类型可以对结构体 成员起到限定输入的作用,只能输入相应已定义的枚举值。 利用这些枚举定义,给 GPIO_InitTypeDef 结构体类型赋值配置就非常直观了,具体见 代码清单 9-13。

 1.4.6 定义 GPIO 初始化函数

       首先就要考虑函数的参数有哪些,我们的目的是为了配置GPIO端口的引脚工作模式,首先第一个参数是端口号,除此之外我们还要知道,配置成什么模式,以及引脚号,还有输出模式下的GPIO 引脚的速率,为了方便不如将这几个参数封装成一个结构体,这几个参数作为结构体的成员变量,我们可以创建一个该结构体的变量对这些结构体成员变量进行赋值配置我们想要的工作模式,然后传结构体变量的地址给函数,函数就可以通过地址来访问这些参数最后经过运算,将这些参数写入相应的寄存器,就实现了通过函数配置GPIO的工作模式。

      接着前面的思路,对初始化结构体赋值后,把它输入到 GPIO 初始化函数,由它来实现寄存器配置。我们的 GPIO 初始化函数实现具体见代码清单 9-14。

  这个函数有 GPIOx 和 GPIO_InitStruct 两个输入参数,分别是 GPIO 外设指针和 GPIO 初始化结构体指针。分别用来指定要初始化的 GPIO 端口及引脚的工作模式。

 函数实现主要分两个环节:

(1) 利用 for 循环,根据 GPIO_InitStruct 的结构体成员 GPIO_Pin 计算出要初始化的引 脚号。这段看起来复杂的运算实际上可以这样理解:它要通过宏“GPIO_Pin_x” 的参数计算出 x 值(宏的参数值是第 x 数据位为 1,其余为 0,参考代码清单 9-8), 计算得的引脚号结果存储在 pinpos 变量中。

(2) 得到引脚号 pinpos 后,利用初始化结构体各个成员的值,对相应寄存器进行配置, 这部分与我们前面直接配置寄存器的操作是类似的,先对引脚号 pinpos 相应的配置位清空,后根据结构体成员对配置位赋值(GPIO_Mode 成员对应 MODER 寄存 器的配置,GPIO_PuPd 成员对应 PUPDR 寄存器的配置等)。区别是这里的寄存器配置值及引脚号都是由变量存储的。

总结:库函数的本质还是操作寄存器,就是封装一个个函数,我们只要传函数相应的参数,函数会帮我们配置好寄存器,实现指定的功能,这里是以GPIO外设为例,其他外设的库函数本质也是一样。 

1.4.7 全新面貌,使用函数点亮 LED 灯

完成以上的准备后,我们就可以用自己定义的函数来点亮 LED 灯了,具体见代码清单 9-15。

      这里解释一下为什么开GPIOF的时钟,时钟相当于人的大脑,寄存器是基于触发器,而触发器的赋值是一定需要时钟的,也就是说有时钟CPU才能向寄存器写入值。一般用什么外设就要开该外设的时钟。我们这里用的是GPIOF这个外设,所以要开启GPIOF的时钟。GPIOF是挂载在AHB1总线上的外设相应位写1,开启对应外设的时钟

       现在看起来,使用函数来控制 LED 灯与之前直接控制寄存器已经有了很大的区别: main 函数中先定义了一个初始化结构体变量 InitStruct,然后对该变量的各个成员按点亮 LED 灯所需要的 GPIO 配置模式进行赋值,赋值后,调用 GPIO_Init 函数,让它根据结构体成员值对 GPIO 寄存器写入控制参数,完成 GPIO 引脚初始化。控制电平时,直接使用 GPIO_SetBits 和 GPIO_Resetbits 函数控制输出。如若对其它引脚进行不同模式的初始化, 只要修改初始化结构体 InitStruct 的成员值,把新的参数值输入到 GPIO_Init 函数再调用即可。

       代码中新增的 Delay 函数,主要功能是延时,让我们可以看清楚实验现象(不延时的话 指令执行太快,肉眼看不出来),它的实现原理是让 CPU 执行无意义的指令,消耗时间, 在此不要纠结它的延时时间,写一个大概输入参数值,下载到实验板实测,觉得太久了就 把参数值改小,短了就改大即可。需要精确延时的时候我们会用 STM32 的定时器外设进行 精确延时的。

1.5 总结

       什么是 ST 固件库?我们上面写的就是,相对于官方的完整版的固件库,我们写的只是一个雏形,写这个固件库的雏形目的是为了帮助我们从寄存器编程顺利地过度到到固件库编程的,让我们知道什么是固件库,为以后能够熟练的使用固件库编程打下基础。

        我们从寄存器映射开始,把内存跟寄存器建立起一一对应的关系,然后操作寄存器点亮 LED,再把寄存器操作封装成一个个函数。一步一步走来,我们实现了库最简单的雏形, 如果我们不断地增加操作外设的函数,并且把所有的外设都写完,一个完整的库就实现了。

       本章中的 GPIO 相关库函数及结构体定义,实际上都是从 ST 标准库搬过来的。这样分 析它纯粹是为了满足自己的求知欲,学习其编程的方式、思想,这对提高我们的编程水平 是很有好处的,顺便感受一下 ST 库设计的严谨性,我认为这样的代码不仅严谨且华丽优 美,不知你是否也有这样的感受。

       与直接配置寄存器相比,从执行效率上看会有额外的消耗:初始化变量赋值的过程、 库函数在被调用的时候要耗费调用时间;在函数内部,对输入参数转换所需要的额外运算也消耗一些时间(如 GPIO 中运算求出引脚号时)。而其它的宏、枚举等解释操作是在编译过程完成的,这部分并不消耗内核的时间。那么函数库的优点呢?是我们可以快速上手 STM32 控制器;配置外设状态时,不需要再纠结要向寄存器写入什么数值;交流方便,查错简单。这就是我们选择库的原因。

      现在的处理器的主频是越来越高,我们不需要担心 CPU 耗费那么多时间来干活会不会被累倒,库主要应用是在初始化过程,而初始化过程一般是芯片刚上电或在核心运算之前的执行的,这段时间的等待是 0.02us 还是 0.01us 在很多时候并没有什么区别。相对来说, 我们还是担心一下如果都用寄存器操作,每行代码都要查《STM32F4xx 中文参考手册》中 的寄存器说明,自己会不会被累倒吧。

       在以后开发的工程中,一般不会去分析 ST 的库函数的实现了。因为外设的库函数是很类似的,库外设都包含初始化结构体,以及特定的宏或枚举标识符,这些封装被库函数这些转化成相应的值,写入到寄存器之中,函数内部的具体实现是十分枯燥和机械的工作。 如果你有兴趣,在你掌握了如何使用外设的库函数之后,可以查看一下它的源码实现。 通常我们只需要通过了解每种外设的“初始化结构体”就能够通过它去了解到 STM32 的外设功能及控制了。

二、初识STM32官方标准固件库

        在上一节中,我们构建了几个控制 GPIO 外设的函数,算是实现了函数库的雏形,但 GPIO 还有很多功能函数我们没有实现,而且 STM32 芯片不仅仅只有 GPIO 这一个外设。 如果我们想要亲自完成这个函数库,工作量是非常巨大的。ST 公司提供的标准软件库,包含了 STM32 芯片所有寄存器的控制操作,我们直接学习如何使用ST标准库,会极大地方便控制 STM32 芯片。接下来我们简单的分析下 ST 官方的固件库的组成部分。

2.1 STM32 固件库与 CMSIS 标准讲解

        前一节我们讲到,STM32F4 固件库就是函数的集合,那么对这些函数有什么要求呢??这 里就涉及到一个 CMSIS 标准的基础知识。经常有人问到 STM32 和 ARM 以及 ARM7 是什么关系这样的问题,其实 ARM 是一个做芯片标准的公司,它负责的是芯片内核的架构设计,而 TI, ST 这样的公司,他们并不做标准,他们是芯片公司,他们是根据 ARM 公司提供的芯片内核标准设计自己的芯片。所以,任何一个做 Cortex-M4 芯片,他们的内核结构都是一样的,不同的是他们的存储器容量,片上外设,IO 以及其他模块的区别。所以你会发现,不同公司设计的 Cortex-M4 芯片他们的端口数量,串口数量,控制方法这些都是有区别的,这些资源他们可以根据自己的需求理念来设计。同一家公司设计的多种 Cortex-M4 内核芯片的片上外设也会有很大的区别,比如 STM32F407 和 STM32F429,他们的片上外设就有很大的区别。 既然大家都使用的是 Cortex-M4 核,也就是说,本质上大家都是一样的,这样 ARM 公司 为了能让不同的芯片公司生产的 Cortex-M4 芯片能在软件上基本兼容,和芯片生产商共同提出 了一套标准 CMSIS 标准(Cortex Microcontroller Software Interface Standard) ,翻译过来是 “ARM Cortex™ 微控制器软件接口标准”。ST 官方库就是根据这套标准设计的。

因为基于 Cortex 系列芯片采用的内核都是相同的,区别主要为核外的片上外设的差异, 这些差异却导致软件在同内核,不同外设的芯片上移植困难。为了解决不同的芯片厂商生 产的 Cortex 微控制器软件 的兼容性问题,ARM 与芯片厂商建立了 CMSIS 标准(Cortex MicroController Software Interface Standard)。 所谓 CMSIS 标准,实际是新建了一个软件抽象层,具体见图。

CMSIS 标准中最主要的为 CMSIS 核心层,CMSIS 又分为 3 个基本功能层:

1) 核内外设访问层:ARM 公司提供的访问,定义处理器内部寄存器地址以及功能函数。

2) 中间件访问层:定义访问中间件的通用 API。由 ARM 提供,芯片厂商根据需要更新。

3) 外设访问层:定义硬件寄存器的地址以及外设的访问函数。

      从图中可以看出,CMSIS 层在整个系统中是处于中间层,向下负责与内核和各个外设直接打交 道,向上提供实时操作系统用户程序调用的函数接口。如果没有 CMSIS 标准,那么各个芯片公 司就会设计自己喜欢的风格的库函数,而 CMSIS 标准就是要强制规定,芯片生产公司设计的库 函数必须按照 CMSIS 这套规范来设计。 其实不用这么讲这么复杂的,一个简单的例子,我们在使用 STM32 芯片的时候首先要进行系统初始化,CMSIS 规范就规定,系统初始化函数名字必须为 SystemInit,所以各个芯片公司写自己的库函数的时候就必须用 SystemInit 对系统进行初始化。CMSIS 还对各个外设驱动文件的文件名字规范化,以及函数名字规范化等等一系列规定。上一节讲的函数 GPIO_ResetBits 这个函数名字也是不能随便定义的,是要遵循 CMSIS 规范的。 

        可见 CMSIS 层位于硬件层与操作系统或用户层之间,提供了与芯片生产商无关的硬件抽象层,可以为接口外设、实时操作系统提供简单的处理器软件接口,屏蔽了硬件差异, 这对软件的移植是有极大的好处的。STM32 的库,就是按照 CMSIS 标准建立的。

2.2 STM32F4 官方库包介绍

     这一节内容主要讲解 ST 官方提供的 STM32F4 固件库包的结构。ST 官方提供的固件库完整包可以在官方网站下载。固件库是不断完善升级的,所以有不同的版本, 我使用的是 V1.0.8 版本的固件库,但是这里讲解的例程全部采用 1.8.0 库文件。以下内容请大家打开 STM32 标准库文件配合阅读。

2.2.1 库目录、文件简介

     首先先看看官方库包1.8.0的目录结构,解压库文件后进入其目录: “STM32F4xx_DSP_StdPeriph_Lib_V1.8.0\”如下图所示:

 (1)Libraries 文件夹:它的下面有 CMSIS 和 STM32F4xx_StdPeriph_Driver 两个目录,这两个目录包含固件库核心的所有子文件夹和文件。主要为驱动库的源代码及启动文件

          CMSIS 文件夹存放的是符合 CMSIS 规范的一些文件。包括 STM32F4 核内外设访问层代码, DSP 软件库,RTOS API,以及 STM32F4 片上外设访问层代码等。我们后面新建工程的时候会从这个文件夹复制一些文件到我们工程。

          STM32F4xx_StdPeriph_Driver 放的是 STM32F4 标准外设固件库源码文件和对应的头文件。 inc 目录存放的是 stm32f4xx_ppp.h 头文件(外设),无需改动。src 目录下面放的是 stm32f4xx_ppp.c 格式的固件库源码文件(外设)。每一个.c 文件和一个相应的.h 文件对应。这里的文件也是固件库外设的关键文件每个外设对应一组文件 Libraries 文件夹里面的文件在我们建立工程的时候都会使用到。

(2)Project  文件夹:它的下面有两个文件夹。 主要是用驱动库写的例子和工程模板。

        STM32F4xx_StdPeriph_Examples 文件夹下面存放的的 ST 官方提供的固件实例源码,在以后的开发过程中,可以参考修改这个官方提供的实例来快速驱动自己的外设,很多开发板的实例都参考了官方提供的例程源码,这些源码对以后的学习非常重要。                   STM32F4xx_StdPeriph_Template 文件夹下面存放的是工程模板。

(3)Utilities 文件夹:它的下面就是官方评估板的一些对应源码,这个对于本手册学习可以忽略不看。包含了基于 ST 官方实验板的例程,以及第三方软件库,如 emwin 图形软件 库、fatfs 文件系统。

(4) stm32f4xx_dsp_stdperiph_lib_um.chm 文件:库帮助文档,这是一个已经编译好的 HTML 文件,主要讲述如何使用驱动库来编写自己的应用程序。说得形象一点,这个 HTML 就是告诉我 们:ST 公司已经为你写好了每个外设的驱动了,想知道如何运用这些例子就来向我求 救吧。不幸的是,这个帮助文档是英文的,这对很多英文不好的朋友来说是一个很大 的障碍。但这里要告诉大家,英文仅仅是一种工具,绝对不能让它成为我们学习的障 碍。其实这些英文还是很简单的,我们需要的是拿下它的勇气。

(5)MCD-ST Liberty…:库文件的 License 说明。

(6) Release_Note.html::库的版本更新说明。

        在使用库开发时,我们需要把 libraries 目录下的库函数文件添加到工程中,并查阅库帮助文档来了解 ST 提供的库函数或者直接在工程里面直接跳转到函数声明和定义处直接查看,这个文档说明了每一个库函数的使用方法。 

2.2.2 主要文件介绍

         这一节我们主要介绍这些文件夹下每个文件里面具体内容和作用是什么?进入 Libraries 文件夹看到,关于内核与外设的库文件分别存放在 CMSIS 和 STM32F4xx_StdPeriph_Driver 文件夹中。先看看 CMSIS 文件夹。STM32F4xx_DSP_StdPeriph_Lib_V1.8.0\Libraries\CMSIS\文件夹下内容见图 。其中 Device 与 Include 中的文件是我们使用得最多的,先讲解这两个文件夹中的内容。

1. Include 文件夹

       在 Include 文件夹中包含了 的是位于 CMSIS 标准的核内设备函数层的 Cortex-M 核通用的头文件,它们的作用是为那些采用 Cortex-M 核设计 SOC 的芯片商设计的芯片外设提供一个进入内核的接口,定义了一些内核相关的寄存器(类似我们前面写的 stm32f4xx.h 文件, 但定义的是内核部分的寄存器)。这些文件在其它公司的 Cortex-M 系列芯片也是相同的。 至于这些功能是怎样用源码实现的,可以不用管它,只需把这些文件加进我们的工程文件即可,有兴趣的朋友可以深究,关于内核的寄存器说明,需要查阅《cortex_m4_Technical Reference Manual》及《Cortex™-M4 内核参考手册》文档,《STM32 参考手册》只包含片上外设说明,不包含内核寄存器。

      我们写 STM32F4 的工程,必须用到其中的四个文件:core_cm4.h、core_cmFunc.h、 corecmInstr.h、core_cmSimd.h,其它的文件是属于其它内核的,还有几个文件是 DSP 函数库使用的头文件。

core_cm4.h:实现了内核(CPU)里面的外设的寄存器映射
core_cm4.c:内核外设的驱动固件库

        core_cm4.c 文件有一些与编译器相关条件编译语句,用于屏蔽不同编译器的差异。里 面包含了一些跟编译器相关的信息,如:“__CC_ARM ”(本书采用的 RVMDK、KEIL), “__GNUC__ ”(GNU 编译器)、“ICC Compiler” (IAR 编译器)。这些不同的编译器对于 C 嵌入汇编或内联函数关键字的语法不一样,这段代码统一使用“__ASM、__INLINE”宏 来定义,而在不同的编译器下,宏自动更改到相应的值,实现了差异屏蔽,见代码清单 10-1。

较重要的是在 core_cm4.c 文件中包含了“stdint.h” 这个头文件,这是一个 ANSI C 文 件,是独立于处理器之外的,就像我们熟知的 C 语言头文件 “stdio.h” 文件一样。位于 RVMDK 这个软件的安装目录下,主要作用是提供一些类型定义。见代码清单 10-2。

这些新类型定义屏蔽了在不同芯片平台时,出现的诸如 int 的大小是 16 位,还是 32 位 的差异。所以在我们以后的程序中,都将使用新类型如 uint8_t 、uint16_t 等。在稍旧版的程序中还经常会出现如 u8、u16、u32 这样的类型,分别表示的无符号的 8 位、16 位、32 位整型。初学者碰到这样的旧类型感觉一头雾水,它们定义的位置在 STM32f4xx.h 文件中。建议在以后的新程序中尽量使用 uint8_t 、uint16_t 类型的定义。

core_cm4.c 跟启动文件一样都是底层文件,都是由 ARM 公司提供的,遵守 CMSIS 标 准,即所有 CM4 芯片的库都带有这个文件,这样软件在不同的 CM4 芯片的移植工作就得 以简化。 

2.Device 文件夹 

    在 Device 文件夹下的是具体芯片直接相关的文件,包含启动文件、芯片外设寄存器定 义、系统时钟初始化功能的一些文件,这是由 ST 公司提供的。

system_stm32f4xx.c 文件

       文件目录:\Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates

       这个文件包含了 STM32 芯片上电后初始化系统时钟、扩展外部存储器用的函数,这个里面有一个非常重要的 SystemInit()函数申明,这个函数在我们系统启动的时候都会调用,用 来设置系统的整个系统和总线时钟。例如 供启动文件调用的“SystemInit”函数,用于上电后初始化时钟,该函数的定义就存储在 system_stm32f4xx.c 文件。STM32F429 系列的芯片,调用库的这个 SystemInit 函数后,系统时钟被初始化为 180MHz,如有需要可以修改这个文件的内容,设 置成自己所需的时钟频率。

启动文件

       文件目录:Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates

在这个目录下,还有很多文件夹,如“arm”、“gcc_ride7”、“iar”等,这些文件夹下包含了对应编译平台的汇编启动文件,在实际使用时要根据编译平台来选择。我们使用的 MDK 启动文件在“arm”文件夹中。其中的“strartup_stm32f40xx.s”即为 STM32F407 芯片的启动文件,创建工程中使用的启动文件就是从这里复制过去的。如果使用其它型号的芯片,要在此处选择对应的启动文件,如 STM32F446 型号使用 “startup_stm32f446xx.s”文件。

stm32f4xx.h 文件和system_stm32f4xx.h 文件

       文件目录: Libraries\CMSIS\Device\ST\STM32F4xx\Include

        stm32f4xx.h 这个文件非常重要,是一个 STM32 芯片底层相关的文件。它是我们前两 章自己定义的“stm32f4xx.h”文件的完整版,包含了 STM32 中所有的外设寄存器地址和结 构体类型定义,在使用到 STM32 标准库的地方都要包含这个头文件。stm32f4xx.h 是 STM32F4 片上外设访问层头文件。这个文件就相当重要了,只要你做 STM32F4 开发,你几乎时刻都要查看这个文件相关的定义。这个文件打开可以看到,里面非常多的结构体以及宏定义。这个文件里面主要是系统寄存器定义申明以及包装内存操作,对于这 里是怎样申明以及怎样将内存操作封装起来的.

CMSIS 文件夹中的主要内容就是这样,接下来我们看看 STM32F4xx_StdPeriph_Driver 文件夹。

3.. STM32F4xx_StdPeriph_Driver 文件夹

      文件目录:Libraries\STM32F4xx_StdPeriph_Driver 进入 libraries 目录下的 STM32F4xx_StdPeriph_Driver 文件夹,见图 。

STM32F4xx_StdPeriph_Driver 文件夹下有 inc(include 的缩写)跟 src(source 的简写) 这两个文件夹,这里的文件属于 CMSIS 之外的的、芯片片上外设部分。

src 里面是每个设备外设的驱动源程序,inc 则是相对应的外设头文件。src 及 inc 文件夹是 ST 标准库的主要内容,甚至不少人直接认为 ST 标准库就是指这些文件,可见其重要性。            在 src 和 inc 文件夹里的就是 ST 公司针对每个 STM32 外设而编写的库函数文件,每个外设对应一个 .c 和 .h 后缀的文件。我们把这类外设文件统称为:stm32f4xx_ppp.c 或 stm32f4xx_ppp.h 文件,PPP 表示外设名称。如在上一章中我们自建的 stm32f4xx_gpio.c 及 stm32f4xx_gpio.h 文件,就属于这一类。

      如针对模数转换(ADC)外设,在 src 文件夹下有一个 stm32f4xx_adc.c 源文件,在 inc 文 件夹下有一个 stm32f4xx_adc.h 头文件,若我们开发的工程中用到了 STM32 内部的 ADC, 则至少要把这两个文件包含到工程里。见图 。

       

       这两个文件夹中,还有一个很特别的 misc.c 文件,这个文件提供了外设对内核中的 NVIC(中断向量控制器)的访问函数,在配置中断时,我们必须把这个文件添加到工程中。其中 misc.c 和 misc.h 是定义中断优先级分组以及 Systick 定时器相关的函数。misc.h:NVIC_InitTypeDef结构体,以及库函数的参数和声明,misc.c:NVIC(嵌套向量中断控制器)、SysTick(系统滴答定时器)相关函数

接下来,我们接着看根目录下的另一个文件夹Project

4. stm32f4xx_it.c、stm32f4xx_it.h、 stm32f4xx_conf.h 文件 

文件目录: STM32F4xx_DSP_StdPeriph_Lib_V1.8.0\Project\STM32F4xx_StdPeriph_Templates 

        在这个文件目录下,存放了官方的一个库工程模板,我们在用库建立一个完整的工程 时,还需要添加这个目录下的 stm32f4xx_it.c、stm32f4xx_it.h、stm32f4xx_conf.h 这三个文件

        stm32f4xx_it.c、stm32f4xx_it.h:这个文件是专门用来编写中断服务函数的,在我们修改前,这个文件已经定义了一些系统异常(特殊中断)的接口,其它普通中断服务函数由我们自己添加。但是我们怎么知道这些中断服务函数的接口如何写?是不是可以自定义呢?答案当然不是的, 这些都有可以在汇编启动文件中找到,在学习中断和启动文件的时候我们会详细介绍,中断服务函数也可以随意编写在工程里面的任意一个文件里面,个人觉得这个文件 没太大意义。

      stm32f4xx_conf.h:这个文件被包含进 stm32f4xx.h 文件。ST 标准库支持所有 STM32F4 型号的芯片,但有的型号芯片外设功能比较多,所以使用这个配置文件根据芯片 型号增减 ST 库的外设文件,具体见代码清单 10-3,针对 STM32F429 和 STM32F427 型号 芯片的差异,它们实际包含不一样的头文件,我们通过宏来指定芯片的型号。 stm32f4xx_conf.h 是外设驱动配置文件。文件打开可以看到一堆的#include,这里你建立工程的时候,可以注释掉一些你不用的外设头文件。这里相信大家一看就明白。

stm32f4xx_conf.h 这个文件还可配置是否使用“断言”编译选项,见代码清单 10-4。在 ST 标准库的函数中,一般会包含输入参数检查,即上述代码中的“assert_param” 宏,当参数不符合要求时,会调用“assert_failed”函数,这个函数默认是空的。 实际开发中使用断言时,先通过定义 USE_FULL_ASSERT 宏来使能断言,然后定义 “assert_failed”函数,通常我们会让它调用 printf 函数输出错误说明。使能断言后,程序 运行时会检查函数的输入参数,当软件经过测试,可发布时,会取消 USE_FULL_ASSERT 宏来去掉断言功能,使程序全速运行。 

代码清单 10-4 断言配置
1 #ifdef USE_FULL_ASSERT
2 
3 /**
4 * @brief The assert_param macro is used for parameters check.
5 * @param expr: If expr is false, it calls assert_failed function
6 * which reports the name of the source file and the source
7 * line number of the call that failed.
8 * If expr is true, it returns no value.
9 * @retval None
10 */
11 #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t 
*)__FILE__, __LINE__))
12 /* Exported functions ---------------------------------- */
13 void assert_failed(uint8_t* file, uint32_t line);
14 #else
15 #define assert_param(expr) ((void)0)
16 #endif /* USE_FULL_ASSERT */

这一节我们就简要介绍到这里,后面我们会介绍怎样建立基于 V1.0.8 版本固件库的工程模板 .

2.3 固件库各个文件的关系(重点理解)

      前面向大家简单介绍了各个库文件的作用,库文件是直接包含进工程即可,丝毫不用修改,而有的文件就要我们在使用的时候根据具体的需要进行配置。接下来从整体上把握 一下各个文件在库工程中的层次或关系,这些文件对应到 CMSIS 标准架构上,如图所示。

       图描述了 STM32 库各文件之间的调用关系,这个图省略了 DSP 核和实时系统层 部分的文件关系。在实际的使用库开发工程的过程中,我们把位于 CMSIS 层的文件包含进工程,除了特殊系统时钟需要修改 system_stm32f4xx.c,其它文件丝毫不用修改,也不建议修改。对于位于用户层的几个文件,就是我们在使用库的时候,针对不同的应用对库文件进行增删(用条件编译的方法增删)和改动的文件。

启动文件作用:

      启动文件到底什么作用,其实我们可以打开启动文件进去看看。启动文件主要是进行堆栈之类的初始化,中断向量表以及中断函数定义。启动文件要引导进入 main 函数。Reset_Handler 中断函数是唯一实现了的中断处理函数,其他的中断函数基本都是死循环。Reset_handler 在我们系统启动的时候会调用,下面让我们看看 Reset_handler 这段代码:

这段代码的作用是在系统复位之后引导进入 main 函数,同时在进入 main 函数之前,首先要调 用 SystemInit 系统初始化函数。

2.4 简单总结

       这里对以上文件的组织结构进行总结,掌握核心的文件,使我们开发做到心中有数,知其然知其所以然,对于排除错误和上手一个新的32开发板,同等重要。

1. 汇编编写的启动文件

      strartup_stm32f40xx.s:启动文件,由汇编语言编写的,是系统启动之后第一个需要执行的文件,主要完成的作用是::设置堆栈指针、设置PC指针、初始化中断向量表、配置系统时钟、对用C库函数_main最终去到C的世界


2. 时钟配置文件

  1.  system_stm32f4xx.c
  2.  system_stm32f4xx.h两个文件

1)用于系统初始化
2) 配置系统时钟,把外部时钟HSE=8M,经过PLL倍频为168M。


3.外设相关的文件

  1. sta32f4xx.h: 实现了内核之外的外设的寄存器映射;
  2. stm32f4xx_ xxx.c: 外设的驱动函数库文件
  3. stm32f4xx_ xxx. h: 存放外设的初始化结构体,外设初始化结构体成员的参数列表,外设固件库函数的声明,xxx: GPIO、USRAT. I2C、SPI、FSMC.


4.内核相关的文件

CMSIS - Cortex 微控制器软件接口标准

  1. core_ cm4. h:实现了内核里面外设的寄存器映射
  2. core_ cm4. c: 内核外设的驱动固件库
  3. misc. h    NWIC(嵌套向量中断控制器)、SysTick(系統滴答定时器)
  4. misc. C
     

5. 头文件的配置文件

stm32f4xx_ .conf.h: 头文件的头文件,条件编译
//stm32f4xx_ usart. h
//stm32f4xx_ 12c. h
//stm32f4xx_ spi. h
//stm32f4xx_ adc. h
//stm32f4xx_ fsmc. h

6.专门存放中断服务函数的C文件

  1. stn32f4xx_ it. c
  2. stm32f4xx_ it. h

中断服务函数你可以随意放在其他的地方,并不是一定要放在stm32f4xx_ it. c

7. 用户/程序员 

  1. main.c 函数存在的地方,  以及分模块/多文件编程的其他文件
  2. stm32f10x_ it. h 、stm32f10x_ it. c用户编写的中断服务函数都放在这里,或者直接写在主函数里面
#include ”stm32f4xx.h” //相当于51单片机中的 #include <reg51. h>
int main(void)
{
      //来到这里的时候,系统的时钟己经被配置成168M。
}

 

2.5 固件库使用帮助文档

       我坚信,授之以鱼不如授之以渔。官方资料是所有关于 STM32 知识的源头,所以在本小节介绍如何使用官方资料。官方的帮助手册,是最好的教程,几乎包含了所有在开发过 程中遇到的问题。

2.5.1 常用官方资料

  1. 《STM32F4xx 中文参考手册》 这个文件全方位介绍了 STM32 芯片的各种片上外设,它把 STM32 的时钟、存储器架构、及各种外设、寄存器都描述得清清楚楚。当我们对 STM32 的外设感到困惑时,可查阅 这个文档。以直接配置寄存器方式开发的话,查阅这个文档寄存器部分的频率会相当高, 但这样效率太低了。
  2. 《STM32F4xx 英文数据手册》 本文档相当于 STM32 的 datasheet,包含了 STM32 芯片所有的引脚功能说明及存储器 架构、芯片外设架构说明。后面我们使用 STM32 其它外设时,常常需要查找这个手册,了 解外设对应到 STM32 的哪个 GPIO 引脚。
  3.  《Cortex™-M4 内核参考手册》 本文档由 ST 公司提供,主要讲解 STM32 内核寄存器相关的说明,例如系统定时器、 中断等寄存器。这部分的内容是《STM32F4xx 参考手册》没涉及到的内核部分的补充。相 对来说,本文档虽然介绍了内核寄存器,但不如以下两个文档详细,要了解内核时,可作 为以下两个手册的配合资料使用。
  4.  《Cortex-M3 权威指南》、《cortex_m4_Technical Reference Manual》。 这两个手册是由 ARM 公司提供的,它详细讲解了 Cortex 内核的架构和特性,要深入了解 Cortex-M 内核,这是首选,经典中的经典,其中 Cortex-M3 版本有中文版,方便学习。 因为 Cortex-M4 内核与 Cortex-M3 内核大部分相同,可用它来学习,而 Cortex-M4 新增的 特性,则必须参考《cortex_m4_Technical Reference Manual》文档了,目前只有英文版。
  5. 《stm32f4xx_dsp_stdperiph_lib_um.chm》 这个就是本章提到的库的帮助文档,在使用库函数时,我们最好通过查阅此文件来了 解标准库提供了哪些外设、函数原型或库函数的调用的方法。也可以直接阅读源码里面的 函数的函数说明

2.5.2 初识库函数

       所谓库函数,就是 STM32 的库文件中为我们编写好的函数接口,我们只要调用这些库函数,就可以对 STM32 进行配置,达到控制目的。我们可以不知道库函数是如何实现的, 但我们调用函数必须要知道函数的功能、可传入的参数及其意义、和函数的返回值。 于是,有读者就问那么多函数我怎么记呀?我的回答是:会查就行了,哪个人记得了 那么多。所以我们学会查阅库帮助文档 是很有必要的。 打开库帮助文档《stm32f4xx_dsp_stdperiph_lib_um.chm》见图 

      层层打开文档的目录标签: 标签目录:Modules\STM32F4xx_StdPeriph_Driver\ 可看到 STM32F4xx _StdPeriph_Driver 标签下有很多外设驱动文件的名字 MISC、ADC、 BKP、CAN 等标签。 我们试着查看 GPIO 的“位设置函数 GPIO_SetBits”看看,打开标签: 标签目录:Modules\STM32F4xx_StdPeriph_Driver\GPIO\Functions\GPIO_SetBits 见图。

利用这个文档,我们即使没有去看它的具体源代码,也知道要怎么利用它了。 如 GPIO_SetBits,函数的原型为 void GPIO_SetBits(GPIO_TypeDef * GPIOx , uint16_t GPIO_Pin)。它的功能是:输入一个类型为 GPIO_TypeDef 的指针 GPIOx 参数,选定要控 制的 GPIO 端口;输入 GPIO_Pin_x 宏,其中 x 指端口的引脚号,指定要控制的引脚。 其中输入的参数 GPIOx 为 ST 标准库中定义的自定义数据类型,这两个传入参数均为 结构体指针。

       初学时,我们并不知道如 GPIO_TypeDef 这样的类型是什么意思,可以点击函数原型中带下划线的 GPIO_TypeDef 就可以查看这个类型的声明了。 就这样初步了解了一下库函数,读者就可以发现 STM32 的库是写得很优美的。每个函 数和数据类型都符合见名知义的原则,当然,这样的名称写起来特别长,而且对于我们来 说要输入这么长的英文,很容易出错,所以在开发软件的时候,在用到库函数的地方,直 接把库帮助文档中的函数名称复制粘贴到工程文件就可以了。而且,配合 MDK 软件的代码自动补全功能,可以减少输入量。 有的用户觉得使用库文档麻烦,也可以直接查阅 STM32 标准库的源码,库帮助文档的说明都是根据源码生成的,所以直接看源码也可以了解函数功能。

       以上便是STM32 认识寄存器开发与标准库开发的全部内容,涵盖了方方面面,从0开始入门,介绍ST公司是如何封装固件库的,以及固件库文件又包含哪些,哪些是重要的部分,这对于我们后期建立工程是非常重要的,请务必掌握,知其然更知其所以然,才能更好的掌握技术,如有兴趣,感谢点赞、关注、收藏,若有不正地方,还请各位大佬多多指教!

       

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未来可期,静待花开~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值