第3章
CHAPTER 3


通用输入/输出模块





与51单片机不同,STM32处理器拥有更多的输入/输出引脚,其对外围设备的驱动能力更强,包括更多更灵活的外围设备控制方式,更多更强大的外围设备功能。在使用STM32处理器这些功能之前,必须对其进行正确配置。本章所介绍的通用输入/输出(General Purpose Input/Output,GPIO)是STM32处理器中非常重要的片内外围设备,是实现外围设备数据输入到STM32处理器或STM32处理器输出数据到外围设备的关键。更重要的是,本章的内容是学习后面章节的基础。本章首先介绍GPIO的基本概念及其工作原理,然后以STM32F103为例讲述GPIO的编程开发,最后通过一个开发案例说明如何使用STM32F103的GPIO。

掌握输入/输出模块的有关概念; 
掌握STM32的GPIO; 
了解STM32的GPIO库函数; 
掌握STM32的GPIO开发流程; 
熟练使用STM32CubeMX工具开发GPIO项目; 
熟练应用Keil软件编译环境和仿真环境; 
了解Proteus仿真环境。
3.1输入/输出
输入/输出(Input/Output,I/O)是指相对微控制器而言作为外围设备的输入或输出的标准双向输入/输出接口,例如采集传感器输入的数据、是否有按键产生、点亮LED灯等。一般按照不同的工作模式,可进一步将微控制器芯片的I/O分为准双向I/O、推挽输出、高阻态、开漏。
准双向I/O模式是指该引脚既可以接收来自外围设备的输入信号,又可以输出信号给外围设备。
推挽输出是指利用相对来说比较大的电流对外围电路设备进行驱动,在该模式输出高电平或低电平。
高阻态是指输入/输出的电阻非常大,该模式又分为高阻输入或高阻输出。其中,高阻输入是指以较高的输入阻抗来获取外围设备的输入信号,例如A/D采集数据作为输入; 高阻输出是指既不输出高电平也不输出低电平。
开漏与准双向I/O模式相近,该模式既可以读取I/O接口的输入电平,又可以输出高电平和低电平。如果作输出,默认输出低电平,如需要输出高电平,则需根据具体的应用选择适合的上拉电阻,并搭建外围电路来提高I/O接口的驱动能力。
3.2STM32的GPIO
通用输入/输出(General Purpose Input/Output,GPIO)是指STM32微控制器片内外设中可配置的输入/输出接口。通过对GPIO引脚进行必要的功能初始化就可实现与外部设备连接的功能,从而达到STM32微控制器与外部设备通信来控制设备以及采集数据的目的。GPIO的引脚对应了不同功能的寄存器,配置STM32的GPIO实际上是更改引脚所对应寄存器的值,以便实现某种输入/输出功能。表31列出了STM32F10xxx系列芯片内置外设的起始地址。


表31寄存器组起始地址



起 始 地 址外设总线

0x5000 0000  0x5003 FFFFUSB OTG 全速
0x4003 0000  0x4FFF FFFF保留AHB
0x4002 8000  0x4002 9FFF以太网
0x4002 3400  0x4002 3FFF保留
0x4002 3000  0x4002 33FFCRC
0x4002 2000  0x4002 23FF闪存存储器接口
0x4002 1400  0x4002 1FFF保留
0x4002 1000  0x4002 13FF复位和时钟控制(RCC)AHB
0x4002 0800  0x4002 0FFF保留
0x4002 0400  0x4002 07FFDMA2
0x4002 0000  0x4002 03FFDMA1
0x4001 8400  0x4001 7FFF保留
0x4001 8000  0x4001 83FFSDIO
0x4001 4000  0x4001 7FFF保留
0x4001 3C00  0x4001 3FFFADC3
0x4001 3800  0x4001 3BFFUSART1
0x4001 3400  0x4001 37FFTIM8定时器
0x4001 3000  0x4001 33FFSPI1
0x4001 2C00  0x4001 2FFFTIM1定时器
0x4001 2800  0x4001 2BFFADC2
0x4001 2400  0x4001 27FFADC1
0x4001 2000  0x4001 23FFGPIO端口GAPB2
0x4001 2000  0x4001 23FFGPIO端口F
0x4001 1800  0x4001 1BFFGPIO端口E
0x4001 1400  0x4001 17FFGPIO端口D
0x4001 1000  0x4001 13FFGPIO端口C
0x4001 0C00  0x4001 0FFFGPIO端口B
0x4001 0800  0x4001 0BFFGPIO端口A
0x4001 0400  0x4001 07FFEXTI
0x4001 0000  0x4001 03FFAFIO
续表


起 始 地 址外设总线

