第5章

STM32 GPIO





本章讲述STM32 GPIO,包括STM32 GPIO接口概述、GPIO功能、GPIO的HAL驱动程序、GPIO使用流程、采用STM32CubeMX和HAL库的GPIO输出应用实例、采用STM32CubeMX和HAL库的GPIO输入应用实例。

5.1STM32 GPIO接口概述

通用输入输出(GPIO)接口的功能是让嵌入式处理器能够通过软件灵活地读出或控制单个物理引脚上的高、低电平,实现内核和外部系统之间的信息交换。GPIO是嵌入式处理器使用最多的外设,能够充分利用其通用性和灵活性,是嵌入式开发者必须掌握的内容。作为输入时,GPIO可以接收来自外部的开关量信号、脉冲信号等,如来自键盘、拨码开关的信号; 作为输出时,GPIO可以将内部的数据传输给外部设备或模块,如输出到LED、数码管、控制继电器等。另外,从理论上讲,当嵌入式处理器上没有足够的外设时,可以通过软件控制GPIO模拟UART、SPI、I2C、FSMC等各种外设的功能。

正因为GPIO作为外设具有无与伦比的重要性,STM32上除特殊功能的引脚外,所有引脚都可以作为GPIO使用。以常见的LQFP144封装的STM32F103ZET6为例,有112个引脚可以作为双向I/O使用。为便于使用和记忆,STM32将它们分配到不同的“组”中,在每个组中再对其进行编号。具体来讲,每个组称为一个端口,端口号通常以大写字母命名,从A开始,依次简写为PA、PB或PC等。每个端口中最多有16个GPIO,软件既可以读写单个GPIO,也可以通过指令一次读写端口中全部16个GPIO。每个端口内部的16个GPIO又被分别标以0~15的编号,从而可以通过PA0、PB5或PC10等方式指代单个的GPIO。以STM32F103ZET6为例,它共有7个端口(PA、PB、PC、PD、PE、PF和PG),每个端口有16个GPIO,共有7×16=112个GPIO。

几乎在所有嵌入式系统应用中,都涉及开关量的输入和输出功能,如状态指示、报警输出、继电器闭合和断开、按钮状态读入、开关量报警信息的输入等。这些开关量的输入和控制输出都可以通过GPIO实现。

GPIO的每个位都可以由软件分别配置成以下模式。

(1) 输入浮空: 浮空(Floating)就是逻辑器件的输入引脚既不接高电平,也不接低电平。由于逻辑器件的内部结构,当引脚输入浮空时,相当于该引脚接了高电平。一般实际运用时,引脚不建议浮空,易受干扰。

(2) 输入上拉: 上拉就是把电压拉高,如拉到VCC。上拉就是将不确定的信号通过一个电阻钳位在高电平。电阻同时起限流作用。弱强只是上拉电阻的阻值不同,没有什么严格区分。

(3) 输入下拉: 下拉就是把电压拉低,拉到GND。与上拉原理相似。

(4) 模拟输入: 模拟输入是指传统方式的模拟量输入。数字输入是输入数字信号,即0和1的二进制数字信号。

(5) 开漏输出: 输出端相当于三极管的集电极。要得到高电平状态,需要上拉电阻才行。该模式适用于电流型的驱动,其吸收电流的能力相对较强(一般20mA以内)。

(6) 推挽式输出: 可以输出高低电平,连接数字器件。推挽结构一般是指两个三极管分别受两个互补信号的控制,总是在一个三极管导通时另一个截止。

(7) 推挽式复用输出: 复用功能可以理解为GPIO被用作第二功能时的配置情况(并非作为通用I/O口使用)。STM32 GPIO的推挽复用模式中输出使能、输出速度可配置。这种复用模式可工作在开漏及推挽模式,但是输出信号是源于其他外设的,这时的输出数据寄存器GPIOx_ODR是无效的; 而且输入可用,通过输入数据寄存器可获取I/O实际状态,但一般直接用外设的寄存器获取该数据信号。

(8) 开漏复用输出: 复用功能可以理解为GPIO被用作第二功能时的配置情况(并非作为通用I/O口使用)。每个I/O端口可以自由编程,而I/O端口寄存器必须按32位字访问(不允许半字或字节访问)。GPIOx_BSRR和GPIOx_BRR寄存器允许对任何GPIO寄存器的读/更改的独立访问,这样,在读和更改访问之间产生中断时不会发生危险。

一个I/O端口的基本结构如图51所示。



图51一个I/O端口的基本结构



STM32的GPIO资源非常丰富,包括26、37、51、80、112个多功能双向5V兼容的快速I/O端口,而且所有I/O端口可以映射到16个外部中断,对于STM32的学习,应该从最基本的GPIO开始学习。

每个GPIO端口具有7组寄存器具体如下。

(1) 两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH)。

(2) 两个32位数据寄存器(GPIOx_IDR,GPIOx_ODR)。

(3) 一个32位置位/复位寄存器(GPIOx_BSRR)。

(4) 一个16位复位寄存器(GPIOx_BRR)。

(5) 一个32位锁定寄存器(GPIC_LCKR)。

GPIO端口的每个位可以由软件分别配置成多种模式。常用的I/O端口寄存器只有4个: CRL、CRH、IDR、ODR。CRL和CRH控制着每个I/O端口的模式及输出速率。

每个GPIO引脚都可以由软件配置成输出(推挽或开漏)、输入(带或不带上拉或下拉)或复用的外设功能。多数GPIO引脚都与数字或模拟的复用外设共用。除了具有模拟输入功能的端口,所有GPIO的引脚都有大电流通过能力。

根据数据手册中列出的每个I/O端口的特定硬件特征,GPIO接口的每个位可以由软件分别配置成多种模式: 输入浮空、输入上拉、输入下拉、模拟输入、开漏输出、推挽式输出、推挽式复用功能、开漏复用功能。

I/O端口的基本结构包括以下几部分。

5.1.1输入通道

输入通道包括输入数据寄存器和输入驱动器。在接近I/O引脚处连接了两只保护二极管,假设保护二极管的导通电压降为Vd,则输入到输入驱动器的信号Vin电压范围被钳位在

VSS-Vd<Vin<VDD+Vd


由于Vd的导通压降不会超过0.7V,若电源电压VDD为3.3V,则输入输入驱动器的信号不会低于-0.7V,不会高于4V,起到了保护作用。在实际工程设计中,一般都将输入信号尽可能调理到0~3.3V,也就是说,一般情况下,两只保护二极管都不会导通,输入驱动器中包括了两个电阻,分别通过开关接电源VDD(该电阻称为上拉电阻)和地VSS(该电阻称为下拉电阻)。开关受软件的控制,用来设置当I/O用作输入时,选择使用上拉电阻或下拉电阻。

输入驱动器中的另一个部件是TTL施密特触发器,当I/O用于开关量输入或复用功能输入时,TTL施密特触发器用于对输入波形进行整形。

GPIO的输入驱动器主要由TTL肖特基触发器、带开关的上拉电阻电路和带开关的下拉电阻电路组成。值得注意的是,与输出驱动器不同,GPIO的输入驱动器没有多路选择开关,输入信号送到GPIO输入数据寄存器的同时也送给片上外设,所以GPIO的输入没有复用功能选项。

根据TTL 肖特基触发器、上拉电阻端和下拉电阻端两个开关的状态,GPIO的输入可分为以下4种。

(1) 模拟输入: TTL肖特基触发器关闭。

(2) 上拉输入: GPIO内置上拉电阻,此时GPIO内部上拉电阻端的开关闭合,GPIO内部下拉电阻端的开关打开。该模式下,引脚在默认情况下输入为高电平。

(3) 下拉输入: GPIO内置下拉电阻,此时GPIO内部下拉电阻端的开关闭合,GPIO内部上拉电阻端的开关打开。该模式下,引脚在默认情况下输入为低电平。

(4) 浮空输入: GPIO内部既无上拉电阻,也无下拉电阻,此时GPIO内部上拉电阻端和下拉电阻端的开关都处于打开状态。该模式下,引脚在默认情况下为高阻态(即浮空),其电平高低完全由外部电路决定。

