STM32F1串口初始化流程
STM32F1HAL库串口通信流程解析
一.串口通信
1.初始化顺序
1.1.时钟初始化
-
首先进行IO口时钟初始化
HAL_UART_MspInit函数的部分代码
__HAL_RCC_USART2_CLK_ENABLE(); // 串口时钟初始化 __HAL_RCC_GPIOD_CLK_ENABLE(); // GPIO时钟初始化 -
进行串口初始化的时候要求初始化IO口原因:串口实质是作为IO口的复用,即将IO口连接到硬件外设上面去,所以要将IO口也初始化时钟
-
IO引脚复用中,IO口需要配置时钟来访问IO寄存器完成引脚配置
-
这里的串口时钟和GPIO口的挂载时钟总线可能会出现不一致的情况,但是这里并不影响实际外设的功能,比如下面为串口和GPIO的时钟挂载
-
#define __HAL_RCC_USART2_CLK_ENABLE() do { \ __IO uint32_t tmpreg; \ SET_BIT(RCC->APB1ENR, RCC_APB1ENR_USART2EN);\ /* Delay after an RCC peripheral clock enabling */\ tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_USART2EN);\ UNUSED(tmpreg); \ } while(0U) #define __HAL_RCC_GPIOD_CLK_ENABLE() do { \ __IO uint32_t tmpreg; \ SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPDEN);\ /* Delay after an RCC peripheral clock enabling */\ tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPDEN);\ UNUSED(tmpreg); \ } while(0U) -
分别挂载在APB1和APB2的时钟总线下,实际以串口外设的时钟源为准,GPIO访问寄存器配置完后就完成工作了,不会影响串口的波特率等性能
-
-
时钟挂载一般需要参考开发手册
1.2.配置复用GPIO
-
配置引脚为复用功能模式(Alternate Function Mode),并指定复用功能编号(比如AF编号)
-
同时作为一个硬件外设,通常会设置一个默认硬件,此时不需要写入寄存器去修改复用其他引脚编号
-
STM32的F1里面会有专门的复用寄存器
-
HAL_UART_MspInit函数的内部部分实现代码GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 设置IO口为复用推挽输出 这里对应TX引脚 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 这个是IO口电平反转的速度,针对波特率来设定 HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); // 将这个IO口的配置信息写入到复用寄存器里面 GPIO_InitStruct.Pin = GPIO_PIN_6; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 设置IO口为输入模式,对应RX引脚 GPIO_InitStruct.Pull = GPIO_NOPULL; // 既不上拉也不下拉 HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); // 将这个IO口的配置信息写入到复用寄存器里面 __HAL_AFIO_REMAP_USART2_ENABLE(); // F1系列需要使能专门的寄存器来进行复用 -
下面为
HAL_GPIO_Init内部实现的部分代码 -
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; } -
根据GPIO的模式来将IO口的速度以及复用引脚编号的寄存器信号写在config里面,然后将config写入寄存器
-
/* Apply the new configuration of the pin to the register */ MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));
-
-
串口配置需要 TX引脚配置推挽输出,能够能主动拉高和拉低电压
-
RX引脚需要配置不上拉以及不下拉的状态,此时才能正常通信
1.3.配置串口
MX_USART2_UART_Init函数内部部分代码
huart2.Instance = USART2;
huart2.Init.BaudRate = 1000000; // 波特率
huart2.Init.WordLength = UART_WORDLENGTH_8B; // 字节长度
huart2.Init.StopBits = UART_STOPBITS_1; // 停止位
huart2.Init.Parity = UART_PARITY_NONE; // 校验方式,这里不采取校验
huart2.Init.Mode = UART_MODE_TX_RX; // 设置串口的工作模式,同时启用接收和发送
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 设置是否启用硬件流方式
huart2.Init.OverSampling = UART_OVERSAMPLING_16; // 设置过采样方式,分8倍和16倍
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
-
过采样方式分为两种,8倍和16倍,F1只支持16倍,与8倍相比,16倍的精度较低,因为采样次数多,所以分布范围较广,容易受到干扰,而8位则不易受到干扰
- 同时8倍支持的波特率也更加高,波特率公式为:
f_CLK / (16 × USARTDIV),f_clk为时钟源,USARTDIV为分频系数,不同的分频系数对应不同的波特率,波特率也通常为8或者16的倍数,不易受干扰
- 同时8倍支持的波特率也更加高,波特率公式为:
-
硬件流相当于在通信的过程中加入应答机制,发送的时候主动提醒对方需要接收数据,但是一般配置的异步发送模式不会自动配置硬件流,且硬件流需要通信双方都开启,否则无法通信
-
开发过程一般不开启校验,仅能检测单个bit的错误,且大多数串口通信的传感器默认不开启,同时由于每次通信增加一个bit位,间接降低了性能
-
HAL_UART_Init函数的内部实现HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart) { /* Check the UART handle allocation */ if (huart == NULL) { return HAL_ERROR; } /* Check the parameters */ if (huart->Init.HwFlowCtl != UART_HWCONTROL_NONE) { /* The hardware flow control is available only for USART1, USART2 and USART3 */ assert_param(IS_UART_HWFLOW_INSTANCE(huart->Instance)); assert_param(IS_UART_HARDWARE_FLOW_CONTROL(huart->Init.HwFlowCtl)); } else { assert_param(IS_UART_INSTANCE(huart->Instance)); } assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength)); #if defined(USART_CR1_OVER8) assert_param(IS_UART_OVERSAMPLING(huart->Init.OverSampling)); // 过采样方式数据检查 #endif /* USART_CR1_OVER8 */ if (huart->gState == HAL_UART_STATE_RESET) { /* Allocate lock resource and initialize it */ huart->Lock = HAL_UNLOCKED; #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) UART_InitCallbacksToDefault(huart); if (huart->MspInitCallback == NULL) { huart->MspInitCallback = HAL_UART_MspInit; } /* Init the low level hardware */ huart->MspInitCallback(huart); #else /* Init the low level hardware : GPIO, CLOCK */ HAL_UART_MspInit(huart); #endif /* (USE_HAL_UART_REGISTER_CALLBACKS) */ } huart->gState = HAL_UART_STATE_BUSY; /* Disable the peripheral */ __HAL_UART_DISABLE(huart); /* Set the UART Communication parameters */ UART_SetConfig(huart); /* In asynchronous mode, the following bits must be kept cleared: - LINEN and CLKEN bits in the USART_CR2 register, - SCEN, HDSEL and IREN bits in the USART_CR3 register.*/ CLEAR_BIT(huart->Instance->CR2, (USART_CR2_LINEN | USART_CR2_CLKEN)); CLEAR_BIT(huart->Instance->CR3, (USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN)); /* Enable the peripheral */ __HAL_UART_ENABLE(huart); /* Initialize the UART state */ huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_READY; huart->RxState = HAL_UART_STATE_READY; huart->RxEventType = HAL_UART_RXEVENT_TC; return HAL_OK; }-
assert_param这个函数是为了检查写入寄存器数据的合理性 -
首先检查句柄是否为NULL
-
判断是否启用硬件流通信
-
然后检查参数,波特率、数据位、停止位、校验位
-
检查过采样方式是否为8倍,否则默认16倍
-
继续判断是否为首次初始化,如果首次初始化会开启锁资源的初始化
- 在内部判断是否开启中断回调,然后开始调用
HAL_UART_MspInit()进行底层硬件初始化,将GPIO的时钟和配置进行初始化,即配合这个串口的复用资源初始化 huart->gState = HAL_UART_STATE_BUSY;设置 UART 状态为 BUSY,开始初始化__HAL_UART_DISABLE(huart);在配置寄存器前先关闭 UART 外设,放置外设干扰UART_SetConfig(huart);将预设好的通信参数设置进去
- 在内部判断是否开启中断回调,然后开始调用
-
消除不适用的模式,如 LIN、同步模式、IrDA 等
-
然后使能外设
__HAL_UART_ENABLE(huart); -
huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_READY; huart->RxState = HAL_UART_STATE_READY; huart->RxEventType = HAL_UART_RXEVENT_TC;- 设置 UART 当前状态为就绪,错误码为无错误
-
-
整体的函数设计逻辑为:
MX_UARTx_UART_Init() // 应用层初始化
↓
HAL_UART_Init() // 通用外设配置(波特率/数据位/校验位等)
↓
UART_SetConfig()
↓
HAL_UART_MspInit() ← 用户实现 MCU特定的引脚/时钟配置(需用户实现)

1.4.串口大体原理
-
异步串行通信协议
-
异步传输:无需时钟信号同步
-
点对点通信:仅需TX(发送)、RX(接收)两根线
-
帧结构传输:数据以固定格式的帧为单位传输
-
波特率同步:通信双方需约定相同的传输速率
初始化流程里面不包含中断,DMA的串口传输初始化
这里是作者对于串口在HAL库内部的大体初始化流程的一些认识,适用于新手初次学会调用API,想要了解底层驱动的初始化,同时文章内容可能会有错误,不足之处,望指正
更多推荐

所有评论(0)