ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DAC0832从原理到实战:R-2R网络、三种工作模式与波形发生器设计

DAC0832从原理到实战:R-2R网络、三种工作模式与波形发生器设计 第一次把DAC0832插上面包板时我以为8位D/A转换就是“数据写进去、模拟电压出来”这么简单。等到示波器探针往输出端一放才发现满屏毛刺数值也根本对不上代码里算好的预期。折腾了一个晚上最后老老实实把手册里的引脚功能一条条对着电路板梳理又把直通、单缓冲、双缓冲这三种工作模式的锁存逻辑彻底想通输出才终于稳定下来。这篇教程就按我自己的踩坑顺序来写先从内部原理讲清楚这块芯片怎么工作再逐个引脚说明接线要点接着把三种工作模式拆开揉碎最后给出一套可以直接抄的电路、代码以及实测中经常踩到的坑。适合正在做单片机课设的学生、想自己搭波形发生器的电子爱好者以及第一次接触并行DA芯片的入门工程师。1. 为什么到今天还要玩DAC0832先搞清楚它解决什么问题1.1 数模转换在信号链里的定位单片机算出来的东西永远是二进制数字量但现实世界里的电压、电流、声音是连续变化的模拟量。DAC数模转换器就是干这个桥接工作的给它一个数字码它输出一个对应的模拟电流或电压。DAC0832是目前教材里出现频率最高的并行8位DA芯片之一结构典型价格便宜非常适合用来理解DAC的基本原理。它和现在MCU自带的12位、16位DAC相比有个非常明显的特点并行输入。数据D0-D7共8根线一次直接送一个字节过来不需要像I2C或SPI那样一位一位串行传输。这个特性带来的好处是更新速度快只要你把数据放到引脚上芯片就能响应代价也很直接占用IO口数量多8根数据线加4-5根控制线一个最小系统就吃掉大半个单片机的引脚。另一个容易忽略的关键点是DAC0832是电流输出型DAC不是电压输出型。它输出的是一对互补电流IOUT1和IOUT2想要得到电压信号必须外加运算放大器做电流电压转换。很多新手一上来就拿着万用表去测IOUT引脚对地电压那测出来的是一个随负载随便飘的值根本不能说明问题。这个点后面会单独用一节来讲。1.2 内部结构R-2R电阻网络的二进制电流分配DAC0832能实现数模转换的核心是内部的R-2R梯形电阻网络。这个名字很形象整个网络只用了两种阻值的电阻一种是R一种是2R按“梯形”的结构排开。每个二进制位控制一个电子开关开关决定这一位的电流是流向IOUT1还是IOUT2。当8位数据进来时每一位对应的电流权重是不一样的。最高位D7贡献满量程的一半D6贡献四分之一D5贡献八分之一以此类推最低位D0只贡献满量程的1/256。这正好就是二进制权重。最终IOUT1的电流可以写成IOUT1 (D / 256) × (VREF / R)其中D是0到255的数字码R是内部梯形网络的等效电阻。VREF是参考电压它直接决定满量程电流大小。所以VREF必须稳定、干净因为输出电流是直接跟它成比例的参考电压上的一点点纹波最后都会按系数传到模拟输出上。芯片内部还有一组非常巧妙的结构两级锁存器。第一级叫输入寄存器第二级叫DAC寄存器两级之间由控制信号分开。正是因为有了这两个独立的锁存器DAC0832才能玩出直通、单缓冲、双缓冲三种工作模式。很多人学这个芯片时只记住了三种模式的名字却不明白为什么需要两个锁存器等到后面讲模式时你会看到双缓冲模式下第二级锁存器是用来实现“多路同时更新”的这在控制多路输出时非常重要。1.3 主要性能指标怎么看看DAC芯片先抓几个指标就够了指标DAC0832典型值说明分辨率8位输出满量程的1/256为一个台阶建立时间约1μs数据更新后输出稳定到这个精度需要的时间参考电压VREF-10V到10V可以是负电压也可以正电压电源VCC5V到15V供电范围宽但逻辑阈值也随之变化功耗约20mW非常省电输出形式互补电流IOUT1/IOUT2需要外部运放转电压分辨率8位意味着什么如果VREF接5V那么理想情况下每个最小台阶对应5V除以256约19.5mV。也就是说你写0x00输出理论上是0V实际上有一个很小的失调写0x01输出约19.5mV写0xFF输出约4.98V永远到不了完整的5V。这个“永远到不了满量程”的特性经常让新手困惑但这是R-2R梯形网络的正常现象不是芯片坏了。建立一个直观概念8位精度做波形发生器、电机调速、模拟电压输出完全够用但要是做精密电源、音频高保真输出8位就不太够看了最好换12位或16位的DA芯片。DAC0832的价值更多在教学、实验和对并行时序的理解上。2. 逐脚核对DAC0832引脚功能与接线前的电路设计DAC0832常见封装是DIP20一共有20个引脚。第一次用这块芯片时我犯过一个很蠢的错误把ILE当成“锁存使能”以为要拉低才能锁存数据。实际上ILE的全称是Input Latch Enable高电平有效它配合CS和WR1共同控制第一级寄存器。单看名字很容易搞反这也是为什么我建议先把引脚功能表打印出来放旁边。2.1 20个引脚完整对照表引脚号名称类型功能特别注意4-7, 13-16DI0-DI7数字输入8位并行数据输入DI0是最低位DI7是最高位17ILE数字输入输入锁存使能高电平有效常接5V1CS数字输入片选低电平有效与WR1配合2WR1数字输入第一级写信号低电平有效上升沿锁存18WR2数字输入第二级写信号低电平有效上升沿锁存19XFER数字输入传输控制低电平有效控制第二级8VREF模拟输入参考电压决定满量程电流需干净稳定9RFB模拟反馈内部反馈电阻引出配合运放做电流电压转换11IOUT2模拟输出互补电流输出2常接运放或地12IOUT1模拟输出主电流输出1与D/256成正比3AGND模拟地模拟地与DGND单点连接10DGND数字地数字地与AGND单点连接20VCC电源正电源5V到15V需去耦电容2.2 数字侧引脚控制信号之间的逻辑关系数字侧除了8根数据线还有5根控制线ILE、CS、WR1、WR2、XFER。它们的逻辑关系并不复杂但很多人搞不清“谁和谁是一组”。第一组是第一级输入寄存器的控制信号ILE、CS、WR1。只有当ILE1、CS0、WR10三者同时满足时数据线上的值才能进入第一级寄存器当WR1从0变成1上升沿时当前数据被锁存住之后数据线再怎么变化都不会影响里面保存的值。第二组是第二级DAC寄存器的控制信号WR2、XFER。类似地XFER0、WR20时数据从第一级寄存器传到第二级WR2上升沿时锁存。这里有个特别容易混淆的地方芯片型号里的0832很多人会以为“写数据”只需要控制WR1就行把CS直接接地。实际上CS也必须保持低电平否则第一级寄存器根本不响应。我在面包板上调试时就遇到过这种问题单独给WR1打脉冲输出纹丝不动查了半天才发现CS被悬空了处于不确定电平。2.3 模拟侧引脚IOUT、RFB、VREF的配合关系模拟侧是DAC0832真正的灵魂也是最容易出问题的地方。前面说了它是电流输出型IOUT1和IOUT2是一对互补电流两者的和始终等于满量程电流。当D0x00时IOUT1为0IOUT2为满量程当D0xFF时IOUT1接近满量程IOUT2接近0。如果直接拿万用表量IOUT1对地电压得到的是电流流过你万用表内阻和寄生电阻后的结果既不准确也不线性。标准做法是接一个运算放大器把电流转成电压。最简单可靠的是反相放大器接法运放反相输入端接IOUT1同相输入端接模拟地AGNDIOUT2直接接AGNDRFB引脚接到运放反相输入端和输出端之间利用芯片内部的反馈电阻完成电流电压转换。这时输出VOUT -D / 256× VREF注意这个负号。如果VREF接5V输出就是0到-5V的负电压。想要正电压输出有两种办法一是把VREF换成-5V参考电压二是在后面再串一级反相放大器把负电压翻成正电压。很多实验板上直接标着“0-5V输出”的接线图实际上是两级运放或者用了负参考电压看电路图时要留意。VREF的范围是-10V到10V它既可以是正也可以是负这就让DAC0832天然支持双极性输出。配合外部运放可以做成正负电压摆动的信号源比如输出-5V到5V的扫频信号。3. 三种工作模式逐层拆解直通、单缓冲、双缓冲DAC0832之所以能在教科书里占据一席之地很大程度上就是因为这三个工作模式把锁存器的概念讲得特别清楚。说白了三种模式的差别就是两级寄存器分别处于“透明状态”还是“锁存状态”。3.1 直通模式数据进去输出马上变直通模式是所有模式里最简单的。把ILE接高电平CS、WR1、WR2、XFER全部接低电平这时两级寄存器全部处于透明状态数据线上的8位数据直接作用于内部的R-2R网络IOUT1电流跟前一秒数据线的值严格对应。直通模式的优点是响应极快没有任何锁存延迟。只要数据线电平变了输出马上就变。缺点也很致命如果单片机总线上有其他操作或者数据线本身有抖动这些毛刺会毫无保留地直接传进模拟输出。另外这种方式不适合“先准备好数据、再统一更新”的场景。直通模式的软件最简单代码都不用专门初始化控制引脚#include reg51.h sbit DAC_ILE P2^4; sbit DAC_CS P2^0; sbit DAC_WR1 P2^1; sbit DAC_WR2 P2^2; sbit DAC_XFER P2^3; void dac_pass_through_init(void) { DAC_ILE 1; DAC_CS 0; DAC_WR1 0; DAC_WR2 0; DAC_XFER 0; } void main(void) { unsigned char i 0; dac_pass_through_init(); while (1) { P0 i; // 数据直接出现在模拟输出端 // 不加延时的话输出频率会非常高 } }3.2 单缓冲模式最常见的实用配置单缓冲模式的意思是两级寄存器中只使用一级做锁存另一级保持直通。实践中绝大多数人用的是“第一级锁存、第二级直通”的接法ILE接高电平WR2接低XFER接低然后通过CS和WR1控制数据的写入。在这种配置下数据线变化时不会立刻影响模拟输出。必须等WR1产生一个上升沿数据才被锁存进第一级寄存器并且马上穿过第二级透明通道到达DAC核心。这个模式的好处是可以先把数据准备好再触发写脉冲避免数据线中间过程的毛刺被输出。写时序也更符合“处理器先算好后赋值再产生写信号”的习惯。51单片机上的单缓冲操作#include reg51.h #include intrins.h #define DAC_DATA P0 sbit DAC_CS P2^0; sbit DAC_WR1 P2^1; sbit DAC_WR2 P2^2; sbit DAC_XFER P2^3; sbit DAC_ILE P2^4; void dac_init(void) { DAC_ILE 1; DAC_WR2 0; // 第二级直通 DAC_XFER 0; // 第二级直通 DAC_CS 1; DAC_WR1 1; } void dac_write(unsigned char val) { DAC_CS 0; // 选中芯片 DAC_WR1 0; // 第一级进入透明状态 DAC_DATA val; // 数据放到总线上 _nop_(); _nop_(); DAC_WR1 1; // 上升沿锁存数据 DAC_CS 1; // 释放片选 } void main(void) { unsigned char i 0; dac_init(); while (1) { dac_write(i); // 输出0到255循环形成锯齿波 } }注意P0口在51单片机上是开漏输出如果直接用P0接DAC0832的数据线必须在P0上接一排上拉电阻比如10kΩ排阻到5V否则高电平是浮空的。这个问题在51实验板上非常常见DAC输出波形奇怪先检查P0口上拉。3.3 双缓冲模式多路同步更新的关键双缓冲模式才是DAC0832真正的精髓。先想象一个场景你想用一个单片机控制两块DAC0832分别输出正弦波和余弦波并且要求两路信号在同一个瞬间完成跳变。如果只用单缓冲你只能先写DAC1再写DAC2这两次写入之间隔了几条指令的时间输出会有时间差。对于需要严格相位关系的场合这个时间差是不能接受的。双缓冲模式解决这个问题的方式是把两级寄存器都用起来。先分别把两路数据写进各自的输入寄存器第一级锁存此时DAC寄存器第二级里的旧数据还维持着原来的输出模拟输出没有变化。等两路数据都装填好了再统一给XFER发一个低脉冲两块芯片的第二级同时锁存新数据模拟输出就在同一个时刻完成切换。这就是为什么需要两级锁存器。第二级不是多余的它是为了“多路DAC同步刷新”而设计的。单缓冲模式下第二级直通看似浪费其实只是没有用到它的同步能力而已。双缓冲模式下STM32的模拟时序// 假设数据线接PC0-PC7 // 控制线PB0CS1, PB1WR1_1, PB2CS2, PB3WR1_2 // PB4WR2(共用), PB5XFER(共用), PB6ILE(共用) #define DAC1_CS_L() GPIOB-BRR GPIO_PIN_0 #define DAC1_CS_H() GPIOB-BSRR GPIO_PIN_0 #define DAC1_WR_L() GPIOB-BRR GPIO_PIN_1 #define DAC1_WR_H() GPIOB-BSRR GPIO_PIN_1 #define DAC2_CS_L() GPIOB-BRR GPIO_PIN_2 #define DAC2_CS_H() GPIOB-BSRR GPIO_PIN_2 #define DAC2_WR_L() GPIOB-BRR GPIO_PIN_3 #define DAC2_WR_H() GPIOB-BSRR GPIO_PIN_3 #define DAC_XFER_L() GPIOB-BRR GPIO_PIN_5 #define DAC_XFER_H() GPIOB-BSRR GPIO_PIN_5 void dac_load(uint8_t val, uint8_t ch) { GPIOB-BRR GPIO_PIN_6; // ILE拉低先不使能 if (ch 1) { DAC1_CS_L(); DAC1_WR_L(); GPIOC-ODR (GPIOC-ODR 0xFFFFFF00) | val; DAC1_WR_H(); // 锁存到第一级 DAC1_CS_H(); } else { DAC2_CS_L(); DAC2_WR_L(); GPIOC-ODR (GPIOC-ODR 0xFFFFFF00) | val; DAC2_WR_H(); DAC2_CS_H(); } GPIOB-BSRR GPIO_PIN_6; // ILE拉高使能 } void dac_sync(void) { DAC_XFER_L(); DAC_XFER_H(); // 两路第二级同时锁存 }双缓冲模式的接线要求是两块芯片的数据线和控制线可以分别连接但XFER往往会并在一起这样一次低脉冲就能同时刷新多块DAC。三种模式的对比用一张表总结会比较直观模式ILECSWR1WR2XFER数据更新机制适用场景直通10000数据线直接进DAC核心实时连续输出教学演示单缓冲10脉冲00数据先锁存第一级第二级直通单路普通DAC输出双缓冲10脉冲脉冲脉冲数据先写第一级XFER统一到第二级多路同步输出波形合成4. 从零搭一套可用的DAC0832最小系统理论讲完该动手了。这里给出一套我实际用过的最小系统方案元件都很常见面包板或者洞洞板都能搭起来。4.1 硬件清单与选型说明元件型号/规格数量用途DAC0832DIP20封装1核心DA芯片运算放大器TL081或LM3581电流电压转换51单片机或STM32开发板任意1输出数据和控制信号精密基准源TL431或REF30251提供稳定VREF电阻10kΩ排阻或分立的1kΩ-10kΩ若干P0上拉、基准分压电容100nF瓷片电容2-3电源去耦面包板/洞洞板-1搭电路运放选型上TL081是经典的FET输入运放输入偏置电流很小配合DAC0832的高阻输出很合适如果手头只有LM358也能工作只是高端线性略差一点。面包板搭电路时运放供电需要双电源的话会比较麻烦可以直接用单电源LM358输出范围大约0到3.5V够验证功能。想要完整0到5V输出最好给运放提供正负电源比如正负5V或正负12V。4.2 面包板接线要点处理好地和电源再去碰信号接线顺序很关键。我的习惯是先接电源和地再接基准最后接数据线和控制线。VCC接5V对地并一个100nF瓷片电容放在芯片VCC引脚附近。AGND和DGND在芯片附近用一根导线单点连在一起然后一起回到电源地。为什么要单点连接因为数字信号跳变时会在数字地上产生很大的瞬态电流如果模拟地和数字地走线混在一起噪声会通过地线耦合到模拟电路。单点连接能让数字噪声尽量在数字地回路里消化不污染模拟地。注意数字信号线的走线尽量短数据线和控制线不要和模拟输出线平行。面包板空间允许的话在DAC芯片和IOUT输出之间留一点间隔。4.3 电流转电压的两种运放接法接法一反相输出VREF接正电压时输出0到负电压。运放LM358用双电源±5V IOUT1 ---- 运放反相输入端- IOUT2 ---- AGND RFB ---- 运放输出端同时也连接到反相输入端 运放同相输入端接AGND这种接法输出VOUT -D/256× VREF。VREF5V时输出0到-5V。接法二两级运放翻正输出0到5V。第一级运放同接法一VOUT1 -D/256× VREF 第二级运放做反相比例放大放大倍数为-1把VOUT1翻成正电压这个方法多用一个运放但逻辑清晰、可调增益我在万能板上测试时用的就是这种结构。4.4 波形发生器实测锯齿波、三角波、正弦波电路搭好后先别急着接反馈和控制线把DAC调成直通模式手动给数据线接一个固定电平比如全部接地用万用表量IOUT1转出来的电压确认基本为零。再用手碰触一根数据线接高电平电压应该跳一个台阶。这一步能快速排除接线错误。确认基础功能后写程序输出锯齿波#include reg51.h #include intrins.h sbit DAC_ILE P2^4; sbit DAC_CS P2^0; sbit DAC_WR1 P2^1; sbit DAC_WR2 P2^2; sbit DAC_XFER P2^3; void delay_us(unsigned int us) { while (us--) _nop_(); } void dac_init(void) { DAC_ILE 1; DAC_WR2 0; DAC_XFER 0; DAC_CS 1; DAC_WR1 1; } void dac_write(unsigned char val) { DAC_CS 0; DAC_WR1 0; P0 val; _nop_(); _nop_(); DAC_WR1 1; DAC_CS 1; } void main(void) { unsigned char i 0; dac_init(); while (1) { dac_write(i); delay_us(50); // 调整这个值可以改变锯齿波频率 i; } }锯齿波实测频率大概是1 /256 × 延时时间如果延时50μs那么一个完整周期是256 × 50μs 12.8ms频率约78Hz。把这个延时减小到5μs左右波形频率能到几百赫兹但要注意51单片机本身执行dac_write的开销实际最高频率会有上限。三角波只需要在两个方向上分别递增递减void main(void) { unsigned char i 0; char dir 1; dac_init(); while (1) { dac_write(i); delay_us(30); if (dir 1) { i; if (i 255) dir -1; } else { i--; if (i 0) dir 1; } } }正弦波则建议在程序里存一张256点的正弦查找表然后循环输出。相位差可以通过查表偏移量实现比如正弦波输出sin_table[i]余弦波输出sin_table[(i 64) % 256]3.3节里的双缓冲同步正好用在这里。如果用STM32GPIO配置要选对。STM32的GPIO有8种工作模式驱动DAC0832数字输入端用“推挽输出”模式就够了速度选择中等GPIO_SPEED_FREQ_MEDIUM不需要开漏。开漏模式必须外接上拉电阻否则引脚只能拉低不能拉高容易让人误以为芯片坏了。如果你的数据线是3.3V单片机接5V供电的DAC0832后面会专门讲电平问题。5. 实测中踩过的坑和处理方案这一节全部来自实际调试中的记录。DAC0832本身不难但几乎所有问题都出在“看起来简单”的细节上。5.1 输出波形完全不对检查VREF是不是真的稳定我第一次把VREF直接接在电源5V上结果输出波形带着明显的高频纹波用万用表看平均值好像对但示波器上一塌糊涂。原因很简单数字电路工作时5V电源上本来就叠加着大量开关噪声这些噪声直接作为参考电压进入DAC等于把噪声按比例打印到了模拟输出上。解决方法是给VREF加基准源。最简单的用TL431搭一个2.5V基准电路只需要两个电阻和一个电容。TL431的输出精度和温漂比电源好得多。如果手头有REF3025这类精密基准芯片更好直接接上就能用。注意更换VREF后满量程电压也会变2.5V基准时每个LSB约9.8mV做计算时别拿5V的表格硬套。5.2 输出台阶跳变时毛刺特别大这是8位并行DAC的宿命直通模式或者快速连续写入时你会在输出上看到很强的毛刺尤其数据从0x7F跳到0x80时毛刺幅度可能比预期台阶还要大。原因在于内部多位电流开关切换时间不是完全一致的D7位开关切换时低位的几个开关还在“路上”导致输出在一瞬间出现接近满量程的尖峰。这个毛刺是并行DAC固有的只能抑制不能完全消除。可以用的办法包括输出端加RC低通滤波器让尖峰被滤掉或者用运放构成低通反馈必要时用采样保持器去毛刺。DAC0832做语音或音频波形输出时这类毛刺会造成可闻的底噪所以要性能还是得换串行高精度DAC。5.3 数据写进去但输出没反应先查CS和WR1的“同时有效”单缓冲模式里数据要进入第一级寄存器有两个前提条件CS为低、WR1为低。两个条件同时满足时寄存器才透明数据才能进去。如果你在软件里只拉了WR1忘记拉CS或者CS还处于高电平那么寄存器根本不会响应。调试时我习惯用一个LED接到CS引脚程序执行到“CS0”那一步时LED应该点亮通过LED状态确认控制信号有没有真正拉低。另外有的单片机执行速度很快WR1低电平持续时间只有几百纳秒对DAC0832这类老芯片来说太短的话可以加几条空指令或者一个简单的延时函数把低电平拉长到1μs以上稳妥很多。5.4 8位精度的物理边界和软件补偿8位DAC的精度极限是1/256但实际精度还受VREF精度、内部电阻温度漂移、运放失调电压等影响。实测满量程误差在0.5%到1%都很正常。如果你只是想要一个大概的电压不需要校准但如果你需要某个特定电压值可以做个两点校准。做法很简单写0x00记录实际输出电压V0写0xFF记录实际输出电压V255然后线性插值。这样要输出目标电压V_target时对应的数字码就是D (V_target - V0) / (V255 - V0) × 255注意V0通常不是严格的0V运放失调会让它有十几毫伏到几十毫伏的偏置忽略它的话低端输出误差会非常显眼。5.5 3.3V单片机和5V芯片混接电平转换不能靠赌DAC0832是TTL电平兼容芯片但它的逻辑阈值会随VCC变化。VCC5V时高电平最小输入阈值大约2V3.3V单片机输出的高电平约3.0V理论上够用实际好多项目也这样跑通了。但如果你把VCC升到12VTTL高电平阈值可能会跟着变高3.3V信号就不一定可靠了。保险做法是在中间加一片74HCT245或者用简单三极管做电平转换。74HCT245的输入兼容3.3V输出能到5VSDIP封装焊起来也不难。至少不要抱“理论能工作就肯定能工作”的心态尤其是做产品而非实验的时候。最后再分享一点我个人的使用习惯。第一次给DAC0832上电先把所有控制信号固定成直通模式用万用表确认每一位的权重都正常再写单缓冲时序最后才上双缓冲。这样一旦出问题你能快速定位是硬件接线、控制时序还是模式配置的锅。你要是纯粹为了出个电压完全有更省引脚的单片机自带DAC可用但如果你想彻底搞懂并行总线时序、锁存器原理和R-2R梯形网络DAC0832依然是块难得的活教材。
返回列表