5.1.2输出通道

输出通道包括置位/清除寄存器、输出数据寄存器、输出驱动器。

要输出的开关量数据首先写入置位/清除寄存器,通过读写命令进入输出数据寄存器,然后进入输出驱动器的输出控制模块。输出控制模块可以接收开关量的输出和复用功能输出。输出的信号通过由PMOS和NMOS场效应管电路输出到引脚。通过软件设置,由PMOS和NMOS场效应管电路可以构成推挽方式、开漏方式或关闭。

GPIO的输出驱动器主要由多路选择器、输出控制逻辑和一对互补的MOS晶体管组成。

1) 多路选择器

多路选择器根据用户设置决定该引脚是GPIO普通输出还是复用功能输出。

(1) 普通输出: 该引脚的输出来自GPIO的输出数据寄存器。

(2) 复用功能(Alternate Function,AF)输出: 该引脚的输出来自片上外设,并且一个STM32微控制器引脚输出可能来自多个不同外设,即一个引脚可以对应多个复用功能输出。但同一时刻,一个引脚只能使用一个复用功能,而这个引脚对应的其他复用功能都处于禁止状态。

2) 输出控制逻辑和一对互补的MOS晶体管

输出控制逻辑根据用户设置通过控制PMOS和NMOS场效应管的状态(导通/关闭)决定GPIO输出模式(推挽、开漏或关闭)。

(1) 推挽(PushPull,PP)输出: 可以输出高电平和低电平。当内部输出1时,PMOS管导通,NMOS管截止,外部输出高电平(输出电压为VDD,对于STM32F103微控制器的GPIO来说,通常是3.3V); 当内部输出0时,NMOS管导通,PMOS管截止,外部输出低电平(输出电压为0V)。

由此可见,相比于普通输出方式,推挽输出既提高了负载能力,又提高了开关速度,适用于输出0V和VDD的场合。

(2) 开漏(OpenDrain,OD)输出: 与推挽输出相比,开漏输出中连接VDD的PMOS管始终处于截止状态。这种情况与三极管的集电极开路非常类似。在开漏输出模式下,当内部输出0时,NMOS管导通,外部输出低电平(输出电压为0V); 当内部输出1时,NMOS管截止,由于此时PMOS管也处于截止状态,外部输出既不是高电平,也是不是低电平,而是高阻态(浮空)。如果想要外部输出高电平,必须在I/O引脚外接一个上拉电阻。

这样,通过开漏输出,可以提供灵活的电平输出方式——改变外接上拉电源的电压,便可以改变传输电平电压的高低。例如,如果STM32微控制器想要输出5V高电平,只需要在外部接一个上拉电阻且上拉电源为5V,并把STM32微控制器对应的I/O引脚设置为开漏输出模式,当内部输出1时,由上拉电阻和上拉电源向外输出5V电平。需要注意的是,上拉电阻的阻值决定逻辑电平电压转换的速度。阻值越大,速度越低,功耗越小,所以上拉电阻的选择应兼顾功耗和速度。

由此可见,开漏输出可以匹配电平,一般适用于电平不匹配的场合,而且开漏输出吸收电流的能力相对较强,适合作为电流型的驱动。


5.2STM32的GPIO功能
5.2.1普通I/O功能


复位期间和刚复位后,复用功能未开启,I/O端口被配置成浮空输入模式。

复位后,JTAG引脚被置于输入上拉或下拉模式。

(1) PA13: JTMS置于上拉模式。

(2) PA14: JTCK置于下拉模式。

(3) PA15: JTDI置于上拉模式。

(4) PB4: JNTRST置于上拉模式。

当作为输出配置时,写到输出数据寄存器(GPIOx_ODR)的值输出到相应的I/O引脚。可以以推挽模式或开漏模式(当输出0时,只有NMOS管被打开)使用输出驱动器。

输入数据寄存器(GPIOx_IDR)在每个APB2时钟周期捕捉I/O引脚上的数据。

所有GPIO引脚有一个内部弱上拉和弱下拉,当配置为输入时,它们可以被激活,也可以被断开。

5.2.2单独的位设置或位清除

当对GPIOx_ODR的个别位编程时,软件不需要禁止中断: 在单次APB2写操作中,可以只更改一个或多个位。这是通过对置位/复位寄存器中想要更改的位写1实现的。没被选择的位将不被更改。

5.2.3外部中断/唤醒线

所有端口都有外部中断能力。为了使用外部中断线,端口必须配置成输入模式。

5.2.4复用功能

使用默认复用功能(AF)前必须对端口位配置寄存器编程。

(1) 对于复用输入功能,端口必须配置成输入模式(浮空、上拉或下拉)且输入引脚必须由外部驱动。

(2) 对于复用输出功能,端口必须配置成复用功能输出模式(推挽或开漏)。

(3) 对于双向复用功能,端口必须配置成复用功能输出模式(推挽或开漏)。此时,输入驱动器被配置成浮空输入模式。

如果把端口配置成复用输出功能,则引脚和输出寄存器断开,并和片上外设的输出信号连接。

如果软件把一个GPIO引脚配置成复用输出功能,但是外设没有被激活,那么它的输出将不确定。

5.2.5软件重新映射I/O复用功能

STM32F103微控制器的I/O引脚除了通用功能外,还可以设置为一些片上外设的复用功能。而且,一个I/O引脚除了可以作为某个默认外设的复用引脚外,还可以作为其他多个不同外设的复用引脚。类似地,一个片上外设,除了默认的复用引脚,还可以有多个备用的复用引脚。在基于STM32微控制器的应用开发中,用户根据实际需要可以把某些外设的复用功能从默认引脚转移到备用引脚上,这就是外设复用功能的I/O引脚重映射。

为了使不同封装器件的外设I/O功能的数量达到最优,可以把一些复用功能重新映射到其他一些引脚上。这可以通过软件配置AFIO寄存器完成,这时,复用功能就不再映射到它们的原始引脚上了。

5.2.6GPIO锁定机制

锁定机制允许冻结I/O配置。当在一个端口位上执行了锁定(LOCK)程序,在下一次复位之前,将不能再更改端口位的配置。这个功能主要用于一些关键引脚的配置,防止程序跑飞引起灾难性后果。

5.2.7输入配置

当I/O配置为输入时: 

(1) 输出缓冲器被禁止; 

(2) 施密特触发输入被激活; 

(3) 根据输入配置(上拉、下拉或浮动)的不同,弱上拉和下拉电阻被连接; 

(4) 出现在I/O引脚上的数据在每个APB2时钟被采样到输入数据寄存器; 

(5) 对输入数据寄存器的读访问可得到I/O状态。

I/O输入配置如图52所示。



图52I/O输入配置



5.2.8输出配置

当I/O被配置为输出时: 

(1) 输出缓冲器被激活,开漏模式下,输出数据寄存器上的0激活NMOS管,而输出数据寄存器上的1将端口置于高阻状态(PMOS管从不被激活); 推挽模式下,输出数据寄存器上的0激活NMOS管,而输出数据寄存器上的1将激活PMOS管; 

(2) 施密特触发输入被激活; 

(3) 弱上拉和下拉电阻被禁止; 

(4) 出现在I/O引脚上的数据在每个APB2时钟被采样到输入数据寄存器; 

(5) 开漏模式下,对输入数据寄存器的读访问可得到I/O状态; 

(6) 推挽式模式下,对输出数据寄存器的读访问得到最后一次写的值。

I/O输出配置如图53所示。



图53I/O输出配置



5.2.9复用功能配置

当I/O被配置为复用功能时: 

(1) 在开漏或推挽式配置中,输出缓冲器被打开; 

(2) 内置外设的信号驱动输出缓冲器(复用功能输出); 

(3) 施密特触发输入被激活; 

(4) 弱上拉和下拉电阻被禁止; 

(5) 在每个APB2时钟周期,出现在I/O引脚上的数据被采样到输入数据寄存器; 

(6) 开漏模式下,读输入数据寄存器时可得到I/O状态; 

(7) 推挽模式下,读输出数据寄存器时可得到最后一次写的值。

