← 返回基础原理库

ADC 采样原理:那个 0–4095 是怎么来的

最后更新 2026-06-20
⏱ 约 9 分钟 🟢 软件/低风险

你写了一行读传感器的代码,它给你返回一个数,比如 2048。它代表多少伏?光照多强?温度几度?光看这个数啥也不是。要把它翻译成有意义的物理量,你得先搞懂 ADC 在你按下读取那一刻,到底在电路里干了什么。

ADC 把"连续电压"翻译成"整数"

ADC = 模数转换器(Analog-to-Digital Converter)。现实世界的电压是连续的——它可以是 1.6437…V,小数点后无穷多位。可单片机是数字世界,只认整数。ADC 的活就是当这个翻译官:把引脚上连续变化的电压,量化成一个 CPU 能读的整数。

这个翻译分三步,理解了这三步,后面所有的坑都有根:

  • 采样(Sample):在某个瞬间"看一眼"引脚电压。注意是瞬间——ADC 读的不是一段时间的平均,而是某一刹那的值。信号变化太快,两次采样之间发生的事它完全不知道。
  • 保持(Hold):把那一瞬间的电压用一个小电容"冻住",在转换期间保持不变。因为转换需要时间,如果转换过程中电压还在动,结果就乱了。这就是常说的"采样保持电路"。
  • 量化(Quantize):把冻住的这个电压,对照满量程分成的许多档,看它落在第几档,输出这个档位号。这一步必然丢精度——连续的电压被强行归到最近的整数档,档与档之间的细微差别就被抹掉了。

所以你拿到的那个整数,是"某一瞬间的电压落在了第几档"。要还原成电压,就得知道两件事:一共分了多少档、满量程是多少伏。这正是下面两个关键参数。

分辨率与参考电压:换算的两把钥匙

分辨率决定分多少档,由位数决定:

  • ESP32 / ESP32-S3 默认 12 位,即 2¹² = 4096 档,读数范围 0–4095。
  • Arduino Uno 是 10 位(2¹⁰ = 1024 档,0–1023)。
  • 位数越高,档分得越细,能分辨的最小电压变化越小。

参考电压决定满量程对应多少伏——也就是"最高那一档 4095 代表几伏"。ESP32-S3 的满量程约 3.3V(准确说取决于衰减档,见下节)。定了这两个数,换算公式就出来了:

电压 ≈ 原始读数 / 4095 × 参考电压

读到 2048,约等于 2048 / 4095 × 3.3 ≈ 1.65V,正好是满量程一半,符合"2048 是 4095 的一半"的直觉。

这里有个实战含义要想清楚:分辨率决定的是"能分多细",采样率决定的是"能跟多快"。 12 位分辨率意味着 3.3V 被切成 4096 份,每份约 0.8mV——比这更小的电压变化你根本读不出来。而采样率(每秒采多少次)决定你能不能跟上信号变化:要读一个 1kHz 的正弦波,采样率至少得是它的 2 倍(这叫奈奎斯特定律,采样率不够会把高频信号"混叠"成假的低频,读出来完全是错的)。测慢变的光照、温度,采样率随便够;测音频、快速振动,就得盯着采样率够不够。

ESP32-S3 的 ADC:三个必须知道的特性

ESP32-S3 的 ADC 好用但有脾气,这三点不知道就会被坑。

其一,12 位分辨率。 默认 12 位、0–4095,够绝大多数传感器用。

其二,衰减档位(atten)决定量程。 ESP32-S3 的 ADC 内核其实只能直接测到约 0.8V 左右的窄范围。为了能测到 3.3V,它在输入端加了个可调衰减器,把大电压按比例缩小再送进去。你通过设置 atten 档位来选量程:

atten 档 大致可测量程 用途
0 dB ~0–0.95V 只测小信号
2.5 dB ~0–1.3V
6 dB ~0–1.75V
11 dB(IDF 5.x 常量已改名 ADC_ATTEN_DB_12 ~0–3.1V+ 最常用,量程最大

绝大多数场景直接选 11dB 档,量程覆盖到 3.3V 附近。选错档最典型的现象是:明明电压才 1V,读数却顶到 4095 撑满了——那是你用了小量程档去测大电压,超量程"削顶"了。

其三,天生不线性,要 adc_cali 校准才准。 这是 ESP32 系列 ADC 最出名的毛病:它的"读数—电压"关系不是一条直线,靠近 0V 和靠近满量程的地方尤其偏。你直接用上面的线性公式换算,误差可能有大几十毫伏。乐鑫的解法是每颗芯片出厂时在 eFuse 里烧了校准数据,你在 ESP-IDF 里用 adc_cali 驱动(配合 adc_oneshot 读原始值)就能把原始读数校正成相对准的毫伏值。要电压准,就走 adc_cali,别信裸公式。

从原始值到电压:换算示例

先搞懂裸换算,再理解为什么要校准。假设 11dB 档、满量程按 3.3V 估:

// 裸换算(快、够用,但不够准)
int raw = /* adc_oneshot 读到的原始值,0~4095 */;
float volt = raw / 4095.0f * 3.3f;   // 估算电压,误差可达几十 mV

// 精确换算(走出厂校准,adc_cali 直接给毫伏)
int voltage_mv = 0;
adc_cali_raw_to_voltage(cali_handle, raw, &voltage_mv);
// voltage_mv 就是校准后的毫伏值,精度好得多

结论很直接:只要"读数大=变亮/变热"这种趋势够用,裸换算随便;要拿电压去算物理量(比如换算电池电压、测量真实电流),一定走 adc_cali

选型与避坑

ADC 读不准,八成不是代码问题,是下面这几个物理坑。

现象 怎么避
参考电压漂移 换个供电、板子发热后读数就变 别依赖 VDD 当基准做精密测量;要准用带内部基准的外部 ADC 芯片
输入阻抗太高 接高阻传感器读数偏低、不稳 采样电容需要电流充满,源阻抗大就充不满;前面加个跟随器(运放缓冲)或降低分压电阻
噪声大、读数跳 每次读都跳好几十 多次采样取平均/中值;模拟走线远离数字高频线;靠近 ADC 脚加个小滤波电容
WiFi 抢占 ADC2 联网时 ADC2 引脚读数异常 ESP32 上 ADC2 与 WiFi 共用,联网时模拟传感器优先接 ADC1 的引脚(查你板子引脚图)
🚧 避坑

ESP32/ESP32-S3 的 ADC 有两条铁律:① 它天生不线性,要电压准必须走 adc_cali 出厂校准,别用裸公式;② ADC2 在 WiFi 开启时不可用——做联网项目,模拟传感器一律接 ADC1 的引脚。另外精密测量别指望它,要准就上外部 ADC 芯片。

想测超过 3.3V 的电压怎么办

ADC 最多量到约 3.3V,可你常要测更高的——比如一节锂电池满电 4.2V、或者一路 12V 电源。直接接进去必然超量程甚至烧脚。办法是先用两个电阻分压:把待测电压按比例缩到 3.3V 以内再送进 ADC,读完在代码里乘回比例。比如测 4.2V,用两个等值电阻分一半,ADC 读到的是实际电压的一半,程序里再乘 2 还原。选电阻别太小(费电、拖累电池),也别太大(源阻抗过高,ADC 采样电容充不满、读数偏低),几十到一两百 kΩ 常见。分压原理和取值见 分压电路

一句话口诀

ADC 干的是"采样—保持—量化"三步,把连续电压归到 0–4095 的某一档。看懂读数只需两个数:分辨率(分几档)和参考电压(满量程几伏),换算就是 读数/4095×参考电压。ESP32-S3 上记牢三件事——选对 atten 量程档、要准走 adc_cali 校准、联网时避开 ADC2

下一步

ADC 把电压读进来,反过来把数字写成电压是 DAC 的活。读进来的信号往往有噪声,怎么滤干净看 噪声与滤波。把这套原理接到真实模拟传感器上跑起来,去 L2 模拟输入实战

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明