ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

上一章通过直接操作寄存器成功点亮了一个LED灯,接下来将学习一种针对复杂程序效率更高的开发方式——固件库开发。本章将通过模仿STM32的固件库构建一个简易库来帮助读者对库开发有一个初步认识。

2.1构建stm32f10x.h头文件

51单片机编程主要是通过直接操作寄存器来实现的,比如:'P0 = 0x01;'让P0.1引脚输出"高电平",其他7个引脚输出低电平。而上一章配置STM32的引脚输出高电平,采用直接访问寄存器地址的方式,比如:'*(unsigned int *)0x4001200C |= 1 << 7;'。撇开两款单片机内核结构的差异,单纯分析上面两条语句会发现它们的区别在于'='的左边,也就是"P0"与"*(unsigned int *)0x4001200C"的差异。如图2-1所示, 51单片机的 "P0"之所以能够代表'端口0'是因为使用了关键字"sfr"将51单片机端口0对应的端口地址"0x80"映射给关键字"P0"。因此,为了更方便的操作,可以新建一个头文件对STM32的GPIO端口寄存器的物理地址进行映射。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-1 51单片机通用头文件

2.1.1添加头文件

新建一个文件夹,将第一章点亮LED灯的工程直接复制过来。新建一个文本文件,保存为"stm32f10x.h"并添加到工程中,内容如图2-2所示,同时在main文件最开始添加包含语句 #include "stm32f10x.h",切记不要写成#include

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-2 新建stm32f10x.h头文件

2.1.2添加地址映射

STM32地址空间按照功能不同设置为不同的分区,包括代码区、片上静态RAM、片上外设、外部存储器等等。STM32的外设接口主要分布在片上外设分区,比如GPIO端口,USART、SPI、I2C等,如图 2-3所示,可以根据外设接口的物理地址进行映射。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-3 片上外设端口结构图

进行地址映射前首先需要了解一下STM32外设接口与总线的关系。总线是芯片各种功能部件之间传送信息的公共通信干线。而STM32内部也有许多总线,其中AHB(Advanced High Performance Bus)中文名称叫高级高性能总线,主要用于高性能模块之间进行高速通信的连接,类似于城市之间的高速公路,可以高速行驶进而缩短行程时间。当然高速公路往往不直接通到城市的任何角落,AHB总线也是如此。AHB总线与外设接口之间是通过APB总线(Advanced Peripheral Bus,外围总线的意思) 相连的,APB总线类似于城市外环公路,而STM32的外设接口就是挂载到APB总线上的。而APB总线又分为APB1总线、APB2总线来挂载STM32众多的外设接口。其中GPIO端口是挂载在APB2总线上的,控制外设接口使能的RCC时钟则是挂载在AHB总线上的。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

2.1.3GPIO端口寄存器映射

按照第一章使用寄存器点亮LED灯的配置流程,接下来需要访问GPIO端口上的寄存器来设置IO口的引脚模式,进而控制引脚输出数据。STM32F103ZE单片机的GPIO外设包含A、B、C、D、E、F、G 共7个端口,每个端口都包含七个相同的GPIO端口寄存器,并且是连续排列在每个GPIO端口上的,每个寄存器占用4字节的地址空间。如表2-1所示,将其使用结构体进行封装。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

volatile是C语言关键字,意思是"易变的",可以确保程序代码访问它所修饰的变量时每次都是直接访问其最新值,而避免编译器进行可能的假设性优化。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

然后使用GPIO_TypeDef类型指针对GPIO端口地址进行强制类型转换,具体操作如下:

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

2.1.4stm32f10x.h头文件

通过前边几节的操作便可以像51单片机一样使用逻辑地址"P0"代替物理地址"0x80"访问寄存器。比如访问"GPIO端口G的配置低寄存器(CRL)": GPIOG->CRL。

其中"->"称作箭头操作符,使用结构体指针对结构体成员进行访问,相当于(* GPIOG). CRL。至此便完成了stm32f10x.h头文件的地址映射,现在可以像操作51单片机一样对寄存器直接赋值。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

通过调用stm32f10x.h头文件可以更加友好的访问GPIO端口寄存器,对比直接操作寄存器地址,可以通过读程序而不是必须读注释就可以知道操作了哪些寄存器。

通过寄存器地址访问:

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

通过友好的命名访问:

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

2.2初始化配置

2.2.1引脚置位和复位

