文章目录
前言
使用FPGA开发板及板载AD模块设计一个测量范围为-5V~5V的电压表,并使用数码管显示出来
一、ADC
1.什么是ADC
模/数转换器即 A/D 转换器,或简称 ADC(Analog to Digital Conver),通常是指一个将模拟信号转变为数字信号的电子元件或电路。常见的模/数转换器将经过与标准量比较处理后的模拟量转换为以二进制数值表示的离散信号。
2.ADC转换过程
模拟信号与数字信号的转换过程一般分为四个步骤:采样、保持、量化、编码。前两个步骤在采样-保持电路中完成,后两步则在 ADC 芯片中完成。
3.ADC种类
常用的 ADC 可分为积分型、逐次逼近型、并行比较型/串并行型、Σ -Δ调制型、电容阵列逐次比较型以及压频变换型。
ADC主要技术指标
ADC 的主要技术指标包括:分辨率、转换速率、量化误差、满刻度误差、线性度。
- 分辨率指输出数字量变化一个最低有效位(LSB)所需的输入模拟电压的变化量。
- 转换速率是指完成一次从模拟转换到数字的 AD 转换所需要的时间的倒数。积分型AD 的转换时间是毫秒级属低速 AD,逐次比较型 AD 是微秒级属中速 AD,全并行/串并行型 AD 可达到纳秒级。采样时间则是另外一个概念,是指两次转换的间隔。为了保证转换的正确完成,采样速率(Sample Rate)必须小于或等于转换速率。因此有人习惯上将转换速率在数值上等同于采样速率也是可以接受的。
- 量化误差是由于 AD 的有限分辩率而引起的误差,即有限分辩率 AD 的阶梯状转移特性曲线与无限分辩率 AD(理想 AD)的转移特性曲线(直线)之间的最大偏差。通常是 1个或半个最小数字量的模拟变化量,表示为 1LSB、1/2LSB。
- 满刻度误差是满刻度输出时对应的输入信号与理想输入信号值之差。
- 线性度指实际转换器的转移函数与理想直线的最大偏移。
二、ADC模块设计
1.ADC计算
- 使用的 ADC 芯片位宽为 8 位,板卡模拟电压输入范围为-5v~+5v,即电压表测量范围,最大值和最小值压降为 10v,分辨率为 10/28。
- 使用定义中值的测量方法:在电压表上电后未接入测量电压时,取 ADC 芯片采集的最初的若干测量值,取平均,作为测量中值 data_median,与实际测量值 0V 对应。
当 ADC 芯片采集后的电压数值 ad_data 位于 0 - data_median 范围内,表示测量电压位于-5V ~ 0V 范围内,分辨率为 10/((data_median + 1) * 2),换算为电压值:Vin = - ((10 /((data_median + 1) * 2)) * (data_median -ad_data));当 ADC芯片采集后的电压数值 ad_data 位于 data_median - 255 范围内,表示测量电压位于 0V ~ 5V范围内,分辨率为 10/((255 - data_median + 1) * 2),换算为电压值:Vin = ((10 /((255 -data_median + 1) * 2)) * (ad_data - data_median))。
2.端口和寄存器设计
- clk_sample:将系统是时钟四分频,变为12.5MHz时钟给AD芯片
- median_en:中值使能信号
- cnt_median:中值数据累加计数器
- data_sum_m:1024次中值数据累加总和
- data_median:中值数据
- data_p、data_n:根据中值计算出的正向电压AD和负向电压AD的分辨率(放大2^13*1000倍)
- volt_reg:处理后的稳定数据
3.RTL代码
module dac(
input wire clk,
input wire rst_n,
input wire [7:0]ad_data,
output wire ad_clk,
output wire sign,
output wire [15:0]volt
);
parameter CNT_DATA_MAX=11'd1024;
reg [1:0]cnt_clk;
reg clk_sample;
reg median_en;
reg [10:0]cnt_median;
reg [18:0]data_sum_m;
reg [7:0]data_median;
reg [27:0]volt_reg;
wire [27:0]data_p;
wire [27:0]data_n;
assign ad_clk=~clk_sample;
assign sign=(ad_data<data_median)? 1'b1:1'b0;
//将系统时钟四分频,变为12.5MHz时钟给ad芯片
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
cnt_clk<=1'b0;
clk_sample<=1'b0;
end
else begin
cnt_clk<=cnt_clk+1'b1;
if(cnt_clk==2'b1)
begin
cnt_clk<=2'b0;
clk_sample<=~clk_sample;
end
end
//中值使能信号
always @(posedge clk or negedge rst_n)
if(!rst_n)
median_en<=1'b0;
else if(cnt_median==CNT_DATA_MAX)
median_en<=1'b1;
else
median_en<=median_en;
//中值数据累加计数器
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_median<=1'b0;
else if(!median_en)
cnt_median<=cnt_median+1'b1;
//1024次中值数据累加总和
always @(posedge clk or negedge rst_n)
if(!rst_n)
data_sum_m<=1'b0;
else if(cnt_median==CNT_DATA_MAX)
data_sum_m<=1'b0;
else
data_sum_m<=data_sum_m+ad_data;
//中值数据
always @(posedge clk or negedge rst_n)
if(!rst_n)
data_median<=1'b0;
else if(cnt_median==CNT_DATA_MAX)
data_median<=data_sum_m/CNT_DATA_MAX;
else
data_median<=data_median;
//根据中值计算出的正向电压AD和负向电压AD的分辨率(放大2^13*1000倍)
assign data_p=(median_en==1'b1)?8192_0000/((255-data_median)*2):0;
assign data_n=(median_en==1'b1)?8192_0000/((data_median+1)*2):0;
//处理后的稳定数据
always @(posedge clk or negedge rst_n)
if(!rst_n)
volt_reg<=1'b0;
else if(median_en)
if((ad_data>(data_median-3)) && (ad_data<(data_median+3)))
volt_reg<=1'b0;
else if(ad_data<data_median)
volt_reg<=(data_n*(data_median-ad_data)) >> 13;
else if(ad_data>data_median)
volt_reg<=(data_p*(ad_data-data_median)) >> 13;
else
volt_reg<=1'b0;
assign volt=volt_reg;
endmodule
三、数码管设计代码
1.二进制数转十进制数
module bcd_8421(
input wire clk,
input wire rst_n,
input wire [19:0]data,
output reg [3:0]unit,
output reg [3:0]ten,
output reg [3:0]hun,
output reg [3:0]tho,
output reg [3:0]t_tho,
output reg [3:0]h_hun
);
reg [4:0]cnt_shift;
reg [43:0]data_shift;
reg shift_flag;
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_shift<=1'b0;
else if(cnt_shift==5'd21 && shift_flag)
cnt_shift<=1'b0;
else if(shift_flag)
cnt_shift<=cnt_shift+1'b1;
else
cnt_shift<=cnt_shift;
always @(posedge clk or negedge rst_n)
if(!rst_n)
data_shift<=1'b0;
else if(!cnt_shift)
data_shift<={24'd0,data};
else if(cnt_shift<=20 && (!shift_flag))
begin
data_shift[23:20]<=(data_shift[23:20]>4)? (data_shift[23:20]+2'd3):(data_shift[23:20]);
data_shift[27:24]<=(data_shift[27:24]>4)? (data_shift[27:24]+2'd3):(data_shift[27:24]);
data_shift[31:28]<=(data_shift[31:28]>4)? (data_shift[31:28]+2'd3):(data_shift[31:28]);
data_shift[35:32]<=(data_shift[35:32]>4)? (data_shift[35:32]+2'd3):(data_shift[35:32]);
data_shift[39:36]<=(data_shift[39:36]>4)? (data_shift[39:36]+2'd3):(data_shift[39:36]);
data_shift[43:40]<=(data_shift[43:40]>4)? (data_shift[43:40]+2'd3):(data_shift[43:40]);
end
else if(cnt_shift<=5'd20 && shift_flag)
data_shift<=data_shift<<1;
else
data_shift<=data_shift;
always @(posedge clk or negedge rst_n)
if(!rst_n)
shift_flag<=1'b0;
else
shift_flag<=~shift_flag;
always @(posedge clk or negedge rst_n)
if(!rst_n)
begin
unit<=1'b0;
ten<=1'b0;
hun<=1'b0;
tho<=1'b0;
t_tho<=1'b0;
h_hun<=1'b0;
end
else if(cnt_shift==5'd21)
begin
unit<=data_shift[23:20];
ten<=data_shift[27:24];
hun<=data_shift[31:28];
tho<=data_shift[35:32];
t_tho<=data_shift[39:36];
h_hun<=data_shift[43:40];
end
endmodule
2.数码管显示代码
module seg
#(
parameter CNT_MAX=16'd49999
)
(
input wire clk,
input wire rst_n,
input wire [5:0]point,
input wire [19:0]data,
input wire seg_en,
input wire sign,
output reg [5:0]sel,
output reg [7:0]seg
);
wire [3:0]unit;
wire [3:0]ten;
wire [3:0]hun;
wire [3:0]tho;
wire [3:0]t_tho;
wire [3:0]h_hun;
bcd_8421 bcd_8421(
.clk(clk),
.rst_n(rst_n),
.data(data),
.unit(unit),
.ten(ten),
.hun(hun),
.tho(tho),
.t_tho(t_tho),
.h_hun(h_hun)
);
reg [23:0]data_reg;
reg [15:0]cnt_1ms;
reg flag_1ms;
reg [2:0]cnt_sel;
reg [5:0]sel_reg;
reg [3:0]data_disp;
reg dot_disp;
//控制数码管显示
always @(posedge clk or negedge rst_n)
if(!rst_n)
data_reg<=1'b0;
//若显示的十万位为非零数据或需要显示小数点,六个数码管全显示
else if(h_hun || point[5])
data_reg<={h_hun,t_tho,tho,hun,ten,unit};
//若显示的万位数为非零数据或需要显示小数点,数值显示在5个数码管上
else if((t_tho || point[4]) && sign)//显示负号
data_reg<={4'd10,t_tho,tho,hun,ten,unit};//定义4'd10为显示负号
else if((t_tho || point[4]) && !sign)
data_reg<={4'd11,t_tho,tho,hun,ten,unit};//定义4‘d11为不显示
//若显示的千位数为非零数据或需要显示小数点,数值显示在4个数码管上
else if((tho || point[3]) && sign)
data_reg<={4'd11,4'd10,tho,hun,ten,unit};
else if((tho || point[3]) && !sign)
data_reg<={4'd11,4'd11,tho,hun,ten,unit};
//若显示的百位数为非零数据或需要显示小数点,数值显示在3个数码管上
else if((hun || point[2]) && sign)
data_reg<={4'd11,4'd11,4'd10,hun,ten,unit};
else if((hun || point[2]) && !sign)
data_reg<={4'd11,4'd11,4'd11,hun,ten,unit};
//若显示的十位数为非零数据或需要显示小数点,数值显示在2个数码管上
else if((ten || point[2]) && sign)
data_reg<={4'd11,4'd11,4'd11,4'd10,ten,unit};
else if((ten || point[2]) && !sign)
data_reg<={4'd11,4'd11,4'd11,4'd11,ten,unit};
//若显示的个位数为非零数据或需要显示小数点,数值显示在1个数码管上
else if((unit || point[1]) && sign)
data_reg<={4'd11,4'd11,4'd11,4'd11,4'd10,unit};
else
data_reg<={4'd11,4'd11,4'd11,4'd11,4'd11,unit};
//计数器计数1ms
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_1ms<=1'b0;
else if(cnt_1ms==CNT_MAX)
cnt_1ms<=1'b0;
else
cnt_1ms<=cnt_1ms+1'b1;
//计数标志位
always @(posedge clk or negedge rst_n)
if(!rst_n)
flag_1ms<=1'b0;
else if(cnt_1ms==CNT_MAX-1'b1)
flag_1ms<=1'b1;
else
flag_1ms<=1'b0;
//cnt_sel:从0到5的循环,用于选择当前显示的数码管
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_sel<=1'b0;
else if(cnt_sel==3'b101 && flag_1ms)
cnt_sel<=1'b0;
else if(flag_1ms)
cnt_sel<=cnt_sel+1'b1;
else
cnt_sel<=cnt_sel;
//数码管位选信号寄存器
always @(posedge clk or negedge rst_n)
if(!rst_n)
sel_reg<=6'b000_000;
else if(!cnt_sel && flag_1ms)
sel_reg<=6'b000_001;
else if(flag_1ms)
sel_reg<=sel_reg<<1;
else
sel_reg<=sel_reg;
//控制数码管的位选信号,使六个数码管轮流显示
always @(posedge clk or negedge rst_n)
if(!rst_n)
data_disp<=1'b0;
else if(seg_en && flag_1ms)
case(cnt_sel)
3'd0:data_disp<=data_reg[3:0];
3'd1:data_disp<=data_reg[7:4];
3'd2:data_disp<=data_reg[11:8];
3'd3:data_disp<=data_reg[15:12];
3'd4:data_disp<=data_reg[19:16];
3'd5:data_disp<=data_reg[23:20];
default:data_disp<=1'b0;
endcase
else
data_disp<=data_disp;
//dot_disp:小数点低电平点亮,需对小数点有效信号取反
always @(posedge clk or negedge rst_n)
if(!rst_n)
dot_disp<=1'b1;
else if(flag_1ms)
dot_disp<=~point[cnt_sel];
else
dot_disp<=dot_disp;
//控制数码管段选信号,显示数字
always @(posedge clk or negedge rst_n)
if(!rst_n)
seg<=8'b1111_1111;
else
case(data_disp)
4'd0:seg<={dot_disp,7'b100_0000};
4'd1:seg<={dot_disp,7'b111_1001};
4'd2:seg<={dot_disp,7'b010_0100};
4'd3:seg<={dot_disp,7'b011_0000};
4'd4:seg<={dot_disp,7'b001_1001};
4'd5:seg<={dot_disp,7'b001_0010};
4'd6:seg<={dot_disp,7'b000_0010};
4'd7:seg<={dot_disp,7'b111_1000};
4'd8:seg<={dot_disp,7'b000_0000};
4'd9:seg<={dot_disp,7'b001_0000};
4'd10:seg<=8'b1011_1111;
4'd11:seg<=8'b1111_1111;
default:seg<=8'b1100_0000;
endcase
//sel:数码管位选信号赋值
always @(posedge clk or negedge rst_n)
if(!rst_n)
sel<=6'b000_000;
else
sel<=~sel_reg;
endmodule
四、顶层文件设计
1.顶层设计代码
module dig_top(
input wire clk,
input wire rst_n,
input wire[7:0]ad_data,
output wire ad_clk,
output wire [5:0]sel,
output wire [7:0]seg
);
wire [15:0]volt;
wire sign;
seg seg_inst(
.clk(clk),
.rst_n(rst_n),
.point(6'b001000),
.data(volt),
.seg_en(1'b1),
.sign(sign),
.sel(sel),
.seg(seg)
);
dac dac_inst(
.clk(clk),
.rst_n(rst_n),
.ad_data(ad_data),
.ad_clk(ad_clk),
.sign(sign),
.volt(volt)
);
endmodule
2.RTL视图
五、仿真测试模块
1.仿真测试模块代码
`timescale 1ns/1ns
`define clk_period 20
module dig_top_tb;
reg clk;
reg rst_n;
reg[7:0]ad_data;
reg data_en;
reg [7:0]ad_data_reg;
wire ad_clk;
wire [5:0]sel;
wire [7:0]seg;
dig_top dig_top_inst(
.clk(clk),
.rst_n(rst_n),
.ad_data(ad_data),
.ad_clk(ad_clk),
.sel(sel),
.seg(seg)
);
initial clk=1'b1;
always #(`clk_period/2) clk=~clk;
initial begin
rst_n = 1'b0;
clk = 1'b0;
#200;
rst_n = 1'b1;
data_en = 1'b0;
#499990;
data_en=1'b1;
end
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
ad_data_reg <= 8'd0;
else if(data_en == 1'b1)
ad_data_reg <= ad_data_reg + 1'b1;
else
ad_data_reg <= 8'd0;
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
ad_data <= 8'd0;
else if(data_en == 1'b0)
ad_data <= 8'd125;
else if(data_en == 1'b1)
ad_data <= ad_data_reg;
else
ad_data <= ad_data;
endmodule
2.仿真波形图
六、上板验证
在没有接入电压时,显示为0.00
接入板载IO口中3.3V电压,在误差允许范围内,设计成功。