「FPGA学习」一文教你轻松实现数模转换的设计

设计背景:

数模转换器(Digital to Analog Converter)即DAC,是数字世界和模拟世界之间的桥梁。人类生活在模拟世界中,虽然数字器件及设备的比重日益增强,但是DAC的发展仍是必不可少的。

从航空航天、国防军事到民用通信、多媒体、数字信号处理等都涉及到DAC应用。DAC基本上由4个部分组成,即权电阻网络、运算放大器、基准电源和模拟开关。它是一种将二进制数字量形式的离散信号转换成以参考电压为基准的模拟量的转换器。

设计原理:

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

「FPGA学习」一文教你轻松实现数模转换的设计

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

转换公式: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-4共5个数码管显示{A1,A0,RNG,DATA}

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

5

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

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

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

11 reg [3:0] num;

12 always@(*)

13 case(sel)

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

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

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 //计数器时钟分频:用cnt第10位的变化作为分频时钟

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 //外部接口

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

仿真图:

「FPGA学习」一文教你轻松实现数模转换的设计

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

「FPGA学习」一文教你轻松实现数模转换的设计
未经允许不得转载:新闻 » 「FPGA学习」一文教你轻松实现数模转换的设计