0x4000 7800  0x4000 FFFF保留
0x4000 7400  0x4000 77FFDAC
0x4000 7000  0x4000 73FF电源控制(PWR)
0x4000 6C00  0x4000 6FFF后备寄存器(BKP)
0x4000 6800  0x4000 6BFFbxCAN2
0x4000 6400  0x4000 67FFbxCAN1
0x4000 6000  0x4000 63FFUSB/CAN共享的512字节SRAM
0x4000 5C00  0x4000 5FFFUSB全速设备寄存器
0x4000 5800  0x4000 5BFFI2C2
0x4000 5400  0x4000 57FFI2C1
0x4000 5000  0x4000 53FFUART5
0x4000 4C00  0x4000 4FFFUART4
0x4000 4800  0x4000 4BFFUSART3
0x4000 4400  0x4000 47FFUSART2APB1
0x4000 4000  0x4000 3FFF保留
0x4000 3C00  0x4000 3FFFSPI3/I2S3
0x4000 3800  0x4000 3BFFSPI2/I2S3
0x4000 3400  0x4000 37FF保留
0x4000 3000  0x4000 33FF独立看门狗(IWDG)
0x4000 2C00  0x4000 2FFF窗口看门狗(WWDG)
0x4000 2800  0x4000 2BFFRTC
0x4000 1800  0x4000 27FF保留
0x4000 1400  0x4000 17FFTIM7定时器
0x4000 1000  0x4000 13FFTIM6定时器
0x4000 0C00  0x4000 0FFFTIM5定时器
0x4000 0800  0x4000 0BFFTIM4定时器
0x4000 0400  0x4000 07FFTIM3定时器
0x4000 0000  0x4000 03FFTIM2定时器


每个GPIO引脚都有两个32位配置寄存器GPIOx_CRL和GPIOx_CRH,两个32位数据寄存器GPIOx_IDR和GPIOx_ODR,一个32位置位/复位寄存器GPIOx_BSRR,一个16位复位寄存器GPIOx_BRR和一个32位锁定寄存器GPIOx_LCKR。根据STM32芯片数据手册中列出的每个I/O引脚的特定功能,GPIO的每个引脚可以由软件配置成多种功能模式。借助GPIO的多种配置模式,STM32可以实现最简单、最直观的动作,例如,检测按键信号、LED的开关等。另外,STM32的GPIO还可用于串行和并行通信、存储器读/写等功能,可以有效避免某些嵌入式应用GPIO引脚不足或片内存储器存储空间不足等问题。
由于STM32的GPIO引脚数目过多,为便于使用往往将这些引脚进行分组(定义为端口),每个端口都有16个输入/输出引脚,分别由0~15个标号表示。端口通常以大写英文字母A表示开始,16个引脚为一组,以此类推。例如,GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOF和GPIOG等。又如,GPIOB端口共有16个引脚,分别标记为PB0~PB15,也可以表示为GPIOB0~GPIOB15。图31为48引脚LQFP封装的STM32F103C8Tx的引脚示意图。



图31LQFP封装的STM32F103C8Tx


图32给出了APB2时钟32位使能寄存器每位的功能,其中寄存器的2~8位用来配置GPIOx端口的使能位。与51单片机不同,STM32的GPIOx端口可以不同时工作,必须通过配置寄存器的控制位使能GPIOx端口,这有利于降低STM32的能耗。例如,仅GPIOA工作而其余端口不工作,则APB2时钟使能寄存器的控制位可以设定位为0x04。


图32APB2时钟使能寄存器


STM32的大多数引脚可以配置为多功能双向的输入/输出,可以连接到GPIO端口或复选功能(Alternative Functions,AF)。STM32的大多数引脚通过AF技术兼具其他专用功能。作为一个标准的命名约定,如果使用库函数进行程序开发,STM32的引脚已经在stm32f10x_gpio.h头文件中定义,例如,PA8表示端口A的第8位,PE1表示端口E的第1位。STM32的GPIO在工作时通常有三种不同的状态,分别是输入态、输出态和高阻态。输入态是指GPIO引脚被配置为输入模式,可以获取来自外围设备的高低电平(高电平为1,低电平为0)。输出态是指GPIO引脚配置为主动向外围设备输出高低电平,可以控制外围设备进行相应动作。高阻态是一种特殊的状态,是指GPIO引脚内部电阻的阻值无穷大,它的输出既不是高电平也不是低电平,对与其关联的输出信号不产生影响。由于这三种状态的作用有着很大的不同,需要根据实际开发的嵌入式应用的需求来配置相应的状态。
STM32的GPIO引脚的基本结构如图31所示。与51单片机不同,STM32的每个GPIO引脚可按功能需求灵活配置,这个配置过程是通过端口配置低位寄存器GPIOx_CRL、端口配置高位寄存器GPIOx_CRH、端口输入数据寄存器GPIOx_IDR、端口输出数据寄存器GPIOx_ODR、端口位清除寄存器GPIOx_BSRR、端口位清除寄存器GPIOx_BRR和端口配置锁定寄存器GPIOx_LCKR共7个寄存器来实现的。为了使用方便,STM32的标准库函数头文件stm32f10x_gpio.h通过结构体的形式定义了这7个寄存器,利用该结构体可以灵活配置GPIO引脚功能,具体代码如下: 

typedef struct

{

volatile uint32_t CRL;

volatile uint32_t CRH;

volatile uint32_t IDR;

volatile uint32_t ODR;

volatile uint32_t BSRR;

volatile uint32_t BRR;

volatile uint32_t LCKR;

}GPIO_TypeDef; 

需要说明的是,这些寄存器必须按32位字被访问。由于STM32的供电电压是3.3V,但很多外围设备的输入电压是5V,为兼容5V的外围设备输入,STM32的GPIO引脚大部分是兼容5V电压输入的,具体兼容5V的GPIO引脚可以从该芯片的数据手册引脚描述章节查到(I/O Level标有VDD_FT的就是5V电平兼容的)。按照不同的输入/输出要求,可以对GPIO引脚的寄存器进行配置,同时也可以利用stm32f10x_gpio.h的库常量配置GPIO引脚,具体如表32所示。


