☰
微机原理接口:ADC0809与DAC0832的逐次逼近、时序与调试
2026/9/29 2:21:13 网站建设 项目流程

做了这么多年微机原理与接口技术的实验带教,A/D 与 D/A 转换器(也就是常说的 ADA 转换器)这一章,翻车率是最高的,没有之一。前面的 8255、8253、8259 实验,只要连线照图插、代码照书敲,基本都能跑出结果;到了 ADC0809 和 DAC0832 这里,同样是照抄,示波器上可能什么都不显示,或者读回来的数据永远钉在一个固定值上,通道切来切去也没变化。问题在于,前面那些实验是纯数字世界内部的事,而 ADA 转换器是数字世界和模拟世界之间唯一的通道——一边讲电平、时序、参考源,另一边讲端口地址、汇编指令、握手信号,任何一侧理解偏了,现象都会表现为"没反应"。

这篇内容面向的是正在做微机接口实验的学生、需要把单片机或工控板上的模拟量接进来的嵌入式新手,以及想重新把 AD/DA 这条链路捋清楚的从业者。我不会只给你一段能跑的代码,而是把"为什么要这么接""这个参数是怎么算出来的""读出来不对时该按什么顺序查"这三件事讲透。代码用 8086 汇编写,硬件按国内实验箱最常见的 ADC0809 + DAC0832 + 8255 + 8253 组合来搭,但里面关于时序、参考电压、地址译码的那些判断逻辑,换个平台照样用。

1. 为什么A/D、D/A转换是微机接口里最"两面派"的一章

1.1 数字的世界与模拟的世界之间横着一堵墙

CPU 只认高低电平,它眼里的一切都是 0 和 1。而现实世界里的温度、压力、光照、声音,全是连续变化的电压或电流。ADC 干的事,是把"连续"切成"离散",用一串二进制数去近似表示某个瞬间的电压值;DAC 干的事正好相反,把一串二进制数还原成一个电压值。这两件事听起来简单,但"近似"两个字里藏着全部的难点:切得够不够细(分辨率)、切得多快(转换速度)、切得准不准(精度)。

我一直觉得理解 ADA 转换器最好的切入点不是看芯片手册,而是先想清楚一件事:转换器本身是个"哑巴",它不会主动告诉你它什么时候在忙、什么时候忙完了。它只有几个引脚:一个告诉你"我开始转换了",一个告诉你"我转完了",一个让你把结果拿走。所以接口设计的核心,其实是设计一套"我问你答"的握手流程。CPU 通过端口地址去敲转换器的门,转换器用状态引脚回话,CPU 判断状态再取数据——这就是接口的全部含义。

把这个逻辑想通了,你会发现硬件接口和软件里的 API 接口是同一套思维:调用方发起请求(写端口)、被调用方返回状态码(读状态引脚)、双方约定好数据格式(几位的并行数据)、还要考虑幂等性(重复读会不会拿到脏数据)。当年学接口定义、引脚定义时觉得枯燥,后来做系统集成才发现,这套"先约定、再握手、后传数据"的模式,几十年没变过。

1.2 我第一次做ADC0809实验时读回来全是0xFF

说个真实的经历。我第一次做 ADC0809 采集实验,接好线、烧完程序,用调试器看内存里存的结果,八个通道读回来全是 FF。当时第一反应是"数据线接反了",拔了重插,还是 FF。又怀疑程序读错了端口,把地址翻来覆去改了一遍,依旧 FF。折腾了快两个小时,最后发现问题出在两个地方:一是我把 EOC 的查询极性判断反了,程序在"转换中"的状态下就直接去读数了,那时候数据总线是浮空的,上拉电阻把它拉成了全 1;二是参考电压的跳线帽没插,VREF 悬空,芯片压根没正常工作。

这两件事后来成了我给学生强调最多的两条:读数全是 FF 或全是 00,八成不是数据线的问题,而是"根本没转换成功"或者"读的时机不对"。数字电路里,浮空输入被上拉成高电平是最常见的假象,它看起来像"数据全 1",实际上意味着没有任何有效驱动。

