STM32F0实战:基于HAL库开发
上QQ阅读APP看书,第一时间看更新

第3章 GPIO

通用I/O端口是STM32F0系列微控制器最基本的外设,STM32F0系列微控制器的I/O口统称为通用输入/输出端口(General Purpose I/O Port, GPIO),本章重点介绍GPIO模块的内部结构和功能。

3.1 GPIO概述

STM32F0系列微控制器的通用I/O端口可划分成若干个I/O组。由于该系列微控制器均集成了数量众多的高性能外设,所以STM32F0系列微控制器的大多数GPIO引脚功能都是复用的,且与片内外设模块共享。而且,与51单片机不同的是,STM32F0系列微控制器的GPIO在使用前需要对端口的数据方向和性能进行设置。

3.1.1 GPIO的功能

STM32F072VBT6微控制器采用LQFP100封装,具有100个引脚,其中87个引脚具有I/O口的功能,这些I/O口分为6组,分别称为GPIOA、GPIOB、GPIOC、GPIOD、GPIOE和GPIOF,每一个GPIO组都可看作一个独立的外设模块与微控制器的内部总线相连,GPIO模块与AHB总线的连接方式如图3-1所示。

图3-1 GPIO模块与AHB总线的连接

对于32位的微控制器而言,一个完整的GPIO组由16个引脚构成,每个GPIO引脚都可以通过软件配置为输出(推挽或漏极开路)、输入(带或不带上拉及下拉)或复用的外设功能,并且所有I/O口均可以配置为外部中断的输入端。GPIO引脚配备详见表3-1,引脚功能定义参见本书附录D。

表3-1 GPIO引脚配备

STM32F0系列微控制器的GPIO在输出状态下,可以配置成带有上拉或下拉的推挽输出或开漏输出,而且每个I/O口的速度可编程。在输入状态下,可以配置成浮空、上拉、下拉或模拟输入,GPIO端口位的功能配置详见表3-2。

表3-2 GPIO端口位功能配置

注:GP=通用,PP=推挽输出,PU=上拉,PD=下拉,OD=开漏,AF=复用功能。

3.1.2 GPIO的位结构

GPIO的标准端口位结构如图3-2所示。每一个GPIO端口位都由相应的寄存器、输入输出驱动等部分构成。系统复位后所有的GPIO端口被配置为浮空输入模式,比较特殊的是,用于调试功能的两个引脚在复位后被自动设置为复用功能的上拉/下拉模式,即:PA14/SWCLK引脚置于下拉模式,而PA13/SWDAT引脚置于上拉模式。

图3-2 GPIO端口位的基本结构

1.输入配置

GPIO端口配置为输入模式时,端口的输出缓冲区禁用,输入端连接的施密特触发器激活,GPIOx_PUPDR寄存器用于设置是否激活上拉、下拉电阻。在每个AHB时钟周期,I/O引脚上的数据都会被锁存至输入数据寄存器,CPU通过对输入数据寄存器的读访问来获得I/O口的实际状态,GPIO端口的输入配置如图3-3所示。

图3-3 GPIO端口的输入配置

2.输出配置

GPIO端口配置为输出时,输出缓冲器开启。如果GPIO工作在开漏模式下,输出寄存器上的数字“0”将激活N-MOS,端口输出低电平,相应地输出寄存器上的数字“1”则不能激活P-MOS,端口处于高阻状态;如果GPIO工作在推挽模式下,输出寄存器上的“0”会激活N-MOS,端口输出低电平,而输出寄存器上的“1”会激活P-MOS,端口输出高电平。

在GPIO端口配置为输出时,施密特触发器输入同样会被激活,弱上拉和弱下拉电阻是否激活取决于GPIOx_PUPDR寄存器的值。在每个AHB时钟周期,I/O引脚上的数据仍会被采样至输入数据寄存器中,对输入数据寄存器的读操作同样可以获取I/O口的真实状态,而对输出数据寄存器的读访问可以获取最后写进该寄存器的值,GPIO端口的输出配置如图3-4所示。

