DAC转化——FPGA驱动LTC1446

发布时间 2023-12-28 19:48:24作者: nanozy

一、前言

最近在学习利用FPGA结合DAC芯片实现数模转换,在实验中选择的LTC1446这款芯片。接下来自己将结合芯片手册进行分析,并编写Verilog代码并进行仿真验证。

二、结合LTC1446芯片手册分析

在这里插入图片描述

  1. 首先从上述第一处可以看出该款芯片为双通道输入,最多可将24位的数字信号进行转换。
  2. 对于第二处,所谓的三线通信其实在这里就是Spi通信的“变种”,因为版权的原因,很多公司这样进行描述,这里是clk,cs,mosi三线;另外,其提高更新频率为500KHZ,则我们在将数字信号输入的频率是不能超过500KHZ的,不然该芯片不能正确工作。

image-20230103234823204

  1. 第三处,展示的是芯片和一般微处理器的连接方式,从而印证了第二处所说的是spi的主设备数据输出,从设备数据输入模式。

image-20230104122537091

  1. 第四处,可以看到当$\overline{CS}$/LD为低电平时,数据是被锁存的,不能输出;当$\overline{CS}$/LD为高电平时,数字信号转化为模拟信号输出,这在写代码的时候也是需要注意的。
  2. 第五处,$D_{OUT}$需要在clk上升沿的时候才可以获取其输出,不过在这个实验仿真中不需要用到,所以需要额外关注。

image-20230104124041673

  1. 第六处的时序转换是最需要我们关注的
  • 首先看clk信号,初始状态为高电平,则在仿真过程中编写testbench时需要注意,另外可以看到对于时钟信号的周期是有标注时间的,需要我们看一下有无限制。

在这里插入图片描述

可以看到最小时钟周期为120ns,1/(120*$10^{-9}$)=8.3MHZ,一般我们使用的FPGA的时钟信号为50MHZ.所以需要进行分频得到芯片的输入时钟信号。

  • 再看$D_{IN}$信号,传输数据是时候是先对应A通道的12位,从高到底;然后再是对应A通道的12位,从高到底。
  • 再看$\overline{CS}$/LD信号,其开始为低电平,在clk为高电平时,$\overline{CS}$/LD由低电平转为高电平;在clk为低电平时,$\overline{CS}$/LD由高电平转为低电平
  • $D_{OUT}$可以看出输出的滞后一个24位数据帧的。

image-20230104131830316

  1. 再看第七处
  • 在时钟上升沿数据被加载进入转换寄存器
  • 先A通道的12bit,再B通道的12bit,且从分别从高位到低位,和时序图中描述的一样
  • $\overline{CS}$/LD被拉高的时候,数据加载到DAC寄存器
  • 时钟内部被禁用当$\overline{CS}$/LD被拉高的时候
  • 在clk为低电平时,$\overline{CS}$/LD由高电平转为低电平,和时序图中的描述一致

三、Verilog代码与仿真

先画出输入输出的模块框图如下:

image-20230104133112881

输入:

clk:FPGA时钟信号

n_rst:复位信号

data_in:24位数字信号

set:使能信号

输出:

spi_clk:生成的输入到芯片的时钟

spi_cs:生成的输入到芯片的cs信号

spi_din:生成的输入到芯片符合时序的数字信号

工程代码:

`timescale 1ns/1ns
module LTC1446_DA
(
    input clk,   //50MHZ时钟输入
    input n_rst,
    
    input [23:0] data_in,  //输入的电压数值(高12位:通道A输出 ;低12位:通道B输出)
    input set,
    
    output reg spi_clk,
    output reg spi_cs,
    output spi_din
);

//0-15计数,用于16分频使用
reg [3:0] cnt;
always@(posedge clk or negedge n_rst)
begin
    if(n_rst==1'b0)
        cnt<=4'h0;
    else if(set==1'b1)
        cnt<=4'h0;
    else
        cnt<=cnt+1'b1;
end

reg [5:0] len_cnt;//发送为数据计数
always@(posedge clk or negedge n_rst)
begin
    if(n_rst==1'b0)
        len_cnt<=6'h0;
    else if(set==1'b1)
        len_cnt<=6'd25; //一帧数据数为25
    else if((cnt==4'd15)&&(len_cnt>1'b0))  //cnt==15时,发送一位数据,即spi_clk时钟下降沿时,使得其上升沿可读取稳定数值
        len_cnt<=len_cnt-6'd1;
    else
        len_cnt<=len_cnt;
end

reg [23:0] data_reg;//输出SPI_DIN数据
always@(posedge clk or negedge n_rst)
begin
    if(n_rst==1'b0)
        data_reg<=23'h0;
    else if(set==1'b1)
        data_reg<={data_in};
    else if((cnt==4'd15)&&(len_cnt>1'b0))
        data_reg<={data_reg[22:0],1'b0};//在spi_clk的下降沿,将数据最高位发送到spi_din
    else
        data_reg<=data_reg;
end

assign spi_din=data_reg[23];

//生成spi_clk时钟信号(clk/16分频)
always@(posedge clk or negedge n_rst)
begin
    if(n_rst==1'b0)
        spi_clk<=1'b1;
    else if(set==1'b1)
        spi_clk<=1'b1;
    else if(len_cnt>1'b0)
        spi_clk<=cnt[3]; //对clk/16分频
    else
        spi_clk<=1'b1;
end

always@(posedge clk or negedge n_rst)
begin
    if(n_rst==1'b0)
        spi_cs<=1'b0;
    else if(set==1'b1)
        spi_cs<=1'b0;
    else if((len_cnt==6'd2)&&(cnt>4'd11))//已完成24位数据发送&当spi_clk下降沿之前
        spi_cs<=1'b1;  //cnt=[12-15]期间为高电平,其余为低电平
    else
        spi_cs<=1'b0;
end

endmodule

仿真代码:

`timescale 1ns/1ns
module tb_LTC1446_DA();

reg clk;
reg n_rst;
reg [23:0] data_in;
reg set;

wire spi_clk;
wire spi_cs;
wire spi_din;

always #10 clk=~clk;

initial 
begin
    clk=0;
    n_rst<=1;
    data_in<=0;
    set<=0;
    #10
    n_rst<=0;
    #20
    n_rst<=1;
    #20
    data_in<=24'b1110_1010_1100_0101_1100_1010;
    set<=1;
    #20
    set<=0;
end
LTC1446_DA LTC1446_DA_inst
(
    .clk     (clk    )   ,
    .n_rst   (n_rst  )   ,
    
    .data_in (data_in)   ,
    .set     (set    )   ,

    .spi_clk (spi_clk)   ,
    .spi_cs  (spi_cs )   ,
    .spi_din (spi_din)
);

endmodule

仿真结果:

image-20230104133951330

分析的时候对应每个信号判断时候符合要求即可。

四、总结

对于这类工程,我觉得最重要的就是厘清芯片手册的中不同信号对应的时序,各种限制如时间周期等才能写出能正常驱动芯片的代码。当然自己的水平是非常有限的,对于不同的芯片手册自己学习中经常会有看的不知所云,无法把握重点的情况,但是我坚信自己一定不是第一个使用该手册的人,前人一定会有相关描述或工程可以借鉴,通过检索不断学习,一定会找到解决问题的办法。加油,努力成为一名优秀的工程师!