1.3 先把"接口"这件事想明白

在动手写汇编之前,我建议先把下面这张"角色分工表"在心里过一遍。很多同学一上来就写 IN/OUT 指令,结果连谁控制谁都没搞清楚,代码自然对不上硬件。

角色负责什么对应引脚/信号
CPU发起启动、查询状态、读取结果地址总线、控制总线、数据总线
地址译码器把地址翻译成片选信号74LS138 等,产生 CS
ADC把模拟量转成数字量START、EOC、OE、A/B/C
8255给 CPU 提供并行的输入输出口PA、PB、PC
8253提供转换所需的时钟OUT0

看懂这张表,你就明白为什么实验箱上的线那么多:每一根线都对应一个"谁问谁答"的动作。少接一根 EOC,CPU 就永远不知道转换结束了;少接一根 OE,数据就永远送不到总线上。

2. ADC0809的内核:逐次逼近到底是怎么"猜"出电压的

2.1 用天平称重理解逐次逼近

ADC 有好几种实现方式,最常见的是逐次逼近型(SAR)和双积分型。ADC0809 属于前者。逐次逼近这个名字听着玄,其实就是"二分查找"的硬件版。

打个比方:你有一堆砝码,重量分别是 128g、64g、32g、16g、8g、4g、2g、1g,要称出一个未知物体的重量。你会先把最重的 128g 放上去,如果超了就拿下来,如果不够就留着;然后依次试 64g、32g……每试一次就确定结果的一个二进制位。8 个砝码试完,就得到 8 位二进制结果。

逐次逼近 ADC 内部就是这个逻辑:一个 DAC、一个比较器、一个逐次逼近寄存器(SAR)。启动后,SAR 从最高位开始置 1,内部 DAC 把这个猜测值转成电压,和输入电压比大小——猜大了就把这一位清 0,猜小了就保留。8 位就是比 8 次,转换结束,SAR 里的值就是结果。

理解了这一点,你就能解释很多现象。比如输入电压刚好卡在两个量化级之间时,结果会在这两个值之间跳动,这叫量化抖动,是原理决定的,不是电路的毛病。再比如,转换时间基本固定(ADC0809 大约是 100 微秒,跟时钟频率直接相关),因为无论输入多大,它都要老老实实比 8 次。

2.2 分辨率、量化误差、转换时间三个参数怎么算

这三个参数是选型和调试时绕不开的,而且都能算,不用死记。

分辨率等于参考电压除以 2 的位数次方。ADC0809 是 8 位,如果参考电压是 5V:

1 LSB = 5V / 2^8 = 5V / 256 ≈ 19.53 mV

也就是说,输入电压每变化 19.53mV,输出数字量才动一格。如果实验箱的输入电位器旋钮转了半天数字量才跳一下,别怀疑芯片坏了,是 8 位精度就这水平。

量化误差是量化过程本身带来的误差,最大值为 ±1/2 LSB,也就是 ±9.77mV。它是原理性的,无法消除,只能通过提高位数或降低参考电压来减小。

转换时间在 ADC0809 上由外部时钟决定。芯片手册给的典型时钟是 640kHz,时钟越高转换越快,但不能超上限。转换时间大约等于(8 + 2)个时钟周期(含启动和输出延迟),所以 640kHz 下大约是 100 微秒左右。这就回答了实验里一个常见疑问:"为什么我用查询方式采样,频率上不去?"因为每次采样至少要等 100 微秒,加上查询和存储的开销,一秒钟能采几千次就不错了。想快,就得上并行转换或者流水线结构。

顺便说一句,如果换成 12 位的 AD574,分辨率一下子变成 5V/4096 ≈ 1.22mV,量化误差也缩小到 ±0.61mV,但转换时间、成本和接口复杂度都会上去。位数不是越高越好,够用就行,这个权衡在后面的章节还会展开。