表32GPIO引脚配置



配 置 模 式功能库常量

通用输入浮空输入GPIO_Mode_IN_FLOATING
上拉输入GPIO_Mode_IPU
下拉输入GPIO_Mode_IPD
模拟输入GPIO_Mode_AIN
通用输出推挽式输出GPIO_Mode_Out_PP
开漏输出GPIO_Mode_Out_OD
复用功能输出推挽式复用功能GPIO_Mode_AF_PP
开漏复用功能GPIO_Mode_AF_OD

STM32的标准库函数头文件stm32f10x_gpio.h定义了上述8种引脚的配置模式,头文件中以枚举类型给出了各个模式在芯片中的地址,具体代码如下: 

typedef enum

{ 

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;

图33是GPIO引脚内部结构示意图。该图内部所示的输入驱动器中所有GPIO引脚都有一个内部弱上拉和弱下拉,当GPIO引脚配置为输入时,首先输出驱动器被禁止; 其次图中的TTL肖特基触发器输入被激活; 然后根据GPIO引脚的配置(浮空输入、上拉输入或下拉输入)的不同,弱上拉和下拉电阻将被连接; 出现在GPIO引脚上的数据在每个APB2时钟周期被采样到输入数据寄存器; 最后STM32内部处理器对输入数据寄存器执行读访问,便可得到GPIO引脚输入的高低电平信号。


图33GPIO引脚内部结构


GPIO引脚输入/输出模式具体如下: 
(1) 浮空输入(Input Floating): 该模式下GPIO引脚内部既不连上拉电阻又不连下拉电阻,而是直接经TTL肖特基触发器输入GPIO引脚的高低电平信号保存到输入数据寄存器。
(2) 上拉输入(Input Pullup): 该模式下GPIO引脚通过开关将电阻连接到电源VDD。当GPIO引脚有输入信号时,输入信号电平保存到输入数据寄存器。该端口在默认情况下输入为高电平。
(3) 下拉输入(Input Pulldown): 该模式下GPIO引脚通过开关将电阻连接到电源VSS。一旦芯片的GPIO引脚接收到来自外部的输入信号,芯片会将外部的输入信号电平保存在与引脚对应的输入数据寄存器中。该端口在默认情况下输入为低电平。
(4) 模拟输入(Analog Input): 该模式下TTL肖特基触发器输入关闭,既不接上拉电阻也不连接下拉电阻,引脚信号连接到芯片内部的片上外设,其典型应用是A/D模拟输入,对外部模拟信号进行采集。
默认情况下,大多数引脚被重置为浮空输入,这确保当系统通电时不会发生硬件冲突。
例如,将按钮一端连接到PA1引脚,利用库函数判断按钮是否被按下,具体如下: 

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init(GPIOA,&GPIO_InitStructure);

例如,利用GPIO库函数读取PA1引脚的高低电平信息,代码如下: 

GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1);

如图33所示的输出驱动器部分,当GPIO引脚配置为输出时,首先STM32内部处理器位设置/清除寄存器输出高低电平驱动信号给输出数据寄存器; 然后利用输出控制逻辑使能内部的PMOS或NMOS; 最后由GPIO引脚输出相应信号。
(1) 推挽式输出(Output PushPull): 该模式下GPIO引脚输出高电平时,则输出控制逻辑使能PMOS; 相反,则输出控制逻辑使能NMOS。由于使用了MOSFET管,增加了GPIO引脚的输出电流,进而有利于驱动负载大的外围设备。
(2) 开漏输出(Output OpenDrain): 该模式下GPIO引脚直接与MOSFET管的漏极相连,处于悬空状态。此时,如外围电路中无上拉电阻时,GPIO引脚输出低电平。只有在GPIO引脚的外围电路中加上拉电阻才能输出高电平。
(3) 推挽式复用功能(Alternate Function PushPull): 该模式下GPIO引脚可以作为多个外设引脚使用,但一个引脚某一时刻只能使用复用功能中的一个,由片上外设进行控制。
(4) 开漏复用功能(Alternate Function OpenDrain): 该模式下与推挽式复用功能相近,但想要输出高电平需要接上拉电阻。
当配置如上所示的输出时还应考虑驱动电路的响应速度,每个GPIO引脚有3种输出速度可供选择,即50MHz、10MHz和2MHz。一般来讲,实际开发中出于信号稳定性和降低功耗等原因,需要结合系统实际情况配置GPIO的响应速度,尽量使用与GPIO引脚要求一致的最低速度。一般常用的外设建议采用2MHz的输出速度,例如LED、蜂鸣器等; 而片上外设I2C、SPI等使用复用功能输出时,应配置高响应速度10MHz,甚至是50MHz。
在STM32的标准固件库中,stm32f10x_gpio.h头文件定义了3种输出速度模式,具体如下: 

typedef enum

{ 

GPIO_Speed_10MHz = 1,

GPIO_Speed_2MHz, 

GPIO_Speed_50MHz

}GPIOSpeed_TypeDef;

如果利用固件库配置GPIO引脚,则首先选定GPIO引脚,指定选择引脚的响应速度,并且需要说明引脚的功能,固件库中stm32f10x_gpio.h头文件给出了引脚的定义,具体如下: 

typedef struct