一组复用功能I/O寄存器允许用户把一些复用功能重新映像到不同的引脚。

I/O复用功能配置如图54所示。



图54I/O复用功能配置



5.2.10模拟输入配置

当I/O被配置为模拟输入配置时: 

(1) 输出缓冲器被禁止; 

(2) 禁止施密特触发输入,实现了每个模拟I/O引脚上的零消耗,施密特触发输出值被强置为0; 

(3) 弱上拉和下拉电阻被禁止; 

(4) 读取输入数据寄存器时数值为0。

I/O高阻抗模拟输入配置如图55所示。



图55I/O高阻抗模拟输入配置



5.2.11STM32的GPIO操作

1. 复位后的GPIO

为防止复位后GPIO引脚与片外电路的输出冲突,复位期间和刚复位后,所有GPIO引脚复用功能都不开启,被配置成浮空输入模式。

为了节约电能,只有被开启的GPIO端口才会被提供时钟。因此,复位后所有GPIO端口的时钟都是关断的,使用之前必须逐一开启。

2. GPIO工作模式的配置

每个GPIO引脚都拥有自己的端口配置位CNFy[1:0](其中y代表GPIO引脚在端口中的编号),用于选择该引脚是处于输入模式中的浮空输入模式、上位/下拉输入模式或模拟输入模式,还是输出模式中的输出推挽模式、开漏输出模式或复用功能推挽/开漏输出模式。每个GPIO引脚还拥有自己的端口模式位MODEy(1:0],用于选择该引脚是处于输入模式,或是输出模式中的输出带宽(2MHz、10MHz和50MHz)。

每个端口拥有16个引脚,而每个引脚又拥有4个控制位,因此需要64位才能实现对一个端口所有引脚的配置。它们被分置在两个字(word)中,称为端口配置高寄存器(GPIOx_CRH)和端口配置低寄存器(GPIOx_CRL)。各种工作模式下的硬件配置总结如下。

(1) 输入模式的硬件配置: 输出缓冲器被禁止; 施密特触发器输入被激活; 根据输入配置(上拉、下拉或浮空)的不同,弱上拉和下拉电阻被连接; 出现在I/O引脚上的数据在每个APB2时钟被采样到输入数据寄存器; 对输入数据寄存器的读访问可得到I/O状态。

(2) 输出模式的硬件配置: 输出缓冲器被激活; 施密特触发器输入被激活; 弱上拉和下拉电阻被禁止; 出现在I/O引脚上的数据在每个APB2时钟被采样到输入数据寄存器; 对输入数据寄存器的读访问可得到I/O状态; 对输出数据寄存器的读访问得到最后一次写的值; 在推挽模式下,互补MOS管对都能被打开; 在开漏模式下,只有NMOS管可以被打开。

(3) 复用功能的硬件配置: 在开漏或推挽式配置中,输出缓冲器被打开; 片上外设的信号驱动输出缓冲器; 施密特触发器输入被激活; 弱上拉和下拉电阻被禁止; 在每个APB2时钟周期,出现在I/O引脚上的数据被采样到输入数据寄存器; 对输出数据寄存器的读访问得到最后一次写的值; 在推挽模式下,互补MOS管对都能被打开; 在开漏模式下,只有NMOS管可以被打开。

3. GPIO输入的读取

每个端口都有自己对应的输入数据寄存器 GPIOx_IDR(其中x代表端口号,如GPIOA_IDR),它在每个APB2时钟周期捕捉I/O引脚上的数据。软件可以通过直接读取GPIOx_IDR寄存器某个位,或读取位带别名区中对应字得到GPIO引脚状态对应的值。

4. GPIO输出的控制

STM32为每组16引脚的端口提供了3个32位的控制寄存器: GPIOx_ODR、GPIOx_BSRR和GPIOx_BRR(其中x指代A、B、C等端口号)。其中,GPIOx_ODR寄存器的功能比较容易理解,它的低16位直接对应了端口的16个引脚,软件可以通过直接对这个寄存器的置位或清零,让对应引脚输出高电平或低电平。也可以利用位带操作原理,对GPIOx_ODR寄存器中某个位对应的位带别名区字地址执行写入操作以实现对单个位的简化操作。利用GPIOx_ODR寄存器的位带操作功能可以有效地避免端口中其他引脚的“读改写”问题,但位带操作的缺点是每次只能操作一位,对于某些需要同时操作多个引脚的应用,位带操作就显得力不从心了。STM32的解决方案是使用 GPIOx_BSRR和GPIOx_BRR两个寄存器解决多个引脚同时改变电平的问题。

5. 输出速度

如果STM32F103的I/O引脚工作在某个输出模式下,通常还需设置其输出速度,这个输出速度指的是I/O驱动电路的响应速度,而不是输出信号的速度。输出信号的速度取决于软件程序。

STM32F103的芯片内部在I/O的输出部分安排了多个响应速度不同的输出驱动电路,用户可以根据自己的需要,通过选择响应速度选择合适的输出驱动模块,以达到最佳噪声控制和降低功耗的目的。众所周知,高频的驱动电路噪声也高。当不需要高输出频率时,尽量选用低频响应速度的驱动电路,这样非常有利于提高系统的电磁干扰(Electro Magnetic Interference,EMI)性能。当然,如果要输出较高频率的信号,但却选用了较低频率的驱动模块,很可能会得到失真的输出信号。一般推荐I/O引脚的输出速度是其输出信号速度的5~10倍。

STM32F103的I/O引脚的输出速度有3种选择: 2MHz、10MHz和50MHz。下面根据一些常见的应用,给读者一些选用参考。

(1) 连接LED、蜂鸣器等外部设备的普通输出引脚: 一般设置为2MHz。

(2) 用作USART复用功能输出引脚: 假设 USART工作时最大比特率为115.2kb/s,选用2MHz的响应速度也就足够了,既省电,噪声又小。

(3) 用作I2C复用功能的输出引脚: 假设I2C工作时最大比特率为400kb/s,那么2MHz的引脚速度或许不够,这时可以选用10MHz的I/O引脚速度。

(4) 用作 SPI复用功能的输出引脚: 假设SPI工作时比特率为18Mb/s或9Mb/s,那么10MHz的引脚速度显然不够,这时需要选用50MHz的I/O引脚速度。

(5) 用作FSMC复用功能连接存储器的输出引脚: 一般设置为50MHz的I/O引脚速度。

5.2.12外部中断映射和事件输出

借助AFIO,STM32F103微控制器的I/O引脚不仅可以实现外设复用功能的重映射,而且可以实现外部中断映射和事件输出。需要注意的是,如需使用STM32F103微控制器I/O引脚的以上功能,必须先打开APB2总线上的AFIO时钟。

1. 外部中断映射

当STM32F103微控制器的某个I/O引脚被映射为外部中断线后,该I/O引脚就可以成为一个外部中断源,可以在这个1/O引脚上产生外部中断,实现对用户STM32 运行程序的交互。

STM32F103微控制器的所有I/O引脚都具有外部中断能力。每根外部中断线EXTI LineXX和所有GPIO端口 GPIO[A..G].XX共享。为了使用外部中断线,该I/O引脚必须配置成输入模式。

2. 事件输出

STM32F103微控制器几乎每个I/O引脚(除端口F和G的引脚外)都可用作事件输出。例如,使用SEV指令产生脉冲,通过事件输出信号将STM32F103从低功耗模式唤醒。

5.2.13GPIO的主要特性

综上所述,STM32F103微控制器的GPIO主要具有以下特性。

(1) 提供最多112个多功能双向I/O引脚,80%的引脚利用率。

(2) 几乎每个I/O引脚(除ADC外)都兼容5V,每个I/O引脚具有20mA驱动能力。

(3) 每个I/O引脚具有最高18MHz的翻转速度,50MHz的输出速度。

(4) 每个I/O引脚有8种工作模式,在复位时和刚复位后,复用功能未开启,I/O引脚被配置成浮空输入模式。

(5) 所有I/O引脚都具备复用功能,包括JTAG/SWD、Timer、USART、I2C、SPI等。