前几节的程序使用"端口输出数据寄存器"控制引脚输出数据,此外STM32还有两组寄存器可以实现单独对某个引脚"置位"和"清零"功能,分别是"端口位设置/清除寄存器"和"端口位清除寄存器"。如图2-4所示,端口位设置/清除寄存器的低16位(BS15~BS0位)对应每一组GPIO端口上的16个引脚,相应引脚位置1时设置该引脚输出高电平。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-4 端口位设置/清除寄存器结构图

如图2-5所示,端口位清除寄存器的低16位(BR15~BR0)同样对应每一组GPIO端口上的16个引脚,相应位置1时设置对应引脚输出低电平。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-5 端口位清除寄存器结构图

通过观察可以发现这两组寄存器的低16位(15~0位)置1时可以分别实现相应引脚输出高电平和低电平。这两组寄存器都是32位的,高16位没有用到可以忽略,剩下的低16位可以用十六进制数表示相应引脚的状态。比如0x0001表示选择引脚0,0x0002表示选择引脚1,……0xFFFF表示选择所有引脚。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

2.2.2 设置引脚模式

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

为了方便设置GPIO端口的3种输出速率和8种引脚模式,将结构体成员中的引脚速率和引脚模式分别定义为枚举类型。

使用1~3表示10MHz、2MHz、50MHz三种模式。相应的枚举结构如下:

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

如表2-2所示,为了更详细的区分每一种模式,增加4位模式区分位,这样就组成八位二进制数表示引脚模式,通过第5位来判定输入输出状态。输入模式第5位为0,输出设置第5位为1。

具体模拟输入表示为0x00;浮空输入表示为0x04;由于上拉/下拉输入的CNF和MODE位相同,都是0x08,因此使用0x28表示上拉;0x48表示下拉。接下来看一下输出模式,配置输出模式时,在配置输出速率未确定之前,MODE的复位值为00,因此通过设置第5位为1与输入模式区分开。具体通用推挽输出为0x10,通用开漏输出为0x14,复用推挽输出为0x18,复用开漏输出为0x1C。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

定义枚举结构表示引脚模式:

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

需要注意的是,定义的GPIOMode_TypeDef类型枚举和GPIOSpeed_TypeDef类型枚举应该位于GPIO_InitTypeDef类型结构体前面。因为GPIO_InitTypeDef类型结构体中调用了这两个枚举类型定义引脚速率和引脚模式。

2.2.3初始化函数

GPIO初始化函数这部分代码是官方固件库中的源码,用户可以直接拿来使用,代码相对略显复杂,不需要做深入研究,有兴趣的读者可以参考图2-6所示的是GPIO端口初始化函数流程图独立详细学习,在此就不详细介绍。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-6 GPIO初始化函数工作流程图

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

2.2.4初始化引脚配置

将2.2.3节关于初始化配置的相关代码分别添加到stm32f10x驱动文件中,接下来就使用一种全新的方式点亮LED灯。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

这种编写程序的方式,可以在不需要查阅手册的寄存器每一位功能的基础上,直接调用函数完成程序功能,并且从程序代码上就可以直观看出对外设进行了何种操作。

代码解析:

2、 由于没有对RCC时钟进行配置,因此使能GPIO端口G时钟仍然使用赋值操作。

3、 对定义的GPIO_InitStructure结构体中的成员进行赋值操作。之前通过指针访问结构体

成员时使用"->"箭头操作符,而通过结构体变量名访问可以使用"."点操作符。

4、 GPIO_Init(GPIOG, &GPIO_InitStructure);是调用初始化函数,GPIOG表示要配置的GPIO端口D的地址,&GPIO_InitStructure则是将前面配置好的GPIO_InitStructure结构体的地址传递给函数。初始化函数根据配置好的引脚模式自动对寄存器赋值,完成配置。

5、 GPIO_ResetBits(GPIOG,GPIO_Pin_7);调用引脚复位函数,让PG7输出低电平。

2.3库函数

通过前面两节利用stm32f10x.h的头文件的宏定义,以及调用stm32f10x.c中的初始化函数可以很方便的对GPIO端口进行配置,避免了直接操作寄存器的繁杂。然而STM32有众多的外设接口,比如I2C、USART、SPI、USB、CAN等等,难道需要一一去编写驱动文件吗?当然不是。或许有许多读者在开始学习之前就了解过STM32的固件库。通过编写驱动文件的方式构建一个简单的GPIO库,目的是让读者能够深入的了解固件库的组成。STM32的固件库可以简单地理解为一个个外设接口驱动文件的集合。

