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

资讯详情

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

FPGA驱动TLC5615 DAC:从SPI时序到波形生成的完整工程实践

FPGA驱动TLC5615 DAC:从SPI时序到波形生成的完整工程实践 1. 项目概述与核心价值最近在整理一些老项目的资料翻到了几年前用FPGA驱动TLC5615数模转换器DAC的笔记。这个项目虽然看起来简单就是一个SPI接口的10位DAC但当时为了让它稳定、精确地工作从时序分析到噪声处理着实踩了不少坑。今天就把这些经验系统地梳理出来希望能给正在或打算用FPGA控制这类低速、高精度外设的朋友们一些参考。简单来说TLC5615是一颗非常经典的串行输入、10位分辨率、电压输出的DAC芯片。它的核心价值在于通过一个简单的三线制SPI接口就能让FPGA这类数字逻辑器件输出模拟电压信号。这对于需要生成特定波形、设定参考电压或者构建简易信号发生器的场景非常有用。比如你可以用它来产生一个可编程的直流偏置或者配合一些算法生成正弦波、三角波等基础波形。这个项目适合两类朋友一类是FPGA的初学者想找一个有明确时序要求、接口标准的外设来练习状态机设计和时序约束另一类是有一定经验的工程师在需要快速实现一个低成本、中等精度的DAC方案时可以直接参考这里的驱动架构和避坑指南。整个驱动逻辑并不复杂但细节决定成败一个稳定的驱动能让你后续的模拟电路设计省心很多。2. 核心芯片TLC5615深度解析在动手写代码之前我们必须像了解一位新同事一样彻底吃透TLC5615的“脾气秉性”。很多驱动问题根源都在于对芯片数据手册的理解不够深入。2.1 关键电气特性与引脚定义TLC5615采用8引脚DIP或SOIC封装其核心引脚就三个通信线DIN串行数据输入、SCLK串行时钟输入和CS片选低电平有效。输出是OUT模拟电压输出和REFIN参考电压输入。它的供电电压范围是4.5V到5.5V典型5V这意味着如果你的FPGA开发板是3.3V电平就需要进行电平转换这是第一个要注意的点。它的分辨率是10位但请注意其数据格式。它内部有一个12位的寄存器你需要发送的数据是16位。这16位中高4位是虚拟位Don‘t care接着是10位有效数据位DB9-DB0最后是2位低零位LSB。数据格式为[XXXX D9 D8 D7 D6 D5 D4 D3 D2 D1 D0 00]。这里的“XXXX”可以任意但通常我们习惯补0。这种格式意味着你实际控制的10位数据会被左移2位后放入内部的12位寄存器因此输出电压的计算公式为Vout 2 * Vrefin * (CODE / 1024)。其中CODE就是你输入的10位数字值0-1023Vrefin是接在REFIN引脚上的参考电压通常接一个2.048V或2.5V的基准源。这个“2倍”增益是芯片内部固定的。注意参考电压Vrefin的质量直接决定了输出精度。如果你直接用供电电压作为参考那么电源的任何纹波和噪声都会直接反映到输出上。对于精度要求稍高的场合务必使用独立的基准电压芯片如TL431、REF5025等。2.2 工作时序的“魔鬼细节”TLC5615的时序是典型的SPI模式但它有自己的特殊要求这也是驱动设计的核心。根据数据手册我们需要关注以下几个关键参数在5V供电常温下t_{SUCS}:CS下降沿到第一个SCLK上升沿的建立时间最小为25ns。t_{HDCS}: 最后一个SCLK下降沿到CS上升沿的保持时间最小为25ns。t_{SUD}:DIN数据在SCLK上升沿之前的建立时间最小为20ns。t_{HDD}:DIN数据在SCLK上升沿之后的保持时间最小为10ns。t_{SCLKH}/t_{SCLKL}:SCLK高电平和低电平的最小脉宽均为25ns。这意味着什么它意味着你的FPGA驱动时钟不能太快。假设你的FPGA主时钟是50MHz周期20ns如果你直接用这个时钟去产生SCLK那么高/低电平脉宽只有20ns不满足25ns的最小要求会导致通信失败。因此我们必须对驱动时钟进行分频或者用一个更慢的时钟域来产生SPI时序。另一个极易忽略的细节是CS的用法。很多SPI设备在CS拉低后可以连续发送多组16位数据。但TLC5615不同每次输出更新都必须是一个完整的“CS脉冲”。即拉低CS- 发送16个SCLK时钟 - 拉高CS。在CS为高期间SCLK必须保持为低电平。这个拉高CS的动作会触发芯片内部执行数模转换和输出更新。如果你试图在CS持续为低时发送第二组数据芯片是不会响应的。3. FPGA驱动设计与状态机实现理解了芯片的规矩我们就可以用FPGA的语言硬件描述语言如Verilog来和它“对话”了。驱动设计的核心是一个清晰的状态机。3.1 驱动模块接口与架构设计首先我们定义驱动模块的接口。除了时钟和复位信号我们需要一个启动信号start_i一个10位的数据输入data_i以及连接到TLC5615的三个物理引脚dac_cs_o,dac_sclk_o,dac_din_o。还可以增加一个忙线指示busy_o告诉上游逻辑何时可以发送下一组数据。module dac_tlc5615_driver ( input wire clk, // 主时钟例如50MHz input wire rst_n, // 低电平复位 input wire start_i, // 数据发送启动脉冲 input wire [9:0] data_i, // 10位待转换数据 output reg busy_o, // 忙信号高电平表示正在转换 output reg dac_cs_o, // 连接到 TLC5615 CS output reg dac_sclk_o, // 连接到 TLC5615 SCLK output reg dac_din_o // 连接到 TLC5615 DIN );内部的驱动时钟spi_clk需要由主时钟分频得到。根据之前25ns的脉宽要求spi_clk的周期至少需要50ns即频率不高于20MHz。一个稳妥的做法是分频到10MHz周期100ns这样裕量充足。我们可以用一个计数器来生成这个时钟使能信号而不是生成一个新的时钟域这样可以避免跨时钟域问题。// 生成SPI时钟使能信号将50MHz分频为10MHz reg [2:0] clk_div_cnt; wire spi_clk_en; // 在spi_clk的上升沿和下降沿处产生一个时钟使能脉冲 always (posedge clk or negedge rst_n) begin if (!rst_n) begin clk_div_cnt 3d0; end else begin clk_div_cnt clk_div_cnt 1; end end // 当计数器为2和4时产生使能脉冲模拟10MHz时钟的边沿 assign spi_clk_en (clk_div_cnt 3‘d2) || (clk_div_cnt 3’d4);3.2 核心状态机与数据移位逻辑驱动过程可以划分为几个状态IDLE空闲、START准备、SHIFT移位发送、FINISH结束。状态机在spi_clk_en的节奏下跳转。IDLE状态等待start_i信号。一旦收到拉高busy_o拉低dac_cs_o片选有效并加载数据。需要将10位的data_i组合成符合格式的16位发送寄存器shift_reg{4‘b0, data_i, 2’b0}。然后进入START状态。START状态这是一个等待状态用于满足t_{SUCS}CS到第一个SCLK上升沿的建立时间。在spi_clk_en有效时可以跳转到SHIFT状态。此时将dac_sclk_o置为低电平。SHIFT状态这是数据移位的核心。我们定义一个位计数器bit_cnt从0计数到15。在每个spi_clk_en有效时在spi_clk_en有效且clk_div_cnt2模拟上升沿前将shift_reg的最高位shift_reg[15]赋值给dac_din_o建立数据。在spi_clk_en有效且clk_div_cnt4模拟下降沿后将dac_sclk_o拉高产生一个时钟上升沿数据在此时被TLC5615采样同时shift_reg左移一位。然后在下个spi_clk_en的“上升沿前”阶段再将dac_sclk_o拉低形成一个完整的时钟脉冲。bit_cnt加1。重复此过程直到bit_cnt计满16。FINISH状态16位发送完毕后需要保持dac_cs_o为低dac_sclk_o为低并等待以满足t_{HDCS}最后一个SCLK到CS上升沿的保持时间。通常等待1-2个spi_clk_en周期即可。然后拉高dac_cs_o触发DAC更新输出。拉低busy_o返回IDLE状态。// 状态定义 localparam S_IDLE 2‘b00; localparam S_START 2’b01; localparam S_SHIFT 2‘b10; localparam S_FINISH 2’b11; reg [1:0] state, next_state; reg [15:0] shift_reg; reg [4:0] bit_cnt; // 计数0-15 always (posedge clk or negedge rst_n) begin if (!rst_n) begin state S_IDLE; shift_reg 16‘b0; bit_cnt 5’d0; dac_cs_o 1‘b1; dac_sclk_o 1’b0; dac_din_o 1‘b0; busy_o 1’b0; end else if (spi_clk_en) begin // 关键在慢速的SPI时钟使能下工作 case (state) S_IDLE: begin if (start_i) begin shift_reg {4‘b0, data_i, 2’b0}; // 组装数据 busy_o 1‘b1; dac_cs_o 1’b0; // CS拉低 state S_START; end end S_START: begin // 等待一个周期满足t_SUCS dac_sclk_o 1‘b0; state S_SHIFT; bit_cnt 5’d0; end S_SHIFT: begin // 在clk_div_cnt2时准备数据在4时产生时钟上升沿并移位 if (clk_div_cnt 3‘d2) begin dac_din_o shift_reg[15]; // 输出最高位 end else if (clk_div_cnt 3’d4) begin dac_sclk_o 1‘b1; // 产生上升沿 shift_reg {shift_reg[14:0], 1’b0}; // 左移 bit_cnt bit_cnt 1; if (bit_cnt 5‘d15) begin // 已发送16位 dac_sclk_o 1’b0; state S_FINISH; end end else if (clk_div_cnt 3‘d0 || clk_div_cnt 3’d1) begin // 在下一个周期的“上升沿前”阶段拉低时钟 dac_sclk_o 1‘b0; end end S_FINISH: begin // 保持CS为低SCLK为低等待t_HDCS dac_sclk_o 1‘b0; // 通常等待1个spi_clk_en周期即可这里直接跳转 dac_cs_o 1’b1; // CS拉高更新输出 busy_o 1‘b0; state S_IDLE; end endcase end end实操心得这里使用spi_clk_en使能信号而非分频后的新时钟spi_clk来驱动整个状态机是一个推荐的做法。这保证了所有寄存器都在同一个主时钟clk的同步下避免了复杂的多时钟域设计减少了时序违例的风险。我们只是在spi_clk_en有效时才更新状态和输出从而实现了“慢速”SPI通信的效果。4. 仿真验证与上板调试代码写完了但绝不能直接上板。仿真可以提前发现90%的逻辑错误。4.1 编写Testbench进行行为仿真我们需要一个测试平台来模拟上游逻辑发送数据并检查驱动模块产生的波形是否符合TLC5615的时序规范。timescale 1ns / 1ps module tb_dac_driver(); reg clk; reg rst_n; reg start; reg [9:0] data; wire busy; wire cs, sclk, din; // 实例化驱动模块 dac_tlc5615_driver uut ( .clk(clk), .rst_n(rst_n), .start_i(start), .data_i(data), .busy_o(busy), .dac_cs_o(cs), .dac_sclk_o(sclk), .dac_din_o(din) ); // 生成50MHz时钟 initial clk 0; always #10 clk ~clk; // 20ns周期 // 初始化与激励 initial begin rst_n 0; start 0; data 10‘b0; #100; rst_n 1; #200; // 测试1发送数据 10‘h3FF (满量程) data 10’h3FF; start 1; #20; // 保持一个时钟周期高脉冲即可 start 0; // 等待一次转换完成 wait(busy 0); #500; // 测试2发送数据 10‘h200 (中间值) data 10’h200; start 1; #20; start 0; wait(busy 0); #500; $finish; end endmodule在仿真工具如ModelSim、Vivado Simulator中运行观察cs、sclk、din的波形。你需要用游标测量cs下降沿到第一个sclk上升沿的时间是否大于25nssclk的高电平和低电平脉宽是否大于25nsdin在sclk上升沿前后的建立保持时间t_{SUD},t_{HDD}是否满足要求是否在发送完16个sclk脉冲后cs才拉高din的数据是否与输入的data补零后一致4.2 实际电路连接与调试要点仿真通过后就可以连接硬件了。你需要一块FPGA开发板、一颗TLC5615芯片、少许电阻电容以及一个基准电压源。电平转换如果FPGA是3.3V IO而TLC5615是5V供电必须进行电平转换。对于DIN、SCLK、CS这三个FPGA输出给DAC的信号可以使用简单的电平转换芯片如TXB0104或者用两个电阻分压将3.3V分压到约3V虽然略低于标准高电平最小值但通常可以工作不推荐用于严谨场合。绝对不要直接将3.3V引脚接到5V器件上长期可能损坏FPGA。电源与去耦这是模拟电路稳定的基石。给TLC5615的VCC和GND之间尽可能靠近芯片引脚并联一个10uF的钽电容和一个0.1uF的陶瓷电容。基准电压源REFIN的输入端也需要同样的去耦处理。基准电压使用一个精度较高的基准电压芯片如ADR45252.5V超低噪声。将其输出连接到TLC5615的REFIN引脚同时通过一个100Ω左右的电阻连接到OUT引脚这是数据手册推荐的小电阻有助于稳定输出。输出滤波TLC5615的OUT引脚输出的是阶梯状的电压内部开关动作会引入毛刺。在输出端接一个简单的RC低通滤波器例如1kΩ电阻串联然后对地接一个0.1uF电容可以平滑波形显著改善输出质量尤其是在输出变化时。连接好电路后上电。用示波器同时测量CS、SCLK、DIN和OUT引脚。首先看数字波形确认时序是否符合仿真波形特别是建立保持时间。然后看模拟输出编写一个简单的测试逻辑让FPGA循环输出0到满量程的值。用示波器观察OUT引脚你应该能看到一个上升的阶梯波。测量每个阶梯的电压值是否与理论公式Vout 2 * Vref * (CODE/1024)计算值相符。检查线性度可以间隔固定编码如每次增加100输出用万用表测量电压看增量是否均匀。5. 性能优化与高级应用一个基础驱动只能让它工作一个优秀的驱动则要追求稳定和性能。5.1 输出稳定性与噪声抑制即使电路和代码都正确输出也可能存在噪声或抖动。除了前面提到的电源去耦和输出滤波还可以在FPGA内部做一些优化同步输出确保驱动模块输出的dac_cs_o、dac_sclk_o、dac_din_o信号都经过输出寄存器Output Register同步后再连接到IO引脚。在FPGA工具中设置IOB属性为TRUE这可以减少输出延迟的抖动。约束时钟对主时钟clk和生成的虚拟spi_clk_en进行正确的时序约束。虽然SPI速度慢但约束能保证clk_div_cnt等逻辑的稳定性。接地策略模拟地AGND和数字地DGND在单点连接。如果板子是双层板尽量将模拟部分的地平面铺完整。5.2 构建波形发生器与闭环控制单个DAC的典型应用就是波形发生器。我们可以利用FPGA内部的ROM或DDS直接数字频率合成技术配合这个DAC驱动产生任意波形。DDS核心在FPGA内定义一个相位累加器例如32位每个时钟周期累加一个频率控制字FTW。取相位累加器的高位如10位作为ROM的查找地址。波形表ROM预存一个正弦波或其他波形的10位幅度数据表。地址就是相位输出就是该相位对应的幅度值。驱动联动将ROM输出的幅度值连接到DAC驱动模块的data_i并以一个固定的频率例如更新率start_i的周期启动转换。这样就能输出一个连续的正弦波了。通过改变FTW可以改变输出频率改变ROM表内容可以改变波形形状。更进一步可以将DAC输出通过运放调理后作为控制系统的参考电压或激励信号再通过ADC读取反馈在FPGA内实现数字PID等闭环控制算法。这时DAC驱动的稳定性和更新速率就成为系统性能的关键一环。6. 常见问题与排查实录在实际调试中你可能会遇到下面这些问题问题现象可能原因排查步骤与解决方案无输出或输出为固定值1. 电源或地未连接好。2.CS引脚一直为高芯片未选中。3. 电平不匹配数字信号未正确识别。1. 用万用表测量芯片VCC和GND间电压是否为5V。2. 用示波器看CS波形是否在发送数据期间有低电平脉冲。3. 测量FPGA输出引脚电压应在3V左右如果直接接5V芯片可能只有2.4V考虑增加电平转换电路。输出电压值不正确1. 参考电压REFIN不准或未接。2. 数据格式发送错误。3. 线性误差大。1. 测量REFIN引脚电压是否为预设的基准电压如2.5V。2. 用示波器抓取DIN波形与SCLK对照看发送的16位数据是否与预期格式[XXXX D9...D0 00]一致。3. 检查输出端负载是否过重阻抗太小TLC5615输出驱动能力有限典型值2mA。输出有高频毛刺1. 电源噪声。2. 数字信号串扰到模拟输出。3. 输出缺少滤波。1. 用示波器交流耦合档观察电源引脚上的噪声加强去耦。2. 检查PCB布局数字线特别是SCLK是否远离模拟输出线OUT。3. 在OUT引脚增加RC低通滤波器如1kΩ 0.1uF。更新速率达不到预期1. SPI时钟频率过快不满足时序。2. 状态机设计有冗余状态转换慢。3. 上游逻辑提供start_i信号的速率慢。1. 确保SCLK周期50ns频率20MHz。2. 分析状态机移除不必要的等待状态。例如FINISH状态可能不需要等待完整周期。3. 计算从start_i到busy_o拉低的总时钟周期数评估理论最大更新率。驱动占用FPGA资源过多设计过于复杂或一个模块驱动多个DAC时复用逻辑不当。一个驱动模块资源消耗应极少几十个LUT。如果驱动多个DAC可以考虑分时复用同一个状态机和数据通路仅用不同的CS信号选通而非实例化多个完整模块。一个典型的调试案例我曾遇到输出正弦波时波形上有规律的“台阶”毛刺。用示波器放大观察发现毛刺出现在每次SCLK跳变的时候。原因是SCLK的数字信号通过空间耦合串扰到了模拟输出走线上。解决方案是在PCB上让这两条线远离并在OUT走线上串联一个小磁珠或电阻并增加对地滤波电容问题立刻解决。驱动一个简单的DAC从看懂数据手册、设计状态机、仿真验证到硬件调试、优化排错是一个完整的FPGA数字系统设计流程的缩影。把每个环节的细节抠清楚获得的不仅仅是这个模块本身更是对数字接口时序、数模混合电路设计、以及系统调试方法的深刻理解。当你看到纯净的模拟波形从自己编写的代码中稳定输出时那种成就感正是硬件开发的乐趣所在。
返回列表