(6) 某些复用功能引脚可通过复用功能重映射用作另一个复用功能,方便PCB设计。

(7) 所有I/O引脚都可作为外部中断输入,同时可以有16个中断输入。

(8) 几乎每个I/O引脚(除端口F和G外)都可用作事件输出。

(9) PA0可作为从待机模式唤醒的引脚,PC13可作为入侵检测的引脚。

5.3GPIO的HAL驱动程序

GPIO引脚的操作主要包括初始化、读取引脚输入和设置引脚输出,相关的HAL驱动程序定义在stm32f1xx_hal_gpio.h文件中。GPIO操作相关函数如表51所示,表中只列出了函数名,省略了函数参数。


表51GPIO操作相关函数



函数名功 能 描 述


HAL_GPIO_Init()
GPIO引脚初始化
HAL_GPIO_DeInit()
GPIO引脚解除初始化,恢复为复位后的状态
HAL_GPIO_WritePin()
使引脚输出0或1
HAL_GPIO_ReadPin()
读取引脚的输入电平
HAL_GPIO_TogglePin()
翻转引脚的输出
HAL_GPIO_LockPin()
锁定引脚配置,而不是锁定引脚的输入或输出状态



使用STM32CubeMX生成代码时,GPIO引脚初始化的代码会自动生成,用户常用的GPIO操作函数是进行引脚状态读写的函数。

1. 初始化函数HAL_GPIO_Init()

HAL_GPIO_Init()函数用于对一个端口的一个或多个相同功能的引脚进行初始化设置,包括输入/输出模式、上拉或下拉等。原型定义如下。



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




其中,第1个参数GPIOx是GPIO_TypeDef类型的结构体指针,它定义了端口的各个寄存器的偏移地址,实际调用HAL_GPIO_Init()函数时使用端口的基地址作为GPIOx的值,在stm32f103xx.h文件中定义了各个端口的基地址,如

#defineGPIOA((GPIO_TypeDef *)GPIOA_BASE)

#defineGPIOB((GPIO_TypeDef *)GPIOB_BASE)

#defineGPIOC((GPIO_TypeDef *)GPIOC_BASE)

#defineGPIOD((GPIO_TypeDef *)GPIOD_BASE)


第2个参数GPIO_Init是一个GPIO InitTypeDef类型的结构体指针,它定义了GPIO引脚的属性,这个结构体的定义如下。

typedef  struct 

{

uint32_t  Pin; //要配置的引脚,可以是多个引脚

uint32_t  Mode;       //引脚功能模式

uint32_t  Pull;        //上拉或下拉

uint32_t  Speed;      //引脚最高输出频率

uint32_t  Alternate;   //复用功能选择

}GPIO_InitTypeDef; 


这个结构体的各个成员变量的意义及取值如下。

(1) Pin是需要配置的GPIO引脚,在stm32f1 xx hal_gpio.h文件中定义了16个引脚的宏。如果需要同时定义多个引脚的功能,就用这些宏的或运算进行组合。

#defineGPIO_PIN_0((uint16_t)0x0001)/*Pin0selected */

#defineGPIO_PIN_1((uint16_t)0x0002)/*Pin1selected */

#defineGPIO_PIN_2 ((uint16_t)0x0004)/*Pin2selected */

#defineGPIO_PIN_3 ((uint16_t)0x0008)/*Pin3selected */

#defineGPIO_PIN_4 ((uint16_t)0x0010)/*Pin4selected */

#defineGPIO_PIN_5 ((uint16_t)0x0020)/*Pin5selected */

#defineGPIO_PIN_6 ((uint16_t)0x0040)/*Pin6selected */

#defineGPIO_PIN_7 ((uint16_t)0x0080)/*Pin7selected */

#defineGPIO_PIN_8 ((uint16_t)0x0100)/*Pin8selected */

#defineGPIO_PIN_9 ((uint16_t)0x0200)/*Pin9selected */

#defineGPIO_PIN_10((uint16_t)0x0400)/*Pin10selected*/

#defineGPIO_PIN_11((uint16_t)0x0800)/*Pin11selected*/

#defineGPIO_PIN_12((uint16_t)0x1000)/*Pin12selected*/

#defineGPIO_PIN_13((uint16_t)0x2000)/*Pin13selected*/

#defineGPIO_PIN_14((uint16_t)0x4000)/*Pin14selected*/

#defineGPIO_PIN_15((uint16_t)0x8000)/*Pin15selected*/

#defineGPIO_PIN_All((uint16_t)0xFFFF) /*Allpinsselected*/


(2) Mode是引脚功能模式设置,其可用常量定义如下。

#defineGPIO_MODE_INPUT0x00000000U//输入浮空模式

#defineGPIO_MODE_OUTPUT_PP0x00000001U //推挽输出模式

#defineGPIO_MODE_OUTPUT_OD 0x000000110//开漏输出模式

#defineGPIO_MODE_AF_PP 0x00000002U //复用功能推挽模式

#defineGPIO_MODE_AF_OD0x00000012U //复用功能开漏模式

#defineGPIO_MODE_ANALOG0x000000030 //模拟信号模式

#defineGPIO_MODE_IT_RISING 0x10110000U//外部中断,上升沿触发

#defineGPIO_MODE_IT_FALLING 0x10210000U//外部中断,下降沿触发

#defineGPIO_MODE_IT_RISING_FALLING 0x10310000U //上升、下降沿触发


(3) Pull定义是否使用内部上拉或下拉电阻,其可用常量定义如下。

#defineGPIO_NOPULL 0x00000000U //无上拉或下拉

#defineGPIO_PULLUP 0x00000001U//上拉

#defineGPIO_PULLDOWN0x00000002U//下拉


(4) Speed定义输出模式引脚的最高输出频率,其可用常量定义如下。

#defineGPIO_SPEED_FREQ_LOW 0x00000000U//2MHz

#defineGPIO_SPEED_FREQ_MEDIUM 0x00000001U//12.5~50MHz

#defineGPIO_SPEED_FREQ_HIGH0x00000002U//25~100MHz

#defineGPIO_SPEED_FREQ_VERY_HIGH0x000000030//50~200MHz


(5) Alternate定义引脚的复用功能,在stm32f1xx hal gpio_ex.h文件中定义了这个参数的可用宏定义,这些复用功能的宏定义与具体的MCU型号有关,部分定义示例如下。

#defineGPIO_AF1_TIM1 ((uint8_t)0x01)//TIM1复用功能映射

#defineGPIO_AF1_TIM2 ((uint8_t)0x01)//TIM2复用功能映射

#defineGPIO_AF5_SPI1((uint8_t)0x05)//SPI1复用功能映射

#defineGPIO_AF5_SPI2((uint8_t)0x05)//SPI2复用功能映射

#defineGPIO_AF7_USART1 ((uint8_t)0x07) //USART1复用功能映射

#defineGPIO_AF7_USART2 ((uint8_t)0x07) //USART2复用功能映射

#defineGPIO_AF7_USART3 ((uint8_t)0x07) //USART3复用功能映射


2. 设置引脚输出的HAL_GPIO_WritePin()函数

使用HAL_GPIO_WritePin()函数向一个或多个引脚输出高电平或低电平。原型定义如下。



void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin,GPIO_PinState PinState); 




其中,参数GPIOx是具体的端口基地址; GPIO_Pin是引脚号; PinState是引脚输出电平,它是GPIO_PinState枚举类型,在stm32f1 xx_hal_gpio.h文件中的定义如下。

typedef enum

{

GPIO_PIN_RESET =0,

GPIO_PIN_SET

}GPIO_PinState; 


GPIO_PIN_RESET表示低电平,GPIO_PIN_SET表示高电平。例如,要使PF9和PF10输出低电平,可使用如下代码。

HAL_GPIO_WritePin (GPIOF,GPIO_PIN_9|GPIO_PIN_10,GPIO_PIN_RESET); 

若要输出高电平,只需修改为如下代码。

HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9|GPIO_PIN_10,GPIO_PIN_SET); 

3. 读取引脚输入的HAL_GPIO_ReadPin()函数

