【STM32F407 开发板】实验四 :UART串口1数据收发实验

  • Post author:
  • Post category:其他




前言:

这篇博客注定是一个具有强大力量的博客,因为这个和之前的实验比起来,之前的实验都是弟弟,这才叫真正的嵌入式实验。如果说单纯的按照实验指导书上的代码敲一遍,运行一下,确实可以很简单的做完这个实验,但是!!!我们是来学习的,不弄清原理,单纯的傻不啦叽的写代码,那就失去了编程的灵魂。



一、创建工程

创建工程这里,很简单的,还是选择之前的板子型号,然后就是在外设这里,除了要使能时钟RCC外,还要加上一个USART1外设的使能。

在这里插入图片描述

在这里插入图片描述

主要说一下这个USART1叭,选了异步模式,这个USART是一个高级一点的串口,和UART还是有一点不同的,USART是可以支持同步模式的,而UART只支持异步模式,在异步模式下,USART与UART没有啥不同之处。

下面的硬件流控,选择的Disable,硬件流控的意思就是:

在这里插入图片描述

呐呐呐,就是一个流量控制嘛。

选择完外设之后,就可以看到芯片上这两个地方变绿了

在这里插入图片描述

在这里插入图片描述

然后UART的电路图如图:

在这里插入图片描述

可以看到管脚和电路图是一样的哈。

还是和之前同样的操作,配置时钟,配置外设,配置外设这里说一下,点开菜单栏中的Configuration,如下:

在这里插入图片描述

在这里插入图片描述

在这里图形化配置外设信息,省了在工程里面还要写代码。



二、编辑工程,编译工程

在这里插入图片描述

使用Keil5打开之后的工程结构如上图。主要的代码编辑还是再main.c中编辑,如下:

头文件中加入这两个头文件,其实没啥用,就是骚气一点而已。

在这里插入图片描述

加入全局变量,这个宏定义,无所谓的,我懒得删了,,,

在这里插入图片描述

在这里插入图片描述

这里面的printf就是我为啥加入stdio头文件的目的了,重定向printf函数,因为printf的定义在stdio头文件里面。下面的while里面就是使用中断接受串口数据。

在这里插入图片描述

这里面加了一个callback函数(里面用到了string.h头文件函数),以及printf重定向的fputc函数,关于这个函数,有好多不同的说法,其一是因为printf本质上是一个宏定义,最后是通过调用fputc函数来实现输出数据的,其二是printf通过循环调用fputc函数来实现输出数据,无论哪种解释,都是最终回归到fputc函数上,而编译器规定,如果出现同名的函数,优先调用用户定义的函数,所以相当于覆盖了之前的fputc函数,将printf重定向了。

这里有一个问题,这个fputc函数只能用HAL_UART_Transmit函数实现,使用中断接收函数HAL_UART_Transmit_IT就无法实现,目前我不太理解为啥,道行太浅,我不配,哈哈哈

接下来就没别的了,编译工程,将工程代码下载到板子上。这里在板子上运行的时候,需要连接一下串口线,我贴个照片叭。

在这里插入图片描述

这里面这个蓝色的线,就是串口线,通过USB转串口连接到电脑上的USB端口。

然后就是用到一个串口调试助手:

在这里插入图片描述

在这里插入图片描述

双击打开:

在这里插入图片描述

串口设置调成如下所示,然后点击打开串口,这时候,按下板子上的复位键,电脑端收到板子通过串口发过来的数据 “中断测试例程”,然后电脑端发送名字,板子返回一个小仙女的称号。

至此,实验算是做完了,撒花,撒花,撒花。但是本篇博客才真正的进入正题,有力量的时候来了。



三、源代码分析

这部分主要分析几个函数叭,这些函数,说实话我都有点蒙蔽,,,,



1 . MX_USART1_UART_Init();

这个函数在main函数里面调用,用来初始化串口配置信息。


UART_HandleTypeDef huart1;

/* USART1 init function */

void MX_USART1_UART_Init(void)
{

  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }

}

这个函数的定义在uart.c文件里面,可以看到huart1是一个全局变量,在此函数里面,对uart1进行了配置,UART_HandleTypeDef类型定义如下:

在这里插入图片描述

可以看到这个Instance是一个地址指针,Instance的详细定义如下:

在这里插入图片描述

好了,这就到了底层了,已经涉及到寄存器部分了,寄存器部分稍后再说,在发送接收数据的时候稍微提一下。

uart1的初始化主要集中在Init字段的配置,点开Init字段,查看详细定义:

在这里插入图片描述

可以看到这些配置,和在Cube中的配置是一摸一样的,也对应了MX_USART1_UART_Init函数中的字段配置部分。

接下来我再说一下UART的帧格式叭,UART的数据帧(也就是数据位)有8Bit和9Bit之说,数据位是通信中衡量实际数据位的参数,当计算机发送一个信息包,实际的数据不会是 8 位的,标准的值是 5、 7 和 8 位。如何设置取决于你想传送的信息。比如,标准的 ASCII 码是 0~127(7 位)。扩展的 ASCII 码是 0~255(8 位)。如果数据使用简单的文本(标准 ASCII 码),那么每个数据包使用 7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。

关于数据帧的详细可以看这个博客:

UART通信与移位寄存器的应用

在这里插入图片描述

从上图中可以看到,这是一个8Bit的数据帧。由于前7位用来传送数据,那么第8位就当作了一个奇偶校验位了。

异步通信时数据是一帧一帧传送的,每帧数据包含起始位(“0”)、数据位、奇偶校验位和停止位(“1”),每帧数据的传送靠起始位来同步。在同一帧数据中,两个相邻位间的时间间隔是固定的,而相邻两帧数据的时间间隔是不固定的。在异步通信的数据传送中,数据线上允许有空闲帧,表示当前没有数据传送。

然后注意到在 MX_USART1_UART_Init 函数中还有一个函数,HAL_UART_Init,下面来看一下这个函数:



2 . HAL_UART_Init():

这个函数是用来初始化UART的,函数代码如下:

/**
  * @brief  Initializes the UART mode according to the specified parameters in
  *         the UART_InitTypeDef and create the associated handle.
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval HAL status
  */
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, USART3 and USART6 */
    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));
  assert_param(IS_UART_OVERSAMPLING(huart->Init.OverSampling));
  
  if(huart->gState == HAL_UART_STATE_RESET)
  {  
    /* Allocate lock resource and initialize it */
    huart->Lock = HAL_UNLOCKED;
    /* Init the low level hardware */
    HAL_UART_MspInit(huart);
  }

  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;
  
  return HAL_OK;
}

这个函数,主要涉及到对Huart的structure中的其他部分进行配置,其中比较核心的是两个函数叭,



2.1 UART_SetConfig(huart)

这个函数才是最恶心的函数,先看看它的源代码:

/**
  * @brief  Configures the UART peripheral. 
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
static void UART_SetConfig(UART_HandleTypeDef *huart)
{
  uint32_t tmpreg = 0x00U;
  
  /* Check the parameters */
  assert_param(IS_UART_BAUDRATE(huart->Init.BaudRate));
  assert_param(IS_UART_STOPBITS(huart->Init.StopBits));
  assert_param(IS_UART_PARITY(huart->Init.Parity));
  assert_param(IS_UART_MODE(huart->Init.Mode));

  /*-------------------------- USART CR2 Configuration -----------------------*/
  tmpreg = huart->Instance->CR2;

  /* Clear STOP[13:12] bits */
  tmpreg &= (uint32_t)~((uint32_t)USART_CR2_STOP);

  /* Configure the UART Stop Bits: Set STOP[13:12] bits according to huart->Init.StopBits value */
  tmpreg |= (uint32_t)huart->Init.StopBits;
  
  /* Write to USART CR2 */
  WRITE_REG(huart->Instance->CR2, (uint32_t)tmpreg);

  /*-------------------------- USART CR1 Configuration -----------------------*/
  tmpreg = huart->Instance->CR1;

  /* Clear M, PCE, PS, TE and RE bits */
  tmpreg &= (uint32_t)~((uint32_t)(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | \
                                   USART_CR1_RE | USART_CR1_OVER8));

  /* Configure the UART Word Length, Parity and mode: 
     Set the M bits according to huart->Init.WordLength value 
     Set PCE and PS bits according to huart->Init.Parity value
     Set TE and RE bits according to huart->Init.Mode value
     Set OVER8 bit according to huart->Init.OverSampling value */
  tmpreg |= (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode | huart->Init.OverSampling;
  
  /* Write to USART CR1 */
  WRITE_REG(huart->Instance->CR1, (uint32_t)tmpreg);
  
  /*-------------------------- USART CR3 Configuration -----------------------*/  
  tmpreg = huart->Instance->CR3;
  
  /* Clear CTSE and RTSE bits */
  tmpreg &= (uint32_t)~((uint32_t)(USART_CR3_RTSE | USART_CR3_CTSE));
  
  /* Configure the UART HFC: Set CTSE and RTSE bits according to huart->Init.HwFlowCtl value */
  tmpreg |= huart->Init.HwFlowCtl;
  
  /* Write to USART CR3 */
  WRITE_REG(huart->Instance->CR3, (uint32_t)tmpreg);
  
  /* Check the Over Sampling */
  if(huart->Init.OverSampling == UART_OVERSAMPLING_8)
  {
    /*-------------------------- USART BRR Configuration ---------------------*/
#if defined(USART6) 
    if((huart->Instance == USART1) || (huart->Instance == USART6))
    {
      huart->Instance->BRR = UART_BRR_SAMPLING8(HAL_RCC_GetPCLK2Freq(), huart->Init.BaudRate);
    }
#else
    if(huart->Instance == USART1)
    {
      huart->Instance->BRR = UART_BRR_SAMPLING8(HAL_RCC_GetPCLK2Freq(), huart->Init.BaudRate);
    }	
#endif /* USART6 */
    else
    {
      huart->Instance->BRR = UART_BRR_SAMPLING8(HAL_RCC_GetPCLK1Freq(), huart->Init.BaudRate);
    }
  }
  else
  {
    /*-------------------------- USART BRR Configuration ---------------------*/
#if defined(USART6) 
    if((huart->Instance == USART1) || (huart->Instance == USART6))
    {
      huart->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK2Freq(), huart->Init.BaudRate);
    }
#else
    if(huart->Instance == USART1)
    {
      huart->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK2Freq(), huart->Init.BaudRate);
    }	
#endif /* USART6 */
    else
    {
      huart->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK1Freq(), huart->Init.BaudRate);
    }
  }
}

这个函数涉及到对四个寄存器的操作,CR1/CR2/CR3/BRR,查看F407的参考手册,可以查到对应的寄存器的解释,我贴上来:

在这里插入图片描述

在这里插入图片描述

代码的第一部分是对CR2的停止位进行的操作,将停止位清空,然后设置用户定义的停止位。

代码中的USART_CR2_STOP是一个掩码,用来清空CR2对应的STOP位。清空后,再或上去,写回CR2。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在配置CR1的代码中:

  /*-------------------------- USART CR1 Configuration -----------------------*/
  tmpreg = huart->Instance->CR1;

  /* Clear M, PCE, PS, TE and RE bits */
  tmpreg &= (uint32_t)~((uint32_t)(USART_CR1_M | USART_CR1_PCE | USART_CR1_PS | USART_CR1_TE | \
                                   USART_CR1_RE | USART_CR1_OVER8));

  /* Configure the UART Word Length, Parity and mode: 
     Set the M bits according to huart->Init.WordLength value 
     Set PCE and PS bits according to huart->Init.Parity value
     Set TE and RE bits according to huart->Init.Mode value
     Set OVER8 bit according to huart->Init.OverSampling value */
  tmpreg |= (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode | huart->Init.OverSampling;
  
  /* Write to USART CR1 */
  WRITE_REG(huart->Instance->CR1, (uint32_t)tmpreg);

获取了寄存器CR1中的字长、奇偶校验使能、奇偶校验选择、发送器使能、接收器使能、过采样模式这些对应的比特位。

然后将用户配置的UART的信息写进CR1寄存器中。完成寄存器CR1的配置。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

对于CR3的配置代码如下:

  /*-------------------------- USART CR3 Configuration -----------------------*/  
  tmpreg = huart->Instance->CR3;
  
  /* Clear CTSE and RTSE bits */
  tmpreg &= (uint32_t)~((uint32_t)(USART_CR3_RTSE | USART_CR3_CTSE));
  
  /* Configure the UART HFC: Set CTSE and RTSE bits according to huart->Init.HwFlowCtl value */
  tmpreg |= huart->Init.HwFlowCtl;
  
  /* Write to USART CR3 */
  WRITE_REG(huart->Instance->CR3, (uint32_t)tmpreg);

将用户配置的硬件流控制写入到CR3寄存器中。

在这里插入图片描述

在这里插入图片描述

对波特率的配置:

/* Check the Over Sampling */
  if(huart->Init.OverSampling == UART_OVERSAMPLING_8)
  {
    /*-------------------------- USART BRR Configuration ---------------------*/
#if defined(USART6) 
    if((huart->Instance == USART1) || (huart->Instance == USART6))
    {
      huart->Instance->BRR = UART_BRR_SAMPLING8(HAL_RCC_GetPCLK2Freq(), huart->Init.BaudRate);
    }
#else
    if(huart->Instance == USART1)
    {
      huart->Instance->BRR = UART_BRR_SAMPLING8(HAL_RCC_GetPCLK2Freq(), huart->Init.BaudRate);
    }	
#endif /* USART6 */
    else
    {
      huart->Instance->BRR = UART_BRR_SAMPLING8(HAL_RCC_GetPCLK1Freq(), huart->Init.BaudRate);
    }
  }
  else
  {
    /*-------------------------- USART BRR Configuration ---------------------*/
#if defined(USART6) 
    if((huart->Instance == USART1) || (huart->Instance == USART6))
    {
      huart->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK2Freq(), huart->Init.BaudRate);
    }
#else
    if(huart->Instance == USART1)
    {
      huart->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK2Freq(), huart->Init.BaudRate);
    }	
#endif /* USART6 */
    else
    {
      huart->Instance->BRR = UART_BRR_SAMPLING16(HAL_RCC_GetPCLK1Freq(), huart->Init.BaudRate);
    }
  }

这个就是根据具体的UART的型号来具体的配置波特率,这个具体为啥这么配置波特率,我也不太懂,毕竟代码只是实现而已,具体标准我不太清楚。

综上,上述的UART_SetConfig函数,实现了对UART的停止位(CR2)、字长、奇偶校验使能、奇偶校验选择、发送器使能、接收器使能、过采样模式(CR1)、硬件流控制(CR3)、波特率(BRR)。

当初用户再Init中的配置如下:

huart1.Init.BaudRate = 115200;(波特率)

huart1.Init.WordLength = UART_WORDLENGTH_8B;(字长)

huart1.Init.StopBits = UART_STOPBITS_1;(停止位)

huart1.Init.Parity = UART_PARITY_NONE;(奇偶校验使能、奇偶校验选择)

huart1.Init.Mode = UART_MODE_TX_RX;(发送器接收器使能)

huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;(硬件流控制)

huart1.Init.OverSampling = UART_OVERSAMPLING_16;(过采样模式)



2.2 CLEAR_BIT

这个本质上是一个宏定义,并不是一个函数。

/* 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));
  

宏定义详细如下:、

#define CLEAR_BIT(REG, BIT)   ((REG) &= ~(BIT))

也就是它CLEAR掉了CR2中的LINEN/CLKEN;CLEAR掉了CR3中的SCEN/HDSEL/IREN这些数据位,(所谓clear就是复位清0了)我贴一下具体的解释吧。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

至于为啥这么设置,我不太了解,功力尚浅,我不配。

好了,这个大函数(MX_USART1_UART_Init()),终于说完了,下面说下一个函数。



3 . HAL_UART_Receive_IT(&huart1, Buff, WORD_SELF);

/**
  * @brief  Receives an amount of data in non blocking mode 
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @param  pData: Pointer to data buffer
  * @param  Size: Amount of data to be received
  * @retval HAL status
  */
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 == 0)) 
    {
      return HAL_ERROR;
    }
    
    /* Process Locked */
    __HAL_LOCK(huart);
    
    huart->pRxBuffPtr = pData;
    huart->RxXferSize = Size;
    huart->RxXferCount = Size;
    
    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->RxState = HAL_UART_STATE_BUSY_RX;
    
    /* Process Unlocked */
    __HAL_UNLOCK(huart);
        
    /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
    SET_BIT(huart->Instance->CR3, USART_CR3_EIE);

    /* Enable the UART Parity Error and Data Register not empty Interrupts */
    SET_BIT(huart->Instance->CR1, USART_CR1_PEIE | USART_CR1_RXNEIE);
    
    return HAL_OK;
  }
  else
  {
    return HAL_BUSY; 
  }
}

这个函数在main函数中被调用,用来从中断接收数据:

在这里插入图片描述

这个函数主要说几点吧:

功能是,从串口huart1中接收Size个字节到Buff中,但是这个函数,并不是真正的接收数据的函数,它相当于一个配置函数,只是配置好了字段,真正的接收数据的函数,是另一个函数,因为是从中断中接收数据,所以真正的接收数据的函数,是从中断处理函数中被调用的。

这个函数的作用就是将Buff挂到HUART中,设置好缓冲区大小,需要接受的数据的大小,只有接收到size个数据后,才会产生callback。

其二就是,下面这个语句:

/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
SET_BIT(huart->Instance->CR3, USART_CR3_EIE);

/* Enable the UART Parity Error and Data Register not empty Interrupts */
SET_BIT(huart->Instance->CR1, USART_CR1_PEIE | USART_CR1_RXNEIE);

我还是贴一下它们的作用吧。

在这里插入图片描述

他说的FE/ORE/NF如下图所示,位于SR寄存器中(发现没?这些错误都是硬件置1):

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

他说的PE是下面这个,位于SR寄存器中(同样也是硬件置1)。

在这里插入图片描述

在这里插入图片描述

其中提到的ORE和RXNE在SR中如下:

在这里插入图片描述

在这里插入图片描述

上面的意思就是,状态寄存器,就是管理状态,不负责任何的中断使能,中断使能位于控制寄存器中。当对应的状态发生改变的时候,会产生相应的中断。

综上,这个函数的作用就是,配置好接收缓存区,接收字节数,启动接收进程,使能错误中断。



4 . HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

这个函数是通过中断发送数据。

/**
  * @brief  Sends an amount of data in non blocking mode.
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @param  pData: Pointer to data buffer
  * @param  Size: Amount of data to be sent
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  /* Check that a Tx process is not already ongoing */
  if(huart->gState == HAL_UART_STATE_READY)
  {
    if((pData == NULL ) || (Size == 0)) 
    {
      return HAL_ERROR;
    }
    
    /* Process Locked */
    __HAL_LOCK(huart);
    
    huart->pTxBuffPtr = pData;
    huart->TxXferSize = Size;
    huart->TxXferCount = Size;

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    /* Enable the UART Transmit data register empty Interrupt */
    SET_BIT(huart->Instance->CR1, USART_CR1_TXEIE);
    
    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;   
  }
}

这个函数还是同上面一样,说一下这个CR1配置叭:

在这里插入图片描述

SR寄存器中的TXE如下:

在这里插入图片描述

然后这个函数的主要功能还是配置发送缓冲区,发送字节数,启动发送进程,设置使能中断。

好了,这里提到了TDR,这个我稍后再说,因为还有好多函数没提到,手动泪目,,,,



5 . void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)

说了之前的中断接收发送函数之类的,终于涉及到最重要的函数了,中断处理函数,即中断服务程序。

/**
  * @brief  This function handles UART interrupt request.
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void 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;
    }
  }  

  /* If some errors occur */
  if((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
  {
    /* UART parity error interrupt occurred ----------------------------------*/
    if(((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_PE;
    }
    
    /* UART noise error interrupt occurred -----------------------------------*/
    if(((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_NE;
    }
    
    /* UART frame error interrupt occurred -----------------------------------*/
    if(((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_FE;
    }
    
    /* UART Over-Run interrupt occurred --------------------------------------*/
    if(((isrflags & USART_SR_ORE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    { 
      huart->ErrorCode |= HAL_UART_ERROR_ORE;
    }

    /* Call UART Error Call back function if need be --------------------------*/    
    if(huart->ErrorCode != HAL_UART_ERROR_NONE)
    {
      /* UART in mode Receiver -----------------------------------------------*/
      if(((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
      {
        UART_Receive_IT(huart);
      }

      /* If Overrun error occurs, or if any error occurs in DMA mode reception,
         consider error as blocking */
      dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
      if(((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || dmarequest)
      {
        /* Blocking error : transfer is aborted
           Set the UART state ready to be able to start again the process,
           Disable Rx Interrupts, and disable Rx DMA request, if ongoing */
        UART_EndRxTransfer(huart);
        
        /* Disable the UART DMA Rx request if enabled */
        if(HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
        {
          CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
          
          /* Abort the UART DMA Rx channel */
          if(huart->hdmarx != NULL)
          {
            /* Set the UART DMA Abort callback : 
               will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */
            huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError;
            if(HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK)
            {
              /* Call Directly XferAbortCallback function in case of error */
              huart->hdmarx->XferAbortCallback(huart->hdmarx);
            }
          }
          else
          {
            /* Call user error callback */
            HAL_UART_ErrorCallback(huart);
          }
        }
        else
        {
          /* Call user error callback */
          HAL_UART_ErrorCallback(huart);
        }
      }
      else
      {
        /* Non Blocking error : transfer could go on. 
           Error is notified to user through user error callback */
        HAL_UART_ErrorCallback(huart);
        huart->ErrorCode = HAL_UART_ERROR_NONE;
      }
    }
    return;
  } /* End if some error occurs */

  /* UART in mode Transmitter ------------------------------------------------*/
  if(((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
  {
    UART_Transmit_IT(huart);
    return;
  }
  
  /* UART in mode Transmitter end --------------------------------------------*/
  if(((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
  {
    UART_EndTransmit_IT(huart);
    return;
  }
}

看,,这个函数长不长,厉不厉害,哈哈哈,慢慢分析一波。

一开始声明了几个变量,isrflags是SR寄存器内容,cr1its是CR1内容,cr3its是CR3内容。

首先获取errorflags,获得到底出了啥错误,才产生了中断异常。

errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));

可以看到这个errorflags是获取的SR寄存器的状态,看看除了啥错误,这里不获取CRx的内容,因为他们只是控制使能的作用,真正的抽象实体还是SR寄存器。

可以看到获得的错误都是之前使能的错误中断,像什么PE(奇偶校验错误)FE(帧错误)ORE(上溢错误)NE(额,这里我感觉要不是板子的参考手册错了,要不就是它的工程代码写错了,这里的NE就是NF检测到噪声标志错误)

对于这里的NE错误,简要说明如下:

板子的参考手册如下:

在这里插入图片描述

工程代码如下:

在这里插入图片描述

可以看到后面也写了Noise Error Flag,所以实锤了,可能是人家为了整齐划一,都起名为xxE,表示Error的意思。

然后接下来的代码:

  if(errorflags == RESET)
  {
    /* UART in mode Receiver -------------------------------------------------*/
    if(((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
    {
      UART_Receive_IT(huart);
      return;
    }
  } 

其中的RESET是0的意思,也就是当没有错误发生时,哎,说明什么,说明有其他中断发生了,那么这个中断还可能是使能的什么中断呢,除去刚刚说的那几种使能错误,还有TXE中断、RXNE中断,也就是无论这两个中断那个发生,都不是错误中断,只是说明需要发送数据或者接受数据而已,上面的代码判断确实是需要接收数据。所以调用UART_Receive_IT函数进行接收,UART_Receive_IT这个函数才是真正的中断接收函数。

IRQ中断处理函数中间的一大堆错误回调代码,就此略过,都是无学习意义的代码,包括之后的DMA方式,我没有去深究,因为就光光这篇博客的内容就已经是我2天的成果了,这个板子的实验指导书,就告诉我写一些简单的代码,运行一下,正确运行,就完事了,之后的所有分析源代码,分析寄存器的操作都是我自己刻苦阅读源代码,分析代码逻辑分析出来的,(哎呀,感动的不行了,我怎么这么努力呢,我太厉害了吧,哈哈哈哈)

之后最后还有一段代码:

/* UART in mode Transmitter ------------------------------------------------*/
if(((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
  UART_Transmit_IT(huart);
  return;
}

/* UART in mode Transmitter end --------------------------------------------*/
if(((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
{
  UART_EndTransmit_IT(huart);
  return;
}

上面刚刚才说道,还剩下一个TXE中断,这个TXE中断,是发送数据寄存器为空,有两种情况:1.发送寄存器TDR为空但是USART_DR不为空,则仅仅只是一个数据帧发送完毕,将USART_DR的数据给TDR寄存器就好了,发送下一个就可以了。2. 发送寄存器TDR为空,且USART_DR也为空,就代表数据全部发送完毕。

哎,突然想起来,之前那个中断发送是不是没使能TC中断呢。(疑惑啊,没有TCIE,是不会发出中断的啊,也就是UART_EndTransmit_IT这个函数根本不会进入啊,也就是根本就不会结束发送????)



6 . UART_Receive_IT(huart);

呐呐呐呐,这才是真正的中断接收函数,真的是有一种拨开云雾见青天的感觉,又有一种柳暗花明又一村的感觉,因为写完这个函数,我还有4个函数,基本上就写完了这篇博客,终于看到写完博客的希望了,啊啊啊啊啊啊。

/**
  * @brief  Receives an amount of data in non blocking mode 
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval HAL status
  */
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) 
  {
    if(huart->Init.WordLength == UART_WORDLENGTH_9B)
    {
      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)
    {
      /* Disable the UART Parity Error Interrupt and RXNE interrupt*/
      CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));

      /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
      CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

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

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

这个接收函数,就很明显的展示出了对于9Bit帧和8Bit帧的不同处理。

首先判断接收进程是否处于启动状态。然后根据数帧的长度不同,来进行if-else

在之下就细分,是否开启了奇偶校验,如果没有开启,说明这9位都是有效数据位,都需要接收,所以BuffPtr每次需要走2个字节可以看到,这里面的接收,都是用的huart结构体里面当初用Buff初始化的pRxBuffPtr字段,

然后就是8Bit数据帧的处理,和9Bit数据帧没啥不同。

然后这里有一个需要注意的一点,就是,每次中断,只接受一个数据帧。所以每次RxXferCount在减少的时候,都是自减一。

如果RxXferCount自减到0了,说明没有数据需要接收了,也就是接收数据完毕,当初使能的中断,应该都被CLEAR掉,如下:

/* Disable the UART Parity Error Interrupt and RXNE interrupt*/
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));

/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

接下来就是调用回调函数,注意此时调用回调函数,是在所有数据都接收完毕之后,才会调用一次回调函数:

HAL_UART_RxCpltCallback(huart);

这里的回调函数,需要用户自己自定义,自定义收到数据之后的回调行为。



7. UART_Transmit_IT(huart);

/**
  * @brief  Sends an amount of data in non blocking mode.
  * @param  huart: Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval HAL status
  */
static HAL_StatusTypeDef UART_Transmit_IT(UART_HandleTypeDef *huart)
{
  uint16_t* tmp;
  
  /* Check that a Tx process is ongoing */
  if(huart->gState == HAL_UART_STATE_BUSY_TX)
  {
    if(huart->Init.WordLength == UART_WORDLENGTH_9B)
    {
      tmp = (uint16_t*) huart->pTxBuffPtr;
      huart->Instance->DR = (uint16_t)(*tmp & (uint16_t)0x01FF);
      if(huart->Init.Parity == UART_PARITY_NONE)
      {
        huart->pTxBuffPtr += 2U;
      }
      else
      {
        huart->pTxBuffPtr += 1U;
      }
    } 
    else
    {
      huart->Instance->DR = (uint8_t)(*huart->pTxBuffPtr++ & (uint8_t)0x00FF);
    }

    if(--huart->TxXferCount == 0U)
    {
      /* Disable the UART Transmit Complete Interrupt */
      CLEAR_BIT(huart->Instance->CR1, USART_CR1_TXEIE);

      /* Enable the UART Transmit Complete Interrupt */    
      SET_BIT(huart->Instance->CR1, USART_CR1_TCIE);
    }
    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

怎么说呢,这个函数,我感觉问题很大啊,对于这个问题,我感觉我唯一的想法就是数组的内存布局不同,因为对于9位来说,它与的是0x01FF,也就是说明,对于一个16bit的指针,它取了前9位,也就是数组是这么摆着的。

在这里插入图片描述

也就是数组是从右向左走的,右面才是Tmp[0],这就很奇怪,而且在9位帧,没有校验位的时候,他会丢数据,就很恶心,分析以上代码,可知,在9位帧的时候,有一个校验位,是可以正常运行的,但是当9位都是数据位的时候,就会丢掉7个比特的数据。

在这里插入图片描述

记录一下错误情况,本来我是完全输出@@@@的,但是出现了逗号,说明传输过程中丢了数据,使得接收端解析失败了。

算了,不讨论这个没定论的事情了,我这两天再研究一下情况,看看能不能改一下它的源代码。

好,现在就假设它完全都是8位帧,那么是完全可以正确运行的。这个函数负责将数据放到DR寄存器上去。

当剩下的发送数据的Count为0的时候,需要失能中断,开启TC中断,这好像照应了之前说的我的疑惑,哈哈哈哈,就当我当时放个屁没说吧,,,,

使能了TC中断之后,就可以获得TC中断,从而进入UART_EndTransmit_IT函数



8 . UART_EndTransmit_IT(huart);

函数代码如下:

/**
  * @brief  Wraps up transmission in non blocking mode.
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval HAL status
  */
static HAL_StatusTypeDef UART_EndTransmit_IT(UART_HandleTypeDef *huart)
{
  /* Disable the UART Transmit Complete Interrupt */    
  CLEAR_BIT(huart->Instance->CR1, USART_CR1_TCIE);
  
  /* Tx process is ended, restore huart->gState to Ready */
  huart->gState = HAL_UART_STATE_READY;
    
  HAL_UART_TxCpltCallback(huart);
  
  return HAL_OK;
}

这个函数就很简单了,因为已经进入了这个函数,那么TC中断就没有用了,需要CLEAR掉TC中断,然后调用传输数据完成的回调函数,这个回调函数需要用户自己自定义函数体。

好了,现在,已经把中断的内容唠叨完了,里面涉及到了几个数据寄存器,那我来画一个大体的图,来表示各个寄存器与中断之间的关系叭

在这里插入图片描述

  1. 关于TXE中断,当TDR的数据发送到移位寄存器,后会产生TXE中断;
  2. 关于TC中断,当移位寄存器发送完数据后,发现TDR还是空的,说明发送完毕,产生TC中断。
  3. 关于RXNE中断,当移位寄存器将数据发送给RDR的时候,产生RXNE中断。



9 . HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

这个函数,是不通过中断来发送数据的发送函数,函数代码:

/**
  * @brief  Sends an amount of data in blocking mode. 
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @param  pData: Pointer to data buffer
  * @param  Size: Amount of data to be sent
  * @param  Timeout: Timeout duration  
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint16_t* tmp;
  uint32_t tickstart = 0U;
  
  /* Check that a Tx process is not already ongoing */
  if(huart->gState == HAL_UART_STATE_READY) 
  {
    if((pData == NULL ) || (Size == 0)) 
    {
      return  HAL_ERROR;
    }
    
    /* Process Locked */
    __HAL_LOCK(huart);
    
    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;
	
    /* Init tickstart for timeout managment */
    tickstart = HAL_GetTick();

    huart->TxXferSize = Size;
    huart->TxXferCount = Size;
    while(huart->TxXferCount > 0U)
    {
      huart->TxXferCount--;
      if(huart->Init.WordLength == UART_WORDLENGTH_9B)
      {
        if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
        { 
          return HAL_TIMEOUT;
        }
        tmp = (uint16_t*) pData;
        huart->Instance->DR = (*tmp & (uint16_t)0x01FF);
        if(huart->Init.Parity == UART_PARITY_NONE)
        {
          pData +=2U;
        }
        else
        { 
          pData +=1U;
        }
      } 
      else
      {
        if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
        {
          return HAL_TIMEOUT;
        }
        huart->Instance->DR = (*pData++ & (uint8_t)0xFF);
      } 
    }
    
    if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
    { 
      return HAL_TIMEOUT;
    }
    
    /* At end of Tx process, restore huart->gState to Ready */
      huart->gState = HAL_UART_STATE_READY;
    
    /* Process Unlocked */
    __HAL_UNLOCK(huart);
    
    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

这个函数,说实话,我感觉很简单很简单,一开始还是简单的配置HUART属性操作,然后与中断发送不同的是,它是在本函数体内完成发送的。即调用一次函数,就会发送完所有的数据。说一下比较核心的函数叭,就是这个UART_WaitOnFlagUntilTimeout函数,这个函数的声明如下:

static HAL_StatusTypeDef UART_WaitOnFlagUntilTimeout(UART_HandleTypeDef *huart, uint32_t Flag, FlagStatus Status, uint32_t Tickstart, uint32_t Timeout)

就是在Flag上面一直等待,看它的值为SET还是RESET,直到Flag的值与Status不同为止,返回HAL_OK,如果一直为Status,直到TimeOut为止,则返回HAL_TIMEOUT。也就是意思是,一直在此Status上等待,直到不为Status为止。

不过这个函数貌似没有回调函数,只会返回一个HAL_OK。



10 . HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

这个函数是不通过中断来接收数据的函数,函数代码如下:

/**
  * @brief  Receives an amount of data in blocking mode. 
  * @param  huart: pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @param  pData: Pointer to data buffer
  * @param  Size: Amount of data to be received
  * @param  Timeout: Timeout duration
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{ 
  uint16_t* tmp;
  uint32_t tickstart = 0U;
  
  /* Check that a Rx process is not already ongoing */
  if(huart->RxState == HAL_UART_STATE_READY) 
  { 
    if((pData == NULL ) || (Size == 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();
        
    huart->RxXferSize = Size; 
    huart->RxXferCount = Size;
    
    /* Check the remain data to be received */
    while(huart->RxXferCount > 0U)
    {
      huart->RxXferCount--;
      if(huart->Init.WordLength == UART_WORDLENGTH_9B)
      {
        if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
        { 
          return HAL_TIMEOUT;
        }
        tmp = (uint16_t*) pData;
        if(huart->Init.Parity == UART_PARITY_NONE)
        {
          *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
          pData +=2U;
        }
        else
        {
          *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
          pData +=1U;
        }

      } 
      else
      {
        if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK)
        { 
          return HAL_TIMEOUT;
        }
        if(huart->Init.Parity == UART_PARITY_NONE)
        {
          *pData++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
        }
        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;
  }
  else
  {
    return HAL_BUSY;   
  }
}

哇,这个函数,我都不想说了,因为感觉就是之前的那个接收函数的翻版,思路完全一模一样,简单的要死,毕竟他是直接查寄存器的值,并不是使能中断,思路就会很简单。



结语:

行了,写了半天多了,终于可以说写完了,简直了,太恶心了,从开始一点都不懂,到后来查寄存器,看寄存器每一位都代表啥,从而联系到代码上,然后通过代码之间的调用,来理解整个工程的运行逻辑,然后上网查各种博客来理解其中的意思。总之,为了完成这篇博客,我可是下了很大的决心,到目前为止,这篇博客已经写了两万七千多字,一下写这么多字,我手都敲疼了,也累了一天了,行了,现在都已经凌晨一点了快,下课,睡觉,哈哈哈哈哈



版权声明:本文为qq_42127861原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。