2. 利用printf调试嵌入式程序
任务目标
在嵌入式目标板上通过串口实现printf输出,完成标准C库printf底层字符输出函数的重定向,使调试信息能够从开发板串口发送到PC终端。本案例以STM32F103系列(HAL库)为例,给出最小可复现实现。
环境与依赖
- 硬件:STM32F103C8T6最小系统板,USB转TTL模块,USART1(PA9/PA10)连接PC
- 软件:STM32CubeIDE 或 Keil MDK-ARM(本文以STM32CubeIDE为例)
- 固件库:STM32CubeF1 HAL库(已包含在CubeIDE中)
实现原理
C标准库的printf最终会调用底层字符输出函数。在ARMCC(Keil)中该函数为fputc,在GCC(CubeIDE默认)中为_write。重定向的核心就是重新实现这两个函数之一,将字符通过串口发送。同时需要确保不使用微库(Microlib)或正确配置半主机模式,避免程序卡死在调试通道。
关键完整代码
以下代码在STM32CubeIDE(GCC工具链)中可直接使用。若使用Keil,将_write函数替换为fputc实现即可。
// main.c 中需要包含的头文件
#include <stdio.h>
#include <stdarg.h>
// 在 main() 函数之前添加以下重定向代码
// GCC工具链(STM32CubeIDE)的printf重定向
int _write(int file, char *ptr, int len)
{
// 逐个字符通过USART1发送
for (int i = 0; i < len; i++)
{
// 等待发送数据寄存器为空
while (!(USART1->SR & USART_SR_TXE));
// 发送字符
USART1->DR = (uint8_t)ptr[i];
}
return len;
}
// 若使用Keil MDK (ARMCC),则用以下函数替代上面的_write
// int fputc(int ch, FILE *f)
// {
// while (!(USART1->SR & USART_SR_TXE));
// USART1->DR = (uint8_t)ch;
// return ch;
// }
// USART1初始化(若已在CubeMX中生成则无需重复)
static void USART1_Init(void)
{
// 使能GPIOA和USART1时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_USART1_CLK_ENABLE();
// 配置PA9为TX(复用推挽),PA10为RX(浮空输入)
GPIO_InitTypeDef gpio = {0};
gpio.Pin = GPIO_PIN_9;
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &gpio);
gpio.Pin = GPIO_PIN_10;
gpio.Mode = GPIO_MODE_INPUT;
gpio.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &gpio);
// 配置USART1:115200-8-N-1
USART_HandleTypeDef huart1 = {0};
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;
HAL_UART_Init(&huart1);
}
int main(void)
{
HAL_Init();
USART1_Init();
// 测试printf输出
int count = 0;
while (1)
{
printf("Count: %d, Hex: 0x%X\r\n", count++, count);
HAL_Delay(1000); // 延时1秒
}
}
运行与验证
- 将代码编译下载到STM32开发板,PA9连接USB转TTL的RX,PA10连接TX,GND共地
- PC端打开串口助手(如PuTTY、SSCOM),选择对应COM口,设置波特率115200、8数据位、无校验、1停止位
- 打开串口后复位开发板,观察串口助手是否每秒收到一行递增计数信息
通过判定标准:串口助手持续显示类似Count: 0, Hex: 0x0、Count: 1, Hex: 0x1的递增输出,且无乱码、无卡死,即表示printf重定向成功。
常见问题
- 程序卡死在printf:检查是否使用了半主机模式。在CubeIDE中需在Project Properties → C/C++ Build → Settings → MCU GCC Linker → Miscellaneous 中添加
--specs=nano.specs --specs=nosys.specs;在Keil中需勾选Target页的“Use MicroLIB” - 输出乱码:检查串口助手波特率是否与代码中配置一致(本例115200),以及TX/RX接线是否交叉连接
- 无输出:确认USART1时钟已使能,且PA9/PA10复用功能配置正确