图3-4 GPIO端口的输出配置

3.复用功能配置

绝大多数的GPIO端口与外设输入输出共同使用端口引脚。当端口被配置为复用功能时,来自外设的信号会驱动输出缓冲器,施密特触发器同样会被激活。弱上拉和弱下拉电阻是否激活则取决于GPIOx_PUPDR寄存器的设定。在每个AHB时钟周期,I/O引脚上的数据也会被采样进入输入数据寄存器,所以读取输入数据寄存器仍可获取I/O口的实际状态。GPIO端口的复用功能配置如图3-5所示。

图3-5 GPIO端口的复用功能配置

4.模拟配置

当GPIO端口被配置为ADC模块的输入通道时,输出缓冲器关闭,为了降低功耗,施密特触发器也被禁止,其输出值被强置为“0”。这样做的好处是可以使每个模拟I/O引脚上的电流消耗为零。同时上拉和下拉电阻被禁止,读取输入数据寄存器将返回数值“0”。GPIO端口的模拟输入配置如图3-6所示。

图3-6 GPIO端口的模拟输入配置

3.1.3 GPIO的特殊功能

GPIO除了可以配置输入、输出或复用功能外,还具有一些较为特殊的功能,比如可以单独对某一位进行置位/复位操作、端口锁定或将端口的复用功能重映射到其他引脚上等。

1.位操作

GPIO端口通过置位/复位寄存器GPIOx_BSRR,可以对端口输出数据寄存器GPIOx_ODR的每个位进行置位和复位操作。置位/复位寄存器是一个32位寄存器,而端口输出数据寄存器是16位的,相对于GPIOx_ODR寄存器中的每一位,在GPIOx_BSRR中都有两个位与之对应,即BSx和BRx。当对BSx位写“1”时会将相应的ODRx位置位,而对BRx位写“1”时则复位相应的ODRx位。对GPIOx_BSRR中的任意位写“0”都不会影响GPIOx_ODR寄存器的值,如果对GPIOx_BSRR寄存器的BSx位和BRx位同时置1,那么其置位操作具有优先权,即对相应位做置位操作。

2.端口锁定

端口配置锁定寄存器(GPIOx_LCKR)用于冻结端口A和端口B的控制寄存器,当一个特定的写/读序列作用在GPIOx_LCKR寄存器时,端口A或端口B的相关寄存器会冻结,即GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR、GPIOx_PUPDR、GPIOx_AFRL和GPIOx_AFRH。冻结后的上述寄存器的值将不能更改,直到下一次复位为止。使用端口的锁定机制可以防止微控制器在运行时意外改变端口配置,从而造成误动作。

3.端口复用功能映射

STM32F0系列微控制器的大部分GPIO端口会与多个外设共用,选择每个端口的有效复用功能可以通过GPIOx_AFR寄存器来实现。通过对GPIOx_AFR寄存器的AFRLx[3:0]位赋值,可以选择该端口所连接的外设,也可以根据需要将某一复用功能映射到其他引脚。

例如,PA7端口的复用功能有“SPI1_MOSI”、“I2S1_SD”、“TIM3_CH2”、“TIM1_CH1N”、“TSC_G2_IO4”、“TIM14_CH1”、“TIM17_CH1”、“EVENTOUT”和“COMP2_OUT”,如果想使用“COMP2_OUT”这个功能,则可以给GPIOA_AFRL寄存器的AFRL7[3:0]位赋值为“0111”(AF7),将PA7端口的复用功能设置为“COMP2_OUT”。又如,比较器1的外部输出端“COMP1_OUT”默认在PA0引脚,可以通过将GPIOA_AFRL寄存器的AFRL6[3:0]位赋值为“0111”(AF7),将“COMP1_OUT”功能从PA0引脚转移到PA6上。端口的复用功能映射详见本书附录F。

3.1.4 GPIO的寄存器分类

GPIO的寄存器可以以字(32位)、半字(16位)或字节(8位)的方式写入。这些相关寄存器按功能不同可以分成以下三类。

1. I/O端口控制寄存器

