尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32 DAC数模转换实战:从寄存器配置到波形生成

STM32 DAC数模转换实战:从寄存器配置到波形生成 1. 项目概述从数字到模拟的桥梁搭建玩STM32的兄弟如果你已经搞定了ADC模数转换把现实世界的电压信号读进来处理得明明白白那下一步就该琢磨怎么把单片机里那些冷冰冰的数字计算结果再变回现实世界能感知的模拟信号了。这就是DAC数模转换要干的活。我这次折腾的就是基于STM32的DAC模块做个基础的数模转换实验。说白了就是让单片机自己生成一个电压或者输出一个波形去驱动个喇叭、控制个电机转速或者给别的模拟电路当个基准源。STM32内部集成的DAC对于大多数F1、F4系列来说算不上多高精度的玩意儿12位的分辨率转换速度也就那样。但它胜在集成度高、用起来方便对于很多不需要超高精度和速度的应用场景比如音频信号发生、简单的闭环控制、可调基准电压输出那是完全够用的。这次实验我的目标不是追求极限参数而是把DAC从寄存器配置到电压输出的整个流程打通理解每一个环节背后的“为什么”顺便把那些容易踩的坑提前标记出来。我会用最直接的寄存器操作方式来写虽然HAL库用起来省事但寄存器操作能让你更清楚地看到数据是怎么从内存走到DAC输出引脚的这对理解底层硬件非常有帮助。2. DAC核心原理与STM32实现机制拆解2.1 数模转换的基本思想如何用数字量“拼凑”出模拟量DAC干的事其实就是“按图索骥”。我们给DAC一个数字代码比如一个12位的数0x0FFF十进制4095DAC内部就会根据这个代码输出一个对应的电压值。这个对应关系核心依赖于两个东西参考电压Vref和DAC的位数。想象一下我们把Vref通常是3.3V平均分成4096份因为2^12 4096。每一份就是一个最小电压增量叫做LSBLeast Significant Bit。那么1 LSB Vref / 4096。当我们输入数字代码0时DAC输出0V输入代码4095时DAC输出一个无限接近Vref的电压实际是Vref * 4095/4096。输入中间的任何代码输出就是Vout (DOR / 4096) * Vref。这里的DOR就是我们写入DAC数据寄存器的值。这就是最简单的“数字权重求和”型DAC的基本原理STM32内部DAC的核心也基于此虽然具体电路是电阻网络和开关的组合。这里有个关键点Vref的选择直接决定了输出范围。对于STM32Vref通常连接到VDDA模拟电源电压。如果你的VDDA是3.3V那么DAC的输出范围就是0~3.3V。如果你想得到负电压或者更高的电压就需要在DAC输出后面加运放电路做调理了这是后话。2.2 STM32 DAC模块架构与数据通路详解以常见的STM32F103系列为例它通常有2个DAC通道DAC1, DAC2。每个通道的核心部件包括数字输入部分主要是数据寄存器。这里有个容易混淆的点。我们程序员直接操作的是DAC_DHRxData Holding Register数据保持寄存器。写入这个寄存器后数据不会立刻去转换。它只是一个缓冲。触发与同步控制这是控制“何时转换”的关键。数据从DAC_DHRx转移到DAC_DORxData Output Register数据输出寄存器需要一个触发信号。这个触发源可以选软件触发写个标志位、定时器触发TIMx的TRGO事件、外部中断线触发。用定时器触发是生成波形如正弦波、三角波的关键因为它能提供精确的、周期性的转换节奏。模拟输出部分DAC_DORx里的数字量被送到内部的电阻网络型DAC转换成模拟电流再经过一个输出缓冲放大器变成电压从DAC_OUTx引脚对应PA4或PA5送出去。数据通路流程再捋一遍你写代码给DAC_DHR12R1寄存器赋值 - 在使能了DAC且某个触发事件到来时 -DAC_DHR12R1的值被硬件自动加载到DAC_DOR1寄存器 -DAC_DOR1的值被送入电阻网络进行数模转换 - 转换结果经过输出缓冲器送到PA4引脚。注意输出缓冲器Buffer默认是使能的。它的作用类似于一个电压跟随器能提高DAC输出的驱动能力降低输出阻抗。但是缓冲器会使输出电压范围受到限制无法达到真正的0V和Vref并且会引入一定的响应时间。在对输出范围要求严格需要轨到轨或对速度要求极高的场合可以考虑关闭缓冲器但此时你必须自己外接一个高输入阻抗的运放来驱动负载。2.3 关键寄存器映射与功能解析以通道1为例用寄存器开发就得跟这些内存地址打交道。我们挑几个最核心的寄存器看看DAC控制寄存器 (DAC_CR)EN1位0DAC通道1使能位。1使能0关闭。这是总开关。BOFF1位1输出缓冲关闭位。1关闭缓冲0使能缓冲。根据你的需求决定。TEN1位2触发使能位。1使能触发0关闭触发使用软件触发或自动加载。如果想用定时器触发这里必须置1。TSEL1[2:0]位5:3触发源选择。比如001代表TIM2 TRGO010代表TIM4 TRGO等。具体看芯片参考手册。WAVE1[1:0]位7:6噪声/三角波生成使能。我们做基础输出就设为00禁止波形生成。DMAEN1位12DMA使能。如果想用DMA自动搬运波形数据到DAC这个要置1。DAC通道1 12位右对齐数据保持寄存器 (DAC_DHR12R1)这是我们最常写的寄存器。地址是0x40007408对于F103。你把12位数据0~4095写在DACDHR[11:0]这低12位就行。高20位保留。写这个寄存器数据只是暂存。DAC通道1数据输出寄存器 (DAC_DOR1)这个寄存器是只读的它反映了当前DAC实际正在转换的数字值。你写DHR触发后硬件会自动加载到DOR。直接写DOR是无效的。DAC软件触发寄存器 (DAC_SWTRIGR)SWTRIG1位0软件触发位。写1产生一个触发事件需要TEN10。当TEN10时你写完DHR再写一下这个位数据就立刻加载到DOR并转换输出。理解这几个寄存器之间的关系配置DAC就成功了一大半。3. 硬件设计与软件配置实操要点3.1 最小系统搭建与引脚分配硬件上非常简单因为DAC是片内集成的。MCU以STM32F103C8T6蓝桥杯常用板为例DAC通道1输出在PA4引脚通道2在PA5引脚。电源确保模拟电源部分干净。VDDA和VSSA必须连接到稳定的3.3V和GND最好通过磁珠或0欧电阻与数字电源VDD隔开并在靠近芯片引脚处放置一个10uF钽电容一个0.1uF陶瓷电容去耦。这是保证DAC输出稳定、噪声低的基础很多人忽略这点结果输出波形毛刺很多。输出引脚PA4DAC_OUT1就是我们的输出点。你可以直接用万用表测电压或者接一个运放电路进行放大、滤波后再接负载。参考电压对于F103DAC的参考电压Vref内部连接到VDDA。所以VDDA的电压精度和稳定性直接决定了DAC的输出精度。如果项目对精度要求高可以考虑使用外部高精度基准电压源接到VDDA或者使用带有独立Vref引脚的型号如STM32F4。实操心得在面包板或洞洞板上搭建电路时万用表是必备的。先不接任何复杂负载直接测量PA4对地的电压验证DAC基本功能是否正常。有时候软件配置对了但硬件上VDDA没接好也会没输出。3.2 寄存器版软件驱动逐行解析下面我们抛开库函数用最原始的寄存器操作写一个DAC通道1的初始化函数并实现一个设置输出电压的函数。// 1. DAC寄存器基地址定义 (参考STM32F103参考手册) #define DAC_BASE 0x40007400 #define DAC_CR (*(volatile unsigned int *)(DAC_BASE 0x00)) #define DAC_SWTRIGR (*(volatile unsigned int *)(DAC_BASE 0x04)) #define DAC_DHR12R1 (*(volatile unsigned int *)(DAC_BASE 0x08)) #define DAC_DOR1 (*(volatile unsigned int *)(DAC_BASE 0x2C)) // 2. 相关时钟和GPIO寄存器 (DAC挂载在APB1上) #define RCC_BASE 0x40021000 #define RCC_APB1ENR (*(volatile unsigned int *)(RCC_BASE 0x1C)) #define GPIOA_BASE 0x40010800 #define GPIOA_CRL (*(volatile unsigned int *)(GPIOA_BASE 0x00)) // 3. 初始化DAC通道1 (软件触发输出缓冲使能) void DAC1_Init(void) { // 3.1 开启DAC时钟 (DAC挂在APB1上) RCC_APB1ENR | 1 29; // 设置DACEN位为1 // 3.2 配置PA4为模拟输入模式 (这是DAC输出引脚的特殊要求) // 虽然引脚输出模拟信号但GPIO必须配置为模拟输入模式以断开内部数字电路 GPIOA_CRL ~(0xF (4*4)); // 清除PA4原有的配置位 (CRL控制低8位PA4是第4个引脚每组配置占4位) GPIOA_CRL | (0x0 (4*4)); // CNF[1:0]00, MODE[1:0]00 - 模拟输入模式 // 3.3 配置DAC控制寄存器DAC_CR DAC_CR 0; // 先清空避免残留配置干扰 // 不使能触发 (TEN10), 使用软件触发或自动加载 // 不产生噪声和三角波 (WAVE1[1:0]00) // 使能输出缓冲 (BOFF10) // 最后使能DAC通道1 (EN11) DAC_CR | 1 0; // 仅使能DAC通道1其他位保持0 // 初始化完成后DAC_DOR1默认是0所以输出应该是0V。 } // 4. 设置DAC通道1输出电压 (软件触发方式) // 参数: vol - 期望电压值 (单位: mV), 范围 0 ~ 3300 (对应0V~3.3V) void DAC1_SetVoltage(uint16_t vol_mV) { uint32_t temp; // 4.1 将电压值转换为12位数字量 // 公式: Digital_Value (vol_mV / 3300) * 4096 // 为避免浮点数使用整数运算: temp (vol_mV * 4096) / 3300 temp (uint32_t)vol_mV * 4096; temp temp / 3300; // 此时temp范围是0~4095 // 4.2 检查边界防止计算溢出或越界 if(temp 4095) temp 4095; // 4.3 写入数据到保持寄存器DAC_DHR12R1 (12位右对齐) DAC_DHR12R1 temp; // 4.4 产生软件触发将DHR的值加载到DOR并开始转换 // 因为初始化时TEN10所以软件触发有效 DAC_SWTRIGR | 1 0; // 置位SWTRIG1位 // 软件触发是“写1触发”硬件会自动清除该位所以无需手动清零。 }代码关键点解析GPIO模式GPIOA_CRL的配置是新手最容易出错的地方。DAC输出引脚必须设为模拟输入模式而不是推挽输出。这是因为设为模拟输入后引脚内部的施密特触发器、上下拉电阻等数字输入部分被禁用引脚直接连接到模拟外设避免了数字噪声干扰。数据对齐我们用的是DHR12R1即12位右对齐寄存器。写数据时只需关心低12位。STM32还有8位右对齐、12位左对齐的寄存器用于适配不同数据宽度我们最常用的是12位右对齐。触发机制本例使用最简单的软件触发。每次设置电压都需要写一次SWTRIGR。如果想让DAC自动、连续地输出比如用DMA就需要使能硬件触发TEN11并选择触发源。3.3 进阶配置使用定时器触发与DMA生成波形让DAC输出一个固定电压只是第一步更有用的是让它输出变化的波形比如正弦波。这就需要定时器来提供稳定的“节拍”用DMA来自动搬运波形数据表。配置思路准备波形数据表在内存中定义一个数组比如sin_table[100]里面预先计算好一个周期正弦波对应的100个DAC数字值0-4095。配置定时器以TIM2为例配置为更新事件UEV触发。计算好定时器频率。假设我们要生成1kHz的正弦波一个周期100个点那么定时器触发频率就需要100kHz。根据系统时钟和分频值算出自动重载值ARR。配置DAC使能DAC通道1使能触发TEN11触发源选择TIM2 TRGO。使能DMADMAEN11。配置DMA将DMA通道查手册确定哪个通道对应DAC1配置为从内存sin_table到外设DAC_DHR12R1的传输传输宽度为半字16位循环模式每次定时器触发就传输一个数据。启动启动DMA启动定时器。然后DAC就会以100kHz的速率自动、循环地将sin_table中的数据输出在PA4上就能用示波器看到一个1kHz的正弦波。避坑技巧使用DMA传输时数据寄存器地址必须写对。DAC的数据保持寄存器DAC_DHR12R1的地址是0x40007408。另外确保DMA传输的数据宽度和寄存器宽度匹配。如果sin_table是uint16_t类型DMA就配成半字传输。4. 核心实验环节与现象验证4.1 实验一基础直流电压输出与测量这是最简单的验证实验。软件调用上面的DAC1_Init()初始化然后在主循环里调用DAC1_SetVoltage(1500)试图输出1.5V。硬件万用表黑表笔接地GND红表笔接PA4引脚。现象万用表显示电压值应在1.5V左右。由于DAC精度、参考电压误差、万用表误差可能在1.48V-1.52V之间都算正常。你可以尝试设置几个不同的电压值0 500 1650 3000 3300观察输出是否线性变化。深入验证尝试在初始化函数里关闭输出缓冲设置BOFF11。再次测量0V和3300mV对应的输出电压。你会发现输出0V时可能还有几十毫伏的电压无法到0输出3300mV时可能达不到3.3V无法到电源轨。这就是输出缓冲器关闭后的影响它验证了数据手册里的描述。4.2 实验二生成三角波与锯齿波不用DMA仅用软件延时和循环也能生成低频波形适合理解原理。三角波在主循环里写一个for循环让DAC_DHR12R1的值从0递增到4095每步加10每次赋值后加一个简短的软件延时如for(i0;i1000;i)空循环并触发。然后再一个循环从4095递减到0。用示波器观察PA4应该能看到一个三角波。波形的频率由你的延时决定。锯齿波上升沿循环从0到4095递增到达顶部后立刻跳回0重复。示波器会看到不断上升然后瞬间下降的锯齿。关键观察用示波器观察时注意波形的“台阶”。因为DAC是数字的输出变化不是绝对平滑的而是以1 LSB为最小步进。在低频率下你能看到波形是由很多个小台阶组成的。这直观地展示了数字量和模拟量的区别。4.3 实验三DMA定时器触发输出正弦波这是最具实用价值的实验能体现DAC在信号生成方面的能力。生成数据表用Excel、Python或MATLAB生成一个正弦函数数组并映射到0-4095的范围。例如sin_table[i] 2048 2048 * sin(2 * PI * i / 100)这里2048是中间值1.65V2048是幅值。i从0到99。配置定时器假设系统时钟72MHz定时器预分频PSC71则计数器时钟为1MHz。要产生100kHz的触发频率周期10us则自动重载值ARR (1MHz / 100kHz) - 1 9。配置TIM2的ARR9PSC71触发源选择为更新事件。配置DMA源地址是sin_table数组首地址目标地址是DAC_DHR12R1的地址0x40007408。传输数量为100循环模式外设到内存不增量内存到外设增量。连接示波器将示波器探头地线夹子接板子GND探头尖接PA4。预期现象示波器应稳定显示一个频率为1kHz100个点 * 10us/点 1ms周期的正弦波。调整定时器的ARR值可以改变正弦波的频率。调整sin_table的幅值或公式可以改变波形的幅值和偏置电压。实操心得用DMA传输时如果波形出现断裂或错乱首先检查两点一是DMA传输数量是否等于波形数据表长度二是DMA是否配置为循环模式。如果不是循环模式DMA传输完指定数量后就会停止波形输出就会卡住。另外确保数据表定义在内存中而不是声明为const放到Flash里因为DMA通常是从内存地址取数。如果非要放在Flash常量区需要配置DMA从Flash读取或者先拷贝到内存缓冲区。5. 性能评估、常见问题与优化技巧5.1 DAC性能关键指标实测与理解玩转DAC不能只看它能不能输出还得关心输出得好不好。几个关键指标需要心里有数分辨率12位就是4096个台阶。这是固定的决定了你能控制的最小电压变化量1 LSB ≈ 0.8mV 3.3V。建立时间从数字量改变到输出模拟量达到终值附近某个误差带比如±0.5 LSB所需的时间。STM32F1的DAC建立时间大概几个微秒。这决定了DAC能输出的最高信号频率。建立时间越短能输出的频率越高。积分非线性INL和微分非线性DNL可以简单理解为“线性度”好不好。INL表示实际转换曲线与理想直线的最大偏差DNL表示实际步进与理想1 LSB步进的偏差。好的DAC这两个值都小。STM32片内DAC的DNL典型值在±1 LSB以内对于一般应用没问题但对精度要求极高的场合如精密仪器可能需要外置独立DAC芯片。噪声输出中的杂散成分。电源噪声、数字开关噪声都会耦合进来。用示波器在交流耦合模式下观察DAC的直流输出能看到一些毛刺和噪声。这就是为什么强调模拟电源VDDA要干净并且DAC输出引脚要远离高速数字信号线如时钟线、SPI总线。5.2 典型问题排查速查表现象可能原因排查步骤与解决方案无输出电压为0或接近01. DAC未使能 (EN10)。2. GPIO模式配置错误未配为模拟输入。3. VDDA未正确供电或电压为0。4. 输出缓冲关闭且负载阻抗太低。1. 检查DAC_CR寄存器EN1位是否为1。2. 用调试器或代码检查GPIOA_CRL寄存器对应PA4的配置位是否为0x0模拟输入。3. 用万用表测量芯片VDDA引脚电压是否为3.3V。4. 测量输出引脚对地电阻如果负载过重电阻太小在关闭缓冲时可能拉不上去尝试减小负载或使能缓冲。输出电压值不对1. 参考电压VDDA不准确。2. 数据计算错误或写入寄存器错误。3. 输出缓冲影响关闭时无法满幅输出。4. 负载电流过大超出DAC驱动能力。1. 精确测量VDDA实际电压代入公式重新计算。2. 单步调试查看写入DAC_DHR12R1寄存器的值是否正确。3. 使能输出缓冲再测试或外接运放做缓冲。4. DAC输出缓冲驱动能力有限数据手册有最大输出电流值通常几mA后级接高输入阻抗的运放电压跟随器进行隔离。输出波形有台阶、不光滑这是正常现象体现了数字量的离散性。在DAC输出后增加一个低通滤波器RC电路滤除高频的“台阶”噪声使波形平滑。滤波器的截止频率应略高于你需要的信号频率。使用DMA输出波形错乱、断裂1. DMA传输长度设置错误。2. DMA未配置循环模式。3. 数据表定义在了Flash中且DMA未配置从Flash读。4. 定时器触发频率过快超过DAC建立时间。1. 核对DMA传输数量与波形数组长度。2. 检查DMA配置寄存器使能循环模式。3. 将波形数组定义到RAM中如全局数组或配置DMA从Flash读取如果支持。4. 降低定时器触发频率给DAC足够的建立时间。输出噪声大、毛刺多1. 电源噪声。2. 数字信号串扰。3. PCB布局布线不良。1. 加强电源去耦VDDA使用LC滤波。2. 让DAC输出线远离时钟、PWM等高速数字走线。3. 在DAC输出引脚就近对地接一个几十pF的小电容可以滤除部分高频噪声但会影响响应速度。5.3 进阶优化与扩展思路当你掌握了基础操作后可以尝试这些进阶玩法让DAC发挥更大作用双通道同步输出STM32的两个DAC通道可以独立工作也可以在某些模式下同步。例如你可以用同一个定时器触发两个DAC的DMA让它们同时输出两路相关的波形如正弦和余弦用于正交调制等应用。与ADC组成闭环用DAC输出一个设定电压再用ADC去读取这个电压可以通过外部电路连接甚至有些芯片内部有通路形成一个最简单的闭环测试验证系统的线性度和精度。音频应用虽然STM32的DAC性能一般但播放低质量的语音或音乐提示音是可行的。将音频PCM数据转换为12位数据流通过DMA送至DAC后端接一个简单的功放和喇叭即可。注意音频采样率如8kHz要与定时器触发频率匹配。精密电压基准如果你对电压精度要求高可以外接一个高精度、低温漂的基准电压芯片如REF3030输出3.0V给VDDA供电这样DAC的输出精度就由这个外部基准决定可以大幅提升性能。软件校准由于DAC存在零点误差和增益误差可以在代码中做简单的两点校准。测量DAC输出为0代码和满量程代码时的实际电压计算出一个斜率和偏移量在输出前对数字量进行补偿。这能有效改善实际应用的精度。折腾DAC的过程是一个典型的从数字世界到模拟世界的跨界体验。它要求你既要有清晰的数字逻辑思维寄存器配置、触发时序又要开始关注模拟世界的特性噪声、驱动能力、滤波。把这一套流程走通你对嵌入式系统的理解就不再局限于0和1而是能真正地与连续的物理世界对话了。
返回列表