本节摘要:物理世界是连续的——温度、光照、声音、电压,而单片机的世界是离散的数字。ADC(模数转换器)负责把连续量翻译成数字,DAC(数模转换器)负责反向输出。本节讲清逐次逼近型 ADC 的工作机理、参考电压的地基作用、分辨率与采样率这对基本矛盾,并用一个 NTC 测温的完整案例演示从电压读数到物理量的换算全过程。模拟外设是四扇门里门槛最高的一扇,也是传感器时代的必修课。
数字引脚只能回答"高还是低",但真实问题往往问的是"多高":电池还剩多少电、电机绕组多热、麦克风拾到的声音多大。ADC 就是回答"多少"的翻译官——把 0 到参考电压之间的连续电压,映射成一个整数(比如 0 到 4095)。DAC 反向作业:给一个整数,输出对应电压,用于音频输出或控制模拟器件。两者合起来,让芯片真正拥有了"听懂物理世界、向物理世界发声"的能力。
主流 MCU 内置的 ADC 几乎都是逐次逼近型(SAR)。它的工作方式像一场二分竞猜:内部产生一个试探电压,先猜满量程的一半,与输入比较;输入高就把猜测区间上移一半,低则下移;每个时钟拍敲定一位,12 位分辨率就猜 12 轮。猜完输出整数结果。
这个机理直接解释了两个工程事实。第一,转换需要时间:逐位猜的过程要走完,12 位转换典型耗时微秒量级——ADC 不是无限快,采样率上限由此而来。第二,转换期间输入必须稳定:竞猜过程中输入电压还在变,猜出来的就是"平均的混乱"。所以信号源阻抗高时,前面要加跟随器或让内部采样保持电路工作足够长时间(采样时间可配置)——这是模拟精度问题的第一大来源。
**参考电压(VREF)**是量程的地基。换算公式恒为:电压 = 读数 / 满量程读数 × 参考电压。参考电压不稳,一切换算都是沙滩上的城堡——芯片供电若直接当参考(多数 MCU 默认如此),电源纹波会一比一混进测量。高精度场合的正确做法是给 VREF 引脚喂独立的基准电压芯片(如 2.5V 基准),或选用带内部基准的型号。测不准先查参考,与"配置不生效先查时钟"并列为模拟外设两大第一反应。
分辨率是尺子的最小刻度。12 位 ADC 把量程切成 4096 格:3.3V 参考下每格约 0.8mV。分辨率高不等于精度高——刻度细但尺子歪(参考偏、噪声大)照样测不准。采样率是尺子的使用速度,每秒能完成多少次转换。分辨率与采样率互相拉扯:同一颗 ADC,常提供"12 位 1Msps"与"16 位降速"多种档位,精度与速度二选一。设计时先问自己要什么:测电池电量,100Hz 采样 12 位绰绰有余;听音频,至少 8kHz 采样率起步。
连续电压 3.3V ──────────────────────────┐ │ 0V ─────────────────────────────────────┘ 分辨率示意(12位):量程切成 4096 格,每格约 0.8mV 读数 2048 → 2048 / 4095 × 3.3V ≈ 1.65V 读数 100 → 100 / 4095 × 3.3V ≈ 0.081V
背景:用一片 NTC 热敏电阻(25 度时标称 10kΩ)给鱼缸水温计测温,要求精度正负 0.5 度。操作分四步。
第一步,搭电路:NTC 与 10k 精密电阻串联接在 3.3V 与地之间,分压中点接 ADC 引脚。NTC 阻值随温度变化,分压点电压随之移动——物理量被转成了电压。
第二步,采电压:连续采 16 次做平均(简单有效的抑制噪声手段),按换算公式得到分压电压,再反解出 NTC 当前阻值:Rntc = R固定 × Vref电压比 / (1 - 电压比),其中电压比等于 ADC 读数除以满量程。
第三步,换温度:NTC 的阻值温度关系是非线性的,工程上常用 B 值公式:1/T = 1/T0 + ln(R/R0)/B,T0 为 298.15K、R0 为 10kΩ、B 值查数据手册(典型 3950)。
/* NTC 测温:采样平均 → 反解阻值 → B 值公式换温度 */ float read_ntc_temp(void) { uint32_t sum = 0; for (int i = 0; i < 16; i++) { /* 16 次平均压噪声 */ sum += adc_read_channel(ADC_CH3); } float raw = sum / 16.0f; float ratio = raw / 4095.0f; /* 电压比 */ float r_ntc = 10000.0f * ratio / (1.0f - ratio); /* 反解 NTC 阻值 */ float inv_t = 1.0f/298.15f + logf(r_ntc/10000.0f)/3950.0f; return 1.0f/inv_t - 273.15f; /* 开尔文转摄氏度 */ }
结果:实测 25 度到 40 度区间与温度计对照,误差在正负 0.3 度内,达标。解读:误差的主要来源按贡献排序是参考电压(供电 3.3V 实际 3.28V,带来约 0.6% 偏差,折合约 0.15 度)、NTC 自身公差(B 值离散,可用两点校准消除大半)、ADC 噪声(16 次平均后已不显著)。变式:若要求提到正负 0.1 度,换 PT100 铂电阻加专用前端,或做软件两点校准(冰水混合物与沸水两个已知点修正偏移)——校准是把器件公差变成系统精度的标准手段。
⚠️ 四个高频坑:其一,读数跳动大——先查参考电压再查布线(模拟走线远离时钟线),最后加平均滤波;其二,高阻抗信号源读数偏低——采样电容在采样瞬间抽走了电荷,加长采样时间或加跟随器;其三,DMA 与 ADC 联用时的数据错位——采样序列与缓冲区长度不匹配,或忘了半字对齐;其四,内部温度传感器直接当体温计用——它测的是芯片结温,比环境温度高好几度,只能做过热保护参考,不能做环境测温。
ADC 讲得多,DAC 常被一句带过,值得给它正名。DAC 的用途分三类。一是产生控制基准:给压控器件(压控振荡器、可调激光驱动、程控电源的给定值)输出一个稳定可调的电压,比 PWM 加滤波的方案纹波小得多、响应也干净。二是波形合成:查正弦表逐点输出,配定时器按固定节奏刷新,就是一台最朴素的信号发生器——采样点之间的台阶可用低通滤波抹平(重建滤波),这与 ADC 侧的抗混叠滤波互为镜像。三是音频输出:早期电子产品里的"滴滴"声与语音提示,多数是 DAC 加一阶滤波的作品。用代码表达其轻巧:
/* 定时器触发下,正弦查表输出:波形合成三行核心 */ const uint16_t sine_table[64] = { /* 预先算好的 64 点正弦 */ }; void timer_interrupt(void) { phase = (phase + 1) & 0x3F; /* 相位循环 */ dac_write(sine_table[phase]); /* 写 DAC 数据寄存器 */ }
片上没有 DAC 的芯片,也可以用 PWM 加多阶 RC 滤波逼出近似效果——代价是纹波与响应速度,选型时(回看 1.4 节的维度表)这就是"要不要为 DAC 多花两块钱"的判断依据。