FPGA学习系列:30. 数模转换的设计(DA)

描述

设计背景: 

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

 

设计原理: 

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

    FPGA

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

    芯片接口功能表如下:

FPGA

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

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

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

    FPGA

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

    FPGA

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

    FPGA

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

    

    FPGA

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

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

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

    FPGA

    

    数据通道选择:

    FPGA

    

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

 

设计架构图: 

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

    FPGA

    

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

    

设计代码: 

key_test模块代码如下:

0   module key_test(     //按键控制模块

1   //端口信号:模块的输入输出接口

2     input             clk,       //50MHZ

3     input             rst_n,     //低电平复位

4     input  [3:0]      key,       //四个按键组合信号

5     

6     output [10:0]    wr_data,    //输出一帧数据,为DA模块的输入数字量

7     output [19:0]    out_data    //输出数码管显示数据

8    );

9    

10      //计数器时钟分频

11      reg [30:0] cnt;

12      reg        clk_r;  //分频时钟:在消除抖动的时钟频率下进行按键的检测

13      always@(posedge clk or negedge rst_n) //按键消抖,时间为0.2s进行一次检测

14          if(!rst_n)

15              begin

16                  cnt <= 0;

17                  clk_r <= 0;

18              end

19       else if(cnt < 30'd1000_0000)

20                  cnt <= cnt + 1'b1;

21        else

22               begin 

23                  cnt <= 0;

24                  clk_r <= ~clk_r;

25               end

26               

27      //按键为低电平有效,当检测到对应按键之后,相应数值加1,并显示相应的通道

28      reg [7:0]  data;     //按键输入数据

29      reg [1:0]  channel;  //通道选择

30      reg [7:0]  key1,key2,key3,key4; //相应四个按键

31      always@(posedge clk_r or negedge rst_n )

32       if(!rst_n)     

33            begin

34              key1 <= 8'h00;

35              key2 <= 8'h00;

36              key3 <= 8'h00;

37              key4 <= 8'h00;

38              data <= 8'h00;

39              channel <= 2'b00;

40            end

41       else

42        case(key) 

43           4'b1110 : begin      //按键1:选择通道A,且输入数字量加1

44                      channel <= 2'b00;

45                          key1 <= key1 + 1'b1;

46                          data <= key1;

47                        end

48           4'b1101 : begin     //按键2:选择通道B,且输入数字量加1

49                          channel <= 2'b01;

50                          key2 <= key2 + 1'b1;

51                          data <= key2;

52                        end

53           4'b1011 : begin    //按键3:选择通道C,且输入数字量加1

54                          channel <= 2'b10;

55                          key3 <= key3 + 1'b1;

56                          data <= key3;

57                        end

58           4'b0111 : begin   //按键4:选择通道D,且输入数字量加1

59                          channel <= 2'b11;

60                          key4 <= key4 + 1'b1;

61                          data <= key4;

62                        end

63           default :;

64          endcase

65          

66      //用赋值语句将需要的数据组合起来,在此例中将RNG默认为1

67      assign wr_data = {channel,1'b1,data};            assignout_data={{3'b000,channel[1]},3'b000,channel[0],4'h1,data};

68

69      endmodule 

TLC_DA模块代码如下:

0   module TLC_DA(    //输入数字量转换为模拟量模块,本实验用TLC5620

1   //端口信号:模块的输入输出接口

2       input         clk,   //系统时钟50MHz

3       input         rst_n, //低电平复位

4       input [10:0]  data_in, //输入一帧数据

5       output        da_data, //串行数据接口

6       output        da_clk,  //串行时钟接口     

7       output reg    da_ldac, //更新控制信号

8       output reg    da_load  //串行加载控制接口

9       );

10

11      //计数器时钟分频:根据芯片内部的时序要求进行分频

12      reg [30:0] cnt;

13      wire       da_clk_r;  //TLC 5620内部时钟信号

14      always@(posedge clk or negedge rst_n)  //满足协议中的时钟要求,在TLC 5620中时钟要求不大于1MHZ

