news 2026/3/2 6:37:00

简单理解: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/3/1 21:08:46

大数据专业就业指南

大专大数据专业是否需要学习数据分析数据分析作为大数据领域的核心技能之一&#xff0c;是大专大数据专业学生必须掌握的重要内容。数据分析能力能够帮助学生更好地理解和处理海量数据&#xff0c;为后续的就业和职业发展打下坚实基础。数据分析在大数据专业中的重要性数据分析…

作者头像 李华
网站建设 2026/2/25 15:07:37

微服务分布式SpringBoot+Vue+Springcloud的动漫服装租赁妆造服务平台的设计与实践 _

目录项目背景与目标技术架构设计核心功能模块系统创新点实践成果开发技术源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;项目背景与目标 动漫服装租赁妆造服务平台旨在解决二次元文化爱好者服装道具租赁、化妆造型需求分散的问题。通过…

作者头像 李华
网站建设 2026/2/19 22:51:45

基于SpringBoot的中小型制造企业质量管理系统毕设源码

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。 一、研究目的 本研究旨在深入探讨基于SpringBoot框架的中小型制造企业质量管理系统&#xff08;QMS&#xff09;的设计与实现&#xff0c;以期为我国中小型制造企业的质量…

作者头像 李华
网站建设 2026/2/19 0:33:14

基于SpringBoot的助农产品采购平台毕业设计源码

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。 一、研究目的 本研究旨在构建一个基于SpringBoot框架的助农产品采购平台&#xff0c;以实现以下研究目的&#xff1a; 首先&#xff0c;通过研究构建一个功能完善、性能稳…

作者头像 李华
网站建设 2026/3/1 3:25:01

基于SpringBoot的智慧草莓基地管理系统毕业设计

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在开发一套基于SpringBoot框架的智慧草莓基地管理系统&#xff0c;以实现草莓种植过程的智能化、自动化和高效化。具体研究目的如下&#xff1a; 首先&a…

作者头像 李华
网站建设 2026/2/23 12:29:39

MiDaS快速入门:5分钟实现照片3D化

MiDaS快速入门&#xff1a;5分钟实现照片3D化 1. 引言&#xff1a;AI 单目深度估计 - MiDaS 在计算机视觉领域&#xff0c;如何让机器“理解”二维图像背后的三维空间结构&#xff0c;一直是核心挑战之一。传统方法依赖双目视觉或多传感器融合&#xff0c;但单目深度估计&…

作者头像 李华