串口的使用

串口使用(HAL库)

串口的初始化通过cube MX可以直接生成,可以减轻很多工作。在使用串口之前根据引脚图选择正确的串口引脚进行复用。如下所示:

这里选择串口2,也就是PA2作为TX,PA3作为RX。并连接好引脚。单片机的TX连其他设备的RX,RX连其他设备的TX.

1、初始化串口

初始化串口本身这个设备,设置波特率,数据单元长度等。

c 复制代码
void MX_USART2_UART_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  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;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */

}

2、初始化引脚

以下是cube MX生成的代码,可以看到初始化了串口和引脚的时钟。初始化引脚的复用以及输入。

c 复制代码
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART2)
  {
  /* USER CODE BEGIN USART2_MspInit 0 */
    //串口的时钟初始化
  /* USER CODE END USART2_MspInit 0 */
    /* USART2 clock enable */
    __HAL_RCC_USART2_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART2 GPIO Configuration
    PA2     ------> USART2_TX
    PA3     ------> USART2_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART2 interrupt Init */
    HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART2_IRQn);
  /* USER CODE BEGIN USART2_MspInit 1 */

  /* USER CODE END USART2_MspInit 1 */
  }
}

这个函数生成后没有看到在main中调用,实际上在串口HAL_UART_Init这个函数执行的时候就已经调用了。

3、中断优先级和使能中断

从上面的最后的代码中,对串口的中断优先级进行了设置,并使能中断。

c 复制代码
/* USART2 interrupt Init */
    HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART2_IRQn);
  /* USER CODE BEGIN USART2_MspInit 1 */

  /* USER CODE END USART2_MspInit 1 */

4、 接收中断回调函数

如下所示,串口的中断函数可以通过重新改写以下的函数实现。

实际上如果使用cube MX生成代码,在stm32f1xx_it.c文件中已经定义好了。我们可以直接吧逻辑写在这个函数中,但是在HAL库中其实早已经定义了弱函数,俗称"钩子"。我们可以重新定义这个函数来实现我们的客制化。HAL_UART_IRQHandler执行的后面会调用这个钩子。

如下所示重新钩子:

c 复制代码
/*
*所有串口的钩子都是一个,所以要判断是哪一个串口
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
  //接收一个字节的数据
  if(huart->Instance == USART2){
    if(rx_len>=128 || rx_buffer[rx_len-1]=='\n'){
      rx_buffer[rx_len]='\0';
    HAL_UART_Transmit(huart,(uint8_t *)rx_buffer,rx_len,500);
    rx_len=0;
    }
     HAL_UART_Receive_IT(huart,(uint8_t *)&rx_buffer[rx_len++],1);
  }
}

这个HAL_UART_Receive_IT函数在主文件中至少要先调用一次,后面才能接收到中断,在触发中断之后又要调用这个函数接收下一次的数据。,如下所示:是我在main中第一次调用

5、HAL库相关函数具体实现分析

5.1 HAL_UART_Transmit函数

c 复制代码
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  const uint8_t  *pdata8bits;
  const uint16_t *pdata16bits;
  uint32_t tickstart = 0U;

  /* Check that a Tx process is not already ongoing */
  if (huart->gState == HAL_UART_STATE_READY)  //查看外设是否已经都是初始化了的
  {
    if ((pData == NULL) || (Size == 0U))//传输的数据是否有效
    {
      return  HAL_ERROR;
    }

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;//设置标志位为可以传输

    /* Init tickstart for timeout management */
    tickstart = HAL_GetTick();//获取当前时间戳,用来计算超时

    huart->TxXferSize = Size;//发送的字节
    huart->TxXferCount = Size;//现在剩余的需要发送的字节

    /* In case of 9bits/No Parity transfer, pData needs to be handled as a uint16_t pointer 
    *这种情况的最后一个比特用来区分地址或者数据,一般用于多机通讯,常用的是8比特的情况
    */
    if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
    {
      pdata8bits  = NULL;
      pdata16bits = (const uint16_t *) pData; //不同的数据长度需要使用不同的类型
    }
    else
    {
      pdata8bits  = pData;
      pdata16bits = NULL;
    }

    /*如果发送数据不是为0就开始发送数据 */
    while (huart->TxXferCount > 0U)
    {
      if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)//发送的数据寄存器为空
      {
        huart->gState = HAL_UART_STATE_READY;

        return HAL_TIMEOUT;
      }
      if (pdata8bits == NULL)//9bit的方式将数据放入数据寄存器
      {
        huart->Instance->DR = (uint16_t)(*pdata16bits & 0x01FFU);
        pdata16bits++;
      }
      else //通过8比特的方式放入寄存器
      {
        huart->Instance->DR = (uint8_t)(*pdata8bits & 0xFFU);
        pdata8bits++;
      }
      huart->TxXferCount--;
    }

    /*这里硬件发现TXE设置之后没有再向数据寄存器中写入内容,于是就判定已经写完,设置TC为1表示发送完成 */
    if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)//发送完成寄存器标志位设置
    {
      huart->gState = HAL_UART_STATE_READY;

      return HAL_TIMEOUT;
    }

    /* At end of Tx process, restore huart->gState to Ready */
    huart->gState = HAL_UART_STATE_READY;

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

5.2 HAL_UART_Receive_IT

c 复制代码
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* Set Reception type to Standard reception */
    huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;//标准接收方式

    return (UART_Start_Receive_IT(huart, pData, Size));
  }
  else  
  {
    return HAL_BUSY;
  }
}

