STM32F103 模数转换功能架构与底层驱动设计
概述:模数转换基础
在嵌入式控制领域,ADC(Analog-to-Digital Converter,模拟-数字转换器)是连接物理世界与数字处理核心的关键桥梁。它负责将输入引脚上的连续模拟电压信号,量化为 MCU 内部存储器可处理的离散数字量。
- 精度特性:采用 12 位逐次逼近(SAR)架构,单次转换耗时约为 1us。
- 输入范围:支持 0 至 3.3V 的电压输入,对应的输出编码范围为 0 到 4095。
- 通道资源:内部集成多达 18 个通道,涵盖 16 个外部管脚及 2 个内部基准信号。
- 分组机制:设有规则序列组(Regular Group)和注入序列组(Injected Group),适用于不同优先级的数据采集。
- 监控功能:内置模拟看门狗,可设定电压阈值,用于实时监测温度或光照等环境参数的异常波动。
针对常见的 STM32F103C8T6 核心板,其硬件配置包含两个独立的 ADC 实例(ADC1 和 ADC2),并提供 10 个可用的外部输入通道。
SAR 工作原理
逐次逼近型 ADC 的核心逻辑依赖于一个闭环反馈系统。如图所示:
数据转换过程通过多路复用开关选定特定输入通道(IN0~IN7),送入比较器。比较器的一端接收待测电压,另一端则连接内部数模转换器(DAC)。系统通过调整 DAC 的数字输入值,产生相应的参考电压与待测电压进行比对。若 DAC 输出电压高于待测值,则下调 DAC 数据;反之则上调。此迭代过程持续直至两者近似相等,此时 DAC 内部的数字代码即代表最终的转换结果。
硬件架构与信号流
理解 STM32 的 ADC 框图对于配置寄存器至关重要:
图中展示了数据流向与状态标志。其中 EOC(End Of Conversion)表示规则组转换完成,JEOC 代表注入组完成。这两个状态位均可在状态寄存器中查询,常配合 DMA 使用以实现对大数据流的无阻塞读取,避免缓冲区覆盖问题。
基本结构解析
通道选择机制
工作模式详解
根据扫描功能与触发方式的不同组合,ADC 拥有多种运行形态:
1. 单次模式(非扫描)
设定规则序列仅包含一个通道(如 CH2)。触发后执行一次采样,结果存入数据寄存器并置位 EOC,随后停止等待下次触发。
2. 连续模式(非扫描)
与单次模式的区别在于,一旦转换结束,硬件自动立即启动下一次对该通道的转换,形成不间断的数据流。
3. 扫描模式(单次)
启用序列表,依次对多个通道(例如 7 个)进行遍历采集。为防止寄存器数据丢失,通常需配合 DMA 传输。所有通道采完后生成 EOC 中断。
4. 扫描模式(连续)
在完成一轮多通道扫描后,自动从头开始下一轮扫描,适合需要周期性监测多个信号的场景。
关键参数配置
触发源控制
转换可由软件直接启动,也可由定时器或其他外设的事件信号触发,具体取决于应用需求。
数据对齐方式
ADC 内部为 12 位精度,但数据寄存器为 16 位,因此存在两种填充策略:
- 右对齐(推荐):
。有效数据位于低 12 位,高位补零。 - 左对齐:
。有效数据左移,低位补零。这种方式在处理高 8 位快速判断时有用,但会损失部分精度,一般情况下不建议使用。
时序与校准
完整的转换周期包括采样、保持、量化及编码四个阶段。总转换时间计算公式如下:
TCONV = 采样时间 + 12.5 个 ADC 时钟周期
假设 ADC 时钟频率为 14MHz,采样时间设定为 1.5 个周期,则单次转换耗时约 1μs。
此外,为消除内部电容阵列随时间和温度产生的误差,建议在系统上电初始化时强制执行自校准流程。校准前需确保 ADC 处于断电状态至少两个时钟周期。
外围硬件电路
外部模拟信号接入时需注意阻抗匹配与滤波处理,典型连接电路如下图所示:
驱动程序实现
示例一:单通道配置(优化版)
以下代码重构了初始化流程,强化了注释逻辑,并修正了部分配置顺序以提升稳定性。
#include "Stm32_AdcDriver.h"
// 定义 ADC 实例指针
static ADC_TypeDef* g_adc_instance = ADC1;
/**
* @brief 初始化 ADC 驱动配置
* @param None
* @retval None
*/
void Sys_ADC_Driver_Initialize(void)
{
RCC_InitTypeDef rcc_conf;
GPIO_InitTypeDef gpio_conf;
ADC_InitTypeDef adc_settings;
// 1. 开启总线时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
// 2. 设置 ADC 分频系数 (PCLK2 / 6 <= 14MHz)
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 3. 配置输入端口为模拟模式
gpio_conf.GPIO_Mode = GPIO_Mode_AIN;
gpio_conf.GPIO_Pin = GPIO_Pin_0;
gpio_conf.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_conf);
// 4. 构建 ADC 主配置
adc_settings.ADC_Mode = ADC_Mode_Independent;
adc_settings.ADC_DataAlign = ADC_DataAlign_Right;
adc_settings.ADC_ContinuousConvMode = ENABLE; // 启用连续采样
adc_settings.ADC_ScanConvMode = DISABLE; // 单通道无需扫描
adc_settings.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
adc_settings.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &adc_settings);
// 5. 启动 ADC 外设
ADC_Cmd(g_adc_instance, ENABLE);
// 6. 执行复位校准 (必须步骤)
ADC_ResetCalibration(g_adc_instance);
while(ADC_GetResetCalibrationStatus(g_adc_instance));
ADC_StartCalibration(g_adc_instance);
while(ADC_GetCalibrationStatus(g_adc_instance));
// 7. 开启软件触发转换
ADC_SoftwareStartConvCmd(g_adc_instance, ENABLE);
}
/**
* @brief 获取当前通道模拟值
* @param None
* @retval unsigned short int
*/
uint16_t Acquire_Analog_Sample(void)
{
// 由于开启了连续模式,可直接读取最新值
return ADC_GetConversionValue(g_adc_instance);
}
示例二:多通道动态切换方案
针对不同通道的轮询读取,本方案展示了如何在运行时动态选择通道并进行同步采集。
#include "Stm32_AdcMultiChan.h"
/**
* @brief 多通道 ADC 初始化框架
* @param None
* @retval None
*/
void Adc_Multi_Channel_Setup(void)
{
GPIO_InitTypeDef pin_cfg;
ADC_InitTypeDef reg_config;
uint8_t channel_pins = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
// 时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 端口配置
pin_cfg.GPIO_Mode = GPIO_Mode_AIN;
pin_cfg.GPIO_Speed = GPIO_Speed_50MHz;
pin_cfg.GPIO_Pin = channel_pins;
GPIO_Init(GPIOA, &pin_cfg);
// ADC 参数设定
reg_config.ADC_Mode = ADC_Mode_Independent;
reg_config.ADC_DataAlign = ADC_DataAlign_Right;
reg_config.ADC_ContinuousConvMode = DISABLE; // 单次触发,便于管理
reg_config.ADC_ScanConvMode = DISABLE;
reg_config.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
reg_config.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, ®_config);
ADC_Cmd(ADC1, ENABLE);
// 校准流程
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
/**
* @brief 指定通道采样
* @param ch_idx : 通道索引 (0-17)
* @retval uint16_t : 转换结果
*/
uint16_t Read_Specific_Channel(uint8_t ch_idx)
{
// 动态配置规则序列通道
ADC_RegularChannelConfig(ADC1, ch_idx, 1, ADC_SampleTime_55Cycles5);
// 手动触发转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 轮询等待转换完成标志
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
// 清除标志位并返回数据
ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
return ADC_GetConversionValue(ADC1);
}