10-ADC.md 7.6 KB

10 ADC(HAL)

目标:单通道 ADC 采集 + 定时器触发注入通道

原理简述

逐次逼近型 (SAR) ADC 工作原理

模拟输入 → 采样保持 → 比较器 ←── DAC (逐次逼近)
                           │
                    逐次逼近寄存器 (SAR)
                           │
                       ┌───┴───┐
                       │ 数据寄存器
                       └───────┘

SAR 转换过程(以 3 位为例):
  1. 采样保持: 锁定输入电压 (Vin)
  2. 比较 MSB: DAC=100(½Vref) → Vin > 100? → MSB=1(或 0)
  3. 比较 next: DAC=110(¾Vref) → Vin > 110? → 确定第二位
  4. 比较 LSB:  确定最后一位
  5. 转换完成 → 存入数据寄存器

STM32F103: 12 位 → 需要 12 个 SAR 比较周期

关键参数

  • 分辨率:12 位 → 数值范围 0~4095
  • 电压范围:0V ~ VREF(通常 VREF = VDDA = 3.3V)
  • 转换公式:V_in = ADC_Value / 4095 × VREF
  • 精度:±1 LSB(理想条件下 3.3V/4096 ≈ 0.8mV/位)

规则通道 vs 注入通道

STM32 ADC 有两组通道,可以理解为一个"常规任务"和一个"紧急插队任务":

规则通道组 (Regular Group):
  ┌─────┐   ┌─────┐   ┌─────┐
  │CH0  │──→│CH1  │──→│...  │──→ 16 位数据寄存器 (DR)
  └─────┘   └─────┘   └─────┘
  最多 16 个通道按顺序转换,结果都进同一个 DR
  (多通道时必须用 DMA,否则后一个覆盖前一个)

注入通道组 (Injected Group):
  ┌─────┐
  │CH0  │──→ 注入数据寄存器 (JDR1~4)
  └─────┘   最多 4 个通道,各占独立寄存器
  可以打断规则组正在进行的转换

扫描模式 vs 连续模式

单次/单通道:  触发 → 转换 CH0 → 停止(常用)
连续/单通道:  触发 → 转换 CH0 → 自动再转 CH0 → 一直转
单次/多通道:  触发 → CH0 → CH1 → CH2 → 停止
连续/多通道:  触发 → CH0→CH1→...→CHn→再CH0→...(一直转)

数据对齐

12 位结果存在 16 位寄存器中,可选择对齐方式:

右对齐(常用):
  DR:  [15][14][13][12][11][10][9][8][7][6][5][4][3][2][1][0]
                                   ↑  DATA[11:0]  ↑
                                   └──────────────┘
                       实际值 = DR(直接可用)

左对齐(某些滤波场景用):
  DR:  [15][14][13][12][11][10][9][8][7][6][5][4][3][2][1][0]
        ↑  DATA[11:0]  ↑
        └──────────────┘
                       实际值 = DR >> 4

采样时间与转换时间

总转换时间 = 采样时间 + 12.5 个 ADC 时钟周期

ADC 时钟 (ADCCLK) 来自 APB2 分频,必须 ≤ 14MHz:

  APB2=72MHz → 分频 2→36MHz(✗超限)  → 分频 4→18MHz(✗超限)
              → 分频 6→12MHz(✓)      → 分频 8→9MHz(✓)

采样时间可选:1.5 / 7.5 / 13.5 / 28.5 / 41.5 / 55.5 / 71.5 / 239.5 周期

示例 (72MHz, 分频=6, 采样=1.5):
  ADCCLK = 72/6 = 12MHz
  总时间 = (1.5 + 12.5) / 12MHz ≈ 1.17µs
  每秒采样 ≈ 856k samples/s

采样时间越短 → 越快,但对信号源内阻敏感
采样时间越长 → 越准,适合高内阻信号源(如分压电阻网络)

校准

ADC 每次上电后需要校准,消除内部电容阵列的偏差。CubeMX 生成的代码会自动完成:

HAL_ADC_Start(&hadc1) 的内部流程:
  1. 检查是否已校准,若未校准则自动校准
  2. 校准完成
  3. 开始转换

如果需要手动校准:HAL_ADCEx_Calibration_Start(&hadc1)(部分 HAL 版本)

单通道转换(软件触发)

CubeMX 配置

  • ADC1 → IN0 (PA0)
  • 参数:
    • Clock Prescaler:PCLK2 divided by 6(ADCCLK = 12MHz)
    • Resolution:12 bits
    • Scan Conversion Mode:Disabled(单通道)
    • Continuous Conversion Mode:Disabled(单次转换)
    • Data Alignment:Right alignment
  • Regular Conversion → Channel:0, Sampling Time:1.5 Cycles

关键 API

函数 说明
HAL_ADC_Start(&hadc) 启动一次转换(含自动校准)
HAL_ADC_PollForConversion(&hadc, Timeout) 轮询等待转换完成
HAL_ADC_GetValue(&hadc) 读取 12 位转换结果

完整代码

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_ADC1_Init();

    while (1)
    {
        HAL_ADC_Start(&hadc1);                                    // 启动转换
        if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK)     // 等完成
        {
            uint16_t val = HAL_ADC_GetValue(&hadc1);              // 读结果 (0~4095)
            float v = (float)val / 4095.0f * 3.3f;                // 换算电压
            printf("ADC=%d, V=%.2f\r\n", val, v);                 // 需重定向 printf
        }
        HAL_Delay(500);
    }
}

定时器触发转换(注入通道)

原理

定时器 TIM3 (TRGO=Update)
       │
       │  每过 N ms 触发一次
       ↓
ADC 注入通道 ──→ 打断正在进行的常规转换
             ──→ 执行注入通道转换
             ──→ 恢复常规转换

优势:精确的采样间隔、CPU 不需要空等轮询。

CubeMX 配置

  • ADC1 → IN0 (PA0)
  • Injected Conversion ModeEnable
  • External Trigger Conversion Source:Timer 3 Trigger Out
  • External Trigger Conversion Edge:Rising Edge
  • NVIC Settings → ADC1 and ADC2 global interrupt → Enable
  • TIM3 → PrescalerCounter Period 决定触发间隔
    • 例如 PSC=7199, Period=4999 → 72MHz/7200/5000 = 2Hz
  • TIM3 → TRGOUpdate Event

关键 API

函数 说明
HAL_ADCEx_InjectedStart(&hadc) 启动注入通道(等待触发)
HAL_ADCEx_InjectedGetValue(&hadc, InjectedRank) 读取注入结果
HAL_ADCEx_InjectedConvCpltCallback(&hadc) 注入完成回调
/* 注入转换完成回调 —— 由 HAL 在 ADC 中断中调用 */
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef *hadc)
{
    if (hadc == &hadc1)
    {
        uint32_t val = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_1);
        // 处理结果(中断中应快速处理,不要用 HAL_Delay)
    }
}

ADC + DMA(多通道/连续采集)

当需要连续采集多通道时,必须使用 DMA 把结果自动搬走,否则新数据会覆盖旧数据:

#define ADC_BUF_LEN  16
uint32_t adc_buf[ADC_BUF_LEN];          // DMA 缓冲区

HAL_ADC_Start_DMA(&hadc1, adc_buf, ADC_BUF_LEN);
// DMA 自动把每次转换结果搬入 adc_buf
// 填满后调用 HAL_ADC_ConvCpltCallback()

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    // adc_buf 已填满,可以处理
    for (int i = 0; i < ADC_BUF_LEN; i++) {
        printf("CH%d = %d\r\n", i, adc_buf[i]);
    }
}

常见坑点

  • ⚠️ ADC 输入电压绝对不能超过 VREF(通常 VDDA = 3.3V),否则损坏 ADC
  • ⚠️ ADCCLK 必须 ≤ 14MHz,否则转换结果不准确(CubeMX 用红色警告)
  • ⚠️ 多通道扫描但不使用 DMA → 后面的结果会覆盖前面的,只能读到最后一个通道
  • ⚠️ 注入通道优先级高于常规通道,可打断常规转换。高频率注入会导致常规通道饥饿
  • ⚠️ HAL_ADC_PollForConversion 返回 HAL_TIMEOUT → 检查 ADC 时钟、校准是否完成
  • ⚠️ 连续模式(Continuous Conversion)开启后,ADC 会不停地转,功耗较高