每个GPIO端口都有4个32位的控制寄存器,分别是GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR、GPIOx_PUPDR,用来配置端口特性。其中:GPIOx_MODER寄存器用来选择I/O模式,如输入、输出、复用或模拟等;GPIOx_OTYPER寄存器用来选择输出类型,如推挽或开漏等;GPIOx_OSPEEDR寄存器用于设定I/O口速度;GPIOx_PUPDR寄存器用来选择I/O口上拉/下拉方式。

2. I/O端口数据寄存器

每个GPIO口有两个16位数据寄存器:输入数据寄存器GPIOx_IDR和输出数据寄存器GPIOx_ODR。其中,从I/O口线锁存的输入数据存放在GPIOx_IDR寄存器中,该寄存器为只读寄存器;GPIOx_ODR用于存储输出数据,可进行读/写访问。另外,每个GPIO口还有一个32位的置位/复位寄存器GPIOx_BSRR,用于对端口的某一位进行单独的位操作。

3. I/O端口锁定及复用功能寄存器

端口A和端口B还含有一个32位端口配置锁定寄存器GPIOx_LCKR和两个32位的复用功能寄存器GPIOx_AFRH和GPIOx_AFRL。端口配置锁定寄存器用于锁定I/O口配置,防止微控制器在运行过程中被更改,复用功能寄存器用于将I/O口的复用功能重映射到其他引脚上。

3.2 GPIO函数

3.2.1 GPIO类型定义

输出类型3-1:GPIO初始化结构定义

输出类型3-2:GPIO位置位和复位枚举

3.2.2 GPIO常量定义

输出常量3-1:GPIO_pins定义

输出常量3-2:GPIO_mode定义

输出常量3-3:GPIO_speed定义

输出常量3-4:GPIO_pull定义

3.2.3 GPIO函数定义

函数3-1

函数3-2

函数3-3

函数3-4

函数3-5

函数3-6

函数3-7

函数3-8

3.3 GPIO应用实例

下面我们要控制连接在PC13引脚上的LED灯,让它以半秒钟的时间间隔闪烁。这里我们使用STM32CubeMX软件完成时钟和PC13引脚的初始化配置并生成开发项目,对PC13的I/O口电平变化控制将使用HAL库函数来实现。

3.3.1 生成开发项目

1)打开STM32CubeMX软件,单击“New Project”按钮,新建开发项目,在视图选项卡的“Pinout”视图中,将“PF0”引脚的工作模式设置为“RCC_OSC_IN”,将“PF1”引脚的工作模式设置为“RCC_OSC_OUT”,将“PC13”引脚的工作模式设置为“GPIO_Output”,用于驱动LED,如图3-7所示。

图3-7 建立GPIO开发项目(一)

2)在“Clock Conf iguration”视图中,将HSE时钟作为锁相环输入时钟,将锁相环时钟倍频设置为“×6”,并且将锁相环时钟设置为系统时钟,如图3-8所示。

图3-8 建立GPIO开发项目(二)

3)在“Conf iguration”视图中,在“System”列表中单击“GPIO”按钮,如图3-9所示。

图3-9 建立GPIO开发项目(三)

4)在弹出的“Pin Conf iguration”对话框中,将PC13引脚的初始化电平设置为“Low”,如图3-10所示。

图3-10 建立GPIO开发项目(四)

5)将生成的开发项目命名为“Flashing”,并将其保存至“D:\STM32F072VB_HAL\chapter03”路径下,如图3-11所示。

图3-11 建立GPIO开发项目(五)

6)使用MDK-ARM集成开发环境打开所生成的项目,在程序的主循环中,找到“/*USER CODE BEGIN 3 */”位置,并加入以下代码:

        /* USER CODE BEGIN 3 */

          /* 置位PC13引脚 */
          HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
          /* 延时500ms */
          HAL_Delay(500);
          /* 复位PC13引脚 */
          HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
          /* 延时500ms */
          HAL_Delay(500);
          }
          /* USER CODE END 3 */

