一、ADC 是什么

模拟信号(连续)


3.3V
2.5V
1.6V
0.8V
time

数字(离散)


4095
2000
0

分辨率 12 位 → 0~4095 · VREF=3.3V → 3.3V / 4096 ≈ 0.8mV / LSB

二、STM32F103 ADC 特性

ADC1 / ADC2 📥 输入通道 16 个外部 IN0~IN15 + 温度传感器 + VREF (ADC1) 🎯 分辨率 12 位(可配 10/8/6 位) 最高 1MHz @ 14MHz ADC 时钟 ⚙️ 工作模式 ├─ 单次转换 ├─ 连续转换 ├─ 扫描模式 ├─ 注入模式(高优先级) └─ 模拟看门狗 ⚡ 触发源 ├─ 软件触发 ├─ 定时器触发 (TRGO) └─ 外部引脚 (EXTI) 📤 数据输出 转换结果 → ADC_DR 寄存器 → 轮询 / 中断 / DMA 读取

F103 通道引脚映射

通道 ADC1 ADC2
IN0 PA0 PA0
IN1 PA1 PA1
IN2 PA2 PA2
IN3 PA3 PA3
IN4 PA4 PA4
IN5 PA5 PA5
IN6 PA6 PA6
IN7 PA7 PA7
IN8 PB0 PB0
IN9 PB1 PB1
IN10 PC0 PC0
IN11 PC1 PC1
IN12 PC2 PC2
IN13 PC3 PC3
IN14 PC4 PC4
IN15 PC5 PC5

三、单通道——轮询方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ADC_HandleTypeDef hadc1;

void MX_ADC1_Init(void) {
__HAL_RCC_ADC1_CLK_ENABLE();

hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; // 单通道,不扫描
hadc1.Init.ContinuousConvMode = DISABLE; // 单次转换
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 右对齐
hadc1.Init.NbrOfConversion = 1; // 1 个通道
HAL_ADC_Init(&hadc1);

// 配置通道
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_0; // PA0
sConfig.Rank = 1; // 序列第 1 个
sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5; // 采样时间
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}

/* 读取一次 */
uint16_t ADC_Read(void) {
HAL_ADC_Start(&hadc1); // 启动
HAL_ADC_PollForConversion(&hadc1, 100); // 等待转换完成(100ms超时)
return HAL_ADC_GetValue(&hadc1); // 读结果
}

/* 使用 */
uint16_t adc_val = ADC_Read();
float voltage = adc_val * 3.3f / 4096.0f; // 转换为电压

四、单通道——中断方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
volatile uint16_t adc_value = 0;
volatile uint8_t adc_done = 0;

void MX_ADC1_Init_IT(void) {
// ... 同上基础配置 ...

HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
}

/* 启动中断转换 */
HAL_ADC_Start_IT(&hadc1);

/* 转换完成回调 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
if (hadc->Instance == ADC1) {
adc_value = HAL_ADC_GetValue(hadc);
adc_done = 1;
}
}

/* 主循环 */
while (1) {
HAL_ADC_Start_IT(&hadc1); // 每次需要重新启动
while (!adc_done); // 等待完成
adc_done = 0;
printf("ADC: %d\r\n", adc_value);
HAL_Delay(500);
}

五、多通道——扫描 + DMA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#define ADC_CH_NUM  4
volatile uint16_t adc_dma_buf[ADC_CH_NUM]; // DMA 目标数组

DMA_HandleTypeDef hdma_adc1;

void MX_ADC1_DMA_Init(void) {
/* ─── ADC 配置 ─── */
__HAL_RCC_ADC1_CLK_ENABLE();
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; // 扫描模式
hadc1.Init.ContinuousConvMode = ENABLE; // 连续转换
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = ADC_CH_NUM; // 4 个通道
HAL_ADC_Init(&hadc1);

// 配置 4 个通道(按 rank 顺序轮转)
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5;

sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; // PA0
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = 2; // PA1
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_2; sConfig.Rank = 3; // PA2
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_3; sConfig.Rank = 4; // PA3
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

/* ─── DMA 配置 ─── */
__HAL_RCC_DMA1_CLK_ENABLE();
hdma_adc1.Instance = DMA1_Channel1; // ADC1 使用 DMA1_CH1
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不变
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR; // 循环模式
hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_adc1);

__HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1); // 绑定 DMA 到 ADC
}

/* 启动 */
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_dma_buf, ADC_CH_NUM);

/* 使用——DMA 循环模式自动更新,直接读数组即可 */
float v0 = adc_dma_buf[0] * 3.3f / 4096.0f;
float v1 = adc_dma_buf[1] * 3.3f / 4096.0f;
float v2 = adc_dma_buf[2] * 3.3f / 4096.0f;
float v3 = adc_dma_buf[3] * 3.3f / 4096.0f;

六、定时器触发 ADC(精确等间隔采样)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* TIM2 每 1ms 产生 TRGO → 触发 ADC 采样
* 适用于需要严格等间隔采样的场景(FFT/功率计算)
*/

// TIM2 主模式:溢出产生 TRGO
TIM_MasterConfigTypeDef sMaster = {0};
sMaster.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMaster.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMaster);

// ADC 外部触发:TIM2_TRGO 上升沿
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;

// 启动 TIM2 + ADC_DMA
HAL_TIM_Base_Start(&htim2);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_dma_buf, BUF_SIZE);

// 现在 TIM2 每溢出一次,ADC 自动采样一次 → DMA 搬走
// 全程硬件自动,CPU 零干预

七、过采样提高分辨率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
硬件 12 位 → 过采样实现更高有效位数

过采样 N 倍 → SNR 提升 √N → ENOB 增加 log₄(N)

过采样 4 倍 → +1 bit (有效 13 位)
过采样 16 倍 → +2 bit (有效 14 位)
过采样 64 倍 → +3 bit (有效 15 位)
过采样 256 倍 → +4 bit (有效 16 位)

软件实现:
uint32_t sum = 0;
for (int i = 0; i < 64; i++) {
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 10);
sum += HAL_ADC_GetValue(&hadc1);
}
uint16_t result = sum >> 3; // 64 次累加右移 3 位 → 15 位有效

八、内部温度传感器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// F103 内部温度传感器接在 ADC1_IN16
void ADC_TempSensor_Init(void) {
// ... ADC1 基础配置 ...

ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_TEMPSENSOR; // 通道 16
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // 温度通道需更长采样时间
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}

// 读取并换算温度
uint16_t adc_val = ADC_Read();
float voltage = adc_val * 3.3f / 4096.0f;
float temperature = ((1.43f - voltage) / 0.0043f) + 25.0f;

// V25 = 1.43V (25℃时传感器输出电压)
// Avg_Slope = 4.3mV/℃
// Temp = (V25 - V_sense) / Avg_Slope + 25

九、实战:采集电位器电压

3.3V

10KΩ 电位器



GND

PA0
ADC_IN0

旋转电位器 → PA0 电压 03.3V → ADC 值 04095

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(void) {
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
MX_ADC1_Init();

printf("ADC 电位器采集\r\n");

while (1) {
uint16_t adc = ADC_Read();
float voltage = adc * 3.3f / 4096.0f;
printf("ADC:%4d Voltage:%.3fV\r\n", adc, voltage);
HAL_Delay(200);
}
}

十、CubeMX 配置速查

场景 CubeMX 要点
单通道轮询 ADC1 → IN0 → 关闭 Scan/Continuous → 软件触发
单通道中断 同上 + NVIC 开启 ADC1_2 中断
多通道 DMA 开启 Scan + Continuous → NbrOfConversion=通道数 → DMA Settings 添加 ADC1
定时器触发 TIM2 主模式 TRGO → ADC 外部触发选 TIM2_TRGO
温度传感器 ADC1 → IN16 → SamplingTime 选最长

十一、常见问题

现象 原因 解决
ADC 值一直 0 ADC 未启动 / 通道配置错 HAL_ADC_Start + 检查 Channel
值乱跳 采样时间太短 增大 SamplingTime
多通道值串位 DMA 循环模式 buf 指针 确认 buf 大小 ≥ 通道数
ADC 时钟超过 14MHz PCLK2 分频不够 ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV6
值卡在 4095 输入电压 ≥ VREF 分压电阻降压
通道间串扰 采样保持电容未充满 增大 SamplingTime 或降低频率
HAL_ADC_PollForConversion 超时 ADC 未启动或时钟未开 检查 __HAL_RCC_ADC1_CLK_ENABLE