6.1STM32IO简介本章将要实现的是控制ALIENTEK战舰STM32开发板上的两个LED实现一个类似跑马灯的效果,该实验的关键在于如何控制STM32的IO口输出。了解了STM32的IO口如何输出的,就可以实现跑马灯了。通过这一章的学习,你将初步掌握STM32基本IO口的使用,而这是迈向STM32的第一步。这一章节因为是第一个实验章节,所以我们在这一章将讲解一些知识为后面的实验做铺垫。为了小节标号与后面实验章节一样,这里我们不另起一节来讲。在讲解STM32的GPIO之前,首先打开我们光盘的第一个固件库版本实验工程跑马灯实验工程(光盘目录为:“4,程序源码\标准例程-V3.5库函数版本\实验1跑马灯/USER/LED.Uv2”),可以看到我们的实验工程目录:图6.1.1跑马灯实验目录结构接下来我们逐一讲解一下我们的工程目录下面的组以及重要文件。①组FWLib下面存放的是ST官方提供的固件库函数,里面的函数我们可以根据需要添加和删除,但是一定要注意在头文件stm32f10x_conf.h文件中注释掉删除的源文件对应的头文件,这里面的文件内容用户不需要修改。②组CORE下面存放的是固件库必须的核心文件和启动文件。这里面的文件用户不需要修改。③组SYSTEM是ALIENTEK提供的共用代码,这些代码的作用和讲解在第五章都有讲解,大家可以翻过去看下。④组HARDWARE下面存放的是每个实验的外设驱动代码,他的实现是通过调用FWLib下面的固件库文件实现的,比如led.c里面调用stm32f10x_gpio.c里面的函数对led进行初始化,这里面的函数是讲解的重点。后面的实验中可以看到会引入多个源文件。⑤组USER下面存放的主要是用户代码。但是system_stm32f10x.c文件用户不需要修改,同时stm32f10x_it.c里面存放的是中断服务函数,这两个文件的作用在3.1节有讲解,大家可以翻过去看看。Main.c函数主要存放的是主函数了,这个大家应该很清楚。针对第①步中怎么随意添加和删除固件库文件,这里我们稍微讲解一下。首先从上面的图中可以看到,stm32f10x_gpio.c源文件下面include了好几个头文件,其中有一个stm32f10x_conf.h,这个文件会被每个固件库源文件引用。我们可以打开看看里面的内容:图6.1.2stm32f10x_conf文件内容从图中可以看出,在头文件stm32f10x_conf.h文件中,我们包含了四个.h头文件,那是因为我们的FWLib组下面引入了相应的4个.c源文件。同时大家记住,后面三个源文件stm32f10x_rcc.c,stm32f10x_usart.c以及misc.c在每个实验基本都需要添加。在这个实验中,因为LED是关系到STM32的GPIO,所以我们增加了stm32f10x_gpio.c和头文件stm32f10x_gpio.h的引入。添加和删除固件库源文件的步骤是:1.在stm32f10x_conf.h文件引入需要的.h头文件。这些头文件在每个实验的目录\STM32F10x_FWLib\inc下面都有存放。2.在FWLib下面加入步骤一中引入的.h头文件对应的源文件。记住最好一一对应,否则就有可能会报错。这些源文件在每个实验的\STM32F10x_FWLib\src目录下面都有存放。添加方法请参考3.3节的内容。最后我们讲解一下这些组之间的层次结构:图6.1.3代码层次结构图从层次图中可以看出,我们的用户代码和HARDWARE下面的外设驱动代码再不需要直接操作寄存器,而是直接或间接操作官方提供的固件库函数。但是后面我们的为了让大家更全面方便的了解外设,我们会增加重要的外设寄存器的讲解,这样对底层知识更加了解,方便我们深入学习固件库。准备内容我们就讲解到这里,接下来我们就要进入我们跑马灯实验的讲解部分了。这里需要说明一下,我们在讲解固件库之前会首先对重要寄存器进行一个讲解,这样是为了大家对寄存器有个初步的了解。大家学习固件库,并不需要记住每个寄存器的作用,而只是通过了解寄存器来对外设一些功能有个大致的了解,这样对以后的学习也很有帮助。首先要提一下,在固件库中,GPIO端口操作对应的库函数函数以及相关定义在文件stm32f10x_gpio.h和stm32f10x_gpio.c中。STM32的IO口相比51而言要复杂得多,所以使用起来也困难很多。首先STM32的IO口可以由软件配置成如下8种模式:1、输入浮空2、输入上拉3、输入下拉4、模拟输入5、开漏输出6、推挽输出7、推挽式复用功能8、开漏复用功能每个IO口可以自由编程,但IO口寄存器必须要按32位字被访问。STM32的很多IO口都是5V兼容的,这些IO口在与5V电平的外设连接的时候很有优势,具体哪些IO口是5V兼容的,可以从该芯片的数据手册管脚描述章节查到(I/OLevel标FT的就是5V电平兼容的)。STM32的每个IO端口都有7个寄存器来控制。他们分别是:配置模式的2个32位的端口配置寄存器CRL和CRH;2个32位的数据寄存器IDR和ODR;1个32位的置位/复位寄存器BSRR;一个16位的复位寄存器BRR;1个32位的锁存寄存器LCKR。大家如果想要了解每个寄存器的详细使用方法,可以参考《STM32中文参考手册V10》P105~P129。CRL和CRH控制着每个IO口的模式及输出速率。STM32的IO口位配置表如表6.1.4所示:表6.1.4STM32的IO口位配置表STM32输出模式配置如表6.1.5所示:表6.1.5STM32输出模式配置表接下来我们看看端口低配置寄存器CRL的描述,如图6.1.6所示:图6.1.6端口低配置寄存器CRL各位描述该寄存器的复位值为0X44444444,从图6.1.4可以看到,复位值其实就是配置端口为浮空136输入模式。从上图还可以得出:STM32的CRL控制着每组IO端口(A~G)的低8位的模式。每个IO端口的位占用CRL的4个位,高两位为CNF,低两位为MODE。这里我们可以记住几个常用的配置,比如0X0表示模拟输入模式(ADC用)、0X3表示推挽输出模式(做输出口用,50M速率)、0X8表示上/下拉输入模式(做输入口用)、0XB表示复用输出(使用IO口的第二功能,50M速率)。CRH的作用和CRL完全一样,只是CRL控制的是低8位输出口,而CRH控制的是高8位输出口。这里我们对CRH就不做详细介绍了。下面我们讲解一下怎样通过固件库设置GPIO的相关参数和输出。GPIO相关的函数和定义分布在固件库文件stm32f10x_gpio.c和头文件stm32f10x_gpio.h文件中。在固件库开发中,操作寄存器CRH和CRL来配置IO口的模式和速度是通过GPIO初始化函数完成:voidGPIO_Init(GPIO_TypeDef*GPIOx,GPIO_InitTypeDef*GPIO_InitStruct);这个函数有两个参数,第一个参数是用来指定GPIO,取值范围为GPIOA~GPIOG。第二个参数为初始化参数结构体指针,结构体类型为GPIO_InitTypeDef。下面我们看看这个结构体的定义。首先我们打开我们光盘的跑马灯实验,然后找到FWLib组下面的stm32f10x_gpio.c文件,定位到GPIO_Init函数体处,双击入口参数类型GPIO_InitTypeDef后右键选择“Gotodefinitionof…”可以查看结构体的定义:typedefstruct{uint16_tGPIO_Pin;GPIOSpeed_TypeDefGPIO_Speed;GPIOMode_TypeDefGPIO_Mode;}GPIO_InitTypeDef;下面我们通过一个GPIO初始化实例来讲解这个结构体的成员变量的含义。通过初始化结构体初始化GPIO的常用格式是:GPIO_InitTypeDefGPIO_InitStructure;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;//LED0--PB.5端口配置GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;//推挽输出GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;//速度50MHzGPIO_Init(GPIOB,&GPIO_InitStructure);//根据设定参数配置GPIO上面代码的意思是设置GPIOB的第5个端口为推挽输出模式,同时速度为50M。从上面初始化代码可以看出,结构体GPIO_InitStructure的第一个成员变量GPIO_Pin用来设置是要初始化哪个或者哪些IO口;第二个成员变量GPIO_Mode是用来设置对应IO端口的输出输入模式,这些模式是上面我们讲解的8个模式,在MDK中是通过一个枚举类型定义的:typedefenum{GPIO_Mode_AIN=0x0,//模拟输入GPIO_Mode_IN_FLOATING=0x04,//浮空输入GPIO_Mode_IPD=0x28,//下拉输入GPIO_Mode_IPU=0x48,//上拉输入GPIO_Mode_Out_OD=0x14,//开漏输出GPIO_Mode_Out_PP=0x10,//通用推挽输出GPIO_Mode_AF_OD=0x1C,//复用开漏输出GPIO_Mode_AF_PP=0x18//复用推挽}GPIOMode_TypeDef;第三个参数是IO口速度设置,有三个可选值,在MDK中同样是通过枚举类型定义:typedefenum{GPIO_Speed_10MHz=1,GPIO_Speed_2MHz,GPIO_Speed_50MHz}GPIOSpeed_TypeDef;这些入口参数的取值范围怎么定位,怎么快速定位到这些入口参数取值范围的枚举类型,在我们上面章节4.7的“快速组织代码”章节有讲解,不明白的朋友可以翻回去看一下,这里我们就不重复讲解,在后面的实验中,我们也不再去重复讲解怎么定位每个参数的取值范围的方法。IDR是一个端口输入数据寄存器,只用了低16位。该寄存器为只读寄存器,并且只能以16位的形式读出。该寄存器各位的描述如图6.1.7所示:图6.1.7端口输入数据寄存器IDR各位描述要想知道某个IO口的电平状态,你只要读这个寄存器,再看某个位的状态就可以了。使用起来是比较简单的。在固件库中操作IDR寄存器读取IO端口数据是通过GPIO_ReadInputDataBit函数实现的:uint8_tGPIO_ReadInputDataBit(GPIO_TypeDef*GPIOx,uint16_tGPIO_Pin)比如我要读GPIOA.5的电平状态,那么方法是:GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5);返回值是1(Bit_SET)或者0(Bit_RESET);ODR是一个端口输出数据寄存器,也只用了低16位。该寄存器为可读写,从该寄存器读出来的数据可以用于判断当前IO口的输出状态。而向该寄存器写数据,则可以控制某个IO口的输出电平。该寄存器的各位描述如图6.1.8所示:图6.1.8端口输出数据寄存器ODR各位描述在固件库中设置ODR寄存器的值来控制IO口的输出状态是通过函数GPIO_Write来实现的:voidGPIO_Write(GPIO_TypeDef*GPIOx,uint16_tPortVal);该函数一般用来往一次性一个GPIO的多个端口设值。BSRR寄存器是端口位设置/清除寄存器。该寄存器和ODR寄存器具有类似的作用,都可以用来设置GPIO端口的输出位是1还是0。下面我们看看该寄存器的描述如下图:图6.1.9端口位设置/清除寄存器BSRR各位描述该寄存器通过举例子可以很清楚了解它的使用方法。例如你要设置GPIOA的第1个端口值为1,那么你只需要往寄存器BSRR的低16位对应位写1即可:GPIOA-BSRR=11;如果你要设置GPIOA的第1个端口值为0,你只需要