{

uint16_t GPIO_Pin;/*指定要配置的GPIO引脚*/

GPIOSpeed_TypeDef GPIO_Speed;/*指定所选引脚的速度*/

GPIOMode_TypeDef GPIO_Mode;/*指定所选引脚的模式*/

}GPIO_InitTypeDef;

例如,对于闪烁灯实验而言,利用库文件配置PB8为2MHz输出,具体如下: 

//stm32f10x_gpio.h

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_StructInit(&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;

GPIO_Init(GPIOB , &GPIO_InitStructure);

GPIO库函数提供了读写单个引脚和整个端口的操作,对于GPIO端口在捕获并行数据时特别有用。
例如,若下面利用GPIO库函数GPIO_WriteBit对PB8引脚输出高低电平进行设置,若通过PB8引脚的高低电平变化可以控制闪烁灯开关。如果控制PB8为高电平,调用库函数GPIO_WriteBit(GPIOB,GPIO_Pin_8,Bit_SET)来设置GPIOB端口的第8个引脚为高电平; 相反,调用库函数GPIO_WriteBit(GPIOB,GPIO_Pin_8,Bit_RESET)来设置端口的第8个引脚为低电平。
STM32中USART之类的外围设备需要与GPIO复用的引脚,即共用同一个引脚。在使用这些外设之前,外设需要的任何输出必须配置为复用输出功能。
例如,USART1的Tx与PA9有相同的引脚,如果使能Tx,则需要配置PA9的输出模式为推挽式复用功能,具体如下: 

GPIO_InitStruct.GPIO_PIN = GPIO_Pin_9; 

GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; 

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; 

GPIO_Init(GPIOA , &GPIO_InitStruct); 

3.3STM32的GPIO库函数
3.3.1GPIO模块的标准库函数

ST公司为了便于嵌入式应用系统的实现,向使用者提供了GPIO模块的标准外设库接口函数。嵌入式开发者可以使用ST公司定义的函数对GPIO引脚进行配置和使用,从而规避了直接读写GPIO寄存器而引发错误的可能性。在实际项目开发中,往往需要用到标准外设库函数,这就要求创建工程时,把标准库的头文件stm32f10x_gpio.h加入工程。在项目开发过程中,想了解该头文件的功能时可以查看GPIO库函数的源码,该头文件所对应的源文件是stm32f10x_gpio.c。文件stm32f10x_gpio.h声明了GPIO共18种库函数的定义,具体函数头定义如下: 

void GPIO_DeInit(GPIO_TypeDef* GPIOx);

/**

* @brief该函数表示将GPIOx外围寄存器反初始化为它们的默认重置值。

* @param GPIOx: x可以是A~G来选择GPIO外围设备。

* @retval 无

*/

void GPIO_AFIODeInit(void);

/**

* @brief该函数是将AF(重新映射、事件控制和EXTI配置)寄存器反初始化为它们的默认重置值。

* @retval 无

*/

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

/**

* @brief该函数是根据GPIO_InitStruct中的指定参数初始化GPIOx外围设备。

* @param GPIOx:指定GPIO的具体端口,x可以是A~G其中一个端口。

* @param GPIO_InitStruct是指向GPIO_InitTypeDef结构的指针,包含指定的GPIO外围设备的配
* 置信息。

* @retval 无

*/

void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);

/**

* @brief该函数是用它的默认值填充每个GPIO_InitStruct成员。

* @param GPIO_InitStruct: 指向将要初始化的GPIO_InitTypeDef结构的指针。

* @retval 无

*/

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

/**

* @brief该函数是读取指定的输入端口引脚。

* @param GPIOx: x可以是A~G来选择GPIO外围设备。

* @param GPIO_Pin: 指定要读取的端口位。这个参数可以是GPIO_Pin_x,其中x可以是0~15。

* @retval输入端口引脚值。

*/

uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);

/**

* @brief该函数是读取指定的GPIO输入数据端口。

* @param GPIOx: x可以是A~G来选择GPIO外围设备。

* @retval GPIO输入数据端口值。

*/

uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

/**

* @brief该函数是读取指定的输出端口引脚。

* @param GPIOx: x可以是A~G来选择GPIO外围设备。

* @param GPIO_Pin: 指定要读取的端口位。这个参数可以是GPIO_Pin_x,其中x可以是0~15。

* @retval输出端口引脚值。

*/

uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);

/**

* @brief该函数是读取指定的GPIO输出数据引脚。

* @param GPIOx: x可以是A~G来选择GPIO外围设备。

* @retval GPIO输出数据端口值。

*/

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

/**

* @brief该函数是设置选定的数据端口引脚。

* @param GPIOx: x可以是A~G来选择GPIO外围设备。

* @param GPIO_Pin: 指定要写入的端口位。这个参数可以是GPIO_Pin_x的任意组合,其中x可以
* 是0~15。

* @retval 无

*/

void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

/**

* @brief该函数是设置选定的数据端口位。

* @param GPIOx: x可以是A~G来选择GPIO外围设备。

* @param GPIO_Pin: 指定要写入的端口位。这个参数可以是GPIO_Pin_x的任意组合,其中x可以
* 是0~15。

* @retval 无

*/

void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);