STM32标准外设库也称为固件函数库,简称固件库。实际是由程序、数据结构和宏定义组成的固件函数包。类似于前边编写的GPIO端口的驱动文件,包含了STM32微控制器所有外设的寄存器命名和相关操作。通过使用固件函数库,无需深入掌握底层硬件细节,就可以轻松应用每一个外设。该函数库还包括每一个外设的驱动描述和应用实例,为开发者访问底层硬件提供了一个中间API(application programming interface 应用编程界面)。API其实就是预先定义的函数,每个外设驱动都由一组函数组成,这组函数覆盖了该外设所有功能。比如GPIO外设的API,定义了GPIO端口初始化函数,设置引脚输出高电平/低电平函数,读取引脚输入数据函数等等。因此,使用函数库可以大大减少用户的程序编写时间,进而降低开发成本。API对驱动程序的结构,函数和参数名称都进行了标准化。有了各外设的API驱动,不需要再查找地址,对寄存器赋值来操作外设,只需要调用相应的函数,从而大幅提高了开发效率。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-7 库函数开发和寄存器开发

简单的说,使用固件函数库进行开发最大的优势就在于开发者不用深入了解底层硬件和寄存器的细节就可以灵活规范的使用每一个外设。标准外设库覆盖了从GPIO到定时器,再到CAN、I2C、SPI、UART和ADC等等的所有外设。对应的C源代码只是用了最基本的C编程的知识,所有代码经过严格测试,易于理解和使用,并且配有完整的文档,非常方便进行二次开发和应用。缺点是函数的相互调用会增加系统响应时间,而寄存器开发的优势就在于执行效率高。所以在实际项目开发中,一般采用固件函数库开发为主,少数对执行效率要求高的场合可采用直接操作寄存器的方式。

2.4固件库移植

固件库开发首先要移植固件库到STM32的工程中,建立一个通用的工程文件夹,为今后做开发提供便利。

(1)首先新建一个文件夹命名为Template,再在Template文件夹下新建4个文件夹,来分类存放不同文件,具体如图2-8所示。

"Library"——用于存放库文件;

"Output"——用于存放输出文件以及编译器生成的文件;

"Project"——用于存放工程文件;

"Source"——用于存放用户自己编写的代码;

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-8 新建文件夹

(2) 如图2-9所示,在开发板配套光盘"Kingst-32F1开发板资料\STM32固件库"中找到3.5.0版本的STM32库文件源码—"STM32F10x_StdPeriph_Lib_V3.5.0",打开固件库Libraries文件夹,将"CMSIS"和"STM32F10x_StdPeriph_Driver"两个文件夹复制到 "Template\Library"文件夹中。

"CMSIS"——主要存放库自带的启动文件和Cortex-M3 系列单片机通用的文件。

"STM32F10x_StdPeriph_Driver"——主要存放外设驱动的源文件,其中"scr"用于存放外设的源文件,"inc"存放对应的头文件。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-9 固件库源码

(3)如图2-10所示,将固件库"Project\STM32F10x_StdPeriph_Template"路径下的4个文件复制到"Template\Source"文件夹中。至此固件库移植所需的所有文件都已经复制完毕,接下来就是新建一个Keil工程,然后向工程中添加文件。

"main.c" —— 主函数体示例文件

"stm32f10x_conf.h"——包含所有外设的头文件,如果屏蔽其中的头文件,则相应外设不能使 用。

"stm32f10x_it.h"—— 包含所有中断处理函数原型的头文件

"stm32f10x_it.c" ——外设中断函数文件

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-10 添加文件

(4)打开Keil,新建工程,并且将工程保存在Template文件夹下的Project文件夹中,工程命名为"Template"。工程建立完毕后,点击Keil工具栏中的File Extension按钮,如图2-11所示,点击"Groups"一栏中最左边的添加按钮,向工程中添加3个Group。

"User" —用于存放中断函数、主函数

"CMSIS"—存放Cortex-M3内核驱动

"STM32_StdPeriph"—用于存放外设源文件

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-11 添加库文件到工程(一)

(5)选中"User"Group,点击"Add Files…"向"User"Group中添加Sources文件夹下的3个文件,如图2-12所示

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-12添加库文件到工程(二)

(6) 如图2-13,向"CMSIS"Group添加启动文件,具体路径如图2-14所示。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-13添加库文件到工程(三)

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-14 文件所在路径

(7)添加外设源文件,将复制到"Template\Library\STM32F10x_StdPeriph_Driver\src"文件夹下的所有文件添加到"STM32_StdPeriph"Group中,如图2-15所示。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-15添加库文件到工程(四)

