自学内容网 自学内容网

03:(手撸HAL+CubeMX)串口UART篇一


有关GPIO引脚和外部中断的基础知识请参考“寄存器开发入门教程的第9章”

本章使用到的HAL库编程接收解释如下:

/***** RCC_Exported_Functions_Group1 *****/
1.HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);
•功能:串口异步通信初始化配置。比如配置指定串口USART1,数据帧格式等。
•参数:
 •huart:需要配置参数的结构体指针
 •UART_HandleTypeDef:参数配置的总的结构体,包含以下的元素:
 •USART_TypeDef *Instance:USART_TypeDef 结构体指针变量,USART_TypeDef 里面的元素是USART的寄存器。
  所以Instance串口的基地址,而基地址为(USART1_BASE/USART1_BASE/USART3_BASE)。
  而宏定义 #define USART1  ((USART_TypeDef *)USART1_BASE)。所以Instance取值如下:
  USART1/USART2/USART3
 •UART_InitTypeDef Init 参数配置的结构体,包含以下元素:
 uint32_t BaudRate :串口波特率
 uint32_t WordLength:有效的数据位,可选如下选项:
 UART_WORDLENGTH_8B:8位数据位
 UART_WORDLENGTH_9B:9位数据位
 uint32_t StopBits:有效的停止位,可选如下选项:
 UART_STOPBITS_1:1位停止位
 UART_STOPBITS_2:2位停止位
 uint32_t Parity:奇偶校验位:可选如下选项:
 UART_PARITY_NONE:无校验位
 UART_PARITY_EVEN:偶校验位
 UART_PARITY_ODD :奇校验位
 uint32_t Mode:串口的模式,可选如下选项:
 UART_MODE_RX   :仅接受
 UART_MODE_TX   :仅发送
 UART_MODE_TX_RX:发送接收
 uint32_t HwFlowCtl:硬件流控,可选如下选项;
 UART_HWCONTROL_NONE    :不使用硬件流控
 UART_HWCONTROL_RTS     :发送方硬件流控
 UART_HWCONTROL_CTS     :接收方硬件流控
 UART_HWCONTROL_RTS_CTS 
 uint32_t OverSampling:设置采样参数,一般在STM32F100xx中使用
 
 下面参数是HAL库内部设置,我们不需要自己设置:
  •uint8_t *pTxBuffPtr:发送的数据缓冲区的指针
  •uint16_t TxXferSize:需要被发送的数据的总数
  •uint16_t TxXferCount:需要被发送的数据的剩余个数。TxXferSize - TxXferCount:表示已经发送了多少个数据
  •uint8_t *pRxBuffPtr:接收的数据缓冲区的指针
  •uint16_t RxXferSize:需要被接收的数据总数
  •uint16_t RxXferCount:需要被接收的数据的剩余个数。RxXferSize - RxXferCount:表示已经接收了多少个数据
  •HAL_LockTypeDef Lock:(串口当前锁定状态),有如下的2个状态
     HAL_UNLOCKED = 0x00U:未被锁定(数值0)
     HAL_LOCKED   = 0x01U:被锁定(数值1)
  •HAL_UART_StateTypeDef gState:指示当前全局/发送/接收状态,有如下的状态
             HAL_UART_STATE_RESET     = 0x00U:尚未初始化
                                  HAL_UART_STATE_READY     = 0x20U:初始化并准备使用
                                   HAL_UART_STATE_BUSY      = 0x24U:忙
                                   HAL_UART_STATE_BUSY_TX   = 0x21U:发送忙
                                   HAL_UART_STATE_BUSY_RX   = 0x22U:接收满
                                   HAL_UART_STATE_BUSY_TX_RX= 0x23U:发送接收忙
                                   HAL_UART_STATE_TIMEOUT   = 0xA0U:超时
                                   HAL_UART_STATE_ERROR     = 0xE0U:错误
                     •HAL_UART_StateTypeDef RxState:指示当前接收状态,有如下的状态
                        HAL_UART_STATE_RESET
                        HAL_UART_STATE_READY
                        HAL_UART_STATE_BUSY_RX
                     •uint32_t ErrorCode:指示当编码状态,有如下的状态 
•返回值:
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
【注意】:此函数中会调用HAL_UART_MspInit(huart)这个函数。用户重写这个函数用于配置串口的引脚端口号
  引脚输入/输出模式,等等。
 
2.HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);
  •功能:串口半双工异步通信配初始化配置。比如配置指定串口USART1,数据帧格式等。
  •参数:
  同上
3.HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  •功能:发送数据(阻塞方式)。将pData指针指向的缓冲区数据,在设定的Timeout时间内,发送Size字节数据。
  •参数:
  •huart:调用HAL_UART_Init()配置好的结构体的指针。
  •pData:需要被发送的数据的缓冲区空间的头指针
  •Size:需要被发送的数据个数
  •Timeout:规定超时时间,若还没有发送Size个数据时,数据卡在DR寄存器里面
    导致TXE置0,则等待Timeout(ms),若等待超过了Timeout(ms),则停止发送,返回HAL_TIMEOUT  
  •返回值:
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
  
4.HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  •功能:接收数据(阻塞方式)。在设定的Timeout时间内,接收Size字节数据,保存到pData指针指向的缓冲区
  •参数:
  •huart:调用HAL_UART_Init()配置好的结构体的指针。
  •pData:用于保存接收到数据的缓冲区的头指针
  •Size:需要被接收的数据个数
  •Timeout:规定超时时间,若还没有接收到Size个数据时,DR中没有数据来了,
    则等待Timeout(ms),若等待超过Timeout(ms),则停止接收,返回一个HAL_TIMEOUT  
  •返回值:
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
【注意】一般情况下我们使用串口接收数据不用阻塞方式,使用中断发送接收。

5.HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
  •功能:发送数据(中断方式)。将pData指针指向的缓冲区数据,发送Size字节数据
  •参数:
  •huart:调用HAL_UART_Init()配置好的结构体的指针。
  •pData:需要被发送的数据的缓冲区空间的头指针
  •Size:需要被接收的数据个数
  •返回值:
  HAL_OK       = 0x00U,
 HAL_ERROR    = 0x01U,
【注意】中断时发送和中断式接收有很多的细节,之后解释

