news 2026/5/12 15:24:53

ADC调试踩坑:一个printf引发的“血案“

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ADC调试踩坑:一个printf引发的“血案“

STM32 ADC调试踩坑记:一个printf引发的"血案"

前言

最近在调试STM32F429的ADC注入通道功能时,遇到了一个"诡异"的问题:注入通道转换完成后,规则通道停止更新

经过一番寄存器级调试,我找到了"解决方案",但最后发现——真正的凶手竟然是调试用的printf!

这个调试过程很有意思,记录下来分享给大家。

源码

仓库地址:https://github.com/SXSBJS-XYT/STM32/tree/ADC/ADC

环境信息

  • MCU: STM32F429IGT6
  • 开发工具: CubeMX + Keil MDK
  • HAL库版本: STM32Cube_FW_F4 V1.28.3

问题描述

功能需求

  • 规则通道(PA0):连续转换模式,后台持续采集
  • 注入通道(PA2):软件触发,高优先级打断规则通道

预期行为


按照STM32参考手册(RM0090)的描述:

11.3.9 注入通道管理 - 触发注入

  1. 通过外部触发或将ADC_CR2寄存器中的SWSTART位置1来启动规则通道组转换。
  2. 如果在规则通道组转换期间出现外部注入触发或者JSWSTART位置1,则当前的转换会复位,并且注入通道序列会切换为单次扫描模式。
  3. 然后,规则通道组的规则转换会从上次中断的规则转换处恢复。

手册明确说注入完成后规则通道会自动恢复

规则通道: [转换][转换][暂停][转换][转换]... ↑ ↑ 注入通道: 触发 完成 打断 自动恢复

实际现象

规则通道: [转换][转换][停止] ↑ 注入通道: 触发 打断后再也不恢复!

规则通道只在初始化后更新一次,触发注入后就再也不更新了。

第一轮调试:寄存器分析

定位问题边界

通过对比测试:

测试场景结果
只启动规则通道(不触发注入)✓ 正常,持续更新
只触发注入通道✓ 正常,回调触发
规则+注入同时使用✗ 注入后规则停止

问题出在两者的交互上。

寄存器调试

在注入完成回调里打印寄存器:

voidHAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef*hadc){s_injected_value=HAL_ADCEx_InjectedGetValue(hadc,ADC_INJECTED_RANK_1);printf("CR2=0x%08lX, SR=0x%08lX\r\n",hadc->Instance->CR2,hadc->Instance->SR);}

输出结果:

CR2=0x00000403, SR=0x00000008

寄存器解析

CR2 = 0x00000403:

Bit名称含义
0ADON1ADC开启 ✓
1CONT1连续转换模式 ✓
30SWSTART0规则通道启动位 = 0 ← 问题!

SR = 0x00000008:

Bit名称含义
3JSTRT1注入通道已启动
4STRT0规则通道未启动 ← 问题!

ADC没关,连续模式还在,但规则通道的启动位被清除了!

初步结论与修复

当时的分析:HAL库的问题,手册说会自动恢复,但实际没恢复。

修复方案——在回调里手动重启规则通道:

voidHAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef*hadc){s_injected_value=HAL_ADCEx_InjectedGetValue(hadc,ADC_INJECTED_RANK_1);/* "修复":手动重启规则通道 */hadc->Instance->CR2|=ADC_CR2_SWSTART;printf("CR2=0x%08lX, SR=0x%08lX\r\n",hadc->Instance->CR2,hadc->Instance->SR);}

加上这行后,规则通道确实恢复工作了。问题"解决",准备写博客吐槽HAL库…

剧情反转:真正的凶手

在整理代码时,我注释掉了调试用的printf,顺便也注释掉了SWSTART那行"修复"代码:

voidHAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef*hadc){s_injected_value=HAL_ADCEx_InjectedGetValue(hadc,ADC_INJECTED_RANK_1);s_injected_ready=true;// hadc->Instance->CR2 |= ADC_CR2_SWSTART; // 注释掉// printf("CR2=0x%08lX, SR=0x%08lX\r\n",// hadc->Instance->CR2,// hadc->Instance->SR);}

神奇的事情发生了——规则通道正常工作了!

验证测试

  • 有printf,无SWSTART ,规则通道停止

  • 有printf,有SWSTART ,规则通道工作
  • 无printf,无SWSTART ,规则通道工作
  • 无printf,有SWSTART ,规则通道工作
测试场景结果
有printf,无SWSTART✗ 规则通道停止
有printf,有SWSTART✓ 规则通道工作
无printf,无SWSTART✓ 规则通道工作
无printf,有SWSTART✓ 规则通道工作

真相大白:printf才是罪魁祸首!

根因分析

ADC转换时间: 约4.36μs (84采样周期 + 12周期 @ 22MHz) printf执行时间: 约500μs~2ms (取决于波特率和字符串长度)

时间线对比:

有printf时: ↓ 注入完成中断 中断回调: [读JDR][──────printf执行中(500μs+)──────][返回] 规则通道: 暂停...等待...等待...超时/异常?...停止 无printf时: ↓ 注入完成中断 中断回调: [读JDR][置标志][返回] ← 几μs 规则通道: 暂停 → 立即恢复 ✓

printf通过串口发送数据,在115200波特率下发送40个字符大约需要3.5ms,而ADC硬件期望中断快速返回以恢复规则通道转换。

长时间占用中断,干扰了ADC硬件的自动恢复机制!

为什么寄存器显示SWSTART=0?

这其实是正常的!SWSTART是触发位,置1后硬件自动清零:

SWSTART位由软件置1来启动转换,转换开始后由硬件清零。

在printf执行期间读取CR2,当然看到的是0。这并不意味着规则通道"没启动",而是"启动信号已经过去了"。

当时的分析思路错了——看到SWSTART=0就以为需要重新置位,实际上是printf延迟导致的错觉。

为什么加上SWSTART能"修复"?

虽然printf导致了问题,但在回调里加上CR2 |= ADC_CR2_SWSTART确实能让规则通道恢复:

voidHAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef*hadc){s_injected_value=HAL_ADCEx_InjectedGetValue(hadc,ADC_INJECTED_RANK_1);printf("...");// 干扰硬件自动恢复hadc->Instance->CR2|=ADC_CR2_SWSTART;// 手动补救}

这是"治标不治本"的方案——用软件手段弥补了printf带来的破坏。

最终正确的写法

voidHAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef*hadc){if(hadc==s_hadc){s_injected_value=HAL_ADCEx_InjectedGetValue(hadc,ADC_INJECTED_RANK_1);s_injected_ready=true;s_trigger_count++;/* 不要在这里printf! *//* 不需要手动重启规则通道,硬件会自动恢复 */}}

