STM32F103单通道ADC中断采样实战:稳定、低耗、易调试

发布时间:2026/9/16 2:12:29
STM32F103单通道ADC中断采样实战:稳定、低耗、易调试
简介本资源是一份面向嵌入式初学者与STM32开发者的ADC实战代码包聚焦STM32F103单通道模拟信号采集的中断读取实现解决传统轮询方式CPU占用高、实时性差等典型痛点。资源共74个文件含30个头文件.h定义外设配置与函数接口、29个源文件.c实现ADC初始化、中断服务程序及串口调试输出、8个汇编启动文件.s辅以Keil工程配置.uvprojx/.uvoptx、链接脚本.sct、可执行镜像.hex及一键清理脚本.bat整体压缩包仅294KB结构规范、开箱即用。已有662人学习下载配套readme.txt说明清晰工程目录按标准CMSISFWlib分层组织含stm32f10x_it.c中断向量表定制、main.c主流程控制及usart模块用于结果打印便于理解中断触发机制、EOC标志处理与ADC数据实时响应逻辑是掌握STM32模数转换核心能力的精简高效实践范例。1. 单通道 ADC 中断读取为什么 STM32F103 上不用 DMA 反而更稳在 STM32F103 这类资源受限的 Cortex-M3 微控制器上做电压监测、传感器读取或电池电量采样时很多人第一反应是“上 DMA ADC 扫描”但实际工程中单通道、中断触发、手动启动的 ADC 模式反而是最可靠、最易调试、最省 RAM 的选择。它不依赖复杂的 DMA 链表配置不引入总线仲裁冲突也不受 FIFO 溢出或通道映射错位影响——尤其当只监控一个关键信号如 VDDA 分压、NTC 温度、光敏电阻时中断方式能精准控制采样时机、规避电源噪声干扰并在主循环被阻塞时仍保证数据及时捕获。本方案面向嵌入式固件工程师聚焦STM32F103原生标准外设库v3.5.0不依赖 HAL 或 CubeMX 自动生成代码所有配置均落在main.c和stm32f10x_it.c两个文件内适配最小系统板无外部晶振、仅 HSI 运行、支持adc采样周期精细调节、可直接对接c语言adc值滤波函数后处理且完全规避gd32e230 adc dma数据紊乱类硬件兼容性风险。2. 从寄存器到中断服务ADC 单通道中断模式的完整初始化链2.1 时钟使能与 GPIO 复用配置先让 PA0 真正成为 ADC1_IN0ADC 采样精度直接受时钟稳定性与模拟输入路径完整性影响。STM32F103 的 ADC1 仅挂载在 APB2 总线上必须显式使能其时钟同时PA0 默认为 GPIO 输入需通过GPIO_Init()将其复用为模拟输入模式并关闭上拉/下拉——这是adc端口保护电路设计前最关键的软件前置动作。// main.c - RCC GPIO 初始化段 RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_ADC1 | RCC_APB2PERIPH_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; // PA0 GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; // 必须设为模拟输入 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure);注意若未调用RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_ADC1, ENABLE)后续ADC_Cmd(ADC1, ENABLE)将静默失败ADCDR 寄存器始终为 0且无任何错误标志位可查——这是新手最常踩的“无声陷阱”。2.2 ADC 核心参数配置采样周期、转换模式与中断使能STM32F103 的 ADC 支持 1.5/7.5/13.5/28.5/41.5/55.5/71.5/239.5 个 ADC 时钟周期的采样时间。对单通道、低速变化信号如热敏电阻推荐使用 239.5 周期ADC_SampleTime_239Cycles5它显著提升信噪比SNR有效抑制adc信噪比下降导致的adc数据漂移代价仅是单次转换耗时增加约 12μs按 ADCCLK14MHz 计。同时必须禁用扫描模式ADC_ScanConvMode(DISABLE)启用连续转换ADC_ContinuousConvMode(ENABLE)并开启 EOC 中断ADC_IT_EOC(ENABLE)// main.c - ADC 初始化段 ADC_DeInit(ADC1); // 先复位避免残留配置干扰 ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode ADC_Mode_Independent; // 独立模式F103 只有 ADC1 ADC_InitStructure.ADC_ScanConvMode DISABLE; // 关闭扫描单通道专用 ADC_InitStructure.ADC_ContinuousConvMode ENABLE; // 连续转换靠中断触发读取 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; // 不用外部触发 ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; // 右对齐低位补 0便于后续滤波 ADC_InitStructure.ADC_NbrOfChannel 1; // 仅 1 个通道 ADC_Init(ADC1, ADC_InitStructure); // 设置 PA0 对应的通道 0采样时间设为最长档 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); // 开启 ADC 转换完成中断EOC和 ADC 本身 ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE); // 注意不是 ADC_IT_JEOC注入通道 ADC_Cmd(ADC1, ENABLE); // 必须执行一次校准上电后首次启用 ADC 前 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1));提示ADC_SampleTime_239Cycles5是adc采样周期最长选项适用于高阻抗信号源如分压网络输出 10kΩ。若信号源阻抗 1kΩ可降为ADC_SampleTime_55Cycles5以加快响应——但务必同步检查adc/dac 电路设计:规避时钟抖动与电源噪声的3个pcb布局要点中的去耦电容布放否则长采样周期反而放大噪声。2.3 NVIC 中断优先级配置与中断向量注册STM32F103 使用stm32f10x_it.c统一管理中断服务函数。ADC 中断号为ADC1_2_IRQn因 ADC1 和 ADC2 共享同一 IRQ需在main.c中配置 NVIC 优先级并确保stm32f10x_it.c内存在对应void ADC1_2_IRQHandler(void)函数体// main.c - NVIC 配置段在 ADC_Cmd(ADC1, ENABLE) 之后 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel ADC1_2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; // 抢占优先级 1高于 SysTick NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; // 子优先级 0 NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure);// stm32f10x_it.c - 添加中断服务函数非弱定义必须显式实现 extern __IO uint16_t ADC_ConvertedValue; // 全局变量用于暂存结果 void ADC1_2_IRQHandler(void) { if (ADC_GetITStatus(ADC1, ADC_IT_EOC) ! RESET) { ADC_ConvertedValue ADC_GetConversionValue(ADC1); // 读取 DR 寄存器 ADC_ClearITPendingBit(ADC1, ADC_IT_EOC); // 清除中断标志 } }关键逻辑说明ADC_GetConversionValue(ADC1)实质是读取ADC1-DR寄存器该操作会自动清除 EOC 标志位——但必须显式调用ADC_ClearITPendingBit()否则中断会持续触发因 NVIC pending bit 未清。这是中断配置中极易遗漏的一步会导致 CPU 被锁死在中断服务中。3. 在主循环中安全读取与滤波避免裸寄存器访问引发的数据竞争3.1 双缓冲机制用 volatile 标志位解决 ISR 与主循环数据同步直接在main()中轮询ADC_ConvertedValue是危险的若 ISR 正在写入该变量而主循环同时读取可能得到半更新值。标准做法是引入一个volatile标志位由 ISR 置位、主循环清零形成轻量级同步// main.c 全局区 __IO uint16_t ADC_ConvertedValue 0; __IO uint8_t ADC_ConversionComplete 0; // volatile 标志位 // stm32f10x_it.c 中修改 ISR void ADC1_2_IRQHandler(void) { if (ADC_GetITStatus(ADC1, ADC_IT_EOC) ! RESET) { ADC_ConvertedValue ADC_GetConversionValue(ADC1); ADC_ConversionComplete 1; // 仅置位不读/写其他变量 ADC_ClearITPendingBit(ADC1, ADC_IT_EOC); } }// main.c 主循环片段 while (1) { if (ADC_ConversionComplete) { uint16_t raw_val ADC_ConvertedValue; // 原子读取 ADC_ConversionComplete 0; // 清零标志 // 此处可安全调用 c语言adc值滤波函数 uint16_t filtered_val MedianFilter(raw_val); // 示例中值滤波 float voltage (filtered_val * 3.3f) / 4095.0f; // 转换为电压假设 VREF 3.3V // 后续处理阈值判断、串口发送等 if (voltage 2.8f) { LED_ON(); } } }参数说明MedianFilter()是典型c语言adc值滤波函数接收原始 12-bit 值0–4095返回经 5 点滑动窗口中值滤波后的结果可有效抑制脉冲噪声。其内部不涉及全局变量或阻塞操作与中断上下文完全解耦。3.2 采样率控制用 SysTick 或定时器中断触发 ADC 启动而非依赖连续模式虽然上节启用了ADC_ContinuousConvMode(ENABLE)但实际项目中往往需要固定采样率如 100Hz 电压监测而非 ADC 自由运行。此时应关闭连续模式改用定时器中断定期触发 ADC 转换// main.c 修改 ADC 初始化 ADC_InitStructure.ADC_ContinuousConvMode DISABLE; // 关闭连续 ADC_Cmd(ADC1, ENABLE); // ... 其他配置不变 // 在 SysTick_Handler() 或 TIMx_IRQHandler() 中启动转换 void SysTick_Handler(void) { static uint32_t tick_count 0; if (tick_count 1000) { // 1ms SysTick此处实现 1s 定时 tick_count 0; ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 软件触发一次转换 } }对比说明连续模式适合高速流式采集1kHz但功耗高且难以精确控制间隔软件触发模式功耗更低、时序确定性强且天然规避stm32f103 多路 捕获场景下的通道切换延迟问题。两种模式均适用本方案选择取决于adc参数中的采样率需求。4. 排查常见中断失效从寄存器状态到 PCB 物理层的逐层验证4.1 中断未触发的三层诊断法寄存器 → NVIC → 硬件当ADC1_2_IRQHandler完全不执行时按以下顺序排查每步均有可执行命令或寄存器检查点层级检查项验证方法常见原因寄存器层ADC 是否就绪if(ADC_GetFlagStatus(ADC1, ADC_FLAG_RDY) RESET)未调用校准或ADC_Cmd(ADC1, ENABLE)失败NVIC 层中断是否使能if((NVIC-ISER[0] (1 ADC1_2_IRQn)) 0)NVIC_Init()未执行或 IRQ 通道号错误F103 是ADC1_2_IRQn非ADC_IRQn硬件层PA0 是否被强拉高/低用万用表测 PA0 对地电压stm32f103 最小系统 原理图中误接上拉电阻或adc端口保护电路二极管击穿// 在 main() 初始化后插入诊断代码 if (ADC_GetFlagStatus(ADC1, ADC_FLAG_RDY) RESET) { // ADC 未就绪检查时钟、校准、使能顺序 } if ((NVIC-ISER[0] (1 ADC1_2_IRQn)) 0) { // NVIC 未使能检查 NVIC_Init 参数 }4.2 数据异常的物理根源聚焦adc/dac 电路设计中的三个 PCB 要点即使软件配置正确adc数据漂移或adc信噪比低下往往源于 PCB 设计缺陷。针对 STM32F103 的adc采样电路必须落实以下三点VDDA 与 VSSA 独立走线VDDA 必须从主 VDD 经 LC 滤波10μH 10μF后单独引至 ADC 电源引脚禁止与数字 VDD 共用铜箔模拟地VSSA单点接地VSSA 引脚就近连接到板边模拟地覆铜区该区域不打过孔、不走数字信号线PA0 输入路径 100Ω 串联电阻 1nF 对地电容构成 RC 低通滤波截止频率 ≈1.6MHz抑制高频噪声进入 ADC 采样保持电路——这正是adc/dac 电路设计:规避时钟抖动与电源噪声的3个pcb布局要点的核心实践。实测反馈某批次板卡在未加 PA0 RC 滤波时ADC 读数标准差达 ±8 LSB加入 100Ω1nF 后降至 ±1 LSB且modbuspoll软件写stm32f103传感器通信时无跳变。5. 进阶技巧利用 ADC 注入通道实现快速事件响应不打断主采样流当系统需在特定事件如按键按下、外部中断触发瞬间捕获另一路模拟信号如电流检测又不想暂停主通道PA0的连续采样时可启用 ADC 注入通道。它拥有独立的规则组之外的 4 个通道、可配置的触发源EXTI、TIM/TRGO且转换结果存入JDRx寄存器不覆盖主 DR 寄存器——完美实现bmc通过adc读取电压是怎么做的类场景中的多优先级采样。// main.c 中追加注入通道配置以 PA1 为 ADC1_IN1 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_Init(GPIOA, GPIO_InitStructure); // 配置注入通道 1对应 PA1 ADC_InjectedChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5); ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T1_CC1); // 例用 TIM1 捕获触发 ADC_ExternalTrigInjectedConvCmd(ADC1, ENABLE); ADC_InjectedSequencerLengthConfig(ADC1, 1); ADC_InjectedConvModeCmd(ADC1, ENABLE); // 在 EXTI0_IRQHandler() 中启动注入转换 void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) ! RESET) { ADC_SoftwareStartInjectedConvCmd(ADC1); // 软件触发注入转换 EXTI_ClearITPendingBit(EXTI_Line0); } } // 在 ADC1_2_IRQHandler 中读取注入结果 void ADC1_2_IRQHandler(void) { if (ADC_GetITStatus(ADC1, ADC_IT_JEOC) ! RESET) { // 注意注入完成中断是 JEOC uint16_t injected_val ADC_GetInjectedConversionValue(ADC1, ADC_InjectedChannel_1); // 处理注入通道数据... ADC_ClearITPendingBit(ADC1, ADC_IT_JEOC); } // ... 其余 EOC 处理保持不变 }关键区别注入通道中断标志为ADC_IT_JEOC读取函数为ADC_GetInjectedConversionValue()且必须在ADC1_2_IRQHandler中同时处理EOC和JEOC——这是stm32f103rx的adc模块扫描模式下扫描规则通道和注入通道的标准共存模式。注入转换不干扰规则通道连续采样真正实现“事件驱动周期采样”双轨并行。本文还有配套的精品资源点击获取