2.3 引脚分工与地址译码的配合

ADC0809 有 28 个引脚,但真正跟接口相关的不多,我按功能分成四组:电源组(VCC、GND、VREF+、VREF-)、模拟输入组(IN0 到 IN7 加 A、B、C 三个通道选择)、数字输出组(D0 到 D7,带三态输出缓冲)、控制组(START、ALE、EOC、OE、CLK)。

  • A、B、C:三位通道选择码,000 选 IN0,111 选 IN7,一共 8 个通道。注意它们不只是选择,还要配合 ALE 锁存。
  • ALE:地址锁存允许。上升沿把 A/B/C 锁进芯片。
  • START:启动转换。上升沿复位内部寄存器,下降沿正式开始转换。
  • EOC:转换结束标志。转换中为低,转换完成后变高——注意这个是高有效,判断错极性是初学者最大的坑。
  • OE:输出允许。给高电平,D0-D7 才把结果送到总线上。

工程里最优雅的做法是把 ALE 和 START 并在一起,由地址译码信号触发。这样 CPU 只要往对应端口执行一次写操作,硬件就自动完成"选中通道 + 锁存 + 启动"三个动作,软件上干净利落。具体做法是:把地址总线的 A0、A1、A2 直接接到 ADC 的 A、B、C,把高位地址译码后的信号接到 ALE 和 START。这样一来,写 0E0H 就是采 IN0,写 0E1H 就是采 IN1,通道和地址一一对应,代码可读性极高。

很多教材采用另一种接法:用 8255 的 PC0 到 PC2 输出通道号,PC3 发 START,PC7 读 EOC。这种接法更"手工",每一步都要软件干预,调试时反而容易出错。两种都能用,但我推荐前者——能用硬件干的事别交给软件,这是接口设计里一条通用的原则。

3. DAC0832:从R-2R梯形网络到三种缓冲模式

3.1 权电阻网络为什么被淘汰

DAC 的核心任务是把二进制数变成电流或电压。最直观的实现是权电阻网络:每一位对应一个电阻,电阻值和这一位的权重成反比,最高位电阻最小、电流最大,所有支路电流加起来就是输出。8 位就需要 8 个阻值跨度 128 倍的电阻。

听起来挺美,实际做不出来。8 位要求电阻比值达到 128:1,12 位就是 2048:1,在集成电路里根本没法保证精度——电阻的相对误差直接就变成了非线性误差。所以工程上几乎全用R-2R 梯形电阻网络:整个网络只用两种阻值的电阻,R 和 2R,靠支路的串并联关系产生二进制权重。

R-2R 的另一个好处是输出电流与数字量成正比,关系非常干净:

Iout = VREF × D / 256

其中 D 是 8 位数字量(0 到 255)。这个公式是理解 DAC0832 的钥匙,后面算输出电压全靠它。

3.2 直通、单缓冲、双缓冲的选择逻辑

DAC0832 内部有两个数据寄存器:输入寄存器(受 WR1 和 CS 控制)和 DAC 寄存器(受 WR2 和 XFER 控制),还有一个 ILE 信号。这两个寄存器怎么用,决定了它的三种工作方式。

直通方式:WR1、WR2 都接地,ILE 接高电平,两个寄存器相当于透明,数据一到就转换。这种方式最简单,适合用 8255 的某个口直接输出数据、实时更新波形的场景。缺点是 CPU 每次输出都会立刻改变模拟输出,中间过程可能产生毛刺。

单缓冲方式:只让一个寄存器起作用,另一个直通。数据先被一级锁存,等 CPU 给个信号再更新输出。比直通稍微"干净"一点。

双缓冲方式:两个寄存器都用上,数据先存进输入寄存器,再由 XFER 信号搬到 DAC 寄存器。这种方式的价值在于可以同时更新多路 DAC 的输出。比如你要做多通道波束合成或者 XY 示波器图形,两路 DAC 的输出必须严格同步,如果每路单独直通更新,中间会有时间差,图形就会歪。双缓冲让两路都先把数据准备好,然后一个信号同时"放行",输出就完全同步了。

