加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
  • 推荐器件
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

源码系列:基于FPGA的数模转换(DA)设计

04/08 11:58
1195
阅读需 21 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。

今天给大侠带来基于FPGA的数模转换(DA)设计,附源码,获取源码,请在“FPGA技术江湖”公众号内回复“ 数模转换设计源码”,可获取源码文件。话不多说,上货。

设计背景:

数模转换器(Digital to Analog Converter)即DAC,是数字世界和模拟世界之间的桥梁。人类生活在模拟世界中,虽然数字器件及设备的比重日益增强,但是DAC的发展仍是必不可少的。从航空航天、国防军事到民用通信、多媒体、数字信号处理等都涉及到DAC应用。DAC基本上由4个部分组成,即权电阻网络、运算放大器、基准电源模拟开关。它是一种将二进制数字量形式的离散信号转换成以参考电压为基准的模拟量的转换器。

设计原理:

本设计采用串行数/模转换芯片TLC5620,TLC5620是一个拥有四路输出的数/模转换器,时钟频率最大可达到1MHz。TLC5620芯片接口如下:

该芯片主要有以下特点:四通道8位电压输出DA转换器、5V单电源供电、串行接口、高阻抗基准输入、可编程1或2输出范围、同时更新设备、内部上电复位、低功耗、半缓冲输出。该芯片主要应用于:可编程电源、数字控制放大器/误差器、移动通信、自动测试设备、研发过程检测和控制和信号合成等。

芯片接口功能表如下:

转换公式:V = REF*(CODE/256)* (1+RNG)

V:实际电压;REF:基准电压;CODE:输入8位数据;RNG:范围。

TLC5620的接口时序如下列图所示:

图1 LOAD控制更新(LDAC为低电平

图2 LDAC控制更新(LDAC为低电平)

 

图3 LOAD控制更新(使用8位串行数据,LOAD为低电平)

图4 LDAC控制更新(使用8位串行数据)

如图1所示:当LOAD为高电平时,数据在CLK的下降沿被锁存至DATA,只要所有数据被锁存,则将LOAD拉低,将数据从串行输入寄存器传送到所选择的DAC。如图2所示:串行编程期间LDAC为高电平,数据在LOAD为低电平时进行锁存,当LDAC变为低电平时传送至DAC输出。如图3、4所示:输入数据最高位(MSB)在前,数据传输使用两个8个时钟周期。

在本设计中运用的是图1的工作时序:

数据通道选择:

RNG:控制DAC输出范围。当RNG为低时,输出范围在基准电压和GND之间;当RNG为高时,输出范围为两倍的基准电压和GND。

设计架构

本设计驱动TLC5620将输入的数字量转换为实际的模拟量(电压),通过四个按键控制四路输出的电压变化,每按一次,电压值也随之上升,同时在数码管上也依次显示相应的值(依次为A1,A0,RNG,输入DATA)。本设计采用的开发板的基准电压为2.5V。设计架构图如下所示:

key_test模块通过四个按键输入的值,组合输出两个数据,11位的wr_data是TLC_DA模块解码所需的数据。20位的out_data是seg_num模块数码管显示所需的数据。

设计代码

顶层top模块代码如下:

module top(    //顶层模块:将各个模块组合//外部接口  input         clk,   //系统时钟50MHz   input         rst_n, //低电平复位  input   [3:0] key,   //四个按键组成的按键信号,低电平有效
  output        da_data,//DA串行接口数据  output       da_clk, //DA串行接口时钟       output        da_ldac,//DA更新信号  output     da_load, //DA串行接口加载控制信号  output  [7:0] seg,   //数码管段选  output  [2:0] sel   //数码管位选);  //内部信号:模块内部的接口信号,比如模块TLC_DA的输出信号data_in,通过内部信号r_data与模块key_test的输入信号wr_data相连  wire [10:0] wr_data;  wire [19:0] out_data;  //输入给数码管的数据
  //模块例化  TLC_DA TLC_DA_inst(         //输入数字量转换为模拟量模块    .clk(clk),    .rst_n(rst_n),    .da_clk(da_clk),                  .da_data(da_data),    .da_ldac(da_ldac),    .da_load(da_load),    .data_in(wr_data)  );
  key_test key_test_inst(    //按键控制模块    .clk(clk),    .rst_n(rst_n),    .key(key),    .wr_data(wr_data),    .out_data(out_data)  );
  seg_num seg_num_inst(      //数码管显示模块    .clk(clk),    .rst_n(rst_n),    .data_in(out_data),    .seg(seg),    .sel(sel)  );
endmodule

key_test模块代码如下:

module key_test(     //按键控制模块//端口信号:模块的输入输出接口  input           clk,       //50MHZ  input           rst_n,     //低电平复位  input  [3:0]    key,       //四个按键组合信号
  output [10:0]    wr_data,    //输出一帧数据,为DA模块的输入数字量  output [19:0]    out_data    //输出数码管显示数据 );
  //计数器时钟分频   reg [30:0] cnt;  reg        clk_r;  //分频时钟:在消除抖动的时钟频率下进行按键的检测  always@(posedge clk or negedge rst_n) //按键消抖,时间为0.2s进行一次检测    if(!rst_n)      begin        cnt <= 0;        clk_r <= 0;      end   else if(cnt < 30'd1000_0000)        cnt <= cnt + 1'b1;    else       begin         cnt <= 0;        clk_r <= ~clk_r;       end
  //按键为低电平有效,当检测到对应按键之后,相应数值加1,并显示相应的通道  reg [7:0]  data;     //按键输入数据  reg [1:0]  channel;  //通道选择  reg [7:0]  key1,key2,key3,key4; //相应四个按键  always@(posedge clk_r or negedge rst_n )   if(!rst_n)           begin      key1 <= 8'h00;      key2 <= 8'h00;      key3 <= 8'h00;      key4 <= 8'h00;      data <= 8'h00;      channel <= 2'b00;      end   else    case(key)      4'b1110 : begin      //按键1:选择通道A,且输入数字量加1          channel <= 2'b00;            key1 <= key1 + 1'b1;            data <= key1;            end     4'b1101 : begin     //按键2:选择通道B,且输入数字量加1            channel <= 2'b01;            key2 <= key2 + 1'b1;            data <= key2;            end     4'b1011 : begin    //按键3:选择通道C,且输入数字量加1            channel <= 2'b10;            key3 <= key3 + 1'b1;            data <= key3;            end     4'b0111 : begin   //按键4:选择通道D,且输入数字量加1            channel <= 2'b11;            key4 <= key4 + 1'b1;            data <= key4;            end     default :;    endcase
  //用赋值语句将需要的数据组合起来,在此例中将RNG默认为1  assign wr_data = {channel,1'b1,data};  assign out_data = {{3'b000,channel[1]},3'b000,channel[0],4'h1,data};
endmodule

TLC_DA模块代码如下:

module TLC_DA(    //输入数字量转换为模拟量模块,本实验用TLC5620 //端口信号:模块的输入输出接口  input         clk,   //系统时钟50MHz   input         rst_n, //低电平复位  input [10:0]  data_in, //输入一帧数据    output         da_data, //串行数据接口  output        da_clk,  //串行时钟接口       output reg    da_ldac, //更新控制信号  output reg     da_load  //串行加载控制接口  );
  //计数器时钟分频:根据芯片内部的时序要求进行分频  reg [30:0] cnt;  wire       da_clk_r;  //TLC 5620内部时钟信号  always@(posedge clk or negedge rst_n)  //满足协议中的时钟要求,在TLC 5620中时钟要求不大于1MHZ    if(!rst_n)      cnt  <= 6'd0;    else        cnt <= cnt + 1'b1;
  assign da_clk_r = cnt[5];
  //接收时序状态机      reg [2:0]  state;  reg [3:0]  cnt_da;  reg        da_data_r;  reg        da_data_en;  //限定da_data,da_clk的有效区域  always@(posedge da_clk_r or negedge rst_n)    if(!rst_n)      begin        state <= 0;        cnt_da <= 0;        da_load <= 1;        da_ldac <= 0;              da_data_r <= 1'b1;        da_data_en <= 0;      end    else      case(state)        0: state <= 1;        1: begin          da_load <= 1;          da_data_en <= 1;            if(cnt_da <= 10)              begin                cnt_da <= cnt_da + 1'b1;                case(cnt_da)                  0:  da_data_r <= data_in[10];                  1:  da_data_r <= data_in[9];                  2:  da_data_r <= data_in[8];                  3:  da_data_r <= data_in[7];                  4:  da_data_r <= data_in[6];                  5:  da_data_r <= data_in[5];                  6:  da_data_r <= data_in[4];                  7:  da_data_r <= data_in[3];                  8:  da_data_r <= data_in[2];                  9:  da_data_r <= data_in[1];                  10: da_data_r <= data_in[0];                  default:;                endcase                state <= 1;              end            else              begin                cnt_da <= 0;                state <= 2;                da_data_en <= 0;              end          end        2: begin            da_load <= 0;            state <= 3;          end        3: begin            da_load <= 1;            state <= 0;          end        default: state <= 0;      endcase
  assign da_data = (da_data_en) ? da_data_r : 1'b1;  assign da_clk  = (da_data_en)?da_clk_r : 1'b0;
endmodule

seg_num模块代码如下:

module seg_num(      //数码管显示模块:选择数码管0-4共5个数码管显示{A1,A0,RNG,DATA}//端口信号:模块的输入输出接口  input         clk,   //系统时钟50MHz   input         rst_n, //低电平复位  input  [19:0]  data_in, //20位输入数据
  output reg [7:0] seg,   //数码管段选  output reg [2:0] sel    //数码管位选  );
  //通过查找表的方式,将相应位的数码管与数据的相应位一一对应  reg [3:0]  num;     always@(*)    case(sel)           4: num = data_in[3:0];    //第五个数码管显示数据的低四位[3:0]      3: num = data_in[7:4];    //第四个数码管显示数据的低四位[7:4]      2: num = data_in[11:8];   //第三个数码管显示数据的低四位[11:8]      1: num = data_in[15:12];  //第二个数码管显示数据的低四位[15:12]      0: num = data_in[19:16];  //第一个数码管显示数据的低四位[19:16]      default:;    endcase
  //通过查找表的方式,将数据与数码管的显示方式一一对应    always@(*)      case(num)      0:  seg <= 8'hC0;    //8'b1100_0000      1:  seg <= 8'hF9;  //8'b1111_1001      2:   seg <= 8'hA4;  //8'b1010_0100        3:  seg <= 8'hB0;  //8'b1011_0000      4:  seg <= 8'h99;  //8'b1001_1001      5:  seg <= 8'h92;  //8'b1001_0010      6:  seg <= 8'h82;  //8'b1000_0010      7:  seg <= 8'hF8;  //8'b1111_1000      8:  seg <= 8'h80;  //8'b1000_0000      9:  seg <= 8'h90;  //8'b1001_0000      default:seg <= 8'hFF; //8'b1111_1111    endcase
  //计数器时钟分频:用cnt第10位的变化作为分频时钟    reg [23:0]  cnt;        always@(posedge clk or negedge rst_n)     if(!rst_n)      cnt <= 4'd0;    else      cnt <= cnt + 1'b1;  //在分频时钟下,数码管的0-5位依次循环  always@(posedge cnt[10] or negedge rst_n)   //分频时钟为2^10/50M    if(!rst_n)      sel <= 0;    else if(sel < 4)      sel <= sel + 1'b1;    else      sel <= 0;      

仿真测试

test顶层模块测试代码:

`timescale 1 ns/ 1 ns    //设置仿真时间单位与精度分别为1ns/1ns           //若设为`timescale 1ns/1ps  (#200 就是延时200 ns; 1ps就是仿真的精度)module test;    //测试模块:主要是将激励信号赋相应的值,仿真之后观察波形,验证与实际功能是否一样
  //端口信号定义,激励信号为reg型  reg       clk;  reg     rst_n;  reg  [3:0]   key;                                                wire [7:0]  seg;  wire [2:0]   sel;
  //模块例化                           top top(     .clk(clk),    .rst_n(rst_n),    .key(key),    .seg(seg),    .sel(sel)  );
   //初始化激励,以及给相应激励赋值    initial                                                    begin                                                        clk = 0;rst_n = 0; key = 4'b1111;   //在复位阶段,将激励赋初值
    #200     rst_n = 1;      //在延时200ns后将复位信号置为1
    //实现按键1开,关    #500000  key = 4'b1110;    #500000  key = 4'b1111;
    end
  always  #10  clk = ~clk;  //时钟的表示,即每隔10ns翻转一次,一个周期的时间即为20ns,时钟为1/20ns = 50MHZ
endmodule

仿真图如下:

由于仿真时间原因,这里只测试按键1按下时的数码管显示,显示为00100,表示通道A,RNG为1,输入数字量为00。之后实际下板验证,用万用表也可测出输入数字量对应的电压值。

推荐器件

更多器件
器件型号 数量 器件厂商 器件描述 数据手册 ECAD模型 风险等级 参考价格 更多信息
ICE40LP8K-CM225 1 Lattice Semiconductor Corporation Field Programmable Gate Array, 960 CLBs, 133MHz, 7680-Cell, CMOS, PBGA225, UCBGA-225
$10.96 查看
XC6SLX25-2FG484I 1 AMD Xilinx Field Programmable Gate Array, 1879 CLBs, 667MHz, 24051-Cell, CMOS, PBGA484, 23 X 23 MM, 1 MM PITCH, FBGA-484
$656.54 查看
A3P600-FG256I 1 Microchip Technology Inc Field Programmable Gate Array, 13824 CLBs, 600000 Gates, 350MHz, CMOS, PBGA256
$50.78 查看

相关推荐

电子产业图谱

任何技术的学习就好比一个江湖,对于每一位侠客都需要不断的历练,从初入江湖的小白到归隐山林的隐世高人,需要不断的自我感悟自己修炼,让我们一起仗剑闯FPGA乃至更大的江湖。