前言
ADC 類比數位轉換器(Analog-to-Digital Converter)是嵌入式系統中感測器資料的入口。無論是讀取電位器、溫度感測器(LM35、DS18B20)、麥克風音訊還是電池電壓,最終都需要 ADC 將連續的類比訊號轉換為數位值供 MCU 處理。STM32 內建多組 12-bit SAR ADC,支援三通道同步採樣;ESP32 則有兩組 12-bit SAR ADC,最多可連接 18 個類比輸入。本文將從 ADC 的基本原理講到實際程式碼,涵蓋 STM32 與 ESP32 的不同實作方式。
一、ADC 基本原理
1.1 什麼是 ADC?
ADC 將連續的類比電壓轉換為離散的數位數值。轉換過程包含三個步驟:
- 取樣(Sampling):以固定間隔擷取類比訊號的瞬時值
- 量化(Quantization):將取樣值近似到最接近的離散電平
- 編碼(Encoding):將量化電平轉為二進位編碼
1.2 核心參數
| 參數 | 說明 | 公式 |
|---|---|---|
| 解析度 (Resolution) | 數位輸出的位元數 | N bits → 2^N 個電平 |
| 參考電壓 (Vref) | ADC 的最大輸入電壓 | 通常 = VDD 或內部參考 |
| LSB (最小有效位) | 1 個數位步進對應的電壓 | Vref / 2^N |
| 取樣率 (Sampling Rate) | 每秒可完成的轉換次數 | SPS (Samples Per Second) |

圖 4:ADC 解析度 vs 電壓解析度 (Vref=3.3V) — 12-bit ADC 的 1 LSB = 0.806mV,16-bit 可達 50.3μV。
1.3 常見 ADC 類型