/**

* @brief该函数是设置或清除选定的数据端口位。

* @param GPIOx: x可以是A~G来选择GPIO外围设备。

* @param GPIO_Pin: 指定要写入的端口位。这个参数可以是GPIO_Pin_x,其中x可以是0~15。

* @param BitVal: 指定要写入所选位的值。该参数可以是BitAction枚举值中的一个:

* @arg Bit_RESET: 清除端口引脚。

* @arg Bit_SET: 设置端口引脚。

* @retval 无

*/

void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

/**

* @brief该函数是将数据写入指定的GPIO数据端口。

* @param GPIOx: x可以是A~G来选择GPIO外围设备。

* @param PortVal: 指定要写入端口输出数据寄存器的值。

* @retval 无

*/

void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

/**

* @brief该函数是GPIO引脚锁定配置寄存器。

* @param GPIOx: x可以是A~G来选择GPIO外围设备。

* @param GPIO_Pin: 指定要写入的端口位。这个参数可以是GPIO_Pin_x的任意组合,其中x可以
* 是0~15。

* @retval 无

*/

void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

/**

* @brief该函数是选择用作事件输出的GPIO引脚。

* @param GPIO_PortSource: 选择作为事件输出源的GPIO端口。这个参数可以是GPIO_* PortSourceGPIOx,其中x可以是A~G。

* @param GPIO_PinSource: 指定事件输出的引脚。这个参数可以是GPIO_PinSourcex,其中x可以* 是0~15。

* @retval 无

*/

void GPIO_EventOutputCmd(FunctionalState NewState);

/**

* @brief该函数是启用或禁用事件输出。

* @param NewState: 事件输出的新状态。此参数可以是启用或禁用。

* @retval 无

*/

void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);

/**

* @brief该函数是更改指定引脚的映射。

* @param GPIO_Remap: 选择要重新映射的引脚。

* @paramNewState: 重新映射端口引脚的新状态。这个参数可以是启用或禁用。

* @retval 无

*/

void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

/**

* @brief该函数是选择作为EXTI线的GPIO引脚。

* @param GPIO_PortSource: 选择作为EXTI线源的GPIO端口。这个参数可以是GPIO_* PortSourceGPIOx,其中x可以是A~G。

* @param GPIO_PinSource: 指定要配置的EXTI行。这个参数可以是GPIO_PinSourcex,其中x可以* 是0~15。

* @retval 无

*/

void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);

/**

* @brief该函数是选择以太网媒体接口。

* @note这个函数只适用于STM32连接线路设备。

* @param GPIO_ETH_MediaInterface指定媒体接口模式。该参数可以是以下值之一:

* @arg GPIO_ETH_MediaInterface_MII: MII模式

* @arg GPIO_ETH_MediaInterface_RMII: RMII模式

* @retval 无

*/


3.3.2GPIO配置步骤
不同于51单片机,在使用GPIO之前,必须对它们进行配置。一般来讲,使用任何GPIO引脚所需的配置如图34所示。


图34GPIO配置步骤


基本初始化步骤如下: 
(1) 启用相应外围设备的时钟; 
(2) 配置外设功能参数,调用初始化函数,初始化外设相关的参数; 
(3) 使能相应的GPIO; 
(4) 编写应用逻辑。
基于STM32的GPIO初始化程序的框架结构,如以下代码所示: 

#include <stm32f10x.h>

#include <stm32f10x_rcc.h>

#include <stm32f10x_gpio.h>

int main(void) {

GPIO_InitTypeDef GPIO_InitStructure

//Enable Peripheral Clocks (1) ...

//Configure Pins (2) ...

//Enable the corresponding peripheral.... (3) ...

while (1) {

//Coding application logic... (4) ...

}

}

3.4STM32 GPIO应用实例
3.4.1实例标准库函数开发

本节采用基于标准固件库的设计方式,利用单个GPIO引脚输出高低电平控制发光二极管,并按一定时间间隔改变I/O口电平,达到GPIO的库函数实现灯光闪烁效果。图35是发光二极管D1与STM32F103C6的PB0接口电路的原理图。


图35发光二极管与STM32F103C6的接口电路


图35的电阻R1是限流电阻。R1阻值的改变会导致发光二极管D1的亮度也发生改变,R1的阻值范围一般选用400Ω~1kΩ的。程序流程设计中首先需要配置GPIO,然后主循环中不断检测发光二极管D1的状态,当检测到发光二极管D1开时,则GPIO的PB0输出为高电平; 相反,则输出低电平。图36为发光二极管闪烁程序流程图。


图36发光二极管闪烁程序流程


发光二极管工程文件中的源文件存放了GPIO的管脚定义、GPIO初始化、全局变量声明、函数声明以及LED状态切换等功能,构成了发光二极管闪烁的主程序。具体代码如下: 

#include "stm32f10x.h"

/*函数名:Delay

*功能描述:不精确的延时,延时时间= nCount/72000000,72MHz为STM32主频

*输入参数:nCount

*输出参数:无

*/

void Delay( u32 nCount)

{

for(;nCount !=0;nCount--);

}



int main(void)