HAL_GPIO_ReadPin()函数用于读取一个引脚的输入状态。原型定义如下。



GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx,uint16_tGPIO_Pin); 




函数的返回值是GPIO_PinState枚举类型。GPIO_PIN_RESET表示输入为0(低电平),GPIO_PIN_SET表示输入为1(高电平)。


4. 翻转引脚输出的HAL_GPIO_TogglePin()函数

HAL_GPIO_TogglePin()函数用于翻转引脚的输出状态。例如,引脚当前输出为高电平,执行此函数后,引脚输出为低电平。原型定义如下,只需传递端口号和引脚号。



voidHAL_GPIO_TogglePin (GPIO_TypeDef* GPIOx,uint16_tGPIO_Pin)




5.4STM32的GPIO使用流程

根据I/O端口的特定硬件特征,I/O端口的每个引脚都可以由软件配置成多种工作模式。

在运行程序之前,必须对每个用到的引脚功能进行配置。

(1) 如果某些引脚的复用功能没有使用,可以先配置为GPIO。

(2) 如果某些引脚的复用功能被使用,需要对复用的I/O端口进行配置。

(3) I/O具有锁定机制,允许冻结I/O。当在一个端口位上执行了锁定(LOCK)程序后,在下一次复位之前,将不能再更改端口位的配置。


5.4.1普通GPIO配置

GPIO是最基本的应用,其基本配置方法如下。

(1) 配置GPIO时钟,完成初始化。

(2) 利用HAL_GPIO_Init()函数配置引脚,包括引脚名称、引脚传输速率、引脚工作模式。

(3) 完成HAL_GPIO_Init()函数的设置。

5.4.2I/O复用功能AFIO配置

I/O复用功能AFIO常对应到外设的输入输出功能。使用时,需要先配置I/O为复用功能,打开AFIO时钟,然后再根据不同的复用功能进行配置。对应外设的输入输出功能有以下3种情况。

(1) 外设对应的引脚为输出: 需要根据外围电路的配置选择对应的引脚为复用功能的推挽输出或复用功能的开漏输出。

(2) 外设对应的引脚为输入: 根据外围电路的配置可以选择浮空输入、带上拉输入或带下拉输入。

(3) ADC对应的引脚: 配置引脚为模拟输入。

5.5采用STM32CubeMX和HAL库的GPIO输出应用实例

本实例实现使用固件库点亮LED。

5.5.1STM32的GPIO输出应用硬件设计

STM32F103与LED的连接如图56所示。这是一个RGB LED灯,由红、蓝、绿3个LED构成,使用PWM控制时可以混合成不同的颜色。


图56STM32F103与LED的连接



这些LED的阴极都连接到STM32F103的GPIO引脚,只要控制GPIO引脚的电平输出状态,即可控制LED的亮/灭。如果使用的开发板中LED的连接方式或引脚不一样,只需修改程序的相关引脚即可,程序的控制原理相同。

LED电路是由外接3.3V电源驱动的。当GPIO引脚输出为0时,LED点亮; 输出为1时,LED熄灭。

在本实例中,根据图56设计一个电路,使LED循环显示如下。

(1) 红灯亮1s,灭1s。

(2) 绿灯亮1s,灭1s。

(3) 蓝灯亮1s,灭1s。

(4) 红灯亮1s,灭1s。

(5) 轮流显示,红、绿、蓝、黄、紫、青、白各1s。

(6) 关灯1s。

5.5.2STM32的GPIO输出应用软件设计

1.  通过STM32CubeMX新建工程

通过STM32CubeMX新建工程的步骤如下。

1) 新建文件夹

在D盘根目录新建Demo文件夹,这是保存所有工程的地方,在该目录下新建LED文件夹,这是保存本实例新建工程的文件夹。

2) 新建STM32CubeMX工程

如图57所示,在STM32CubeMX开发环境中执行File→New Project菜单命令或通过STM32CubeMX启动界面中的New Project提示窗口新建工程。



图57新建STM32CubeMX工程



3) 选择MCU或开发板

以MCU为例,Commercial Part Number选择STM32F103ZET6,如图58所示。



图58选择Commercial Part Number



在MCUs/MPUs List中选择STM32F103ZET6,如图59所示。



图59在MCUs/MPUs List中选择STM32F103ZET6



单击Start Project按钮启动工程,如图510所示。



图510启动工程



4) 保存STM32Cube MX工程

执行File→Save Project菜单命令,保存工程到LED文件夹,如图511所示。



图511保存工程



生成的STM32CubeMX文件LED.ioc如图512所示。



图512生成的STM32CubeMX文件



此处直接配置工程名称和保存位置,后续生成的工程应用结构(Application Structure)为Advanced模式,即Inc、Src文件夹存放于Core文件夹下,如图513所示。



图513Advanced模式的工程应用结构



5) 生成工程报告

执行File→Generate Report菜单命令生成当前工程的报告文件LED.pdf,如图514所示。



图514生成工程报告



6) 配置MCU时钟树

在Pinout & Configuration工作界面,选择System Core→RCC,根据开发板实际情况,High Speed Clock(HSE)选择为Crystal/Ceramic Resonator(晶体/陶瓷晶振),如图515所示。

切换到Clock Configuration工作界面,配置完成的时钟树如图516所示。


7) 配置MCU外设

根据LED电路,整理出MCU连接的GPIO引脚的输入/输出配置,如表52所示。



图515HSE选择Crystal/Ceramic Resonator




图516配置完成的时钟树




表52MCU引脚配置



用户标签引脚名称引脚功能GPIO模式上拉或下拉端口速率


LED1_RED
PB5
GPIO_Output
推挽输出
上拉
最高
LED2_GREEN
PB0
GPIO_Output
推挽输出
上拉
最高
LED3_BLUE
PB1
GPIO_Output
推挽输出
上拉
最高



根据表52进行GPIO引脚配置。在引脚视图上单击相应的引脚,在弹出的菜单中选择引脚功能。与LED连接的引脚是输出引脚,设置引脚功能为GPIO_Output,具体步骤如下。

在Pinout & Configuration工作界面选择System Core→GPIO,此时可以看到与RCC相关的两个GPIO已自动配置完成,如图517所示。



图517RCC相关GPIO配置



以控制红色LED的PB5引脚为例,通过搜索框搜索可以定位I/O的引脚位置或在引脚视图上选择PB5,视图中会闪烁显示,配置 PB5 的属性为GPIO_Output,如图518所示。



图518配置PB5引脚为GPlO_Output



在GPIO组件的模式和配置(GPIO Mode and Configuration)界面对每个GPIO引脚进行更多的设置。例如,GPIO输入引脚是上拉还是下拉,GPIO输出引脚是推挽输出还是开漏输出,按照表52的内容设置引脚的用户标签。所有设置是通过下拉列表选择的。GPIO输出引脚的最高输出速率指的是引脚输出变化的最高频率。初始输出设置根据电路功能确定,此工程LED默认输出高电平,即灯不亮状态。具体步骤如下。

如图519所示,配置PB5引脚属性,GPIO output level选择High,GPIO mode选择Output Push Pull,GPIO Pullup/Pulldown选择Pullup,Maximum output speed选择High,User Label定义为LED1_RED。



图519PB5引脚配置



用同样的方法配置LED2_GREEN(PB0)和LED3_BLUE (PB1)。

我们为引脚设置了用户标签,在生成代码时,STM32CubeMX会在main.h文件中为这些引脚定义宏定义符号,然后在GPIO初始化函数中使用这些符号。

配置完成后的GPIO引脚如图520所示。



图520配置完成后的GPIO引脚



8) 配置工程

在Project Manager工作界面Project栏下,Toolchain/IDE选择为MDKARM,Min Version选择为V5,可生成Keil MDK工程; 选择STM32CubeIDE,可生成STM32CubeIDE工程。其余配置默认即可,如图521所示。



图521配置工程



若前面已经保存过工程,生成的工程应用结构默认为Advanced模式,此处不可再次修改; 若前面未保存过工程,此处可修改工程名,存放位置等信息,生成的工程应用结构为Basic模式,即Inc、Src为单独的文件夹,不存放于Core文件夹内,如图522所示。