圖 3:常見 ADC 類型比較 — SAR 是 MCU 內建的主流架構,Sigma-Delta 用於高精度儀表,Flash 用於極高速場景。
STM32 和 ESP32 的內建 ADC 都是 SAR(逐次逼近暫存器,Successive Approximation Register) 架構,12-bit 解析度,支援多通道多工。
二、SAR ADC 時序
圖 1:SAR ADC 轉換時序 (12-bit) — Sample → Hold → 逐位逼近 (D11~D0) → 輸出結果。每個位元需要一個 SCLK 週期。
SAR ADC 的工作原理類似天平:
- Sample 階段:開關閉合,取樣電容追蹤輸入電壓
- Hold 階段:開關斷開,電容保持取樣電壓
- 逐次逼近:從 MSB 開始,比較器逐位決定每一位元是 0 還是 1
- 轉換完成:結果鎖存到資料暫存器,觸發 IRQ 或 DMA
轉換時間計算:
T_conv = (取樣週期 + 轉換位元數) × ADC_CLK 週期
T_conv = (n_cycles + 12) × 1/ADC_CLK
// STM32F4 @ 21 MHz ADC Clock, 3 cycle sampling:
T_conv = (3 + 12) / 21 MHz = 0.714 μs
Sampling Rate ≈ 1.4 MSPS
三、STM32 ADC 實作 (HAL Library)
STM32F4 的 ADC 支援多種模式:單次/連續/掃描、暫存器/DMA/Triple 交錯。
3.1 單通道單次轉換
#include "stm32f4xx_hal.h"
ADC_HandleTypeDef hadc1;
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; // 84/4=21 MHz
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = 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;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
HAL_ADC_Init(&hadc1);
sConfig.Channel = ADC_CHANNEL_0; // PA0
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}
uint32_t read_adc(void)
{
HAL_ADC_Start(&hadc1); // 啟動轉換
HAL_ADC_PollForConversion(&hadc1, 100); // 等待完成
return HAL_ADC_GetValue(&hadc1); // 讀取結果
}
// 轉換為電壓
float adc_to_voltage(uint32_t raw)
{
return (float)raw * 3.3f / 4095.0f; // 12-bit, Vref=3.3V
}
3.2 連續模式 + DMA (多通道)
圖 2:ADC 多通道掃描 + DMA 傳輸 — Timer 觸發轉換,DMA 自動將結果搬運到記憶體陣列。
#define ADC_CHANNELS 3
uint32_t adc_values[ADC_CHANNELS];
void MX_ADC1_DMA_Init(void)
{
// ... 同上設定,修改以下參數
hadc1.Init.ScanConvMode = ENABLE; // 掃描模式
hadc1.Init.ContinuousConvMode = ENABLE; // 連續轉換
hadc1.Init.NbrOfConversion = ADC_CHANNELS; // 3 通道
HAL_ADC_Init(&hadc1);
// 通道 0: PA0 (電位器)
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// 通道 1: PA1 (LM35 溫度)
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// 通道 2: PA2 (電池電壓)
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = 3;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
}
void start_dma_adc(void)
{
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_values, ADC_CHANNELS);
}
// DMA half-complete / complete callback
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == ADC1)
{
// adc_values[0] = PA0, adc_values[1] = PA1, adc_values[2] = PA2
float v0 = adc_values[0] * 3.3f / 4095.0f;
float temp = adc_values[1] * 3.3f / 4095.0f * 100.0f; // LM35: 10mV/°C
float batt = adc_values[2] * 3.3f / 4095.0f * 2.0f; // 分壓 x2
printf("Pot: %.2fV Temp: %.1f°C Batt: %.2fV\n", v0, temp, batt);
}
}
3.3 雙重 ADC 交錯模式 (Dual Interleaved)
使用兩個 ADC 同時轉換同一通道,可將取樣率翻倍:
// ADC1 = Master, ADC2 = Slave (同步模式)
// 需要 HAL_ADCEx_MultiModeConfigChannel() 設定
// ADC1 轉換偶數次,ADC2 轉換奇數次
// 有效取樣率從 ~1.4 MSPS → ~2.8 MSPS
四、ESP32 ADC 實作
ESP32 有兩組 SAR ADC,總共支援 18 個通道(ADC1: 8ch, ADC2: 10ch)。注意 ADC2 與 Wi-Fi 共用,Wi-Fi 啟用時建議只用 ADC1。
4.1 基本 ADC 讀取 (Arduino)
#define POT_PIN 34 // ADC1_CH6
#define LIGHT_PIN 35 // ADC1_CH7
#define BAT_PIN 36 // ADC1_CH0 (SENSOR_VP)
void setup()
{
Serial.begin(115200);
analogReadResolution(12); // 12-bit (0~4095)
analogSetAttenuation(ADC_11db); // 0~3.3V 全量程
}
void loop()
{
int pot = analogRead(POT_PIN);
int light = analogRead(LIGHT_PIN);
int bat = analogRead(BAT_PIN);
float v_pot = pot * 3.3f / 4095.0f;
float v_light = light * 3.3f / 4095.0f;
float v_bat = bat * 3.3f / 4095.0f * 2.0f; // Voltage divider
Serial.printf("Pot: %.2fV Light: %.2fV Batt: %.2fV\n",
v_pot, v_light, v_bat);
delay(100);
}
4.2 ESP-IDF ADC 單次讀取
#include "driver/adc.h"
#include "esp_adc_cal.h"
#define DEFAULT_VREF 1100 // mV (typ. calibration)
static esp_adc_cal_characteristics_t adc_chars;
void adc_init(void)
{
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11); // GPIO34
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11,
ADC_WIDTH_BIT_12, DEFAULT_VREF, &adc_chars);
}
uint32_t read_adc_mv(void)
{
uint32_t raw = adc1_get_raw(ADC1_CHANNEL_6);
uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &adc_chars);
return mv; // mV 單位
}
4.3 ESP32 連續 ADC + DMA (ESP-IDF)
ESP32 的連續 ADC 驅動(Continuous ADC)使用硬體 FIFO + DMA,支援多通道自動掃描:
#include "esp_adc/adc_continuous.h"
adc_continuous_handle_t adc_handle;
void adc_continuous_init(void)
{
adc_continuous_handle_cfg_t handle_cfg = {
.max_store_buf_size = 1024,
.conv_frame_size = 256,
};
adc_continuous_new_handle(&handle_cfg, &adc_handle);
adc_continuous_config_t dig_cfg = {
.sample_freq_hz = 20000, // 20 kHz 取樣率
.conv_mode = ADC_CONV_SINGLE_UNIT_1,
.format = ADC_DIGI_OUTPUT_FORMAT_TYPE2,
};
adc_digi_pattern_config_t patterns[2] = {
{.atten = ADC_ATTEN_DB_11, .channel = 6, .unit = ADC_UNIT_1, .bit_width = ADC_BIT_WIDTH_12},
{.atten = ADC_ATTEN_DB_11, .channel = 7, .unit = ADC_UNIT_1, .bit_width = ADC_BIT_WIDTH_12},
};
dig_cfg.adc_pattern = patterns;
dig_cfg.pattern_num = 2;
adc_continuous_config(adc_handle, &dig_cfg);
adc_continuous_start(adc_handle);
}
void adc_read_task(void *pv)
{
uint8_t buf[256];
uint32_t ret_num = 0;
while (1)
{
adc_continuous_read(adc_handle, buf, 256, &ret_num, pdMS_TO_TICKS(100));
if (ret_num > 0)
{
for (int i = 0; i < ret_num; i += 4) // TYPE2: 4 bytes per sample { uint32_t raw = *(uint32_t *)&buf[i]; uint8_t ch = (raw >> 16) & 0xF;
uint16_t val = raw & 0xFFF;
printf("CH%d: %d (%.2fV)\n", ch, val, val * 3.3f / 4095.0f);
}
}
}
}
五、實戰專案:資料記錄儀 (Data Logger)
整合 ADC + RTC + SPI Flash 實現長時間感測器記錄:
// 架構
// Sensor → ADC1_CH0 (PA0) ─┐
// Sensor → ADC1_CH1 (PA1) ─┤ DMA → adc_values[2] → Log to SD/Flash
// Sensor → ADC1_CH2 (PA2) ─┘
typedef struct {
uint32_t timestamp; // RTC timestamp
uint16_t ch0; // 12-bit raw
uint16_t ch1;
uint16_t ch2;
uint16_t reserved; // 4-byte alignment
} LogEntry;
#define LOG_SIZE 512
LogEntry log_buf[LOG_SIZE];
uint16_t log_index = 0;
void log_adc_data(void)
{
log_buf[log_index].timestamp = get_rtc_sec();
log_buf[log_index].ch0 = adc_values[0];
log_buf[log_index].ch1 = adc_values[1];
log_buf[log_index].ch2 = adc_values[2];
log_index++;
if (log_index >= LOG_SIZE)
{
flash_write_sector((uint8_t *)log_buf, LOG_SIZE * sizeof(LogEntry));
log_index = 0;
}
}
六、常見問題與除錯
6.1 讀值不穩定 / 跳動
- 硬體濾波:在 ADC 輸入腳加 100nF 電容對地
- 軟體濾波:取多次讀值後平均
- 取樣時間不足:增加 SamplingTime(特別是訊號源阻抗高時)
- 參考電壓雜訊:Vref+ 腳加濾波電容,不建議與數位電源共用
// 移動平均濾波
#define FILTER_TAP 16
uint32_t filter_buf[FILTER_TAP];
uint8_t filter_idx = 0;
uint32_t adc_filtered(void)
{
filter_buf[filter_idx++] = read_adc();
if (filter_idx >= FILTER_TAP) filter_idx = 0;
uint32_t sum = 0;
for (int i = 0; i < FILTER_TAP; i++) sum += filter_buf[i];
return sum / FILTER_TAP;
}
6.2 轉換速度太慢
- 提高 ADC Clock(STM32F4 最高 36 MHz,但建議 21 MHz)
- 降低取樣週期(從 480 cycles → 3 cycles)
- 使用 DMA + 連續模式(不佔 CPU)
- 雙重 ADC 交錯模式(取樣率翻倍)
6.3 ESP32 ADC 非線性誤差
- ESP32 ADC 在中間範圍 (0.2V~2.8V) 線性度較好,接近 Vref 或 GND 時誤差增大
- 使用 esp_adc_cal 庫進行廠商校正(每個晶片不同)
- 避免使用 ADC2 通道(與 Wi-Fi 衝突)
// ESP32 ADC 校正(更加精確)
uint32_t adc_calibrated_read(adc1_channel_t channel)
{
uint32_t raw = adc1_get_raw(channel);
uint32_t voltage = esp_adc_cal_raw_to_voltage(raw, &adc_chars);
return voltage; // 已校準到 mV
}
七、總結
ADC 是嵌入式系統中類比與數位世界的橋樑。無論是 STM32 還是 ESP32,掌握以下要點就能穩定獲取感測器資料:
- 解析度決定精度:12-bit = 0.8mV/LSB,16-bit = 50μV/LSB(Vref=3.3V)
- 取樣時間:訊號源阻抗越高,需要的取樣時間越長
- 硬體平均 vs 軟體濾波:硬體 oversampling 不耗 CPU,軟體濾波靈活
- DMA 是王道:連續多通道採集一定要用 DMA,CPU 可以專心做控制
- 參考電壓:ADC 的精度最終取決於 Vref 的穩定度
文章評論