ARTICLE DETAIL

资讯详情

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

GD32E502C DAC实战:从直流输出到方波生成与问题排查

GD32E502C DAC实战:从直流输出到方波生成与问题排查 1. 从零开始GD32E502C的DAC功能初探最近在折腾一块GD32E502C-START开发板想用它来生成一些模拟信号。你可能知道在嵌入式开发里数字信号处理起来方便但最终驱动扬声器、电机或者一些传感器往往还是需要模拟电压。这时候数模转换器DAC就派上用场了。GD32E502C这颗芯片内置了一个12位的DAC模块对于大多数需要模拟量输出的场景来说精度和速度都够用了。我这次的目标很简单就是把这个DAC用起来让它输出一个稳定的直流电压再试着让它输出一个简单的方波看看效果如何。很多人拿到开发板照着例程跑一遍看到电压变化就觉得会了。但实际项目中你可能会遇到输出不准确、波形有毛刺甚至驱动能力不足的问题。这篇文章我就结合GD32E502C的数据手册和我的实测过程把DAC从配置到输出再到一些实际应用中容易踩的坑都捋一遍。无论你是刚开始接触GD32的开发者还是从其他平台比如STM32迁移过来的希望这些内容都能帮你更快地上手。2. GD32E502C DAC模块的核心机制与配置要点在动手写代码之前我们必须先搞清楚GD32E502C的DAC到底是怎么工作的以及它有哪些关键的限制。这就像开车前得先知道油门、刹车和方向盘在哪一样。2.1 理解12位DAC的分辨率与参考电压GD32E502C的DAC是12位的。这是什么概念呢12位意味着它有 2^12 4096 个不同的输出等级。DAC输出的电压范围取决于它的参考电压源Vref。在GD32E502C-START开发板上DAC的参考电压通常直接连接到芯片的VDDA模拟电源我实测板子上是3.3V。那么输出电压的计算公式就很简单了输出电压 (设定值 / 4095) * Vref例如如果你想输出1.65V即3.3V的一半你的设定值就应该是 4095 / 2 2047注意从0到4095总共4096个等级。这里第一个容易迷糊的点是“设定值”的数据对齐方式。GD32的DAC数据寄存器支持三种对齐方式右对齐12位、左对齐12位和8位右对齐。为了直观我们最常用的是右对齐12位。这意味着你把一个0到4095之间的数字直接写入数据寄存器的低12位bit[11:0]就可以了高20位硬件会忽略。如果你错误地使用了左对齐那输出的电压会完全不对。2.2 使能DAC的完整流程与时钟树关系配置DAC的第一步是打开时钟。GD32E502C的DAC挂载在APB1总线下。这里有个细节它的时钟不是独立的DAC时钟而是依赖于APB1的时钟。所以你需要确保rcu_apb1_clock_enable(RCU_DAC)被正确调用。更关键的一步是使能DAC控制器和使能对应的DAC输出通道。这是两个独立的步骤顺序不能错。GD32E502C有两个DAC通道DAC0和DAC1对应输出引脚分别是PA4和PA5。在START开发板上PA4是直接通过跳线帽引出的非常方便。配置流程如下打开APB1上DAC的时钟。配置GPIO引脚为模拟模式DAC输出引脚必须设为模拟输入这个设定可能反直觉但记住就行。调用dac_enable()使能DAC模块。调用dac_output_enable()使能你需要的具体通道例如DAC0。向该通道的数据寄存器写入数值。很多例程把这几步写在一起但实际调试时我建议每一步之后都加个延时或者通过调试器查看寄存器状态确认使能位确实被置起了。特别是从低功耗模式唤醒后一定要重新检查DAC的使能状态。2.3 触发模式选择软件触发与定时器触发DAC的数据更新可以由软件直接写入触发也可以由外部事件如定时器更新事件触发。对于输出静态电压用软件触发就够了即直接写dac_data_set()函数。但如果你想生成波形比如方波、三角波就必须用到定时器触发。这是DAC的一个高级功能可以让你不占用CPU资源自动地、周期性地更新DAC输出值。GD32E502C的DAC可以连接到多个定时器如TIMER2, TIMER4, TIMER5, TIMER6等的更新事件。你需要做的是配置一个定时器产生固定频率的更新事件UPDATE。配置DAC选择该定时器作为触发源 (dac_trigger_source_config())。使能DAC的触发功能 (dac_trigger_enable())。此时向DAC的数据寄存器写入的值不会立即输出而是被存入一个缓冲区。当定时器更新事件到来时这个缓冲区的值才会被自动加载到DAC的输出锁存器从而改变输出电压。这个机制是实现任意波形发生器的基础。理解“软件写入-缓冲区-硬件触发加载”这个流水线对于后续调试波形输出至关重要。3. 实战输出直流电压与测量校准理论清楚了我们接上示波器或者至少有个万用表来实际输出一个电压看看。3.1 基础代码实现与硬件连接首先我们实现一个最简单的功能让DAC0PA4输出一个1.0V的直流电压。硬件上用跳线帽将开发板上标有“DAC0”的排针与扩展接口连接好然后用万用表或示波器探头测量这个引脚对地的电压。#include gd32e50x.h #include stdio.h void dac_simple_output_config(void) { /* 1. 开启时钟 */ rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_DAC); /* 2. 配置GPIO PA4 为模拟模式 */ gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_4); /* 3. 使能DAC模块 */ dac_enable(); /* 4. 使能DAC通道0 */ dac_output_enable(DAC0); /* 5. 设置输出电压为1.0V (假设Vref3.3V) */ // 计算设定值 1.0 / 3.3 * 4095 ≈ 1241 uint16_t dac_value (uint16_t)(1.0 / 3.3 * 4095); dac_data_set(DAC0, DAC_ALIGN_12B_R, dac_value); } int main(void) { systick_config(); // 初始化系统滴答定时器用于延时 dac_simple_output_config(); while(1) { // 主循环可以空跑或者添加其他逻辑 // DAC输出会一直保持1.0V delay_1ms(1000); } }烧录代码后用万用表测量PA4引脚你应该能看到一个接近1.0V的电压。但很可能它不是精确的1.000V可能是0.998V也可能是1.012V。这就是接下来要讨论的校准问题。3.2 实测电压偏差分析与校准思路你测到的电压与计算值有偏差主要来自两个地方参考电压VDDA的精度板载的3.3V电源通常由LDO产生它本身就有一定的误差比如±1%。这意味着你计算时用的3.3V基准实际可能是3.267V或3.333V。DAC自身的积分非线性INL和微分非线性DNL误差这是芯片制造工艺带来的固有误差表现为输出曲线不是完美的直线。对于大多数应用如果偏差在几十毫伏以内可以忽略。但如果需要较高精度就需要校准。一个简单实用的校准方法是两点校准法让DAC输出一个接近0的值如对应0.1V用高精度万用表测量实际电压V_meas1。让DAC输出一个接近满量程的值如对应3.2V测量实际电压V_meas2。根据这两个实测点可以拟合出一条更接近实际特性的直线。在代码中你需要建立一个查找表或者一个修正公式在设定输出值时进行补偿。例如你发现输出设定值1241时实际是0.992V输出设定值3960时实际是3.288V。那么你可以推导出修正公式实际设定值 (目标电压 - 零点偏移) / 斜率。这个计算可以在上位机完成然后将修正系数存入芯片的Flash中。注意校准需要在电路稳定、电源干净的情况下进行。如果系统中有大功率器件频繁开关导致电源纹波增大会严重影响DAC的输出精度。此时为模拟部分VDDA、VSSA增加LC滤波电路是必要的。4. 进阶应用生成方波与处理过冲振荡输出直流电压只是热身。DAC更常见的用途是生成波形。我们从最简单的方波开始并会遇到一个经典问题过冲Overshoot和下冲Undershoot也就是你提到的“上升有过冲振荡下冲振荡”。4.1 使用定时器触发实现方波输出要生成一个1kHz的方波0V和2V交替我们需要定时器来周期性地触发DAC更新数据。这里以TIMER6为例因为它是一个基本定时器配置简单。void dac_square_wave_config(void) { // ... 前面的GPIO、DAC使能步骤与3.1节相同 ... /* 配置TIMER6用于触发 */ rcu_periph_clock_enable(RCU_TIMER6); timer_deinit(TIMER6); // 假设系统时钟是120MHzAPB1分频后为60MHz // 我们想要1kHz方波即周期1ms。DAC每0.5ms切换一次电压。 // 定时器频率 60MHz / (预分频器 1) / (周期值 1) // 设置预分频器为59999周期值为1则定时器更新频率 60MHz / 60000 / 2 500Hz不对。 // 重新计算我们需要0.5ms触发一次即2kHz触发频率。 // 定时器计数频率 60MHz / (prescaler 1) // 让定时器每计数一定次数后产生更新事件该次数 (period 1) // 所以触发频率 60MHz / (prescaler1) / (period1) // 设 prescaler 29999, period 1则触发频率 60MHz / 30000 / 2 1000Hz (1ms周期)还是不对。 // 我们的目标是2kHz触发频率0.5ms一次。 // 令60,000,000 / (PSC1) / (ARR1) 2000 // 取 PSC 29999则 (PSC1)30000计数器时钟2kHz。 // 那么需要 ARR1 1即 ARR0。但ARR为0时计数器从0计数到0即溢出更新频率极高。这里理解有误。 // 正确理解当ARR0时计数器从0计数到0实际上每个时钟周期都会产生更新事件。这太快了。 // 我们需要的是每0.5ms一个事件即每30000个时钟周期一个事件60MHz/2kHz30000。 // 因此设置PSC0ARR29999。这样计数器从0计数到29999耗时30000个时钟周期正好0.5ms产生一次更新事件。 timer_autoreload_value_config(TIMER6, 29999); // ARR timer_prescaler_config(TIMER6, 0, TIMER_PSC_RELOAD_NOW); // PSC timer_single_pulse_mode_config(TIMER6, TIMER_SP_MODE_REPETITIVE); timer_update_source_config(TIMER6, TIMER_UPDATE_SRC_REGULAR); // 使用ARR作为更新源 /* 配置DAC使用TIMER6作为触发源 */ dac_trigger_source_config(DAC0, DAC_TRIGGER_T6_TRGO); // 假设TIMER6的TRGO事件是更新事件 dac_trigger_enable(DAC0); /* 启动定时器 */ timer_enable(TIMER6); /* 初始化写入两个电压值到DAC的数据缓冲区 */ // 注意在触发模式下dac_data_set写入的是数据保持寄存器需要触发事件来加载到输出寄存器 uint16_t dac_value_low 0; uint16_t dac_value_high (uint16_t)(2.0 / 3.3 * 4095); // 2.0V dac_data_set(DAC0, DAC_ALIGN_12B_R, dac_value_low); // 如何交替写入这里需要结合DMA或中断。更简单的做法是使用DAC的噪声波或三角波生成功能但这里我们用双缓冲模式。 // GD32的DAC支持双数据寄存器。我们可以先写DAC0_R12DH等第一次触发输出后再在中断中写下一个值。 }上面的代码展示了配置思路但实现一个干净的方波更好的方法是使用DAC双寄存器模式配合DMA。这样CPU只需要初始化一次DMA会自动循环地将两个电压值低和高在每次定时器触发时轮流加载到DAC中。这是最有效率、最不容易出错的方式。4.2 过冲与下冲振荡的成因与抑制措施当你用示波器观察生成的方波边沿时很可能会看到下图所示的现象(注此处为文字描述实际博文可配图)在电压上升沿信号会先冲过一个峰值过冲然后衰减振荡几次才稳定到高电平。下降沿同理会先下冲到一个谷底下冲。这不仅仅是“不好看”在驱动精密负载时可能会引发误动作。产生原因主要有以下几点阻抗不匹配与寄生参数这是最主要的原因。开发板上的走线、你的测量探头、以及DAC输出引脚本身的驱动能力构成了一个传输系统。当信号边沿非常陡峭高频分量丰富时如果传输路径的特性阻抗与源端或负载端不匹配就会发生信号反射叠加在原信号上形成振荡。PCB上的走线电感、对地电容等寄生参数也会形成LC谐振电路。DAC输出缓冲器的局限性GD32的DAC内部有一个输出缓冲放大器用于提高驱动能力。但这个放大器的压摆率Slew Rate和带宽是有限的。当你要求它瞬间从一个电压跳到另一个电压时它可能“反应不过来”产生振铃。测量探头的影响示波器探头本身有输入电容通常几pF到十几pF连接到测试点相当于并联了一个电容会改变电路的频率响应可能加剧振荡。解决措施可以尝试以下几种在DAC输出端串联一个小电阻这是最简单有效的方法。在PA4引脚串联一个20Ω到100Ω的电阻再连接到你的测量点或后续电路。这个电阻可以与探头的输入电容或负载电容形成一个一阶低通滤波器衰减高频分量平滑边沿。电阻值需要权衡太大虽然滤波效果好但会降低驱动能力和增加输出阻抗太小则效果不明显。可以从33Ω开始尝试。降低信号边沿速度如果应用允许不要追求极快的方波。可以通过降低触发DAC更新的定时器频率即降低方波频率来间接降低边沿速度。或者不使用方波而使用经过平滑的梯形波。优化PCB布局与负载在你自己设计电路时DAC输出走线应尽量短远离数字信号线并考虑在靠近引脚处放置一个到地的微小电容如10-100pF进行滤波。但要小心电容过大会导致DAC缓冲器不稳定。软件滤波对于某些特定波形你可以不直接输出目标电压而是输出一系列小幅阶跃的电压值用多个小跳变来逼近一个大跳变这也能有效减少高频分量。在我的实测中在GD32E502C的DAC0输出脚串联一个47Ω的电阻后1kHz方波的过冲振荡从原来的约200mV峰峰值减小到了50mV以内效果非常显著。5. 深入排查当DAC输出异常或无输出时即使按照例程操作有时也会遇到DAC没有输出或者输出值完全不对的情况。下面是一个系统性的排查链路你可以像侦探一样一步步缩小范围。5.1 电源与引脚配置检查这是最基础也最容易被忽略的。VDDA电压是否正常用万用表测量开发板上VDDA引脚通常与VDD相连对地的电压确认是否是稳定的3.3V左右。如果VDDA电压为0或过低DAC模块根本不会工作。GPIO模式是否正确反复检查代码确认DAC输出引脚PA4/PA5是否被配置为模拟模式(GPIO_MODE_ANALOG)。如果误配置为推挽输出、上拉输入等模式DAC的内部输出电路可能无法控制该引脚。引脚是否被其他外设复用检查你的工程中PA4是否还被其他功能如SPI、USART占用。一个引脚在同一时刻只能用于一个主要功能。5.2 寄存器级诊断与调试器使用当软件层面检查无误后就需要深入硬件寄存器了。使用调试器查看外设寄存器在IDE如Keil, IAR的调试模式下暂停程序查看外设寄存器窗口。找到RCU时钟控制单元确认RCU_APB1EN寄存器中DAC使能位DACEN是否为1。找到DAC寄存器组确认DAC_CTL寄存器中DEN0DAC0使能位是否为1。DTOG0触发使能位是否按你的需求设置软件触发应关闭定时器触发应打开。查看DAC0_R12DH寄存器12位右对齐数据保持寄存器确认你写入的数值是否还在。检查触发事件是否产生如果使用定时器触发检查定时器是否在正常运行。查看定时器的状态寄存器TIMERx_INTF中的更新中断标志位UPIF或者直接观察定时器的计数器TIMERx_CNT是否在递增。也可以尝试在定时器更新中断里设置一个断点看能否进入。测量引脚电平即使DAC没有模拟输出如果GPIO模式配置错误引脚也可能有数字电平输出。用万用表直流电压档测量PA4对地电压。如果是0V或3.3V固定不变那很可能是配置问题如果是一个不稳定的中间值比如1.2V那DAC可能在输出但负载或测量有问题。5.3 负载影响与外部电路隔离DAC的输出驱动能力有限数据手册通常给出的是驱动一个特定负载如5kΩ并联100pF电容时的性能。如果你的负载阻抗太低比如直接驱动一个LED阻抗可能只有几百欧姆DAC可能带不动导致输出电压被拉低波形失真。断开所有外部连接仅用示波器探头设置为10X档以减小影响直接测量开发板上的DAC测试点。看是否有输出。接入一个简单的分压电阻作为负载在输出引脚和地之间接一个10kΩ的电阻再看输出。这是一个标准的测试负载。检查后续运放电路如果你的DAC输出后面接了运算放大器做缓冲或放大检查运放的电源、虚短虚断是否正常。运放输入端的偏置电流可能会对DAC输出造成影响。通过以上由软到硬、由内到外的排查绝大多数DAC相关的问题都能定位到。核心思想就是先确认芯片内部配置正确再排除外部电路的影响。6. 从GD32出发DAC在不同场景下的应用扩展掌握了基础操作和问题排查我们可以看看DAC在更广阔的场景下能做什么。这有助于你将来做项目时知道该不该用、该怎么用这个外设。6.1 音频信号生成与“ESP32 DAC视频”的启示你提到的“ESP32 DAC 视频”这个热词很可能指的是有人用ESP32的DAC来生成复合视频信号如PAL/NTSC。这是一个非常酷的应用它利用了DAC可以高速、精确地输出模拟电压序列的特性。视频信号的亮度和同步信息都是电压值通过精心计算每个像素点对应的DAC数值并严格按照行频、场频定时输出就能在屏幕上显示出图像。GD32E502C的DAC性能速度、精度比ESP32的8位DAC要强。理论上你也可以用它来玩类似的项目。但这需要极高的数据吞吐率视频信号数据量巨大必须使用DMA并且DMA的数据源最好来自内存中的一块预先计算好的帧缓冲区。精确的定时需要高精度定时器来触发DAC误差必须控制在微秒级以内。复杂的信号合成算法你需要理解视频信号的格式并编写代码将图像数据转换成包含同步头、消隐区和有效像素区的电压序列。虽然实现起来有挑战但这完美展示了DAC如何作为连接数字世界和模拟世界的桥梁。6.2 作为可编程电压基准源在很多传感器调理电路或者比较器电路中需要一个可调的参考电压。DAC非常适合这个角色。例如用一个电位器ADC读取或通信命令来动态设置DAC的输出电压这个电压再作为另一个电路如运放、比较器的基准。GD32的12位DAC可以提供约0.8mV3.3V/4096的步进精度对于多数应用足够了。注意事项用作电压基准时对DAC的稳定性、温漂和噪声要求更高。需要确保模拟电源VDDA非常干净并且负载电流极小最好接入一个高输入阻抗的运放作为缓冲器。6.3 与ADC协同实现闭环控制这是工业控制中的经典模式。例如你想控制一个加热器的温度。MCU通过DAC输出一个电压驱动一个功率调节电路如MOSFET来控制加热功率。温度传感器如热电偶的信号经过放大后由MCU内部的ADC读取。MCU运行PID控制算法根据目标温度与ADC读取的实际温度的差值动态调整DAC的输出值从而形成一个闭环控制系统。在这个过程中DAC的输出精度和稳定性直接影响到系统的控制精度。你需要关注DAC的建立时间从数字值改变到输出电压稳定到目标精度范围内所需的时间这在快速响应的控制系统中很重要。折腾完GD32E502C的DAC从点亮一个静态电压到生成带毛刺的方波再到想办法把毛刺压下去这个过程比单纯看手册要有趣得多。嵌入式开发很多时候就是这样原理图、数据手册给你指明了路但路上的坑还得自己踩一遍才记得牢。关于那个串联电阻抑制过冲的方法我建议你手边备几个不同阻值22Ω, 47Ω, 100Ω的电阻实际焊上去或者用面包板搭一下用示波器看看效果感受会比读任何文章都深刻。DAC这个外设用好了就是连接数字算法和物理世界的神奇开关值得花时间把它摸透。
返回列表