图522Basic模式工程应用结构



在Project Manager工作界面Code Generator栏,按图523勾选Generated files选项。



图523Generated files配置





图524Code Generation提示框


9) 生成C代码工程

返回STM32CubeMX主界面,单击GENERATE CODE按钮生成C代码工程。生成代码后,STM32CubeMX会弹出提示打开工程对话框,如图524所示。


2. 通过Keil MDK实现工程

1) 打开工程


打开LED/MDKArm文件夹下的工程文件LED.uvprojx,如图525所示。



图525MDKArm文件夹



2) 编译STM32CubeMX自动生成的MDK工程

在MDK开发环境中执行Project→Rebuild all target files菜单命令或单击工具栏的Rebuild按钮编译工程,如图526所示。



图526编译MDK工程



3) STM32CubeMX自动生成的MDK工程

main.c文件中main()函数依次调用了以下3个函数。

(1) HAL_Init()函数: HAL库的初始化函数,用于复位所有外设、初始化Flash接口和SysTick定时器。HAL_Init()函数是在stm32f1xx_hal.c文件中定义的函数,它的代码调用了MSP函数HAL_MspInit(),用于对具体MCU进行初始化处理。HAL_MspInit()函数在项目的用户程序文件stm32f1xx_hal_msp.c中重新实现,实现的代码举例如下,功能是开启各个时钟系统。

void HAL_MspInit(void)

{

HAL_RCC_AFIO_CLK_ENABLE();

HAL_RCC_PWR_CLK_ENABLE();

/*System interrupt init*/

}


(2) SystemClock_Config()函数: 在main.c文件中定义和实现,它是根据 STM32CubeMX里的RCC和时钟树的配置自动生成的代码,用于配置各种时钟信号频率。

void SystemClock_Config(void)

{

RCC_OscInitTypeDef RCC_OscInitStruct = {0};

RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};



/**Initializes the RCC Oscillators according to the specified parameters

* in the RCC_OscInitTypeDef structure.

*/

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;

RCC_OscInitStruct.HSEState = RCC_HSE_ON;

RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;

RCC_OscInitStruct.HSIState = RCC_HSI_ON;

RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;

RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;

RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;

if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)

{

Error_Handler();

}



/**Initializes the CPU, AHB and APB buses clocks

*/

RCC_ClkInitStruct.ClockType= RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK

|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;

RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;

RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;

RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;

RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;



if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, Flash_LATENCY_2) != HAL_OK)

{

Error_Handler();

}

}


(3) GPIO端口函数MX_GPIO_Init(): 在gpio.h文件中定义的GPIO引脚初始化函数,它是STM32CubeMX中GPIO引脚图形化配置的实现代码。

在main()函数中,HAL_Init()和SystemClock_Config()是必然调用的两个函数,再根据使用的外设情况,调用各个外设的初始化函数,然后进入while死循环。

在STM32CubeMX中,为LED连接的GPIO引脚设置了用户标签,这些用户标签的宏定义在main.h文件中。代码如下。

/*Private defines --------------------------------*/

#define LED2_GREEN_Pin GPIO_PIN_0

#define LED2_GREEN_GPIO_Port GPIOB

#define LED3_BLUE_Pin GPIO_PIN_1

#define LED3_BLUE_GPIO_Port GPIOB

#define LED1_RED_Pin GPIO_PIN_5

#define LED1_RED_GPIO_Port GPIOB

/*USER CODE BEGIN Private defines*/

在STM32CubeMX中设置的一个GPIO引脚用户标签,会在此生成两个宏定义,分别是端口宏定义和引脚号宏定义,如PB5引脚设置的用户标签为LED1_RED,就生成了LED1_RED_Pin和LED1_RED_GPIO_Port两个宏定义。

GPIO引脚初始化文件gpio.c和gpio.h是STM32CubeMX生成代码时自动生成的用户程序文件。注意,必须在STM32CubeMX Project Manager工作界面Code Generator栏中勾选“生成.c/.h文件对”选项,才会为一个外设生成.c/.h文件对。

头文件gpio.h定义了一个MX_GPIO_Init()函数,这是在STM32CubeMX中图形化设置的GPIO引脚的初始化函数。

gpio.h文件的代码如下,定义了MX_GPIO_Init()函数原型。

#include "main.h"

void MX_GPIO_Init(void);

gpio.c文件包含了MX_GPIO_Init()函数的实现代码,具体如下。

#include "gpio.h"

void MX_GPIO_Init(void)

{

GPIO_InitTypeDef GPIO_InitStruct = {0};

/*GPIO Ports Clock Enable*/

HAL_RCC_GPIOB_CLK_ENABLE();

/*Configure GPIO pin Output Level*/

HAL_GPIO_WritePin(GPIOB, LED2_GREEN_Pin|LED3_BLUE_Pin|LED1_RED_Pin, GPIO_PIN_SET);



/*Configure GPIO pins : PBPin PBPin PBPin*/

GPIO_InitStruct.Pin = LED2_GREEN_Pin|LED3_BLUE_Pin|LED1_RED_Pin;

GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

GPIO_InitStruct.Pull = GPIO_PULLUP;

GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}


GPIO引脚初始化需要开启引脚所在端口的时钟,然后使用一个GPIO_InitTypeDef结构体变量设置引脚的各种GPIO参数,再调用HAL_GPIO_Init()函数进行GPIO引脚初始化配置。使用HAL_GPIO_Init()函数可以对一个端口的多个相同配置的引脚进行初始化,而不同端口或不同功能的引脚需要分别调用HAL_GPIO_Init()函数进行初始化。在MX_GPIO_Init()函数的代码中,使用了main.h文件中为各个GPIO引脚定义的宏。这样编写代码的好处是程序可以很方便地移植到其他开发板上。

4) 新建用户文件



图527添加文件到MDK工程


在LED/Core/Src文件夹下新建bsp_led.c文件,在LED/Core/Inc文件夹下新建bsp_led.h文件。将bsp_led.c文件添加到Application/User/Core文件夹下,如图527所示。

5) 编写用户代码

如果用户想在生成的STM32CubeIDE初始项目的基础上添加自己的应用程序代码,只需把用户代码写在代码沙箱段内,就可以在STM32CubeMX中修改MCU设置,重新生成代码,而不会影响用户已经添加的程序代码。沙箱段一般以USER CODE BEGIN和USER CODE END标识。此外,用户自定义的文件不受STM32CubeMX生成代码影响。

/*USER CODE BEGIN*/

用户自定义代码

/*USER CODE END*/


为了方便控制LED,把LED常用的亮、灭及状态翻转的控制也直接定义成宏,定义在bsp_led.h文件中。

//R 红色灯

#define LED1_PINGPIO_PIN_5

#define LED1_GPIO_PORTGPIOB

#define LED1_GPIO_CLK_ENABLE()HAL_RCC_GPIOB_CLK_ENABLE()



//G 绿色灯

#define LED2_PIN GPIO_PIN_0

#define LED2_GPIO_PORTGPIOB

#define LED2_GPIO_CLK_ENABLE()HAL_RCC_GPIOB_CLK_ENABLE()



//B 蓝色灯 

#define LED3_PIN GPIO_PIN_1

#define LED3_GPIO_PORTGPIOB

#define LED3_GPIO_CLK_ENABLE() HAL_RCC_GPIOB_CLK_ENABLE()



/************************************************************/

/** 控制LED亮灭的宏

*LED低电平亮,设置ON=0,OFF=1

*若LED高电平亮,把宏设置成ON=1 ,OFF=0 即可

*/

#define ONGPIO_PIN_RESET

#define OFF GPIO_PIN_SET

/*带参宏,可以像内联函数一样使用*/

#define LED1(a)HAL_GPIO_WritePin(LED1_GPIO_PORT,LED1_PIN,a)

#define LED2(a)HAL_GPIO_WritePin(LED2_GPIO_PORT,LED2_PIN,a)

#define LED3(a)HAL_GPIO_WritePin(LED2_GPIO_PORT,LED3_PIN,a)



