跳转至

ADC 与 DAC

ADC(模数转换)让 MCU 能"感知"现实世界的模拟信号(光照、温度、电压),DAC(数模转换)让 MCU 能"输出"模拟电压。这两个外设是连接数字世界和模拟世界的桥梁。


一、ADC(模数转换器)

什么是 ADC?

ADC(Analog-to-Digital Converter)将连续的模拟电压转换为离散的数字值。

\[ \text{数字值} = \frac{V_{\text{in}}}{V_{\text{ref}}} \times (2^n - 1) \]

其中 \(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 位的,需要选择对齐方式:

右对齐(常用):0000 xxxx xxxx xxxx  → 直接读取就是 0~4095
左对齐:        xxxx xxxx xxxx 0000  → 需要右移 4 位

推荐

一般使用右对齐,读出来直接就是数字值,不需要额外处理。

ADC 采样时间

每个通道可以独立配置采样时间。采样时间越长,精度越高,转换速度越慢:

\[ T_{\text{conv}} = (\text{采样周期} + 12.5) \times T_{\text{ADC\_CLK}} \]
采样周期选项 采样时间(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 配置步骤:

  1. Analog → ADC1
    • IN0 勾选使能(PA0)
  2. 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
  3. PA0 自动配置为模拟模式
  4. 生成代码
/* 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 库中可以在启动前调用校准函数,提高精度:

/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);  // ADC 校准(上电后执行一次)
/* USER CODE END 2 */

多通道扫描 + DMA

当需要同时采集多路 ADC 时,使用扫描模式 + DMA 最高效:

CubeMX 配置步骤:

  1. Analog → ADC1 → 勾选 IN0, IN1, IN2
  2. Parameter Settings
    • Scan Conversion Mode: Enabled
    • Continuous Conversion Mode: Enabled
    • Number Of Conversion: 3
    • Rank 1/2/3 分别配置 Channel 0/1/2
  3. DMA Settings 标签页 → Add → ADC1
    • Direction: Peripheral To Memory
    • Mode: Circular(循环模式,自动更新)
    • Data Width: Half Word(16 位)
  4. 生成代码
/* 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 相反,将数字值转换为模拟电压输出。

\[ V_{\text{out}} = \frac{\text{数字值}}{2^n} \times V_{\text{ref}} \]

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

  1. Analog → DAC → OUT1 Configuration: Connected to external pin only
  2. Parameter Settings:
    • Output Buffer: Enable
    • Trigger: None(软件设置值)
  3. PA4 自动配置
  4. 生成代码

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 自动输出波形:

  1. DAC Trigger 选择对应的 TIM(如 TIM6 TRGO)
  2. 配置 TIM6 的更新频率 = 波形频率 × 查找表点数
  3. 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 精度优化

  1. 多次采样取平均:减少随机噪声

    uint32_t sum = 0;
    for (int i = 0; i < 16; i++)
    {
        HAL_ADC_Start(&hadc1);
        HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
        sum += HAL_ADC_GetValue(&hadc1);
    }
    uint16_t avg = sum / 16;
    

  2. 滤波算法:滑动平均、中值滤波、一阶低通滤波

    // 一阶低通滤波(IIR)
    // α越小越平滑,但响应越慢
    float filtered = 0;
    float alpha = 0.1f;
    filtered = alpha * new_value + (1 - alpha) * filtered;
    

  3. 硬件方面:ADC 输入引脚加 RC 低通滤波,VDDA 并联去耦电容

没有 DAC 怎么输出模拟电压?

使用 PWM + RC 低通滤波 代替 DAC:

PWM 输出 ──[ R ]──┬── 模拟电压输出
                [ C ]
                 GND

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 读数跳动很大?

  1. ADC 输入引脚被其他信号干扰 → 引脚加 RC 滤波
  2. 电源噪声 → VDDA 引脚加 0.1μF + 10μF 去耦电容
  3. 软件未做滤波 → 多次采样取平均
  4. 采样时间太短 → 增加采样周期

ADC 值和万用表测量不一致?

  1. 检查参考电压:VREF+ 是否真的是 3.3V?用万用表实测
  2. 是否调用了 HAL_ADCEx_Calibration_Start() 进行校准
  3. 信号源阻抗是否太高(> 10kΩ 时需要增加采样时间或加缓冲器)