4. UART驱动移植:LL库UART初始化流程、波特率配置、UART发送/接收函数实现

UART(Universal Asynchronous Receiver/Transmitter)是嵌入式系统中最基础的串行通信外设。在LL库框架下,UART驱动的移植核心在于正确配置时钟源、GPIO复用功能以及USART外设寄存器。本章将详细拆解LL库UART的初始化流程、波特率计算与配置方法,并给出阻塞式发送与中断式接收的标准实现。

4.1 LL库UART初始化流程

LL库对UART的初始化不依赖HAL库的句柄结构,而是直接操作寄存器。标准初始化流程包含以下五个步骤:

  1. 使能时钟:使能USART外设时钟及对应GPIO端口时钟。
  2. 配置GPIO复用:将TX引脚设置为推挽复用输出,RX引脚设置为浮空输入或带上拉输入。
  3. 复位USART外设:通过LL_USART_DeInit()确保寄存器处于默认状态。
  4. 配置核心参数:波特率、数据位、停止位、校验位、硬件流控。
  5. 使能USART:调用LL_USART_Enable()启动外设,并根据需要使能发送/接收。
移植要点: 在步骤2中,GPIO速度必须设置为高速模式(LL_GPIO_SPEED_FREQ_HIGH),否则在高波特率下(如921600 bps)会出现数据错位。

下图展示了LL库UART初始化的完整软件架构与寄存器操作路径:

LL库UART初始化软件架构图 应用层调用 LL_USART_Init() LL_USART_Init(USARTx, USART_InitStruct) 配置波特率、数据位、停止位、校验位 LL_RCC_SetUSARTClockSource() 选择PCLK或SYSCLK LL_GPIO_Init() TX/RX复用功能 LL_USART_Enable() 使能USART + TX/RX 硬件寄存器:USART->BRR / CR1 / CR2 / CR3 注意:LL库不自动处理GPIO时钟使能,需在调用前手动使能 RCC->AHBxENR 对应位

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初始化失败的系统级排查流程:

UART初始化失败排查流程图 UART无输出/乱码 时钟使能? 使能RCC时钟 检查GPIO配置 波特率正确? 重新计算BRR值 检查线路/终端

4.6 移植验证清单

完成UART驱动移植后,建议按以下清单逐项验证:

  • 回环测试:将TX与RX短接,发送数据并验证接收内容一致。
  • 波特率精度测试:发送0x55(交替位),用示波器测量位宽是否在误差范围内。
  • 中断响应测试:连续发送1000字节,检查是否有丢失或重复字节。
  • 压力测试:在最高波特率下持续发送10分钟,监控ORE错误计数。
移植总结: LL库UART驱动的核心在于精确的时钟源确认和BRR计算。初始化阶段务必使用LL_RCC_GetUSARTClockFreq()获取实际时钟频率,而非假设某个固定值。发送/接收函数实现时,注意区分TXE与TC标志的用途——TXE仅表示数据寄存器空,TC才表示移位寄存器发送完成。