6.HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
  •功能:接收数据(中断方式)。接收Size字节数据,保存到pData指针指向的缓冲区
  •参数:
  •huart:调用HAL_UART_Init()配置好的结构体的指针。
  •pData:用于保存接收到数据的缓冲区的头指针
  •Size:需要被接收的数据个数
  •返回值:
  HAL_OK       = 0x00U,
 HAL_ERROR    = 0x01U,
【注意】中断时发送和中断式接收有很多的细节,之后解释

7.void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
  •功能:串口中断服务器总函数函数,串口的所有中断请求,都会执行这个函数。
   这个函数里面有很多的中断分函数
  •参数:
  •huart:调用HAL_UART_Init()配置好的结构体的指针。
  
8.void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
  •功能:发送数据完成的中断服务函数
  •参数:
  •huart:调用HAL_UART_Init()配置好的结构体的指针。
  
9.void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
  •功能:接收数据完成的中断服务函数
  •参数:
  •huart:调用HAL_UART_Init()配置好的结构体的指针。
  1. 对HAL库串口中断服务函数的详解:
    在这里插入图片描述

  2. 接收中断:
    ①如图: CPU执行HAL_UART_Receive_IT(&huart, Receive_Data, 10)这个函数代表:打开串口接收数据中断仅此而已。如下图所示:
    在这里插入图片描述
    而CPU将DR数据寄存器中的数据搬运到Receive_Data里面操作是在UART_Receive_IT(huart)函数
    当CPU执行了HAL_UART_Receive_IT(&huart, Receive_Data, 10) 函数,串口的DR寄存器一旦有数据时,串口的RXNE(读数据寄存器非空)会被置1。触发数据接收中断请求。然后CPU就会去执行中断服务总函数,当执行到中断服务分函数UART_Receive_IT(huart)时,CPU将DR中的数据搬运到Receive_Data数组中。如下图所示:
    在这里插入图片描述
    如上图所示:一旦有DR中有1个数据时,CPU就会执行USART1_IRQHandler——>HAL_UART_IRQHandler(&huart1)——>UART_Receive_IT(huart1)函数,将串口中数据搬运到Receive_Data数组里面,然后让RxXferCount减1。所以每搬运1个数据,RxXferCount就会减1。当搬运完最后1个数据时,RxXferCount会减为0,然后CPU就会进入if语句里面去执行关闭串口接收中断。然后执行全部接收完成回调函数。若此时串口的DR还有数据,CPU也不会在进行将DR中的数据搬运到数组里了。
    【注意】HAL_UART_Receive_IT(&huart, Receive_Data, 10)会使CPU搬运10个数据后,停止搬运。若想CPU进行进行搬运数据,则需要重新调用此函数,打开串口接收数据中断。而进行第二轮数据搬运时,CPU会将搬运的数据重新从Receive_Data数组的第0位开始存储。

  3. 发送中断
    ①如图: CPU执行了HAL_UART_Transmit_IT(&huart, Receive_Data, 10)这个函数代表着:打开串口发送数据中断仅此而已。如下图所示:
    在这里插入图片描述
    而CPU将Receive_Data中的数据搬运到DR数据寄存器里面操作是在UART_Transmit_IT(huart)函数
    当CPU执行了HAL_UART_Transmit_IT(&huart, Receive_Data, 10) ,串口的DR寄存器一旦为空,即TXE(发送数据寄存器空)会被置1。触发发送中断请求,CPU会执行中断服务总函数,当执行到中断服务分函数UART_Transmit_IT(huart)时,将Receive_Data中的数据搬运到DR数据寄存器。然后串口将数据发送出去。如下图所示:
    在这里插入图片描述 如上图所示:CPU执行了HAL_UART_Transmit_IT(&huart, Receive_Data, 10),一旦有DR中没有数据时,CPU就会执行USART1_IRQHandler——>HAL_UART_IRQHandler(&huart1)——>UART_Transmit_IT(huart)函数,将Receive_Data数组中数据搬运到串口DR数据寄存器里面,然后让TxXferCount减1。所以每搬运1个数据,TxXferCount就会减1。当搬运完最后1个数据时,TxXferCount会减为0,然后CPU就会进入if语句里面去执行关闭串口发送中断,执行打开串口发生完成中断。然后CPU执行USART1_IRQHandler——>HAL_UART_IRQHandler(&huart1)——>UART_EndTransmit_IT(huart)——>回调函数。若此时串口的DR没有数据了,CPU也不会在进行将数组的数据搬运到DR里面了。

1、阻塞式串口的发送与接收

  一般情况下不使用阻塞式的数据接收,因为接收数据是单片机被动操作,只有当DR寄存器只能有数据时,单片机去执行接收函数HAL_UART_Receive()将DR中的数据存储到数组中才有意义。这就是说这里面有个先后顺序,DR寄存器中有数据在先,然后去执行HAL_UART_Receive()将数据搬运到数组中在后。而我们根本不知道上位机何时给单片机发送数据,有从何谈起进行HAL_UART_Receive搬运数据喃?
  除非在主函数中while循环中一直执行HAL_UART_Receive()进行搬运,且其他的代码量较少。但是在项目中while循环不可能存在代码量较少的情况。当单片机去执行其他的代码时,上位机向单片机发送数据,而单片机没有即使的执行HAL_UART_Receive()进行数据的搬运,从而造成了数据的丢失。
  综上:使用串口进行数据的接收时,一般情况下使用串口的接收中断进行中断。

1. 对HAL_UART_Receive()源码详解
源码如下:

/*下面是阻塞式接收数据的源码*/
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint16_t *tmp;//定义一个指针变量tmp,在9位数据位的代码处理里面会将tmp = pData
  uint32_t tickstart = 0U;//定义一个变量tickstart 

  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)//判断当前接收状态是否初始化并准备使用,串口初始化完毕后,
  //会将huart->RxState = HAL_UART_STATE_READY
  {
    if ((pData == NULL) || (Size == 0U))//如果接收缓冲区没有被定义或者需要被接收的数据个数为0
    {
      return  HAL_ERROR;//跳出函数,不在进行搬运数据,返回一个接收错误
    }

    /* Process Locked */
    __HAL_LOCK(huart);//将串口的配置锁住

    huart->ErrorCode = HAL_UART_ERROR_NONE;//给编码状态一个无错误状态
    huart->RxState = HAL_UART_STATE_BUSY_RX;//给接收状态一个接收满状态

    /* Init tickstart for timeout managment */
    tickstart = HAL_GetTick();//获取当前的计数的数值,位于滴答定时器中断,uwTick每隔1ms进行+1

    huart->RxXferSize = Size;//将Size给RxXferSize,表示单片机需要Size个数据(即需要被接收到的数据总个数)
    huart->RxXferCount = Size;//将Size给RxXferCount,表示单片机还需要Size个数据(即还需要被接收到的数据个数)

    /* Check the remain data to be received */
    while (huart->RxXferCount > 0U)//还需要被接收到的数据个数>0(即实际接收到的数据个数还没达到需要被接收的总个数),进入while循环
    {
      huart->RxXferCount--;//还需要被接收的数据个数据减1
      if (huart->Init.WordLength == UART_WORDLENGTH_9B)//判断有效数据位是否为9位,一般为8位,所以不成立
      {
      //这里面是9位数据位的数据处理,我将其删除
      }
      else//有效数据位不是9位,一般情况下为8位
      {
        if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)//超时判断,超时了这个函数返回HAL_TIMEOUT,不等于HAL_OK,调节成立
        {
          return HAL_TIMEOUT;//退出函数,返回HAL_TIMEOUT
        }
        if (huart->Init.Parity == UART_PARITY_NONE) //判断是否为无校验位
        {
          *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);//将DR中的数据,搬运到pData数组中
        }
        else
        {
          *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
        }
      }
    }

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;//将接收状态变为初始化并准备使用,以便接收第二个数据

    /* Process Unlocked */
    __HAL_UNLOCK(huart);//对串口配置解锁

    return HAL_OK;//返回一个OK,表示在规定的超时时间内接收到需要被接收的数据个数
  }
  else//如果接收状态不是为HAL_UART_STATE_READY,即串口还没有准备好
  {
    return HAL_BUSY;//那么就返回一个忙状态
  }
}



if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)//超时判断,超时了这个函数返回HAL_TIMEOUT,不等于HAL_OK,调节成立
        {
          return HAL_TIMEOUT;//退出函数,返回HAL_TIMEOUT
        }
/*下面是接收数据超时的处理*/
static HAL_StatusTypeDef UART_WaitOnFlagUntilTimeout(UART_HandleTypeDef *huart, uint32_t Flag, FlagStatus Status, uint32_t Tickstart, uint32_t Timeout)
{
  /* Wait until flag is set */
  while ((__HAL_UART_GET_FLAG(huart, Flag) ? SET : RESET) == Status)//通过一个三元运算符进行对UART_FLAG_RXNE的判断。当DR中有数据时__HAL_UART_GET_FLAG(huart, Flag) = 1.
  //则执行SET,而SET != Status(RESET)。使用不会进入while循环中,则返回一个HAL_OK
  //若DR中一直没有数据,则__HAL_UART_GET_FLAG(huart, Flag) = 0
  //则执行RESET,而RESET == Status(RESET),则会进入while循环中
  {
    /* Check for the Timeout */
    if (Timeout != HAL_MAX_DELAY)//判断设定的时间Timeout是否为HAL_MAX_DELAY,假设设定为200ms
    {
      if ((Timeout == 0U) || ((HAL_GetTick() - Tickstart) > Timeout))//判断设定是超时时间是否为0,若为0
         //进入if里面执行下面语句,然后返回一个HAL_TIMEOUT,若不为0则进行超时判断
      //HAL_GetTick() - Tickstart表示当前的计数ms减去开始接收数据的计数ms
      //若减去的结果没有超过设定的超时时间,则不进入下面的语句,再次在while循环里面判断
      //( __HAL_UART_ GET_FLAG(huart, Flag),若DR寄存器中还是没有数据。
      //则继续进入if判断,判断当前计数ms减去开始接收数据的计数ms,若大于设置的超时时间。
      //即等待的时间已经超过了超时时间了。进入if里面执行下面的语句。最后返回一个HAL_TIMEOUT
      {
        /* Disable TXE, RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts for the interrupt process */
        CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE | USART_CR1_TXEIE));
        CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

        huart->gState  = HAL_UART_STATE_READY;
        huart->RxState = HAL_UART_STATE_READY;

        /* Process Unlocked */
        __HAL_UNLOCK(huart);

        return HAL_TIMEOUT;
      }
    }
  }
  return HAL_OK;
}

1.1、阻塞式进行数据的收发

实验:使用串口1进行阻塞式的串口数据接收和发送

①UART.c文件的代码如下:

#include "UART.h"

/**
 * 串口1的初始化 
 */
UART_HandleTypeDef huart1;
void UART1_Init(uint32_t baudrate)
{
    /* 1、开启串口1的时钟*/
    __HAL_RCC_USART1_CLK_ENABLE();
    
    /* 2、对串口1进行配置*/
    huart1.Instance = USART1;
    huart1.Init.BaudRate = baudrate;                    //波特率
    huart1.Init.WordLength = UART_WORDLENGTH_8B;        //8位数据位
    huart1.Init.Parity = UART_PARITY_NONE;              //无校验位
    huart1.Init.StopBits = UART_STOPBITS_1;             //1位停止位
    huart1.Init.Mode = UART_MODE_TX_RX;                 //收+发模式
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;        //无硬件流控
    HAL_UART_Init(&huart1);
}

/**
 * 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
    GPIO_InitTypeDef GPIO_Init;
    if(huart->Instance == USART1)//如果是USART1,则对引脚进行配置
    {
         /* 1、开启GPIOA的时钟 */
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        /* 2、对PA9进行配置,PA9 = Tx */
        GPIO_Init.Pin = GPIO_PIN_9;                 //选择PA9
        GPIO_Init.Mode = GPIO_MODE_AF_PP;           //选择复用推挽输出
        GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;     //最大输出速度50MHz
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
          
        /* 2、对PA10进行配置,PA10 = Rx */
        GPIO_Init.Pin = GPIO_PIN_10;                //选择PA10
        GPIO_Init.Mode = GPIO_MODE_AF_INPUT;        //选择输入模式
        GPIO_Init.Pull = GPIO_NOPULL;               //选择浮空输入
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
    }
}

②UART.h文件的代码如下:

#ifndef __UART_H
#define __UART_H
#include "stm32f1xx_hal.h"

extern UART_HandleTypeDef huart1;

void UART1_Init(uint32_t baudrate);

#endif

③主函数main.c文件的代码如下:

#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "UART.h"

uint8_t pData[200];//数组用来保存数据
int main(void){

    HAL_Init();
    HSE_RCC_Init();     //时钟树的初始化
    UART1_Init(115200); //串口1的初始化

    while (1)
    {
        switch(HAL_UART_Receive(&huart1, pData, 200, 200))//阻塞式接受数据,200ms内接受200字节的数据
        {
            case HAL_OK: //表示在200ms内成功接受到了20个字节的数据
                HAL_UART_Transmit(&huart1, pData, 200, 200);//将20个数据发送出去
                break;
            case HAL_TIMEOUT: //表示200ms内没有接受到200个字节数据.
                if(huart1.RxXferCount != 199)//接受到了部分的数据,RxXferCount表示剩余需要被接受的数据
                {
                    HAL_UART_Transmit(&huart1, pData, (199-huart1.RxXferCount), 200);//将接受到的部分数据发送出去
                }
                else //一个字节的数据都没有接受到
                {                      
                    //不做任何处理
                }
                break;
            case HAL_BUSY:
                break;
            case HAL_ERROR:
                break;
        }
    }
}

1.2、串口的重映射

实验:串口2和串口3的数据收发+串口1的重映射
①UART.c文件的代码如下:

#include "UART.h"

/**
 * 串口1的初始化 
 */
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;
UART_HandleTypeDef huart3;
void UART1_Init(uint32_t baudrate)
{
    /* 1、开启串口1的时钟*/
    __HAL_RCC_USART1_CLK_ENABLE();

     /* 2、对串口2进行配置*/
    huart1.Instance = USART1;
    huart1.Init.BaudRate = baudrate;                    //波特率
    huart1.Init.WordLength = UART_WORDLENGTH_8B;        //8位数据位
    huart1.Init.Parity = UART_PARITY_NONE;              //无校验位
    huart1.Init.StopBits = UART_STOPBITS_1;             //1位停止位
    huart1.Init.Mode = UART_MODE_TX_RX;                 //收+发模式
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;        //无硬件流控
    HAL_UART_Init(&huart1);
}

/**
 * 串口1的初始化 
 */
void UART2_Init(uint32_t baudrate)
{
     /* 1、开启串口2的时钟*/
    __HAL_RCC_USART2_CLK_ENABLE();
    
     /* 2、对串口2进行配置*/
    huart2.Instance = USART2;
    huart2.Init.BaudRate = baudrate;                    //波特率
    huart2.Init.WordLength = UART_WORDLENGTH_8B;        //8位数据位
    huart2.Init.Parity = UART_PARITY_NONE;              //无校验位
    huart2.Init.StopBits = UART_STOPBITS_1;             //1位停止位
    huart2.Init.Mode = UART_MODE_TX_RX;                 //收+发模式
    huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;        //无硬件流控
    HAL_UART_Init(&huart2);
}

/**
 * 串口1的初始化 
 */
void UART3_Init(uint32_t baudrate)
{
     /* 1、开启串口3的时钟*/
    __HAL_RCC_USART2_CLK_ENABLE();
    
     /* 2、对串口3进行配置*/
    huart3.Instance = USART3;
    huart3.Init.BaudRate = baudrate;                    //波特率
    huart3.Init.WordLength = UART_WORDLENGTH_8B;        //8位数据位
    huart3.Init.Parity = UART_PARITY_NONE;              //无校验位
    huart3.Init.StopBits = UART_STOPBITS_1;             //1位停止位
    huart3.Init.Mode = UART_MODE_TX_RX;                 //收+发模式
    huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;        //无硬件流控
    HAL_UART_Init(&huart3);
}

/**
 * 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
    GPIO_InitTypeDef GPIO_Init;
    if(huart->Instance == USART1)//如果是USART1,使用重映射对引脚进行配置
    {
        /* 1、开启AFIO的时钟 */
        __HAL_RCC_AFIO_CLK_ENABLE();
        /* 1、开启GPIOB的时钟 */
        __HAL_RCC_GPIOB_CLK_ENABLE();
        /* 1、开启UART1的引脚重映射 */
        __HAL_AFIO_REMAP_USART1_ENABLE();
        
        /* 2、对PB6进行配置,PB6 = Tx */
        GPIO_Init.Pin = GPIO_PIN_6;                 //选择PB6(重映射)
        GPIO_Init.Mode = GPIO_MODE_AF_PP;           //选择复用推挽输出
        GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;     //最大输出速度50MHz
        HAL_GPIO_Init(GPIOB, &GPIO_Init);
          
        /* 2、对PB7进行配置,PB7 = Rx */
        GPIO_Init.Pin = GPIO_PIN_7;                 //选择B7(重映射)
        GPIO_Init.Mode = GPIO_MODE_AF_INPUT;        //选择输入模式
        GPIO_Init.Pull = GPIO_NOPULL;               //选择浮空输入
        HAL_GPIO_Init(GPIOB, &GPIO_Init);
    }
    else if(huart->Instance == USART2)//如果是USART2,则对引脚进行配置
    {
         /* 1、开启GPIOA的时钟 */
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        /* 2、对PA2进行配置,PA2 = Tx */
        GPIO_Init.Pin = GPIO_PIN_2;                 //选择PA2
        GPIO_Init.Mode = GPIO_MODE_AF_PP;           //选择复用推挽输出
        GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;     //最大输出速度50MHz
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
          
        /* 2、对PA3进行配置,PA3 = Rx */
        GPIO_Init.Pin = GPIO_PIN_3;                 //选择PA10
        GPIO_Init.Mode = GPIO_MODE_AF_INPUT;        //选择输入模式
        GPIO_Init.Pull = GPIO_NOPULL;               //选择浮空输入
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
    }
    else if(huart->Instance == USART3)//如果是USART3,则对引脚进行配置
    {
         /* 1、开启GPIOB的时钟 */
        __HAL_RCC_GPIOB_CLK_ENABLE();
        
        /* 2、对PB10进行配置,PB10 = Tx */        
        GPIO_Init.Pin = GPIO_PIN_10;                //选择PB10
        GPIO_Init.Mode = GPIO_MODE_AF_PP;           //选择复用推挽输出
        GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;     //最大输出速度50MHz
        HAL_GPIO_Init(GPIOB, &GPIO_Init);
          
        /* 2、对PB11进行配置,PB11 = Rx */
        GPIO_Init.Pin = GPIO_PIN_11;                //选择PB11
        GPIO_Init.Mode = GPIO_MODE_AF_INPUT;        //选择输入模式
        GPIO_Init.Pull = GPIO_NOPULL;               //选择浮空输入
        HAL_GPIO_Init(GPIOB, &GPIO_Init);
    }
}

②UART.h文件的代码如下:

#ifndef __UART_H
#define __UART_H
#include "stm32f1xx_hal.h"

extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;
extern UART_HandleTypeDef huart3;

void UART1_Init(uint32_t baudrate);
void UART2_Init(uint32_t baudrate);
void UART3_Init(uint32_t baudrate);

#endif

③主函数main.c文件的代码如下:

#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "UART.h"


uint8_t pData[200];
int main(void){

    HAL_Init();
    HSE_RCC_Init();     //时钟树的初始化
    UART2_Init(115200); //串口2的初始化

    while (1)
    {
        switch(HAL_UART_Receive(&huart2, pData, 200, 200))//阻塞式接受数据,200ms内接受200字节的数据
        {
            case HAL_OK:        //表示在200ms内成功接受到了20个字节的数据
                HAL_UART_Transmit(&huart2, pData, 200, 200);//将20个数据发送出去
                break;
            case HAL_TIMEOUT:   //表示200ms内没有接受到200个字节数据.
                if(huart2.RxXferCount != 199)//接受到了部分的数据,RxXferCount表示剩余需要被接受的数据
                {
                    HAL_UART_Transmit(&huart2, pData, (200-1-huart2.RxXferCount), 200);//将接受到的部分数据发送出去
                }
                else //一个字节的数据都没有接受到
                {                      
                    //不做任何处理
                }
                break;
            case HAL_BUSY:
                break;
            case HAL_ERROR:
                break;
        }
    }
}

2、中断式串口的发送与接收

  • HAL_UART_Receive_IT源码详解
若CPU执行HAL_UART_Receive_IT(&huart1, pData, 20);

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)//先判断接收状态是否为READY,一般串口初始化完毕会将RxState状态变为READY
  {
    if ((pData == NULL) || (Size == 0U))//判断存放数据的数组是否被定义了,或者需要被搬运的是否为0
    {
      return HAL_ERROR;//是的话就返回一个HAL_ERROR,代表串口接收中断开启失败
    }

    /* Process Locked */
    __HAL_LOCK(huart);//锁住串口的配置

    huart->pRxBuffPtr = pData;//存放数据的数组的头指针pData给pRxBuffPtr变量
    huart->RxXferSize = Size;//需要搬运的总数20个RxXferSize 
    huart->RxXferCount = Size;//需要搬运的总数20个RxXferCount

    huart->ErrorCode = HAL_UART_ERROR_NONE;//让编码状态为无错误编码
    huart->RxState = HAL_UART_STATE_BUSY_RX;//数据接收状态为BUSY_RX

    /* Process Unlocked */
    __HAL_UNLOCK(huart);//解锁

    /* 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;//返回OK,代表配置完成了
  }
  else//若判断接收状态不是READY
  {
    return HAL_BUSY;//返回一个HAL_BUSY,代表串口还没准备好
  }
}

程序流程如下:
在这里插入图片描述

  • UART_Receive_IT源码详解
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
  uint16_t *tmp;

  /* Check that a Rx process is ongoing */
  if (huart->RxState == HAL_UART_STATE_BUSY_RX)//判断RxState 是否为BUSY_RX,HAL_UART_Receive_IT()会将RxState变为BUSY_RX
  {
    if (huart->Init.WordLength == UART_WORDLENGTH_9B)//9位数据位,不管他
    {
      tmp = (uint16_t *) huart->pRxBuffPtr;
      if (huart->Init.Parity == UART_PARITY_NONE)
      {
        *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
        huart->pRxBuffPtr += 2U;
      }
      else
      {
        *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
        huart->pRxBuffPtr += 1U;
      }
    }
    else
    {
      if (huart->Init.Parity == UART_PARITY_NONE)//判断是否有校验位,一般没有
      {
        *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);//若没有,就进行数据搬运
      }
      else//若有
      {
        *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);//进行这一步数据搬运
      }
    }

    if (--huart->RxXferCount == 0U)//判断搬运的总数20个是否完成
    {
      /* Disable the UART Data Register not empty Interrupt */
      __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);//总数20个搬运完成了,将关闭接受中断

      /* 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;//总数20个搬运完成了,将RxState变为READY

#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);//总数20个搬运完成了,将执行搬运完成的回调函数
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */

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

执行流程如下:
在这里插入图片描述

2.1、中断接受定长数据

实验:中断的方式接收和发送数据,
①UART.c文件的代码如下:

#include "UART.h"

/**
 * 串口1的初始化 
 */
UART_HandleTypeDef huart1;
uint8_t pData[256];
void UART1_Init(uint32_t baudrate)
{
    /* 1、开启串口1的时钟*/
    __HAL_RCC_USART1_CLK_ENABLE();

     /* 2、对串口2进行配置*/
    huart1.Instance = USART1;
    huart1.Init.BaudRate = baudrate;                    //波特率
    huart1.Init.WordLength = UART_WORDLENGTH_8B;        //8位数据位
    huart1.Init.Parity = UART_PARITY_NONE;              //无校验位
    huart1.Init.StopBits = UART_STOPBITS_1;             //1位停止位
    huart1.Init.Mode = UART_MODE_TX_RX;                 //收+发模式
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;        //无硬件流控
    HAL_UART_Init(&huart1);
    
    HAL_UART_Receive_IT(&huart1, pData, 20);            //开启接收中断,搬运了20个字节停止搬运
    
    HAL_NVIC_SetPriority(USART1_IRQn,3,0);//中断优先级
HAL_NVIC_EnableIRQ(USART1_IRQn);
}

/**
 * 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
    GPIO_InitTypeDef GPIO_Init;
    if(huart->Instance == USART1)//如果是USART1,使用重映射对引脚进行配置
    {
        /* 1、开启GPIOA的时钟 */
        __HAL_RCC_GPIOA_CLK_ENABLE();
  
        /* 2、对PA9进行配置,PA9 = Tx */
        GPIO_Init.Pin = GPIO_PIN_9;                 //选择A9
        GPIO_Init.Mode = GPIO_MODE_AF_PP;           //选择复用推挽输出
        GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;     //最大输出速度50MHz
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
          
        /* 2、对PA10进行配置,PA10 = Rx */
        GPIO_Init.Pin = GPIO_PIN_10;                 //选择PA10
        GPIO_Init.Mode = GPIO_MODE_AF_INPUT;        //选择输入模式
        GPIO_Init.Pull = GPIO_NOPULL;               //选择浮空输入
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
    }   
}

/**
 * 中断服务函数
 */
void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart1);//中断服务总函数,这个函数调用UART_Receive_IT()函数
    //HAL_UART_Receive_IT(&huart1, pData, 20)//若在这里再次开启开启接收数据中断,不会成功,因为RxState != READY
}
/**
 * 接收数据的回调函数,
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//UART_Receive_IT()会调用此函数
{
    if(huart->Instance == USART1)
    {
        HAL_UART_Transmit_IT(&huart1,pData,20); //启动发送中断,当TEX发送数据寄存器空时,就会触发中断,将数据发送出去
        HAL_UART_Receive_IT(&huart1, pData, 20); //再次开启开启接收数据中断
    }
}

②UART.h文件的代码如下:

#ifndef __UART_H
#define __UART_H
#include "stm32f1xx_hal.h"

extern UART_HandleTypeDef huart1;
void UART1_Init(uint32_t baudrate);

#endif

③主函数main.c文件的代码如下:

#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "UART.h"

int main(void)
{
    HAL_Init();
    HSE_RCC_Init();     //时钟树的初始化
    UART1_Init(115200);
    while (1)
    {
 
    }
}

上面的实验是接受20个定长的数据。存在着一些弊端:
  ①若上位机给单片机发送50个字节的数据,单片机第一轮接受20个数据存储到pData[0]~pData[19]。第二轮接受20个数据也存储到pData[0] ~pData[19],所以会造成第一轮接受到的数据覆盖丢失。
  ②那将需要搬运的总个数增大不就解决了这个问题了吗?但是若总个数设定为300 > 数组的范围256时,若上位机给单片机发送300个字节的数据时,单片机搬运前256个数据能正常的存储在数据中,若继续搬运,则后面的数据将会造成数组的越界。
  ③将总个数设定为20 <数组的范围256,若上位机第一次发送5个数据,第二次发送10个数据,若想对第二次接收到的数据进行处理也是比较困难的。

2.2、中断接受不定长的数据

①UART.c文件的代码如下:

#include "UART.h"

/**
 * 串口1的初始化 
 */
UART_HandleTypeDef huart1;
uint8_t Receive_Data = 0;
uint8_t pData[256];
uint16_t Index = 0;//定义数组下标

void UART1_Init(uint32_t baudrate)
{
    /* 1、开启串口1的时钟*/
    __HAL_RCC_USART1_CLK_ENABLE();

     /* 2、对串口2进行配置*/
    huart1.Instance = USART1;
    huart1.Init.BaudRate = baudrate;                    //波特率
    huart1.Init.WordLength = UART_WORDLENGTH_8B;        //8位数据位
    huart1.Init.Parity = UART_PARITY_NONE;              //无校验位
    huart1.Init.StopBits = UART_STOPBITS_1;             //1位停止位
    huart1.Init.Mode = UART_MODE_TX_RX;                 //收+发模式
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;        //无硬件流控
    HAL_UART_Init(&huart1);
    
    HAL_UART_Receive_IT(&huart1, &Receive_Data, 1);     //开启接收1个字节数据中断
    __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);         //使能空闲中断
    HAL_NVIC_SetPriority(USART1_IRQn,3,0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}

/**
 * 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
    GPIO_InitTypeDef GPIO_Init;
    if(huart->Instance == USART1)//如果是USART1,使用重映射对引脚进行配置
    {
        /* 1、开启GPIOA的时钟 */
        __HAL_RCC_GPIOA_CLK_ENABLE();
  
        /* 2、对PA9进行配置,PA9 = Tx */
        GPIO_Init.Pin = GPIO_PIN_9;                 //选择A9
        GPIO_Init.Mode = GPIO_MODE_AF_PP;           //选择复用推挽输出
        GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;     //最大输出速度50MHz
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
          
        /* 2、对PA10进行配置,PA10 = Rx */
        GPIO_Init.Pin = GPIO_PIN_10;                 //选择PA10
        GPIO_Init.Mode = GPIO_MODE_AF_INPUT;        //选择输入模式
        GPIO_Init.Pull = GPIO_NOPULL;               //选择浮空输入
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
    }
    
}

/**
 * 中断服务函数
 */
void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart1);//这个函数调用UART_Receive_IT()函数
    
    /* 对每一轮的数进行处理*/
    if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))//若空闲置位了
    {
        //清除中断标志位
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);
        //对数据处理,将第一轮接受到的数据发送出去
        HAL_UART_Transmit(&huart1,pData,Index,200);
        Index = 0;
    }
}
/**
 * 搬运数据完成的  的回调函数,
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//UART_Receive_IT()会调用此函数
{
    if(huart->Instance == USART1)
    {
        if(Index > 256)
        {
           Index = 0;
        }
        pData[Index++] = Receive_Data;//将Receive_Data中的数据保存到pData数组中
        HAL_UART_Receive_IT(&huart1, &Receive_Data, 1); //开启接收数据中断
    }
}

②主函数main.c文件的代码如下:

#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "UART.h"



int main(void){

    HAL_Init();
    HSE_RCC_Init();     //时钟树的初始化
    UART1_Init(115200);
    while (1)
    {
 
    }
}

在这里插入图片描述上位机向串口每发送一轮数据,这轮数据都是从数组的pData[0]进行存储。这样就方便操作数据

当然我们也可以不使用HAL_UART_Receive_IT(&huart1, &Receive_Data, 1);使能中断,使用__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);直接使能接收中断,在中断服务总函数里面自定义操作搬运数据,如下:

①UART.c文件的代码如下:

#include "UART.h"

/**
 * 串口1的初始化 
 */
UART_HandleTypeDef huart1;
uint8_t pData[256];
uint16_t Index = 0;//定义数组下标
void UART1_Init(uint32_t baudrate)
{
    /* 1、开启串口1的时钟*/
    __HAL_RCC_USART1_CLK_ENABLE();

     /* 2、对串口2进行配置*/
    huart1.Instance = USART1;
    huart1.Init.BaudRate = baudrate;                    //波特率
    huart1.Init.WordLength = UART_WORDLENGTH_8B;        //8位数据位
    huart1.Init.Parity = UART_PARITY_NONE;              //无校验位
    huart1.Init.StopBits = UART_STOPBITS_1;             //1位停止位
    huart1.Init.Mode = UART_MODE_TX_RX;                 //收+发模式
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;        //无硬件流控
    HAL_UART_Init(&huart1);
    
    __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);          //使能接受中断
    __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);          //使能空闲帧中断
    
    HAL_NVIC_SetPriority(USART1_IRQn,3,0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}

/**
 * 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
    GPIO_InitTypeDef GPIO_Init;
    if(huart->Instance == USART1)//如果是USART1,使用重映射对引脚进行配置
    {
        /* 1、开启GPIOA的时钟 */
        __HAL_RCC_GPIOA_CLK_ENABLE();
  
        /* 2、对PA9进行配置,PA9 = Tx */
        GPIO_Init.Pin = GPIO_PIN_9;                 //选择A9
        GPIO_Init.Mode = GPIO_MODE_AF_PP;           //选择复用推挽输出
        GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;     //最大输出速度50MHz
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
          
        /* 2、对PA10进行配置,PA10 = Rx */
        GPIO_Init.Pin = GPIO_PIN_10;                 //选择PA10
        GPIO_Init.Mode = GPIO_MODE_AF_INPUT;        //选择输入模式
        GPIO_Init.Pull = GPIO_NOPULL;               //选择浮空输入
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
    }
    
}

/**
 * 中断服务函数
 */
void USART1_IRQHandler(void)
{
    uint8_t Receive_Data = 0;
    
    /*对数据进行搬运*/
    if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE))//判断是否是RXNE置位
    {
        if(Index > 256)//接受到的数据个数大于数组范围
        {
            Index = 0;
        }
        HAL_UART_Receive(&huart1, &Receive_Data, 1, 100); //将数据搬运到Receive_Data里面,
                                                           //注:进行数据的搬运会将RXNE置0,无需清除RXNE标志位
        pData[Index++] = Receive_Data;                     //将Receive_Data中的数据保存到数组里面
    }
    
    /* 对每一轮接受到的数据进行处理 */
    if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))//判断是否接受到空闲帧,代表一轮的接受结束了
    {
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除空闲帧标志位
        HAL_UART_Transmit(&huart1, pData, Index, 200);//将这一轮接受到的数据进行处理,发送出去
        Index = 0;
    }
}
/* 重构了printf()函数 */
int fputc(int ch,FILE *f)
{
    while((USART1->SR & 0x40) == 0);
    USART1->DR = (uint8_t)ch;
    return ch;
}

3、收发缓冲区方案(环形队列)

①环形缓冲区Queue.c文件的代码如下:

#include "Queue.h"

/**
 * 队列的初始化接口函数:用户调用此函数,向里面填值即可
 * queue:队列结构体指针
 * Buffer:数组
 * size:数组的长度
 */
void Queue_Init(QueueType_t *queue,uint8_t *Buffer)
{
    queue->head = 0;
    queue->tail = 0;
    queue->count = 0;
    queue->buffer = Buffer;
}

/**
 * 判断队列是否为空
 */
bool Queue_IsEmpty(QueueType_t *queue)
{
    if(queue->head == queue->tail)//队列为空,返回true
    {
        return true;
    }
    return false;
}

/**
 * 判断队列是否满
 */
bool Queue_IsFull(QueueType_t *queue)
{
    if((queue->tail + 1) % QUEUE_SIZE == queue->head)//对了为满,返回true
    {
        return true;
    }
    return false;
}

/**
 * 入队的接口函数:向队列里面放入一个字节的数据
 * Data:需要放入队列的数据
 */
bool QueuePut(QueueType_t *queue, uint8_t Data)
{
    /* 先判断队列是否满,满了就不能放入数据了 */
    if(Queue_IsFull(queue) == true)//代表队列满,则不能向队列放入数据
    {
        return false;
    }
    //队列没有满,向里面放入数据
    queue->buffer[queue->tail] = Data;//将数据Data放入数组buffer中
    queue->tail = (queue->tail + 1) % QUEUE_SIZE;;//将放入数据的下标往后移一位
    queue->count++;//有效数据加1
    return true;//代表放入成功,返回OK
}

/**
 * 出队的接口函数:向队列取出一个字节的数据
 * pData:将队列中的数据取出来放入pData的数组中
 */
bool QueueGet(QueueType_t *queue, uint8_t *pData)
{
    /* 先判断队列是否为空,若为空则不能取数据 */
    if(Queue_IsEmpty(queue) == true)//代表队列空,则不能向队列放入数据
    {
        return false;
    }
    //队列没有空,代表有数据,则可以取
    *pData = queue->buffer[queue->head];
    queue->head = (queue->head + 1) % QUEUE_SIZE;//将取数据的下标向后移动一位
    queue->count--;//有效数据加1
    return true; //表示取出超过,返回OK
}

/**
 * 入队的接口函数:向队列里面放入多个字节(一组)的数据
 * pArray:需要放入队列的一组数据的数组
 * Len:需要放入的数据个数
 */
uint16_t QueuePut_Array(QueueType_t *queue, uint8_t *pArray, uint32_t Len)
{
    uint32_t i = 0;
    for (i = 0; i < Len; i++)
    {
        if(Queue_IsFull(queue) == true)//代表队列满,则不能向队列放入数据
        {
            return i;
        }
        //队列没有满,向里面放入数据
        queue->buffer[queue->tail] = pArray[i];//将数据Data放入数组buffer中
        queue->tail = (queue->tail + 1) % QUEUE_SIZE;//将放入数据的下标往后移一位
        queue->count++;//有效数据加1
    }
    return i;//返回实际被放入数据的个数
}

/**
 * 出队的接口函数:将队列的取出多个数据
 * pArray:将队列中的多个数据取出来放入pArray的数组中
 */
uint16_t QueueGet_Array(QueueType_t *queue, uint8_t *pArray, uint32_t Len)
{
    uint16_t i = 0;
    for(i = 0; i<Len; i++)
    {
        if(Queue_IsEmpty(queue) == true)//代表队列空,则不能向队列  取出数据
        {
            return i;
        }
        pArray[i] = queue->buffer[queue->head];
        queue->head = (queue->head + 1) % QUEUE_SIZE;//将取数据的下标向后移动一位
        queue->count--;//有效数据加1
    }
    return i;//返回实际取出数据的个数
}

/**
 * 获取队列中数据的个数
 */
uint16_t Queue_GetCount(QueueType_t *queue)
{
    if (queue->head <= queue->tail)
    {
        return (queue->tail - queue->head);
    }
    return (QUEUE_SIZE - (queue->head - queue->tail));
}

②Queue.h文件的代码如下:

#ifndef __Queue_H
#define __Queue_H
#include <stdint.h>
#include <stdbool.h>

/**
 * 创建一个队列必要元素的结构体
 */
typedef struct
{
    uint16_t head;              //出队时的数组下标
    uint16_t tail;              //入队时的数组下标
    uint16_t count;             //有效数据的个数
    uint8_t *buffer;            //数组    
} QueueType_t;
#define QUEUE_SIZE 256

bool Queue_IsEmpty(QueueType_t *queue);
bool Queue_IsFull(QueueType_t *queue);
void Queue_Init(QueueType_t *queue, uint8_t *Buffer);
bool QueuePut(QueueType_t *queue, uint8_t Data);
bool QueueGet(QueueType_t *queue, uint8_t *pData);
uint16_t QueuePut_Array(QueueType_t *queue, uint8_t *pArray, uint32_t Len);
uint16_t QueueGet_Array(QueueType_t *queue, uint8_t *pArray, uint32_t Len);
uint16_t Queue_GetCount(QueueType_t *queue);

#endif

③UART.c文件的代码如下:

#include "UART.h"

QueueType_t QueueStruct;
uint8_t Buffer[QUEUE_SIZE];
uint8_t Receive_Data;
/**
 * 串口1的初始化 
 */
UART_HandleTypeDef huart1;
void UART1_Init(uint32_t baudrate)
{
    Queue_Init(&QueueStruct,Buffer);    //对缓冲区的初始化

    /* 1、开启串口1的时钟*/
    __HAL_RCC_USART1_CLK_ENABLE();

     /* 2、对串口2进行配置*/
    huart1.Instance = USART1;
    huart1.Init.BaudRate = baudrate;                    //波特率
    huart1.Init.WordLength = UART_WORDLENGTH_8B;        //8位数据位
    huart1.Init.Parity = UART_PARITY_NONE;              //无校验位
    huart1.Init.StopBits = UART_STOPBITS_1;             //1位停止位
    huart1.Init.Mode = UART_MODE_TX_RX;                 //收+发模式
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;        //无硬件流控
    HAL_UART_Init(&huart1);
  
     HAL_UART_Receive_IT(&huart1, &Receive_Data, 1);     //开启接收1个字节数据中断
    __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);         //使能接收空闲帧中断,通过空闲中断来判断接收是否完成
    
    HAL_NVIC_SetPriority(USART1_IRQn,3,0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}

/**
 * 函数重写,用于配置UASRT引脚
*/
void HAL_UART_MspInit(UART_HandleTypeDef *huart)//HAL_UART_Init()里面会调用这个函数
{
    GPIO_InitTypeDef GPIO_Init;
    if(huart->Instance == USART1)//如果是USART1,使用重映射对引脚进行配置
    {
        /* 1、开启GPIOA的时钟 */
        __HAL_RCC_GPIOA_CLK_ENABLE();
  
        /* 2、对PA9进行配置,PA9 = Tx */
        GPIO_Init.Pin = GPIO_PIN_9;                 //选择A9
        GPIO_Init.Mode = GPIO_MODE_AF_PP;           //选择复用推挽输出
        GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;     //最大输出速度50MHz
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
          
        /* 2、对PA10进行配置,PA10 = Rx */
        GPIO_Init.Pin = GPIO_PIN_10;                 //选择PA10
        GPIO_Init.Mode = GPIO_MODE_AF_INPUT;        //选择输入模式
        GPIO_Init.Pull = GPIO_NOPULL;               //选择浮空输入
        HAL_GPIO_Init(GPIOA, &GPIO_Init);
    }   
}

/**
 * 中断服务函数
 */
void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart1);
    
    /*对每一轮发送来的数据进行处理 */
    if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))//如果产生了空闲中断,标志位置位
    {
      __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);//清除中断标志位   
       UART1_SendData(&QueueStruct);
    }
}
 
/**
 * 接收完数据的回调函数,
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)//UART_Receive_IT()会调用此函数
{
    if(huart->Instance == USART1)
    {        
         if(QueuePut(&QueueStruct,Receive_Data) == true)//将Receive_Data放入环形缓冲区成功,
                                                        //没有成功则代表缓冲区满了,不在使能接收中断
         {
            HAL_UART_Receive_IT(&huart1, &Receive_Data, 1); //开启第2个轮接收中断 
         }      
    }
}
/**
 * 对环形队列的数据进行处理
 */
bool UART1_SendData(QueueType_t *queue)
{
    uint16_t a = queue->count;
    if(a == 0)//代表队列中的数据为空
    {
        return false;
    }
    uint8_t SendBuffer[a];//创建一个数组
    QueueGet_Array(queue, SendBuffer, a);//从环形队列中取出数据存储到数组SendBuffer里面
    HAL_UART_Transmit(&huart1, SendBuffer, a, 200);//发送数据
    return true;
}

④主函数main.c文件的代码如下:

#include "stm32f1xx_hal.h"
#include "STM32_RCC_Init.h"
#include "UART.h"


int main(void)
{
    HAL_Init();
    HSE_RCC_Init();     //时钟树的初始化
    UART1_Init(115200);
    while (1)
    {
        
    }
}

在这里插入图片描述


原文地址:https://blog.csdn.net/qq_51284092/article/details/143814612

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!