2. 利用printf调试嵌入式程序

任务目标

在嵌入式环境中,printf是最直接的调试手段。本案例以STM32F407(Cortex-M4)为硬件平台,完成两件事:一是将printf重定向到UART,使标准库的printf输出到串口终端;二是演示如何用printf定位一个典型的逻辑错误(变量溢出)。最终通过串口助手观察输出,确认调试信息正确打印。

环境与依赖

  • 硬件:STM32F407开发板(板载USB转串口,连接USART2)
  • 软件:STM32CubeIDE(或任何支持ARM GCC的IDE)
  • 固件库:STM32CubeF4 HAL库(或标准外设库)
  • 串口终端:PuTTY / SSCOM / minicom,波特率115200,8N1

核心实现

重定向printf的关键在于:ARM GCC工具链使用_write作为底层输出函数(ARMCC使用fputc)。本案例使用GCC,因此实现_write函数,将字符逐个发送到UART2。同时需要初始化UART2的GPIO和USART外设。

```c #include "stm32f4xx_hal.h" #include #include #include UART_HandleTypeDef huart2; // 重定向printf到UART2(GCC/ARM Compiler 6) int _write(int file, char *ptr, int len) { HAL_UART_Transmit(&huart2, (uint8_t*)ptr, len, 100); return len; } // 初始化USART2:PA2=TX, PA3=RX, 115200-8N1 static void MX_USART2_UART_Init(void) { __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART2; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; HAL_UART_Init(&huart2); } // 待调试的逻辑:模拟一个8位计数器溢出问题 void debug_demo(void) { uint8_t counter = 0; for (int i = 0; i < 300; i++) { counter++; // 关键调试点:观察counter何时回绕 if (counter < 10 || counter > 245) { printf("[i=%d] counter=%u\n", i, counter); } } printf("Demo finished.\n"); } int main(void) { HAL_Init(); MX_USART2_UART_Init(); printf("=== printf debug demo ===\n"); debug_demo(); while (1) { // 空循环,保持程序运行 } } ```

编译与运行

  1. 在STM32CubeIDE中新建STM32F407项目,将上述代码复制到main.c(替换默认生成的初始化代码)。
  2. 确认系统时钟已通过HAL_Init()配置(默认使用HSI 16MHz,UART波特率误差在允许范围内)。
  3. 编译并下载到开发板。
  4. 打开串口终端,连接对应COM口,设置波特率115200。
  5. 按下开发板复位键,观察终端输出。

验证方法与通过标准

串口终端应依次输出以下内容(预期输出,与代码逻辑严格对应):


=== printf debug demo ===
[i=0] counter=1
[i=1] counter=2
...
[i=8] counter=9
[i=246] counter=247
[i=247] counter=248
...
[i=255] counter=0
[i=256] counter=1
...
[i=298] counter=43
[i=299] counter=44
Demo finished.

通过标准:

  • 终端能收到=== printf debug demo ===开头和Demo finished.结尾的完整输出;
  • 观察counter在i=255后从0重新计数,证明8位溢出发生——这正是printf帮助定位的逻辑问题;
  • 若输出乱码,检查波特率设置和USB转串口驱动;若无输出,检查PA2/PA3跳线连接及_write是否被正确链接。

常见问题

  • 无输出:确认_write符号未被优化器移除(加-u _write链接选项或使用volatile);检查UART时钟使能。
  • 输出乱码:核对波特率是否一致;检查系统时钟频率是否与CubeMX配置匹配。
  • 半主机(Host)模式卡死:若使用printf但未重定向,程序会进入半主机死循环。本案例已通过_write重定向避免此问题。