news 2026/4/23 15:41:39

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/4/23 12:56:47

S7-1500在洁净空调控制系统中的实战应用

西门子S7-1500暖通空调制药厂洁净空调PLC程序案例,硬件采用西门子1500CPUET200SP接口IO模块,HMI采用西门子触摸屏。具体为制药厂BMS(洁净空调自控系统)医药洁净室程序,程序结构采用SCL编程。 有详细注释,很…

作者头像 李华
网站建设 2026/4/23 12:57:34

精通开关电源设计(第2版)资源下载

精通开关电源设计(第2版)资源下载 【免费下载链接】精通开关电源设计第2版资源下载 本仓库提供经典书籍《精通开关电源设计(第2版)》的资源下载。本书由浅入深地介绍了开关电源设计的各个方面,从基础的电感知识到复杂的…

作者头像 李华
网站建设 2026/4/23 13:03:21

开关电源设计实战指南:从理论到工程应用的完整解决方案

还在为复杂的开关电源设计而苦恼吗?面对电感参数计算、电路拓扑选择、噪声抑制等关键问题,你是否常常感到无从下手?现在,一本被誉为电源设计领域经典著作的《精通开关电源设计(第2版)》将为你提供全方位的技…

作者头像 李华
网站建设 2026/4/23 13:02:58

Harmony之路:一多适配之道——响应式布局与资源限定

Harmony之路:一多适配之道——响应式布局与资源限定从单设备到多设备,让应用在手机、平板、智慧屏上都能完美呈现在上一篇中,我们学习了服务卡片的开发技术,让应用能力突破应用边界。现在,我们将深入探讨HarmonyOS的一…

作者头像 李华
网站建设 2026/4/23 10:19:39

RAG系统构建必看:8种文本分块策略全解析,收藏备用!

本文详细解析了RAG系统中的8种文本分块策略,从基础的固定大小、递归分块到前沿的语义分块、代理分块和后置分块。每种策略各有优缺点和适用场景,如固定大小分块适合简单文档,层级分块适合结构化知识库,语义分块和后置分块适合高价…

作者头像 李华
网站建设 2026/4/23 10:17:25

用 Go 像写 Web 一样做桌面应用:完全离线的手机号归属地查询工具

前阵子我做了一个小工具:一个完全离线的手机号归属地查询桌面应用 功能本身其实并不复杂,但在这个过程中,我反而重新认识了一次 用 Go 做桌面应用,其实可以非常像在写一个 Web 项目。 这篇文章不打算讲手机号归属地怎么查&…

作者头像 李华