FPGA学习系列:29. 数字电压表设计(AD)

设计背景: 
    模数转换器,又称a/d转换器,简称adc,通常是指一个将模拟信号转换为抗干扰性更强的数字信号的电子器件。一般的adc是将一个输入电压信号转换为一个输出的数字信号。由于数字信号本身不具有实际意义,仅仅表示一个相对大小,故任何一个adc都需要一个参考模拟量作为转换标准。比较常见的参考标准为最大的可转换信号大小,而输出的数字量则表示输入信号相对于参考信号的大小。本设计则通过对模数转换芯片(tlc549)的采样控制,实现一个简易的数字电压表。
设计原理: 
    tlc549典型的配置电路如下图所示:
tlc549的端口描述如下:
tlc549是一个8位的串行模数转换器,a/d转换时间最大为17us,最大转换速率为4mhz。下图为tlc549的访问时序,从图中可以看出,tlc549的使用只需对外接输入输出时钟(i/o clk)和芯片选择(/cs)、输入的模拟信号(analog in)的控制。
分析时序图可知:当片选信号(/cs)拉低时,adc前一次的转换数据(a)的最高位a7立即出现在数据线data out上,之后的数据在时钟i/o clock的下降沿改变,可在i/o clock的上升沿读取数据。转换时,/cs要置为高电平。在设计操作时,要注意tsu(cs)、tconv、twh(cs)和i/o clock的频率这几个参数。tsu(cs)为cs拉低到i/o clock第一个时钟到来的时间,至少要1.4us;twh(cs)为adc的转换时钟,不超过17us,tconv的值也不超过17us;i/o clock为 1.1mhz。其他参数可参考数据手册。
由于adc是8位的,所以采样的电压值为:
v =(d*vref)/256
    其中v为采样的电压值;d为adc转换后读取的8位二进制数;vref为参考电压值,此处为2.5v。
设计架构图: 
    本设计通过调节电位器rw1改变adc的模拟输入值,数据采样读取后由数码管显示,最后用万用表测量输入电压,并与读取在数码管上的数据(单位为mv)作比较。设计的架构图如下:
设计架构图对应端口的功能描述表:
tlc549_driver模块采用序列机实现接口访问时序,并且产生1mhz的adc_clk和采集到adc_data;control模块,将采集到的adc数据(adc_data)换算成对应的电压值,并经过二进制到bcd转换以后传送到数码管;dig_led_drive模块负责数码管的驱动,将传递过来的数据显示出来。
设计代码: 
    tlc549_driver模块代码:
0   module tlc549_driver (clk,rst_n,en,adc_din,adc_clk,adc_cs_n,data,get_flag);

2       input clk;  //系统50mhz时钟输入
3       input rst_n;//全局复位
4       input en;   //adc转换使能,高电平有效
5       
6       input adc_din;//adc串行数据输入
7       
8       output reg adc_clk; //adc时钟信号输出
9       output reg adc_cs_n;//adc片选信号输出
10      output reg get_flag;//数据转换完成标志
11      output reg [7:0] data;//adc转换以后的电压值
12      
13      reg [10:0] cnt1;   //系统时钟计数器
14      reg [7:0] data_tmp;//数据寄存器
15      
16      //系统时钟上升沿计数
17      always@(posedge clk or negedge rst_n)
18      begin 
19          if(!rst_n)
20              cnt1 <= 11'd0;
21          else if(!en)
22              cnt1 <= 11'd0;
23          else if(cnt1 == 11'd1310)
24              cnt1 <= 11'd0;
25          else 
26              cnt1 <= cnt1 + 1'b1;    
27      end
28      
29      always@(posedge clk or negedge rst_n)
30      begin
31          if(!rst_n)
32              begin
33                  adc_clk  <= 1'b0;
34                  adc_cs_n <= 1'b1;
35                  data_tmp <= 8'd0;
36                  data <= 8'd0;
37              end
38          else if(en)
39              begin
40                  case(cnt1)
41                      1   :   adc_cs_n <= 1'b0;  //1~71(tsu)
42                      71      :   begin adc_clk <= 1; data_tmp[7] <= adc_din;end
43                      96      :   adc_clk <= 0;
44                      121 :   begin adc_clk <= 1; data_tmp[6] <= adc_din;end
45                      146 :   adc_clk <= 0;
46                      171 :   begin adc_clk <= 1; data_tmp[5] <= adc_din;end
47                      196 :   adc_clk <= 0;
48                      221 :   begin adc_clk <= 1; data_tmp[4] <= adc_din;end
49                      246 :   adc_clk <= 0;
50                      271 :   begin adc_clk <= 1; data_tmp[3] <= adc_din;end
51                      296 :   adc_clk <= 0;
52                      321 :   begin adc_clk <= 1; data_tmp[2] <= adc_din;end
53                      346 :   adc_clk <= 0;
54                      371 :   begin adc_clk <= 1; data_tmp[1] <= adc_din;end
55                      396 :   adc_clk <= 0;
56                      421 :   begin adc_clk <= 1; data_tmp[0] <= adc_din;end
57                      446 :   begin adc_clk <= 0; adc_cs_n <= 1'b1; get_flag<=1;end
58                      447 :   begin data <= data_tmp;  get_flag<=0; end //447~1310(twh)
59                      1310:   ;
60                      default:;
61                  endcase 
62              end
63          else
64              begin
65                  adc_cs_n <= 1'b1;
66                  adc_clk <= 1'b0;
67              end
68      end 
69
70  endmodule
control模块代码:
0   module control(clk,rst_n,get_flag,adc_data,seg_data);
1       
2       input clk;//系统时钟输入
3       input rst_n;//系统复位
4       input get_flag;//adc采集数据完成标志
5       input [7:0]adc_data;//adc采集数据输入
6       
7       output reg [23:0]seg_data;//数码管待显示数据
8       
9       reg [3:0]qianwei;  //千位
10      reg [3:0]baiwei;   //百位
11      reg [3:0]shiwei;   //十位
12      reg [3:0]gewei;    //个位
13      reg [15:0]tenvalue;//采样的电压值
14      
15      //采集电压值计算
16      always@(posedge clk or negedge rst_n)
17      begin
18          if(!rst_n)
19              tenvalue<=0;
20          else if(get_flag)//新的数据采集完成,可以进行计算
21              tenvalue<=(adc_data*100*25)/256;
22      end     
23          
24      //二进制转bcd值
25      always@(posedge clk or negedge rst_n)
26      begin
27          if(!rst_n) 
28              begin 
29                  qianwei<=0;
30                  baiwei<=0;
31                  shiwei<=0;
32                  gewei<=0;
33              end
34          else 
35          begin 
36              qianwei<=tenvalue/1000;     //2
37              baiwei<=(tenvalue/100)%10;  //5
38              shiwei<=(tenvalue/10)%10;   //0
39              gewei<=tenvalue%10;         //0
40          end         
41      end 
42          
43      //数码管显示数值
44      always@(posedge clk or negedge rst_n)
45      begin
46          if(!rst_n)
47              seg_data<=0;
48          else
49              seg_data<={
50                          qianwei,  //千位
51                          baiwei,   //百位
52                          shiwei,   //十位
53                          gewei,    //个位
54                          8'hff     //空闲
55                      };
56      end 
57      
58  endmodule
dig_led_drive模块代码:
0   /*数码管扫描模块,位选为外部74hc138译码器进行控制*/
1   /*仿真时请将本文件设置为顶层,并在代码中根据相应注释中的内容选择cnt1_max = 24*/
2  
3       module dig_led_drive(clk,rst_n,data,dig_led_seg,dig_led_sel);
4  
5       input clk;       //系统时钟输入
6       input rst_n;     //系统复位
7       input [23:0]data;//待显示数据
8       
9       output [7:0]dig_led_seg;//数码管段选
10      output [2:0]dig_led_sel;//数码管位选
11      
12      parameter system_clk = 50_000_000;
13      
14      //  localparam cnt1_max = 24;/*仿真的时候使用,板级验证时请注释掉*/
15      localparam cnt1_max = system_clk/1000/2-1;/*板级验证的时候使用,仿真时请注释掉*/
16 
17      reg [14 :0] cnt1;   //分频计数器
18      reg clk_1k;         //扫描时钟,1khz
19      reg [2:0]sel_r;     //数码管位选
20      reg [7:0]seg_r;     //数码管段选
21      reg [3:0]disp_data; //单位显示数据缓存
22 
23      //1khz时钟分频计数器
24      always@(posedge clk)
25      begin
26          if(!rst_n)cnt1<=0;
27          else if(cnt1==cnt1_max)cnt1<=0;
28          else cnt1<=cnt1+1'b1;
29      end
30 
31      //得到1khz时钟
32      always@(posedge clk or negedge rst_n)
33      begin
34          if(!rst_n)
35              clk_1k<=0;
36          else if(cnt1==cnt1_max)
37              clk_1k<=~clk_1k;
38      end 
39      
40      //位选信号控制
41      always@(posedge clk_1k or negedge rst_n)
42      begin
43          if(!rst_n)
44              sel_r<=3'd0;
45          else if(sel_r == 3'd3)
46              sel_r<=3'd0;
47          else
48              sel_r<=sel_r+1'b1;
49      end 
50 
51      //根据不同的数码管位选择不同的待显示数据
52      always@(*)
53      begin
54          if(!rst_n)
55              disp_data=4'd0;
56          else
57              begin
58                  case(sel_r)
59                      3'd0:disp_data=data[23:20];
60                      3'd1:disp_data=data[19:16];
61                      3'd2:disp_data=data[15:12];
62                      3'd3:disp_data=data[11:8];
63                      3'd4:disp_data=data[7:4];
64                      3'd5:disp_data=data[3:0];
65                      default :disp_data=4'd0;
66                  endcase
67              end
68      end 
69 
70      //数据译码,将待显示数据翻译为符合数码管显示的编码
71      always@(*)
72      begin   
73          if(!rst_n)
74              seg_r=8'hff;
75          else
76              begin
77                  case(disp_data)
78                      4'd0:       seg_r=8'hc0;
79                      4'd1:       seg_r=8'hf9;
80                      4'd2:       seg_r=8'ha4;
81                      4'd3:       seg_r=8'hb0;
82                      4'd4:       seg_r=8'h99;
83                      4'd5:       seg_r=8'h92;
84                      4'd6:       seg_r=8'h82;
85                      4'd7:       seg_r=8'hf8;
86                      4'd8:       seg_r=8'h80;
87                      4'd9:       seg_r=8'h90;
88                      4'd10:   seg_r=8'h88;
89                      4'd11:    seg_r=8'h83;
90                      4'd12:    seg_r=8'hc6;
91                      4'd13:   seg_r=8'ha1;
92                      4'd14:   seg_r=8'h86;
93                      4'd15:   seg_r=8'h8e;
94                      default : seg_r=8'hff;
95                  endcase
96              end
97      end 
98 
99      assign dig_led_seg = seg_r;
100     assign dig_led_sel = sel_r;
101
102 endmodule 
ad_tlc549顶层模块代码:
0   module ad_tlc549 (clk,rst_n,adc_din,adc_clk,adc_cs_n,dig_led_sel,dig_led_seg);

2       input clk;
3       input rst_n;
4       input adc_din;

6       output adc_clk;
7       output adc_cs_n;
8       output [2:0]dig_led_sel;
9       output [7:0]dig_led_seg;
10
11      wire get_flag;
12      wire [7:0]adc_data;
13      wire [23:0]seg_data;
14
15      tlc549_driver tlc549_driver(    
16          .clk(clk),
17          .rst_n(rst_n),
18          .en(1'b1),
19          .adc_din(adc_din),
20          .adc_clk(adc_clk),
21          .adc_cs_n(adc_cs_n),
22          .data(adc_data),
23          .get_flag(get_flag)
24      );
25
26      control control(
27          .clk(clk),
28          .rst_n(rst_n),
29          .get_flag(get_flag),
30          .adc_data(adc_data),
31          .seg_data(seg_data)
32      );
33
34      dig_led_drive dig_led_drive(
35          .clk(clk),      
36          .rst_n(rst_n),      
37          .data(seg_data),        
38          .dig_led_seg(dig_led_seg),
39          .dig_led_sel(dig_led_sel)   
40      );
41              
42  endmodule
ad_tlc549_tb顶层测试代码如下:
0   `timescale 1ns/1ps

2   module ad_tlc549_tb;

4       reg clk;
5       reg rst_n;
6       reg adc_din;

8       wire adc_clk;
9       wire adc_cs_n;
10      wire [2:0] dig_led_sel;
11      wire [7:0] dig_led_seg;
12      
13      initial begin
14          clk = 1;
15          rst_n = 0;
16          adc_din = 0;
17          #200.1 
18          rst_n = 1;
19
20          #1400 adc_din=1; //aa
21          #1000 adc_din=0;
22          #1000 adc_din=1;
23          #1000 adc_din=0;
24          #1000 adc_din=1;
25          #1000 adc_din=0;
26          #1000 adc_din=1;
27          #1000 adc_din=0;  
28          
29          #17000 
30          #1400 adc_din=1; //98
31          #1000 adc_din=0;
32          #1000 adc_din=0;
33          #1000 adc_din=1;
34          #1000 adc_din=1;
35          #1000 adc_din=0;
36          #1000 adc_din=0;
37          #1000 adc_din=0;
38          
39      end 
40
41      ad_tlc549 ad_tlc549_dut(
42          .clk(clk),
43          .rst_n(rst_n),
44          .adc_din(adc_din),
45          .adc_clk(adc_clk),
46          .adc_cs_n(adc_cs_n),
47          .dig_led_sel(dig_led_sel),
48          .dig_led_seg(dig_led_seg)
49      );
50
51      always #10 clk = ~clk;
52
53  endmodule
仿真图: 
设计仿真图如下所示:
观察仿真图,实现了数据的采集,并正确显示,下板验证结果也达到了设计的预期效果。

关于水质毒性快速检测仪的产品介绍说明
Silicon Labs发布Wireless Xpress快速联网方案
肖特基二极管工作频率是多少?
盘点5款值得收藏的Linux开发板
苹果iPhone7/7plus国行价格5388元/6388元 性能配置横向大对比!
FPGA学习系列:29. 数字电压表设计(AD)
dfrobot迷你阻燃插头XT30 恒定15A 瞬时30A简介
时代赋予柔性导电软连接的使命,金桥铜业将不负众望
uc3844引脚图及功能_封装
一文解析美打压中兴的原因是什么?
什么是核心交换机_选择核心交换机要考虑的因素
宁德时代拟定增募资不超过582亿元 宁德时代最新消息
2016电子设备产品环保涂料研讨会在深圳顺利举行
物联网时代,WiFi厂商在争什么?
蓝牙是射频技术吗?蓝牙射频技术是什么
英飞凌发布适用于xEV主逆变器的功率模块 打造高性价比电动汽车产品组合
基于STM32单片机的疫苗冷链加温湿度系统设计
陀螺仪的工作原理 陀螺仪在航天航空中的应用
聊一下解梯度折射率
苹果获汽车专利,“泰坦计划”开始实施