工作方式WR1/WR2 接法典型用途同步性
直通都接地单路简单波形输出无同步概念
单缓冲一级有效一级直通单路、需要一点缓冲一般
双缓冲两级独立控制多路同步输出严格同步

在接口实验里,如果只是让 DAC 输出一个锯齿波或正弦波给示波器看,直通或单缓冲就够了。但如果你想做"两路 DAC 画一个圆",那必须上双缓冲,否则调出来的图形永远是个歪椭圆。

3.3 数字量到电压的换算与运放接法

DAC0832 输出的是电流(IOUT1 和 IOUT2),而且电流方向是从引脚往外流(因为内部是电流沉结构),所以必须接一个运放做 I/V 转换。标准接法是反相放大器:IOUT1 接运放反相输入端,IOUT2 接同相输入端并接地,反馈电阻 RFB 接在输出和反相输入端之间。

这样输出电压就是:

Vout = -VREF × D / 256

如果 VREF 接 +5V,D=0 时输出 0V,D=255 时输出约 -4.98V,输出是负的。想要正电压输出,要么把 VREF 接负电压,要么在运放后面再加一级反相。

这个负号很有迷惑性。我见过不少人接完电路发现示波器上波形在零位以下,第一反应是"运放坏了",其实是反相放大的必然结果。记住一句话:DAC0832 配反相运放,输入正参考电压,输出就是负电压。想都不用想。

另外还有一个细节:DAC0832 的参考电压范围是 ±10V 到 ±25V(VREF 引脚上是 -10V 到 +10V 之间都可以),但为了和 ADC 的参考电压一致、方便做回环实验(DAC 输出接 ADC 输入),通常统一取 5V。

4. 汇编层驱动:查询与中断两条路线的完整代码

4.1 硬件连线与端口地址分配表

先把地址分配定下来,后面所有代码都基于这套地址。这套地址在国内实验箱上很常见,你根据自己板子的译码电路改一下即可。

器件端口地址用途
8255 PA060H输入,接 ADC 数据线 D0-D7
8255 PB061H输出,接 DAC 数据线 DI0-DI7
8255 PC062HPC7 输入接 EOC,其余备用
8255 控制口063H写方式控制字
8253 通道0040H分频产生 ADC 时钟
8253 控制口043H写方式控制字
ADC08090E0H - 0E7H通道 0 - 7,写操作触发 ALE+START
DAC08320F0H写操作更新输出

ADC 那边走的是"地址即通道"的接法:地址低三位 A0-A2 接 ADC 的 A、B、C,译码信号接 ALE 和 START,读信号接 OE,EOC 接 8255 的 PC7。这样一次 OUT 就完成了通道选择和启动,一次 IN 就完成了数据读取。

4.2 8255与8253的初始化

8255 的方式控制字要这么定:A 口方式 0 输入(接收 ADC 数据),B 口方式 0 输出(送给 DAC),C 口高四位输入(PC7 读 EOC),C 口低四位输出(备用)。

把控制字的位拆开算:D7=1(方式设置标志),D6D5=00(A 组方式 0),D4=1(A 口输入),D3=1(C 口高四位输入),D2=0(B 组方式 0),D1=0(B 口输出),D0=0(C 口低四位输出)。拼起来就是10011000B,也就是98H。

8253 用来给 ADC 提供时钟。假设实验箱给的是 2MHz 时钟源,ADC0809 希望工作在 500kHz 左右,那么计数初值 = 2MHz / 500kHz = 4。通道 0 用方式 3(方波发生器)最合适,因为方波占空比接近 50%,适合做转换时钟。

控制字同样拆位:SC1SC0=00(选通道 0),RL1RL0=11(先读写低字节再读写高字节),M2M1M0=011(方式 3),BCD=0(二进制计数)。拼起来是00110110B,即36H。