/*直接操作寄存器的方法控制I/O*/

#define digitalHi(p,i){p->BSRR=i;}//设置为高电平

#define digitalLo(p,i)			{p->BSRR=(uint32_t)i << 16;}//输出低电平

#define digitalToggle(p,i)		{p->ODR ^=i;}		  //输出反转状态

/*定义控制I/O的宏*/

#define LED1_TOGGLE		digitalToggle(LED1_GPIO_PORT,LED1_PIN)

#define LED1_OFF		  digitalHi(LED1_GPIO_PORT,LED1_PIN)

#define LED1_ON			 digitalLo(LED1_GPIO_PORT,LED1_PIN)



#define LED2_TOGGLE		digitalToggle(LED2_GPIO_PORT,LED2_PIN)

#define LED2_OFF		  digitalHi(LED2_GPIO_PORT,LED2_PIN)

#define LED2_ON		  	digitalLo(LED2_GPIO_PORT,LED2_PIN)



#define LED3_TOGGLE		digitalToggle(LED3_GPIO_PORT,LED3_PIN)

#define LED3_OFF		  digitalHi(LED3_GPIO_PORT,LED3_PIN)

#define LED3_ON			 digitalLo(LED3_GPIO_PORT,LED3_PIN)

/*基本混色,使用PWM可混出全彩颜色,且效果更好*/

//红

#define LED_RED\

LED1_ON;\

LED2_OFF\

LED3_OFF



//绿

#define LED_GREEN\

LED1_OFF;\

LED2_ON\

LED3_OFF

//蓝

#define LED_BLUE\

LED1_OFF;\

LED2_OFF\

LED3_ON



//黄(红+绿)					

#define LED_YELLOW \

LED1_ON;\

LED2_ON\

LED3_OFF

//紫(红+蓝)

#define LED_PURPLE\

LED1_ON;\

LED2_OFF\

LED3_ON



//青(绿+蓝)

#define LED_CYAN \

LED1_OFF;\

LED2_ON\

LED3_ON



//白(红+绿+蓝)

#define LED_WHITE\

LED1_ON;\

LED2_ON\

LED3_ON



//黑(全部关闭)

#define LED_RGBOFF \

LED1_OFF;\

LED2_OFF\

LED3_OFF


这部分宏控制LED亮灭的操作是通过直接向BSRR寄存器写入控制指令实现的,对BSRR寄存器低16位写1输出高电平,对BSRR寄存器高16位写1输出低电平,对ODR寄存器某位进行异或操作可翻转位的状态。

利用上面的宏,bsp_led.c文件实现LED的初始化函数LED_GPIO_Config()。此处仅关闭RGB灯,用户可根据需要初始化RGB灯的状态。

void LED_GPIO_Config(void)

{		

/*关闭RGB灯*/

LED_RGBOFF;

}

在main.c文件中添加对bsp_led.h文件引用。

/*Private includes------------------------------------*/

/*USER CODE BEGIN Includes*/

#include "bsp_led.h"

/*USER CODE END Includes*/


在main()函数中添加对LED的控制。调用前面定义的LED_GPIO_Config()函数初始化LED,然后直接调用各种控制LED亮灭的宏实现对LED的控制,延时采用库自带的基于滴答时钟延时函数HAL_Delay(),单位为ms,直接调用即可,这里HAL_Delay(1000)表示延时1s。

int main(void)

{

/*MCU Configuration--------------------------------------------*/

/*Reset of all peripherals, initializes the Flash interface and the Systick*/

HAL_Init();

/*Configure the system clock*/

SystemClock_Config();

/*Initialize all configured peripherals*/

MX_GPIO_Init();

/*USER CODE BEGIN 2*/

/*LED 端口初始化*/

LED_GPIO_Config();

/*USER CODE END 2*/

/*Infinite loop*/

/*USER CODE BEGIN WHILE*/

while (1)

{

LED1( ON );//亮 

HAL_Delay(1000);

LED1( OFF );		  //灭

HAL_Delay(1000);



LED2( ON );		 //亮 

HAL_Delay(1000);

LED2( OFF );		  //灭



LED3( ON );		 //亮 

HAL_Delay(1000);

LED3( OFF );		  //灭	



/*轮流显示红绿蓝黄紫青白颜色*/

LED_RED;

HAL_Delay(1000);



LED_GREEN;

HAL_Delay(1000);



LED_BLUE;

HAL_Delay(1000);



LED_YELLOW;

HAL_Delay(1000);



LED_PURPLE;

HAL_Delay(1000);



LED_CYAN;

HAL_Delay(1000);



LED_WHITE;

HAL_Delay(1000);



LED_RGBOFF;

HAL_Delay(1000);

/*USER CODE END WHILE*/

}


}


开发板上的RGB彩灯可以实现混色,最后一段代码控制各种颜色的实现。

6) 重新编译工程

重新编译添加代码后的工程,如图528所示。



图528重新编译MDK工程



7) 配置工程仿真与下载项

在MDK开发环境中执行Project→Options for Target菜单命令或单击工具栏按钮配置工程,如图529所示。



图529配置MDK工程



切换至Debug选项卡,选择使用的仿真下载器STLink Debugger。配置Flash Download,勾选Reset and Run复选框。单击“确定”按钮,如图530所示。



图530配置Flash Download选项




图531下载工程


8) 下载工程

连接好仿真下载器,开发板上电。

在MDK开发环境中执行Flash→Download菜单命令或单击工具栏按钮下载工程,如图531所示。



工程下载成功提示如图532所示。



图532工程下载成功提示



工程下载完成后,观察开发板上LED的闪烁状态,RGB彩灯轮流显示不同的颜色。

5.6采用STM32CubeMX和HAL库的GPIO输入应用实例

本实例实现使用固件库的按键检测。

5.6.1STM32的GPIO输入应用硬件设计


按键机械触点断开、闭合时,由于触点的弹性作用,按键开关不会马上稳定接通或一下子断开,使用按键时会产生抖动信号,需要用软件消抖处理滤波,不方便输入检测。本实例开发板连接的按键附带硬件消抖功能,如图533所示。它利用电容充放电的延时消除了波纹,从而简化软件的处理,软件只需要直接检测引脚的电平即可。


图533按键检测电路



由按键检测电路可知,这些按键在没有被按下时,GPIO引脚的输入状态为低电平(按键所在的电路不通,引脚接地); 当按键按下时,GPIO引脚的输入状态为高电平(按键所在的电路导通,引脚接到电源)。只要检测按键引脚的输入电平,即可判断按键是否被按下。

若使用的开发板按键的连接方式或引脚不一样,只需根据工程修改引脚即可,程序的控制原理相同。

在本实例中,根据按键检测电路设计一个实例,通过按键控制LED,功能如下。

(1) 按下KEY1,红灯反转。

(2) 按下KEY2,绿灯反转。

5.6.2STM32的GPIO输入应用软件设计

编程要点如下。

(1) 使能GPIO端口时钟。

(2) 初始化GPIO目标引脚为输入模式(浮空输入)。

(3) 编写简单测试程序,检测按键的状态,实现按键控制LED。

下面讲述如何通过STM32CubeMX新建工程、如何通过Keil MDK实现工程。


1. 通过STM32CubeMX新建工程

(1) 新建文件夹。在Demo目录下新建KEY文件夹,这是保存本实例新建工程的文件夹。

(2) 在STM32CubeMX开发环境中新建工程。

(3) 选择MCU或开发板。选择STM32F103ZET6,启动工程。

(4) 执行File→Save Project菜单命令,保存工程。

(5) 执行File→Generate Report菜单命令生成当前工程的报告文件。

(6) 配置MCU时钟树。

在Pinout & Configuration工作界面下,选择System Core→RCC,根据开发板实际情况,High Speed Clock(HSE)选择为Crystal/Ceramic Resonator(晶体/陶瓷晶振)。

切换到Clock Configuration工作界面,根据开发板外设情况配置总线时钟。此处配置PLL Source Mux为HSE,PLLMul为9倍频72MHz,System Clock Mux为PLLCLK,APB1 Prescaler为X2,其余保持默认设置即可。

