STM32 ADC中断进不去?HAL库回调与CubeMX配置深度排查
2026/8/30 11:45:25 网站建设 项目流程

1. 问题现象与初步定位

先说结论:如果你在 STM32 Nucleo F446RE 上遇到 ADC 中断进不去、回调函数不执行、转换结果永远停在初始值这类问题,八成不是芯片坏了,而是你对 HAL 库的中断处理流程理解有偏差。这类问题我前后折腾了一个下午才彻底搞清楚,把关键点梳理出来,希望能帮你少走弯路。

我当时的场景是这样的:用 STM32CubeMX 配置 ADC1 的 IN0 通道,开启 ADC 全局中断,然后想让 ADC 每次转换完成都触发一次中断,在回调函数里读取转换值来做后续控制逻辑。配置完生成代码,烧进去,结果发现中断一次都没触发。最诡异的是,程序能跑,主循环正常,其他外设如串口也正常工作,唯独 ADC 中断像消失了一样。

排查过程中我试过几种方法:先检查 CubeMX 里是否勾选了 NVIC 的 ADC interrupt,确认勾了;再检查时钟树,ADC 时钟配置为 APB2 的 4 分频,没问题;甚至换了一个引脚重新配置。最终问题出在一个容易被忽略的细节上,后面会在第四节里拆开说。

实际上,这种“IRQ handler not working properly”的问题,在 STM32 家族里非常典型,网上搜索量一直很高,很多刚接触 HAL 库的人都会卡在这里。因为这个问题的表象看似简单,背后涉及的却是中断优先级分组、HAL 库回调机制、ADC 转换模式的选择以及中断标志位处理等一系列知识点。

2. STM32 ADC 中断的核心机制解析

2.1 规则组与注入组的区别,理解 ADC 中断的根源

在 STM32 的 ADC 模块里,中断源主要围绕两类转换来完成:规则组转换和注入组转换。规则组就是你最常用的普通通道转换,比如一组通道轮流采样;注入组类似于抢占式的转换,可以打断正在进行的规则组转换,一般用在需要高优先级采样的场景。

F446RE 的 ADC1 规则组转换对应的中断标志是EOC(End of Conversion),注入组对应的是JEOC(End of Injected Conversion)。此外还有模拟看门狗事件中断 AWD、溢出中断 OVR 等。这里最容易混淆的是:你以为开了“ADC 全局中断”就等于所有中断都处理了,实际上 HAL 库的HAL_ADC_IRQHandler会根据这些不同的标志位分发到不同的回调函数里。

如果你使用的是扫描模式(Scan Mode),那么在连续扫描多个通道时,每个通道转换结束都会产生 EOC 标志。但这里有个大坑:只有当DMA 被禁用且选择的是单次转换模式时,EOC 中断才会正常触发。如果你开启了连续转换模式(Continuous Conversion Mode),转换结果会不断刷新,EOC 持续置位,这时候如果代码没有正确处理,中断可能进入一个死循环式的反复触发,导致看起来就像中断“乱掉了”。

2.2 中断标志位的清与应用

很多人在中断服务函数里手动清标志位时踩坑,比如直接调用__HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_EOC)。表面上看寄存器确实清零了,但实际上下一次转换结束又会重新置位,这是正常的。问题的关键在于你必须厘清什么时候该软件清除,什么时候硬件自动清除。

对于单次转换模式下的 EOC 中断,转换完成时硬件自动置位 EOC,读ADCx->DR寄存器会自动清除 EOC 标志。因此你在中断回调里直接读HAL_ADC_GetValue()是不会出问题的。但如果你在回调里先手动清了 EOC 再去读数据寄存器,数据依然读得出来,可下次中断能否触发,就得看你清理的时机对不对了。

我实测下来发现:HAL 库的HAL_ADC_IRQHandler内部会在处理完 EOC 后自动调用HAL_ADC_ConvCpltCallback,并且它自己会通过读寄存器的方式把 EOC 标志清掉。所以你如果又在回调函数里手动调了一次读寄存器或清标志,也不至于出错,但如果你在回调之外、主循环里也去读 ADC 值,就可能出现标志被提前清掉、中断丢失的情况。

