第5章 STM32F4串口简介
第五章 STM32F4串口简介
1. 导入
STM32F4 的串口资源相当丰富的,功能也相当强劲。 ALIENTEK 探索者 STM32F4 开发板所使用的 STM32F407ZGT6 最多可提供 6 路串口,有分数波特率发生器、支持同步单线通信和半双工单线通讯、支持 LIN、 支持调制解调器操作、 智能卡协议和 IrDA SIR ENDEC 规范、具有 DMA 等。
接下来我们将主要从库函数操作层面结合寄存器的描述,告诉你如何设置串口,以达到我们最基本的通信功能。
我们将实现利用串口 1 不停的打印信息到电脑上,同时接收从串口发过来的数据,把发送过来的数据直接送回给电脑。 探索者 STM32F4 开发板板载了 1 个 USB 串口和 2 个 RS232 串口,我们本章介绍的是通过 USB 串口和电脑通信。
端口复用功能已经讲解过,对于复用功能的 IO,我们首先要使能 GPIO 时钟,然后使能相应的外设时钟,同时要把 GPIO 模式设置为复用。这些准备工作做完之后,剩下的当然是串口参数的初始化设置,包括波特率,停止位等等参数。在设置完成只能接下来就是使能串口,这很容易理解。
2. 串口使用基本步骤
-
串口时钟使能, GPIO 时钟使能。
-
设置引脚复用器映射:调用 GPIO_PinAFConfig 函数。
-
GPIO 初始化设置:要设置模式为复用功能。
-
串口参数初始化:设置波特率,字长,奇偶校验等参数。
-
开启中断并且初始化 NVIC,使能中断(如果需要开启中断才需要这个步骤)。
-
使能串口。
-
编写中断处理函数:函数名格式为 USARTxIRQHandler(x 对应串口号)。
下面, 我们就简单介绍下这几个与串口基本配置直接相关的几个 HAL 库函数。这些函数和定义主要分布在 stm32f4xx_hal_usart.h 和 stm32f4xx_hal_usart.c 文件中。
3. 相关HAL库函数
3.1 串口时钟和GPIO时钟使能
串口作为 STM32 的一个外设, HAL 库为其配置了串口初始化函数。接下来我们看看串口初始化函数 HAL_UART_Init 相关知识,定义如下:
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);
该函数只有一个入口参数 huart,为 UART_HandleTypeDef 结构体指针类型,我们俗称其为串口句柄,它的使用会贯穿整个串口程序。
一般情况下,我们会定义一个 UART_HandleTypeDef结构体类型全局变量,然后初始化各个成员变量。接下来我们看看结构体 UART_HandleTypeDef的定义:
typedef struct
{
USART_TypeDef *Instance;
UART_InitTypeDef Init;
uint8_t *pTxBuffPtr;
uint16_t TxXferSize;
uint16_t TxXferCount;
uint8_t *pRxBuffPtr;
uint16_t RxXferSize;
uint16_t RxXferCount;
DMA_HandleTypeDef *hdmatx;
DMA_HandleTypeDef *hdmarx;
HAL_LockTypeDef Lock;
__IO HAL_UART_StateTypeDef State;
__IO uint32_t ErrorCode;
}UART_HandleTypeDef;
- USART_TypeDef *Instance:
- 指向具体的 UART 外设实例,例如
USART1
,USART2
等,用于指明你要使用哪个 UART 硬件。
- UART_InitTypeDef Init:
- 包含 UART 初始化参数的结构体,通常包括波特率、数据位、停止位、校验位等配置。这些参数在调用初始化函数时会被使用。
- uint8_t *pTxBuffPtr:
- 指向用于发送数据的缓冲区。在 UART 发送数据时,UART 驱动会从这个缓冲区读取数据。
- uint16_t TxXferSize:
- 需要发送的数据字节总数,表示准备发送的字节长度。
- uint16_t TxXferCount:
- 当前已发送的数据字节数,通常在发送过程中更新,以便跟踪发送进度。
- uint8_t *pRxBuffPtr:
- 指向用于接收数据的缓冲区。UART 驱动将把接收到的数据存储在这个缓冲区中。
- uint16_t RxXferSize:
- 需要接收的数据字节总数,表示期望接收到的字节长度。
- uint16_t RxXferCount:
- 当前已接收的数据字节数,通常在接收过程中更新,以便跟踪接收进度。
- DMA_HandleTypeDef *hdmatx:
- 指向与 UART 发送相关的 DMA 句柄,如果使用 DMA 模式进行发送,相关的 DMA 配置将通过这个句柄来管理。
- DMA_HandleTypeDef *hdmarx:
- 指向与 UART 接收相关的 DMA 句柄,如果使用 DMA 模式进行接收,相关的 DMA 配置将通过这个句柄来管理。
- HAL_LockTypeDef Lock:
- 用于保护结构体的并发访问,确保在多个任务或中断情况下对 UART 资源的安全访问。
- __IO HAL_UART_StateTypeDef State:
- 表示 UART 的当前状态,如空闲、忙碌、错误等。通过这个状态可以判断 UART 目前的工作情况。
- __IO uint32_t ErrorCode:
- 用于存储 UART 操作中的错误代码,方便调试和错误处理。
该结构体成员变量非常多,一般情况下载调用函数 HAL_UART_Init 对串口进行初始化的时候,我们只需要先设置 Instance 和 Init 两个成员变量的值。接下来我们依次解释一下各个成员变量的含义。
3.1.1 INSTANCE
Instance 是 USART_TypeDef 结构体指针类型变量,它是执行寄存器基地址,实际上这个基地址 HAL 库已经定义好了,如果是串口 1,取值为 USART1 即可。
3.1.2 INIT
Init 是 UART_InitTypeDef 结构体类型变量,它是用来设置串口的各个参数,包括波特率,停止位等,它的使用方法非常简单。 UART_InitTypeDef 结构体定义如下:
typedef struct
{
uint32_t BaudRate; // 波特率
uint32_t WordLength; // 字长
uint32_t StopBits; // 停止位
uint32_t Parity; // 奇偶校验
uint32_t Mode; // 收/发模式设置
uint32_t HwFlowCtl; // 硬件流设置
uint32_t OverSampling;// 过采样设置
}UART_InitTypeDef
- BaudRate (波特率)
- 意义:通信的速度,通常以比特每秒 (bps) 表示。常见值包括 9600, 115200 等。
- 示例:如果设置
BaudRate = 115200
,则串口通信速度为 115200 bps。
- WordLength (字长)
- 意义:每个数据字节的位数。常见选项包括 8 位和 9 位。
- 示例:如果设置
WordLength = 8
,则每个字节将由 8 位组成。
- StopBits (停止位)
- 意义:数据传输结束后发送的位数,用于指示一帧数据的结束。可以设置为 1、1.5 或 2。
- 示例:如果设置
StopBits = 1
,则每帧数据结束时会有一个停止位。
- Parity (奇偶校验)
- 意义:用于检测传输错误的一种方法。选项通常为 None (无)、Even (偶校验) 和 Odd (奇校验)。
- 示例:如果设置
Parity = None
,则不使用奇偶校验。
- Mode (收/发模式设置)
- 意义:设置 UART 的工作模式,通常包括接收 (RX)、发送 (TX) 或双向 (TX/RX)。
- 示例:如果设置
Mode = TX_RX
,则 UART 可以同时发送和接收数据。
- HwFlowCtl (硬件流控制)
- 意义:用于流控制的硬件支持,常见选项包括 None (无)、RTS/CTS (请求发送/清除发送)。
- 示例:如果设置
HwFlowCtl = RTS_CTS
,则启用 RTS/CTS 硬件流控制功能。
- OverSampling (过采样设置)
- 意义:选择数据过采样的方式,通常可以设置为 16 或 8,以提高抗干扰能力。
- 示例:如果设置
OverSampling = 16
,则表示使用 16 倍过采样。
以下是一个使用 UART_InitTypeDef
结构体初始化 UART 的示例代码:
UART_InitTypeDef UART_InitStruct;
// 设置波特率为 115200
UART_InitStruct.BaudRate = 115200;
// 设置字长为 8 位
UART_InitStruct.WordLength = 8;
// 设置停止位为 1 位
UART_InitStruct.StopBits = 1;
// 不使用奇偶校验
UART_InitStruct.Parity = 0; // 0 表示 None
// 设置为双向模式
UART_InitStruct.Mode = TX_RX;
// 使用 RTS/CTS 硬件流控制
UART_InitStruct.HwFlowCtl = RTS_CTS;
// 设置过采样为 16
UART_InitStruct.OverSampling = 16;
// 调用 UART 初始化函数
UART_Init(&UART_InitStruct);
3.1.3 接发数据相关结构体成员
pTxBuffPtr, TxXferSize 和 TxXferCount 三个变量分别用来设置串口发送的数据缓存指针,发送的数据量和还剩余的要发送的数据量。而接下来的三个变量 pRxBuffPtr, RxXferSize 和RxXferCount 则是用来设置接收的数据缓存指针,接收的最大数据量以及还剩余的要接收的数据量。
3.1.4 HDMATX & HDMARX
hdmatx 和 hdmarx 是串口 DMA 相关的变量,指向 DMA 句柄,等到后面学习到DMA再讲解
3.1.5 错误处理结构体成员
其他的三个变量就是一些 HAL 库处理过程状态标志位和串口通信的错误码。
3.1.6 完整配置示例
UART_HandleTypeDef UART1_Handler; // UART 句柄
UART1_Handler.Instance = USART1; // USART1
UART1_Handler.Init.BaudRate = 115200; // 波特率
UART1_Handler.Init.WordLength = UART_WORDLENGTH_8B; // 字长为 8 位格式
UART1_Handler.Init.StopBits = UART_STOPBITS_1; // 一个停止位
UART1_Handler.Init.Parity = UART_PARITY_NONE; // 无奇偶校验位
UART1_Handler.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 无硬件流控
UART1_Handler.Init.Mode = UART_MODE_TX_RX; // 收发模式
HAL_UART_Init(&UART1_Handler); // HAL_UART_Init()会使能 UART1
这里我们需要说明的是,函数 HAL_UART_Init 内部会调用串口使能函数使能相应串口,所以调用了该函数之后我们就不需要重复使能串口了。当然, HAL 库也提供了具体的串口使能和关闭方法,具体使用方法如下:
__HAL_UART_ENABLE(handler); // 使能句柄 handler 指定的串口
__HAL_UART_DISABLE(handler); // 关闭句柄 handler 指定的串口
这里还需要提醒大家,串口作为一个重要外设,在调用的初始化函数 HAL_UART_Init 内部,会先调用 MSP 初始化回调函数进行 MCU 相关的初始化,函数为:
void HAL_UART_MspInit(UART_HandleTypeDef *huart);
我们在程序中,只需要重写该函数即可。一般情况下,该函数内部用来编写 IO 口初始化,时钟使能以及 NVIC 配置。
3.2 GPIO初始化设置(速度,上下拉等)以及复用映射配置
我们在跑马灯实验中讲解过,在 HAL 库中 IO 口初始化参数设置和复用映射配置是在函数HAL_GPIO_Init 中一次性完成的。这里大家只需要注意,我们要复用 PA9 和 PA10 为串口发送接收相关引脚,我们需要配置 IO 口为复用,同时复用映射到串口 1。配置源码如下:
GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin = GPIO_PIN_9|GPIO_PIN_10; // PA9/PA10
GPIO_Initure.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_Initure.Pull = GPIO_PULLUP; // 上拉
GPIO_Initure.Speed = GPIO_SPEED_FAST; // 高速
GPIO_Initure.Alternate = GPIO_AF7_USART1; // 复用为 USART1
HAL_GPIO_Init(GPIOA, &GPIO_Initure); // 初始化 PA9/PA10
3.3 开启串口相关中断,配置串口中断优先级
HAL 库中定义了一个使能串口中断的标识符__HAL_UART_ENABLE_IT,大家可以把它当一个函数来使用,具体定义请参考 HAL 库文件 stm32f4xx_hal_uart.h 中该标识符定义。例如我们要使能接收完成中断,方法如下:
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); // 开启接收完成中断
第一个参数为我们讲解的串口句柄,类型为 UART_HandleTypeDef 结构体类型。第二个参数为我们要开启的中断类型值,可选值在头文件 stm32f4xx_hal_uart.h 中有宏定义。
有开启中断就有关闭中断,操作方法为:
__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE); // 关闭接收完成中断
对于中断优先级配置,方法就非常简单
HAL_NVIC_EnableIRQ(USART1_IRQn); // 使能 USART1 中断通道
HAL_NVIC_SetPriority(USART1_IRQn, 3, 3); // 抢占优先级 3,子优先级 3
3.4 编写中断服务函数
串口1中断服务函数为:
void USART1_IRQHandler(void)
{
// 用户实现
}
当发生中断的时候,程序就会执行中断服务函数。然后我们在中断服务函数中编写们相应的逻辑代码即可。
3.5 串口数据接收和发送
STM32F4 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包含了 TDR 和 RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。 HAL 库操作 USART_DR 寄存器发送数据的函数是:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size, uint32_t Timeout);
通过该函数向串口寄存器 USART_DR 写入一个数据。
HAL 库操作 USART_DR 寄存器读取串口接收到的数据的函数是:
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart,
uint8_t *pData, uint16_t Size, uint32_t Timeout);
通过该函数可以读取串口接受到的数据。
完整的配置如下:
#include "main.h"
// 定义串口句柄
UART_HandleTypeDef huart2; // 根据你选择的 USART 修改
// 函数声明
void SystemClock_Config(void); // 系统时钟配置函数
static void MX_GPIO_Init(void); // GPIO初始化函数
static void MX_USART2_UART_Init(void); // USART2初始化函数
int main(void)
{
// 初始化硬件抽象层库
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 初始化GPIO
MX_GPIO_Init();
// 初始化USART2
MX_USART2_UART_Init();
// 示例数据,用于发送
uint8_t txData[] = "Hello, STM32!";
// 接收缓冲区
uint8_t rxData[20];
while (1)
{
// 发送数据,通过 USART2 发送 txData
HAL_UART_Transmit(&huart2, txData, sizeof(txData), HAL_MAX_DELAY);
// 接收数据,通过 USART2 接收并存储到 rxData 中
HAL_UART_Receive(&huart2, rxData, sizeof(rxData), HAL_MAX_DELAY);
// 可以在此处理接收到的数据,比如将其打印到串口等
}
}
// USART2 初始化函数
static void MX_USART2_UART_Init(void)
{
// 配置 USART2 的实例
huart2.Instance = USART2;
// 设置波特率为 115200
huart2.Init.BaudRate = 115200;
// 设置数据位为 8 位
huart2.Init.WordLength = UART_WORDLENGTH_8B;
// 设置停止位为 1 位
huart2.Init.StopBits = UART_STOPBITS_1;
// 设置无校验位
huart2.Init.Parity = UART_PARITY_NONE;
// 设置模式为发送和接收
huart2.Init.Mode = UART_MODE_TX_RX;
// 设置硬件流控制为无
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
// 设置过采样为 16
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
// 初始化 USART2
HAL_UART_Init(&huart2);
}
// GPIO 初始化函数(如果需要可配置引脚)
static void MX_GPIO_Init(void) {
// 此处根据具体需要配置 GPIO 引脚,如 LED、按钮等
}
// 系统时钟配置函数(根据你的系统需求配置时钟)
void SystemClock_Config(void) {
// 此处添加配置代码,根据具体使用的时钟源进行配置
}
2024.9.27 第一次修订,后期不再维护
2024.12.21 简化内容