在上述代码中,通过“HAL_GPIO_WritePin()”函数控制PC13引脚的输出电平,使其循环输出各为500ms的高低电平。这里还调用了HAL固件库中的延时函数“HAL_Delay();”,用于设置高低电平的持续时间。上述代码经编译后下载至STM32F072VBT6系统板中,代码运行后系统板上与PC13引脚连接的LED指示灯即开始闪烁,该项目的完整代码详见代码清单3-1。


代码清单3-1 GPIO测试代码(main.c)(在附录J中指定的网站链接下载源代码)

3.3.2 主程序文件结构解析

下面我们来仔细分析一下main.c源文件中的代码结构,这有助于我们进一步理解STM32CubeMX软件所生成代码的功能。

1)在程序的开始部分,是意法公司的软件版权声明,具体代码如下:

        /**
          ******************************************************************************
          * File Name             : main.c
          * Description          : Main program body
          ******************************************************************************
          *
          * COPYRIGHT(c) 2016 STMicroelectronics
          *
          * Redistribution and use in source and binary forms, with or without modification,
          * are permitted provided that the following conditions are met:
          *    1. Redistributions of source code must retain the above copyright notice,
          *        this list of conditions and the following disclaimer.
          *
                …… <部分代码略> ……
          *
          ******************************************************************************
          */

2)在程序代码的“Includes”部分,包含了“stm32f0xx_hal.h”头文件,用于对STM32F0系列微控制器的支持,其他需要包含到程序中的文件可以在“/* USER CODE BEGIN Includes */”之后的位置添加,具体代码如下:

        /* Includes ------------------------------------------------------------------ */
        #include "stm32f0xx_hal.h"

        /* USER CODE BEGIN Includes */

        /* USER CODE END Includes */

3)在程序代码的专用变量定义部分,可以添加需要定义的全局变量,具体代码如下:

        /* Private variables --------------------------------------------------------- */

        /* USER CODE BEGIN PV */
        /* Private variables --------------------------------------------------------- */

        /* USER CODE END PV */

4)在程序代码的专用函数原型声明部分,软件在此对自动生成的SystemClock_Conf ig();等3个函数进行了原型声明。在“/* USER CODE BEGIN PFP */”部分可以对自行定义的函数进行声明,在“/* USER CODE BEGIN 0 */”部分可以加入用户需要的其他代码,这部分程序运行的具体内容如下:

        /* Private function prototypes ----------------------------------------------- */
        void SystemClock_Config(void);
        void Error_Handler(void);
        static void MX_GPIO_Init(void);

        /* USER CODE BEGIN PFP */
        /* Private function prototypes ----------------------------------------------- */

        /* USER CODE END PFP */

        /* USER CODE BEGIN 0 */

        /* USER CODE END 0 */

5)在主函数“int main(void)”中,开始部分“/* USER CODE BEGIN 1 */”是用户代码区,可以自行加入需要的程序代码;之后是“MCU Conf iguration”区,用于复位所有外设、初始化Flash存储器接口和系统节拍器等。软件在此自动生成了代码,其中通过调用HAL_Init()函数来配置Flash、时基源、NVIC和底层硬件;通过调用SystemClock_Conf ig()函数来配置系统时钟;通过调用MX_GPIO_Init()函数来配置GPIO引脚。具体代码如下:

        /* USER CODE BEGIN 1 */

          /* USER CODE END 1 */

          /* 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 */

          /* USER CODE END 2 */

6)在主函数“int main(void)”的循环体中,可以加入程序中需要重复执行的部分,我们定义的LED闪烁程序行就在此加入。具体代码如下:

        /* Infinite loop */
          /* USER CODE BEGIN WHILE */
          while (1)
          {
          /* USER CODE END WHILE */

          /* USER CODE BEGIN 3 */
              …… <部分代码略> ……
          }
          /* USER CODE END 3 */