{

GPIO_InitTypeDef GPIO_InitStructure;//定义一个GPIO_InitTypeDef类型
//的结构体变量

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB的时钟

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//选择要使用的I/O引脚,此处选
//择PB0引脚

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//设置引脚输出模式为推挽输出

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//设置引脚的输出频率为50MHz

GPIO_Init(GPIOB,&GPIO_InitStructure);//调用初始化库函数初始化GPIOB
//端口



while(1) {

if(ReadOutputDataBit(GPIOB,GPIO_Pin_0)==0){//读取PB0状态

GPIO_SetBits(GPIOB,GPIO_Pin_0);//调用GPIO_SetBits函数,将PB0
//置为高电平,点亮D1

}else{

 GPIO_ResetBits(GPIOB,GPIO_Pin_0); //调用GPIO_ReSetBits函数,将
//PB0置为低电平,熄灭D1

}

Delay(72000);//调用延迟函数,延迟100ms

}

}

3.4.2基于STM32CubeMX的实例开发
通过3.4.1节的实验发现,基于标准库函数的STM32工程需要写很多初始化代码,这些初始化代码往往都是固定的,开发人员没必要每次都重写这些初始化程序。为解决这个问题,ST公司推出了STM32CubeMX工具自动生成相应配置的初始化代码。下面以STM32CubeMX为例进行发光二极管闪烁程序的开发。


图37STM32CubeMX快捷图标

1. 构建STM32CubeMX工程
第2章已经介绍了STM32CubeMX工具的安装,找到如图37所示快捷图标,双击该图标启动STM32CubeMX开发工具。
STM32CubeMX开发工具启动后的首界面如图38所示。在STM32CubeMX的菜单中选中File选项卡,并单击“New Project...”,即可创建一个新的项目。


图38STM32CubeMX开发工具首界面


与前面利用标准库函数创建工程不同,STM32CubeMX的新工程都需要预先指定使用的芯片型号。图39是弹出的新创建项目界面,在Part Number搜索框中输入芯片型号。例如,STM32F103C后按Enter键,检索出与输入关键词相近和封装不同的芯片信息,这里选择封装为LQFP48的STM32F103C6芯片。


图39创建项目界面


双击LQFP48封装的STM32F103C6芯片,弹出如图310所示界面,至此工程创建完成。


图310创建工程结束界面


1) 配置STM32CubeMX工程
在创建了基于STM32F103C6Tx的工程后,还要对该芯片的引脚参数进行配置。首先配置芯片STM32F103C6Tx的SYS,选择左边目录System Core中的SYS选项卡,会弹出SYS模式和配置(SYS Mode and Configuration)界面,如图311所示。


图311STM32F103C6Tx芯片的SYS配置


Debug的下拉列表中包括No Debug、Serial Wire、JTAG(4 pins)、JTAG(5 pins)和Trace Asynchronous Sw五种模式。其中No Debug主要用于利用仿真软件调试程序。Serial Wire是指利用PA13和PA14引脚进行串口调试。JTAG(4 pins)由TDO4、TCK、TDI、TMS线组成,分别为数据输出线、时钟、数据输入和模式选择。JTAG(5 pins)是在4线JTAG基础上加入了VREF。Trace Asynchronous Sw是指轨迹异步SW调试。由于STM32CubeMX默认是关闭调试接口的,为确保工程的完整性,可以选择Trace Asynchronous Sw把调试器选进来; 另外,选进调试器也不会占用额外的程序代码。本节实验将利用仿真软件实现程序的调试,Debug选项可以选为No Debug或Serial Wire。
2) 配置系统时钟
图312是配置STM32F103C6Tx的时钟。通过选择图312最左侧的RCC,可以设置芯片的时钟源。首先从STM32CubeMX的引脚配置页面上找到RCC选项卡,单击RCC选项卡后右侧出现RCC模式和配置面板(RCC Mode and Configuration)。通过该面板配置高速时钟(HSE)和低速时钟(LSE)。考虑到在发光二极管闪烁程序中需要用到高速时钟,所以选定高速时钟后面的下拉列表框进行设置。一般而言,高速时钟和低速时钟的下拉选项卡包括禁止(Disable)、旁路时钟源(Bypass Clock Source)以及外部晶体/陶瓷谐振器(Crystal/Ceramic Resonator)三种。其中,Disable选项表示当前工程不启用时钟源; Bypass Clock Source是外部时钟,外部提供时钟只需要接入OSC_IN引脚,而OSC_OUT引脚悬空; 而Crystal/Ceramic Resonator相当于石英/陶瓷晶振,需要通过外部无源晶体与芯片内部时钟的驱动电路共同配合形成时钟源,且OSC_IN与OSC_OUT引脚都要连接,将会增加启动时间,但时钟源的精度往往较高。


图312系统时钟选择


HSE为本实验中采用的系统时钟源,选项选为Crystal/Ceramic Resonator,此时STM32F103C6Tx的PD0和PD1引脚会被占用。
MCO是指使能MCO引脚时钟输出。由于本次实验选择的是HSE,所以只对HSE时钟配置,具体操作步骤如下: 
(1) 将HSE外部时钟源输入频率(Input Frequency)设置默认值8,其取值范围为4~16MHz; 
(2) 选择PLL Source Mux的通道,选择PLL; 
(3) 双击HCLK频率,然后系统会自动配置成用于期望的时钟。配置前的时钟树如图313所示。


图313系统时钟配置


配置完成的时钟树如图314所示。


图314配置后系统时钟结构



3) 配置GPIO口功能
在完成STM32F103C6Tx系统时钟的配置后,需要继续配置其GPIO口功能。切换回Pinout&Configuration选项卡,打开如图315所示的界面。


图315GPIO功能配置


