资讯动态

51单片机AD/DA实战:ADC0809/DAC0832选型接线与精度排错

发布时间:2026/9/29 7:07:07 来源:尧图企业网站定制
1. 先把转换这件事想明白51 单片机为什么总要外挂 AD/DA1.1 模拟量和数字量之间那道过不去的坎51 单片机的内核是彻头彻尾的数字电路它认的只有 0 和 1P0 到 P3 口读进来的电平要么被判定为高要么被判定为低中间那一片连续变化的电压对它来说是不存在的。可现实世界里温度、光照、压力、电位器的位置、电池电压几乎全是连续变化的模拟量。这就产生了一个刚性的缺口AD 模数转换与 DA 数模转换是 51 单片机从数字世界伸向模拟世界的两只手。AD 负责把模拟电压翻译成 0 到 255 这样的数字码DA 反过来把数字码还原成电压。搞嵌入式的人第一次接触这两个东西大多是在做电压表、做数控电源、做波形发生器、做光控调光这类题目的时候。我这些年带过不少学生做课程设计和毕业设计发现一个普遍现象代码能抄到接线也照着图连了可输出就是不准要么读数老是跳要么 DA 出来的波形像锯齿上长了毛。问题基本都不在代码本身而在对转换原理、基准电压、时序约束、地线处理这几件事的理解上。所以这篇内容不打算只给你一段能跑的代码而是把选型、接线、时序、换算、标定、排错这条链路整条讲透让刚上手的人能照着做出来也让已经做过一轮的人找到自己那块板子到底差在哪。1.2 没有 ADC 的 51 内核靠什么补上这一环经典的 8051 内核包括 AT89C51、AT89C52 这些内部是没有 ADC 的也没有真正意义上的 DAC。这是历史包袱当年的设计定位就是控制型 MCU做开关量逻辑和定时计数。所以传统方案必须外挂芯片最经典的三件套就是ADC0809、ADC0832、DAC0832教材和实验箱上基本都是这几个型号。它们的共同特点是便宜、资料多、时序简单缺点是分立器件多占板面积大抗干扰要靠自己设计。另一条路是换芯片。STC 的很多型号比如 STC12C5A60S2、STC15W 系列、STC8 系列内部直接集成了 8 位或 10 位逐次逼近型 ADC有的还带 PWM 可以直接当 DAC 用。我在实际项目里如果不是要去兼容老实验箱基本都会优先选这类带 ADC 的型号省掉一颗芯片、省掉一堆走线稳定性还更好。但这并不意味着外挂 ADC 就没用了——多通道同时采样、需要更高精度、需要隔离测量的时候外挂芯片依然是正解。选择的核心判断标准是精度要求、通道数量、采样速度、成本、开发周期这五个里哪个是硬约束。2. 器件选型AD 和 DA 的几条主流路线怎么挑2.1 ADC0809、ADC0832、TLC549 与内部 ADC 的横向对比选 ADC 之前先确定三件事要几路输入、要多快、要多准。ADC0809 是 8 位、8 通道、逐次逼近型并行输出转换时间大约 100 微秒需要外部提供 10kHz 到 1280kHz 的时钟。它的好处是通道多、并行接口读起来简单缺点是引脚多28 脚、必须给时钟、体积大。ADC0832 是 8 位、双通道、串行输出只有 8 个引脚接线极简代价是读取速度慢需要软件模拟时序一位一位地挪。TLC549 是 8 位单通道串行速度和 ADC0832 接近接口更简单但没有多通道。下面这张表是我自己选型时常用的对照参数按常见模块的典型值给型号分辨率通道数接口方式典型转换时间是否需要外部时钟适合场景ADC08098 位8并行约 100us需要10k~1280kHz多路采集、教学实验ADC08328 位2串行约 32us不需要引脚紧张的小板子TLC5498 位1串行约 17us不需要单点电压监测STC 内部 ADC8/10 位最多 8寄存器由设置决定不需要新设计首选PCF85918 位4 路 AD 1 路 DAI2C约 100us不需要需要 AD 和 DA 一起用我个人的经验是做课程设计、要交实验报告用 ADC0809 最稳妥因为它的时序在教材里写得最清楚老师也认做实际的小产品、板子空间紧张优先 ADC0832 或者干脆用带内部 ADC 的 STC如果同一个系统里既要有输入采样又要有模拟输出PCF8591 一颗就够虽然精度一般但省事。2.2 DAC0832、PCF8591 和 PWM 模拟 DAC 的取舍DA 这一侧的选择逻辑和 AD 类似。DAC0832 是 8 位、电流输出型、并行接口建立时间只有 1 微秒速度非常快做波形发生器绰绰有余。它内部有两级锁存器可以组成直通、单缓冲、双缓冲三种工作方式这个设计当年是为了配合多路同步输出现在大部分场合用单缓冲甚至直通就够了。注意它是电流输出必须外接运放做 I/V 转换才能得到电压这一点新手最容易忽略直接把电流输出当电压用结果读到的值完全不对。PCF8591 是 I2C 接口的 8 位 AD/DA 二合一输出是电压型直接接出来就是 0 到 Vref 的电压不用运放接线极其简单。它的短板是 I2C 速率有限输出更新率不高做慢速设定值输出没问题做音频波形就力不从心。第三条路是用PWM 加 RC 低通滤波来模拟 DAC这是很多低成本方案的常用做法因为 51 的定时器本来就能输出 PWM硬件成本几乎为零。它的原理是把占空比当作数字量滤波后得到平均电压。但这条路有两个硬伤纹波和响应速度是一对矛盾滤波越狠响应越慢而且 51 的定时器位数有限8 位分辨率下 PWM 频率不高滤波电容要取得很大实际做到的精度和稳定性都不如专用 DAC 芯片。我做慢速调光、设定基准这类场景会用 PWM 法做需要波形质量的就老老实实上 DAC0832。3. 硬件落地接线、基准、时钟和地线怎么处理3.1 ADC0809 最小系统接线与时钟来源ADC0809 的引脚分几组IN0 到 IN7 是八路模拟输入A、B、C 是通道地址选择ALE 是地址锁存允许START 是启动转换EOC 是转换结束标志OE 是输出允许CLK 是时钟输入D0 到 D7 是数据输出Vref() 和 Vref(-) 是基准电压VCC 和 GND 供电。接线时有几个点必须说清楚。第一ALE 和 START 在很多模块上被短接在一起这时候 ST 的上升沿负责锁存地址下降沿负责启动转换代码里必须保证先设好地址再给脉冲。第二EOC 是转换结束信号转换期间为低转换完变高用查询方式读的时候要等它变高但这里有个坑——ST 下降沿之后EOC 并不是立刻变低中间有 1 到 8 个时钟周期的延迟。如果你在拉低 ST 后马上写while(EOC0)很可能因为 EOC 还是高电平而直接跳出循环读到一个没转换完的旧数据。稳妥的做法是拉低 ST 后先延时几十微秒再去查询 EOC。第三时钟从哪来。ADC0809 的典型工作时钟是 640kHz教材里最经典的做法是拿单片机的 ALE 输出经过 74LS74 二分频得到。6MHz 晶振下ALE 输出约 1MHz二分频就是 500kHz正好落在可用范围内。如果手头没有 74LS74也可以用定时器中断翻转一个 IO 口来产生时钟代码示例如下#include reg52.h sbit ADC_CLK P3^3; void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x02; /* 定时器0模式28位自动重装 */ TH0 0xFF; /* 12MHz 晶振下约 1us 溢出一次 */ TL0 0xFF; ET0 1; EA 1; TR0 1; } void Timer0_ISR(void) interrupt 1 { ADC_CLK ~ADC_CLK; /* 中断开销叠加后实际输出约 200~250kHz */ }这里要提醒一句12MHz 晶振下机器周期是 1 微秒中断响应加现场保护再加翻转 IO实际耗时远不止 1 微秒所以最终输出的时钟频率会比理论值低不少。实测大概在 200 到 250kHz 之间对 ADC0809 来说完全够用它最高能到 1280kHz所以不用纠结理论值对不对。如果实在不想折腾时钟直接用带内部 ADC 的 STC 型号这一整块电路都能省掉。3.2 DAC0832 的三种锁存方式与运放输出级DAC0832 内部有两级 8 位锁存器第一级由 CS 和 WR1 控制第二级由 XFER 和 WR2 控制ILE 是输入锁存允许。三种接法分别是直通方式CS、WR1、WR2、XFER 全部接地ILE 接高电平数据线上给什么输出端就跟着变。接线最简单适合单路输出。单缓冲方式一级直通另一级由单片机控制锁存。适合需要输出保持、避免数据线抖动影响输出的场合。双缓冲方式两级都由单片机控制可以做到多片 DAC 同步更新。做多路同步波形输出时才需要。绝大多数实验和小项目用直通或者单缓冲就够了。我用得最多的是单缓冲代码长这样#include reg52.h #define DAC_PORT P0 sbit DAC_CS P2^0; sbit DAC_WR1 P2^1; void DAC_Write(unsigned char code) { DAC_PORT code; DAC_CS 0; DAC_WR1 0; /* 第一级锁存打开 */ DAC_WR1 1; /* 上升沿锁存数据 */ DAC_CS 1; }输出级必须接运放。DAC0832 的 IOUT1 和 IOUT2 是两路互补电流IOUT1 IOUT2 等于常量。标准接法是 IOUT1 接运放反相端IOUT2 接同相端并接地或接参考反馈电阻 RFB 接在输出和反相端之间。单极性输出时Vout -Vref × D / 256其中 D 是 0 到 255 的数字码。注意这个负号。如果 Vref 接 5V那么 D0 时输出 0VD255 时输出约 -4.98V是一段负电压。想要 0 到 5V 的正输出有两个办法一是把 Vref 接负电压比如 -5V二是后面再跟一级反相器。另一个小细节是分母用 256 还是 255严格按二进制满量程算D255 对应的是 255/256 而不是满量程所以按 256 算更接近实际但很多教材写 255。我在标定的时候一律用实测两点拟合不纠结这个理论值。4. 软件实操时序、换算、滤波与输出4.1 ADC0809 的查询式驱动与电压换算把时序捋顺之后ADC0809 的驱动代码其实很短。整个流程是选通道、给启动脉冲、等转换结束、开输出允许、读数据、关输出允许。#include reg52.h #include intrins.h #define ADC_PORT P1 sbit ST P3^0; /* 与 ALE 短接 */ sbit OE P3^1; sbit EOC P3^2; sbit ADDA P3^4; sbit ADDB P3^5; sbit ADDC P3^6; unsigned char ADC_Read(unsigned char ch) { unsigned char val; ADDA (ch 0x01) ? 1 : 0; ADDB (ch 0x02) ? 1 : 0; ADDC (ch 0x04) ? 1 : 0; ST 0; _nop_(); ST 1; /* 上升沿锁存通道地址 */ _nop_(); _nop_(); ST 0; /* 下降沿启动转换 */ /* 关键延时等 EOC 真正拉低避免误判 */ { unsigned int i; for(i 0; i 30; i) _nop_(); } while(EOC 0); /* 等待转换完成 */ OE 1; val ADC_PORT; OE 0; return val; }换算成电压假设基准是 5.000V8 位分辨率下分辨率 5000mV / 256 ≈ 19.53mV也就是说理论上相邻两个码之间差 19.5 毫伏比这个更小的电压变化ADC 是分辨不出来的。这就是为什么用 8 位 ADC 做电压表最后一位总是在跳——它本身就只有这个精度。实际换算时我建议不要用理论公式硬算而是做两点标定输入一个准确的 0V记录读数 code0输入一个准确的高电压比如用精密基准源给 4.500V记录读数 code1。然后线性插值V (code - code0) × Vref_actual / (code1 - code0)这样能把芯片的零点偏移和增益误差一起修掉实测精度能提高不少。读数抖动的问题靠软件滤波解决。我常用的组合是连续采 8 次去掉最大最小值再取平均或者直接做 16 次滑动平均。前者对付突发干扰比如继电器动作效果好后者对随机噪声更平滑。采样间隔建议给到几百微秒以上因为 ADC0809 本身转换就要 100 微秒连续快速采样只会拿到同一批数据。4.2 DAC0832 输出直流与波形以及 PWM 滤波法用 DAC0832 输出一个固定电压很简单调用前面那个写函数就行。做波形发生器的话思路是先在内存里建一个正弦表然后定时中断里依次把表里的值送出去。#include reg52.h #define DAC_PORT P0 sbit DAC_CS P2^0; sbit DAC_WR1 P2^1; /* 64 点正弦表幅值 0~255中点在 128 */ unsigned char code sine_tab[64] { 128,140,152,165,176,188,198,208, 218,226,234,240,245,250,253,255, 255,255,253,250,245,240,234,226, 218,208,198,188,176,165,152,140, 128,115,103,90,79,67,57,47, 37,29,21,15,10,5,2,0, 0,0,2,5,10,15,21,29, 37,47,57,67,79,90,103,115 }; unsigned char idx 0; void Timer1_Init(void) { TMOD 0x0F; TMOD | 0x10; /* 定时器1模式116位 */ TH1 0xFE; TL1 0x0C; /* 12MHz 下约 500us 中断一次 */ ET1 1; EA 1; TR1 1; } void Timer1_ISR(void) interrupt 3 { TH1 0xFE; TL1 0x0C; DAC_PORT sine_tab[idx]; DAC_CS 0; DAC_WR1 0; DAC_WR1 1; DAC_CS 1; idx; if(idx 64) idx 0; } void main(void) { Timer1_Init(); while(1); }这个例子里64 点表、每点 500 微秒一个完整波形周期是 32 毫秒也就是大约 31Hz。想提高输出频率就缩短中断周期或者减少表的点数但点数太少波形会明显变形。实际调试的时候我用示波器看输出第一个要确认的是波形有没有明显台阶感第二个要确认幅值是否达到预期第三个要确认有没有高频毛刺。有台阶感就加点数幅值不对就查运放级有毛刺通常是地线或者电源的问题。用 PWM 滤波模拟 DAC 的时候关键参数要算一下。假设 PWM 频率是 30kHz用 8 位分辨率RC 滤波的截止频率要远低于 30kHz一般取十分之一以下fc 1 / (2πRC)取 R 1kΩ、C 1μFfc ≈ 159Hz远低于 30kHz平坦度没问题。但这时候时间常数是 1 毫秒意味着设定值改变后要几个毫秒才能稳定。纹波估算用ΔVpp ≈ Vcc × D × (1-D) / (f_pwm × R × C)代入 Vcc5V、D0.5、f30kHz、R1k、C1μF得到约 42mV 的纹波这个值其实不算小。想压到 5mV 以内就得把电容加到 10μF或者把 PWM 频率提到 100kHz 以上。这就是 PWM 法做 DAC 的本质限制要么纹波大要么响应慢两头只能顾一头。做慢速基准源可以接受做实时波形就不要勉强。5. 精度不达标的原因误差从哪来怎么一项项排除5.1 五类主要误差源与对应的硬件手段转换结果不准来源基本可以归到五类我按出现频率从高到低排第一类是基准电压不稳。Vref 是换算的分母它漂 1%读数就跟着漂 1%。很多板子直接拿 7805 的输出当 Vref而 7805 的精度只有 ±4% 左右温度漂移也不小。想改善就用 TL431 搭一个 2.5V 基准或者用专用的基准芯片成本不高但效果立竿见影。第二类是地线处理不当。数字部分的开关电流会在公共地线上产生压降而模拟输入恰恰是以地为参考的这就相当于在信号上叠加了噪声。正确做法是把数字地和模拟地在电源入口处单点连接模拟部分的走线尽量短、尽量粗、远离时钟线。我见过太多板子改完地线之后噪声直接降一个数量级。第三类是输入阻抗不匹配。ADC0809 内部有模拟开关采样瞬间会对输入电容充电如果前级电路的输出阻抗太大比如直接用大阻值电阻分压充电来不及采样值就偏低而且随通道切换变化。经验做法是分压后的源阻抗控制在 1kΩ 以内或者加一级运放跟随器做缓冲。第四类是电源纹波耦合。数字电路切换产生的电源噪声会通过供电引脚进入 ADC 内部常见表现是读数随程序运行状态变化。在每个芯片的电源脚旁边加 0.1μF 瓷片电容大电容也并一个能挡住大部分。第五类是时序没留够余量。ADC0809 的转换时间跟时钟频率直接相关时钟太慢转换就慢如果代码里等待时间不够读到的就是半成品。DAC0832 虽然建立时间只有 1 微秒但如果写脉冲太窄锁存器可能没锁住输出就是随机的。5.2 常见故障速查表和现场排查顺序遇到问题不要乱猜按固定顺序排查效率最高。先看电源和基准再看时序信号然后看地线最后才怀疑代码。下面这张表是我自己整理的直接照着查现象最可能原因排查动作读数恒为 0 或恒为 255输入悬空、超量程、OE 未打开万用表量输入端电压示波器看 OE 脉冲读数跳动几十个码基准不稳、地线噪声、滤波不足示波器看 Vref 纹波检查地线布局读数始终偏小前级输出阻抗过大加电压跟随器或减小分压电阻通道切换后数据错位地址锁存时序不对确认 ST 上升沿前地址已稳定通道间串扰明显采样保持电容未充分放电切换通道后加延时降低采样率DA 输出恒为 0运放供电缺失、反馈电阻未接量运放电源确认 RFB 接线DA 输出为负电压单极性接法下 Vref 为正改 Vref 极性或加反相级DA 波形有台阶表点数太少增加表点数提高中断频率PWM 滤波后纹波大RC 时间常数不够加大电容或提高 PWM 频率上电瞬间输出乱跳复位期间 IO 状态不定加外部上拉或在初始化里先设安全值有三条避坑经验值得单独拎出来说。第一条调试 ADC 的时候先用一个稳定的直流源接输入端不要一上来就接传感器。传感器本身的噪声会让你分不清是芯片的问题还是信号源的问题。第二条示波器是必备的万用表的平均值会骗你。很多纹波和毛刺万用表根本显示不出来。第三条每改一个变量就重新测一次不要一次改好几个地方。否则出了问题你都不知道是哪个改动引起的。6. 一个能跑起来的小综合数字电压表加简易波形发生器6.1 系统框图和模块分工把前面的东西串起来做一个小综合项目用 51 单片机加 ADC0809 加 DAC0832 加 LCD1602实现一个能测电压、能输出设定电压、还能输出几种波形的装置。模块分工是这样的主控STC89C52 或 AT89C5212MHz 晶振负责时序控制和数据处理。采集ADC0809 接 IN0 通道输入经过分压网络和电压跟随器量程设成 0 到 15V。输出DAC0832 加 LM358 做 I/V 转换输出 0 到 5V可以切三种模式——固定电压、方波、正弦波。显示LCD1602 显示当前采集到的电压值和当前输出模式。交互三个按键一个切模式两个调数值。分压网络的具体参数要测 0 到 15VADC 输入不能超过 5V分压比取 1/3。用 20kΩ 和 10kΩ 串联20kΩ 在上、10kΩ 在下中间抽头接 ADC 输入。抽头处的等效源阻抗是 20k 并 10k约 6.7kΩ这个值偏大所以在抽头和 ADC 之间要加一级 LM358 电压跟随器把源阻抗降到接近零。这一步很多同学会省掉结果就是读数随输入电压不同而系统性偏低怎么调代码都调不回来。6.2 关键代码骨架与调参心得主循环的结构很简单定时采集、更新显示、扫描按键、根据模式更新输出。核心是两个定时中断——一个给 ADC0809 提供时钟一个给波形输出提供节拍。这里有个细节要注意两个中断的优先级要安排好波形输出的节拍要求更准时可以给它高优先级ADC 时钟抖动一点没关系。调参的时候我一般按这个顺序走。先把 ADC 单独调通用一个可调电源从 0V 慢慢升到 5V观察读数是不是线性增长记录下来做两点标定。然后把 DAC 单独调通从 0 到 255 逐个写值用万用表量输出看是不是均匀地变化。最后再把两边合起来联调。千万不要两个模块一起上电就开始调那样出了问题你根本定位不到。还有几个实际经验。LCD1602 显示电压的时候建议只显示到小数点后两位因为 8 位 ADC 在 5V 量程下的分辨率只有 19.5mV显示三位小数是没有意义的只会让人觉得读数在跳。波形的频率和幅值建议做成可以调的用按键加减这样调试的时候方便。最后整机上电前先单独给每颗芯片上电测试确认电源电压正常再插芯片尤其是 DAC0832 和运放接错电源很容易直接烧掉。我在这个电路上踩过最深的一个坑是地线走成了环形。当时现象是采集值总是带着一个跟显示刷新同步的小波动查了半天代码没找到问题最后拿示波器量地线上的波形才发现数字部分的回流电流在环形地线上绕了一圈正好从模拟输入的参考点上经过。把地线改成单点星形连接之后波动立刻消失。所以如果你也遇到类似规律性的干扰先别怀疑代码拿探头量一量地线往往会有收获。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