观前提醒:本文详尽分析了HAL库中GPIO配置的相关函数,包括MX_GPIO_Init()、HAL_GPIO_WritePin()、HAL_GPIO_Init()。文末附f1系列GPIO口相关寄存器说明
MX_GPIO_Init()
先看源码:
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_SET);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = LED4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED4_GPIO_Port, &GPIO_InitStruct);
}
下面我们来逐行分析代码
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
这里定义了一个名为 MX_GPIO_Init
的函数,没有输入参数,也没有返回值。同时创建了一个 GPIO_InitTypeDef
类型的结构体变量 GPIO_InitStruct
并初始化为零。
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
通过调用 __HAL_RCC_GPIOD_CLK_ENABLE()
、__HAL_RCC_GPIOC_CLK_ENABLE()
和 __HAL_RCC_GPIOA_CLK_ENABLE()
函数,使 GPIOD、GPIOC 和 GPIOA 端口的时钟被启用。
HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_SET);
这行代码将 LED4_Pin
引脚的输出电平设置为高电平(GPIO_PIN_SET)。它使用了 HAL 库提供的 HAL_GPIO_WritePin
函数。
GPIO_InitStruct.Pin = LED4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED4_GPIO_Port, &GPIO_InitStruct);
这几行代码配置了引脚 LED4_Pin
的模式、上下拉和速度。首先,将 LED4_Pin
赋值给 GPIO_InitStruct.Pin
,然后将模式设置为输出模式(GPIO_MODE_OUTPUT_PP),无上下拉(GPIO_NOPULL),低速(GPIO_SPEED_FREQ_LOW)。最后,通过调用 HAL_GPIO_Init
函数,将配置应用到 LED4_GPIO_Port
上。
总结一下,以上代码的主要功能是初始化指定的 GPIO 引脚。它启用了相应端口的时钟,设置了一个引脚的输出电平,然后配置了该引脚的模式、上下拉和速度。这样,在调用该函数后,相关的 GPIO 引脚就被正确地初始化,可以进行后续的输入输出操作。
有聪明的小伙伴又要问了:为啥上述代码先配置IO口的电平后设置IO口的模式?
答:在初始化 IO 口之前,我们需要确保 IO 口的电平是正确的,以避免在切换为输出模式后产生意外的电平变化。如果我们先设置模式再设置电平,会导致在切换为输出模式之前,IO 口的电平发生瞬时变化,从而引起不必要的干扰或错误。
HAL_GPIO_WritePin()
Talk is cheap. Show me the code
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 库中的 HAL_GPIO_WritePin
函数的实现。该函数用于设置指定 GPIO 引脚的输出电平。
让我们逐行分析代码的功能和操作:
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
这是函数的定义,接受三个参数:GPIOx
是指向 GPIO 端口的指针,GPIO_Pin
是要设置的 GPIO 引脚,PinState
是要设置的输出电平。
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
这些代码行用于参数检查,确保传入的引脚和电平参数是有效的。assert_param()
是一个断言宏,用于在调试时检查参数的合法性。
if (PinState != GPIO_PIN_RESET)
{
GPIOx->BSRR = GPIO_Pin;
}
else
{
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u;
}
这是函数的主要逻辑。如果 PinState
不等于 GPIO_PIN_RESET
,即要设置的输出电平为高电平,
那么将 GPIOx->BSRR
寄存器的对应引脚位置为 1,从而将引脚的输出电平设置为高电平。
如果 PinState
等于 GPIO_PIN_RESET
,即要设置的输出电平为低电平,那么将 GPIOx->BSRR
寄存器的对应引脚位置为 1(设置引脚为低电平),同时将 GPIOx->BSRR
寄存器的高 16 位位置为 1(清除引脚为高电平),从而将引脚的输出电平设置为低电平。
通过这样的逻辑,根据 PinState
的值,可实现对 GPIO 引脚的输出电平设置。
总结:以上代码实现了 HAL_GPIO_WritePin
函数,用于设置指定 GPIO 引脚的输出电平。通过判断 PinState
的值,将对应的 GPIO 寄存器的位设置为高或低电平,实现了输出电平的设置。
HAL_GPIO_Init()
鉴于本函数过长,源码将放置在分析之后,省略了无关紧要的部分
uint32_t position = 0x00u;
uint32_t ioposition;
uint32_t iocurrent;
uint32_t temp;
uint32_t config = 0x00u;
__IO uint32_t *configregister;
uint32_t registeroffset;
position
:用于追踪当前处理的引脚位置。在循环中递增,以处理下一个引脚。ioposition
:用于获取当前引脚的位位置。通过对position
进行移位操作得到。iocurrent
:通过将GPIO_Init->Pin
与ioposition
进行位与操作,用于检查当前引脚是否需要初始化。如果iocurrent
等于ioposition
,则说明当前引脚需要初始化。temp
:一个临时变量config
:用于存储引脚的配置值。根据不同的模式和参数,将相应的配置值写入该变量。configregister
:用于存储指向CRL
或CRH
寄存器的指针,具体取决于引脚号的范围。在后续的代码中,将使用该指针来修改寄存器的值。registeroffset
:在计算CNF
和MODE
位在CRL
或CRH
寄存器中的位置时使用的偏移量。
while (((GPIO_Init->Pin) >> position) != 0x00u)
{
/* Get the IO position */
ioposition = (0x01uL << position);
/* Get the current IO position */
iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;
if (iocurrent == ioposition)
{
/* Check the Alternate function parameters */
assert_param(IS_GPIO_AF_INSTANCE(GPIOx));
/* Based on the required mode, filling config variable with MODEy[1:0] and CNFy[3:2] corresponding bits */
switch (GPIO_Init->Mode)
{
/* If we are configuring the pin in OUTPUT push-pull mode */
case GPIO_MODE_OUTPUT_PP:
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;
break;
....
让我们逐行分析以上代码段:
while (((GPIO_Init->Pin) >> position) != 0x00u)
这是一个循环的起始。它通过判断 (GPIO_Init->Pin)
右移 position
位后的值是否不等于 0,来确定是否继续执行循环。GPIO_Init->Pin
是一个表示引脚状态的位掩码,通过右移 position
位来检查每个引脚的状态。
ioposition = (0x01uL << position);
这行代码计算了当前引脚的位置。它使用了位运算,将 1 左移 position
位,并将结果存储在 ioposition
变量中。这样可以得到一个只有第 position
位为 1,其余位为 0 的值。
iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;
这行代码获取当前引脚的状态。它将 GPIO_Init->Pin
强制转换为 uint32_t
类型,然后与 ioposition
进行按位与运算,并将结果存储在 iocurrent
变量中。这样可以判断当前引脚的状态是否为 1。
if (iocurrent == ioposition)
这是一个条件语句,用于检查当前引脚是否需要进行初始化配置。如果当前引脚的状态等于 ioposition
的值,即当前引脚的状态为 1,则进入条件判断。
assert_param(IS_GPIO_AF_INSTANCE(GPIOx));
这是一个断言宏,用于检查 GPIOx
是否是有效的 GPIO 外设实例。如果 GPIOx
不满足条件,即不是有效的 GPIO 外设实例,则会触发断言错误。
switch (GPIO_Init->Mode)
这是一个 switch
语句,根据 GPIO_Init->Mode
的值进行多重分支判断。GPIO_Init->Mode
表示当前引脚的模式。
case GPIO_MODE_OUTPUT_PP:
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;
break;
这是一个分支语句,用于处理引脚配置为输出推挽模式(GPIO_MODE_OUTPUT_PP)的情况。在这个分支中,会检查GPIO的速度参数,然后根据速度参数和相应的控制寄存器配置值计算出 config 的值。
总结一下:它们共同组成了一段循环结构,用于遍历一组引脚的状态,并根据需要进行初始化配置。在每个循环迭代中,会获取当前引脚的位置和状态,并根据状态进行条件判断和配置操作。
/* Check if the current bit belongs to first half or last half of the pin count number
in order to address CRH or CRL register*/
configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL : &GPIOx->CRH;
registeroffset = (iocurrent < GPIO_PIN_8) ? (position << 2u) : ((position - 8u) << 2u);
/* Apply the new configuration of the pin to the register */
MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));
这段代码主要用于将引脚的配置应用到相应的寄存器中。下面是对代码的详细分析:
首先,代码根据引脚编号 iocurrent
判断当前位是否属于引脚编号的前半部分(小于8)还是后半部分(大于等于8)。这是因为在寄存器中,引脚0到7的配置位于 Control Register Low (CRL),而引脚8到15的配置位于 Control Register High (CRH)。
代码使用三元条件运算符,将 configregister
设置为指向 CRL 或 CRH 寄存器的指针,具体取决于引脚编号 iocurrent
的范围。同样,使用三元条件运算符,将 registeroffset
设置为相应的寄存器偏移量。如果 iocurrent
小于8,说明引脚属于前半部分,寄存器偏移量为 position
左移2位;否则,引脚属于后半部分,寄存器偏移量为 (position - 8u)
左移2位。
接下来,代码使用 MODIFY_REG
宏将引脚的新配置应用到寄存器中。MODIFY_REG
宏用于修改寄存器的特定位域,其参数包括要修改的寄存器、要修改的位域以及要写入位域的新值。
在这里,(*configregister)
表达式解引用 configregister
指针,获取到要修改的寄存器。((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset)
表达式计算出要修改的位域的掩码,它将位域的配置值左移 registeroffset
位,并与 GPIO_CRL_MODE0
和 GPIO_CRL_CNF0
的按位或结果相乘。config << registeroffset
是要写入位域的新配置值。
通过使用 MODIFY_REG
宏,代码将新的引脚配置写入到适当的寄存器位域中,以实现引脚的配置更新。
if ((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
{
/* Enable AFIO Clock */
__HAL_RCC_AFIO_CLK_ENABLE();
temp = AFIO->EXTICR[position >> 2u];
CLEAR_BIT(temp, (0x0Fu) << (4u * (position & 0x03u)));
SET_BIT(temp, (GPIO_GET_INDEX(GPIOx)) << (4u * (position & 0x03u)));
AFIO->EXTICR[position >> 2u] = temp;
/* Enable or disable the rising trigger */
if ((GPIO_Init->Mode & RISING_EDGE) == RISING_EDGE)
{
SET_BIT(EXTI->RTSR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->RTSR, iocurrent);
}
/* Enable or disable the falling trigger */
if ((GPIO_Init->Mode & FALLING_EDGE) == FALLING_EDGE)
{
SET_BIT(EXTI->FTSR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->FTSR, iocurrent);
}
/* Configure the event mask */
if ((GPIO_Init->Mode & GPIO_MODE_EVT) == GPIO_MODE_EVT)
{
SET_BIT(EXTI->EMR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->EMR, iocurrent);
}
/* Configure the interrupt mask */
if ((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
{
SET_BIT(EXTI->IMR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->IMR, iocurrent);
}
}
}
这段代码是用于配置GPIO外部中断的相关设置。让我们逐行分析这段代码的功能:
-
首先,通过判断GPIO_Init->Mode与EXTI_MODE的按位与运算结果是否等于EXTI_MODE,来确定是否需要配置外部中断。EXTI_MODE是一个宏定义,表示外部中断模式。
-
如果需要配置外部中断,首先使能AFIO(Alternate Function I/O)时钟。AFIO是一个外设,用于管理GPIO的复用功能。
-
接下来,从AFIO的EXTICR寄存器中读取当前位置对应的值。
-
根据GPIOx(GPIO端口号)和position(GPIO引脚号)的值,计算出需要设置的值,并将其设置到temp中。
-
将temp的值写入到AFIO的EXTICR寄存器中,完成对外部中断的配置。
-
根据GPIO_Init->Mode与RISING_EDGE的按位与运算结果,判断是否需要使能上升沿触发中断。如果需要,设置EXTI的RTSR寄存器中对应的位。
-
根据GPIO_Init->Mode与FALLING_EDGE的按位与运算结果,判断是否需要使能下降沿触发中断。如果需要,设置EXTI的FTSR寄存器中对应的位。
-
根据GPIO_Init->Mode与GPIO_MODE_EVT的按位与运算结果,判断是否需要配置事件屏蔽寄存器。如果需要,设置EXTI的EMR寄存器中对应的位。
-
根据GPIO_Init->Mode与GPIO_MODE_IT的按位与运算结果,判断是否需要配置中断屏蔽寄存器。如果需要,设置EXTI的IMR寄存器中对应的位。
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
{
uint32_t position = 0x00u;
uint32_t ioposition;
uint32_t iocurrent;
uint32_t temp;
uint32_t config = 0x00u;
__IO uint32_t *configregister; /* Store the address of CRL or CRH register based on pin number */
uint32_t registeroffset; /* offset used during computation of CNF and MODE bits placement inside CRL or CRH register */
/* Check the parameters */
assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Init->Pin));
assert_param(IS_GPIO_MODE(GPIO_Init->Mode));
/* Configure the port pins */
while (((GPIO_Init->Pin) >> position) != 0x00u)
{
/* Get the IO position */
ioposition = (0x01uL << position);
/* Get the current IO position */
iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;
if (iocurrent == ioposition)
{
/* Check the Alternate function parameters */
assert_param(IS_GPIO_AF_INSTANCE(GPIOx));
/* Based on the required mode, filling config variable with MODEy[1:0] and CNFy[3:2] corresponding bits */
switch (GPIO_Init->Mode)
{
/* If we are configuring the pin in OUTPUT push-pull mode */
case GPIO_MODE_OUTPUT_PP:
/* Check the GPIO speed parameter */
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;
break;
/* If we are configuring the pin in OUTPUT open-drain mode */
case GPIO_MODE_OUTPUT_OD:
/* Check the GPIO speed parameter */
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_OD;
break;
/* If we are configuring the pin in ALTERNATE FUNCTION push-pull mode */
case GPIO_MODE_AF_PP:
/* Check the GPIO speed parameter */
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
config = GPIO_Init->Speed + GPIO_CR_CNF_AF_OUTPUT_PP;
break;
/* If we are configuring the pin in ALTERNATE FUNCTION open-drain mode */
case GPIO_MODE_AF_OD:
/* Check the GPIO speed parameter */
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
config = GPIO_Init->Speed + GPIO_CR_CNF_AF_OUTPUT_OD;
break;
/* If we are configuring the pin in INPUT (also applicable to EVENT and IT mode) */
case GPIO_MODE_INPUT:
case GPIO_MODE_IT_RISING:
case GPIO_MODE_IT_FALLING:
case GPIO_MODE_IT_RISING_FALLING:
case GPIO_MODE_EVT_RISING:
case GPIO_MODE_EVT_FALLING:
case GPIO_MODE_EVT_RISING_FALLING:
/* Check the GPIO pull parameter */
assert_param(IS_GPIO_PULL(GPIO_Init->Pull));
if (GPIO_Init->Pull == GPIO_NOPULL)
{
config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_FLOATING;
}
else if (GPIO_Init->Pull == GPIO_PULLUP)
{
config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_PU_PD;
/* Set the corresponding ODR bit */
GPIOx->BSRR = ioposition;
}
else /* GPIO_PULLDOWN */
{
config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_PU_PD;
/* Reset the corresponding ODR bit */
GPIOx->BRR = ioposition;
}
break;
/* If we are configuring the pin in INPUT analog mode */
case GPIO_MODE_ANALOG:
config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_ANALOG;
break;
/* Parameters are checked with assert_param */
default:
break;
}
/* Check if the current bit belongs to first half or last half of the pin count number
in order to address CRH or CRL register*/
configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL : &GPIOx->CRH;
registeroffset = (iocurrent < GPIO_PIN_8) ? (position << 2u) : ((position - 8u) << 2u);
/* Apply the new configuration of the pin to the register */
MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));
/*--------------------- EXTI Mode Configuration ------------------------*/
/* Configure the External Interrupt or event for the current IO */
if ((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
{
/* Enable AFIO Clock */
__HAL_RCC_AFIO_CLK_ENABLE();
temp = AFIO->EXTICR[position >> 2u];
CLEAR_BIT(temp, (0x0Fu) << (4u * (position & 0x03u)));
SET_BIT(temp, (GPIO_GET_INDEX(GPIOx)) << (4u * (position & 0x03u)));
AFIO->EXTICR[position >> 2u] = temp;
/* Enable or disable the rising trigger */
if ((GPIO_Init->Mode & RISING_EDGE) == RISING_EDGE)
{
SET_BIT(EXTI->RTSR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->RTSR, iocurrent);
}
/* Enable or disable the falling trigger */
if ((GPIO_Init->Mode & FALLING_EDGE) == FALLING_EDGE)
{
SET_BIT(EXTI->FTSR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->FTSR, iocurrent);
}
/* Configure the event mask */
if ((GPIO_Init->Mode & GPIO_MODE_EVT) == GPIO_MODE_EVT)
{
SET_BIT(EXTI->EMR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->EMR, iocurrent);
}
/* Configure the interrupt mask */
if ((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
{
SET_BIT(EXTI->IMR, iocurrent);
}
else
{
CLEAR_BIT(EXTI->IMR, iocurrent);
}
}
}
position++;
}
}
GPIO相关寄存器
端口配置寄存器
端口输入数据寄存器
端口输出数据寄存器
端口位设置/清除寄存器
端口位清除寄存器
端口配置锁定寄存器
评论(0)
您还未登录,请登录后发表或查看评论