; ============================================= ; 初始化:8255 + 8253 ; 8255: PA输入, PB输出, PC高四位输入, PC低四位输出 ; 8253: 通道0 方式3, 计数初值4 (2MHz -> 500kHz) ; ============================================= INIT_IO PROC ; ---- 8255 初始化 ---- MOV DX, 063H MOV AL, 98H OUT DX, AL ; ---- 8253 通道0 初始化 ---- MOV DX, 043H MOV AL, 36H ; 通道0, 方式3, 二进制 OUT DX, AL MOV DX, 040H MOV AL, 04H ; 低字节 = 4 OUT DX, AL MOV AL, 00H ; 高字节 = 0 OUT DX, AL ; 8255 控制字已经写了, PC0 复位一下避免残留电平 MOV DX, 062H MOV AL, 00H OUT DX, AL RET INIT_IO ENDP

注意 8253 写计数值的顺序:控制字里 RL1RL0=11 表示 16 位读写,先低字节后高字节,这个顺序绝对不能颠倒。颠倒了的后果是计数器初值变成 1024,时钟频率直接掉到 2kHz,ADC 转换慢得像蜗牛,你还以为是程序卡死了。

4.3 查询EOC采集8个通道

查询方式的核心是先启动、再等 EOC 变高、最后读数据。这个顺序一步都不能乱。很多人写反了,先读再启动,读回来的自然是没有意义的残值。

; ============================================= ; 采集指定通道, 结果返回在 AL ; 入口: BL = 通道号 (0-7) ; ============================================= SAMPLE PROC PUSH DX PUSH BX ; ---- 第一步: 启动转换 ---- MOV AL, BL MOV DX, 0E0H ADD DX, AX ; 0E0H + 通道号 OUT DX, AL ; 写操作产生 ALE + START ; ---- 第二步: 等待 EOC 变高 ---- WAIT_HERE: MOV DX, 062H IN AL, DX TEST AL, 80H ; 检查 PC7, 即 EOC JZ WAIT_HERE ; EOC=0 表示还在转换, 继续等 ; ---- 第三步: 读取转换结果 ---- MOV DX, 0E0H ADD DX, BX IN AL, DX ; 数据经 8255 PA 送回 POP BX POP DX RET SAMPLE ENDP

这段代码有三个细节值得说。

第一,OUT DX, AL里的 AL 值其实无所谓,因为启动信号是由写操作产生的,数据线上是什么不影响。这一点和很多人的直觉相反——他们以为写进去的是通道号,实际上通道号已经由地址线决定了,数据总线在这里只是"陪跑"。

第二,TEST AL, 80H之后用JZ而不是JNZ,原因就是前面说的 EOC 高有效。EOC 为低说明还在转换,所以要一直循环等待。如果你板子上的 EOC 接的是反相后的信号,这里就得改成JNZ。这是最容易错的地方,务必拿万用表量一下 EOC 在转换前后的电平。

第三,这个查询循环是"死等",期间 CPU 什么也干不了。8 通道依次采集一遍大约需要 8×100μs = 0.8ms,这段时间 CPU 完全被占用。如果系统里还有别的事要干,就得改用中断方式。

中断方式的做法是把 EOC 经过 8259 接到 CPU 的 INTR,转换结束时触发中断,中断服务程序里读数据。这样 CPU 在等待期间可以做别的事情。不过中断方式在实验里调试起来麻烦得多,涉及 8259 的初始化、中断向量表填写、中断结束命令,稍微错一处就进不了中断。我的建议是先把查询方式调通,确认硬件链路没问题,再换中断方式,否则你分不清是硬件问题还是中断配置问题。

4.4 用DAC输出锯齿波与正弦波

DAC 这边就简单多了,写一次端口就更新一次输出。先看锯齿波:

; ============================================= ; 输出锯齿波, 幅度 0 - 255 ; ============================================= SAWTOOTH PROC NEXT_STEP: MOV DX, 0F0H OUT DX, AL ; 输出当前值 INC AL ; 累加 ; ---- 软件延时, 控制输出频率 ---- PUSH CX MOV CX, 30 DLY: LOOP DLY POP CX JMP NEXT_STEP SAWTOOTH ENDP

频率可以算出来:256 个台阶,每个台阶的耗时包括一次 OUT、一次 INC、一次压栈出栈加 30 次 LOOP。8086 上 LOOP 指令大约 17 个时钟周期,30 次就是 510 个周期,加上其他指令算 560 个周期。如果 CPU 主频 5MHz,每个台阶约 112μs,256 个台阶就是一个完整周期,大约 28.7ms,对应频率约35Hz。示波器上应该能看到一个还不错的锯齿波。想调频率,就改延时循环的计数初值,非常简单。

如果要输出正弦波,就不能靠累加了,得预先算好一张 256 点的正弦表存在内存里,然后依次查表输出:

; ============================================= ; 查表输出正弦波 ; SINE_TAB 为 256 字节的正弦采样表 ; ============================================= SINE_OUT PROC LEA SI, SINE_TAB MOV CX, 256 PLAY: MOV AL, [SI] MOV DX, 0F0H OUT DX, AL INC SI PUSH CX MOV CX, 20 DLY2: LOOP DLY2 POP CX LOOP PLAY RET SINE_OUT ENDP

正弦表怎么生成?用 Python 或 Excel 算一遍就行,公式是D = 128 + 127 × sin(2πi/256),i 从 0 到 255,取整后写成一串 DB 数据。注意要把结果限制在 0 到 255 之间,负数和高位溢出会让波形顶部被削平。

这里有个实测经验:查表输出的正弦波频率不能太高。因为 CPU 是一条一条指令搬数据的,256 个点搬完要花不少时间,加上延时,能做到几百赫兹就不错了。想要高频正弦波,得靠硬件 DDS 或者专用波形发生器芯片,靠软件搬数据是有物理上限的。

5. 上电没反应?按这个顺序往下查

5.1 先看参考电压和转换时钟

这是我排查 ADA 电路的第一站,没有例外。因为这两样东西一旦出问题,所有现象都是"什么都读不到"。

参考电压怎么查:万用表量 VREF+ 和 VREF- 之间的电压,ADC0809 应该是 5V 左右(或你设计的值)。如果量出来是 0V 或者飘忽不定,检查跳线帽、检查供电。VREF 悬空的时候,芯片内部的比较器没有基准,逐次逼近的结果完全随机,表现出来就是读回来的数在乱跳。

转换时钟怎么查:用示波器量 ADC0809 的 CLK 引脚,应该有稳定的方波。如果没有波形,先量 8253 的 OUT0 有没有输出,再量 8253 的 CLK0 有没有输入。我遇到过一种情况:8253 的时钟源引脚接的是实验箱上一个没使能的输出,示波器上看是一条直线,折腾了半天才发现是跳线问题。时钟这条链路要一节一节往前量,别跳步。

如果时钟是有的但频率明显不对,八成是 8253 的计数初值写错了,回头检查高低字节的写入顺序。

5.2 EOC极性判断错导致的死循环

现象是程序卡在查询循环里出不来,或者读回来的数据是固定值。这两个现象的根因其实是同一个:EOC 的状态判断错了。

正确做法是拿示波器同时看 START 和 EOC 两个信号。启动时应该能看到 START 上一个负脉冲,随后 EOC 从高变低,保持大约 100μs,然后回到高。如果你的 EOC 是反的(比如中间加了反相器),那查询条件就得反过来写。

还有一种情况是 EOC 一直保持高,程序一次都不等就往下走,读回来的是上一次的残值或者浮空值。这说明转换根本没启动,往两个方向查:一是 START 信号有没有真正产生(示波器量),二是 ALE 有没有正常锁存通道号。

