R-2R电阻网络DAC实战:从原理到STM32驱动的低成本多通道模拟输出方案

发布时间:2026/9/29 1:43:48
R-2R电阻网络DAC实战:从原理到STM32驱动的低成本多通道模拟输出方案 做控制类嵌入式项目模拟输出是个绕不开的需求。前阵子给一套老产线做改造需要四路0~5V的可调偏置电压精度要求不高8位就够但现场不能断电重装专用DAC板卡预算也卡得死。我翻老工程师留下的图纸看到了一排规规矩矩的R和2R才想起R-2R电阻网络DAC这个几乎被集成芯片时代遗忘的结构。用一把电阻加几个GPIO把四路模拟输出全部搞定之后我觉得这个方案值得系统聊一聊。这篇文章我会从R-2R网络的原理推导讲起把电阻、运放、开关、基准源的选型心得和实测校准方法都写出来最后落到STM32这类MCU上怎么驱动它出波形。适合正在做低成本多通道模拟输出、想绕开PWM滤波、又不愿意给每路都塞一颗独立DAC芯片的读者。1. 为什么现在还要研究R-2R电阻网络DAC1.1 低成本多通道模拟输出的务实选择先说实话R-2R并不是什么新技术上世纪就已经被大量集成到DAC芯片内部。但恰恰因为它是芯片内部方案很多做应用的工程师反而忽略了它可以直接拿分立器件搭出来用。当一个项目需要4路、8路甚至更多模拟输出时方案选型会非常尴尬。用PWM加RC滤波成本确实低但纹波和建立时间往往压不下去尤其现场负载对纹波敏感的时候滤波电容加得再大也治标不治本。用集成DAC芯片比如常见的8位SPI接口DAC一颗也要几块钱四路就是四颗无论是BOM成本还是PCB面积都不划算。用STM32内置DACF4系列很多型号只有两路还不够。R-2R电阻网络最大的价值就在这里一组8位输出只需要16颗电阻或者两片排阻就能搞定成本几乎可以忽略。而且位数、路数完全由你自己定想扩到16路就复制16组排阻接16组GPIO扩展起来非常直接。对于慢速控制类场景比如偏置电压、数控增益、可调比较阈值、低频信号源它完全够用。我那次改造就是用两片8位R-2R排阻模块加两个通用运放把四路0~5V偏置电压全部做出来了。实测输出稳定线性度也能满足现场需求前后花在电阻上的钱不到十块钱。1.2 R-2R与其他DAC方案的差距对比很多新手会问既然有现成的DAC芯片为什么还要折腾分立电阻这个问题换个问法就是R-2R到底在什么场景下性价比最高。我把常见方案放在一起对比大家心里就有数了。方案典型成本(8位)输出特性建立时间通道扩展适合场景集成DAC芯片(SPI/I2C)5~30元/路好非线性低微秒级每路一颗成本线性增长高精度、高速、需要低功耗PWMRC滤波不足1元/路纹波大需外部滤波毫秒级(取决于RC)只要有定时器就能扩对纹波不敏感、更新率低R-2R分立电阻网络1~3元/路无固有纹波线性度取决于电阻1~2微秒每组只需几个GPIO低成本多路慢速模拟输出MCU内置DAC0(芯片自带)取决于型号通常较好微秒级一般只有1~2路路数少、不想加外围从表格能看出来R-2R的定位非常清晰它填补的是低成本、多路、中等精度、微秒级建立时间这个空档。PWM便宜但动态性能差集成DAC性能好但贵内置DAC路数少而R-2R恰好把这些短板避开了。当然它也有自己的软肋。电阻网络对电阻精度敏感做10位以上必须用0.1%甚至0.05%的精密电阻否则非线性比PWM还难看。另外输出阻抗固定等于R不带负载能力必须加运放缓冲。这些限制不是劝退理由而是选型和画板时要注意的设计约束。2. R-2R网络的数学本质为什么电阻只用两种阻值就能实现二进制权重2.1 从电流逐级减半说起我第一次看R-2R网络时也觉得不可思议凭什么一排电阻就能把数字量变成模拟量而且权重刚好是二进制。后来发现核心只有一句话R-2R梯形网络让流过每个垂直支路的电流从高位到低位依次减半。这里说的垂直支路就是每个节点上那个接到开关或地线的2R电阻。网络能实现电流减半靠的是R和2R的巧妙比例。从任意一个垂直节点向它的右侧方向看过去网络的等效电阻永远是2R这是整个结构的定海神针。因为从最右侧的2R终端开始先是两个2R并联成R再串上一个R变成2R然后继续两个2R并联成R再串R一路递推下去看到的电阻值始终是2R。有了从每个节点向右看过去都是2R这个结论电流分配就变得非常清晰。某个节点的电流从左端流过来到了节点处会分成两路一路沿水平R继续向右一路沿垂直2R流下去。由于向右看的等效电阻是2R垂直支路也是2R两条路的电阻完全相等所以电流必定对半平分。第一级分一半剩下的一半到第二级再分一半到第三级再分一半这样一级一级下去每个垂直支路的电流就是I/2、I/4、I/8……直到I/2^n。这个逐级减半就是二进制权重的来源。最高位拿到总电流的一半次高位拿到四分之一第三位拿到八分之一最低位只有总电流的2^n分之一。把这n路电流用开关分别引到求和点或者地上再经过运放和反馈电阻转换成电压就完成了从数字码到模拟电平的映射。之所以只需要R和2R两种阻值是因为整个网络的电阻值设计完全是围绕两个相等电阻分流来展开的。2.2 用4位实例把电流计算走一遍光说等效电阻可能还是抽象我用一个4位R-2R网络的实际数值把电流分配走一遍。设R10kΩ2R20kΩ参考电压Vref5V。从Vref端看进去整个R-2R网络的输入电阻等于R也就是10kΩ。这一点可以通过从LSB端开始逐级化简验证前面提过两个2R并联成R串R合并成2R反复递推最后输入端看到的正好是10kΩ。于是总输入电流Iin Vref / R 5V / 10kΩ 0.5mA这0.5mA进入网络后的分配情况如下表位权重支路电流该位1时对输出的贡献(通过RfR转换)MSB(bit3)1/20.25mA2.50Vbit21/40.125mA1.25Vbit11/80.0625mA0.625VLSB(bit0)1/160.03125mA0.3125V如果把这4个电流支路全部接入运放求和点反馈电阻取R10kΩ总输出电压就是每位贡献的叠加。写成公式就是Vout (b3×2^3 b2×2^2 b1×2^1 b0×2^0) × Vref / 16其中b3到b0分别是最低位到最高位的数字值0或1。这个公式大家应该很眼熟它的本质就是标准DAC的传输特性Vout Vref × D / 2^n。对应到4位网络D的值从0到15输出步进就是5V/160.3125V。我用这个例子在Multisim里做过仿真也实际搭过板子结果跟表格里的数值对得上。需要注意这个公式的前提是输出端用运放接成虚地把电流模式转换成电压模式如果是电压模式直接取网络某端电压权重次序和接法会有变化后面会专门讲。2.3 输出阻抗恒定与单调性的工程意义R-2R网络还有一个容易被忽略但工程上很有价值的特性从任意节点看输出阻抗都恒定等于2R从参考端看也恒定等于R。这个阻抗恒定意味着什么就是无论你往网络里写什么数字码网络对外的负载特性几乎不变驱动电路不会因为码值变化而出现明显的负载波动。对用GPIO直接驱动的DIY方案来说这个特性特别重要。GPIO的输出能力有限如果网络输入阻抗随着码值变化高电平就会被拉到不同程度输出就会出现额外的非线性。R-2R因为输入阻抗恒定GPIO拉高的时候承担的负载基本固定一定程度上减小了这种失真。另一件值得留意的事是单调性。DAC的单调性是指数字码增加时模拟输出也跟着增加不能出现倒退。R-2R网络天然是单调的因为每一位的贡献都是正的不存在类似于R-string DAC里那种权重抵消的机制。无论电阻精度多差只要电阻值本身是正的分母也就是正数输出就不会因为码值增加而下降。这个性质用在控制环路里很关键如果DAC不单调闭环负反馈在某些点会变成正反馈系统可能振荡。当然单调不代表线性。非单调和线性度差是两回事后面误差分析那一节会展开讲电阻精度对INL和DNL的影响。3. 把R-2R DAC搭成能实际用的电路关键器件选择3.1 缓冲运放没有它输出会被拉垮R-2R网络本质上是被动电阻网络它的等效输出阻抗是R不是0。以8位网络用10kΩ的R为例输出阻抗就是10kΩ如果直接拿去带负载假设负载是10kΩ分压就吃掉一半输出幅度直接不对。更麻烦的是负载还会改变网络的电流分配导致各位的权重发生变化数字码和输出电压之间的关系就不准了。所以运放进不去。运放在这里的作用是高输入阻抗缓冲它从网络取走的电流几乎为0不会干扰网络内部的电流分配同时又能提供低阻抗输出驱动力去推ADC输入、下一级电路或者直接接负载。选运放时我一般关注三个参数输入偏置电流、失调电压、带宽。输入偏置电流会流进网络相当于额外电流注入改变LSB权重失调电压会叠加在输出上造成固定偏移。对8位精度来说偏置电流在几十纳安以内、失调在几毫伏以内的通用运放都够用。如果想做快速信号源带宽和压摆率要跟上普通LM358就不太合适了建议用OP07、TL072或者轨到轨的运放。3.2 基准电压源Vref是整个输出精度的锚点R-2R DAC的输出公式里Vref是直接乘进去的所以参考电压的精度和稳定性决定了输出绝对电压的精度。这句话怎么理解如果Vref有1%的波动输出就会有1%的波动电阻网络再准也救不回来。实用的做法是不要在GPIO的高电平上直接做文章而是把R-2R网络的参考端接到专用的基准源或LDO上。用MCU的3.3V做Vref也不是不行但要意识到单片机的电源会随着负载波动而且普通LDO的输出噪声和温漂都比较大。如果目标是8位精度Vref的误差至少要控制在0.4%以内也就是一个LSB以内用TL431或者REF3030这类基准源比较稳妥。另外要注意Vref的驱动能力。R-2R网络从Vref端抽取的电流是Vref/R如果R取10kΩ5V基准就要提供0.5mA电流。大多数基准源能提供几毫安到几十毫安电流但还是要确认一下不能用内阻很大的简易稳压管直接顶上去否则负载变化会导致Vref跟着变化。3.3 模拟开关或MCU GPIO导通电阻会悄悄改变权重DIY的R-2R网络有两种驱动方式一种是直接用MCU的GPIO去推每个位GPIO输出高电平当作Vref输出低电平当作地另一种是用模拟开关CD4066之类的芯片把每个支路接到外部基准源和地之间。两种方式都有个共同问题开关导通电阻不是零。GPIO在输出高电平时的导通电阻一般在几十欧姆量级CD4066在5V供电下典型导通电阻也有60到100多欧姆。这个Ron会和垂直支路的2R串联等效把2R变大从而改变该位的权重。定量感受一下如果2R取20kΩRon取50Ω影响是50/200000.25%。8位的LSB权重是1/256约0.39%所以50Ω的Ron在8位系统里会造成不到一个LSB的误差勉强能接受。但如果要做到10位LSB是1/1024约0.1%0.25%的影响就超过两个LSB了必须考虑补偿。补偿的办法有两个方向。一是把网络里的R值做大让Ron相对可以忽略比如R取47kΩ、2R取94kΩ这样50Ω的Ron只占0.05%左右。代价是网络输入阻抗变大运放偏置电流的影响更明显电路更容易受干扰。二是选低导通电阻的开关比如某些模拟多路器或专用DAC开关Ron能做到几欧姆。GPIO的话STM32F4这类推挽输出驱动能力较强实测输出高电平时带10k负载的压降不大只要不是高精度场合直推8位R-2R还是可行的。3.4 电阻精度与温漂对线性度的影响电阻是R-2R网络的核心它的精度直接决定DAC的线性度。我用一个粗略估算来说明问题如果最高位对应的2R电阻偏差1%那么最高位的输出权重就会偏差约0.5%Vref。对8位DAC来说一个LSB是0.39%Vref也就是说1%的电阻误差已经大到一个LSB还多8位精度全靠运放和基准撑着电阻反而是瓶颈。实际项目里我见过很多人用普通1%贴片电阻搭8位R-2R测出来INL确实不太好零点几个LSB横着飘有些位会跳到接近2个LSB的误差。如果只是给指示器或者粗略偏置用这问题不大要是拿去控制精密电源或者波形发生器最好还是选0.1%精度的金属膜电阻。0.1%的电阻在8位系统里富余很多做到10位也勉强够用12位就得上0.01%的精密电阻网络了。温漂同样要当心。电阻的温漂系数如果各家料不一致温度一变化各支路电阻比例就会漂移DAC的线性度随温度恶化。选料时尽量用同一批次的排阻或者干脆买集成好的R-2R电阻网络模块内部匹配度比散装电阻高得多。4. 在STM32这类MCU上驱动R-2R从码值到电压的完整链路4.1 并行GPIO直驱8位R-2R的接法与换算STM32驱动R-2R最直接的方式就是拿一组GPIO并排输出。接法上把GPIO的PA0到PA7分别接到R-2R网络的8个位PA0做LSBPA7做MSB每个GPIO经过一个2R电阻接到网络节点节点之间用R连接末端用2R接地作为终端输出从MSB端用运放缓冲取出来。这种接法有一个细节我踩过坑输出节点要选在靠近MSB的位置原因前面原理部分已经解释过越靠近输出端的位权重越大MSB必须离输出最近否则测出来的电压权重是反的。如果你从LSB端取输出最高位的贡献会变得非常小整个DAC的传输曲线就乱了。代码层面非常简单把码值写到GPIO的输出数据寄存器就行// 假设PA0~PA7接R-2RPA0LSBPA7MSB void r2r_dac_write(uint8_t code) { GPIOA-ODR (GPIOA-ODR 0xFF00) | (uint16_t)code; }写码值和输出电压的换算是// 8位R-2RVref3.3V float r2r_code_to_voltage(uint8_t code) { return 3.3f * code / 256.0f; }如果接了外部基准源把3.3换成Vref的实际电压即可。注意如果GPIO高电平本身用的是3.3V那么Vref就是这个3.3VGPIO的饱和压降会造成微小误差实测时需要校准。4.2 定时器触发与刷新率高频率连续波形怎么推当R-2R不只是输出直流电压而是要产生正弦波、三角波之类连续波形时刷新率就成了关键指标。用延时循环直接翻转GPIO是最简单的方式但在高波特率下会占满CPU。更高效的方式是借助定时器中断把波形表一点一点喂给GPIO。STM32内置DAC经常配合定时器触发使用比如TIM6更新事件可以作为DAC的触发源。如果你手上只用了GPIO加R-2R同样可以借用这个思路配置一个定时器中断在中断服务函数里取出波形表的下一个样本写入GPIO的ODR从而实现定时刷新。// 定时器6中断里刷新R-2R输出 uint8_t sine_wave[256]; // 预填充的正弦波表 volatile uint16_t wave_index 0; void TIM6_IRQHandler(void) { TIM6-SR ~TIM_SR_UIF; GPIOA-ODR (GPIOA-ODR 0xFF00) | sine_wave[wave_index]; wave_index (wave_index 1) 0xFF; }中断方式的刷新率上限主要取决于定时器中断服务函数的执行时间和GPIO翻转速度。对STM32F4跑168MHz来说GPIO每秒翻转几十兆次不是问题定时器中断如果只有几条指令做到几百k样本每秒也很轻松。R-2R网络本身因为寄生电容建立时间大约在1~2微秒量级所以最终瓶颈还是网络和运放不是GPIO。如果想进一步把CPU解放出来有人会想用DMA自动把波形表搬到GPIO。这个想法很好但要注意STM32F4这类型号的DMA请求映射里通常没有GPIO这个外设不能直接在定时器触发下DMA传输到ODR。做之前一定要查参考手册的DMA请求映射表确认你用的型号支持否则白忙一圈。4.3 与内置DAC的比较DHR寄存器与R-2R的对照用过STM32内置DAC的人都知道DHR寄存器全称是Data Holding Register数据保持寄存器。软件先把要输出的码值写进DHR然后由触发源比如定时器更新事件把DHR的值锁存到DORDAC模拟部分再根据DOR里的码值输出对应电压。这个先写保持寄存器、再定时锁存的机制保证了波形输出和定时器同步不会出现写到一半被截断的毛刺。对照一下R-2R的GPIO方案GPIO的ODR就相当于DOR你的波形表相当于DHR定时器中断里取波形表写入ODR就等价于定时器触发锁存DAC输出。区别只是内置DAC把所有电阻网络和运放都集成进了芯片你只需要关心数字码怎么写而R-2R方案里电阻网络和运放是你自己搭的数字侧的代码逻辑反而大同小异。这种对照对项目移植很有帮助。如果哪天你的R-2R方案精度不够需要升级到STM32内置DAC或者外挂DAC芯片驱动层的代码结构可以几乎不改造只是把r2r_dac_write()换成DAC_SetChannel1Data()或者SPI写命令而已。5. 实测校验与误差修正用数字万用表和示波器把指标找回来5.1 建立时间、毛刺和负载效应示波器上能看到什么搭好R-2R网络之后别急着写上层逻辑先上示波器验证几个硬指标。第一个是建立时间。用GPIO从0x00直接跳到0xFF观察运放输出从10%上升到90%的过渡时间如果R取10kΩ加上运放的压摆率限制通常要1到2微秒。如果看到明显过冲或者振铃多半是运放带宽过高加上PCB寄生电容引起可以在反馈电阻上并联一小电容来抑制。第二个要看的是毛刺。把波形从0x7F跳到0x80这两个码值只差LSB但二进制从01111111变成10000000所有位都翻转。如果各个位的GPIO翻转速度不完全一致网络中会短暂出现一个错误的中间码值输出上表现为一个窄尖峰。集成DAC芯片内部会用去毛刺电路处理这个问题DIY方案里想完全消除很难但可以通过减小GPIO翻转时间差或者在输出端加低通滤波器来压制幅度。第三个必测项是负载效应。断开运放直接拿万用表量网络输出端数值变化范围会很大接上运放后再量一次得到的就是干净的DAC输出。这个对比能直观理解为什么运放缓冲不是可选项而是必选项。5.2 两点校准与最小二乘校准电阻用1%的普通货色温度一变精度就飘所以量产或者现场使用前做一次软件校准能把大部分固定误差找回来。最简单的两点校准思路是先输出0x00用万用表精确测量输出电压V0再输出0xFF测量满量程电压Vmax。实际输出电压和理想值Vref×code/256之间存在一个近似线性的关系通过这两个测量点可以算出实际增益G和失调B// 两点校准测量得V0和Vmax计算G和B // 实际电压 G * 理想电压 B float G (Vmax - V0) / (Vref - 0); // 理想满量程减去理想零点 float B V0; float calibrated_voltage G * (Vref * code / 256.0f) B;两点校准能修正增益误差和零点偏移但修不了非线性。所谓非线性是指电阻比例失配导致某些位的权重和理论值不一样这种情况在输出曲线上表现为中间段向上凸或向下凹。想要修正非线性需要做多点校准通常采集几十个码值的实测电压用最小二乘法拟合成一条曲线或者做成查表。像0x00、0x10、0x20这样每隔16个码采一次256个码共16个点就够8位DAC用拟合出来的效果不错。实操时还有个细节测量要等输出完全稳定转换器建立需要微秒级时间但万用表积分需要时间建议每个码值延迟几十毫秒再读数不然数据会很散。另外校准用的万用表精度一定要比待测的DAC高至少要4位半表最好用6位半否则校准本身引入的误差比电阻差的还大。5.3 常见问题排查表中的经验最后整理一份我在调试R-2R DAC过程中遇到最多的问题排查表都是真实项目中踩过的按症状找原因能省不少时间。症状可能原因排查方向输出电压整体偏低网络输出端没有被运放缓冲负载分压接上高输入阻抗缓冲运放再测量最高位权重明显不足输出取点位置选在了LSB端改为从MSB端取输出单调性没问题但中点附近非线性大电阻精度不够高位电阻失配换0.1%电阻或做多点校准输出波形出现过冲运放带宽过高反馈补偿不足反馈电阻并联几pF~几十pF电容0x7F和0x80跳变时有窄毛刺多个GPIO翻转时间不一致加输出低通滤波或优化GPIO不同步问题输出随温度漂移明显电阻温漂不匹配或Vref温漂换同一批次排阻加基准源数字码增加时输出偶尔倒退检查是否电阻虚焊或GPIO接反用万用表逐位验证权重别只看整体曲线GPIO高电平压降大单片机的GPIO负载能力不足或Ron过大增大R值或改用模拟开关外接基准源如果你在调试中遇到输出变化范围和理论值对不上这种问题先不要怀疑公式先测每一单独位的权重电压。把某一位置1其余全部置0量一下输出再和理论值对比这比看整体曲线更容易定位是哪一位电阻或哪根线出了毛病。根据我个人经验R-2R电阻网络DAC做完之后一定要把每一项测量数据记录下来。尤其是校准用的V0和Vmax值保存到MCU的Flash里下次上电直接用校准参数运行。这样即使用的是普通电阻最终精度也能接近10位DAC的水平。如果你后续想进一步扩展还可以把多组R-2R网络和模拟开关组合起来做成程控增益放大器或者多路波形发生器原理和驱动代码都不用改只要把数据表和通道扩展逻辑加进去就行。