
调模拟电路的时候最怕的不是电路不工作而是“该稳的地方稳不住”。上个月帮朋友调一块传感器信号调理板需要在差分放大器入口叠一个2.5V共模偏置第一版图省事用了两颗0.1%精密电阻分压结果温度循环一跑输出跟着漂了十几毫伏整个系统零点全偏了。后来索性把偏置改成STM32C542R片上DAC固定电压输出不用改任何硬件寄存器一填电压就稳稳钉在2.5V。这篇就把DAC固定电压输出的路子彻底捋一遍从为什么选DAC不选电阻分压到CubeMX配置、电压换算、HAL库代码、实测误差最后再讲清楚PCB布局、电源噪声和时钟抖动会在什么地方坑你。最近一直在做STM32C542R这颗料的小项目DAC这块也算踩了不少。希望这篇能给正在用同型号或隔壁系列片子的朋友省点时间。1. 为什么推荐用DAC搞定固定电压而不是电阻分压1.1 哪些项目真的需要固定电压输出很多人一看到“固定电压”四个字第一反应是不就是个分压电路嘛。但实际项目里“固定电压”这四个字背后往往跟着不同的需求我只说这几年真正遇到过的一个是传感器偏置。像电桥、热敏电阻、应变片这些传感器后面接的仪表放大器或差分放大器通常需要一个共模偏置电压把信号垫到ADC能采的范围内。这种电压一旦漂移零点跟着漂整个采集链路都废。一个是比较器阈值。当过压保护、欠压检测、窗口比较器里的参考阈值用传统做法是TL431加电阻分压但若要支持多个档位或做软件微调电阻方案就要换挡麻烦。DAC则可以随时用软件调阈值对产线调试特别友好。还有一个是模拟校准源。测试工装里要给被测板提供精确的直流电平用可调稳压电源要么太贵要么太大单片机上集成的DAC把电压设置好走线拉过去就能当简易基准用。所以固定电压输出听着基础实际上遍布在传感器接口、电源保护、工装夹具这些地方。1.2 电阻分压、PWMRC和片上DAC怎么选很多人问我固定电压这种事电阻分压不香吗PWM加个RC滤波不也一样我都用过直接给结论方案输出纹波可调性温漂/精度成本适用场景精密电阻分压无纹波不可调或需换电阻取决于电阻精度与温漂系数极低大批量、单一电压、默认固定PWMRC滤波有纹波RC时间常数越大纹波越小软件可调受MCU电源和IO驱动影响低不需要严格稳定、允许纹波的场合片上DAC开关纹波极低但受基准噪声影响软件任意调典型INL/DNL约±1~±2LSB级只需软件资源多档位、需微调、需闭环控制我踩过的最深一个坑就是PWMRC方案。当时想着用20kHz PWM加两级RC滤波输出1.2V给比较器做阈值电容选得不够大结果示波器上一看有大约50mV的纹波直接把比较器阈值抖得来回翻转。后来换成DAC输出纹波问题直接消失了。PWMRC不是不能用而是要把纹波压到可接受范围需要仔细算RC时间常数和负载阻抗工程上的约束比看起来多。至于片上DAC和外挂DAC芯片的选择我的经验是如果需求是16位以上分辨率、多通道严格匹配、或者要求极低噪声比如仪表级参考源才考虑外挂。一般12位、单个通道、稳定电压输出片内DAC完全够用还省一块芯片和一个I2C/SPI接口。2. 输出电压是怎么算出来的基准、对齐、缓冲三个决定参数2.1 一个公式看懂DAC输出STM32C542R的DAC核心是一个电阻分压网络软件往DOR寄存器里写一个数字值模拟输出引脚上就出现对应电压。换算关系是Vout VREF × DOR / 2^N其中N是分辨率。默认12位模式下N12分母是4096也可以配置成8位模式分母就是256。这里的DOR是数据输出寄存器里的实际值计算时用的是目标电压和基准电压反推DOR Vout / VREF × 4096四舍五入取整。举个例子。假设VREF是3.3V想输出1.000VDOR 1.000 / 3.3 × 4096 1241.2取1241然后HAL_DAC_SetValue里Data参数填1241输出端就能测到约1V的电压。这里有个小细节容易让人纠结公式里分母用4096还是4095。严格讲DOR最大值是4095对应输出是4095/4096×VREF≈0.99976×VREF所以DOR永远写不出真正的满量程VREF。用4096算出来的DOR在目标电压等于VREF时会得到4096这时候代码里需要做个钳位限制到4095。用4095算则天然不会越界。两种写法对固定电压输出来说也就一个LSB的差别但如果你在做精密电压源还是建议钳位一下避免寄存器写溢出值。注意一个基本换算12位下每个LSB对应的电压是VREF/4096。3.3V基准时约0.806mV2.5V基准时约0.610mV。做按键微调时每次加减1个LSB输出变化不到1mV手感很细腻。2.2 基准电压从哪来VREF、VDDA和内部基准输出公式里的VREF是决定精度的关键。STM32C542R的VREF通常就是芯片的一个引脚如果硬件设计上把VREF和VDDA直接连在一起那么DAC输出的精度就完全取决于模拟电源的精度和噪声。我建议做固定电压输出时VREF这块单独处理。最简单的做法是给VREF引脚接一个RC滤波器比如10Ω串联电阻加10uF钽电容和100nF陶瓷电容并联尽可能把数字电源上的开关噪声滤掉。追求更高精度的场景可以直接把VREF接到外部精密基准源比如REF5030输出3V、TL431搭出2.5V这样DAC输出的绝对值精度会好很多。部分STM32型号内部带基准电压缓冲器VREFBUF能从芯片内部生成2.048V、2.5V或2.9V的基准不需要外部基准芯片。C542R具体支持与否建议拿着你手上的参考手册确认一下有的话直接把DAC基准切到内部基准硬件上能省不少事。不过内部基准同样存在初始误差和温漂要求高的场合依然建议外部基准。2.3 输出缓冲打开还是关闭DAC输出级有一个可选输出缓冲放大器CubeMX里对应Output Buffer选项默认Enable。这个缓冲的作用是降低输出阻抗让DAC能够直接驱动下游电路。日常做固定电压输出我建议默认Enable。缓冲打开后DAC输出阻抗很低即使后端接了一个10kΩ的负载电压跌落也很小。如果关闭缓冲输出阻抗会变得很高负载稍微重一点电压就往下掉。但缓冲不是没有代价。第一输出缓冲有失调电压典型的几个mV级别会让实测输出电压和理论值有偏差。第二缓冲到接近电源轨时会进入饱和区线性度变差这个后面第5章专门讲。第三缓冲会消耗少量静态电流对超低功耗设备需要考虑。所以结论是普通固定电压开缓冲需要极致低功耗或输出接的是高阻负载比如运放输入端关缓冲但后面必须得有个外部放大器承接。3. CubeMX里把DAC模块配出来引脚、时钟与生成代码3.1 引脚与通道的对应关系STM32C542R的DAC1外设有两个输出通道对应关系在CubeMX引脚图上一目了然DAC1_OUT1一般在PA4DAC1_OUT2一般在PA5。用CubeMX配置时直接在芯片引脚图上点PA4和PA5能选的复用功能里找到DAC对应项选中后引脚会变色表示复用功能已锁定。我见过不少新手朋友在这一步翻车以为要额外配置GPIO为输出模式。其实生成代码后HAL的MspInit函数会自动把这个引脚设置成模拟模式GPIO_MODE_ANALOG你只需要把DAC外设打开不用手动干预GPIO方向。3.2 DAC关键参数怎么选CubeMX中用DAC1外设时有一堆配置项固定电压输出场景下核心就三个Trigger选None。这意味着DAC更新完全由软件控制我调用HAL_DAC_SetValue写一次寄存器输出就变一次不需要外部事件或定时器去触发。如果你想以后做正弦波等周期性波形再考虑Timer或TRGO触发固定电压用不上。Output Buffer选Enable。原因前面说了降低输出阻抗驱动能力更强。DAC_Align选12位右对齐。数据寄存器往低12位放数据最直观计算也方便。如果想用8位分辨率也可以选8位右对齐但固定电压输出通常不在乎那点数据宽度12位更通用。生成代码后你会在main.c里看到类似这样的初始化函数void MX_DAC1_Init(void) { DAC_ChannelConfTypeDef sConfig {0}; hdac1.Instance DAC1; HAL_DAC_Init(hdac1); sConfig.DAC_SampleAndHold DAC_SAMPLEANDHOLD_DISABLE; sConfig.DAC_Trigger DAC_TRIGGER_NONE; sConfig.DAC_OutputBuffer DAC_OUTPUTBUFFER_ENABLE; sConfig.DAC_ConnectOnChipPeripheral DAC_CHIPCONNECT_DISABLE; sConfig.DAC_UserTrimming DAC_TRIMMING_FACTORY; HAL_DAC_ConfigChannel(hdac1, sConfig, DAC_CHANNEL_1); }DAC_SampleAndHold这里一般选DISABLE只在需要低功耗采样保持的场景下才会启用。DAC_UserTrimming选FACTORY使用芯片出厂校准。3.3 时钟树里被忽视的DAC时钟在CubeMX时钟树界面里DAC模块挂在APB总线上保证对应APB时钟已开启即可通常生成代码时CubeMX会自己处理好。固定电压输出这种场景DAC时钟的精度和抖动其实对最终电压几乎没有影响因为输出电平只跟DOR寄存器值、基准电压有关跟DAC模块跑多快无关。但如果你后续要切到定时器触发或者DMA连续输出波形时钟的抖动就会实实在在影响模拟输出波形质量。我见过有人固定电压输出没问题一换成定时器触发的锯齿波输出就有毛刺查了半天发现是APB时钟树里给定时器的时钟源配置不合理分频器选得太大导致定时器计数到某个边沿时出现了微小抖动。所以本章先把DAC时钟这点钉死了做固定电压没问题做波形时别忽略。3.4 生成的代码结构CubeMX生成完工程后在main函数里你会看到int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DAC1_Init(); // ... }DAC初始化的最后一次调用是HAL_DAC_Start这个函数会把DAC模块真正使能让模拟输出引脚开始工作。很多第一次接触DAC的人只做了MX_DAC1_Init忘了调HAL_DAC_Start结果量不到电压还以为是芯片坏了。这个函数必须调用。HAL_DAC_MspInit里会做两件事开启DAC外设时钟配置DAC引脚为模拟模式。如果没有特殊需求这部分不用改。4. HAL库代码、万用表实测与一次性校准4.1 最小可用的固定电压输出代码跑通固定电压输出的最小代码量其实比多数人想的少。初始化完DAC后核心就两行HAL_DAC_Start(hdac1, DAC_CHANNEL_1); HAL_DAC_SetValue(hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 3103);第一行启动DAC通道第二行把数据写入寄存器。3103这个数是按2.500V输出、3.3V基准算出来的2.5 / 3.3 × 4096 ≈ 3103。实际应用中我会封装一个计算函数避免每次手动算寄存器值#define VREF_MV 3300 #define DAC_MAX 4095 static uint32_t dacCalcValue(float voltageMv) { uint32_t dac (uint32_t)(voltageMv / VREF_MV * 4096.0f 0.5f); if (dac DAC_MAX) { dac DAC_MAX; } return dac; }这样写的好处是后期如果换成其他基准电压只需要改VREF_MV一个宏所有计算自动跟着变。调试阶段我还喜欢加个按键微调逻辑。因为固定电压做成之后往往需要根据实际电路微调一两个毫伏用按键改比反复烧录固件效率高很多while (1) { if (buttonPressed()) { currentDac 124; // 每次加0.1V if (currentDac DAC_MAX) currentDac 0; HAL_DAC_SetValue(hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, currentDac); } }124这个数字怎么来的0.1V / 3.3V × 4096 ≈ 124正好是0.1V对应的LSB数量。按一下键加0.1V方便对比观察输出和负载的反应。4.2 实测万用表读到的值和理论值总有偏差代码写好之后用万用表实测我拿到的一组数据长这样基准实测3.298V目标电压寄存器值DOR万用表实测偏差0.500V6210.4986V-1.4mV1.000V12410.9987V-1.3mV2.500V31032.4972V-2.8mV整体看偏差在-0.1%到-0.3%之间绝对电压精度确实不算高。这个偏差来源主要有几块VREF实际值和标称值有偏差DAC自身的INL/DNL误差通常±1~±2LSB输出缓冲的失调电压几个mV级别万用表的固有误差最后还有面包板或杜邦线的接触电阻。所以做DAC固定电压我从来不会期待“理论1.000V上电就是精准的1.0000V”。能测到1.000V附近对大多数应用已经够用。4.3 要绝对精度做一次软件校准如果项目里对电压绝对值有硬性要求比如0.1%以内那就得做一次软件校准。固定电压输出做校准非常简单因为它只需要校一两个点。校准流程我是这么做的第一步用高精度万用表实测当前基准电压VREF_real代入DOR Vout / VREF_real × 4096这样先消除基准偏差。第二步设置目标电压Vout测实际输出Vout_measured。第三步计算修正系数k Vout / Vout_measured。以后每次设置电压时用目标电压乘以k再代入公式反算DOR。说得更直白点如果我想输出1.000V实测只有0.9987V那么修正后我就按1.0013V来算寄存器值输出就会被顶回1.000V附近。这个修正系数可以存在Flash里掉电不丢失。产线校准时一台设备跑一遍这个流程几十秒搞定。校准只对当前板卡有效换一块板子因为基准电压、缓冲失调都有差异最好重新校准一次。如果批量大可以每个板子在产线烧录时执行一次校准程序。5. 三个最容易翻车的细节饱和、带载与基准噪声5.1 输出缓冲的“顶部饱和”现象这是最容易忽略的问题。DAC输出缓冲虽然能把输出驱动到接近基准电压但并不是线性的。当目标电压超过VREF某个比例后输出缓冲进入饱和区增益非线性会明显加剧。我用3.3V基准实测时输出设置在3.1V以上实际电压增长明显变缓每增加1个LSB输出变化比理论值小。也就是说你期望输出3.250V实测可能是3.198V而且不是整体偏差是输出级本身“没劲了”。原因也很好理解缓冲放大器输出的最高电压受内部晶体管压差限制不可能完全到电源轨。所以实用区间我会限制在基准电压的5%到95%。做固定电压输出时如果目标电压接近基准要么把基准往上提比如用内部VREFBUF生成一个更高的基准要么接受非线性并单独校准。5.2 负载电阻不是越小越好DAC输出缓冲驱动能力有限虽然比关缓冲好很多但也不是随便接负载都能稳住电压。我在一块测试工装上踩过这个坑。当时DAC输出2.5V后端为了做一个电流检测直接接了一个1kΩ电阻到地。想象中2.5V/1k2.5mA这点电流DAC扛得住但实测输出电压变成了2.42V左右掉了80mV。原因是缓冲输出级有一定等效内阻输出电流增大时内部压降也在增大电压就被拉低了。所以接负载前要估算一下负载电流。DAC输出级的驱动能力具体是多少mA不同芯片不一样建议查对应型号数据手册但经验上说想保持稳定输出外部负载电阻尽量不要小于5kΩ到10kΩ。如果后端确实需要驱动重负载正确做法是DAC输出接一个外部Rail-to-Rail运放由运放去驱动负载DAC只负责给出电压信号。5.3 VREF/VDDA上的噪声直接进DAC输出固定电压输出时DAC的DOR寄存器值不变但输出电压并不是绝对平的直流。你拿示波器看DAC输出引脚能测到高频毛刺或周期性纹波如果排除接地环路问题大概率是VREF或VDDA上的噪声直接耦合进了DAC内部电阻网络。这个噪声源很多板上DC-DC开关纹波、数字GPIO翻转时产生的电源扰动、通信接口的瞬态电流。特别是很多板子VDDA直接和VDD接在一起数字电路跑起来后模拟电源被污染得厉害。处理办法分两级。硬件上VDDA最好独立滤波常见做法是用磁珠或小电阻把模拟电源和数字电源隔开VDDA引脚放0.1uF1uF电容VREF引脚再加一级RC滤波。软件上可以选择在系统噪声比较小的时刻去更新DAC寄存器比如避开ADC采样、避开通信发送窗口虽然固定电压不常改值但校准或微调时这个策略能减少输出跳变的毛刺。6. 绕着热搜走一圈定时器触发、PCB布局与任意波形扩展6.1 为什么那么多人搜“DAC由定时器6启动”我在查DAC相关经验时发现“STM32F4单片机DAC由定时器6启动的初始化程序”这个搜索词很热。定时器作为DAC的触发源核心作用是让DAC的DOR寄存器在一个精确的时刻被更新。但固定电压输出其实完全不需要这个功能。固定场景下电压值不变用软件触发写一次寄存器就够了多此一举地加定时器反而引入了触发源配置、定时器中断等一堆潜在问题。所以如果你只是要做固定电压Trigger配置成None就行。那什么时候会用到定时器触发典型场景是任意波形输出。正弦波、三角波、锯齿波这类周期性波形要求DAC以固定频率不断更新输出电压。如果用主循环里whiledelay去刷波形频率和相位都不可控抖动大。用定时器触发则每次定时器计数溢出自动把DHR寄存器里的数据搬到DOR输出更新间隔精确可控。STM32C542R上同样有这套机制CubeMX里在DAC配置中将Trigger选为Timer即可。6.2 电源噪声与时钟抖动PCB布局的三个要点DAC这种模拟外设最终输出质量很依赖PCB布局。我调过一块自己画的板子DAC固定电压输出示波器上看有周期性尖峰排查到最后是DC-DC电感的开关节点离DAC输出走线只有5mm磁场和电场都耦合过去了。总结了三个对我帮助最大的布局要点第一个VREF和VDDA的滤波电路要靠近芯片引脚摆放。去耦电容离引脚越近寄生电感越小滤波效果越好。VREF走线尽量短、尽量粗避免和其他数字信号线平行走向。第二个模拟区域和数字区域在接地上要单点汇接。不要在DAC输出附近留一整块被数字回流电流污染的地平面模拟地参考点要干净。如果两层板不好做分割至少保证数字信号的回流路径不要经过DAC的模拟信号区域。第三个开关电源的走线要远离DAC输出和VREF。DC-DC的电感、续流二极管、开关节点都是强噪声源距离至少拉开5到10mm换层时也不要和模拟线在相邻层平行重叠。这条是我用示波器换来的教训一开始图省事走线绕了近路结果噪声全耦合过来了。时钟抖动这块DAC本身不依赖外部高速时钟定时器触发波形场景才需要关注。如果发现定时器触发出来的波形边缘抖动大除了定时器时钟源配置还要检查给MCU提供时钟的晶振或振荡器附近有没有放置去耦电容、有没有被数字开关噪声干扰。6.3 扩展DAC加定时器加DMA做任意波形最后随手提个扩展方向。DACDMA是STM32输出任意波形的经典组合原理很简单把要输出的波形数据算好放在一个数组里用定时器触发DMA传输DMA每来一次触发就从数组里取一个数写入DAC数据寄存器数组读完后DMA循环模式自动从头开始波形就这样持续输出。查STM32F103 DAC正弦波相关搜索词的朋友大多就是想要这个效果。正弦波表一般取256个点每个点用12位精度算出来放在const数组里。用定时器配置输出更新频率比如50kHz更新率256点一个周期波形频率就是50k/256≈195Hz。想调频率就调整定时器分频想调幅值就把每个点的值按比例缩放。不过这篇的主题是固定电压我就先不展开波形表怎么算了知道DAC具备这个扩展潜力即可。下一篇如果有时间我再单独写一下正弦波查表DMA的实现细节。固定电压输出这件事在STM32C542R上最核心的思路其实就三条把基准电压和DOR值的关系算清楚配置好输出缓冲和引脚复用测量后做一次必要的校准。至于PCB布局和电源噪声属于做得好了看不出差别、做不好示波器上立竿见影的部分值得多花一点心思。祝各位调板顺利输出稳定。