目标:单通道 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 × VREFSTM32 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 时钟、校准是否完成