串口调试 — printf 重定向与 USART 通信配套硬件DshanMCU-F407STM32F407ZGT6 USB-TTLHW-597模块学习目标学会用 printf 打印变量这是嵌入式开发最重要的调试手段核心思想不用 LED 闪烁猜问题直接看数据目录为什么要学串口调试什么是 USART/UART引脚选择——为什么是 PB10 和 PC11USART 配置参数详解115200 8N1硬件接线CubeMX 配置代码解读fputc 重定向详解核心难点用 printf 调试——这才是重点串口助手的设置常见问题扩展思考1. 为什么要学串口调试1.1 没有串口之前你是怎么 debug 的方式 1LED 闪烁 - 亮灭亮灭 → 程序活着 - 一直亮 → 卡死了 - 一闪而过 → 看不清 方式 2猜 - 你觉得是这里错了改一改烧进去试试 - 不行再猜 - 效率极低1.2 有了串口 printf 之后printf(距离 %d cm\r\n,distance);// 直接看距离值printf(中断触发了count %d\r\n,count);// 看中断次数printf(进入函数 Aparam %d\r\n,param);// 看函数是否执行了你看见了变量内部的值不用猜。1.3 06-4 课程为什么讲 OLED 调试06-4 的题眼是调试手段——LED 能给你的信息只有 1 位亮/灭OLED/printf 能给你成千上万字符的信息。你学会的是一种把变量值说出来的方法不管用 OLED 还是串口本质一样。2. 什么是 USART/UART2.1 名字拆解UART Universal Asynchronous Receiver / Transmitter 通用异步收发器 USART Universal Synchronous / Asynchronous Receiver / Transmitter 通用同步/异步收发器USART 比 UART 多一个同步功能有时钟线但大多数时候我们把它当 UART 用——异步两根线TX/RX。2.2 UART 通信物理层F407USART3 USB-TTL 模块 电脑 PB10 (TX) ─────────── RXD ───→ 串口芯片 → USB → 串口助手 PC11 (RX) ──────────── TXD ←──串口芯片 ← USB ← 串口助手 GND ────────────── GND 共地统一参考电平TX 接 RX、RX 接 TX交叉连接GND 一定要共地。2.3 UART 一帧数据的结构空闲高电平 │ ▼ ┌─────────┬───────────────┬───┬────────┐ │ 起始位 │ 8 位数据 │校 │ 停止位 │ │ 低 │ LSB→MSB │验 │ 高 │ └─────────┴───────────────┴───┴────────┘ 1 bit 8 bit 无 1 bit从前面看BIT0 BIT1 BIT2 ... BIT7 ┌──┐ ┌┐ ┌┐ ┌┐ TX ────高─────┘ └──┘└──┘└──────┘└────── 高 ───── 起始 停止关键没有时钟线靠收发双方约定好每秒传多少位波特率。3. 引脚选择——为什么是 PB10 和 PC113.1 STM32 的 Alternate Function 表F407 的 USART 不止一组外设模块可以通过不同的 GPIO 引脚连接到芯片内部。具体哪个引脚可以当哪个外设用看芯片手册的Alternate Function 表USART3_TX 有 3 个可选引脚 PB10 ─── AF7Alternate Function 7 PC10 ─── AF7 PD8 ─── AF7 USART3_RX 有 3 个可选引脚 PB11 ─── AF7 PC11 ─── AF7 PD9 ─── AF73.2 为什么最终选了 PB10 和 PC11查 P3 排针引出的空闲引脚候选P3 排针上有没有结论PB10有✅ 做 TXPB11N/A❌ 排针上没有PC10N/A❌ 排针上没有PC11有✅ 做 RXPD8/PD9N/A❌ 排针上没有不是随便选的是从 Alternate Function 表找到可用引脚 → 再和排针对比 → 选出都能用的组合。这是硬件工程师的基本功也是你在 CubeMX 配引脚时经常要做的事。⭐⭐SHOULD KNOWCubeMX 可以直接帮你看——当你选择 USART3 时它会自动高亮所有可用的 TX/RX 引脚绿色标出可选橙色标出已选。4. USART 配置参数详解115200 8N14.1 Baud Rate波特率波特率 每秒钟传输的符号数单位 bpsbit per second 115200 bps → 每秒传 115200 位115200 是怎么来的115200 115.2 Kbps ≈ 11.52 KB/s有效数据 ↓ 假设你传 1000 字节的数据 用时 ≈ 1000 / 11520 ≈ 0.087 秒为什么用 115200 而不是其他值波特率优点缺点9600兼容老设备太慢传 1KB 要 1 秒115200速度适中大部分设备支持—921600极快线长了容易乱码115200 是嵌入式开发最常用的波特率能兼顾速度和稳定性。⚠️重要收发双方的波特率必须一致。你设 115200电脑串口助手也要设 115200。否则收到的数据是乱码。4.2 Word Length数据位8 bit → 每帧传输 8 位刚好 1 个字节 这是最常用的设置因为你要传的数据都是以字节为单位的4.3 Parity校验位None无校验 不添加校验位10 位 起始 1 数据 8 停止 1 Even偶校验 校验位自动调整使数据中 1 的个数为偶数 11 位 起始 1 数据 8 校验 1 停止 1 Odd奇校验 同上但使 1 的个数为奇数为什么 None如果你只是调试用不需要校验。校验位多一位有效传输率降低。串口线短2 米出错概率极低。4.4 Stop Bits停止位1 bit → 停止位 1 位 停止位告诉接收方这一帧结束了8N1 的完整含义8 8 data bits数据位 8 位 N No Parity无校验 1 1 stop bit停止位 1 位 总共1起始 8数据 0校验 1停止 10 位每字符4.5 面试会问的问题问波特率 1152008N1每秒实际传多少字节答每字节需要 10 位1 起始 8 数据 1 停止。每秒传 115200 / 10 11520 字节。5. 硬件接线5.1 接线表USB-TTLHW-597F407说明TXDPC11(USART3_RX)发→收交叉RXDPB10(USART3_TX)收→发交叉GNDGND共地必须接5V 或 3V3不接USB-TTL 由 USB 供电F407 由自己的电源供电注意TX 接 RXRX 接 TX不要接反。5.2 USB-TTL 电脑端USB-TTL 插入电脑 → 查看设备管理器Windows- 端口COM 和 LPT→ 看出现哪个 COM 口如 COM3、COM4。这个 COM 口号后面打开串口助手时要选。6. CubeMX 配置6.1 新建工程打开 STM32CubeMX → New Project搜索STM32F407ZGTx→ 双击选中6.2 引脚配置引脚设置说明PB10USART3_TX发送数据给电脑PC11USART3_RX接收电脑发来的数据本次实验不涉及6.3 USART3 参数配置左边Connectivity → USART3→Parameter Settings参数设置值说明ModeAsynchronous异步模式只有 TX/RX无时钟Baud Rate115200每秒传 115200 位Word Length8 Bit每帧传 8 位ParityNone无校验Stop Bits1停止位 1 位6.4 NVIC 配置左边菜单 →NVIC→ 找到USART3 global interrupt→ 打勾本次实验用查询方式不勾也能工作但建议勾上习惯就好6.5 时钟配置Clock Configuration→ HCLK 168 MHz。时钟树里 USART3 挂载在 APB1 上42MHzCubeMX 会自动计算分频因子使波特率误差最小。6.6 生成工程Project ManagerProject Nameuart_debugToolchain/IDEMDK-ARM点击GENERATE CODE7. 代码解读7.1 完整代码/* USER CODE BEGIN Includes */#includestdio.h// printf、sprintf 等函数的声明/* USER CODE END Includes */intmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART3_UART_Init();/* USER CODE BEGIN 2 */intcount0;// 定义一个计数器/* USER CODE END 2 */while(1){count;// 每次循环加 1// 打印字符串printf(Hello from STM32F407!\r\n);// 打印变量的值printf(count %d\r\n,count);HAL_Delay(1000);// 每秒打印一次}}7.2 printf 格式化输出速查表intval42;floatpi3.14159f;printf(%d\r\n,val);// %d 十进制整数 → 42printf(%x\r\n,val);// %x 十六进制 → 2aprintf(%u\r\n,val);// %u 无符号十进制 → 42printf(%.2f\r\n,pi);// %.2f 小数后2位 → 3.14printf(val %d, pi %.2f\r\n,val,pi);// 多个变量// 字符串charname[]STM32;printf(chip: %s\r\n,name);// %s 字符串 → chip: STM32注意打印float类型需要开启 MicroLIB或换用sprintf加整数转换。7.3\r\n是什么\r 回车CR→ 光标回到行首 \n 换行LF→ 光标移到下一行 \r\n 合起来 回车换行 → 新的一行从头开始不同串口助手的处理不同只写\n有的助手不换行只写\r光标到行首但不换行覆盖之前的内容\r\n最保险的写法8. fputc 重定向详解核心难点8.1 为什么需要重定向printf 本身不知道往哪发printf是 C 语言标准库函数它只负责把数据格式化成一个字符串比如把count42变成count 42\r\n。但它不关心这个字符串最终去哪——屏幕文件串口fputc 是 printf 的底层出口每次 printf 调用内部都会多次调fputc一次发一个字符printf(Hello\r\n); ↓ 内部等价于 fputc(H, stdout); // stdout 标准输出 fputc(e, stdout); fputc(l, stdout); fputc(l, stdout); fputc(o, stdout); fputc(\r, stdout); fputc(\n, stdout);8.2 重定向代码做了什么intfputc(intch,FILE*f)// 这行是 C 库规定的格式不能改{HAL_UART_Transmit(huart3,// 通过 USART3 发出去(uint8_t*)ch,// 要发的字符转成字节指针1,// 发 1 个字节1000);// 超时时间 1000msreturnch;// 返回发出去的字符}代码解读int ch要发送的字符虽然是 int 类型但只存储了一个字符FILE *f流指针C 库的格式要求我们不用管它(uint8_t *)ch把 ch 的地址转成 uint8_t 指针因为 HAL_UART_Transmit 要求接收 uint8_t* 类型return ch返回发送的字符表示发送成功所以整体流程是printf(count %d\r\n, count); ↓ C 库格式化 → 得到字符串 count 42\r\n ↓ 逐个字符调 fputc → fputc(c, stdout)、fputc(o, stdout)... ↓ fputc 内部调用 HAL_UART_Transmit → 通过 USART3 发出去 ↓ USB-TTL 模块收到 → 通过 USB 传给电脑 ↓ 串口助手显示count 428.3 什么是 FILE *fFILE是 C 标准库中用来表示流stream的类型。常见的流stdin → 标准输入键盘 stdout → 标准输出屏幕/串口 stderr → 标准错误屏幕在嵌入式系统中我们重定向 fputc让stdout从显示器改成USART3 串口。所以FILE *f参数直接忽略。8.4 什么是 MicroLIB为什么需要 MicroLIBKeil 默认带的 C 库ARM C Library功能很全——支持文件系统、异常处理、区域语言等。但这也意味着占用的 Flash 空间大printf 重定向配置非常复杂要设置syscall.c等MicroLIB是 ARM 提供的一个精简版 C 运行库体积小几十 KBprintf 重定向直接写 fputc 就行不需要额外配置支持绝大部分常用功能printf、sprintf、strlen 等副作用不支持浮点 printf%f——但可以用sprintf加整数转换绕过不能同时使用标准的文件操作fopen/fread 等开启方法Keil → Options for Target → Target → 勾选Use MicroLIB9. 用 printf 调试——这才是重点这一节演示调试debug的核心看变量内部的值9.1 第一个真实调试场景看中断触发了多少次结合之前的 PIR 实验不需要实际接 PIR 硬件就能演示原理。用 while 里的软件自增变量来模拟。在/* USER CODE BEGIN 2 */MX 初始化完成后添加/* USER CODE BEGIN 2 */intcount0;/* USER CODE END 2 */在 while(1) 循环里/* USER CODE BEGIN WHILE */while(1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */count;// 每次都打印printf(程序运行了 %d 次循环\r\n,count);// 每 100 次打印一次减少刷屏if(count%1000){printf( 已经跑了 %d 次 \r\n,count);}HAL_Delay(10);// 10ms 一次}9.2 第二个真实调试场景看传感器的 ADC 值// 假设你用 ADC 读取了光敏模块的值uint32_tadc_valueHAL_ADC_GetValue(hadc1);// 调试打印printf(ADC value %d (0~4095)\r\n,adc_value);// 也可以打印十六进制printf(ADC hex 0x%04X\r\n,adc_value);// 根据值做简单判断if(adc_value500){printf(▶ 光线很暗\r\n);}elseif(adc_value2000){printf(▶ 光线一般\r\n);}else{printf(▶ 光线充足\r\n);}9.3 第三个场景跟踪函数的执行路径voidmy_function(intparam){printf([DEBUG] 进入 my_function, param %d\r\n,param);if(param100){printf([DEBUG] 分支 A 执行\r\n);// 分支 A 的代码}else{printf([DEBUG] 分支 B 执行\r\n);// 分支 B 的代码}printf([DEBUG] 离开 my_function\r\n);}在串口助手上你会看到[DEBUG] 进入 my_function, param 50 [DEBUG] 分支 B 执行 [DEBUG] 离开 my_function这就是 debug——你知道程序走了哪条路、参数是什么不用靠猜。9.4 调试系统时间// 打印系统运行时间printf(系统运行了 %lu ms\r\n,HAL_GetTick());// 测量函数执行时间uint32_tstartHAL_GetTick();my_function();uint32_tendHAL_GetTick();printf(my_function 耗时 %lu ms\r\n,end-start);10. 串口助手的设置10.1 常用串口助手Windows 推荐SSCOM友善串口助手—— 小巧够用MobaXterm—— 可以切换十六进制显示10.2 设置步骤① USB-TTL 插入电脑 ② 设备管理器 → 查看 COM 口号如 COM3 ③ 打开串口助手 ├── 端口号COM3和你设备管理器一致 ├── 波特率115200 ├── 数据位8 ├── 校验位None ├── 停止位1 └── 打开串口如果选错 COM 口会提示端口被占用或端口不存在如果波特率不对收到乱码10.3 两个常见操作DTR 和 RTS 大部分串口助手的 DTR 默认勾选会导致 STM32 复位 如果 STM32 反复重启 → 取消勾选 DTR HEX 显示 vs 文本显示 想看数值 → 文本显示能看到 count 42 想看原始字节 → HEX 显示能看到 0x63 0x6F 0x75...11. 常见问题问题 ①什么也收不到排查步骤 ① TX → RX 接反了检查接线 ② GND 接了吗必须共地 ③ 串口助手 COM 口选对了吗设备管理器确认 ④ 串口助手波特率设对了吗必须 115200 ⑤ 代码里勾选 MicroLIB 了吗 ⑥ fputc 写对了吗注意函数名是 fputc 不是 fputs问题 ②收到乱码原因 1波特率不匹配 确认 STM32 设 115200串口助手也设 115200 原因 2时钟配置不对 CubeMX 时钟树配错 → USART 波特率计算不准 → HCLK 必须 168 MHz 原因 3USB-TTL 模块坏了 换一个模块试试问题 ③程序烧录后没反应检查 Keil 的 Debug 设置 ① Options for Target → Debug → 选 ST-Link ② Utilities → 选 ST-Link ③ 点击 Settings → Flash Download → 勾选 Reset and Run 烧录后自动复位运行不用按复位键问题 ④为什么一定要用 MicroLIB不用 MicroLIB 你必须实现 _sys_open、_sys_write、_sys_close 等多个底层函数 printf 才能工作 非常麻烦 用 MicroLIB 只实现一个 fputc 就够 轻量、简单12. 扩展思考12.1 如何从电脑发数据给 F407// 在中断回调里接收voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart-InstanceUSART3){// rx_byte 就是电脑发来的数据printf(收到: 0x%02X (%c)\r\n,rx_byte,rx_byte);}}12.2 多个 printf 重定向如果要同时用 USART1 和 USART3 打印// 重定向到 USART3intfputc(intch,FILE*f){HAL_UART_Transmit(huart3,(uint8_t*)ch,1,1000);returnch;}// 自定义函数用 USART1 发voiddebug_printf_USART1(constchar*fmt,...){charbuf[256];va_list args;va_start(args,fmt);vsnprintf(buf,sizeof(buf),fmt,args);va_end(args);HAL_UART_Transmit(huart1,(uint8_t*)buf,strlen(buf),1000);}12.3 sprintf LCD 显示chardisplay_buf[32];sprintf(display_buf,距离: %d cm,distance);// 把这个字符串显示到 LCD 上等你的 LCD 有了文字显示功能后编写日期2026年5月25日适用硬件DshanMCU-F407STM32F407ZGT6 USB-TTLHW-597模块配套视频07-3-1 UART 串口编程查询方式前置知识已掌握 GPIO 输出/输入、定时器关键概念fputc 重定向、MicroLIB、波特率、8N1 帧格式