7)在“/** System Clock Conf iguration */”部分,软件自动生成了系统时钟配置函数SystemClock_Conf ig(),并允许在时钟错误时调用Error_Handler()函数来处理错误。具体代码如下:

        /** System Clock Configuration
        */
        void SystemClock_Config(void)
        {

          RCC_OscInitTypeDef RCC_OscInitStruct;
          RCC_ClkInitTypeDef RCC_ClkInitStruct;

          RCC_OscInitStruct.OscillatorType=RCC_OSCILLATORTYPE_HSE;
          RCC_OscInitStruct.HSEState=RCC_HSE_ON;
          RCC_OscInitStruct.PLL.PLLState=RCC_PLL_ON;
          RCC_OscInitStruct.PLL.PLLSource=RCC_PLLSOURCE_HSE;
          RCC_OscInitStruct.PLL.PLLMUL=RCC_PLL_MUL6;
          RCC_OscInitStruct.PLL.PREDIV=RCC_PREDIV_DIV1;
          if (HAL_RCC_OscConfig(&RCC_OscInitStruct) !=HAL_OK)
          {
            Error_Handler();
          }

          RCC_ClkInitStruct.ClockType=RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                        |RCC_CLOCKTYPE_PCLK1;
          RCC_ClkInitStruct.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK;
          RCC_ClkInitStruct.AHBCLKDivider=RCC_SYSCLK_DIV1;
          RCC_ClkInitStruct.APB1CLKDivider=RCC_HCLK_DIV1;
          if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) !=HAL_OK)
          {
            Error_Handler();
          }

          HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

          HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

          /* SysTick_IRQn interrupt configuration */
          HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
        }

8)在“/** Conf igure pins as …… */”部分,可以将引脚配置成模拟、输入和输出等模式。软件在此自动生成了MX_GPIO_Init()函数来配置引脚,并在之后的“/* USER CODE BEGIN 4 */”区域允许用户自行添加其他程序代码。具体内容如下:

        /** Configure pins as
                * Analog
                * Input
                * Output
                * EVENT_OUT
                * EXTI
        */
        static void MX_GPIO_Init(void)
        {

          GPIO_InitTypeDef GPIO_InitStruct;              //定义结构体变量GPIO_InitStruct

          /* GPIO Ports Clock Enable */
          __HAL_RCC_GPIOC_CLK_ENABLE();                   //使能GPIOC时钟
          __HAL_RCC_GPIOF_CLK_ENABLE();                   //使能GPIOF时钟

          /* Configure GPIO pin Output Level */
          HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);  //复位PC13引脚

          /* Configure GPIO pin : PC13 */
          GPIO_InitStruct.Pin=GPIO_PIN_13;             //初始化目标引脚PC13
          GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_PP;   //推挽输出模式
          GPIO_InitStruct.Pull=GPIO_NOPULL;            //无上下拉
          GPIO_InitStruct.Speed=GPIO_SPEED_FREQ_LOW;  /低速
          HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);       //调用GPIO初始化函数

        }

        /* USER CODE BEGIN 4 */

        /* USER CODE END 4 */

9)在“/** @brief This function is executed in case of error occurrence……”部分定义了一个空的错误处理函数Error_Handler(void),我们可以给该函数添加相应的功能,用于应对可能发生的错误。这部分的具体代码如下:

        /**
          * @brief   This function is executed in case of error occurrence.
          * @param   None
          * @retval None
          */
        void Error_Handler(void)
        {
          /* USER CODE BEGIN Error_Handler */
          /* User can add his own implementation to report the HAL error return state */
          while(1)
          {
          }
          /* USER CODE END Error_Handler */
        }

10)在主程序文件的最后部分是一个条件编译结构,其中定义了一个assert_failed()函数,用于检测程序中存在错误的文件名称和程序行号码。这部分的具体代码如下:

        #ifdef USE_FULL_ASSERT

        /**
            * @brief Reports the name of the source file and the source line number
            * where the assert_param error has occurred.
            * @param file: pointer to the source file name
            * @param line: assert_param error line source number
            * @retval None
            */
        void assert_failed(uint8_t* file, uint32_t line)
        {
          /* USER CODE BEGIN 6 */
          /* User can add his own implementation to report the file name and line number,
            ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
          /* USER CODE END 6 */

        }

        #endif

