串口作为MCU的重要外部接口,同时也是软件开发重要的调试手段。通用同步异步收发器(USART)能够灵活地与外部设备进行全双工数据交换,满足外部设备对工业标准 NRZ 异步串行数据格式的要求。USART 通过小数波特率发生器提供了多种波特率,通过配置多个缓冲区使用DMA可实现高速数据通信。
任何 USART 双向通信均需要至少两个引脚:接收数据输入引脚(RX)和发送数据输出引脚(TX)。在同步模式下,需要使用时钟输出引脚(CK),在硬件流控制模式下,需要使用清除发送引脚(CTS)和发送请求引脚(RTS)。
串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口,通讯双方的数据包格式要一致才能正常收发数据,STM32中串口通讯数据包内容有:起始位、数据位、奇偶校验位、停止位:
- 起始位和停止位:串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑0的数据位表示,而数据包的停止信号可由 0.5、1、1.5或 2个逻辑 1的数据位表示,只要双方约定一致即可。
- 数据位:在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为 8或 9位长。
- 奇偶校验位:在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。有无奇偶校验位数据帧格式如下:
数据长度 | 奇偶校验 | 数据帧格式 |
8 | 无 | 起始位+8位数据+停止位 |
8 | 有 | 起始位+7位数据+校验位+停止位 |
9 | 无 | 起始位+9位数据+停止位 |
9 | 有 | 起始位+8位数据+校验位+停止位 |
奇校验是奇校验是指有效数据和校验位中“1”的个数为奇数,比如一个8位字长的有效数据为:00110101,此数据总共有4个“1”,为达到奇校验效果,校验位为“1”,最后传输的数据将是8位的有效数据加上1位的校验位总共9位。偶校验与奇校验要求刚好相反,要求帧数据和校验位中“1”的个数为偶数,比如“00110101”,此数据帧“1”的个数为4个,所以偶校验位为“0”。
- 波特率:本实验主要讲解的是串口异步通讯,异步通讯中由于没有时钟信号,所以两个通讯设备之间约定好波特率,即每个码元的长度,以便对信号进行解码,常见的波特率为4800、9600、115200等。
USART功能框图如下所示:
①:功能引脚
TX:发送数据输出引脚。
RX:接收数据输入引脚。
SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚没有具体外部引脚。
nRTS:发送数据请求引脚,用于指示UART已经准备好接收数据。低电平有效,该引脚只适用于硬件流控制。
nCTS:清除发送引脚,用于在当前传输结束时阻止数据发送。低电平有效,该引脚只适用于硬件流控制。
STM32F407ZGT6芯片的USART引脚分布如下所示:
STM32F407ZGT6 有四个 USART 和两个 UART,其中 USART1和 USART6 的时钟来源于 APB2 总线时钟,其最大频率为 84MHz,其他四个的时钟来源于 APB1总线时钟,其最大频率为 42MHz。
UART只是异步传输功能,所以没有 SCLK、nCTS和 nRTS 功能引脚。
②:数据寄存器
USART_DR包含了已发送的数据或者接收到的数据。USART_DR 实际是包含了两个寄存器,一个专门用于发送的可写 TDR,一个专门用于接收的可读 RDR。当进行发送操作时,往 USART_DR 写入数据会自动存储在 TDR内;当进行读取操作时,向 USART_DR读取数据会自动提取 RDR数据。
TDR 和 RDR 都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的,发送时把 TDR内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收时把接收到的每一位数据保存在接收移位寄存器内然后才转移到 RDR。
③:控制器
USART 有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。使用 USART 之前需要向 USART_CR1寄存器的 UE位置 1 使能 USART。发送或者接收数据字长可选 8位或 9位,由 USART_CR1的 M位控制。
④:波特率生成
波特率指数据信号对载波的调制速率,它用单位时间内载波调制状态改变次数来表示,单位为波特。比特率指单位时间内传输的比特数,单位bit/s(bps)。对于USART波特率与比特率相等,波特率越大,传输速率越快。波特率的常用值有2400、9600、19200、115200。
串口通讯有三种方式用来处理数据,包括:轮询方式、中断方式、DMA方式。本实验只介绍轮询方式,其它方式在后面的实验中讲解。轮询方式是指程序中循环查询串口寄存器,如果寄存器接收到数据,则进行相应的数据处理。
HAL库串口部分相关函数解析如下:
在HAL库中,串口使用的模式不同,调用的初始化函数也不一样:
- 当串口用于异步模式时,使用函数:
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);
- 当串口用于半双工模式时,使用函数:
HAL_StatusTypeDef HAL_HalfDuplex_Init(UART_HandleTypeDef *huart);
- 当串口用于Lin模式时,使用函数:
HAL_StatusTypeDef HAL_LIN_Init(UART_HandleTypeDef *huart, uint32_t BreakDetectLength);
- 当串口用在多处理器模式时,使用函数:
HAL_StatusTypeDef HAL_MultiProcessor_Init(UART_HandleTypeDef *huart, uint8_t Address, uint32_t WakeUpMethod);
UART HAL中最常用的宏列表如下:
1、__HAL_UART_ENABLE: 使能串口外设
2、__HAL_UART_DISABLE: 使能串口外设
3、__HAL_UART_GET_FLAG : 检查是否设置了指定的UART标志
4、__HAL_UART_CLEAR_FLAG : 清除指定的UART挂起标志
5、__HAL_UART_ENABLE_IT: 启用指定的UART中断
6、__HAL_UART_DISABLE_IT: 禁用指定的UART中断
7、__HAL_UART_GET_IT_SOURCE: 检查指定的UART中断是否发生
串口中止传输函数:
HAL_StatusTypeDef HAL_UART_Abort(UART_HandleTypeDef *huart);
在这个函数中,可以中止串口在中断或DMA模式下的传输。函数主要执行:
- 禁用相关中断(帧错误中断、噪声中断、溢出中断等)。
- 禁用寄存器中DMA的传输(DMA-TX,DMA-RX)
- 如果DMA开启状态,则通过调用HAL_DMA_Abort来中止DMA传输。
- 将RxState和gState句柄设置为准备状态(这两个句柄会在下次传输数据中使用)。
函数如下:
HAL_StatusTypeDef HAL_UART_Abort(UART_HandleTypeDef *huart)
{
/* 禁用TXEIE、TCIE、RXNE、PE和ERR(帧错误、噪声错误、溢出错误)中断 */
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE | USART_CR1_TXEIE | USART_CR1_TCIE));
CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
/* 如果串口DMA TX 使能,那么就禁用*/
if(HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAT))
{
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);
/* 中止UART DMA Tx通道:使用阻塞DMA中止API(没有回调) */
if(huart->hdmatx != NULL)
{
/* 将UART DMA中止回调设置为Null。
DMA中止过程结束时没有回调执行 */
huart->hdmatx->XferAbortCallback = NULL;
HAL_DMA_Abort(huart->hdmatx);
}
}
/* 如果串口DMA Rx 使能,那么就禁用 */
if(HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
{
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
/* 中止UART DMA Rx通道:使用阻塞DMA中止API(没有回调) */
if(huart->hdmarx != NULL)
{
/* 将UART DMA中止回调设置为Null。
DMA中止过程结束时没有回调执行 */
huart->hdmarx->XferAbortCallback = NULL;
HAL_DMA_Abort(huart->hdmarx);
}
}
/* 重置串口发送/接收转换个数*/
huart->TxXferCount = 0x00U;
huart->RxXferCount = 0x00U;
/* 复位错误代码 */
huart->ErrorCode = HAL_UART_ERROR_NONE;
/* 将RxState和gState句柄设置为准备状态(发送接收函数里面用到)*/
huart->RxState = HAL_UART_STATE_READY;
huart->gState = HAL_UART_STATE_READY;
return HAL_OK;
}