2.3 中断优先级分组对 ADC 中断的影响

另外一个容易忽略的因素是中断优先级分组。默认 CubeMX 生成的代码会把优先级分组设置为NVIC_PRIORITYGROUP_4,即全部 4 位用于抢占优先级,0~15 级抢占优先级,无子优先级。在这种分组下,如果你的 ADC 中断抢占优先级设置得过高(数值小代表优先级高),而且中断服务函数里处理的逻辑过重,比如打印日志、延时、调用阻塞函数,就会导致其他中断响应变慢,甚至出现 ADC 中断反复被更高优先级中断打断以至于标志位混乱。

我记得有一次为了调试,直接在 ADC 中断回调里用HAL_UART_Transmit(&huart2, ...)发送调试数据。串口的波特率是 115200,发送几十个字节需要几毫秒,这样一次回调就占用了好几毫秒,导致后续 ADC 中断一直被阻塞,表现就是“ADC 采样率上不去”,甚至偶发中断不触发的假象。所以中断回调里千万别做重活,正确做法是只置一个标志位,把读值和后续处理放到主循环里。

3. CubMX 配置与代码实现详解

3.1 从 CubeMX 开始的正确配置流程

既然要彻底解决这个问题,我把 CubeMX 里的配置步骤完整列出来,你可以照着检查一遍自己的工程。我用的是 STM32CubeMX 6.x 版本,固件包版本 F4 1.27.1,不同版本界面略有差异但核心配置相同。

第一步,在 Pinout & Configuration 页面里找到 Analog 选项,点开 ADC1,把 IN0 勾选上。如果你的板子用的是其他引脚,比如 PA1 对应 IN1,就选对应的通道。

第二步,点击 ADC1 的 Mode 按钮,选择Single-ended Input。然后在 Configuration 面板里设置参数:

参数项推荐值说明
Clock PrescalerAsynchronous clock mode / 4ADC 时钟不能超过 36MHz,F446RE 的 APB2 是 90MHz,4 分频后为 22.5MHz,留足裕量
Resolution12 bits默认即可
Scan Conversion ModeDisabled只转换一个通道时不开启
Continuous Conversion ModeDisabled单次转换模式,中断触发更容易控制
DMA Continuous RequestsDisabled不用 DMA 就关掉
End of Conversion SelectionEOC flag at end of single conversion每个通道转换结束即产生 EOC
Number Of Conversion1只转换一个通道

第三步,在 NVIC Settings 标签页里,勾选ADC1 global interrupt,同时要注意看旁边的优先级数字。我建议设置为抢占优先级 5 或 6,不要设置成 0,否则会抢占系统滴答中断导致HAL_Delay出问题。

第四步,生成代码。CubeMX 会帮你初始化好MX_ADC1_InitHAL_NVIC_EnableIRQ(ADC_IRQn),这些都不用改。关键是在你的用户代码里写启动转换的调用。

3.2 用户代码的关键实现

生成工程后,在main.cmain()函数里,你需要手动调用一次启动转换的函数。

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); /* 启动第一次 ADC 转换 */ HAL_ADC_Start_IT(&hadc1); while (1) { /* 主循环不做重活,只监控标志位 */ } }

看到这段代码,你可能会有疑问:HAL_ADC_Start_IT是不是只启动一次?其实它的作用是使能 ADC 并使能中断。在非连续转换模式下,每次转换完成后 ADC 会停止,你需要再次调用HAL_ADC_Start_IT才能开始下一次转换。所以一般的做法有两种:一种是每次转换完成回调里再启动下一次,形成“单次转换轮询式”的循环;另一种是开启连续转换模式,一次启动后一直采样。

如果是单次转换模式,需要在回调函数里再次启动:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { uint16_t adc_val = HAL_ADC_GetValue(&hadc1); /* 处理 adc_val */ /* 关键:启动下一次转换 */ HAL_ADC_Start_IT(&hadc1); } }

