USART简介
通用同步异步收发器(USART)提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。
USART利用分数波特率发生器提供宽范围的波特率选择。 一个波特率寄存器(USART_BRR),12位的整数和4位小数
任何USART双向通信至少需要两个脚: 接收数据输入(RX)和发送数据输出(TX)
当发送器被激活并且不发送数据时,TX引脚处于高电平 在起始位期间,TX脚处于低电平,在停止位期间处于高电平。
发送和接收由一共用的波特率发生器驱动,当发送器和接收器的使能位分别置位时,分别为其产生时钟。
空闲符号和端口符号
- 空闲符号是一个帧,并且全部由1组成,空闲帧后面跟着下一个数据帧的起始位。(空闲帧包含停止位)
空闲符号被视为完全由’1’组成的一个完整的数据帧,后面跟着包含了数据的下一帧的开始位(‘1’的位数也包括了停止位的位数)。
- 断开符号是一个帧,并且全部有0组成,断开帧后面跟着一个停止位。
断开符号被视为在一个帧周期内全部收到’0’(包括停止位期间,也是’0’)。在断开帧结束时,发送器再插入1或2个停止位(‘1’)来应答起始位。
当起始位为1位,数据位为8位,停止位为1位时:
-
空闲帧包括了停止位。 -
断开帧是10位低电平,后跟停止位。
发送(TX)器
- 可设置寄存器来确定数据位是8位还是9位。
- 通过置位TE位来使能发送,发送移位寄存器中的数据将会被输出到TX引脚上。相应的时钟脉冲在CK脚上输出。
- 在TX引脚上首先移出数据的最低有效位LSB,先发送LSB
- TE位被激活后将发送一个空闲帧(空闲符号)
配置步骤
串口数据发送过程:
将数据写入DR(一个字节的大小)中,DR会将数据复制到TDR中,TDR会将数据复制到发送移位寄存器中,从LSB(最低有效位)一位位发送到TX引脚上,实现数据的发送。 其中: 每发送完一个字节(发送数据寄存器TDR为空)TXE标志会被置位。 全部数据发送完毕后TC标志会被置位。
接收器
USART可以根据USART_CR1的M位接收8位或9位的数据字 在USART接收期间,数据的最低有效位首先从RX脚移进到接收数据寄存器中。
空闲符号 当一空闲帧被检测到时,其处理步骤和接收到普通数据帧一样,但如果IDLEIE位被设置将产生一个中断。
配置步骤
串口数据接收过程: 接收移位寄存器从RX引脚接收数据,从数据最低有效位开始(因为数据是先发送最低有效位)接收数据,当接收到一个字节的数据时,将数据复制到RDR寄存器和DR寄存器中,此时RXNE标志被置位,通过读取DR寄存器来获取接收的一个字节数据。
分数波特率
波特率寄存器(USART_BRR),12位的整数和4位小数 用12位二进制数表示整数部分,4位表示小数部分。
波特率计算公式 设置波特率位115200,fck = 36MHz,则USARTDIV = 19.5 整数部分为19 << 4 = 304 小数部分为0.5*16 = 8 USART_BRR寄存器的值位304+8=312=0x138
中断
注意:USART的各种中断事件被连接到同一个中断向量
demo
串口异步通信-阻塞式发送-仿printf发送
采用STM32F103C8T6单片机,KeilMDK5.32版本
串口异步通信,,仅开启发方向,阻塞式发送数据(仿printf发送)。 PC13控制LED灯,LED灯的亮灭指示程序正常运行。
串口仿printf发送函数
#define USART1_SENDBUFF_MAX_BYTES 100U
int USART1Printf(const char* format, ...)
{
static char sendBuff[USART1_SENDBUFF_MAX_BYTES] = { 0 };
int bytes = 0;
va_list list;
va_start(list, format);
bytes = vsprintf(sendBuff, format, list);
va_end(list);
CLEAR_BIT(huart1.Instance->SR, USART_SR_TC_Msk);
HAL_UART_Transmit(&huart1, (void*)sendBuff, bytes, INFINITE);
return bytes;
}
HAL_UART_Transmit() 是HAL库串口阻塞式发送函数,这个函数没有用到TXE与TC标志位,发送结束后也没有清除标志。
主程序中的代码
while (1)
{
HAL_Delay(1000);
USART1Printf("Hello World! %hhu\r\n", times);
times++;
PCout(13) = !PCin(13);
}
STM32CubeMX配置
工程文件下载链接
串口异步通信-非阻塞式发送-仿printf发送
采用STM32F103C8T6单片机,KeilMDK5.32版本
串口异步通信,,仅开启发方向,非阻塞式发送数据(仿printf发送)。 PC13控制LED灯,LED灯的亮灭指示程序正常运行。
串口仿printf发送函数
#define USART_SENDBUFF_MAX_BYTES 100U
int USARTPrintf(UART_HandleTypeDef *huart, const char* format, ...)
{
static char sendBuff[USART_SENDBUFF_MAX_BYTES] = { 0 };
int bytes = 0;
va_list list;
va_start(list, format);
bytes = vsprintf(sendBuff, format, list);
va_end(list);
CLEAR_BIT(huart->Instance->SR, USART_SR_TC_Msk);
HAL_UART_Transmit_IT(huart, (void*)sendBuff, bytes);
return bytes;
}
HAL_UART_Transmit_IT() 函数开启了TXE中断,并在最后一个字节发送结束之后开启TC中断。如下图
HAL_UART_IRQHandler()函数 这里有2个函数需要注意
发送完成回调函数HAL_UART_TxCpltCallback() 在进入该函数前,TXE与TC中断已关闭。
在发生串口回调函数(TXE/TC)时,并且全部数据发送完毕,HAL_UART_IRQHandler()调用UART_EndTransmit_IT(),UART_EndTransmit_IT()在关闭TC中断后进入HAL_UART_TxCpltCallback()。
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
UNUSED(huart);
}
工程文件下载链接
串口异步通信-非阻塞式接收数据
HAL库的阻塞式接收数据函数HAL_UART_Receive() 使用示例HAL_UART_Receive(&huart1, (void*)receiveBuff, sizeof(receiveBuff), INFINITE);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
非阻塞式接收数据函数HAL_UART_Receive_IT() 该函数会开启以下中断
- 奇偶检验错(PE)
- 帧错误、噪声错误、溢出错误(NE或ORT或FE)
- UART数据寄存器非空中断(RXNE)
并且会将huart的接收类型设置为HAL_UART_RECEPTION_STANDARD 接收类型值有
HAL_UART_RECEPTION_STANDARD 标准接收HAL_UART_RECEPTION_TOIDLE 接收至完成或闲置事件
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
HAL_UART_IRQHandler()函数会调用UART_Receive_IT()进行串口接收中断处理。UART_Receive_IT()会将DR寄存器中的数据复制到串口接收缓存区中,如果接收数据的字节数目满足指定的数目,则会关闭中断(RXNE,NE或ORT或FE,PE),根据接收类型进入相应的接收完成回调函数中。
下面示例串口非阻塞式接收数据 采用STM32F103C8T6单片机,KeilMDK5.32版本
串口异步通信,开启收发方向,阻塞式发送(仿printf发送);非阻塞式接收数据。 PC13控制LED灯,LED灯的亮灭指示接收到数据。
程序初始化完成之后,开启接收中断。 在接收完成回调函数中,重新开启接收中断(因为在进入接收回调函数前,所有与接收相关的中断已经关闭)
STM32CubeMX配置 接收完成回调函数HAL_UART_RxCpltCallback()
在进入该函数前,以下中断已关闭。
- 奇偶检验错(PE)
- 帧错误、噪声错误、溢出错误(NE或ORT或FE)
- UART数据寄存器非空中断(RXNE)
extern char receiveBuff[15];
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart == &huart1)
{
PCout(13) = !PCin(13);
HAL_UART_Receive_IT(&huart1, (void*)receiveBuff, sizeof(receiveBuff));
}
}
注意一下:主要当全部数据都接收完毕(比如,想要收到15个字节的数据,当收到第15个字节的数据的时候)才会进入接收完成回调函数(在该函数中重新开启串口空闲接收)HAL_UART_RxCpltCallback()
工程文件下载链接
串口异步通信-串口空闲中断接收,未使用DMA
采用STM32F103C8T6单片机,KeilMDK5.32版本
串口异步通信,开启收发方向,阻塞式发送(仿printf发送);非阻塞式接收数据。 PC13控制LED灯,LED灯的亮灭指示接收到数据。
程序初始化完成之后,开启接收空闲中断。 在接收空闲回调函数中,重新开启接收空闲中断(因为在进入接收回调函数前,所有与接收相关的中断已经关闭)
串口空闲中断:检测到有数据被接收后,当总线上在一个字节的时间内没有再接收到数据的时候,触发串口空闲中断。
调用HAL_UARTEx_ReceiveToIdle_IT() 来使用串口空闲中断,该函数会将串口接收类型设置为HAL_UART_RECEPTION_TOIDLE ,开启RXNE,NE或ORT或FE,PE和串口空闲中断(IDLE)
在RXNE中断中,将数据(一个字节)复制到串口接收缓冲区中
串口空闲回调函数 在该函数中将LED亮灭取反,并重新开启串口空闲接收
extern char receiveBuff[15];
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart == &huart1)
{
PCout(13) = !PCin(13);
HAL_UARTEx_ReceiveToIdle_IT(&huart1, (void*)receiveBuff, sizeof(receiveBuff));
}
}
HAL_UART_IRQHandler() 函数中有2处可以进入串口空闲回调函数
一处是UART_Receive_IT() 当数据全部接收完毕时(比如,想要收到15个字节的数据,实际也收到15个字节的数据),则进入串口空闲回调函数,在进入该函数前会将接收类型设置为HAL_UART_RECEPTION_STANDARD 另外一处是数据没有接收完毕(比如,想要收到15个字节的数据,实际收到少于15个字节的数据),也会进入串口空闲中断。
STM32CubeMX配置与非阻塞式接收数据demo一样。
工程文件下载链接
DMA式收发数据
利用DMA发送数据
注意:根据需要来确定是不是要DMA控制寄存器中开启存储器地址自增的位
TXE事件后,DMA传输指定地址上的数据到DR中和从DR中传输数据到TX引脚上是同时进行。
利用DMA接收数据
RXNE事件后,从RX引脚读出数据到DR寄存器中和DR寄存器中的数据复制的指定的地址中式同时进行的
串口异步通信-DMA式收发数据-仿printf发送
采用STM32F103C8T6单片机,KeilMDK5.32版本
串口异步通信,开启收发方向,DMA式收发数据(仿printf发送)。 收发的DMA不在循环模式下(单次)。 PC13控制LED灯,LED灯的亮灭指示接收到数据。 在STM32CubeMX中需要同时开启DMA与串口全局中断
DMA发送
调用HAL_UART_Transmit_DMA() 来使用DMA发送数据,会开启DMA传输错误(TE),传输完成(TC)和传输完成一半(HT)中断。注意并没有开启串口的TC中断 一般用不到传输完成一半中断,如果不需要这个中断,可以将HAL_UART_Transmit_DMA() 函数中 huart->hdmatx->XferHalfCpltCallback = UART_DMA_TxHalfCplt; 改为huart->hdmatx->XferHalfCpltCallback = NULL; 即可
在DMA传输完成之后,开启串口的TC中断(UART_DMATransmitCplt() 中开中断)
UART_DMATransmitCplt() 函数如下
通过观察UART_DMATransmitCplt() 函数源码,在DMA单次模式下,需要开启串口全局中断,在TC中断发生后,进入串口发送完成回调函数。
串口发送完成回调函数
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
UNUSED(huart);
}
DMA接收
调用HAL_UART_Receive_DMA() 函数来使能串口DMA接收。 该函数会使能NE或ORT或FE,PE 并且也会开启DMA传输错误(TE),传输完成(TC)和传输完成一半(HT)中断 注意:并没有开启RXNE中断
在DMA传输完成之后进入中断服务函数后, UART_DMAReceiveCplt() 函数调用HAL_UART_RxCpltCallback() 串口接收完成回调函数 与DMA发送类似,如果不需要DMA传输完成一半的中断可以更改源码UART_Start_Receive_DMA()
huart->hdmarx->XferHalfCpltCallback = NULL; 以前是UART_DMARxHalfCplt;
STM32CubeMX配置
接收回调函数中重新开启DMA接收(因为DMA处于单次模式下)
工程文件下载链接
串口异步通信-DMA式收发数据-仿printf发送-接收的DMA循环
采用STM32F103C8T6单片机,KeilMDK5.32版本
串口异步通信,开启收发方向,DMA式收发数据(仿printf发送)。 发的DMA不在循环模式下(单次);接收的DMA在循环模式下。 PC13控制LED灯,LED灯的亮灭指示接收到数据。 在STM32CubeMX中需要同时开启DMA与串口全局中断
在每次DMA传输完成之后,DMA中断服务函数中,检查DMA是否处于循环模式,如果是则不关闭传输完成TC和传输完成一半HT中断 中断服务函数调用UART_DMAReceiveCplt()函数,再次判断串口接收类型,根据类型进入相应的传输完成回调函数。 调用HAL_UART_Receive_DMA() 函数,该函数不会开启RXNE中断。
因为DMA是循环模式,所有不需要重新开启接收
STM32CubeMX配置
工程文件下载链接
串口异步通信-DMA式收发数据-仿printf发送-串口空闲接收
采用STM32F103C8T6单片机,KeilMDK5.32版本
串口异步通信,开启收发方向,DMA式收发数据(仿printf发送)。 发的DMA不在循环模式下(单次);接收的DMA在单次模式下。开启串口接收空闲中断 PC13控制LED灯,LED灯的亮灭指示接收到数据。 在STM32CubeMX中需要同时开启DMA与串口全局中断
通过调用函数HAL_UARTEx_ReceiveToIdle_DMA 来使能DMA接收。 该函数会开启DMA相应的中断传输完成,传输完成一半,传输错误 和串口空闲中断,NE或ORT或FE,PE 注意没有开启RXNE中断
接收回调函数为HAL_UARTEx_RxEventCallback() (在该函数中重新开启串口DMA空闲接收) 分2种情况
工程文件下载链接
串口异步通信-DMA式收发数据-仿printf发送-接收DMA循环-串口空闲接收
采用STM32F103C8T6单片机,KeilMDK5.32版本
串口异步通信,开启收发方向,DMA式收发数据(仿printf发送)。 发的DMA不在循环模式下(单次);接收的DMA在循环模式下。开启串口接收空闲中断 PC13控制LED灯,LED灯的亮灭指示接收到数据。 在STM32CubeMX中需要同时开启DMA与串口全局中断
调用HAL_UARTEx_ReceiveToIdle_DMA() 开启串口DMA空闲接收, 该函数会开启DMA相应的中断传输完成,传输完成一半,传输错误 和串口空闲中断,NE或ORT或FE,PE 注意没有开启RXNE中断
接收回调函数为HAL_UARTEx_RxEventCallback() (不需要在该函数中重新开启串口DMA空闲接收)
同样的,
- 接收的数据数目少于指定数目
通过串口空闲中断进入接收回调函数中断函数 - 接收到指定数目的数据
通过DMA中断服务函数进入接收回调函数中断函数
在DMA传输完成一半中断关闭的条件下。按空闲中断的定义来说,接收到指定数据的数据也会触发串口空闲中断,从而在退出DMA中断后进入串口空闲中断,也就是触发2次中断(一次是DMA的中断传输完成中断,一次是串口空闲中断)。 但是在实验调试中,只触发一次中断DMA传输完成中断。在进入HAL_DMA_IRQHandler() 函数前,调试发现SR寄存器被置位,在进入函数后,串口SR寄存器的IDLE位被自动复位,与手册的需要软件序列来复位说法不同, 猜想读串口DR寄存器也会清除IDLE标志位。 设计实验,开启串口空闲中断,发送数据,在串口中断服务函数中,先读DR寄存器,发现IDLE标志位被复位。 查看手册 串口DMA接收时序图 在DMA的传输完成标志位被置位后,进入DMA中断服务函数,此时DMA正在读串口的DR寄存器,从而将IDLE标志位复位。 但是,为了安全起见,还是在串口接收完成回调函数中添加软件系列复位IDLE标志的代码,如下 另外一个注意点 在接收的数据数目少于指定数目时候(DMA把串口DR寄存器的数据复制到串口接收缓冲区数组array中),比如指定接收15个字节数据,但是实际接收到7个字节数据,此时触发串口空闲中断,进入串口回调函数中处理这7个字节数据,存放下一个待接收字节的地址是array + 7,而不是array(从数组第0个字节开始存放接收到的数据)。
可通过如下方法修改 先失能DMA通道,修改修改DMA数据传输数量,再使能DMA通道
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart == &huart1)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
__HAL_DMA_DISABLE(huart->hdmarx);
WRITE_REG(huart->hdmarx->Instance->CNDTR, sizeof(receiveBuff));
__HAL_DMA_ENABLE(huart->hdmarx);
PCout(13) = !PCin(13);
}
}
工程文件下载链接
STM32CubeMX使用DMA的注意点
使用STM32CubeMX配置得工程文件中,任何使用DMA的外设初始化函数必须得再DMA初始化后再进行外设初始化。否则DMA无效
|