串口简介
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步串行接收发送器
即串口。是一个高度灵活的收发模块,它可以做到与上位机,或者其他MCU进行通信,实现上位机,其他MCU间的相互控制
STM32单片机芯片内部集成了多个串口。
一般而言,我们使用串口都使用异步收发模式。同步收发模式在外设和代码上的处理较为复杂,而且对时钟的要求严格,MCU的频率较PC频率低得多,所以没很大的必要使用同步模式。而同步通信的目的就是为了加快通信速率,但在使用上有些不尽如人意。以我用过的串口模块(外设)来说,基本上都是异步收发的串口模块(外设)。如ESP8266,其他一些蓝牙模块。
以STM32F4为例,该芯片集成了4个同步异步串口(USART1,USART2,USART3,USART6),2个异步串口(UART4,UART5),同步异步串口可以初始化为同步模式,也可以初始化为异步模式,而异步串口仅能初始化为异步模式。
若要将同步异步串口初始化为同步模式,则调用同步模式的串口初始化函数,否则调用异步模式串口初始化函数来初始化串口。
串口初始化具体步骤
这里我们先介绍初始化串口要用到的函数
相关HAL库函数 | 具体功能 |
---|
HAL_UART_Init | 顾名思义,异步串口初始化函数,初始化串口时我们调用的函数。所在源文件:stm32f4xx_hal_uart.c |
HAL_UART_DeInit | 取消对某异步串口的初始化 |
HAL_UART_MspInit | 串口MSP初始化 |
HAL_UART_MspDeInit | 取消MSP初始化 |
这是MSP初始化函数的声明,可以发现,在void关键字之前有一个weak关键字
__weak void HAL_UART_MspInit(UART_HandleTypeDef *huart)
这个关键字告诉编译器,这个定义是弱定义,若在编译时,有一处函数定义不含此关键字,则所有该函数的调用都指向那一个非weak型的函数定义。这个操作叫函数重定向。因为在系统头文件里,MSP层是没有声明的,MSP层是具体对应每一个MCU的物理状态,系统文件不会声明也不能声明,但在单片机做上电初始化时,有一些函数又必须调用,所以系统文件采取了这种用户可以重定向的写法。我们在初始化串口时,需要自己写一个MSPInit,而且不含weak。函数声明如下:
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
我们要初始化串口,就要定义一个属于自己的MSP函数,并且系统会自动的把所有的MSP调用都指向我们定义的这个MSP函数
以下为系统定义的MSP函数,可见,内部几乎没什么内容,唯一一个函数调用是使该串口变为UNUSED(不使用)
__weak void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
UNUSED(huart);
}
取消MSP使能函数的声明如下,可见在内部代码中的该函数也为弱定义,内部代码也十分简单,此处不做赘述
__weak void HAL_UART_MspDeInit(UART_HandleTypeDef *huart)
具体初始化过程(以CUBEMX生成的空项目为例):
使用串口第一步。添加串口同步异步源文件到项目中。(stm32f4xx_hal_usart.c,stm32f4xx_hal_uart.c)若只使用异步模式,应该不需要添加同步异步模式的头文件进入项目,但一方面是为了方便,一方面是为了少出一点事情,还是将它加进项目吧)
点击Add Files
返回项目文件夹层级,可见有如上几个文件夹。点击Drivers
点击HAL_Driver,下一栏点src,从中找到stm32f4xx_hal_usart.c和stm32f4xx_hal_uart.c,点击Add加入项目。文件即加入了我们的项目
文件加入我们的项目后,我们仅仅引用了源文件,而头文件的引用,进入main.h中,打开stm32f4xx_hal.h,找到stm32f4xx_hal_conf.h
可以发现头文件的模块使能宏定义被注释掉了,把注释去掉即可。
这样一来所有的准备工作都做好了,我们开始着手初始化串口吧
串口初始化函数
void Usart1Init(int baudRate)
{
UART1_Handle.Instance = USART1;
UART1_Handle.Init.BaudRate = baudRate;
UART1_Handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UART1_Handle.Init.Mode = UART_MODE_TX_RX;
UART1_Handle.Init.OverSampling = UART_OVERSAMPLING_16;
UART1_Handle.Init.Parity = UART_PARITY_NONE;
UART1_Handle.Init.StopBits = UART_STOPBITS_1;
UART1_Handle.Init.WordLength = UART_WORDLENGTH_8B;
if (HAL_UART_Init(&UART1_Handle) != HAL_OK)
{
Error_Handler();
}
}
其中UART1_Handle为UART_HandleTypeDef类型的结构体
结构体内容如下:
typedef struct __UART_HandleTypeDef
{
USART_TypeDef *Instance;
UART_InitTypeDef Init;
uint8_t *pTxBuffPtr;
uint16_t TxXferSize;
__IO uint16_t TxXferCount;
uint8_t *pRxBuffPtr;
uint16_t RxXferSize;
__IO uint16_t RxXferCount;
DMA_HandleTypeDef *hdmatx;
DMA_HandleTypeDef *hdmarx;
HAL_LockTypeDef Lock;
__IO HAL_UART_StateTypeDef gState;
__IO HAL_UART_StateTypeDef RxState;
__IO uint32_t ErrorCode;
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart);
void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart);
void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart);
void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart);
void (* ErrorCallback)(struct __UART_HandleTypeDef *huart);
void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart);
void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart);
void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart);
void (* WakeupCallback)(struct __UART_HandleTypeDef *huart);
void (* MspInitCallback)(struct __UART_HandleTypeDef *huart);
void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart);
#endif
} UART_HandleTypeDef;
这个结构体声明串口句柄。句柄是什么?因为笔者在初学时也未能理解,这里做一个简单的介绍。句柄,可以理解为一个对象的控制终端,所有和这个对象有关的数据和内容,或者相关的控制传递,通过这个句柄就可以完成。所以不论在串口初始化,还是在数据收发过程中,都需要传入相关串口的句柄。比较常见的句柄还有窗口句柄,控制PC显示时会用到。
串口句柄对使用串口而言十分重要,可以说是HAL库操纵串口的核心部位。也体现出了HAL库封装度之高。
可以发现,串口句柄中还包含一个串口初始化结构体,在串口初始化函数中便是往这个对象中写入了相关的初始化数据,再经由初始化函数对MCU进行配置
再下一步:初始化MSP层
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
GPIO_InitTypeDef UART1_GPIO = {0};
if(huart->Instance == USART1)
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
UART1_GPIO.Pin = GPIO_PIN_9 | GPIO_PIN_10;
UART1_GPIO.Alternate = GPIO_AF7_USART1;
UART1_GPIO.Mode = GPIO_MODE_AF_PP;
UART1_GPIO.Pull = GPIO_PULLUP;
UART1_GPIO.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOA,&UART1_GPIO);
HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
}
这样一来,串口便初始化完毕了。我们拿简单的代码来测试数据收发是否能成功吧。
先来介绍串口收发相关的函数
HAL库函数 | 具体功能 |
---|
HAL_UART_Transmit | 阻塞模式下的串口发送函数 |
HAL_UART_Receive | 阻塞模式下的串口接收函数 |
HAL_UART_Transmit_IT | 非阻塞模式下的串口发送配置函数 |
HAL_UART_Receive_IT | 非阻塞模式下的串口接收配置函数 |
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
串口发送函数。该函数轮询发送DR寄存器状态,DR为空,则传输1字节给DR,重复此动作直到串口内部数据发送完毕,超时则返回
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
串口接收函数,该函数轮询接收DR寄存器状态,DR非空则从DR中获取内容存入pData,等待设置的时间,在时间内获得新的数据则接收放在前一个数据后,超时则返回
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
开启串口发送寄存器空中断(文章中若没有特殊说明,发送中断既是串口发送寄存器空中断)。注意!此并非以中断方式的串口发送函数,这个函数只是帮我们使能发送中断,而真正的发送,依靠的是串口中断服务函数中的UART_Transmit_IT()函数
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
开启串口接收寄存器非空中断(文章中若没有特殊说明,接收中断既是串口接收寄存器非空中断)同上,这个函数也并非中断方式的串口接收函数,而是帮我们开启了接收中断,真正的中断接收在中断服务函数中
此次讲解主要围绕着这几个函数与一些系统调用初始化会执行到的函数进行,以上函数请尽可能理解其工作原理。
另外,串口收发工作模式除轮询,中断外,还有DMA方式,DMA方式在计组(计算机组成原理,以下简称计组)中讲过原理,而要深究,最好要有计组的理论支撑。理论虽然不难,但在这里讲解还是略为复杂,毕竟本文的初衷是详解“串口初始化与使用”,而要好好地使用串口,笔者认为,理解了如何使用中断收发,就已经做到了,而中断收发的效率,较DMA方式也不差多少,再者中断方式可以以较复杂的模式处理收发数据,在优劣上不劣于DMA方式,只是各有千秋。所以,DMA方式执行的读写,在以后若有时间会专门做一次详解。此处不进行赘述。
调用HAL库的收发函数即可实现收发
Main.c的代码如下
串口收发理论
首先,我们来剖析一下,上面具体列出的几个函数的具体功能。
HAL_UART_Init:HAL库初始化函数。
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
{
if (huart == NULL)
{
return HAL_ERROR;
}
if (huart->Init.HwFlowCtl != UART_HWCONTROL_NONE)
{
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)
{
huart->Lock = HAL_UNLOCKED;
if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
UART_InitCallbacksToDefault(huart);
if (huart->MspInitCallback == NULL)
{
huart->MspInitCallback = HAL_UART_MspInit;
}
huart->MspInitCallback(huart);
#else
HAL_UART_MspInit(huart);
#endif
}
huart->gState = HAL_UART_STATE_BUSY;
__HAL_UART_DISABLE(huart);
UART_SetConfig(huart);
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));
__HAL_UART_ENABLE(huart);
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->gState = HAL_UART_STATE_READY;
huart->RxState = HAL_UART_STATE_READY;
return HAL_OK;
}
我们稍微理清一下这个函数的逻辑。库函数中基本都有许多选择分支,所以会显得比较长,但实际上执行的步骤并不多。可以看到,这里严谨地为每一种可能的状况都做出了回应,具体是在哪对这些设置进行修改,我们不需要关注。但是这个函数帮我们做了什么我们需要知道。所以,具体看以下几行代码
if (huart == NULL)
...
...
if (huart->Init.HwFlowCtl != UART_HWCONTROL_NONE)
...
...
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));
...
...
HAL_UART_MspInit(huart);
...
...
__HAL_UART_ENABLE(huart);
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->gState = HAL_UART_STATE_READY;
huart->RxState = HAL_UART_STATE_READY;
return HAL_OK;
可以发现,初始化函数的逻辑是这样的。首先判断传入句柄是否存在,不存在返回ERROR,存在则接着执行,下面的代码将句柄中的设置具体配置到串口中,然后调用初始化MSP层函数,最后使能这个外围设备,将所有的标志位都复位,返回HAL_OK。
HAL库对底层硬件层层封装,所以呈现在我们用户眼里的,就是简单的调用HAL_UART_Init即可完成对串口的初始化,但了解内部的代码帮我们做了什么是十分重要的,这将在我们介绍串口收发函数时,有更深刻的理解。
HAL_UART_DeInit:失能函数则主要将使能函数做的那些都关闭,内部逻辑简单,而且较少使用,为简洁易懂,不在此处贴出代码解析。
HAL_UART_MspInit:MSP层函数由用户编写,是物理层对应管脚、模式等的具体实现
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(huart->Instance==USART1)
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
}
函数具体操作了串口和对应的GPIO的时钟,最开始让对应GPIO和该串口的时钟使能,并将所有管脚配置都初始化好,最后设置中断优先级并使能中断
HAL_UART_MspDeInit:将MSPINIT使能的部分关闭,较少使用,逻辑简单,不贴出代码解析。
HAL_UART_Transmit:我们比较关心的部分来了,串口作为一个通信口,我们更多的关注一定是如何实现数据的收发和处理,所以这个函数是很重要的函数,函数将发送过程封装成了一个函数待我们使用,只要我们做好了串口初始化的工作,就可以拿这个函数以阻塞的方式发送我们想要发送的数据。输入参数分别为,要使用串口的串口句柄,待发送数据的地址,发送数据的大小,发送等待时间。前三个参数第一次接触都很好理解。这个发送等待时间,第一次接触可能会有点疑惑。我们来讲解一下。由于这个函数是以阻塞的方式发送数据,当程序执行到这一行之后,函数试图从data地址处读出指定长度大小的数据,但是此时,数据还为空,读不到。那怎么办?由于这是单片机,外设上的很多事情还等待刷新,不能一直阻塞在这个地方,所以设置了一个时间上限,到这个时间上限时不论有没有数据都跳出这个函数,执行接下来的代码,这就是发送等待时间。
所以这个函数实现:在指定的时间内,从指定的区域读取出指定长度的数据,用指定的串口发送,若超时则返回
我们来看内部的代码实现
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
uint16_t *tmp;
uint32_t tickstart = 0U;
if (huart->gState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
__HAL_LOCK(huart);
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->gState = HAL_UART_STATE_BUSY_TX;
tickstart = HAL_GetTick();
huart->TxXferSize = Size;
huart->TxXferCount = Size;
__HAL_UNLOCK(huart);
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;
}
huart->gState = HAL_UART_STATE_READY;
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
所以可以看出,串口发送函数在这里做了什么:首先在发送标志不为忙,且有数据发送的情况下,记录下发送的SIZE大小,然后用while对其进行循环发送,直到SIZE个数据被发送出去,做完一切之后返回HAL_OK
HAL_UART_Receive:其实理解了发送函数接收函数就同理了,不赘述占用篇幅
HAL_UART_Transmit_IT:非阻塞模式下发送数据函数。这个函数需要拿出来仔细讲。因为这个函数本身并不实现发送的功能
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
if (huart->gState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
__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;
__HAL_UNLOCK(huart);
__HAL_UART_ENABLE_IT(huart, UART_IT_TXE);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
代码如上。可以发现代码长度短了很多,代码很多配置相关的,都和阻塞发送一样。唯一需要注意的是加黑的那一行。这一行代码的意思是开启串口发送空中断。只要串口发送DR寄存器中的数据为空则产生,请求新数据,而具体的发送则在中断服务函数中。这个函数只是配置中断使能的函数。接收函数也同理
串口中断接收配置函数:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
if (huart->RxState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
__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;
__HAL_UNLOCK(huart);
__HAL_UART_ENABLE_IT(huart, UART_IT_PE);
__HAL_UART_ENABLE_IT(huart, UART_IT_ERR);
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
接收函数唯一不同的就是多开了两个错误中断。具体接收也在中断服务函数中
扩展资料:
在硬件层串口如何实现发送的?我们需要做哪些事情?哪些事情是串口硬件设备做的?
这个问题在笔者刚接触串口时困扰了很久,而且资料很少,因为涉及到内部的寄存器控制,现在不是主流,但对理解串口十分重要。
内部原理图如下:
串口硬件做了哪些?**串口硬件当配置好内部的CR寄存器,开始按指定的要求开始工作时,检测到DR寄存器内非空,便利用SR寄存器开始移位进行发送。所以我们发送数据的过程,就是往DR寄存器内部写入数据的过程。**串口通信硬件在DR寄存器发送结束后也会产生相应的标志位,告知MCU发送完毕,然后检测到标志位,我们就可以往DR寄存器中填入新的内容。
我们需要做哪些事情?如果用HAL库的话,只用调用发送函数就好了!如果不是用hal库是用寄存器的话,我们刚刚说了——发送的过程,就是往DR寄存器中写入数据的过程。所以我们要自己利用寄存器发送数据的话,就需要自己把数据从发送缓冲区移入到DR寄存器。换句话说,我们要做到——把自己的数据填入DR寄存器。就可以实现发送。当然大前提是串口已经初始化好了。初始化配置各种寄存器的函数就是相应的初始化函数。所以这些函数大大地减少了我们开发所需要的时间,也体现了封装的重要性。
在硬件层面如何实现发送的?过程如下。由MCU运算或采集到相关的数据,再经过代码填充到发送缓冲中,执行把发送缓冲的数据按序循环发送到DR寄存器中,DR寄存器由SR移位寄存器指挥发送到外部,循环这个过程直到数据发送完毕
代码执行
重要的函数介绍完毕了。最后我们开始看数据发送的过程在代码层面上如何执行的。这个过程我们需要关注的是,我们输入的代码,在MCU上电之后具体是如何执行的。如果使用了STLINK的数据线(其他调试线也行),可以进入DEBUG模式一步一步移动查看代码是按什么顺序执行的,具体调用了哪些底层的代码。虽然这在使用串口上没有多大的作用,但对实际理解MCU对串口是如何配置的,还是很有帮助的。而理解了这个,更能加深我们对MCU以及串口的理解
首先是配置过程。配置过程代码先要求MCU初始化了总线时钟,初始化了相应GPIO时钟,初始化了HAL库,调用该串口初始化的初始化函数,这个函数调用了HAL_UART_Init去按用户设置的初始化串口,然后再调用了用户编写的HAL_MSP_Init()初始化MSP层,配置相应的管脚和输入输出模式等,并开启了串口的时钟。这样一来,被指定的串口设备就变得可用
接下来是收发过程。以中断收数据为例,中断发数据不太常用,在理论上也是同理。中断接收,需要开启中断接收DR寄存器非空中断。每当中断接收寄存器中有数据输入,就会产生中断信号告知MCU,MCU执行相关操作,在数据全部被读入后,中断标志消失。上面介绍到的中断接收函数
HAL_UART_Receive_IT()帮我们打开了接收中断,在中断服务函数中调用的UART_Receive_IT()帮我们将数据放入了我们给串口指定的数据缓冲区。也就是说,使用HAL库读取,只需要调用HAL_UART_Receive_IT()函数,指定一个缓冲区。有数据产生后HAL库的内核代码会帮我们处理好所有和IO有关的操作,至于我们如何处理读入的数据,就可以依靠串口中断回调函数,对缓冲区内的数据进行相关的运算和执行相应的指令。
另外,稍微讲一下我对发送中断的看法。发送中断个人在使用时,正常使用时没有问题的,但由于数据是发出不是读入,很多情况下不需要回调函数再进行额外的操作。而且,将数据从发送缓冲区移入DR寄存器的操作需要MCU来进行,中断模式节约的时间仅有等待DR寄存器内的数据全部发出的时间。在MCU运算速度不高的情况下,发送中断节约的时间和提高的效率不高,操作起来有些繁琐。当然,目前的32位MCU的处理速度确实比较快,在MCU资源被大部分利用上的情况下,中断模式很有必要,正常使用时其实阻塞发送带来的影响几乎可以忽略不计。这也是我与一位从事嵌入式开发的资深工作者交流后,他给我的意见。经过交流探讨后,我较为支持这种想法。但是节省MCU资源总是一件好事,所以写出我对发送中断的看法,只是对发送的阻塞模式和非阻塞模式提出一点见解,并且让大家能够大概思考一下发送的阻塞模式和非阻塞模式的效率差距大概有多少。实际工程中,是否使用阻塞模式发送,还是看个人的想法。
至于接收中断,接收中断是十分必要的。发送的行为是MCU主动的行为,所以节约的时间只是等待串口DR寄存器中数据全部发出的时间,而接收则是被动的行为,阻塞将会让CPU的工作长时间停止,等待数据到来,然后处理数据。这是对CPU资源极大的浪费。读取数据,请尽可能不要使用阻塞模式。
因为本人水平实在有限,文章内容若有错误,敬请谅解。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)