(7) 配置MCU外设。

根据LED和KEY电路,整理出MCU连接的GPIO引脚的输入/输出配置,如表53所示。


表53MCU引脚配置



用户标签引脚名称引脚功能GPIO模式上拉或下拉端口速率


LED1_RED
PB5
GPIO_Output
推挽输出
上拉
最高
LED2_GREEN
PB0
GPIO_Output
推挽输出
上拉
最高
LED3_BLUE
PB1
GPIO_Output
推挽输出
上拉
最高
KEY1
PA0
GPIO_Input
浮空输入
无
—
KEY2
PC13
GPIO_Input
浮空输入
无
—



再根据表53进行GPIO引脚配置。在引脚视图上单击相应的引脚,在弹出的菜单中选择引脚功能。与LED连接的引脚是输出引脚,设置引脚功能为GPIO_Output; 与KEY连接的引脚是输入引脚,设置引脚功能为GPIO_Input。具体步骤如下。

在Pinout & Configuration工作界面下选择System Core→GPIO,对使用的GPIO进行设置。LED输出端口: LED1_RED(PB5)、LED2_GREEN(PB0)和LED3_BLUE (PB1); 按键输入端口: KEY1(PA0)和KEY2(PC13)。配置完成后的GPIO页面如图534所示。



图534配置完成后的GPIO页面



(8) 配置工程。

在Project Manager工作界面Project栏,Toolchain/IDE选择为MDKARM,Min Version选择为V5,可生成Keil MDK工程; 选择为STM32CubeIDE,可生成STM32CubeIDE工程。

(9) 生成C代码工程。

返回STM32CubeMX主页面,单击GENERATE CODE按钮生成C代码工程。

2. 通过Keil MDK实现工程

通过Keil MDK实现工程的步骤如下。

(1) 打开KEY/MDKArm文件夹下的工程文件。

(2) 编译STM32CubeMX自动生成的MDK工程。

在MDK开发环境中执行Project→Rebuild all target files菜单命令或单击工具栏Rebuild按钮编译工程。

(3) STM32CubeMX自动生成的MDK工程如下。

main.c文件中main()函数依次调用了: HAL_Init()函数,用于复位所有外设、初始化Flash接口和SysTick定时器; SystemClock_Config()函数,用于配置各种时钟信号频率; MX_GPIO_Init()函数,用于初始化GPIO引脚。

在STM32CubeMX中,为LED和KEY连接的GPIO引脚设置了用户标签,这些用户标签的宏定义在main.h文件中。代码如下。

/*Private defines -----------------------------------*/

#define KEY2_Pin GPIO_PIN_13

#define KEY2_GPIO_Port GPIOC

#define KEY1_Pin GPIO_PIN_0

#define KEY1_GPIO_Port GPIOA

#define LED2_GREEN_Pin GPIO_PIN_0

#define LED2_GREEN_GPIO_Port GPIOB

#define LED3_BLUE_Pin GPIO_PIN_1

#define LED3_BLUE_GPIO_Port GPIOB

#define LED1_RED_Pin GPIO_PIN_5

#define LED1_RED_GPIO_Port GPIOB

/*USER CODE BEGIN Private defines*/

gpio.c文件包含了MX_GPIO_Init()函数的实现代码,具体如下。

void MX_GPIO_Init(void)

{

GPIO_InitTypeDef GPIO_InitStruct = {0};

/*GPIO Ports Clock Enable*/

HAL_RCC_GPIOC_CLK_ENABLE();

HAL_RCC_GPIOA_CLK_ENABLE();

HAL_RCC_GPIOB_CLK_ENABLE();

/*Configure GPIO pin Output Level*/

HAL_GPIO_WritePin(GPIOB, LED2_GREEN_Pin|LED3_BLUE_Pin|LED1_RED_Pin, GPIO_PIN_SET);

/*Configure GPIO pin : PtPin*/

GPIO_InitStruct.Pin = KEY2_Pin;

GPIO_InitStruct.Mode = GPIO_MODE_INPUT;

GPIO_InitStruct.Pull = GPIO_NOPULL;

HAL_GPIO_Init(KEY2_GPIO_Port, &GPIO_InitStruct)

/*Configure GPIO pin : PtPin*/

GPIO_InitStruct.Pin = KEY1_Pin;

GPIO_InitStruct.Mode = GPIO_MODE_INPUT;

GPIO_InitStruct.Pull = GPIO_NOPULL;

HAL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct);

/*Configure GPIO pins : PBPin PBPin PBPin*/

GPIO_InitStruct.Pin = LED2_GREEN_Pin|LED3_BLUE_Pin|LED1_RED_Pin;

GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;

GPIO_InitStruct.Pull = GPIO_PULLUP;

GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;

HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}


(4) 新建用户文件。

在KEY/Core/Src文件夹下新建bsp_led.c、bsp_key.c文件,在KEY/Core/Inc文件夹下新建bsp_led.h、bsp_key.h文件。将bsp_led.c和bsp_key.c文件添加到Application/User/Core文件夹下。

(5) 编写用户代码。

bsp_led.h和bsp_led.c文件实现LED操作的宏定义和LED初始化。

bsp_key.h文件实现按键检测引脚相关的宏定义。

/** 按键按下标志宏

* 若按键按下为高电平,设置 KEY_ON=1, KEY_OFF=0

* 若按键按下为低电平,把宏设置成KEY_ON=0 ,KEY_OFF=1 即可

*/

#define KEY_ON 1

#define KEY_OFF 0


bsp_key.c文件实现按键扫描函数Key_Scan()。GPIO引脚的输入电平可通过读取IDR寄存器对应的数据位感知,而STM32 HAL库提供了HAL_GPIO_ReadPin()库函数获取位状态,该函数输入GPIO端口及引脚号,返回该引脚的电平状态,高电平返回1,低电平返回0。Key_Scan()函数中将HAL_GPIO_ReadPin()函数的返回值与自定义的宏KEY_ON进行对比,若检测到按键按下,则使用while循环持续检测按键状态,直到按键释放,按键释放后Key_Scan()函数返回一个KEY_ON值; 若没有检测到按键按下,则函数直接返回KEY_OFF。若按键的硬件没有做消抖处理,需要在这个Key_Scan()函数中做软件滤波,防止波纹抖动引起误触发。

uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)

{			

/*检测是否有按键按下*/

if(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON )  

{	 

/*等待按键释放*/

while(HAL_GPIO_ReadPin(GPIOx,GPIO_Pin) == KEY_ON);

return KEY_ON;	 

}

else

return KEY_OFF;

}


在main.c文件中添加对用户自定义头文件的引用。

/*Private includes ------------------------------*/

/*USER CODE BEGIN Includes*/

#include " bsp_led.h"

#include "bsp_key.h"	

/*USER CODE END Includes*/

在main.c文件中添加对LED的初始化和对按键的控制。KEY1控制LED1_RED,KEY2控制LED2_GREEN。按一次按键,LED状态就翻转一次。

/*USER CODE BEGIN 2*/

/*LED 端口初始化*/

LED_GPIO_Config();

/*USER CODE END 2*/



/*Infinite loop*/

/*USER CODE BEGIN WHILE*/

while (1)

{

if( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON  )

{

/*LED1翻转*/

LED1_TOGGLE;

}

if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON  )

{

/*LED2翻转*/

LED2_TOGGLE;

} 

/*USER CODE END WHILE*/


初始化LED及按键后,在while循环里不断调用Key_Scan()函数,并判断其返回值,若返回值表示按键按下,则反转LED的状态。

(6) 重新编译添加代码后的工程。

(7) 配置工程仿真与下载项。

在MDK开发环境中执行Project→Options for Target菜单命令或单击工具栏按钮配置工程。

切换至Debug选项卡,选择使用的仿真下载器STLink Debugger。配置Flash Download,勾选Reset and Run复选框。

(8) 下载工程。

连接好仿真下载器,开发板上电。

在MDK开发环境中执行Flash→Download菜单命令或单击工具栏按钮下载工程。

工程下载完成后,操作按键,观察开发板上LED的状态。