在 STM32 开发中,UART 通信可以通过 HAL 库函数或直接操作寄存器实现。下面将详细对比两种方式,并解释 HAL 库函数背后的寄存器操作原理。
1. 串口初始化
HAL 库方式
UART_HandleTypeDef huart1;
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();
}
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1) {
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
// 配置TX引脚(PA9)
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 配置RX引脚(PA10)
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
寄存器方式
void UART1_Init(void) {
// 使能USART1和GPIOA时钟
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 配置PA9(TX)为复用功能
GPIOA->MODER |= GPIO_MODER_MODER9_1; // 复用模式
GPIOA->OTYPER &= ~GPIO_OTYPER_OT_9; // 推挽输出
GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9_0 | GPIO_OSPEEDER_OSPEEDR9_1; // 高速
GPIOA->AFR[1] |= (7 << 4); // AF7: USART1
// 配置PA10(RX)为复用功能
GPIOA->MODER |= GPIO_MODER_MODER10_1; // 复用模式
GPIOA->AFR[1] |= (7 << 8); // AF7: USART1
// 计算波特率分频值(假设PCLK2=84MHz)
uint32_t baud = 115200;
uint32_t div = 84000000 / (16 * baud); // 16倍过采样
// 配置USART1
USART1->BRR = div; // 设置波特率
USART1->CR1 = USART_CR1_TE | USART_CR1_RE; // 使能发送和接收
USART1->CR2 = 0; // 1个停止位
USART1->CR3 = 0; // 无硬件流控制
USART1->CR1 |= USART_CR1_UE; // 使能USART
}
寄存器解释
- 时钟使能 :
RCC->APB2ENR
和RCC->AHB1ENR
分别控制 USART 和 GPIO 的时钟。 - GPIO 配置 :
MODER
:设置引脚为复用模式(10
)。OTYPER
:设置为推挽输出(0
)。OSPEEDR
:设置为高速模式(11
)。AFR
:选择复用功能 AF7(USART1)。
- 波特率计算 :
BRR
寄存器根据公式fPCLK / (16 * 波特率)
计算分频值。 - 控制寄存器 :
CR1
:使能发送(TE
)、接收(RE
)和 USART(UE
)。CR2
:设置停止位(默认 00 为 1 位)。CR3
:禁用硬件流控制(0
)。
2. 数据发送
HAL 库方式
uint8_t txData[] = "Hello, World!";
HAL_UART_Transmit(&huart1, txData, sizeof(txData), 1000);
寄存器方式
void UART1_Send(uint8_t* data, uint32_t length) {
for (uint32_t i = 0; i < length; i++) {
// 等待发送缓冲区为空
while (!(USART1->SR & USART_SR_TXE));
USART1->DR = data[i]; // 写入数据到数据寄存器
}
// 等待发送完成
while (!(USART1->SR & USART_SR_TC));
}
寄存器解释
- SR 寄存器 :
TXE
(发送缓冲区空):当置位时,表示可以写入新数据。TC
(发送完成):当置位时,表示最后一个数据已发送完毕。
- DR 寄存器:存储要发送的数据(低位 8 位有效)。
3. 数据接收
HAL 库方式
uint8_t rxData[10];
HAL_UART_Receive(&huart1, rxData, 10, 1000);
寄存器方式
void UART1_Receive(uint8_t* data, uint32_t length) {
for (uint32_t i = 0; i < length; i++) {
// 等待接收缓冲区非空
while (!(USART1->SR & USART_SR_RXNE));
data[i] = USART1->DR; // 读取数据寄存器
}
}
寄存器解释
- SR 寄存器 :
RXNE
(接收缓冲区非空):当置位时,表示接收到新数据。
- DR 寄存器:存储接收到的数据(低位 8 位有效)。
4. 中断接收
HAL 库方式
uint8_t rxBuffer;
HAL_UART_Receive_IT(&huart1, &rxBuffer, 1);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef* huart) {
if (huart->Instance == USART1) {
// 处理接收到的数据 rxBuffer
HAL_UART_Receive_IT(&huart1, &rxBuffer, 1); // 重新开启中断
}
}
寄存器方式
void UART1_Receive_IT(void) {
// 使能接收中断
USART1->CR1 |= USART_CR1_RXNEIE;
// 配置NVIC
NVIC_SetPriority(USART1_IRQn, 0);
NVIC_EnableIRQ(USART1_IRQn);
}
void USART1_IRQHandler(void) {
if (USART1->SR & USART_SR_RXNE) { // 接收缓冲区非空
uint8_t data = USART1->DR;
// 处理接收到的数据
USART1->SR &= ~USART_SR_RXNE; // 清除标志(读取DR自动清除)
}
}
寄存器解释
- CR1 寄存器 :
RXNEIE
:使能接收中断。
- NVIC 配置:设置中断优先级并使能 USART1 中断。
- 中断处理 :
- 检查
RXNE
标志是否置位。 - 读取
DR
寄存器获取数据(读取后RXNE
自动清除)。
- 检查
总结
功能 | HAL 库函数 | 对应寄存器操作 |
---|---|---|
初始化 | HAL_UART_Init() |
配置 BRR、CR1、CR2、CR3 寄存器 |
发送数据 | HAL_UART_Transmit() |
等待 TXE 置位,写入 DR 寄存器 |
接收数据 | HAL_UART_Receive() |
等待 RXNE 置位,读取 DR 寄存器 |
中断接收 | HAL_UART_Receive_IT() |
使能 RXNEIE,配置 NVIC,在中断中读取 DR |
发送完成回调 | HAL_UART_TxCpltCallback() |
检查 TC 标志 |
接收完成回调 | HAL_UART_RxCpltCallback() |
检查 RXNE 标志 |
通过对比可以看出,HAL 库函数本质上是对寄存器操作的封装,简化了开发流程,但理解寄存器原理有助于深入掌握 UART 通信机制。