3.3.3 外设初始化过程分析

下面我们以GPIO的初始化过程为例,分析STM32CubeMX软件(使用HAL库)是如何对外设进行初始化的。在开始分析之前,需要先回顾一下C语言中关于结构体的相关内容。C语言允许定义构造类型的数据结构,它是由若干个成员组成的,每一个成员的数据类型可以不同,典型的结构体定义方法如下:

        struct  date
        {
          int month;
          int day;
          int year;
        };

上面的代码定义了一个结构体,其类型名是“date”,由三个int型成员构成。结构体类型定义完成后,与其他系统定义的类型(如int型)一样,仅仅是多了一种数据类型而已,并不在内存中占用固定的存储空间。然而在声明了该类型的变量之后,就会在内存中为该变量分配存储单元。声明结构体变量的方法可参考以下代码:

        struct  date   date1;

我们也可以在定义结构体的同时声明结构体变量,具体方法如下:

        struct  date
        {
          int month;
          int day;
          int year;
        } date1;

与上面例子相似的是,在HAL库的stm32f0xx_hal_gpio.h头文件中同样定义了一个结构体类型,具体如下:

        typedef struct
        {
          uint32_t Pin;          /* ! < Specifies the GPIO pins to be configured.
                                    This parameter can be any value of @ref GPIO_pins */

          uint32_t Mode;         /* ! < Specifies the operating mode for the selected pins.
                                      This parameter can be a value of @ref GPIO_mode */

          uint32_t Pull;         /* ! < Specifies the Pull-up or Pull-Down activation for the
    selected pins.
                                      This parameter can be a value of @ref GPIO_pull */

          uint32_t Speed;        /* ! < Specifies the speed for the selected pins.
                                      This parameter can be a value of @ref GPIO_speed */

          uint32_t Alternate;   /* ! < Peripheral to be connected to the selected pins
                                      This parameter can be a value of @ref GPIOEx_Alternate_
    function_selection */
        }GPIO_InitTypeDef;

以上代码在定义结构体类型时,使用了C语言的关键词“typedef”,这里我们还需要对这一关键词进行解读。“typedef”的作用是为一种已经存在的数据类型重新定义一个新的名字。这里所讲的数据类型包括C语言中默认的系统数据类型(如int、char型等),也包括自定义的数据类型(如struct、enum型等)。“typedef”最简单的使用方法如下:

        typedef  int  INTEGER;

在上述代码中,使用INTEGER代替int作为整型变量的类型名,这样做的好处是让代码更加清晰直观,之后我们就可以使用以下方法来声明整型变量了:

        INTEGER   num1, num2;

同样,使用“typedef”也可以为结构体类型重新命名,以使程序更加具有可读性。具体可以参考如下代码:

        typedef  struct  date
        {
          int month;
          int day;
          int year;
        }DateStruct;

上述语句实际上是完成了两个步骤的操作过程。第一步是定义了一个新的结构体类型,“date”虽然是结构体类型名,但它需要与“struct”关键词一同使用来声明结构体变量。

        struct  date
        {
          int month;
          int day;
          int year;
        };

第二步是使用“typedef”为定义好的“struct date”结构体类型重新命名,称为“Date Struct”,这相当于如下代码:

        typedef  struct date   DateStruct;

之后,我们就可以使用“DateStruct”这个结构体类型名来声明结构体变量了,例如:

        DateStruct  date1;

其作用就相当于如下代码:

        struct  date  date1;

有了上面对结构体和关键词“typedef”内容的解读,我们再回过头来分析stm32f0xx_hal_gpio.h头文件中关于结构体类型的定义就十分好理解了。具体如下:

        typedef struct
        {
          uint32_t Pin;            //定义一个32位的结构体成员Pin,用于指定目标GPIO引脚
          uint32_t Mode;           //定义一个32位的结构体成员Mode,用于引脚模式配置
          uint32_t Pull;           //定义一个32位的结构体成员PuLL,用于设定引脚的上拉和下拉
          uint32_t Speed;         //定义一个32位的结构体成员Speed,用于设定引脚的速度
          uint32_t Alternate;     //定义一个32位的结构体成员Alternate,用于设定与外设连接的引脚
        }GPIO_InitTypeDef;

