ADC 与 DAC 基础
Author:余生
我们将从最底层的物理原理出发,深入剖析 ADC(模数转换器)和 DAC(数模转换器)的工作机制,详细讲解它们是如何实现模拟信号与数字信号之间的相互转换的。我们将探讨不同类型的转换架构(如逐次逼近型、Σ-Δ 型、R-2R 梯形网络等),分析其精度、速度、噪声等关键参数,并结合 STM32 等微控制器的实际外设,手写底层寄存器操作与库函数实现。最后,我们将对 ADC 与 DAC 进行全面对比,涵盖工作原理、应用场景、性能指标、电路实现等多个维度,力求内容详实、逻辑清晰、深入浅出。
一:从物理世界到数字世界 —— 为什么要 ADC/DAC?
1.1 模拟信号 vs 数字信号
- 模拟信号(Analog):连续变化的物理量,如电压、电流、温度、声音、光强等。其值在时间和幅度上都是连续的。
- 数字信号(Digital):离散的二进制值(0 和 1),由微控制器、CPU 等数字系统处理。
现实世界是模拟的,而计算机是数字的。 ADC 和 DAC 是连接这两个世界的 “桥梁”。
二:ADC —— 模拟到数字的转换
2.1 ADC 的基本概念
ADC 的作用是:将一个连续的模拟电压值转换为一个离散的数字值(二进制数)。
关键参数
参数 | 含义 |
---|---|
分辨率(Resolution) | 输出数字值的位数,如 8-bit、10-bit、12-bit、16-bit |
参考电压(Vref) | 转换的基准电压,决定输入范围(如 0~3.3V) |
采样率(Sampling Rate) | 每秒能完成多少次转换(Hz) |
量化误差(Quantization Error) | 由于离散化引入的误差,最大为 ±0.5 LSB |
信噪比(SNR)、有效位数(ENOB) | 衡量实际精度的指标 |
2.2 ADC 的工作流程
ADC 转换通常分为两个阶段:
- 采样(Sampling):
- 使用 采样保持电路(Sample-and-Hold, S/H) 在极短时间内 “捕获” 输入电压。
- 保持该电压稳定,供后续转换使用。
- 量化与编码(Quantization & Encoding):
- 将采样到的电压值映射到最近的数字码。
- 例如:12-bit ADC,Vref = 3.3V,则每 LSB = 3.3V / 4096 ≈ 0.806 mV。
2.3 ADC 的主要类型与原理
类型 1:逐次逼近型 ADC(SAR ADC)
这是 STM32 内部 ADC 最常用的类型。
工作原理
- 使用一个 逐次逼近寄存器(SAR) 和一个 内部 DAC。
- 从最高位(MSB)开始,逐位试探:
- 将当前猜测的数字码送入内部 DAC,生成模拟电压。
- 与输入电压比较。
- 如果 DAC 输出 < 输入,则保留该位为 1;否则为 0。
- 重复 N 次(N = 分辨率),完成 N 位转换。
示例(4-bit SAR ADC)
步骤 | SAR 值 | DAC 输出 | 比较结果 | 保留位 |
---|---|---|---|---|
1 | 1000 | Vref/2 | < 输入 | 1 |
2 | 1100 | 3Vref/4 | > 输入 | 0 |
3 | 1010 | 5Vref/8 | < 输入 | 1 |
4 | 1011 | 11Vref/16 | ≈ 输入 | 1 |
最终结果: 1011
优点:
- 中高速(100 kSPS ~ 10 MSPS)
- 功耗低
- 集成度高
缺点:
- 分辨率通常 ≤ 16-bit
- 需要高精度比较器和 DAC
类型 2:Σ-Δ(Sigma-Delta)ADC
用于高精度、低速应用(如称重、温度测量)。
工作原理
- 使用 调制器(Modulator) 将输入信号转换为高速、低分辨率(1-bit)的脉冲流。
- 脉冲密度与输入电压成正比。
- 通过 数字滤波器(Decimation Filter) 对脉冲流进行平均和降采样,输出高分辨率数字值。
核心思想:
- 过采样(Oversampling):以远高于奈奎斯特频率的速率采样。
- 噪声整形(Noise Shaping):将量化噪声推向高频,便于滤除。
优点:
- 分辨率高(16~24 bit)
- 线性度好
- 抗干扰强
缺点:
- 速度慢(几 Hz ~ 几 kHz)
- 延迟大
类型 3:Flash ADC(并行 ADC)
速度最快,但功耗和面积大。
原理:
- 使用 2N−12N−1 个比较器同时比较输入电压与不同参考电压。
- 编码器直接输出 N 位结果。
优点:
- 极高速(GHz 级)
- 延迟极小
缺点:
- 8-bit 需 255 个比较器,面积和功耗巨大
- 分辨率低(通常 ≤ 8-bit)
2.4 STM32 ADC 外设与底层实现
主要寄存器(以 STM32F103 ADC1 为例)
寄存器 | 作用 |
---|---|
ADC_SR | 状态寄存器(EOC, JEOC, AWD 等) |
ADC_CR1/CR2 | 控制寄存器(使能、扫描、注入通道等) |
ADC_SMPR1/SMPR2 | 采样时间寄存器 |
ADC_SQR1/SQR2/SQR3 | 规则通道序列寄存器 |
ADC_JSQR | 注入通道序列寄存器 |
ADC_DR | 数据寄存器(16 位) |
配置 ADC(轮询方式)
cpp
void ADC1_Init(void) {
// 1. 使能时钟
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_IOPAEN;
// 2. 配置 GPIO(PA0 = ADC1_IN0)
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 配置 ADC_CR2
ADC1->CR2 = 0;
ADC1->CR2 |= ADC_CR2_ADON; // 开启 ADC 电源
for(volatile int i = 0; i < 1000; i++); // 等待稳定
// 4. 设置采样时间(通道 0,采样周期 = 239.5 cycles)
ADC1->SMPR2 |= ADC_SMPR2_SMP0_2 | ADC_SMPR2_SMP0_1 | ADC_SMPR2_SMP0_0;
// 5. 单通道,规则序列长度 = 1
ADC1->SQR1 = 0;
ADC1->SQR3 = 0; // 通道 0 在 SQ1
}
uint16_t ADC_ReadChannel(uint8_t channel) {
// 设置通道
ADC1->SQR3 = channel;
// 开始转换
ADC1->CR2 |= ADC_CR2_SWSTART;
// 等待转换完成(EOC 标志)
while (!(ADC1->SR & ADC_SR_EOC));
// 读取数据
return (uint16_t)ADC1->DR;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
使用 DMA 实现连续采样
cpp
uint16_t adc_buffer[100];
void ADC1_DMA_Init(void) {
// 1. 使能 DMA 时钟
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
// 2. 配置 DMA 通道 1(ADC1 → 内存)
DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;
DMA1_Channel1->CMAR = (uint32_t)adc_buffer;
DMA1_Channel1->CNDTR = 100;
DMA1_Channel1->CCR =
DMA_CCR_EN | // 使能
DMA_CCR_CIRC | // 循环模式
DMA_CCR_MINC | // 内存地址自增
DMA_CCR_PSIZE_0 | // 外设 8-bit
DMA_CCR_MSIZE_0 | // 内存 8-bit
DMA_CCR_DIR; // 外设 → 内存
// 3. 使能 ADC 的 DMA 请求
ADC1->CR2 |= ADC_CR2_DMA;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
三:DAC —— 数字到模拟的转换
3.1 DAC 的基本概念
DAC 的作用是:将一个数字值(二进制数)转换为对应的模拟电压或电流。
关键参数
参数 | 含义 |
---|---|
分辨率 | 输入数字的位数(8/12-bit) |
参考电压(Vref) | 输出范围的基准(0 ~ Vref) |
建立时间(Settling Time) | 输出稳定到最终值所需时间 |
积分非线性(INL)、微分非线性(DNL) | 衡量线性度的指标 |
单调性(Monotonicity) | 数字输入增加时,模拟输出不减小 |
3.2 DAC 的主要类型与原理
类型 1:R-2R 梯形网络 DAC
原理:
- 使用精密电阻构成 R-2R 梯形网络。
- 每个位控制一个开关,连接到 Vref 或 GND。
- 网络输出一个与数字输入成正比的电压。
优点:
- 电阻匹配要求相对较低
- 易于集成
缺点:
- 需要高精度电阻
- 高位变化时可能产生毛刺
类型 2:权电阻网络 DAC
原理:
- 每个位使用一个电阻,阻值为 2n−1R2n−1R。
- 电流相加,通过运放转换为电压。
缺点:
- 电阻值范围大(如 1R, 2R, 4R, ..., 128R),难以高精度匹配。
- 仅适用于低分辨率。
类型 3:Σ-Δ DAC
与 Σ-Δ ADC 类似,使用调制器将高分辨率数字信号转换为低分辨率高速脉冲流,再通过模拟滤波器恢复为平滑模拟信号。
应用:
- 音频 DAC(如 CD 播放器)
- 高保真音频输出
3.3 STM32 DAC 外设与底层实现
STM32F1/F4 等系列内置 12-bit DAC。
主要寄存器(DAC1)
寄存器 | 作用 |
---|---|
DAC_CR | 控制寄存器(使能、触发源) |
DAC_SWTRIGR | 软件触发寄存器 |
DAC_DHRx | 数据保持寄存器(左 / 右对齐) |
DAC_DORx | 输出寄存器(只读) |
配置 DAC(软件触发)
cpp
void DAC1_Init(void) {
// 1. 使能时钟
RCC->APB1ENR |= RCC_APB1ENR_DACEN;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 2. 配置 GPIO(PA4 = DAC1_OUT)
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AIN; // 模拟输出
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 3. 配置 DAC_CR
DAC->CR = 0;
DAC->CR |=
DAC_CR_EN1 | // 使能 DAC 通道 1
DAC_CR_TEN1 | // 使能软件触发
(0 << 8); // 不使用波形生成功能
}
void DAC_SetVoltage(uint16_t digital_value) {
// 写入数据寄存器(12-bit 右对齐)
DAC->DHR12R1 = digital_value & 0x0FFF;
// 软件触发转换
DAC->SWTRIGR |= DAC_SWTRIGR_SWTRIG1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
使用定时器触发 DAC(生成波形)
cpp
// 配置 TIM6 作为 DAC 触发源
TIM6->PSC = 72 - 1; // 72MHz / 72 = 1MHz
TIM6->ARR = 100 - 1; // 1MHz / 100 = 10kHz 触发
TIM6->DIER = TIM_DIER_UDE; // 更新事件触发 DMA
TIM6->CR1 = TIM_CR1_CEN; // 启动定时器
// 配置 DAC_CR:触发源 = TIM6 TRGO
DAC->CR |= (5 << 17); // TSEL1 = 101 → TIM6 TRGO
1
2
3
4
5
6
7
2
3
4
5
6
7