当前位置: 首页 > news >正文

Stm32F103R6之ADC:从基础配置到高级应用全解析

1. 认识Stm32F103R6的ADC模块

第一次接触Stm32F103R6的ADC功能时,我完全被各种专业术语搞晕了。后来在实际项目中反复使用才发现,它其实就像个"电子秤",能把模拟世界的电压信号转换成数字世界能理解的数值。这款芯片内置的12位ADC(模数转换器)最高采样率能达到1MHz,对于大多数嵌入式应用来说完全够用。

ADC模块最吸引人的是它的灵活性。你可以选择单次转换测量某个特定时刻的电压,也可以开启连续转换模式实时监控信号变化。我做过一个锂电池电压监测项目,就是用连续模式每秒钟采样100次,配合简单的滤波算法就能得到稳定的电压读数。

硬件连接上要注意几个关键点:

  • 输入电压范围必须控制在0~3.3V(VREF+引脚电压)
  • 对于高阻抗信号源建议加电压跟随器
  • 模拟地和数字地要单点连接
  • 电源引脚需要加0.1uF去耦电容
// 最简单的ADC初始化代码 void ADC1_Init(void) { RCC->APB2ENR |= 1<<9; // 开启ADC1时钟 ADC1->CR2 = 0; // 先清零配置寄存器 ADC1->CR2 |= 1<<0; // 开启ADC ADC1->CR2 |= 1<<2; // 持续转换模式 ADC1->CR2 |= 1<<20; // 启用外部触发 }

2. 基础配置五步走

2.1 时钟与引脚配置

刚开始用ADC时最容易忽略时钟配置。Stm32F103R6的ADC时钟最大不能超过14MHz,我一般选择PCLK2的6分频(72MHz/6=12MHz)。GPIO引脚要设置为模拟输入模式,这点很多人会误配成浮空输入。

// 正确的GPIO配置 GPIOA->CRL &= ~(0xF<<(1*4)); // PA1模拟输入 RCC->APB2ENR |= 1<<2; // 开启GPIOA时钟 RCC->APB2ENR |= 1<<9; // 开启ADC1时钟 RCC->CFGR |= (2<<14); // ADC时钟=APB2/6

2.2 校准流程

校准是保证精度的关键步骤。我发现很多初学者会跳过这步,结果采样值总是有几十个LSB的偏差。正确的做法是:

  1. 上电后等待电压稳定
  2. 确保ADC处于关闭状态(ADON=0)
  3. 延时至少1ms
  4. 启动校准(CAL=1)
  5. 等待校准完成(CAL自动清零)
ADC1->CR2 &= ~(1<<0); // 先关闭ADC delay_ms(1); ADC1->CR2 |= 1<<2; // 开启持续转换 ADC1->CR2 |= 1<<3; // 启动校准 while(ADC1->CR2 & (1<<3)); // 等待校准完成

2.3 通道与采样时间

采样时间设置直接影响转换精度。对于不同的信号源阻抗,需要配置不同的采样周期。我总结的经验值是:

  • 低阻抗信号(<10kΩ):7.5个周期
  • 中阻抗信号(10kΩ-50kΩ):41.5个周期
  • 高阻抗信号(>50kΩ):239.5个周期
// 配置通道1采样时间为239.5周期 ADC1->SMPR2 |= (7<<3); // SMP1=111

2.4 触发方式选择

ADC支持软件触发和硬件触发两种方式。在电机控制项目中,我使用定时器触发ADC采样,确保采样时刻与PWM波形严格同步:

// 配置TIM1 TRGO事件触发ADC TIM1->CR2 |= (2<<4); // MMS=010 ADC1->CR2 |= 1<<20; // 外部触发使能 ADC1->CR2 |= (3<<17); // EXTSEL=011(TIM1_TRGO)

2.5 数据对齐与读取

12位转换结果可以左对齐或右对齐存储。我建议使用右对齐,这样数值处理更直观:

uint16_t read_ADC(void) { ADC1->CR2 |= 1<<0; // 开启ADC ADC1->CR2 |= 1<<22; // 启动转换 while(!(ADC1->SR & (1<<1))); // 等待转换完成 return ADC1->DR; // 读取右对齐结果 }

3. 多通道管理实战

3.1 规则通道组配置

规则通道组最多支持16个通道,我常用它来循环采集多个传感器信号。关键是要正确设置SQR寄存器的转换序列:

// 配置通道1、2、3按顺序转换 ADC1->SQR1 = 0; // 1个转换 ADC1->SQR3 = (1<<0) | (2<<5) | (3<<10); // SQ1=1, SQ2=2, SQ3=3

实际项目中遇到过一个问题:修改SQR寄存器后采样值异常。后来发现必须在ADC停止状态下修改配置,转换过程中修改会导致数据错误。

3.2 注入通道高级用法

注入通道就像ADC的中断系统,它有这些特点:

  • 最多4个通道
  • 可以打断规则通道转换
  • 有自己的数据寄存器
  • 支持自动注入功能

在电源监控项目中,我用注入通道实现过压保护:

// 配置注入通道4为紧急电压检测 ADC1->JSQR = (1<<0); // 1个注入转换 ADC1->JSQR |= (4<<15); // JSQ1=通道4 ADC1->HTR = 0x7FF; // 设置高阈值(3V) ADC1->CR1 |= 1<<5; // 开启模拟看门狗

3.3 通道切换优化技巧

快速切换通道时要注意采样保持电容的放电时间。我的经验是:

  1. 高精度测量时切换后延时10us
  2. 常规应用至少延时5个ADC时钟周期
  3. 可以使用间断模式减少切换时间
// 使用间断模式快速切换 ADC1->SQR1 = (2<<20); // 每3个转换后间断 ADC1->SQR3 = (1<<0)|(2<<5); // 先通道1后通道2 ADC1->CR1 |= (1<<12); // 开启间断模式

4. 中断与DMA高效处理

4.1 中断回调实现

ADC中断有3种常用场景:

  • 转换完成中断
  • 模拟看门狗中断
  • 注入转换完成中断

我建议使用状态机模式处理中断:

void ADC1_IRQHandler(void) { if(ADC1->SR & (1<<1)) { // 规则通道完成 adc_values[adc_index++] = ADC1->DR; if(adc_index >= 8) adc_index = 0; } if(ADC1->SR & (1<<2)) { // 注入通道完成 emergency_check(ADC1->JDR1); } ADC1->SR = 0; // 清除所有标志位 }

4.2 DMA传输配置

DMA是高效处理多通道采样的利器。配置时要注意:

  • 内存地址递增
  • 数据宽度匹配
  • 循环模式使能
  • DMA中断合理使用
// 配置DMA1通道1用于ADC1 DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR; DMA1_Channel1->CMAR = (uint32_t)adc_buffer; DMA1_Channel1->CNDTR = 8; // 传输8个数据 DMA1_Channel1->CCR = (1<<5)|(1<<7)|(1<<10); // 循环模式,内存递增,16位 ADC1->CR2 |= 1<<8; // 启用DMA

4.3 双缓冲技巧

在音频采集项目中,我使用双缓冲避免数据竞争:

uint16_t adc_buf1[256], adc_buf2[256]; volatile uint8_t current_buf = 0; void DMA1_Channel1_IRQHandler(void) { if(DMA1->ISR & (1<<1)) { // 传输完成中断 if(current_buf == 0) { process_data(adc_buf1); DMA1_Channel1->CMAR = (uint32_t)adc_buf2; } else { process_data(adc_buf2); DMA1_Channel1->CMAR = (uint32_t)adc_buf1; } current_buf ^= 1; DMA1->IFCR |= (1<<1); // 清除标志 } }

5. 高级应用场景

5.1 温度传感器校准

内部温度传感器的精度虽然不高,但用来监测芯片温度变化很实用。实测发现不同芯片的偏差可能达到±10°C,所以必须校准:

float read_temperature(void) { ADC1->SQR3 = 16; // 温度传感器通道 ADC1->CR2 |= 1<<22; // 启动转换 while(!(ADC1->SR & (1<<1))); uint16_t temp = ADC1->DR; return ((1.43 - temp*3.3/4096) / 0.0043) + 25; }

5.2 电池电压监测

用ADC测量电池电压时,要注意分压电阻的精度和温漂。我通常使用0.1%精度的电阻,并软件补偿温度影响:

#define VDDA 3.3f #define R1 100.0f #define R2 100.0f float read_battery(void) { ADC1->SQR3 = 1; // 通道1 ADC1->CR2 |= 1<<22; // 启动转换 while(!(ADC1->SR & (1<<1))); float adc = ADC1->DR; return (adc / 4096 * VDDA) * (R1 + R2) / R2; }

5.3 双ADC同步采样

在电机三相电流采样等需要同步测量的场景,双ADC模式非常有用:

// 配置ADC1为主,ADC2为从 ADC1->CR1 = (1<<13); // 同步注入模式 ADC2->CR1 = (1<<13); // 同步规则模式 ADC1->CR2 |= 1<<20; // 外部触发使能 ADC2->CR2 |= 1<<20; // 外部触发使能 TIM1->CR2 |= (2<<4); // 使用TIM1触发

6. 常见问题排查

6.1 采样值不稳定

遇到ADC值跳动时,可以按以下步骤排查:

  1. 检查电源稳定性(纹波<50mV)
  2. 确认参考电压干净(加10uF+0.1uF电容)
  3. 优化采样时间(噪声大时延长采样时间)
  4. 检查信号源阻抗(高阻抗源要加缓冲)
  5. 软件滤波(移动平均或中值滤波)

6.2 转换时间异常

转换时间计算公式为: Tconv = 采样时间 + 12.5个周期

如果发现转换比预期慢,检查:

  • ADC时钟分频设置
  • 是否开启了间断模式
  • 触发信号频率是否过高

6.3 DMA传输不触发

DMA不工作的常见原因:

  • 未使能DMA控制器时钟
  • 内存/外设地址未对齐
  • 数据量寄存器CNDTR为0
  • ADC的DMA使能位未设置
  • DMA通道优先级冲突

7. 性能优化技巧

7.1 降低功耗方案

在电池供电设备中,我采用这些方法降低ADC功耗:

  • 仅在需要时开启ADC(CR2的ADON位)
  • 使用单次转换模式
  • 降低采样频率
  • 关闭未用通道的IO口时钟
// 低功耗采样模式 void low_power_sample(void) { ADC1->CR2 |= 1<<0; // 开启ADC delay_us(10); // 等待稳定 ADC1->CR2 |= 1<<22; // 启动转换 while(!(ADC1->SR & (1<<1))); uint16_t val = ADC1->DR; ADC1->CR2 &= ~(1<<0); // 关闭ADC }

7.2 提高采样精度

要获得最佳精度,需要注意:

  • 避免IO口同时翻转(产生噪声)
  • 保持VDDA和VREF+电压稳定
  • 适当oversampling(过采样)
  • 定期重新校准
  • 远离高频信号线

7.3 软件滤波算法

常用的软件滤波方法有:

  1. 移动平均滤波(适合缓慢变化信号)
  2. 中值滤波(适合去除突发干扰)
  3. 卡尔曼滤波(动态系统最佳)
  4. IIR低通滤波(计算量小)
// 简单的移动平均滤波 #define FILTER_SIZE 8 uint16_t adc_filter(uint16_t new_val) { static uint16_t buf[FILTER_SIZE]; static uint8_t index = 0; static uint32_t sum = 0; sum -= buf[index]; buf[index] = new_val; sum += new_val; index = (index + 1) % FILTER_SIZE; return sum / FILTER_SIZE; }

在实际项目中,ADC的稳定性和精度往往决定了整个系统的性能。经过多个项目的验证,我发现Stm32F103R6的ADC模块虽然不如专业ADC芯片,但通过合理的配置和软件处理,完全能满足大多数工业应用的需求。特别是在使用DMA+双缓冲的方案后,即使在复杂系统中也能保证稳定的采样性能。

http://www.jsqmd.com/news/636379/

相关文章:

  • 如何快速掌握Akagi:雀魂AI辅助工具的完整实战教程
  • 告别云端依赖:3分钟上手Buzz,你的本地语音转文字专家
  • DeEAR语音情感识别惊艳案例:识别抑郁症患者语音中‘韵律平坦化’与‘自然度衰减’双指标
  • .NET对象转JSON,到底有几种方式?啃
  • Qwen3-ASR-1.7B多场景落地:直播实时转录、法庭笔录辅助生成案例
  • SARADC前仿真实战指南:从静态到动态的完整流程解析
  • 1.0】Matlab Simulink动态电压恢复器(DVR)模型:高质量仿真治理电能质量问题...
  • FinAgent 本周进度记录|本机 LLM 联调、任务中心/历史、按用户隔离与单股日期约束
  • SQL索引策略深度解析:从理论到实战的优化指南
  • Spring IOC 源码学习 事务相关的 BeanDefinition 解析过程 (XML)感
  • 在集群作业中激活 conda 环境
  • 告别LabelImg!用Roboflow一站式搞定图片标注、增强与数据集管理
  • 银河麒麟 aarch64 大数据 Excel 导入:常见问题与优化思路分享
  • 多商户平台小程序的核心功能有哪些?
  • BiRefNet高分辨率二分图像分割:5分钟快速上手指南
  • 大模型下半场:2026年的五个关键预测
  • 深度学习理论框架:六周掌握核心技能
  • 4.14
  • Internet Archive Downloader:3步突破数字图书馆借阅限制的终极指南
  • 遥感图像智能分析:基于PyTorch的变化检测框架深度解析
  • 内存测试指标和工具
  • 一个简洁易用的 Delphi JSON 封装库,基于 System.JSON`单元封装,提供更直观的 API浅
  • 4.15
  • Z-Image-Turbo-辉夜巫女快速上手:8步生成动漫美图,新手5分钟搞定
  • iPad+向日葵远程控制:通过降级安装旧版向日葵11.2.2解决键盘快捷键失效问题(附罗技K380适配方案)
  • JasperReport 6.16 报表开发实战:Table组件与Dataset数据源的高效整合
  • B站资源下载终极指南:跨平台BiliTools使用全攻略
  • Intv_AI_MK11算法优化实战:改进模型推理效率的常用策略
  • 别再手动切文档了!用Dify 2.0的‘知识管道’流水线,5分钟搞定复杂PDF的RAG知识库搭建
  • 低代码平台新引擎:Dify集成Phi-4-mini-reasoning构建专属AI工作流