15          if(!rst_n)

16              cnt  <= 6'd0;

17          else    

18              cnt <= cnt + 1'b1;

19              

20      assign da_clk_r = cnt[5];

21              

22      //接收时序状态机     

23      reg [2:0]  state;

24      reg [3:0]  cnt_da;

25      reg        da_data_r;

26      reg        da_data_en;  //限定da_data,da_clk的有效区域

27      always@(posedge da_clk_r or negedge rst_n)

28          if(!rst_n)

29              begin

30                  state <= 0;

31                  cnt_da <= 0;

32                  da_load <= 1;

33                  da_ldac <= 0;           

34                  da_data_r <= 1'b1;

35                  da_data_en <= 0;

36              end

37          else

38              case(state)

39                  0: state <= 1;

40                  1: begin

41                      da_load <= 1;

42                      da_data_en <= 1;

43                          if(cnt_da <= 10)

44                              begin

45                                  cnt_da <= cnt_da + 1'b1;

46                                  case(cnt_da)

47                                      0:  da_data_r <= data_in[10];

48                                      1:  da_data_r <= data_in[9];

49                                      2:  da_data_r <= data_in[8];

50                                      3:  da_data_r <= data_in[7];

51                                      4:  da_data_r <= data_in[6];

52                                      5:  da_data_r <= data_in[5];

53                                      6:  da_data_r <= data_in[4];

54                                      7:  da_data_r <= data_in[3];

55                                      8:  da_data_r <= data_in[2];

56                                      9:  da_data_r <= data_in[1];

57                                      10: da_data_r <= data_in[0];

58                                      default:;

59                                  endcase

60                                  state <= 1;

61                              end

62                          else

63                              begin

64                                  cnt_da <= 0;

65                                  state <= 2;

66                                  da_data_en <= 0;

67                              end

68                      end

69                  2: begin

70                          da_load <= 0;

71                          state <= 3;

72                      end

73                  3: begin

74                          da_load <= 1;

75                          state <= 0;

76                      end

77                  default: state <= 0;

78              endcase

79

80      assign da_data = (da_data_en) ? da_data_r : 1'b1;

81      assign da_clk  = (da_data_en)?da_clk_r : 1'b0;

82

83  endmodule   

seg_num模块代码如下:

0   module seg_num(      //数码管显示模块:选择数码管0-45个数码管显示{A1A0RNGDATA}

1   //端口信号:模块的输入输出接口

2       input         clk,   //系统时钟50MHz

3       input         rst_n, //低电平复位

4       input  [19:0]  data_in, //20位输入数据

5       

6       output reg [7:0] seg,   //数码管段选

7       output reg [2:0] sel    //数码管位选

8       );

9 

10      //通过查找表的方式,将相应位的数码管与数据的相应位一一对应

11      reg [3:0]  num;   

12      always@(*)

13          case(sel)     

14              4: num = data_in[3:0];    //第五个数码管显示数据的低四位[30]

15              3: num = data_in[7:4];    //第四个数码管显示数据的低四位[74]

16              2: num = data_in[11:8];   //第三个数码管显示数据的低四位[11:8]

17              1: num = data_in[15:12];  //第二个数码管显示数据的低四位[15:12]

18              0: num = data_in[19:16];  //第一个数码管显示数据的低四位[19:16]

19              default:;

20          endcase

21

22      //通过查找表的方式,将数据与数码管的显示方式一一对应   

23      always@(*)  

24          case(num)

25              0:  seg <= 8'hC0;   //8'b1100_0000

26              1:  seg <= 8'hF9;   //8'b1111_1001

27              2:   seg <= 8'hA4;  //8'b1010_0100  

28              3:  seg <= 8'hB0;   //8'b1011_0000

29              4:  seg <= 8'h99;   //8'b1001_1001

30              5:  seg <= 8'h92;   //8'b1001_0010

31              6:  seg <= 8'h82;   //8'b1000_0010

32              7:  seg <= 8'hF8;   //8'b1111_1000

33              8:  seg <= 8'h80;   //8'b1000_0000

34              9:  seg <= 8'h90;   //8'b1001_0000

35              default:seg <= 8'hFF; //8'b1111_1111

36          endcase

37          

38      //计数器时钟分频:用cnt10位的变化作为分频时钟

39      reg [23:0]  cnt;            

40      always@(posedge clk or negedge rst_n) 

41          if(!rst_n)

42              cnt <= 4'd0;

43          else

44              cnt <= cnt + 1'b1;

45      //在分频时钟下,数码管的0-5位依次循环

46      always@(posedge cnt[10] or negedge rst_n)   //分频时钟为2^10/50M

47          if(!rst_n)

48              sel <= 0;

49          else if(sel < 4)

50              sel <= sel + 1'b1;

51          else

52              sel <= 0;   

53          

54  endmodule 

top顶层模块代码如下:

0   module top(    //顶层模块:将各个模块组合

1   //外部接口

2     input         clk,   //系统时钟50MHz

3     input         rst_n, //低电平复位

4     input   [3:0] key,   //四个按键组成的按键信号,低电平有效

5     

6     output         da_data,//DA串行接口数据

7     output         da_clk, //DA串行接口时钟     

8     output        da_ldac,//DA更新信号

9     output         da_load, //DA串行接口加载控制信号

10    output  [7:0] seg,   //数码管段选

11    output  [2:0] sel   //数码管位选

12  );

13      //内部信号:模块内部的接口信号,比如模块TLC_DA的输出信号data_in,通过内部信号r_data与模块key_test的输入信号wr_data相连

14      wire [10:0] wr_data;

15      wire [19:0] out_data;  //输入给数码管的数据

16

17      //模块例化

18      TLC_DA TLC_DA_inst(         //输入数字量转换为模拟量模块

19          .clk(clk),

20          .rst_n(rst_n),

21          .da_clk(da_clk),              

22          .da_data(da_data),

23          .da_ldac(da_ldac),

24          .da_load(da_load),

25          .data_in(wr_data)

26      );

27

28      key_test key_test_inst(    //按键控制模块

29          .clk(clk),

30          .rst_n(rst_n),

31          .key(key),

32          .wr_data(wr_data),

33          .out_data(out_data)

34      );

35          

36      seg_num seg_num_inst(           //数码管显示模块

37          .clk(clk),

38          .rst_n(rst_n),

39          .data_in(out_data),

40          .seg(seg),

41          .sel(sel)

42      );

43          

44  endmodule 

test顶层模块测试代码:

0   `timescale 1 ns/ 1 ns    //设置仿真时间单位与精度分别为1ns/1ns

1                        //若设为`timescale 1ns/1ps  (#200 就是延时200 ns; 1ps就是仿真的精度)

2   module test;    //测试模块:主要是将激励信号赋相应的值,仿真之后观察波形,验证与实际功能是否一样

3 

4       //端口信号定义,激励信号为reg

5       reg         clk;

6       reg         rst_n;

7       reg  [3:0]  key;                                              

8       wire [7:0]  seg;

9       wire [2:0]  sel;

10

11      //模块例化                         

12      top top( 

13          .clk(clk),

14          .rst_n(rst_n),

15          .key(key),

16          .seg(seg),

17          .sel(sel)

18      );

19

20       //初始化激励,以及给相应激励赋值    

21      initial                                                

22          begin                                                  

23            clk = 0;rst_n = 0; key = 4'b1111;     //在复位阶段,将激励赋初值

24           

25          #200     rst_n = 1;     //在延时200ns后将复位信号置为1

26

27          //实现按键1开,关

28          #500000  key = 4'b1110;

29          #500000  key = 4'b1111;

30                                

31          end 

32          

33      always  #10  clk = ~clk;  //时钟的表示,即每隔10ns翻转一次,一个周期的时间即为20ns,时钟为1/20ns = 50MHZ                                           

34

35  endmodule 

 

  

仿真图: 

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


 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分