news 2026/4/23 14:49:04

简单理解:STM32F103 ADC 单通道电压采集(小白全解析:流程图 + 逐行注释代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
简单理解:STM32F103 ADC 单通道电压采集(小白全解析:流程图 + 逐行注释代码)

一、核心流程图(双版本适配)

1. 纯文本流程图(无渲染依赖,直接可读)

┌─────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────────────────┐ │ 系统上电 │───>│ 引入核心头文件 │───>│ 定义全局变量(原始值/电压值) │ └─────────────┘ └──────────────────────────────┘ └──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────────┐ │ GPIO初始化 │ │ 开启GPIOA时钟 │ │ 配置PA0为模拟输入 │ └──────────────────────────────┘ └──────────────────────────────┘ └──────────────────────────────┘ │ ▼ ┌──────────────────────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────────┐ │ ADC初始化 │ │ 开启ADC1时钟 │ │ ADC时钟分频(72MHz→12MHz) │ └──────────────────────────────┘ └──────────────────────────────┘ └──────────────────────────────┘ │ ▼ ┌──────────────────────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────────┐ │ 配置ADC参数(单次/软件触发) │ │ 使能ADC1 │ │ ADC校准(复位→启动→等待) │ └──────────────────────────────┘ └──────────────────────────────┘ └──────────────────────────────┘ │ ▼ ┌──────────────────────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────────┐ │ 进入main函数 │ │ 调用GPIO+ADC初始化函数 │ │ 进入while(1)死循环 │ └──────────────────────────────┘ └──────────────────────────────┘ └──────────────────────────────┘ │ ▼ ┌──────────────────────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────────┐ │ 读取ADC原始值 │ │ 原始值换算电压(值/4095×3.3)│ │ 简易延时 │ └──────────────────────────────┘ └──────────────────────────────┘ └──────────────────────────────┘ │ ▼ ┌──────────────────────────────┐ │ 回到while(1)继续循环采集 │ └──────────────────────────────┘

二、工程分步解析(逐行注释版)

工程背景

实现PA0引脚(ADC1_CH0)的模拟电压采集,输出实际电压值(参考电压3.3V,12位ADC,数值范围0~4095)。

核心目标

让小白看懂「每一步为什么做、每一行代码什么意思」,流程拆分为8个核心步骤,代码行级注释。

步骤1:引入必要头文件(工程基础)

#include "stm32f10x.h" // STM32F103标准库核心头文件,包含所有寄存器/函数定义 #include <stdio.h> // 可选:如果需要用printf打印电压值,需引入

注释解读:

  • stm32f10x.h是STM32标准库的"基础字典",没有它就无法调用库函数/操作寄存器

  • stdio.h是C语言标准输入输出头文件,后续打印电压会用到

步骤2:定义全局变量(方便后续调用)

u16 ADC_RawValue; // 存储ADC原始采集值(12位范围:0~4095) float ADC_Voltage; // 存储换算后的实际电压值(单位:V)

注释解读:

  • u16是STM32标准库定义的"无符号16位整数"(等价于unsigned short),刚好存12位ADC值

  • float是浮点型,用于存储带小数的电压值(如1.85V)

步骤3:GPIO初始化(配置PA0为模拟输入)

// 函数功能:配置ADC通道对应的GPIO口(PA0) void ADC_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; // 定义GPIO初始化结构体(标准库固定写法) // 1. 使能GPIOA的时钟(STM32所有外设必须先开时钟才能用) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 2. 配置PA0为"模拟输入模式" GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; // 指定配置PA0引脚 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入模式(关键:禁止数字功能,避免干扰) GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;// 模拟输入模式下该参数无效,随便填即可 // 3. 将配置写入硬件寄存器(标准库核心操作:把结构体参数"刷"到硬件) GPIO_Init(GPIOA, &GPIO_InitStruct); }

小白必懂:

  1. STM32的GPIO默认是"关闭时钟"的,必须先通过RCC_APB2PeriphClockCmd开启时钟,否则配置无效

  2. 模拟输入模式(GPIO_Mode_AIN)是ADC专用模式,此模式下引脚不响应任何数字信号,只采集模拟电压

步骤4:ADC核心参数初始化

// 函数功能:配置ADC1的工作模式、通道、采样时间等核心参数 void ADC_Config(void) { ADC_InitTypeDef ADC_InitStruct; // 定义ADC初始化结构体(标准库固定写法) // 1. 使能ADC1的时钟(ADC属于APB2总线外设) RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 2. 配置ADC时钟分频(ADC最大时钟不能超过14MHz,STM32F103主频72MHz) RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 72MHz / 6 = 12MHz(符合要求) // 3. 配置ADC核心参数(逐行解释) ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // 独立模式(单ADC工作,最常用) ADC_InitStruct.ADC_ScanConvMode = DISABLE; // 关闭扫描模式(单通道采集无需扫描) ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; // 单次转换模式(采集1次就停止) ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发(手动启动转换) ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐(方便计算,小白必选) ADC_InitStruct.ADC_NbrOfChannel = 1; // 采集通道数:1个(只采PA0) // 4. 将ADC参数写入硬件寄存器 ADC_Init(ADC1, &ADC_InitStruct); // 5. 使能ADC1(相当于给ADC"开机") ADC_Cmd(ADC1, ENABLE); // 6. ADC校准(提升采集精度,小白必做) ADC_ResetCalibration(ADC1); // 复位校准寄存器 while(ADC_GetResetCalibrationStatus(ADC1));// 等待复位完成(循环检测标志位) ADC_StartCalibration(ADC1); // 启动ADC校准 while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成(校准期间不能操作ADC) }

小白必懂:

  1. 扫描模式ADC_ScanConvMode):多通道采集时开启,单通道必须关

  2. 连续转换模式ADC_ContinuousConvMode):开启后ADC会自动重复采集,新手先学单次模式

  3. 校准:ADC出厂后有微小零点误差,校准能消除误差,是提升精度的关键步骤,必须等校准完成再用ADC

步骤5:编写ADC数据读取函数(查询方式)

// 函数功能:读取指定ADC通道的原始值(查询方式:等转换完成再读) // 参数ch:要采集的ADC通道(如ADC_Channel_0对应PA0) u16 ADC_Read_Value(u8 ch) { // 1. 配置要采集的通道和采样时间 // 参数1:ADC1;参数2:通道号;参数3:通道优先级(单通道随便填1);参数4:采样时间 ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_55Cycles5); // 2. 软件触发ADC转换(手动"开始采集") ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 3. 等待转换完成(循环检测"转换完成标志位") while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // EOC=1表示转换完成 // 4. 读取ADC原始值并返回(ADC_GetConversionValue读取数据寄存器) return ADC_GetConversionValue(ADC1); }

小白必懂:

  1. 采样时间ADC_SampleTime_55Cycles5):采样时间越长,精度越高,55.5个时钟周期是新手常用值

  2. 查询方式:最容易理解的读取方式,缺点是CPU要等转换完成(适合低速采集)

  3. ADC_FLAG_EOC​ 是ADC的"转换完成标志位",硬件自动置1,读数据后自动清零

步骤6:编写电压换算函数(把原始值转实际电压)

// 函数功能:将ADC原始值换算为实际电压(参考电压3.3V,12位ADC) void ADC_Calc_Voltage(void) { ADC_RawValue = ADC_Read_Value(ADC_Channel_0); // 读取通道0的原始值 // 电压公式:实际电压 = (原始值 / ADC满量程) × 参考电压 // 12位ADC满量程是4095(2^12 - 1),参考电压3.3V ADC_Voltage = (float)ADC_RawValue / 4095 * 3.3; }

小白必懂:

  1. (float)​ 是强制类型转换,因为ADC_RawValue是整数,不转换会导致除法结果为0(如2048/4095=0,转换后是0.5)

  2. 参考电压:STM32F103默认用VREF+引脚的电压(板载一般接3.3V),如果你的板子接5V,就把3.3改成5

步骤7:主函数(工程入口,串联所有步骤)

int main(void) { // 1. 初始化GPIO(PA0) ADC_GPIO_Init(); // 2. 初始化ADC1 ADC_Config(); // 死循环(单片机主程序必须用循环,否则会跑飞) while(1) { // 3. 计算当前电压 ADC_Calc_Voltage(); // 4. 可选:打印电压值(需提前配置串口,新手可先注释) // printf("ADC原始值:%d,实际电压:%.2fV\r\n", ADC_RawValue, ADC_Voltage); // 5. 延时(避免采集过快,新手可加简易延时) for(int i=0; i<1000000; i++); } }

小白必懂:

  1. main函数是程序入口,所有代码从这里开始执行

  2. 死循环while(1):单片机上电后会一直执行循环内的代码,直到断电

  3. 简易延时:新手暂时不用学定时器,用for循环延时即可,数值越大延时越长


三、工程汇总(完整可运行代码)

/* 文件: adc_voltage_measure.c * 功能: STM32 ADC电压采集完整示例 * 硬件: STM32F103C8T6 (BluePill) * 引脚: PA0 (ADC1_IN0) 接可调电压源 (0-3.3V) * 输出: 采集模拟电压并计算实际电压值 */ #include "stm32f10x.h" #include <stdio.h> // 步骤2:定义全局变量 u16 ADC_RawValue; // 存储ADC原始采集值 float ADC_Voltage; // 存储换算后的实际电压值 // 步骤3:GPIO初始化(PA0模拟输入) void ADC_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; // 1. 使能GPIOA的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 2. 配置PA0为模拟输入模式 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 3. 将配置写入硬件寄存器 GPIO_Init(GPIOA, &GPIO_InitStruct); } // 步骤4:ADC核心参数初始化 void ADC_Config(void) { ADC_InitTypeDef ADC_InitStruct; // 1. 使能ADC1的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 2. 配置ADC时钟分频 RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 3. 配置ADC核心参数 ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; ADC_InitStruct.ADC_ScanConvMode = DISABLE; ADC_InitStruct.ADC_ContinuousConvMode = DISABLE; ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStruct.ADC_NbrOfChannel = 1; // 4. 将ADC参数写入硬件寄存器 ADC_Init(ADC1, &ADC_InitStruct); // 5. 使能ADC1 ADC_Cmd(ADC1, ENABLE); // 6. ADC校准 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); } // 步骤5:读取ADC原始值 u16 ADC_Read_Value(u8 ch) { // 1. 配置要采集的通道和采样时间 ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_55Cycles5); // 2. 软件触发ADC转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 3. 等待转换完成 while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 4. 读取ADC原始值并返回 return ADC_GetConversionValue(ADC1); } // 步骤6:电压换算 void ADC_Calc_Voltage(void) { // 1. 读取通道0的原始值 ADC_RawValue = ADC_Read_Value(ADC_Channel_0); // 2. 换算为实际电压 ADC_Voltage = (float)ADC_RawValue / 4095 * 3.3; } // 步骤7:主函数 int main(void) { // 1. 初始化GPIO ADC_GPIO_Init(); // 2. 初始化ADC ADC_Config(); // 3. 主循环 while(1) { // 4. 计算电压 ADC_Calc_Voltage(); // 5. 延时(控制采集频率) for(int i = 0; i < 1000000; i++); } }

四、小白额外提示

1. 编译运行环境

  • 开发环境:Keil MDK 5.x

  • 芯片选择:新建工程时选择"STM32F103C8T6"(最常用的入门芯片)

  • 库文件:添加STM32F10x标准库

  • 代码位置:将上面代码粘贴到main.c

2. 硬件验证步骤

1. 连接硬件 PA0引脚 ──→ 可调电源正极 (0-3.3V) GND引脚 ──→ 可调电源负极 2. 烧录程序 用ST-Link或USB转串口下载程序 3. 调试查看 - 方法1:用Keil调试模式查看ADC_RawValue和ADC_Voltage变量 - 方法2:配置串口后用printf打印 4. 测试验证 调节电压源,观察采集值是否正确变化 0V → ADC值约0 1.65V → ADC值约2048 3.3V → ADC值约4095

3. 串口打印配置(进阶)

如果想启用printf打印,需要添加以下代码:

#include "stm32f10x_usart.h" // 串口初始化函数 void USART1_Init(void) { // 串口配置代码(略) } // 重定向printf到串口 int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); return ch; }

4. 常见问题排查表

现象

可能原因

解决方法

采集值一直为0

1. PA0没接电压
2. GPIO未配置为模拟输入
3. ADC时钟未开启

1. 检查接线
2. 检查GPIO_Mode_AIN
3. 检查RCC_APB2Periph_ADC1

采集值偏差大

1. 未执行ADC校准
2. 参考电压不是3.3V
3. 采样时间过短

1. 确保执行校准流程
2. 检查板子VREF+电压
3. 增加采样时间

数值跳变严重

1. 电源噪声
2. 输入信号波动
3. 无软件滤波

1. 加滤波电容
2. 稳定输入源
3. 添加均值滤波算法

编译报错

1. 头文件路径错误
2. 库文件未添加

1. 检查include路径
2. 添加标准库到工程

5. 进阶功能扩展

// 1. 多通道采集(扫描模式) ADC_InitStruct.ADC_ScanConvMode = ENABLE; ADC_InitStruct.ADC_NbrOfChannel = 3; // 采集3个通道 // 2. 连续转换模式 ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; // 3. 硬件触发(定时器触发) ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; // 4. 添加软件滤波 uint16_t ADC_Filter_Avg(uint8_t ch, uint8_t times) { uint32_t sum = 0; for(uint8_t i = 0; i < times; i++) { sum += ADC_Read_Value(ch); } return sum / times; }

6. 学习路径建议

新手入门(1-2天) → 中级应用(3-5天) → 高级应用(1-2周) ↓ ↓ ↓ 1. 单通道采集 1. 多通道扫描 1. DMA高速采集 2. 查询方式 2. 中断方式 2. 双重/三重ADC 3. 电压换算 3. 定时器触发 3. 过采样提高精度 4. 基本调试 4. 软件滤波 4. 与DAC闭环控制

记住:ADC是连接模拟世界和数字世界的桥梁。从简单的电压采集开始,逐步掌握多通道、中断、DMA等高级功能,你就能应对各种传感器数据采集需求!

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

AI万能分类器快速入门:10分钟从零到结果

AI万能分类器快速入门&#xff1a;10分钟从零到结果 引言&#xff1a;为什么你需要AI万能分类器&#xff1f; 想象一下&#xff0c;你手头有成千上万条用户反馈需要分类&#xff0c;或者需要快速从海量图片中筛选出特定类型的产品图。传统方法可能需要几天时间手动处理&#…

作者头像 李华
网站建设 2026/4/22 22:21:12

MiDaS技术详解:热力图生成算法与色彩映射原理

MiDaS技术详解&#xff1a;热力图生成算法与色彩映射原理 1. 引言&#xff1a;AI 单目深度估计的视觉革命 1.1 技术背景与核心挑战 在计算机视觉领域&#xff0c;从二维图像中恢复三维空间信息一直是极具挑战性的任务。传统方法依赖双目立体匹配或多视角几何重建&#xff0c…

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

MiDaS实战:工业检测深度估计案例

MiDaS实战&#xff1a;工业检测深度估计案例 1. 引言&#xff1a;AI 单目深度估计在工业场景中的价值 随着智能制造和自动化检测的快速发展&#xff0c;传统2D视觉系统在复杂环境下的局限性日益凸显。尤其是在缺陷检测、物料定位、空间避障等任务中&#xff0c;仅依赖颜色和轮…

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

一键启动Qwen3-VL-4B-Instruct|WEBUI镜像让多模态模型开箱即用

一键启动Qwen3-VL-4B-Instruct&#xff5c;WEBUI镜像让多模态模型开箱即用 在多模态大模型快速演进的今天&#xff0c;如何将强大的视觉语言能力高效落地到实际应用中&#xff0c;已成为开发者和企业关注的核心问题。部署复杂、依赖繁多、环境配置门槛高&#xff0c;常常成为技…

作者头像 李华
网站建设 2026/4/18 13:13:20

MiDaS模型详解:轻量高效的秘密

MiDaS模型详解&#xff1a;轻量高效的秘密 1. 技术背景与问题提出 在计算机视觉领域&#xff0c;深度估计是实现3D空间感知的关键技术之一。传统方法依赖双目立体视觉或多传感器融合&#xff08;如LiDAR&#xff09;&#xff0c;但这些方案成本高、部署复杂&#xff0c;难以在…

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

手把手玩转S7-200与双变频器Modbus通讯

s7-200和两台变频器modbus rtu通信程序 采用西门子224xp&#xff0c;配mcgs触摸屏&#xff0c;变频器一台三菱D700,一台台达vfd-m,通过modbus rtu程序可以控制变频器的正反转&#xff0c;停止&#xff0c;频率的设定&#xff0c;加减速&#xff0c;以及频率电流的读取。 可以看…

作者头像 李华