如果确实需要调试输出,用标志位在主循环里打印:

/* 中断回调 - 快进快出 */voidHAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef*hadc){s_injected_value=HAL_ADCEx_InjectedGetValue(hadc,ADC_INJECTED_RANK_1);s_debug_flag=true;// 设置标志}/* 主循环 - 在这里打印 */while(1){if(s_debug_flag){s_debug_flag=false;printf("Injected: %d\r\n",s_injected_value);}}

总结

问题根因

不是HAL库的bug,不是芯片的问题,是调试代码(printf)在中断里执行太久,干扰了ADC硬件的自动恢复机制。

核心教训

  1. 中断回调要快进快出:不要在中断里做printf、HAL_Delay等耗时操作
  2. 调试代码也会引入bug:海森堡效应——观测行为本身影响了被观测对象
  3. 不要急于下结论:找到"解决方案"后,要验证根因是否正确
  4. 相信手册,但要正确理解:手册说的"自动恢复"是对的,前提是中断正常返回

中断里应该做什么

允许禁止
读写寄存器printf/sprintf
设置标志位HAL_Delay
读写全局变量复杂计算
短小的赋值操作调用阻塞函数

这次调试经历提醒我:有时候bug不在你怀疑的地方,而在你最信任的地方——比如那行看起来人畜无害的printf。

如果这篇文章对你有帮助,欢迎点赞收藏。有问题可以在评论区讨论。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/9 10:49:16

色彩工程革命:Chroma.js如何重塑现代数字色彩处理

色彩工程革命:Chroma.js如何重塑现代数字色彩处理 【免费下载链接】chroma.js JavaScript library for all kinds of color manipulations 项目地址: https://gitcode.com/gh_mirrors/ch/chroma.js Chroma.js作为一款功能强大的JavaScript色彩处理库&#xf…

作者头像 李华
网站建设 2026/5/11 0:19:32

Harmony之路:安全之门——权限模型与动态权限申请

Harmony之路:安全之门——权限模型与动态权限申请从隐私保护到功能完整,掌握HarmonyOS权限管理的核心机制在上一篇中,我们学习了响应式布局与资源限定词,实现了应用在多设备上的完美适配。现在,让我们深入探讨HarmonyO…

作者头像 李华
网站建设 2026/5/10 0:29:10

让代码自己说话——AI驱动的自动化架构文档生成革命

问题背景:架构文档的沉默危机1.1 传统文档维护的困境在现代软件开发中,架构文档往往成为团队的技术债重灾区。根据行业调研,超过80%的技术团队面临以下挑战:文档滞后性:代码变更后,相关文档平均滞后2-4周更…

作者头像 李华
网站建设 2026/5/10 22:09:52

Dify 30天4次迭代的战略考量:AI应用开发平台实战指南!

简介 Dify在30天内密集发布4个版本,应对市场竞争与安全威胁。各版本重点修复安全漏洞、优化性能、重构多模态知识库。频繁迭代虽提升响应速度,但也带来技术风险、用户体验挑战和团队管理压力。未来将向安全左移、模态融合和生态开放方向发展&#xff0c…

作者头像 李华
网站建设 2026/5/11 14:46:06

国庆收心指南:用AI提示词工程解决节后综合征

程序员的节后困境相信很多同行都有过这样的经历:国庆7天假期,前4天出门旅游累成狗,后3天报复性熬夜刷剧打游戏。现在是10月7日,后天(10月9日)就要上班了,突然发现:生物钟混乱&#x…

作者头像 李华
网站建设 2026/5/11 14:46:13

基于STM32红外感应的自动迎客人语音控制系统设计

(一)系统功能设计 STM32单片机自动迎客门红外感应步进电机语音播报41 本系统由STM32F103C8T6单片机核心板、语音播报、ULN2003步进电机控制、红外避障传感器、按键及电源组成。 1、红外探头检测到有人时,自动门打开(步进电机向打开…

作者头像 李华