一个实用的小技巧:在查询循环里加一个计数上限,超时就跳出并置一个错误标志。这样程序不会永远卡死,你能从错误标志判断是"转换没完成"还是别的问题,比盯着一个卡住的调试器强得多。

5.3 数据总线冲突与地址译码的重叠

ADC 的三态输出、8255 的输出、DAC 的输入,都挂在同一条数据总线上。如果它们的片选信号在地址上重叠了,就会出现"两个器件同时说话",总线电平被拉成一个中间值,读回来什么都有可能。

典型的错误是地址空间划分得太紧。比如 ADC 占了 0E0H 到 0E7H,而 8255 用的是 060H 到 063H,这本来不冲突。但如果地址译码器只接了部分高位地址线,实际会把多个地址段映射到同一个器件上,看上去不冲突的地址其实重叠了。

排查方法很土但很有效:把可疑地址逐个写数据,用示波器看对应的片选引脚会不会动作。如果两个不同地址都让同一个芯片的 CS 变低,那就是地址译码有重叠。解决方法是把地址线接全,或者用 74LS688 之类的比较器做更精确的译码。

另外,ADC 的三态输出必须由 OE 控制,OE 不有效时它是高阻态,这时候数据总线该由别的器件驱动或者由上拉电阻保持。如果上拉电阻没焊,总线浮空,读回来就是随机的,而且每次还不一样。

5.4 几个"看着没问题"的隐性坑

下面这些坑的共同特点是:电路图上看不出问题,但实际就是不工作。

第一个坑:8255 的口在不同实验箱上地址基准不一样。有的板子 PA 是 060H,有的是 280H,有的是 300H。写代码前一定要对着板子上的丝印或者手册确认,别照抄别人的地址。

第二个坑:ADC0809 的模拟输入悬空。空着的输入通道会感应周围的电场,读回来的值随机跳动。如果只用一个通道,其他不用的通道最好接地或者接一个固定电压。

第三个坑:DAC 运放的供电。运放需要正负双电源,如果只给了正电源,负半周输出会被削平,波形看起来像是被切了一刀。这个现象在示波器上非常明显,看到削顶先量运放供电。

第四个坑:输入信号超出量程。ADC0809 的输入范围是 0 到 VREF,如果输入信号带了负电压,芯片内部的保护二极管会导通,轻则读数错乱,重则烧芯片。加一个限流电阻和钳位二极管是最稳妥的做法。

6. 从实验箱走向工程:精度、速度与成本的三角

6.1 软件过采样与均值滤波

实验里采一个值就够了,真实产品里一个值根本不能用——噪声会让相邻两次采样差出好几个 LSB。最经济的解决办法是软件滤波,不需要改硬件。

最简单的是算术平均:连续采 16 次,求和后右移 4 位。这个操作在汇编里就是 16 次读加一个移位,开销很小,但能把随机噪声压下去大约 4 倍(噪声按 √N 衰减)。代价是等效采样率降到原来的 1/16。

再进一步是过采样加抽取。如果你的 ADC 是 12 位,但系统只需要 10 位有效数据,那就可以用 16 倍过采样加平均,理论上能把有效位数提高 2 位,等效于用 12 位的硬件做出了 14 位的分辨率。这个技巧在低成本的单片机上特别实用,原理是白噪声在平均过程中会互相抵消,而真实信号不会。

需要注意的是,过采样只对随机噪声有效。如果噪声是周期性的干扰(比如电源纹波),平均多少次都消不掉,反而可能因为采样时刻固定而把干扰固化下来。这种情况要么在硬件上滤掉,要么让采样时刻随机抖动一下。

滤完波记得做校准。至少做两点校准:输入端接 0V 采一组,接满量程采一组,算出增益和偏移,写进程序里做线性修正。这一步能把芯片本身的偏移误差和增益误差消掉大半,成本几乎为零。

6.2 参考源与地平面的处理

很多人以为精度完全取决于 ADC 的位数,其实参考电压的质量才是天花板。ADC 的转换结果本质上是在和参考电压比较,参考电压如果本身在抖,结果再好也好不到哪去。

