本文主要是在学习STM32开发过程中记录一些学习过程,日后可以经常查看。
1 GPIO概述
GPIO是STM32最基本的外设之一。
GPIO是通用输入输出端口(General-purpose input/output)的英文简写。
STM32单片机的GPIO被分为很多组,每组有16个引脚,不同型号的MCU的GPIO个数是不同的,比如STM32F103C8T6只有PA、PB以及个别PC引脚而STM32F103ZET6拥有PA~PG的全部112个引脚。
所有的GPIO都有基本的输入输出功能,同时GPIO还可以作为其它的外设功能引脚。
作为STM32最基本的外设,GPIO最基本的输出功能是由STM32控制 引脚输出高低电平,比如可以把GPIO接LED灯来控制其亮灭。
GPIO最基本的输入功能是检测外部电平变化,比如把GPIO引脚连接到按键电路,通过电平的高低变化来识别按键是否被按下。
2 GPIO工作模式
STM32的GPIO共有8种工作模式,分别是:
模拟输入、上拉输入、下拉输入、浮空输入
推挽输出、开漏输出、推挽复用输出、开漏复用输出
注意:
推挽输出可以输出强高低电平(高电平为3.3V),一般用来连接数字器件。在STM32的应用中,除了必须用开漏模式的场合,我们都习惯使用推挽输出模式。
开漏输出只可输出强低电平,高电平需要靠外部电阻拉高。输出端相当于三极管的集电极;要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内)。开漏输出一般应用在I2C、SMBUS通讯等需要“线与”功能的总线电路中。除此之外,还用在电平不匹配的场合,如需要输出5伏的高电平,就可以在外部接一个上拉电阻,上拉电源为5伏,并且把GPIO设置为开漏模式,当输出高阻态时,由上拉电阻和电源向外输出5伏电平。
3 GPIO复用和重映射
为了最大限度的利用端口资源,STM32的大部分端口都具有复用功能。
所谓复用,就是一些端口不仅仅可以做为通用IO口,还可以复用为一些外设引脚,比如PA9,PA10可以复用为STM32的串口1引脚。
为了方便布线 ,STM32配有端口重映射功能,所谓重映射就是可以把某些功能引脚映射到其他引脚。比如串口1默认引脚是PA9,PA10可以通过配置重映射映射到PB6,PB7。
4 GPIO寄存器
STM32的每组GPIO都包含7个寄存器,分别是:
- GPIOx_CRL:端口配置低寄存器
- GPIOx_CRH:端口配置高寄存器
- GPIOx_IDR:端口输入寄存器
- GPIOx_ODR:端口输出寄存器
- GPIOx_BSRR:端口位设置/清除寄存器
- GPIOx_BRR :端口位清除寄存器
- GPIOx_LCKR:端口配置锁存寄存器
每个I/O端口位可以自由编程,然而I/O端口寄存器必须按32位字被访问(不允许半字或字节访问)
5 GPIO库函数编程
1> 初始化函数
//stm32l4xx_hal_gpio.h
/**
* @brief GPIO Init structure definition
*/
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;
/* Initialization and de-initialization functions *****************************/
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin);
2> IO操作函数
//stm32l4xx_hal_gpio.h
/* IO operation functions ********/
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin);
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);
3> 例程
3.1> GPIO输出
#include "sys.h"
#include "delay.h"
#include "usart.h"
//RGB接口定义
#define LED_R(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_7,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_7,GPIO_PIN_RESET))
#define LED_R_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_7) //LED_R电平翻转
#define LED_G(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_RESET))
#define LED_G_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_8) //LED_G电平翻转
#define LED_B(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_RESET))
#define LED_B_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_9) //LED_B电平翻转
void led_init( void )
{
/*
LED-B PE9
LED-G PE8
LED-R PE7
*/
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOE_CLK_ENABLE(); //开启 GPIOE 时钟
GPIO_InitStruct.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
}
int main( void )
{
HAL_Init();
/* Configure the System clock to have a frequency of 80 MHz */
SystemClock_Config();
led_init(0);
while(1)
{
LED_R(1);
delay_ms(1000);
LED_R(0);
delay_ms(1000);
}
return 0;
}
3.2> GPIO输入
#include "sys.h"
#include "delay.h"
#include "usart.h"
#define KEY0 HAL_GPIO_ReadPin(GPIOD,GPIO_PIN_10)
#define KEY1 HAL_GPIO_ReadPin(GPIOD,GPIO_PIN_9)
#define KEY2 HAL_GPIO_ReadPin(GPIOD,GPIO_PIN_8)
#define WK_UP HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_13)
#define KEY0_PRES 1
#define KEY1_PRES 2
#define KEY2_PRES 3
#define WKUP_PRES 4
void KEY_Init( void )
{
/*
KEY0 - PD10 //低电平有效
KEY1 - PD9 //低电平有效
KEY2 - PD8 //低电平有效
WK_UP - PC13 //高电平有效
*/
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOD_CLK_ENABLE(); //使能GPIOD 时钟
__HAL_RCC_GPIOC_CLK_ENABLE(); //使能GPIOC 时钟
GPIO_InitStruct.Pin = GPIO_PIN_8| GPIO_PIN_9 | GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
/**
* @brief 按键处理函数
*
* @remark 注意此函数有响应优先级,KEY0>KEY1>KEY2>WK_UP!!
*
* @param mode 0:不支持连续按,1:支持连续按
*
* @return u8 返回按键值
* 0:没有任何按键按下,1:KEY0按下,2:KEY1按下,3:KEY2按下,4:WK_UP按下
*/
u8 KEY_Scan(u8 mode)
{
static u8 key_up = 1; //按键松开标志
if(mode == 1)
key_up = 1; //支持连按
if(key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 1))
{
delay_ms(10);
key_up = 0;
if(KEY0 == 0)
return KEY0_PRES;
else if(KEY1 == 0)
return KEY1_PRES;
else if(KEY2 == 0)
return KEY2_PRES;
else if(WK_UP == 1)
return WKUP_PRES;
}
else if(KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 0)
key_up = 1;
return 0; //无按键按下
}
int main( void )
{
HAL_Init();
/* Configure the System clock to have a frequency of 80 MHz */
SystemClock_Config();
KEY_Init();
u8 key = 0;
while(1)
{
key = KEY_Scan(0);
switch( key )
{
case KEY0_PRES:
{
printf( "key0 press\n" );
}
break;
case KEY1_PRES:
{
printf( "key1 press\n" );
}
break;
case KEY2_PRES:
{
printf( "key2 press\n" );
}
break;
case WKUP_PRES:
{
printf( "wkup press\n" );
}
break;
default:
break;
}
}
return 0;
}
3.3> GPIO外部中断
#include "sys.h"
#include "delay.h"
#include "usart.h"
/*
KEY0 - PD10
KEY1 - PD9
KEY2 - PD8
WK_UP - PC13
*/
#define KEY0 HAL_GPIO_ReadPin(GPIOD,GPIO_PIN_10)
#define KEY1 HAL_GPIO_ReadPin(GPIOD,GPIO_PIN_9)
#define KEY2 HAL_GPIO_ReadPin(GPIOD,GPIO_PIN_8)
#define WK_UP HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_13)
#define KEY0_PRES 1
#define KEY1_PRES 2
#define KEY2_PRES 3
#define WKUP_PRES 4
/**
* @brief 外部中断初始化函数
*
* @param void
*
* @return void
*/
void EXTI_Init( void )
{
/*
KEY0 - PD10
KEY1 - PD9
KEY2 - PD8
WK_UP - PC13
*/
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOC_CLK_ENABLE(); //使能GPIO_C时钟
__HAL_RCC_GPIOD_CLK_ENABLE(); //使能GPIO_D时钟
GPIO_Initure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;
GPIO_Initure.Mode = GPIO_MODE_IT_FALLING; //下降沿触发GPIO_MODE_IT_FALLING
GPIO_Initure.Pull = GPIO_PULLUP;
HAL_GPIO_Init( GPIOD, &GPIO_Initure );
GPIO_Initure.Pin = GPIO_PIN_13;
GPIO_Initure.Mode = GPIO_MODE_IT_RISING; //下降沿触发GPIO_MODE_IT_FALLING
GPIO_Initure.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init( GPIOC, &GPIO_Initure );
//中断线8,9
HAL_NVIC_SetPriority( EXTI9_5_IRQn, 2, 0 ); //抢占优先级为2,子优先级为0
HAL_NVIC_EnableIRQ( EXTI9_5_IRQn ); //使能中断线
HAL_NVIC_SetPriority( EXTI15_10_IRQn, 2, 1 );//抢占优先级为2,子优先级为1
HAL_NVIC_EnableIRQ( EXTI15_10_IRQn ); //使能中断线
}
/**
* @brief EXTI9_5中断服务函数, 产生中断的时候进入该函数
*
* @param void
*
* @return void
*/
void EXTI9_5_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler( GPIO_PIN_8 ); //调用中断处理公用函数
HAL_GPIO_EXTI_IRQHandler( GPIO_PIN_9 );
}
/**
* @brief EXTI15_10中断服务函数, 产生中断的时候进入该函数
*
* @param void
*
* @return void
*/
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);//调用中断处理公用函数
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);//调用中断处理公用函数
}
/**
* @brief 中断服务程序中需要做的事情,在HAL库中所有的外部中断服务函数都会调用此函数
* 注:该函数时__WEEK函数
* @param GPIO_Pin 中断引脚号
*
* @return void
*/
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
delay_ms(50); //消抖
switch(GPIO_Pin)
{
case GPIO_PIN_13:
if( WK_UP == 1)
printf("WK_KEY\n");
break;
case GPIO_PIN_8:
if( KEY2 == 0)
printf("KEY_2\n");
break;
case GPIO_PIN_9:
if( KEY1 == 0)
printf("KEY_1\n");
break;
case GPIO_PIN_10:
if( KEY0 == 0)
printf("KEY_0\n");
break;
default:
break;
}
}
int main( void )
{
HAL_Init();
/* Configure the System clock to have a frequency of 80 MHz */
SystemClock_Config();
EXTI_Init();
while (1)
{
printf("OK\r\n"); //打印OK提示程序运行
delay_ms(1000); //每隔1s打印一次
}
}
参考文档: