前言:
这篇博客注定是一个具有强大力量的博客,因为这个和之前的实验比起来,之前的实验都是弟弟,这才叫真正的嵌入式实验。如果说单纯的按照实验指导书上的代码敲一遍,运行一下,确实可以很简单的做完这个实验,但是!!!我们是来学习的,不弄清原理,单纯的傻不啦叽的写代码,那就失去了编程的灵魂。
一、创建工程
创建工程这里,很简单的,还是选择之前的板子型号,然后就是在外设这里,除了要使能时钟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中断,然后调用传输数据完成的回调函数,这个回调函数需要用户自己自定义函数体。
好了,现在,已经把中断的内容唠叨完了,里面涉及到了几个数据寄存器,那我来画一个大体的图,来表示各个寄存器与中断之间的关系叭
- 关于TXE中断,当TDR的数据发送到移位寄存器,后会产生TXE中断;
- 关于TC中断,当移位寄存器发送完数据后,发现TDR还是空的,说明发送完毕,产生TC中断。
- 关于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;
}
}
哇,这个函数,我都不想说了,因为感觉就是之前的那个接收函数的翻版,思路完全一模一样,简单的要死,毕竟他是直接查寄存器的值,并不是使能中断,思路就会很简单。
结语:
行了,写了半天多了,终于可以说写完了,简直了,太恶心了,从开始一点都不懂,到后来查寄存器,看寄存器每一位都代表啥,从而联系到代码上,然后通过代码之间的调用,来理解整个工程的运行逻辑,然后上网查各种博客来理解其中的意思。总之,为了完成这篇博客,我可是下了很大的决心,到目前为止,这篇博客已经写了两万七千多字,一下写这么多字,我手都敲疼了,也累了一天了,行了,现在都已经凌晨一点了快,下课,睡觉,哈哈哈哈哈