ADC 与 DAC¶
ADC(模数转换)让 MCU 能"感知"现实世界的模拟信号(光照、温度、电压),DAC(数模转换)让 MCU 能"输出"模拟电压。这两个外设是连接数字世界和模拟世界的桥梁。
一、ADC(模数转换器)¶
什么是 ADC?¶
ADC(Analog-to-Digital Converter)将连续的模拟电压转换为离散的数字值。
其中 \(n\) 是分辨率(位数),\(V_{\text{ref}}\) 是参考电压。
直观理解
STM32F103 的 ADC 是 12 位,参考电压 = 3.3V:
- 输入 0V → 数字值 = 0
- 输入 1.65V → 数字值 = \(\frac{1.65}{3.3} \times 4095 \approx 2048\)
- 输入 3.3V → 数字值 = 4095
分辨率 = \(\frac{3.3V}{4096} \approx 0.8mV\),即最小可分辨 0.8mV 的电压变化。
STM32F103 ADC 特性¶
| 特性 | 参数 |
|---|---|
| ADC 数量 | 3 个(ADC1, ADC2, ADC3) |
| 分辨率 | 12 位(0 ~ 4095) |
| 输入通道 | 18 个(16 个外部 + 温度传感器 + 内部参考电压) |
| 转换速度 | 最快 1μs(ADC 时钟 = 14MHz 时) |
| 输入电压范围 | 0V ~ 3.3V(不可超过 VDDA) |
| ADC 时钟 | 来自 APB2,最高 14MHz |
ADC 通道与引脚对应¶
| ADC 通道 | 引脚 | ADC 通道 | 引脚 |
|---|---|---|---|
| CH0 | PA0 | CH8 | PB0 |
| CH1 | PA1 | CH9 | PB1 |
| CH2 | PA2 | CH10 | PC0 |
| CH3 | PA3 | CH11 | PC1 |
| CH4 | PA4 | CH12 | PC2 |
| CH5 | PA5 | CH13 | PC3 |
| CH6 | PA6 | CH16 | 温度传感器 |
| CH7 | PA7 | CH17 | 内部参考电压 |
转换模式¶
| 模式 | 说明 | 使用场景 |
|---|---|---|
| 单次转换 | 转换一次后停止,需要重新触发 | 偶尔读一次电压 |
| 连续转换 | 一次启动,自动反复转换 | 实时监控模拟量 |
| 扫描模式 | 依次转换多个通道 | 同时采集多路传感器 |
| 间断模式 | 每次触发只转换 N 个通道 | 分批采集 |
数据对齐方式¶
ADC 结果是 12 位的,但数据寄存器是 16 位的,需要选择对齐方式:
推荐
一般使用右对齐,读出来直接就是数字值,不需要额外处理。
ADC 采样时间¶
每个通道可以独立配置采样时间。采样时间越长,精度越高,转换速度越慢:
| 采样周期选项 | 采样时间(ADC_CLK=14MHz时) |
|---|---|
| 1.5 周期 | 1μs(最快) |
| 7.5 周期 | 1.43μs |
| 28.5 周期 | 2.93μs |
| 239.5 周期 | 18μs(最精确) |
采样时间选择原则
- 信号变化快(音频、电流采集):用短采样时间
- 信号变化慢(温度、电池电压):用长采样时间,精度更高
- 高阻抗信号源:必须增加采样时间,否则采集不准
二、ADC 编程(CubeMX + HAL)¶
单通道单次转换¶
最简单的 ADC 使用方式——读取 PA0 上的电压:
CubeMX 配置步骤:
- Analog → ADC1
- IN0 勾选使能(PA0)
- Parameter Settings:
- Scan Conversion Mode: Disabled
- Continuous Conversion Mode: Disabled(单次转换)
- Data Alignment: Right
- Number Of Conversion: 1
- Rank 1 → Channel 0,Sampling Time: 55.5 Cycles
- PA0 自动配置为模拟模式
- 生成代码
/* main.c */
/* USER CODE BEGIN WHILE */
while (1)
{
// 启动 ADC 转换
HAL_ADC_Start(&hadc1);
// 等待转换完成
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
// 读取 ADC 值
uint16_t adc_value = HAL_ADC_GetValue(&hadc1);
// 转换为电压(mV)
float voltage = (float)adc_value / 4095.0f * 3300.0f;
printf("ADC = %d, Voltage = %.1f mV\r\n", adc_value, voltage);
HAL_Delay(500);
/* USER CODE END WHILE */
}
ADC 校准
HAL 库中可以在启动前调用校准函数,提高精度:
多通道扫描 + DMA¶
当需要同时采集多路 ADC 时,使用扫描模式 + DMA 最高效:
CubeMX 配置步骤:
- Analog → ADC1 → 勾选 IN0, IN1, IN2
- Parameter Settings:
- Scan Conversion Mode: Enabled
- Continuous Conversion Mode: Enabled
- Number Of Conversion: 3
- Rank 1/2/3 分别配置 Channel 0/1/2
- DMA Settings 标签页 → Add → ADC1
- Direction: Peripheral To Memory
- Mode: Circular(循环模式,自动更新)
- Data Width: Half Word(16 位)
- 生成代码
/* main.c */
uint16_t adc_values[3]; // DMA 自动写入
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1); // 校准
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_values, 3); // 启动 ADC + DMA
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
// adc_values[0] = CH0(PA0) 的值
// adc_values[1] = CH1(PA1) 的值
// adc_values[2] = CH2(PA2) 的值
// 它们会被 DMA 自动持续更新,直接读取即可
float v0 = adc_values[0] / 4095.0f * 3.3f;
float v1 = adc_values[1] / 4095.0f * 3.3f;
float v2 = adc_values[2] / 4095.0f * 3.3f;
printf("V0=%.2f V1=%.2f V2=%.2f\r\n", v0, v1, v2);
HAL_Delay(500);
/* USER CODE END WHILE */
}
DMA 循环模式的优势
启动一次 HAL_ADC_Start_DMA() 后,ADC 会持续转换,DMA 持续搬运数据到数组中。你随时读取数组就能拿到最新的 ADC 值,CPU 几乎不需要参与。
三、DAC(数模转换器)¶
什么是 DAC?¶
DAC(Digital-to-Analog Converter)与 ADC 相反,将数字值转换为模拟电压输出。
STM32F103 DAC 特性¶
| 特性 | 参数 |
|---|---|
| DAC 数量 | 2 个通道(DAC1 = PA4, DAC2 = PA5) |
| 分辨率 | 12 位(0~4095)或 8 位(0~255) |
| 输出电压范围 | 0V ~ VREF+(通常 3.3V) |
| 建立时间 | 约 3μs |
注意
不是所有 STM32F103 型号都有 DAC。只有 大容量型号(如 F103RC、F103ZE 等)才有 DAC 外设。STM32F103C8T6(蓝色小板)没有 DAC。
CubeMX 配置 DAC¶
- Analog → DAC → OUT1 Configuration: Connected to external pin only
- Parameter Settings:
- Output Buffer: Enable
- Trigger: None(软件设置值)
- PA4 自动配置
- 生成代码
DAC 编程¶
/* main.c */
/* USER CODE BEGIN 2 */
HAL_DAC_Start(&hdac, DAC_CHANNEL_1); // 启动 DAC 通道 1
/* USER CODE END 2 */
// 输出指定电压(0 ~ 3300mV)
void DAC_SetVoltage(uint16_t voltage_mv)
{
uint16_t dac_value = (uint16_t)((float)voltage_mv / 3300.0f * 4095);
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dac_value);
}
/* USER CODE BEGIN WHILE */
while (1)
{
DAC_SetVoltage(1500); // 输出 1.5V
HAL_Delay(1000);
DAC_SetVoltage(2500); // 输出 2.5V
HAL_Delay(1000);
/* USER CODE END WHILE */
}
DAC 产生正弦波¶
CubeMX 中可以配置 DAC 的触发源为定时器,结合 DMA 自动输出波形:
- DAC Trigger 选择对应的 TIM(如 TIM6 TRGO)
- 配置 TIM6 的更新频率 = 波形频率 × 查找表点数
- DMA Settings → Add → DAC Channel1
// 正弦波查找表(256 点,12 位分辨率)
const uint16_t sine_table[256] = {
2048, 2098, 2148, 2198, 2248, 2298, 2348, 2397,
2447, 2496, 2545, 2594, 2642, 2690, 2737, 2784,
// ... 填充完整的 256 个值
// 值 = 2048 + 2047 * sin(2π * i / 256)
};
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim6); // 启动触发定时器
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1,
(uint32_t*)sine_table, 256, DAC_ALIGN_12B_R);
/* USER CODE END 2 */
// 正弦波频率 = TIM6 更新频率 / 256
四、实用技巧¶
ADC 精度优化¶
-
多次采样取平均:减少随机噪声
-
滤波算法:滑动平均、中值滤波、一阶低通滤波
-
硬件方面:ADC 输入引脚加 RC 低通滤波,VDDA 并联去耦电容
没有 DAC 怎么输出模拟电压?¶
使用 PWM + RC 低通滤波 代替 DAC:
PWM 占空比 50% → 输出约 1.65V(3.3V 的一半)
RC 滤波参数选择
截止频率 \(f_c = \frac{1}{2\pi RC}\) 应远小于 PWM 频率。
例如 PWM = 10kHz,选 R = 10kΩ, C = 1μF → \(f_c \approx 16Hz\),滤波效果好。
五、常见问题¶
ADC 读数跳动很大?
- ADC 输入引脚被其他信号干扰 → 引脚加 RC 滤波
- 电源噪声 → VDDA 引脚加 0.1μF + 10μF 去耦电容
- 软件未做滤波 → 多次采样取平均
- 采样时间太短 → 增加采样周期
ADC 值和万用表测量不一致?
- 检查参考电压:VREF+ 是否真的是 3.3V?用万用表实测
- 是否调用了
HAL_ADCEx_Calibration_Start()进行校准 - 信号源阻抗是否太高(> 10kΩ 时需要增加采样时间或加缓冲器)