以上代码的作用就是定义了一个“GPIO_InitTypeDef”类型的结构体变量,其成员有5个,均为32位长,成员名称分别是“Pin”“Mode”“Pull”“Speed”和“Alternate”。之后在main.c用户文件中,定义了一个函数用于初始化GPIO端口。具体代码如下:

        static void MX_GPIO_Init(void)
        {

          GPIO_InitTypeDef GPIO_InitStruct;              //定义结构体变量GPIO_InitStruct

          /* GPIO Ports Clock Enable */
          __HAL_RCC_GPIOC_CLK_ENABLE();                   //使能GPIOC时钟
          __HAL_RCC_GPIOF_CLK_ENABLE();                   //使能GPIOF时钟

          /* Configure GPIO pin Output Level */
          HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);  //复位PC13引脚

          /* Configure GPIO pin : PC13 */
          GPIO_InitStruct.Pin=GPIO_PIN_13;             //初始化目标引脚PC13
          GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_PP;   //推挽输出模式
          GPIO_InitStruct.Pull=GPIO_NOPULL;            //无上下拉
          GPIO_InitStruct.Speed=GPIO_SPEED_FREQ_LOW;  //低速
          HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);       //调用GPIO初始化函数

        }

上述函数中使用了如下的语句来定义“GPIO_InitTypeDef”类型的结构体变量,该变量用于给GPIO端口初始化,其名称为“GPIO_InitStruct”:

        GPIO_InitTypeDef GPIO_InitStruct;

给“GPIO_InitStruct”结构体变量赋值即可完成对端口相关寄存器的初始化操作。给结构体变量赋值的方法就是分别给结构体变量的每一个成员赋值,具体代码如下:

        GPIO_InitStruct.Pin=GPIO_PIN_13;                        //初始化目标引脚PC13
        GPIO_InitStruct.Mode=GPIO_MODE_OUTPUT_PP;              //推挽输出模式
        GPIO_InitStruct.Pull=GPIO_NOPULL;                       //无上下拉
        GPIO_InitStruct.Speed=GPIO_SPEED_FREQ_LOW;             //低速

赋值完成后,使用初始化函数来完成对GPIO端口的初始化。该函数的定义在“stm32 f0xx_hal_gpio.c”文件中,函数原型如下:

        void HAL_GPIO_Init   (
        GPIO_TypeDef *   GPIOx,
                                      GPIO_InitTypeDef *   GPIO_Init
                              )

上述函数有两个参数,其中,第一个参数是“GPIOx”,是指向“GPIO_TypeDef”结构体的指针,其值可以是GPIOA、GPIOB等;另一个参数是“GPIO_Init”,是一个指向“GPIO_InitTypeDef”结构的指针。在调用“HAL_GPIO_Init()”函数时,使用以下语句:

        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

在以上代码中,HAL库已经将GPIOC定义成了指针类型的变量,指向GPIOC的端口地址,而GPIO_InitStruct是一个结构体变量,在此需要将其地址取出,所以使用了“&GPIO_InitStruct”的写法。

通过上述对于GPIO外设初始化过程的分析,我们可以触类旁通,STM32CubeMX软件就是通过与之类似的方法对STM32F0系列微控制器的外设进行初始化的。

回顾本章,我们已经知道GPIO是微控制器最基本的外设。STM32F0系列微控制器的GPIO与其他微控制器相比还有一些较为特殊的地方,如每一个GPIO引脚都可以配置成外部中断输入端;端口A和B具有配置锁定功能,可以防止端口误动作;与外设复用的端口可以通过重映射功能将端口的复用功能转移至其他引脚上等。另外,通过端口置位/复位寄存器,可以对端口进行灵活的位操作,这会给程序的编写带来便利。至此,我们已经成功地迈出了STM32F0系列微控制器开发最重要的一步,而且似乎这一步也没有想象中那么难!