实验箱上用的大多是普通的稳压源,纹波在毫伏级。换到 12 位以上的应用,这个纹波就已经超过 1 个 LSB 了。工程上的做法是给参考源单独供电、单独布线、加上大电容和小电容并联去耦。所谓分开数字地和模拟地,也是同一个道理:数字电路的开关噪声会通过地线耦合到模拟部分,把最后几位的精度吃掉。

具体做法是在 PCB 上把地分成数字地和模拟地两块,中间只在一个点用磁珠或 0 欧电阻连起来。ADC 的 AGND 接模拟地,DGND 接数字地。这个细节在小信号采集里是决定性的,实验阶段感受不到,一旦上到工业现场,不做这个分离就会出各种莫名其妙的漂移。

还有一个常被忽略的点:ADC 的输入阻抗。逐次逼近型 ADC 的输入是一个开关电容网络,采样瞬间会从信号源抽一小股电流。如果信号源的内阻太大,这个电流会让输入电压瞬间塌下去,采到的是错的。解决办法是在前面加一个电压跟随器(运放缓冲),把高阻信号变成低阻驱动。

6.3 采样保持器什么时候必须加

采样保持器(S/H)的作用是在转换期间"冻结"输入电压。逐次逼近 ADC 需要大约 100μs 来完成 8 次比较,如果在这段时间里输入电压一直在变,那就相当于每次比较用的都是不同的值,结果是错的。

什么时候必须加?判断标准是看输入信号在转换时间内变化了多少。如果变化量超过 1/2 LSB,误差就不可接受了。

举个例子:一个 8 位 ADC,参考 5V,1 LSB 是 19.53mV,转换时间 100μs。假设输入是正弦波,允许的最大变化率是 19.53mV / 100μs ≈ 195 V/s。对一个幅度 5V 的正弦波,最大斜率是 2πfA,代入 5V 幅度,解出 f ≈ 6.2Hz。也就是说,输入信号频率超过 6Hz,理论上就需要采样保持器了。这个数字第一次算出来的时候挺吓人的,但这就是 8 位、100μs 的硬件限制。实际使用中因为正弦波大部分时间斜率较小,可以放宽到几十赫兹。

所以后来的 ADC 芯片(比如 ADC0804 的后续型号)大多把采样保持电路集成进去了,用起来省心。但如果你手上是老的 ADC0809,采动态信号时一定要在前面加 S/H,否则波形失真会非常明显。

顺带说个接口设计上的联想法。软件里我们也会遇到类似的问题:一个请求发出去了,被调用方处理需要时间,这段时间里如果请求参数被别的线程改了,结果就乱了。解决思路和采样保持器一模一样——在进入临界区之前把数据"快照"一份。硬件的采样保持和软件的快照隔离,本质上是同一个问题的两种表述,这也是为什么我一直觉得接口这门课值得反复看:它教的不是某个芯片,而是一整套处理"异步协作"的方法论。

如果你手上正好在做这个实验,我的建议是别急着抄代码。先拿万用表把 VREF、CLK、EOC 三个点量一遍,确认硬件链路是活的;再写一个最简单的单通道采集,把原始值打到数码管上,转动电位器看数值有没有跟着变。这一步通了,剩下的多通道扫描、波形输出、滤波校准都是在这个基础上加东西。我见过太多人一上来就想做完整的采集加显示加通信,结果卡在最基础的时序上,越调越乱。

另外补一句,不同实验箱的接线和地址差异很大,本文的代码是基于"地址即通道"这种最干净的接法写的。如果你的板子是 8255 的 PC 口控制 START 和通道选择,那 SAMPLE 过程里的启动部分要改成先输出通道号到 PC0-PC2,再从 PC3 打一个启动脉冲,等待和读取部分不用动。改的时候记得保持"启动—等待—读取"这个三段式结构不变,顺序错了什么都白搭。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询