赞
踩
基于FPGA的数字电压表设计
本文代码数据处理模块参照大佬设计:https://blog.csdn.net/aodan3459/article/details/102011527?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159469213619725222417809%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=159469213619725222417809&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v2~rank_blog_default-2-102011527.pc_v2_rank_blog_default&utm_term=FPGA%E6%95%B0%E5%AD%97%E7%94%B5%E5%8E%8B%E8%A1%A8,
感谢大佬,侵删。
参考文献:
1.《现代数字电子技术基础实践》陈龙 牛小燕 马学条 杨柳 编著 机械工业出版社
2.《EDA Technology and Verilog HDL》黄继业 郑兴 黄汐威 潘松 编著 清华大学出版社
3.《Verilog HDL与FPGA数字系统设计》罗杰 主编 机械工业出版社
开发板资料:
1.AX301B_UG.pdf
2.高速ADDA使用指南REV1.3.pdf
3.黑金Altera开发板AX301_AX4010Verilog实例教程.pdf
以上资料均来自黑金动力社区:http://www.heijin.org/*
一、设计指标:
(1)使用ALINX AX301 FPGA开发平台。
(2)使用ALINX AN108 AD/DA模块。
(3)通过DA部分AD9708芯片产生固定模拟电压信号。
(4)通过AD部分AD9280芯片将模拟信号转化为数字信号。
(5)数码管显示所测电压值,分辨率0.01V,并且负电压可以显示负号。
(6)量程为 -4.00到4.00V。
二、系统框图:
三、代码部分:
1.顶层模块:
使用“例化”方式连接各个模块。
module adc9280_top( //input sys_clk, //系统时钟50MHz rst_n, //复位信号 data_ad, //AD输出信号 //output clk_adc, //ADC时钟 clk_dac, //DAC时钟 data_da, //DA输出信号 wei_slec, //数码管位选信号 duan_slec //数码管段选信号 ); input sys_clk; input rst_n; input [7:0] data_ad; output clk_adc; output clk_dac; output [7:0] data_da; output [3:0] wei_slec; output [7:0] duan_slec; wire [7:0] pre_data; wire [11:0] cout; //分频器模块 clk_10M u1( //input .sys_clk(sys_clk), .rst_n(rst_n), //ouput .clk_10M(clk_10M) ); //ADC采样模块 ADC9280 u2( //input .sys_clk(sys_clk), .data_ad(data_ad), //ouput .pre_data(pre_data) ); //数据处理模块 data_ad_pro u3( //input .sys_clk(sys_clk), .rst_n(rst_n), .pre_data(pre_data), //output .cout(cout) ); //数码显示译码模块 display u4( //input .sys_clk(sys_clk), .rst_n(rst_n), .cout(cout), //output .slec_wei(wei_slec), .slec_duan(duan_slec) ); //信号发生模块 signal_generator u5( //input .sys_clk(sys_clk), .rst_n(rst_n), //output .data_da(data_da) ); assign clk_adc = clk_10M; assign clk_dac = sys_clk; endmodule
2.分频器模块:
AD9280最大采样率为32MSPS,使用10MHz时钟比较合适。
module clk_10M( //input sys_clk, //输系统时钟50MHz rst_n, //复位信号 //ouput clk_10M //输出分频后的时钟10MHz ); input sys_clk; input rst_n; output clk_10M; reg clk_10M; reg [2:0]cnt; //计数信号 always @(posedge sys_clk or negedge rst_n) if(!rst_n) begin cnt <= 5'd0; clk_10M <= 1'b0; end else if(cnt == 5'd5) //5分频,系统时钟每翻转5次,clk_10M翻转一次 begin cnt <= 5'd0; clk_10M <= ~clk_10M; end else cnt <= cnt + 1'b1; endmodule
3.ADC采样模块:
module ADC9280( //input sys_clk, //输入系统时钟50MHz data_ad, //输入AD9280转化的8位数字信号 //output pre_data //输出采样到的8位电压信号 ); input sys_clk; input [7:0] data_ad; output [7:0] pre_data; reg [7:0]addata; always @(posedge sys_clk) begin addata <= data_ad; end assign pre_data = addata; endmodule
4.数据处理模块:
本次实验精度为0.01V,使用四位数码管作为显示(有一位为负号)。将AD9280的8位BCD数据按低四位与高四位分开编码,得到各自12位宽的数据,对两个编码进行相加,如代码中的cout[11:0] = L[11:0] + H[11:0],这里注意,高四位[11:8]、中四位[7:4]、低四位[3:0]。
假如AD9280得到的数据是8’b0110_1010转成16进制为8’h6a,从代码中可以看到,低四位4’ha:L <= 12’h050;高四位4’h6:H <= 12’h480;H+L = 12’h4D0,中位为D,大于9,向高位产生进位C1= 1,中位需要加6得到3,故cout[11:0]=12’h530,电压也就是5.30V,然后把530送给数码显示译码模块进行处理并显示。同理如果低位相加大于9也得向中位进位C0,此时的低位要变为加6后的值。
module data_ad_pro( //input sys_clk, //输入系统时钟50MHz rst_n, //输入复位信号 pre_data, //输入AD采样模块传来的数据 //output cout //输出处理后的12位电压数据 ); input sys_clk; input rst_n; input [7:0] pre_data; output [11:0] cout; //由于AD9280限制,需作电压转化,Vin = 5*Vad - 5 /************************************/ //低四位BCD编码 reg [11:0] L; always @ (posedge sys_clk) case(pre_data[3:0]) //L = n * 12'h005(n=1、2、3、、f) 4'h1: L <= 12'h005; 4'h2: L <= 12'h010; 4'h3: L <= 12'h015; 4'h4: L <= 12'h020; 4'h5: L <= 12'h025; 4'h6: L <= 12'h030; 4'h7: L <= 12'h035; 4'h8: L <= 12'h040; 4'h9: L <= 12'h045; 4'ha: L <= 12'h050; 4'hb: L <= 12'h055; 4'hc: L <= 12'h060; 4'hd: L <= 12'h065; 4'he: L <= 12'h070; 4'hf: L <= 12'h075; default: L <= 12'h000; endcase /************************************/ //高四位BCD编码 reg [11:0] H; always @ (posedge sys_clk) case(pre_data[7:4]) //H = n * 12'h080(n=1、2、3、、f) 4'h1: H <= 12'h080; 4'h2: H <= 12'h160; 4'h3: H <= 12'h240; 4'h4: H <= 12'h320; 4'h5: H <= 12'h400; 4'h6: H <= 12'h480; 4'h7: H <= 12'h560; 4'h8: H <= 12'h640; 4'h9: H <= 12'h720; 4'ha: H <= 12'h800; 4'hb: H <= 12'h880; 4'hc: H <= 12'h960; 4'hd: H <= 12'hA40; 4'he: H <= 12'hB20; 4'hf: H <= 12'hC00; default: H <= 12'h000; endcase /************************************/ //判断低四位是否大于9并进位 reg c0; always @ (posedge sys_clk) begin if(H[3:0] + L[3:0] > 4'd9) c0 <= 1; else c0 <= 0; end /************************************/ //判断中间四位是否大于9并进位 reg c1; always @(posedge sys_clk) begin if(H[7:4] + L[7:4] > 4'd9) c1 <= 1; else c1 <= 0; end /************************************/ //对进位进行计算,因为在0.00V校准的时候,AD默认会读取到1.30V的基准电压,因此需要减去这多出来的电压值 reg [11:0] cout; always @(c1 or c0) begin case({c1,c0}) 2'b00: begin cout[11:8] <= H[11:8] + L[11:8] -4'h1; cout[7:4] <= H[7:4] + L[7:4] - 4'h3; cout[3:0] <= H[3:0] + L[3:0]; end 2'b01: begin if((H[7:4] + L[7:4] + 4'b0001) > 9) begin cout[11:8] <= H[11:8] + L[11:8] + 4'b0001 -4'h1; cout[7:4] <= H[7:4] + L[7:4] + 4'b0111 - 4'h3;//加上6并加上来自低位上的进位 cout[3:0] <= H[3:0] + L[3:0]+ 4'b0110;//加上6 end else begin cout[11:8] <= H[11:8] + L[11:8] -4'h1; cout[7:4] <= H[7:4] + L[7:4] + 4'b0001 - 4'h3; cout[3:0] <= H[3:0] + L[3:0] + 4'b0110; end end 2'b10:begin cout[11:8] <= H[11:8] + L[11:8] + 4'b0001 -4'h1; cout[7:4] <= H[7:4] + L[7:4] + 4'b0110 - 4'h3; cout[3:0] <= H[3:0] + L[3:0]; end 2'b11:begin cout[11:8] <= H[11:8] + L[11:8] + 4'b0001 -4'h1; cout[7:4] <= H[7:4] + L[7:4] + 4'b0110 - 4'h3; cout[3:0] <= H[3:0] + L[3:0] + 4'b0110; end endcase end endmodule
5.数码显示译码模块:
module display( //input sys_clk, //输系统时钟50MHz rst_n, //输入复位信号 cout, //输入数据处理模块传来的12位电压数据 //output slec_wei, //输出数码管位选信号 slec_duan //输出数码管段选信号 ); input sys_clk; input rst_n; input [11:0] cout; output [3:0] slec_wei; output [7:0] slec_duan; parameter SEG_NUM0 = 8'b1100_0000, //数码管显示0 SEG_NUM1 = 8'b1111_1001, //数码管显示1 SEG_NUM2 = 8'b1010_0100, //数码管显示2 SEG_NUM3 = 8'b1011_0000, //数码管显示3 SEG_NUM4 = 8'b1001_1001, //数码管显示4 SEG_NUM5 = 8'b1001_0010, //数码管显示5 SEG_NUM6 = 8'b1000_0010, //数码管显示6 SEG_NUM7 = 8'b1111_1000, //数码管显示7 SEG_NUM8 = 8'b1000_0000, //数码管显示8 SEG_NUM9 = 8'b1001_0000; //数码管显示9 parameter T1MS = 16'd49999; //1ms计数 reg [11:0] data; /**********************************************/ //将0_10V转为-5_5V reg flag; //判断是否为负电压,1表示是,0表示否 always @ (posedge sys_clk) if(cout < 12'h500)begin flag = 1; data = 12'h499 - cout; end else begin flag = 0; data = cout - 12'h500; end /**********************************************/ //1ms计数器 reg [15:0] cnt; always @(posedge sys_clk or negedge rst_n) if(!rst_n) cnt <= 16'd0; else if(cnt == T1MS) cnt <= 16'd0; else cnt <= cnt + 1'b1; /**********************************************/ //数码管轮流导通 reg [2:0] i; reg [3:0] slec_wei; reg [7:0] slec_duan; always @ (posedge sys_clk or negedge rst_n) if( !rst_n )begin i <= 4'd0; slec_wei <= 4'b0000; end else case( i ) 0: if( cnt == T1MS ) i <= i + 1'b1; else begin slec_wei <= 4'b0111; //第一个数码选通 if(flag == 1) slec_duan <= 8'b1011_1111; //显示负号 else slec_duan <= 8'b1111_1111; //不显示 end 1: if( cnt == T1MS ) i <= i + 1'b1; else begin slec_wei <= 4'b1011; //第二个数码选通 slec_duan <= data0 + 8'b1000_0000; //第一位是整数位,加上小数点 end 2: if( cnt == T1MS ) i <= i + 1'b1; else begin slec_wei <= 4'b1101; //第三个数码选通 slec_duan <= data1; end 3: if( cnt == T1MS ) i <= 4'd0; else begin slec_wei <= 4'b1110; //第四个数码选通 slec_duan <= data2; end endcase /*****************************************/ reg [7:0] data0; always @ (posedge sys_clk) case(data[11:8]) //进行编码 高 4'h0: data0 <= SEG_NUM0; 4'h1: data0 <= SEG_NUM1; 4'h2: data0 <= SEG_NUM2; 4'h3: data0 <= SEG_NUM3; 4'h4: data0 <= SEG_NUM4; 4'h5: data0 <= SEG_NUM5; 4'h6: data0 <= SEG_NUM6; 4'h7: data0 <= SEG_NUM7; 4'h8: data0 <= SEG_NUM8; 4'h9: data0 <= SEG_NUM9; default:data0 <= SEG_NUM0; endcase /*****************************************/ reg [7:0] data1; always @ (posedge sys_clk) case(data[7:4]) //进行编码 中 4'h0: data1 <= SEG_NUM0; 4'h1: data1 <= SEG_NUM1; 4'h2: data1 <= SEG_NUM2; 4'h3: data1 <= SEG_NUM3; 4'h4: data1 <= SEG_NUM4; 4'h5: data1 <= SEG_NUM5; 4'h6: data1 <= SEG_NUM6; 4'h7: data1 <= SEG_NUM7; 4'h8: data1 <= SEG_NUM8; 4'h9: data1 <= SEG_NUM9; default:data1 <= SEG_NUM0; endcase /*****************************************/ reg [7:0] data2; always @ (posedge sys_clk) case(data[3:0]) //进行编码 低 4'h0: data2 <= SEG_NUM0; 4'h1: data2 <= SEG_NUM1; 4'h2: data2 <= SEG_NUM2; 4'h3: data2 <= SEG_NUM3; 4'h4: data2 <= SEG_NUM4; 4'h5: data2 <= SEG_NUM5; 4'h6: data2 <= SEG_NUM6; 4'h7: data2 <= SEG_NUM7; 4'h8: data2 <= SEG_NUM8; 4'h9: data2 <= SEG_NUM9; default:data2 <= SEG_NUM0; endcase /***********************************************/ endmodule
四、实验验证:
1.使用ALINX AX301 FPGA cycloneⅣ开发板:
2.使用ALINX AN108 AD/DA模块:
其接口定义:
3.引脚分配:
五、实验结果:
正电压:
测试1:
测试2:
测试3:
负电压:
测试1:
测试2:
测试3:
结果评估:
在正电压和负电压的测试下,误差没有超过0.1V,满足课程设计要求,在正电压的时候,误差很小,但是由于算法原因,FPGA逻辑运算的限制,在负电压作减法的时候,会出现一些误差,导致结果没有很精确。但是在误差允许范围内,这次实验是成功的!
六、注意事项:
在烧录前,一旦代码作了任何改动,都一定要编译一次,否则烧录的程序是改动前的程序。在插入AN108模块的时候,一定要对准对应IO口,否则模块AD/DA不起作用。校准电压值一定要进行0.00V校准,否则后面所有的校准都是无效的。VOLTAGE的值不能超过MINVOL和MAXVOL。
本次设计对应资源链接:https://download.csdn.net/download/weixin_43586860/12612981
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。