接着调用UART_Start_Receive_IT继续使能中断

c 复制代码
HAL_StatusTypeDef UART_Start_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  huart->pRxBuffPtr = pData;
  huart->RxXferSize = Size;
  huart->RxXferCount = Size;

  huart->ErrorCode = HAL_UART_ERROR_NONE;
  huart->RxState = HAL_UART_STATE_BUSY_RX;

  if (huart->Init.Parity != UART_PARITY_NONE)
  {
    /* Enable the UART Parity Error Interrupt */
    __HAL_UART_ENABLE_IT(huart, UART_IT_PE);
  }

  /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
  __HAL_UART_ENABLE_IT(huart, UART_IT_ERR);

  /* Enable the UART Data Register not empty Interrupt */
  __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); //使能寄存器写入中断

  return HAL_OK;
}

当中断产生的时候,通过异常向量表对应的函数,如下所示:

c 复制代码
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */

  /* USER CODE END USART2_IRQn 1 */
}

HAL_UART_IRQHanler函数:

c 复制代码
oid HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
  uint32_t isrflags   = READ_REG(huart->Instance->SR); //读取状态寄存器和控制寄存器
  uint32_t cr1its     = READ_REG(huart->Instance->CR1);
  uint32_t cr3its     = READ_REG(huart->Instance->CR3);
  uint32_t errorflags = 0x00U;
  uint32_t dmarequest = 0x00U;

  /* If no error occurs */
  errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
  if (errorflags == RESET) //没有错误中断产生
  {
    /* UART in mode Receiver -------------------------------------------------*/
    if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))//有接收中断并接收中断使能了
    {
      UART_Receive_IT(huart);//继续接收数据
      return;
    }
  }

继续看数据的接收,接收了寄存器中的数据并调用了钩子函数

c 复制代码
tatic HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
  uint8_t  *pdata8bits;
  uint16_t *pdata16bits;

  /* Check that a Rx process is ongoing */
  if (huart->RxState == HAL_UART_STATE_BUSY_RX)
  {
    if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
    {
      pdata8bits  = NULL;
      pdata16bits = (uint16_t *) huart->pRxBuffPtr;
      *pdata16bits = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
      huart->pRxBuffPtr += 2U;
    }
    else
    {
      pdata8bits = (uint8_t *) huart->pRxBuffPtr;
      pdata16bits  = NULL;

      if ((huart->Init.WordLength == UART_WORDLENGTH_9B) || ((huart->Init.WordLength == UART_WORDLENGTH_8B) && (huart->Init.Parity == UART_PARITY_NONE)))
      {
        *pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
      }
      else
      {
        *pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
      }
      huart->pRxBuffPtr += 1U;
    }

    if (--huart->RxXferCount == 0U)
    {
      /* Disable the UART Data Register not empty Interrupt */
      __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);

      /* Disable the UART Parity Error Interrupt */
      __HAL_UART_DISABLE_IT(huart, UART_IT_PE);

      /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
      __HAL_UART_DISABLE_IT(huart, UART_IT_ERR);

      /* Rx process is completed, restore huart->RxState to Ready */
      huart->RxState = HAL_UART_STATE_READY;

      /* Initialize type of RxEvent to Transfer Complete */
      huart->RxEventType = HAL_UART_RXEVENT_TC;

      /* Check current reception Mode :
         If Reception till IDLE event has been selected : */
      if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
      {
        /* Set reception type to Standard */
        huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;

        /* Disable IDLE interrupt */
        ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);

        /* Check if IDLE flag is set */
        if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))
        {
          /* Clear IDLE flag in ISR */
          __HAL_UART_CLEAR_IDLEFLAG(huart);
        }

#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
        /*Call registered Rx Event callback*/
        huart->RxEventCallback(huart, huart->RxXferSize);
#else
        /*Call legacy weak Rx Event callback*/
        HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
      }
      else
      {
        /* Standard reception API called */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
        /*Call registered Rx complete callback*/
        huart->RxCpltCallback(huart);
#else
        /*Call legacy weak Rx complete callback*/
        HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
      }

      return HAL_OK;
    }
    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

除了以上中断方式接收数据的方式,也可以使用阻塞方式UART_receive方式或者DMA方式接收,DMA方式就是可以等数据接收指定长度或者串口空闲的时候再触发一个DMA中断去接收数据,CPU不用每一次数据都进行处理。

相关推荐
踏着七彩祥云的小丑2 小时前
嵌入式测试学习第2天:欧姆定律 + 功率计算 + 电路单位换算
单片机·嵌入式硬件
BT-BOX2 小时前
Multisim 14.3 安装与汉化指南(附下载链接)
嵌入式硬件·物联网
Hello_Embed2 小时前
串口硬件结构与三种编程方式
笔记·stm32·学习·ai编程
不断提高3 小时前
别再写 while(1) 死循环了,嵌入式开发该换个活法
c语言·嵌入式硬件·嵌入式·状态模式
LCG元3 小时前
STM32实战:基于STM32F103的智能停车场车位引导系统
stm32·单片机·嵌入式硬件
WYH2873 小时前
【STM32 串口完全指南】从轮询到中断再到 DMA,一步步教你搞定串口收发!
stm32·单片机·嵌入式硬件
hrw_embedded3 小时前
STM32单片机增加全局内存增大导致ADC数据丢失,明明两个不相干的两个部分,为什么会相互干扰?
stm32·单片机·嵌入式硬件
余生皆假期-3 小时前
YuanHub 源码分析【六】MIT 模式
笔记·单片机·嵌入式硬件