开始配置STM32F103C6Tx芯片GPIO口的功能,本实验的目标是实现PB0连接发光二极管的闪烁,所以需要配置PB0引脚的功能。该引脚功能如下: 
Reset_State设置为低电平状态功能; 
ADC1_IN8是模/数转换器1通道8的数据采集功能; 
ADC2_IN8是模/数转换器2通道8的数据采集功能; 
TIM1_CH2N是高级定时器1的通道2功能; 
TIM3_CH3是通用定时器3的通道3功能; 
GPIO_Input是通用输入引脚功能; 
GPIO_Output是通用输出引脚功能; 
GPIO_Analog是通用模拟信号输出功能; 
EVENTOUT是事件输出功能; 
GPIO_EXTI0是中断EXTI0功能。
由图35可知,需要将PB0引脚配置为输出功能。单击图315中STM32F103C6Tx示意图的PB0引脚,在弹出的选项选框中,找到GPIO_Output选项条并选择,如图316所示。


图316GPIO功能配置


配置好PB0为输出后,右击PB0引脚可以为GPIO的标识设置,这个标识可以在程序中直接使用,而不需要通过PB0的地址进行访问,如图317所示。


图317GPIO标识设置


下面选择Enter User Label选项,本次设置为LEDRED,如图318所示。


图318GPIO标识分配


以上PB0引脚功能配置结束后,接下来在最左侧的System Core目录下单击GPIO选项卡。在GPIO模式和配置(GPIO mode and configuration)页面中列出了上面配置PB0引脚的信息,如图319所示。


图319GPIO的PB0配置信息


单击列表中的PB0查看引脚详细参数,如图320所示。GPIO output level 选项可以将引脚电平设置成高电平或低电平。GPIO mode 为 GPIO模式,具体见表32。GPIO Pullup/Pulldown为上拉电阻、下拉电阻、无上拉或下拉。Maximum output speed为引脚速度设置,包括低速、中速、高速。User Label 为用户标签,可以给引脚设置名称,如LEDRED。根据本实验的要求将PB0配置为推挽输出模式、上拉、高速输出模式,并且引脚标识为LEDRED。


图320GPIO详细配置界面


至此,STM32F103C6Tx芯片的基本参数已经配置完成了。可以看出和我们使用库函数时的配置过程是一样的,但不同的是仅需要单击鼠标便可以完成上述操作,这正是STM32CubeMX的强大之处。
4) 输出配置后的工程
根据发光二极管闪烁实验的功能需求,需要对STM32F103C6Tx芯片进行一定的配置,为了能在仿真器或芯片中执行配置后的工程,需要使用Project Manager选项卡配置导出当前工程生成的源程序,进入图321所示的左侧Project选项卡的Project Settings界面进行输出配置。


图321配置好的输出配置


其中,Project Name为当前工程文件名,该文件名根据实验的实际功能进行自定义,在本实验中将STM32_GPIO_LED; Project Location作为当前工程存放的路径,也可以根据硬盘的情况选择合适的路径; 而Toolchain/IDE是非常关键的,它决定了用STM32CubeMX生成代码所能编译源代码的集成开发环境,即确定集成开发环境的类型。这需要根据项目的开发环境进行选择,本书用到的集成开发环境是Keil,因此选择MDKARM。Min Version是对应集成开发环境的版本号,用于本书选用的是Keil5,所以选择V5的版本; 除此之外的参数,保持系统默认即可。如有特殊的要求,需要结合实际项目开发的需要进行设定。
单击左侧的Code Generator选项卡可以进入如图322所示的配置界面,其选择内容具体包括: 为每一个外设的芯片能够生成独立的初始化头文件和源文件,选择只复制所需文件到工程,这样就会有.h和.c两种形式的输出文件; 当代码重新生成时,文件会保留用户原有代码,而当没有代码重新生成时,就会自动删除之前系统生成的文件。


图322配置Code Generator


5) 生成代码
以上选项信息设置好后,单击Generate Code按钮便可生成Keil源代码,如图323所示。


图323生成用户源代码


代码生成后弹出是否打开工程对话框,如图324所示。
利用Stm32CubeMX生成源代码后,通过单击弹出的对话框中的“Open Project”按钮,便可以启动Keil 5,同时用Keil5打开当前生成源代码的工程文件,启动后的效果如图325所示。



图324代码生成后弹出对话框




图325启动Keil开发环境


2. Keil软件
Keil软件启动后,进入图326所示的页面。STM32CubeMX生成了左侧工程目录树中的源程序,接着选择左侧工程目录树中Application/User/Core文件下的main.c文件,然后在Keil软件的右侧显示窗打开源代码,如图326所示。


图326Keil首页面


根据已经生成的源文件可以看到,发光二极管闪烁实验大部分的源代码都已经生成,其中包括启用相应外围设备的时钟、配置外设功能参数、调用初始化函数、初始化外设相关的参数以及使能相应的外设等,但应用业务逻辑的源代码,用户需要根据具体的应用来设计和实现此部分代码,如图327所示。


图327发光二极管闪烁程序的应用逻辑


使用Keil软件将编写完成的发光二极管闪烁程序进行编译,继而生成.hex文件。
3. Proteus仿真


图328Proteus 8 Professional

快捷图标

