3. 实现串口驱动并重定向printf

实现串口驱动并重定向printf 学习卡片

任务目标

在嵌入式目标板上实现串口(UART)底层驱动,并将C标准库的printf输出重定向到该串口,使调试信息能够通过串口工具打印到PC终端。本案例以STM32F103系列MCU和STM32CubeIDE环境为例,给出最小可复现实现。

硬件与依赖

  • 硬件:STM32F103最小系统板(USART1,PA9=TX,PA10=RX),USB转TTL模块
  • 软件:STM32CubeIDE(含HAL库),串口终端工具(如PuTTY、MobaXterm)
  • CubeMX初始化:USART1异步模式,波特率115200,8数据位,1停止位,无校验

关键完整代码

在CubeMX生成的工程基础上,核心实现分为两部分:串口发送函数封装,以及printf重定向。以下代码直接添加到usart.c或main.c中(需包含stdio.h)。

/* 在 usart.c 或 main.c 中添加 */

/* 1. 串口发送单个字节(HAL库底层已实现,此处仅做重定向接口) */
int _write(int file, char *ptr, int len)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, HAL_MAX_DELAY);
    return len;
}

/* 2. 若使用MicroLIB,则改用以下实现(二选一,不可同时启用) */
#if 0
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}
#endif

/* 3. 在 main() 中调用示例 */
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();

    /* 重定向后,printf 输出将发送到 USART1 */
    printf("UART printf redirect OK\r\n");
    printf("System Clock: %d MHz\r\n", (int)HAL_RCC_GetSysClockFreq() / 1000000);

    while (1)
    {
        printf("Hello from STM32, tick: %lu\r\n", HAL_GetTick());
        HAL_Delay(1000);
    }
}

工程配置要点

  • MicroLIB设置:在工程属性 → C/C++ Build → Settings → MCU Settings 中勾选 Use MicroLIB。若使用标准库,则必须实现_write函数(上述代码已提供);若使用MicroLIB,则需改用fputc实现(代码中#if 0部分)。
  • 头文件:确保包含 #include <stdio.h>。
  • 中断优先级:本案例使用阻塞式发送(HAL_MAX_DELAY),无需配置中断。若需非阻塞发送,可改用中断或DMA方式,但不在本案例范围内。

运行与验证

  1. 使用USB转TTL模块连接:GND→GND,TX→PA10(STM32的RX),RX→PA9(STM32的TX)。
  2. 打开串口终端,选择对应COM口,设置波特率115200、8N1。
  3. 编译并烧录程序,复位开发板。
  4. 通过条件:终端窗口每秒打印一次 Hello from STM32, tick: N(N为递增的毫秒计数值),且首次上电时打印两行初始化信息。若能看到上述输出,说明串口驱动和printf重定向均正常工作。

常见问题

  • 无输出:检查USB转TTL模块的TX/RX是否交叉连接;确认波特率设置一致;检查PA9/PA10是否被其他外设占用。
  • 输出乱码:多为波特率不匹配或时钟配置错误导致,检查CubeMX中USART1的波特率配置与实际终端设置。
  • 编译报错_write未定义:若使用MicroLIB,请改用fputc实现;若使用标准库,确认已包含stdio.h且函数签名正确。