目录
[1、GPIO Init structure definition](#1、GPIO Init structure definition)
[1、GPIO Init structure definition](#1、GPIO Init structure definition)
一、硬件连接与基本原理
PB5和PE5,输出高点平则LED关闭,输出低电平LED点亮。
二、库函数工程模板
stm32f10x_gpio.c、stm32f10x_gpio.h为核心。
1、GPIO Init structure definition
cpp
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;
首先是配置,刚才前文讲了其实GPIO可以配置的就是模式和速度,除此之外还有引脚,因此可以封装为结构体。
2、时钟使能
前文未进行阐述这个部分,其实很简单的输出有速度,输出有各类时序电路其都需要时钟进行支撑,而对于前面提到的PB5和PE5,其本质也有对应的端口时钟,其挂载在APB2,这个具体在后文进行介绍。
cpp
#define RCC_APB2Periph_AFIO ((uint32_t)0x00000001)
#define RCC_APB2Periph_GPIOA ((uint32_t)0x00000004)
#define RCC_APB2Periph_GPIOB ((uint32_t)0x00000008)
#define RCC_APB2Periph_GPIOC ((uint32_t)0x00000010)
#define RCC_APB2Periph_GPIOD ((uint32_t)0x00000020)
#define RCC_APB2Periph_GPIOE ((uint32_t)0x00000040)
#define RCC_APB2Periph_GPIOF ((uint32_t)0x00000080)
#define RCC_APB2Periph_GPIOG ((uint32_t)0x00000100)
#define RCC_APB2Periph_ADC1 ((uint32_t)0x00000200)
#define RCC_APB2Periph_ADC2 ((uint32_t)0x00000400)
#define RCC_APB2Periph_TIM1 ((uint32_t)0x00000800)
#define RCC_APB2Periph_SPI1 ((uint32_t)0x00001000)
#define RCC_APB2Periph_TIM8 ((uint32_t)0x00002000)
#define RCC_APB2Periph_USART1 ((uint32_t)0x00004000)
#define RCC_APB2Periph_ADC3 ((uint32_t)0x00008000)
#define RCC_APB2Periph_TIM15 ((uint32_t)0x00010000)
#define RCC_APB2Periph_TIM16 ((uint32_t)0x00020000)
#define RCC_APB2Periph_TIM17 ((uint32_t)0x00040000)
#define RCC_APB2Periph_TIM9 ((uint32_t)0x00080000)
#define RCC_APB2Periph_TIM10 ((uint32_t)0x00100000)
#define RCC_APB2Periph_TIM11 ((uint32_t)0x00200000)
#define RCC_APB1Periph_TIM2 ((uint32_t)0x00000001)
#define RCC_APB1Periph_TIM3 ((uint32_t)0x00000002)
#define RCC_APB1Periph_TIM4 ((uint32_t)0x00000004)
#define RCC_APB1Periph_TIM5 ((uint32_t)0x00000008)
#define RCC_APB1Periph_TIM6 ((uint32_t)0x00000010)
#define RCC_APB1Periph_TIM7 ((uint32_t)0x00000020)
#define RCC_APB1Periph_TIM12 ((uint32_t)0x00000040)
#define RCC_APB1Periph_TIM13 ((uint32_t)0x00000080)
#define RCC_APB1Periph_TIM14 ((uint32_t)0x00000100)
#define RCC_APB1Periph_WWDG ((uint32_t)0x00000800)
#define RCC_APB1Periph_SPI2 ((uint32_t)0x00004000)
#define RCC_APB1Periph_SPI3 ((uint32_t)0x00008000)
#define RCC_APB1Periph_USART2 ((uint32_t)0x00020000)
#define RCC_APB1Periph_USART3 ((uint32_t)0x00040000)
#define RCC_APB1Periph_UART4 ((uint32_t)0x00080000)
#define RCC_APB1Periph_UART5 ((uint32_t)0x00100000)
#define RCC_APB1Periph_I2C1 ((uint32_t)0x00200000)
#define RCC_APB1Periph_I2C2 ((uint32_t)0x00400000)
#define RCC_APB1Periph_USB ((uint32_t)0x00800000)
#define RCC_APB1Periph_CAN1 ((uint32_t)0x02000000)
#define RCC_APB1Periph_CAN2 ((uint32_t)0x04000000)
#define RCC_APB1Periph_BKP ((uint32_t)0x08000000)
#define RCC_APB1Periph_PWR ((uint32_t)0x10000000)
#define RCC_APB1Periph_DAC ((uint32_t)0x20000000)
#define RCC_APB1Periph_CEC ((uint32_t)0x40000000)
通过如下配置即可实现开启APB2上的GPIOB和GPIOE的时钟。
cpp
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); //使能PB,PE端口时钟
3、配置输出模式、速度
cpp
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;
cpp
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
可以看到在库函数已进行了响应的封装,具有了各类的配置。
4、初始化
可以看到在GPIO_Init(GPIOB, &GPIO_InitStructure)前面是对PB进行了配置,后面配置PE5的时候,结构体本质上只有IO、速度、模式的区别,在均相同的时候,直接初始化即可,修改好GPIOB、GPIOE即可。
cpp
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); //使能PB,PE端口时钟
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; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //LED1-->PE.5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOE, &GPIO_InitStructure); //根据设定参数初始化GPIOE.5
例如可以修改为:
cpp
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); //使能PB,PE端口时钟
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; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5
GPIO_Init(GPIOE, &GPIO_InitStructure); //根据设定参数初始化GPIOE.5
5、设定高低电平
前文提到,本质上要设定BSRR寄存器和BRR寄存器,这个部分库函数也进行了相关的封装。
cpp
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BSRR = GPIO_Pin;
}
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Pin));
GPIOx->BRR = GPIO_Pin;
}
三、库函数API
1、API简介
API(应用程序编程接口)是软件系统中不同部分之间通信的一套规则。它定义了请求的格式、传输方式、数据结构和操作规则,使得不同的软件应用能够相互交互和数据交换。
因此如何设计前文的初始化使得用户层可以不用管底层配置,从而直接调用很重要。
2、初始化封装
cpp
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE); //使能PB,PE端口时钟
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; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOB.5
GPIO_SetBits(GPIOB,GPIO_Pin_5); //PB.5 输出高
GPIO_Init(GPIOE, &GPIO_InitStructure); //推挽输出 ,IO口速度为50MHz
GPIO_SetBits(GPIOE,GPIO_Pin_5); //PE.5 输出高
}
可以看到封装为LED_Init,用户侧基本上直接调用即可。
cpp
LED_Init(); //初始化与LED连接的硬件接口
3、输出高低电平封装
除了初始化一次性配置外,对于GPIO输出高低电平肯定是要进行重复的操作的。直接看GPIO_SetBits、GPIO_ResetBits本质上还是官方提供的函数名,不适合用户侧去直观使用,可以进行额外封装。封装后几乎隔离了PB5的概念。
cpp
void LED0_HIGH()
{
GPIO_SetBits(GPIOB,GPIO_Pin_5);
}
void LED1_HIGH()
{
GPIO_SetBits(GPIOE,GPIO_Pin_5);
}
void LED0_LOW()
{
GPIO_ResetBits(GPIOB,GPIO_Pin_5);
}
void LED1_LOW()
{
GPIO_ResetBits(GPIOE,GPIO_Pin_5);
}
后续如果要添加额外内容,即可在LED_HIGH、LED_LOW内进行编写即可。
五、HAL库工程模板
stm32f1xx_hal_gpio.c、 stm32f1xx_hal_gpio.h为核心。其实HAL就是更深层次的封装,进一步隔离了BSP和用户层。
1、GPIO Init structure definition
cpp
typedef struct
{
uint32_t Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
uint32_t Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIO_mode_define */
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_define */
uint32_t Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIO_speed_define */
} GPIO_InitTypeDef;
首先是配置,刚才前文讲了其实GPIO可以配置的就是模式和速度,除此之外还有引脚,因此可以封装为结构体。相比较于库函数,其增加了Pull作为上下拉的配置。
2、时钟使能
前面提到本质是挂载在APB2总线,HAL库则不再让用户考虑这个。
cpp
__HAL_RCC_GPIOB_CLK_ENABLE(); //开启GPIOB时钟
__HAL_RCC_GPIOE_CLK_ENABLE(); //开启GPIOE时钟
3、配置输出模式、速度
可以看到不再封装在结构体了,而是直接配置为define。
cpp
#define GPIO_MODE_INPUT 0x00000000u /*!< Input Floating Mode */
#define GPIO_MODE_OUTPUT_PP 0x00000001u /*!< Output Push Pull Mode */
#define GPIO_MODE_OUTPUT_OD 0x00000011u /*!< Output Open Drain Mode */
#define GPIO_MODE_AF_PP 0x00000002u /*!< Alternate Function Push Pull Mode */
#define GPIO_MODE_AF_OD 0x00000012u /*!< Alternate Function Open Drain Mode */
#define GPIO_MODE_AF_INPUT GPIO_MODE_INPUT /*!< Alternate Function Input Mode */
cpp
#define GPIO_NOPULL 0x00000000u /*!< No Pull-up or Pull-down activation */
#define GPIO_PULLUP 0x00000001u /*!< Pull-up activation */
#define GPIO_PULLDOWN 0x00000002u /*!< Pull-down activation */
cpp
#define GPIO_SPEED_FREQ_LOW (GPIO_CRL_MODE0_1) /*!< Low speed */
#define GPIO_SPEED_FREQ_MEDIUM (GPIO_CRL_MODE0_0) /*!< Medium speed */
#define GPIO_SPEED_FREQ_HIGH (GPIO_CRL_MODE0) /*!< High speed */
可以看到在库函数已进行了相应的封装,具有了各类的配置。
4、初始化
可以看到在GPIO_Init(GPIOB, &GPIO_InitStructure)前面是对PB进行了配置,后面配置PE5的时候,结构体本质上只有IO、速度、模式的区别,在均相同的时候,直接初始化即可,修改好GPIOB、GPIOE即可。
cpp
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE(); //开启GPIOB时钟
__HAL_RCC_GPIOE_CLK_ENABLE(); //开启GPIOE时钟
GPIO_Initure.Pin=GPIO_PIN_5; //PB5
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_5; //PE5
HAL_GPIO_Init(GPIOE,&GPIO_Initure);
5、设定高低电平
前文提到,本质上要设定BSRR寄存器和BRR寄存器,这个部分HAL库函数也进行了相关的封装。
cpp
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
if (PinState != GPIO_PIN_RESET)
{
GPIOx->BSRR = GPIO_Pin;
}
else
{
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u;
}
}
不同于HAL库,这次直接使用一个函数进行了封装,且只使用BSRR寄存器,前文提到BSRR可以直接设定0和1。
除此之外还封装了一个Toggle,即如果目前GPIO为高电平,则切换为低电平;如果目前GPIO为低电平,则切换为高电平,这个也是很好用的。
cpp
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
if ((GPIOx->ODR & GPIO_Pin) != 0x00u)
{
GPIOx->BRR = (uint32_t)GPIO_Pin;
}
else
{
GPIOx->BSRR = (uint32_t)GPIO_Pin;
}
}
可以看到,之前提到过ODR寄存器不仅可以输出而且可以输入,相当于切换之前读取一下当前的状态。
六、HAL库API
1、初始化封装
cpp
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE(); //开启GPIOB时钟
__HAL_RCC_GPIOE_CLK_ENABLE(); //开启GPIOE时钟
GPIO_Initure.Pin=GPIO_PIN_5; //PB5
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_5; //PE5
HAL_GPIO_Init(GPIOE,&GPIO_Initure);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET); //PB5置1,默认初始化后灯灭
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET); //PE5置1,默认初始化后灯灭
}
可以看到封装为LED_Init,用户侧基本上直接调用即可。
cpp
LED_Init(); //初始化与LED连接的硬件接口
2、输出高低电平封装
除了初始化一次性配置外,对于GPIO输出高低电平肯定是要进行重复的操作的。直接看GPIO_SetBits、GPIO_ResetBits本质上还是官方提供的函数名,不适合用户侧去直观使用,可以进行额外封装。封装后几乎隔离了PB5的概念。
cpp
void LED0_HIGH()
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);
}
void LED1_HIGH()
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET);
}
void LED0_LOW()
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET);
}
void LED1_LOW()
{
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_RESET);
}
后续如果要添加额外内容,即可在LED_HIGH、LED_LOW内进行编写即可。
七、用户侧
1、库函数
cpp
int main(void)
{
delay_init(); //延时函数初始化
LED_Init(); //初始化与LED连接的硬件接口
while(1)
{
LED0_HIGH();
LED1_LOW();
delay_ms(300); //延时300ms
LED0_LOW();
LED1_HIGH();
delay_ms(300); //延时300ms
}
}
delay_init()和delay_ms()先不进行介绍,可以看到经过封装后,用户侧非常好进行操作修改。
2、HAL库
cpp
int main(void)
{
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M
delay_init(72); //初始化延时函数
LED_Init(); //初始化LED
while(1)
{
LED0_HIGH(); //LED0对应引脚PB5拉低,亮,等同于LED0(0)
LED1_HIGH(); //LED1对应引脚PE5拉高,灭,等同于LED1(1)
delay_ms(500); //延时500ms
LED0_LOW(); //LED0对应引脚PB5拉高,灭,等同于LED0(1)
LED1_LOW(); //LED1对应引脚PE5拉低,亮,等同于LED1(0)
delay_ms(500); //延时500ms
}
}
HAL_Init、Stm32_Clock_Init、delay_init以后进行介绍。
3、用户
对于用户而言,不管使用标准库还是HAL库,都是LED_Init()初始化LED,LED_HIGH、LED_LOW设定LED的高低电平状态,非常非常方便。这就是API封装的魅力所在。