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的倍数,不易受干扰
  • 硬件流相当于在通信的过程中加入应答机制,发送的时候主动提醒对方需要接收数据,但是一般配置的异步发送模式不会自动配置硬件流,且硬件流需要通信双方都开启,否则无法通信

  • 开发过程一般不开启校验,仅能检测单个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,想要了解底层驱动的初始化,同时文章内容可能会有错误,不足之处,望指正

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