为了确定上述程序编写的准确性,可以运用Proteus工具构建STM32F103C6Tx的发光二极管闪烁的仿真环境,利用Proteus模拟STM32F103C6Tx在物理环境下的运行状态,然后通过观察STM32F103C6Tx引脚的变化来证明上述实验的有效性。
1) 创建Proteus工程
双击桌面上Proteus 8 Professional的快捷图标,启动软件,如图328所示。
软件启动后,进入图329所示的首界面。


图329Proteus 8 Professional的首界面


单击Proteus 8的File选项卡,选择New Project选项,打开如图330所示的新建工程界面。


图330新建工程界面


设置好工程名和工程存放的物理位置后,单击“Next”按钮,进入如图331所示的原理图大小设置界面,根据设计需要选择图纸大小。


图331原理图大小设置界面


原理图的尺寸可根据元器件的多少进行选择,本实验选择DEFAULT模板,选择完毕后单击“Next”按钮进入如图332所示的PCB配置界面。


图332PCB配置界面


如果需要创建PCB,则可以选择创建PCB选项,而且需要选择合适的模板。本实验不需要PCB,所以选择不创建PCB布局选项,即无须设计PCB。然后选择“Next”按钮即可,进入如图333所示的固件库设置界面。


图333固件信息配置


固件库是指确保程序在控制器上正确运行所需要的中间件,需要结合项目的实际情况进行配置。本书使用STM32CubeMX和Keil联合对STM32进行仿真验证,所以无须用Proteus进行开发,故直接单击“Next”按钮进入图334所示的配置概要界面。


图334配置概要


单击“Finish”按钮完成仿真环境的工程创建。接下来,利用当前工程对实验进行仿真。
2) 检索器件
单击Library选项卡,并单击从库选择部件(Pick parts from libraries),如图335所示,添加本实验所需的元器件。


图335器件选择


进入选择元器件界面后,在Keywords界面输入要检索的器件并回车后,可以查看具体的器件结果。例如,输入关键词STM32并按回车后,右边列表会列出与STM32相关的MCU,如图336所示。


图336MCU选择


本次选择STM32F103C6作为仿真实验的MCU,如图337所示。


图337STM32F103C6元件


当单击STM32F103C6元件,则该器件便添加到原理图中,如图338所示。


图338添加STM32F103C6元件


用同样的方式,选择本实验需要的发光二极管,如图339所示。


图339添加发光二极管



最后,添加限流电阻R1和接地,并将这些元件连接到一起。注意,D1需要接到STM32F103C6元件的PB0引脚。发光二极管闪烁实验原理图如图340所示。


图340发光二极管闪烁实验原理图


接下来配置STM32F103C6,如图341所示。双击弹窗上的STM32F103C6选项,在STM32F103C6芯片中加载之前用Keil编译好的发光二极管闪烁程序并设置好STM32F103C6芯片的晶振频率,其余设置均保持默认值,最后单击OK按钮。


图341STM32F103C6芯片配置


3) 仿真
基于发光二极管闪烁实验的原理图搭建的电路运行仿真,如图342所示。单击运行仿真或者快捷键F12进行仿真实验。


图342运行仿真


运行仿真后可以观察STM32F103C6引脚PB0和LEDRED的变化。根据发光二极管闪烁程序,每隔50ms,LEDRED状态变化一次。LEDRED关状态的仿真结果如图343所示,这时PB0引脚输出低电平。


图343LEDRED关闭


图344是LEDRED开状态的仿真结果。这时PB0引脚由程序拉高,LEDRED变红,说明发光二极管被打开。


图344LEDRED打开


本章小结
本章主要介绍了输入/输出的概念、STM32的GPIO、标准库函数、GPIO配置的步骤等,最后通过一个发光二极管闪烁程序进一步说明了如何使用GPIO。通过本章的学习,要求学生掌握输入/输出的有关概念、掌握STM32的GPIO开发流程、熟练使用STM32CubeMX工具生成GPIO项目、熟练应用Keil软件编译环境和仿真环境、了解Proteus仿真环境,对基于STM32的嵌入式系统开发建立初步的认识,为后面的学习打下基础。
习题3
1. 填空题
(1) 微控制器芯片将输入/输出分为、、和。
(2) 用来配置GPIOx端口的使能位。
2. 选择题
(1) ()指输出较大的电流来驱动外围电路设备,该模式既可以输出高电平又可以输出低电平。

A. 准双向I/OB. 推挽输出 
C. 高阻态D. 开漏

(2) ()是指STM32微控制器片内外设中可配置的输入/输出接口。
A. GPIOB. EXTIC. DMAD. NVIC
(3) STM32的GPIO的每个端口都有()个输入/输出引脚。
A. 8B. 16C. 32D. 0
(4) 关于STM32的GPIO引脚的寄存器描述不正确的是()。
A. 每个GPIO引脚都有两个32位配置寄存器
B. 每个GPIO引脚都有两个32位数据寄存器
C. 每个GPIO引脚都有一个32位锁定寄存器
D. 每个GPIO引脚都有一个32位复位寄存器
(5) 每个GPIO引脚有三种输出速度,不包括()。
A. 50MHzB. 10MHz  C. 2MHzD. 100MHz
3. 简答题
(1) 简述什么是输入/输出,及各种工作模式的含义。
(2) 简述GPIO引脚包含几种输入和输出模式,及每种模式的含义。
(3) 简述GPIO配置步骤。