(8)库文件添加完毕后,点击Options for Target按钮。分别打开"Output"和"Listing"选项卡,在"Select Folder for xxx….."中,选择将输出文件和编译文件保存路径设置为 "Output"文件夹,设置完毕后点击"OK"即可,如图2-16所示。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-16 设置输出路径

(9)选择"C/C++"选项卡,首先向编译器添加"STM32F10X_HD和USE_STDPERIPH_DRIVER"两个宏,用英文","符号隔开。

STM32F10X_HD——告诉编译器使用的STM32F103ZE型号是大容量(高密度)产品,即Flash大于128K,不同容量的产品添加的宏不同。

USE_STDPERIPH_DRIVER——让编译器调用stm32f10x_conf.h头文件

接下来是添加使用的库函数的头文件路径,便于编译器查找工程中使用的头文件的位置。需要注意:工程中调用的任何驱动文件都需要设置头文件的路径,否则编译器会因找不到目标文件而报错,具体操作如图2-17所示。(注意:外设接口头文件路径不是src文件夹而是inc文件夹)

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-17 添加头文件路径

(10)路径设置完毕后,编译一下工程,显示"0 Error , 0 Warning(s)"表示固件库移植成功,如果提示有错误,根据提示错误,重新检查移植步骤是否出错。

(11) 新建两个文本文件保存在新建工程的Source文件夹下,分别保存文件名为config.c和config.h,两个文件作为系统配置文件,如图2-18所示。简单的延时函数可以写在config.c文件中,同时将main.c中包含的stm32f10x.h头文件添加到config.h中,这样只需要在main.c中包含config.h头文件,以后编写外设驱动程序时也只需要包含config.h头文件即可。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-18 新建config文件

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

(11)如图2-19所示,新建库函数工程界面。将这个工程保存为工程模板,以后每次使用时直接复制一份。此外下载程序时还需要在Debug选项中设置下载器,大家可以到第一章1.7小结"配置工程"中查找。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-19 新建固件库工程界面

2.5 LED和RGB小灯

RGB小灯由红光LED、绿光LED、蓝光LED并联而成,可以发出红、绿、蓝三原色及其混合光。RGB小灯有共阳和共阴之分,共阳就是三种颜色光源的阳极为公共端,阴极为控制端。本书采用的是共阳RGB小灯,其第2引脚为公共端,接电源3.3V,而第1、3、4引脚分别为红、绿、蓝三色光源的阴极控制端,由于红、绿、蓝三个光源的导通压降并不相同红光LED的压降为1.8~2.6V,绿色和蓝色LED的压降为2.8~3.6V,;除此之外它们的发光强度也各不相同,为了达到良好的发光效果,需要对R、G、B三个LED串联不同的电阻,如图2-20所示。

可以像点亮普通LED灯一样控制RGB小灯,不同的是RGB可以由红、绿、蓝三原色任意组合,达到多色光效果。表2-2是Kingst-32F1开发板LED灯和RGB小灯与STM32F103ZE单片机的引脚连接关系。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

2.6流水灯

使用库函数开发时,结合《STM32固件库使用手册》可以更好的理解每个函数的作用以及用法,该文件位于配套光盘"Kingst-32F1开发板资料\数据手册\STM32相关"。STM32固件库参考手册就像产品说明书一样,包含所有外设模块的配置函数,切记不要盲目全部阅读参考手册,用到哪些外设功能直接到参考手册中查找即可。前几节使用的简易库就是从标准函数库中摘出的关于GPIO引脚相关的函数,下面用一个简单延时的流水灯的例子巩固这个知识点。

为了提高开发效率,直接将模板固件库工程复制过来,将工程所在文件夹的名称修改为LED,同时将Projects文件夹下与工程名称相关的两个文件重命名为LED,如图2-21所示,以后移植工程文件夹时也是直接修改这两个文件。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-21 修改工程文件名

打开LED工程,新建两个文本文件分别保存为led.c和led.h,将其保存在LED工程的Source文件夹下,作为LED小灯的驱动文件,如图2-22所示。当使用LED时可以直接复制这两个文件到所在工程,今后每个模块都会采用这种形式编程,避免重复编写驱动代码。

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

图2-22 新建LED驱动文件

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

上面程序中使用了库函数中的GPIO引脚置位和清除函数,分别对IO口进行置1和清0;由于流水灯至少需要3个LED才能达到演示效果,为此采用RGB小灯的一个颜色作为一个LED,具体点亮顺序为: LED_R—>LED1—>LED2,大家可以先尝试一下自己编写,然后再比对一下参考程序。

流水灯代码:

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库

ARM嵌入式编程与实战应用(STM32F1系列)—第2章从头文件到库


分享到:


相關文章: