目标:单通道 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 比较周期
关键参数:
V_in = ADC_Value / 4095 × VREF逐次逼近型 ADC 的核心是 二进制搜索算法。STM32F103 的 ADC 是 12 位 SAR,需要 12 个比较周期完成一次转换:
第 1 步:采样阶段 (Sampling Phase)
+───┐
Vin ────│ S │
└───┘
│
┌─┴─┐
│ C │ ← 采样电容 (~8pF) 充电到 Vin
└───┘
│
═══╧═══ GND
模拟开关 S 闭合,采样电容 C_ADC (≈8pF) 充电至输入电压 Vin。采样时间由 SMPx[2:0] 位配置。采样开关断开后,电容上保持的电压即为待转换的模拟量。
第 2 步:保持与逐次逼近 (Hold & SAR Conversion)
采样结束后,SAR 开始逐位比较(12 位为例):
① 置 DAC = ½ VREF(二进制 1000 0000 0000 = 0x800)
→ Vin > ½ VREF ? 是 → MSB = 1, 保留下半区间
否 → MSB = 0, 保留上半区间
② 根据 MSB 结果设置 DAC:
MSB=1 → DAC = ¾ VREF(1100 0000 0000 = 0xC00)
MSB=0 → DAC = ¼ VREF(0100 0000 0000 = 0x400)
→ 比较 → 确定 bit10
③~⑫ 重复上述过程,逐位逼近,直到 LSB 确定
最终 12 位二进制数即为转换结果
时序示意图:
采样阶段 SAR 比较阶段
├────────┤├──────────────────────┤
① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
MSB LSB → 结果存入 DR
总转换时间公式:
T_conv = (t_sampling + 12.5) / ADCCLK
其中:
t_sampling = SMPx[2:0] 配置的采样周期数
12.5 = 固定 SAR 转换周期数(12 位比较 + 1 个额外周期)
ADCCLK = APB2 时钟分频后(≤ 14MHz)
ADC 时钟来自 APB2 总线,通过可编程预分频器分频:
APB2 (72MHz)
│
▼
┌──────┐ ┌────────────────────┬────────────────┐
│ /2 │───→│ 36MHz (✗ > 14MHz) │ 不可用 │
├──────┤ ├────────────────────┼────────────────┤
│ /4 │───→│ 18MHz (✗ > 14MHz) │ 不可用 │
├──────┤ ├────────────────────┼────────────────┤
│ /6 │───→│ 12MHz (✓ ≤ 14MHz) │ 常用配置 │
├──────┤ ├────────────────────┼────────────────┤
│ /8 │───→│ 9MHz (✓ ≤ 14MHz) │ 可用,较慢 │
└──────┘ └────────────────────┴────────────────┘
预分频器通过 ADC_CR2.ADCPRE[1:0] 位配置。
注入通道组 (Injected Group) 的行为类似于中断——可以打断正在进行的规则通道转换:
时间轴 →
规则转换: CH0_CH1_CH2_..._CH0_CH1_
│
注入触发到来 ──────────────┤
▼
┌──────────┐
│ 注入 CH0 │ ← 打断并执行注入
├──────────┤
│ 注入 CH1 │
└──────────┘
│
规则转换: ..._CH0_CH1_CH2_... ← 恢复规则转换
优先级规则:
注入通道组 > 规则通道组(注入可打断规则)
注入通道之间按注入通道序号顺序转换(JCH0 > JCH1 > ...)
⚠️ 高频率注入触发会导致规则通道饥饿(永远无法完成转换)。
ADC 采样电容的充电速度受信号源输出阻抗影响:
R_source R_ADC (~1kΩ)
V_source ──┬────/\/\/\/\────┬────/\/\/\/\────┬────┐
│ │ │ │
│ │ │ ┌┴┐
│ │ │ │ C_ADC
│ │ │ │ (~8pF)
│ │ │ └┬┘
═══ ═══ ═══ │
GND GND GND ═══ GND
为使采样电容在采样时间内充分充电,需满足:
t_sampling ≥ (R_source + R_ADC_max) × C_ADC × ln(2^(n+2))
代入 STM32F103 参数(n=12):
R_ADC_max ≈ 1kΩ
C_ADC ≈ 8pF
ln(2^14) ≈ 9.7
t_sampling ≥ (R_source + 1kΩ) × 8pF × 9.7
例如 R_source = 10kΩ:
t_sampling ≥ (10k + 1k) × 8pF × 9.7 ≈ 11k × 8pF × 9.7 ≈ 0.85µs
→ 对应 ADCCLK=12MHz(周期≈83ns)→ 需要 0.85µs/83ns ≈ 10.2 → 选 13.5 周期
V_in = ADC_Value / 4095 × VREF
反推:给定目标电压,求期望 ADC 值:
ADC_Value = V_target / VREF × 4095
例:
VREF = 3.3V, ADC_Value = 2048
V_in = 2048 / 4095 × 3.3V ≈ 1.65V
STM32 ADC 有两组通道,可以理解为一个"常规任务"和一个"紧急插队任务":
规则通道组 (Regular Group):
┌─────┐ ┌─────┐ ┌─────┐
│CH0 │──→│CH1 │──→│... │──→ 16 位数据寄存器 (DR)
└─────┘ └─────┘ └─────┘
最多 16 个通道按顺序转换,结果都进同一个 DR
(多通道时必须用 DMA,否则后一个覆盖前一个)
注入通道组 (Injected Group):
┌─────┐
│CH0 │──→ 注入数据寄存器 (JDR1~4)
└─────┘ 最多 4 个通道,各占独立寄存器
可以打断规则组正在进行的转换
单次/单通道: 触发 → 转换 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 版本)
| 函数 | 说明 |
|---|---|
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 不需要空等轮询。
| 函数 | 说明 |
|---|---|
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)
}
}
当需要连续采集多通道时,必须使用 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]);
}
}
HAL_ADC_PollForConversion 返回 HAL_TIMEOUT → 检查 ADC 时钟、校准是否完成