这里有个细节值得注意:HAL_ADC_GetValue返回的是一个 16 位的无符号整数,但实际有效位数取决于你配置的分辨率。12 位分辨率时,取值范围是 0~4095。当配置为 12 位时,ADC 数据寄存器中的低 4 位无效,左对齐和右对齐模式下读取到的数据会不一样。CubeMX 默认是右对齐,所以直接读取就是正确结果。如果你改成了左对齐,就需要把读到的值右移 4 位才是真实转换值。这个细节在我第一次调试时也踩过坑,读出来的数据满量程乱跳,怀疑人生。

3.3 如果你用了连续转换模式,得知道这套写法

某些场景下,比如音频采样、连续监测传感器波形,你需要 ADC 不停地采样。这时候可以开启连续转换模式,并且只需要在初始化后调用一次HAL_ADC_Start_IT,后续 ADC 就会不断转换并持续触发中断。

这里需要注意一个 HAL 库版本之间的差异:早期版本的 HAL 库在连续转换模式下,EOC 标志在中断回调读取HAL_ADC_GetValue后会被清除,转换仍然继续;但在某些版本里,HAL_ADC_Start_IT会导致 ADC 启动时立即产生一次中断,然后后续转换却不触发中断了,原因是启动时软件触发了一次转换,转换结束产生了 EOC,但由于中断还未使能,这一次标志被积压,之后中断使能了,这个旧标志又触发了一次多余的中断,反而扰乱了判断逻辑。

如果遇到这种“只触发一次中断,之后再也不触发”的怪现象,优先检查是不是连续转换模式下 EOC 标志没有被正确清除。我建议在回调函数里加上下面的清标志操作,虽然 HAL 库理论上会处理,但加一行能让你排除这个变量:

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { __HAL_ADC_CLEAR_FLAG(&hadc1, ADC_FLAG_EOC); uint16_t adc_val = HAL_ADC_GetValue(&hadc1); /* 业务处理 */ } }

3.4 直接操作寄存器的排查法

如果你用的是标准外设库或者寄存器操作,判断逻辑会更直接。ADC 初始化完成后使能 ADC 和中断:

ADC1->CR2 |= ADC_CR2_ADON; // 使能 ADC1 ADC1->CR1 |= ADC_CR1_EOCIE; // 使能转换结束中断 ADC1->CR2 |= ADC_CR2_SWSTART; // 软件触发转换

在中断服务函数里:

void ADC_IRQHandler(void) { if (ADC1->SR & ADC_SR_EOC) { uint16_t value = ADC1->DR; // 读 DR 自动清 EOC /* 处理 value */ } }

这段代码可以在不借助 HAL 库的情况下验证你的 ADC 中断到底能不能触发。如果在寄存器操作下都能正常进中断,说明硬件没问题,问题就出在上层封装的使用逻辑上。这是我认为最有效的排查方法——绕开 HAL 库这个中间层,直达硬件验证。

4. 常见问题与排查技巧实录

4.1 中断进不去,先检查这几处

针对“IRQ handler not working properly”,我总结了一套排查步骤,按顺序操作基本能定位 90% 以上的问题。

第一,检查 NVIC 是否真的使能了 ADC 中断。在 CubeMX 里看是一回事,生成代码之后再看一眼stm32f4xx_hal_msp.c里的HAL_ADC_MspInit函数,确认里面有HAL_NVIC_EnableIRQ(ADC_IRQn)这行代码。我遇到过有人改了 CubeMX 配置但没重新生成代码,导致中断始终没被使能的情况。

第二,检查是否有其他代码修改了中断分组。比如你自己在某个外设初始化里调用了HAL_NVIC_SetPriorityGrouping,或者在启动代码里做了特殊处理,都会影响中断的抢占关系。F446RE 的默认中断分组是 CubeMX 在SystemClock_Config里调的,具体在HAL_InitTick里,别去乱动它。

第三,在中断服务函数入口处打断点。直接在看stm32f4xx_it.c文件里找到ADC_IRQHandler函数,在函数入口处打断点。如果程序正常运行却没有停在这个断点,说明中断硬件上就没有触发;如果停在这里但没有继续进入HAL_ADC_IRQHandler,说明中断服务函数里的代码被你自己改坏了。

第四,检查是否被其他更高优先级的中断阻塞。我前面提到过,如果在优先级 0 的中断回调里做了长时间阻塞操作,ADC 中断(优先级更低)会被无限期推迟。这时候表现为 ADC 中断“完全不工作”,但实际上是饿死了,不是硬件问题。

第五,检查 ADC 是否真的启动转换了。HAL_ADC_Start_IT这个函数名称虽然叫 Start,但它内部做了两件事:校准 ADC 和启动转换。如果在校准阶段就出错,函数返回HAL_ERROR,后续就没有转换产生。调试时留意HAL_ADC_Start_IT的返回值,如果返回错误,先检查 ADC 的校准状态,必要时手动调用HAL_ADCEx_Calibration_Start

4.2 中断回调总是执行但值不对,问题出在读取时序

进了中断、回调也执行了,但读到的 ADC 值永远是 0,或者永远是上一次的值,这个问题也很常见,一般原因有两个。

第一个原因是读取时机太早。在HAL_ADC_ConvCpltCallback里,转换已经完成,EOC 标志已经置位,数据寄存器里已经有有效数据,此时读取是没问题的。但如果你在别的地方(比如 EXTI 中断回调里)尝试读取 ADC 的值,可能转换还没完成,读到的就是旧数据。解决办法是在读取之前检查 EOC 标志位,或者直接利用转换完成回调来读取。

第二个原因是多通道扫描模式下没有正确切换通道。如果你配置了多个通道扫描,但没有配置 DMA 来搬运数据,那么每次 EOC 中断只代表当前某个通道转换完成,中断回调里需要判断当前是哪个通道,并保存到对应的变量里。HAL 库不帮你做这个判断,需要自己实现。

uint16_t adc_values[4]; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { uint32_t ch = hadc->Instance->SQR3 & ADC_SQR3_SQ1; switch (ch) { case 0: // 通道 0 adc_values[0] = HAL_ADC_GetValue(&hadc1); break; case 1: // 通道 1 adc_values[1] = HAL_ADC_GetValue(&hadc1); break; /* ... */ } }

这是我踩过的一个大坑:配置了两路 ADC 输入,分别接电位器和光敏电阻,满心以为每次中断都能拿到对应通道的值,结果发现第一次中断读到的地址是通道 0 的值,第二次还是通道 0 的值,后来才意识到扫描模式下必须手动维护通道索引。

4.3 中断触发过于频繁导致的“假死”问题

如果把连续转换模式和中断结合起来用,可能存在一个隐患:ADC 转换速度非常快,F446RE 的 ADC 在 22.5MHz 时钟下,12 位分辨率转换时间大约为 3.2us,也就是说理论上每秒可以触发约 30 万次中断。如果你的中断回调处理耗时超过 3.2us,CPU 就永远在处理中断,主循环直接被饿死,整个系统表现为“死机”。

我测试过:在回调里加一个 GPIO 翻转来输出方波,用示波器观察频率。开启连续转换模式、不做任何滤波处理,回调里翻转 GPIO,示波器显示的方波频率高达 150kHz 左右,此时 CPU 占用率几乎是 100%,串口发送、按键扫描全部失效。

遇到这种情况,建议改用以下任意一种方案:

  • 降低 ADC 转换速度,比如把 ADC 时钟分频加大,或者把分辨率从 12 位降到 10 位;
  • 开启 DMA 传输,多个采样值存到缓冲区后,利用 DMA 传输完成中断统一处理,而不是每个样本都触发一次 CPU 中断;
  • 在中断回调里加一个简单的时间滤波,比如每隔 N 次转换才处理一次,虽然这个方案比较粗糙,但简单有效。

4.4 使用 ST-LINK 在线调试时的一个陷阱

调试 F446RE 时我还有一个体会:用 ST-LINK 在线仿真跑 ADC 中断程序,和在板上独立运行的效果可能不一样。原因在于仿真器会通过 SWD 接口周期性地暂停 CPU 来读取寄存器,这会干扰 ADC 的实时性,特别是当你设置了中断点后,ADC 中断可能被拖延,导致时序完全错乱。

所以,调试 ADC 中断相关代码时,尽量用串口打印、LED 翻转、DAC 输出等方式观察结果,而不是频繁打断点查看变量。这是我用惨痛教训换来的建议。有一次我以为找到了问题所在,在HAL_ADC_ConvCpltCallback里设置了断点,程序每次都能停住,看起来“正常工作”,但我一但单步执行,后续的转换中断就全乱了。最终拔掉 ST-LINK 直接上电运行,现象完全不同。

5. 一个经过验证的最小可用工程示例

为了避免你只看理论不动手,我提供一个完整可运行的最小工程代码框架。基于 CubeMX 生成的代码结构,在main.cmain函数和stm32f4xx_it.c里稍作修改即可。

main.c 的核心函数:

/* Private variables */ ADC_HandleTypeDef hadc1; uint16_t g_adc_value = 0; volatile uint8_t g_adc_flag = 0; /* ADC init function */ static void MX_ADC1_Init(void) { hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; hadc1.Init.DMAContinuousRequests = DISABLE; hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; if (HAL_ADC_Init(&hadc1) != HAL_OK) { Error_Handler(); } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); HAL_ADC_Start_IT(&hadc1); while (1) { if (g_adc_flag) { g_adc_flag = 0; /* 这里使用 g_adc_value 做处理 */ printf("ADC Value: %d\r\n", g_adc_value); } } } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { g_adc_value = HAL_ADC_GetValue(&hadc1); g_adc_flag = 1; HAL_ADC_Start_IT(&hadc1); /* 启动下一次转换 */ } }

注意事项:

  • printf重定向需要你自己实现fputc函数,F446RE 上可以用 ST-LINK 虚拟串口,或者通过板载的 USB 转串口芯片输出到电脑。这个细节在这里不展开,但你们在测试时要注意串口工具里的波特率要设置成和代码里一致。
  • 中断回调里只赋值两个变量,不做重活,不调用HAL_UART_Transmit,不在中断里做数学运算,这些经验在前面已经提过。
  • ContinuousConvMode设为DISABLE时,每次转换完需要重新调用HAL_ADC_Start_IT。如果你忘了这一步,程序只会在启动时进一次中断,之后便一直安静地待着。这是很多“IRQ handler not working properly”问题的真实原因,不是没配置好,而是没有再次启动转换。
  • 如果ContinuousConvMode设为ENABLE,则第一次调用HAL_ADC_Start_IT之后就不要再在回调里启动下一次了,否则可能出现意想不到的重复启动。具体来说,连续模式下,ADC 会在转换完成后立即开始下一次转换,EOC 标志会周期性置位,中断自然持续触发,回调里再调用HAL_ADC_Start_IT实际上是在已经运行的 ADC 上重复执行启动,有些版本可能返回HAL_BUSY而直接忽略。

这是一个非常容易出错的细节,我特意把两种模式下的用法都列出来,你在工程里一定要先搞清楚自己的配置再动手改代码。

6. 关于中断回调中不能做的几件事

最后专门写一段,算是我反复踩坑后总结的经验。你在 ADC 中断回调(其实任何中断回调都一样)里,千万别做这几件事:

  • 不要调用HAL_Delay它的实现依赖 SysTick 中断,如果你的 ADC 中断优先级比 SysTick 高,就会在这里死等;如果比 SysTick 低,理论上能工作,但延时期间其他中断无法响应,性能大打折扣。
  • 不要在回调里调用耗时较长的库函数,比如串口发送大量数据、擦写 Flash、等待 I2C 通信完成。中断应该是“短平快”的,能置标志就置标志,能存变量就存变量,其余事情主循环做。
  • 不要直接用 matlab 这种重型工具去解析实时数据。我见过有人把 ADC 采样值通过串口发到电脑,再用 Python 脚本做实时绘图,结果板子端的串口发送缓冲一满,中断回调就被阻塞,系统行为完全变形。数据要打印,但要控制速率,比如每秒发 20 个点足够看到趋势了。
  • 不要在回调里动态分配内存。嵌入式环境下用malloc本身就有风险,在中断上下文里动态分配更是危险操作,可能导致不可预测的混乱。

我自己的经验法则是:中断回调里的执行时间控制在 10us 以内。F446RE 主频 180MHz,一个简单的赋值语句只需要几个时钟周期,10us 相当于执行几千条指令,对绝大多数业务来说绰绰有余。超过这个量级就要反思是不是把太多事情塞进中断里了。

7. 更深层的避坑指南:从项目实战中提炼的几点心得

如果你已经按照上面的步骤排查过,ADC 中断依然“不工作”,我建议你从项目整体角度再审视一遍。以下几个方向是很多人忽略的:

电源和参考电压的影响。Nucleo F446RE 板上的 VDDA 和 VREF+ 默认是接到 3.3V 的,但如果你外接了传感器,传感器的输出电压范围超出了 0~3.3V,或者传感器和单片机不共地,ADC 采到的值就会异常。这种异常不直接表现为中断不触发,但会表现为值不对、波动大,进而让你误判是中断处理逻辑的问题。所以排查 ADC 问题的时候,先确认输入信号是否干净。

引脚复用的干扰。F446RE 的某些 ADC 输入引脚和调试接口引脚有复用,比如 PA0 还可能是 TIM2_CH1 等。如果你的初始化代码里在其他外设的 GPIO 配置中把这个引脚重映射了,ADC 转换就会失败。特别是连了外部按键或者 LED 的引脚,很容易被别的初始化函数改掉复用功能。

多通道扫描时的数据管理。我前面提到过扫描模式下手动判断通道索引。实际上更推荐的做法是直接用HAL_ADC_Start_DMA,这样多通道的结果会自动按顺序存到数组里,中断回调里只需要在一个半满或全满事件中统一处理。这种方式效率更高,代码也更简洁,很多热词搜索里提到的“stm32 adc多通道扫描循环采样dma”就是在讲这个场景。用 DMA 之后,CPU 负担大大降低,中断频繁导致的假死问题也会自然消失。

在实时性要求高的应用里,使用定时器触发 ADC 转换。如果你需要精确的采样间隔,比如音频采样、电机的电流环采样,软件触发是满足不了要求的。正确的做法是配置一个定时器(如 TIM2)输出 TRGO 事件,让硬件自动触发 ADC 转换,转换完成后再触发 DMA 搬运,整个过程不需要 CPU 干预。这就是论坛里常说的“adc定时器触发”方案。F446RE 的 ADC 外部触发源可以配置为 TIM1、TIM2、TIM3 等的 TRGO 事件,这在 CubeMX 的外设配置里都有选项。

我自己的项目里,做电机电流采样时就是用的这种方案:TIM1 的更新事件触发 ADC1 注入组转换,转换结果通过 DMA 传输到内存数组,DMA 传输完成中断里做 FOC 算法。整个流程里,EOC 中断只在启动阶段参与了一次,之后完全靠 DMA 中断来做节奏控制。可以这么说,只要你的需求里有一点“周期性采样”的影子,就不要再依赖软件触发和 EOC 中断了,硬件定时器触发加 DMA 才是正解。

8. 写在最后的调试心法

回到最初的问题:STM32 Nucleo F446RE 的 ADC IRQ handler not working properly。这类问题看似复杂,本质上是几个环节中的某一个没对上。我把排查顺序再压成一句话:

先看中断使能,再看转换启动,然后查回调函数是否在正确的文件里,最后用寄存器版本验证硬件。

如果这些步骤做完了还不能解决,99% 的情况是你对 HAL 库某个模式的理解有偏差,回到第四节里的对比表,逐项核对你的配置。还有一个土办法我想推荐给你:把工程简化到极致,只保留 ADC 中断和 GPIO 翻转,其他全部注释掉,让板子在最小系统里裸奔,然后用示波器观察 GPIO 翻转频率。如果这一步能出来波形,说明 ADC 中断链路是通的,剩下的就是在你的业务代码里找问题了。这种方法虽然土,但真的能帮你快速缩小排查范围。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询