4. UART驱动移植:LL库UART初始化流程、波特率配置、UART发送/接收函数实现
UART(Universal Asynchronous Receiver/Transmitter)是嵌入式系统中最基础的串行通信外设。在LL库框架下,UART驱动的移植核心在于正确配置时钟源、GPIO复用功能以及USART外设寄存器。本章将详细拆解LL库UART的初始化流程、波特率计算与配置方法,并给出阻塞式发送与中断式接收的标准实现。
4.1 LL库UART初始化流程
LL库对UART的初始化不依赖HAL库的句柄结构,而是直接操作寄存器。标准初始化流程包含以下五个步骤:
- 使能时钟:使能USART外设时钟及对应GPIO端口时钟。
- 配置GPIO复用:将TX引脚设置为推挽复用输出,RX引脚设置为浮空输入或带上拉输入。
- 复位USART外设:通过LL_USART_DeInit()确保寄存器处于默认状态。
- 配置核心参数:波特率、数据位、停止位、校验位、硬件流控。
- 使能USART:调用LL_USART_Enable()启动外设,并根据需要使能发送/接收。
移植要点: 在步骤2中,GPIO速度必须设置为高速模式(LL_GPIO_SPEED_FREQ_HIGH),否则在高波特率下(如921600 bps)会出现数据错位。
下图展示了LL库UART初始化的完整软件架构与寄存器操作路径:
4.2 波特率配置原理与计算
LL库中波特率通过寄存器 USART_BRR 配置,其值由 USART时钟频率 和 过采样模式 决定。STM32系列支持8倍或16倍过采样,通过 USART_CR1->OVER8 位选择。
| 过采样模式 | OVER8位 | BRR计算公式 | 典型误差范围 |
|---|---|---|---|
| 16倍过采样 | 0 | BRR = f_PCLK / (16 × BaudRate) | < 1.5% |
| 8倍过采样 | 1 | BRR = f_PCLK / (8 × BaudRate) | < 2.5% |
常见错误: 直接使用HAL库的宏定义计算BRR值,但未确认LL库中时钟源频率。例如,当USART挂载在APB2总线且APB2预分频器不为1时,f_PCLK = HCLK / 2,若仍按HCLK计算,波特率将偏差50%。
LL库提供 LL_USART_SetBaudRate() 函数,内部自动处理BRR寄存器的整数部分和小数部分。推荐直接使用该函数而非手动写寄存器:
/* 配置波特率为115200,16倍过采样 */
LL_USART_SetBaudRate(USART1,
LL_RCC_GetUSARTClockFreq(LL_RCC_USART1_CLKSOURCE),
LL_USART_OVERSAMPLING_16,
115200);
下表列出了常用波特率在不同时钟频率下的BRR值(16倍过采样):
| f_PCLK (MHz) | 9600 bps | 115200 bps | 921600 bps |
|---|---|---|---|
| 8 | 52.08 (52) | 4.34 (4) | 0.54 (0) |
| 16 | 104.17 (104) | 8.68 (8) | 1.09 (1) |
| 72 | 468.75 (468) | 39.06 (39) | 4.88 (4) |
调试技巧: 当BRR计算值的小数部分接近0.5时,误差最小。若误差超过2%,建议调整PCLK分频系数或改用8倍过采样模式。
4.3 UART发送函数实现
LL库发送数据有两种方式:轮询发送 和 中断发送。在驱动移植初期,推荐先实现轮询发送以验证硬件通路。
4.3.1 轮询发送(阻塞式)
核心思路:等待发送数据寄存器空(TXE标志),写入数据,等待发送完成(TC标志)。
/**
* @brief 轮询发送单字节
* @param USARTx: USART外设基地址
* @param Data: 待发送字节
* @retval None
*/
void LL_UART_TransmitByte(USART_TypeDef *USARTx, uint8_t Data)
{
/* 等待TXE标志置位 */
while (!LL_USART_IsActiveFlag_TXE(USARTx));
/* 写入数据寄存器 */
LL_USART_TransmitData8(USARTx, Data);
/* 等待发送完成(TC),确保移位寄存器空 */
while (!LL_USART_IsActiveFlag_TC(USARTx));
}
/**
* @brief 轮询发送多字节
* @param USARTx: USART外设基地址
* @param pData: 数据缓冲区指针
* @param Size: 发送字节数
* @retval None
*/
void LL_UART_TransmitBuffer(USART_TypeDef *USARTx, uint8_t *pData, uint32_t Size)
{
for (uint32_t i = 0; i < Size; i++)
{
LL_UART_TransmitByte(USARTx, pData[i]);
}
}
性能说明: 轮询发送在低波特率(≤115200)下CPU占用率较高。对于大数据量传输,建议切换到DMA或中断方式。
4.3.2 中断发送(非阻塞式)
中断发送需要配合全局缓冲区和状态标志。以下为简化实现:
/* 全局变量 */
static uint8_t *txBuffer;
static volatile uint32_t txSize;
static volatile uint32_t txIndex;
/**
* @brief 启动中断发送
*/
void LL_UART_TransmitIT(USART_TypeDef *USARTx, uint8_t *pData, uint32_t Size)
{
txBuffer = pData;
txSize = Size;
txIndex = 0;
/* 使能TXE中断 */
LL_USART_EnableIT_TXE(USARTx);
}
/* USART中断服务函数中调用 */
void USARTx_IRQHandler(void)
{
if (LL_USART_IsActiveFlag_TXE(USARTx) && LL_USART_IsEnabledIT_TXE(USARTx))
{
if (txIndex < txSize)
{
LL_USART_TransmitData8(USARTx, txBuffer[txIndex++]);
}
else
{
/* 发送完成,关闭TXE中断,使能TC中断 */
LL_USART_DisableIT_TXE(USARTx);
LL_USART_EnableIT_TC(USARTx);
}
}
if (LL_USART_IsActiveFlag_TC(USARTx) && LL_USART_IsEnabledIT_TC(USARTx))
{
LL_USART_DisableIT_TC(USARTx);
/* 可在此设置发送完成回调标志 */
}
}
4.4 UART接收函数实现
接收函数通常采用 中断方式 或 DMA方式。轮询接收仅用于调试场景。
4.4.1 中断接收(单字节)
使能RXNE中断,在中断服务函数中读取数据并存入环形缓冲区:
/* 全局环形缓冲区 */
#define RX_BUF_SIZE 256
static uint8_t rxRingBuf[RX_BUF_SIZE];
static volatile uint16_t rxHead = 0;
static volatile uint16_t rxTail = 0;
/**
* @brief 启动中断接收
*/
void LL_UART_ReceiveIT(USART_TypeDef *USARTx)
{
LL_USART_EnableIT_RXNE(USARTx);
}
/* USART中断服务函数中增加 */
if (LL_USART_IsActiveFlag_RXNE(USARTx) && LL_USART_IsEnabledIT_RXNE(USARTx))
{
uint8_t data = LL_USART_ReceiveData8(USARTx);
/* 存入环形缓冲区 */
uint16_t nextHead = (rxHead + 1) % RX_BUF_SIZE;
if (nextHead != rxTail) /* 缓冲区未满 */
{
rxRingBuf[rxHead] = data;
rxHead = nextHead;
}
/* 若缓冲区满,丢弃数据(可设置溢出标志) */
}
4.4.2 接收超时与帧错误处理
实际通信中需处理以下异常:
- 溢出错误(ORE):上一字节未及时读取,新数据覆盖。需清除ORE标志并重新使能接收。
- 帧错误(FE):停止位检测失败。通常由波特率不匹配或线路干扰引起。
- 噪声错误(NE):信号质量差。可考虑降低波特率或启用硬件滤波。
/* 在中断中检查错误标志 */
if (LL_USART_IsActiveFlag_ORE(USARTx))
{
LL_USART_ClearFlag_ORE(USARTx);
/* 丢弃当前数据,重新使能接收 */
(void)LL_USART_ReceiveData8(USARTx);
}
if (LL_USART_IsActiveFlag_FE(USARTx))
{
LL_USART_ClearFlag_FE(USARTx);
/* 记录帧错误计数 */
}
4.5 初始化失败常见原因与排查
UART初始化失败通常表现为:无输出、输出乱码、或卡死在等待标志位。下表总结了典型故障原因及解决方案:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 无输出 | GPIO复用功能未配置 | 检查GPIO_AFR寄存器是否设置正确 |
| 输出乱码 | 波特率误差过大 | 用逻辑分析仪测量实际波特率 |
| 卡死在TXE等待 | USART未使能或时钟未开启 | 检查RCC寄存器中USARTEN位 |
| 接收不到数据 | RX引脚未配置为输入 | 确认GPIO模式为浮空输入或上拉输入 |
| 中断不触发 | NVIC未使能或优先级分组错误 | 检查NVIC_EnableIRQ()与优先级配置 |
快速验证方法: 在初始化完成后,立即发送一个固定字节(如0x55),用示波器或逻辑分析仪测量TX引脚波形。若波形正确但接收端乱码,则问题在接收端配置;若无波形,则问题在发送端配置。
下图总结了UART初始化失败的系统级排查流程:
4.6 移植验证清单
完成UART驱动移植后,建议按以下清单逐项验证:
- 回环测试:将TX与RX短接,发送数据并验证接收内容一致。
- 波特率精度测试:发送0x55(交替位),用示波器测量位宽是否在误差范围内。
- 中断响应测试:连续发送1000字节,检查是否有丢失或重复字节。
- 压力测试:在最高波特率下持续发送10分钟,监控ORE错误计数。
移植总结: LL库UART驱动的核心在于精确的时钟源确认和BRR计算。初始化阶段务必使用LL_RCC_GetUSARTClockFreq()获取实际时钟频率,而非假设某个固定值。发送/接收函数实现时,注意区分TXE与TC标志的用途——TXE仅表示数据寄存器空,TC才表示移位寄存器发送完成。