最近刚做完学校的实验,记录一下自己的实验过程,也可以给需要的朋友一个参考,不对之处欢迎指正。
XADC:fpga内部IP核
XADC可以将0-1V的模拟电压信号转化为12位的数字电压信号,存储在DO[15:0]口的高12位上,转化速率为1MSPS的ADC(模数转换器)。
XADC中文全称应该是“Xilinx模拟混合信号模块”,是FPGA中的一个硬核。在7系列FPGA中,XADC提供了DRP和JTAG接口,用于访问XADC的状态和控制寄存器。XADC默认差分模式,若使用单端模式,请将XADC的IP核配置成单端模式(其实就是例化时候让一个输入直接连低电平,然后让输入和板子共地)。
xadc的配置
由于在ego1的接口中没有找到vp/vn,所以选择了VAUXP2/VAUXN2这对输入,因为vp/vn的引脚无论我勾不勾选都会有(不太清楚为什么),所以我这里顺便也勾选了,也方便了之后调试时候观察数据。
实验中不需要这些报警信号,所以我都没选,简化输出引脚
其他未显示的配置均默认,以下就是所有xadc需要例化的引脚(包含我自己看数据手册理解的意思)看似很多,其实需要写代码的部分很少
xadc_wiz_0 your_instance_name (
.di_in(di_in), // input wire [15 : 0] di_in DRP输入数据
.daddr_in(daddr_in), // input wire [6 : 0] daddr_in DRP地址
.den_in(den_in), // input wire den_in DRP使能信号
.dwe_in(dwe_in), // input wire dwe_in DRP读写信号
.drdy_out(drdy_out), // output wire drdy_out 当DRP可以输出数据时拉高
.do_out(do_out), // output wire [15 : 0] do_out DRP输出数据
.dclk_in(dclk_in), // input wire dclk_in 时钟
.reset_in(reset_in), // input wire reset_in DRP复位信号
.vp_in(vp_in), // input wire vp_in vp输入
.vn_in(vn_in), // input wire vn_in vn输入
.vauxp2(vauxp2), // input wire vauxp2 vauxp2输入
.vauxn2(vauxn2), // input wire vauxn2 vauxn2输入
.channel_out(channel_out), // output wire [4 : 0] channel_out 通道选择输出
.eoc_out(eoc_out), // output wire eoc_out xadc完成一次转换后拉高
.alarm_out(alarm_out), // output wire alarm_out 报警信号
.eos_out(eos_out), // output wire eos_out xadc完成一组转换后拉高
.busy_out(busy_out) // output wire busy_out xadc在转换时拉高
);
xadc转换完会自动把数据输入到DRP中,所有不需要例化输入数据,只需要从DRP中读取数据,所以可以把读写使能信号一直设置为低电平即读使能,从官方文档中可以看到,想要读到对应通道的转换结果,DRP的地址应该设置为{2'b0,CHANNEL_OUT},即channel_out前两位补零。drdy_out和do_out要连接到下一个模块。时钟和复位信号都可以连到系统的时钟和复位信号,vp/vn不使用所有可以不管,因为我是想要单端输入,所以vauxn2/vauxp2一个连低电平一个接外部输入。eoc_out会在一次转换后拉高,这个可以作为DRP的使能信号,即当xadc转换完一次,可以从DRP中读取数据,eos_out会在所有通道转换完拉高,不做使用,busy_out也可以不使用,例化如下
.di_in(16'b0), // input wire [15 : 0] di_in
.daddr_in({2'b0,CHANNEL_OUT}), // input wire [6 : 0] daddr_in 地址总线
.den_in(EOC_OUT), // input wire den_in 使能信号
.dwe_in(1'b0), // input wire dwe_in 读写信号
.drdy_out(DRDY_OUT), // output wire drdy_out 数据就绪信号
.do_out(DO_OUT), // output wire [15 : 0] do_out
.dclk_in(clock), // input wire dclk_in
.reset_in(reset), // input wire reset_in
.vp_in(), // input wire vp_in
.vn_in(), // input wire vn_in
.vauxp2(IN1), // input wire vauxp2
.vauxn2(1'd0), // input wire vauxn2
.channel_out(CHANNEL_OUT), // output wire [4 : 0] channel_out 通道选择输出
.eoc_out(EOC_OUT), // output wire eoc_out
.alarm_out(), // output wire alarm_out
.eos_out(), // output wire eos_out
.busy_out() // output wire busy_out
);
xadc配置完成后就需要将采集的波形通过vga显示在显示屏上,显示屏分辨率采用640*480,60Hz的刷新率,为了显示波形,让横轴为时间,纵轴为电压,所以在一个屏幕上只能最多显示480个时间点。由于xadc的转换速率非常高,心电信号为一个低频信号,如果直接将xadc转换结果作为输出,在一个屏幕上显示不出来完整的波形,基本上看到的就是一条直线。
为了解决这个问题,我配置了一个简单双口ram即伪双口ram(关于简单双口ram的配置方法和原理网上有非常多的资源,这里不再详细讲述)让a口只做输入,b口只输出,ram的深度设置为640,与屏幕的行分辨率相同,这样就可以让一个横坐标点对应一个地址。对应地址的数据就可以作为纵坐标来显示对应时间的电压值。ram的宽度设置为12,与xadc输出的有效位数相同(xadc的16位输出仅高12位有效)。关于ram的配置如下
未显示的配置为默认。
假定心率是1Hz,我用200个点显示一个周期的波形,一个屏幕可以显示出来大概三个波形,所以让ram的a口(输入数据口)时钟为分频得到的200Hz的时钟,这样就是以200Hz的频率将xadc输出存到ram里(可以理解为xadc以200Hz的频率转换)。因为vga以25MHz的速率扫描(关于vga的原理网上也有许多的资源,这里不再详细说明)b口输出时钟与vga的时钟相同即25MHz,一个像素的有横纵坐标,当一个像素点的纵坐标与对应横坐标(即ram地址)的数据相同时,这个像素点就显示红色,不相同时显示黑色。通过ram的低频输入高频输出就做到了将完整波形显示到屏幕上。还有一点要说明的是,因为xadc的12位输出值为0-4095,而列分辨率只有480,想让一个纵坐标对应一个电压值,需要将0-4095的输出缩到0-480,这里我直接选择了将12位输出除以10(其实应该选择截位更好,fpga做除法太耗费资源了)vga的时钟产生我选择配置一个pll ip 核(配置过程可参考自行搜索,非常简单)进行25MHz分频,而ramA口的200Hz时钟是用代码描述了一个分频模块进行分频,200Hz分频代码如下。
module count(
input clock,
input reset,
output reg clk
);
reg [27:0] cnt_200Hz;//时钟分频计数器
always@(posedge clock)begin
if(reset)begin
cnt_200Hz <= 28'd0;
clk <= 1'd0;
end
else begin
if (cnt_200Hz == 28'd249999)begin
cnt_200Hz <= 28'd0;
clk <= ~clk;
end
else
cnt_200Hz <= cnt_200Hz + 1'b1;
end
end
endmodule
大体思路就是这样,还有一些细节下文结合代码进行说明。
因为配置的xadc是两个通道交替转换,所以会输出两个波形的数据,为了得到所需要的数据,需要进行滤波,如代码所示,当数据xadc数据就绪且通道地址为h12时,ramA口的输入数据为xadc的输出数据.
always@(posedge clock)begin
if(reset)
ram_data_a <= 1'd0;
else begin
if(DRDY_OUT && (CHANNEL_OUT == 5'h12))
ram_data_a <= DO_OUT[15:4];
else
ram_data_a <= ram_data_a;
end
end
至于为什么是h12,因为xadc采集心电信号的通道数据存储地址为h12,如图。
vga的控制模块代码我大部分搬运了正点原子的代码(正点家的代码是真的好看),关于vga的扫描显示原理可以观看b站正点原子开拓者fpga视频vga部分。
module vga_control(
input vga_clk, //VGA驱动时钟
input sys_rst_n, //复位信号
//VGA接口
output vga_hs, //行同步信号
output vga_vs, //场同步信号
output [11:0] vga_rgb, //红绿蓝三原色输出
input [11:0] pixel_data, //像素点数据
output [ 9:0] pixel_xpos, //像素点横坐标
output [ 9:0] pixel_ypos //像素点纵坐标
);
//parameter define
parameter H_SYNC = 10'd96; //行同步
parameter H_BACK = 10'd48; //行显示后沿
parameter H_DISP = 10'd640; //行有效数据
parameter H_FRONT = 10'd16; //行显示前沿
parameter H_TOTAL = 10'd800; //行扫描周期
parameter V_SYNC = 10'd2; //场同步
parameter V_BACK = 10'd33; //场显示后沿
parameter V_DISP = 10'd480; //场有效数据
parameter V_FRONT = 10'd10; //场显示前沿
parameter V_TOTAL = 10'd525; //场扫描周期
//reg define
reg [9:0] cnt_h;
reg [9:0] cnt_v;
//wire define
wire vga_en;
wire data_req;
//VGA行场同步信号
assign vga_hs = (cnt_h <= H_SYNC - 1'b1) ? 1'b0 : 1'b1;
assign vga_vs = (cnt_v <= V_SYNC - 1'b1) ? 1'b0 : 1'b1;
//使能数据输出
assign vga_en = (((cnt_h >= H_SYNC+H_BACK) && (cnt_h < H_SYNC+H_BACK+H_DISP))
&&((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;
//数据输出
assign vga_rgb = vga_en ? pixel_data : 16'd0;
//请求像素点颜色数据输入
assign data_req = (((cnt_h >= H_SYNC+H_BACK-1'b1) && (cnt_h < H_SYNC+H_BACK+H_DISP-1'b1))
&& ((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;
//像素点坐标
assign pixel_xpos = data_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 10'd0;
assign pixel_ypos = data_req ? (cnt_v - (V_SYNC + V_BACK - 1'b1)) : 10'd0;
//行计数器对像素时钟计数
always @(posedge vga_clk) begin
if (sys_rst_n)
cnt_h <= 10'd0;
else begin
if(cnt_h < H_TOTAL - 1'b1)
cnt_h <= cnt_h + 1'b1;
else
cnt_h <= 10'd0;
end
end
//场计数器对行计数
always @(posedge vga_clk) begin
if (sys_rst_n)
cnt_v <= 10'd0;
else if(cnt_h == H_TOTAL - 1'b1) begin
if(cnt_v < V_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= 10'd0;
end
end
endmodule
vga显示模块代码
always @(posedge vga_clk) begin
if (sys_rst_n)
pixel_data <= BLACK ;
else begin
if(((10'd480-data_xadc) >= pixel_ypos - 2'd2) && ((10'd480-data_xadc) <= pixel_ypos +2'd2))
pixel_data <= RED;
else
pixel_data <= BLACK;
end
end
当处理后的xadc数据(data_xadc已经被除10)在等于纵坐标的时候,像素点显示为红,其余部分为黑色,因为纵坐标是从上往下增,为了符合平时使用坐标轴的习惯,我上下反转了一下。为了让波形跟好看,我让xadc数据对应坐标轴上下四个像素点都显示红色。
心电采集模块为老师提供的Pulsesensor,用了三个相同的10k电阻进行简单的1/3分压(xadc只能接受0-1V的电压)最后的上机效果如下
完整代码如下
头文件文章来源:https://uudwc.com/A/d5z0
`timescale 1ns / 1ps
module comprehensive_lib(
input clock, //系统时钟
input reset, //复位信号
input IN1, //差分输入
input IN2, //差分输入
output vga_hs, //行同步信号
output vga_vs, //场同步信号
output [11:0] vga_rgb //红绿蓝三原色输出
);
reg [9:0]ram_addr_a; //ram_a地址
wire [9:0]ram_addr_b; //ram_b地址
wire [15:0] DO_OUT;
wire [4:0] CHANNEL_OUT; //xadc通道
wire [11:0] doutb_1; //ram_B输出
wire [11:0] doutb_2; //ram_B输出
wire EOC_OUT;
wire DRDY_OUT;
wire vga_clk; //vga25M时钟
wire locked; //pll稳定信号
wire ram_clk; //ram时钟
wire [8:0]data_xadc; //显示数据
wire [11:0]pixel_data; //像素点数据
wire [ 9:0] pixel_xpos; //像素点横坐标
wire [ 9:0] pixel_ypos; //像素点纵坐标
reg [11:0] reg_ram_data_a;
reg [11:0] ram_data_a;
reg [3:0] data_a_cnt;
initial ram_addr_a <= 1'd1;
always@(posedge clock)begin
if(reset)
ram_data_a <= 1'd0;
else begin
if(DRDY_OUT && (CHANNEL_OUT == 5'h12))
ram_data_a <= DO_OUT[15:4];
else
ram_data_a <= ram_data_a;
end
end
always@(posedge ram_clk)begin
if(reset)
ram_addr_a <= 1'd0;
else begin
if(ram_addr_a == 10'd639)
ram_addr_a <= 1'd0;
else
ram_addr_a <= ram_addr_a + 1'd1;
end
end
assign data_xadc = doutb_1 / 10 ;//缩小dout位数
assign ram_addr_b = pixel_xpos + 1'b1;
xadc_wiz_0 u_xadc_wiz_0 (
.di_in(16'b0), // input wire [15 : 0] di_in 动态重配置端口 (DRP) 的输入数据总线。
.daddr_in({2'b0,CHANNEL_OUT}), // input wire [6 : 0] daddr_in 地址总线
.den_in(EOC_OUT), // input wire den_in 使能信号
.dwe_in(1'b0), // input wire dwe_in 读写信号
.drdy_out(DRDY_OUT), // output wire drdy_out 数据就绪信号
.do_out(DO_OUT), // output wire [15 : 0] do_out 动态重配置端口 (DRP) 的输出数据总线。
.dclk_in(clock), // input wire dclk_in
.reset_in(reset), // input wire reset_in
.vp_in(), // input wire vp_in
.vn_in(), // input wire vn_in
.vauxp2(IN1), // input wire vauxp2
.vauxn2(1'd0), // input wire vauxn2
.channel_out(CHANNEL_OUT), // output wire [4 : 0] channel_out 通道选择输出
.eoc_out(EOC_OUT), // output wire eoc_out
.alarm_out(), // output wire alarm_out
.eos_out(), // output wire eos_out
.busy_out() // output wire busy_out
);
ip_pll u_ip_pll(
// Clock out ports
.vga_clk(vga_clk), // output vga_clk
// Status and control signals
.reset(reset), // input reset
.locked(locked), // output locked
// Clock in ports
.clock(clock)
); // input clock
ram_12x640d u_ram_12x640d (
.clka(ram_clk), // input wire clka
.wea(1'b1), // input wire [0 : 0] wea
.addra(ram_addr_a), // input wire [9 : 0] addra
.dina(ram_data_a), // input wire [11 : 0] dina
.clkb(vga_clk), // input wire clkb
.addrb(ram_addr_b), // input wire [9 : 0] addrb
.doutb(doutb_1) // output wire [11 : 0] doutb
);
count u_count(
.clock (clock ),
.reset (reset ),
.clk (ram_clk)
);
vga_show u_vga_show(
.vga_clk (vga_clk), //VGA驱动时钟
.sys_rst_n (reset ), //复位信号
.data_xadc (data_xadc ), //xadc数据
.pixel_xpos (pixel_xpos), //像素点横坐标
.pixel_ypos (pixel_ypos), //像素点纵坐标
.pixel_data (pixel_data) //像素点数据
);
vga_control u_vga_control(
.vga_clk(vga_clk),
.sys_rst_n(!locked),
.vga_hs(vga_hs),
.vga_vs(vga_vs),
.vga_rgb(vga_rgb),
.pixel_data(pixel_data),
.pixel_xpos(pixel_xpos),
.pixel_ypos(pixel_ypos)
);
endmodule
//分频模块
module count(
input clock,
input reset,
output reg clk
);
reg [27:0] cnt_200Hz;//时钟分频计数器
always@(posedge clock)begin
if(reset)begin
cnt_200Hz <= 28'd0;
clk <= 1'd0;
end
else begin
if (cnt_200Hz == 28'd249999)begin
cnt_200Hz <= 28'd0;
clk <= ~clk;
end
else
cnt_200Hz <= cnt_200Hz + 1'b1;
end
end
endmodule
//vga控制模块
module vga_control(
input vga_clk, //VGA驱动时钟
input sys_rst_n, //复位信号
//VGA接口
output vga_hs, //行同步信号
output vga_vs, //场同步信号
output [11:0] vga_rgb, //红绿蓝三原色输出
input [11:0] pixel_data, //像素点数据
output [ 9:0] pixel_xpos, //像素点横坐标
output [ 9:0] pixel_ypos //像素点纵坐标
);
//parameter define
parameter H_SYNC = 10'd96; //行同步
parameter H_BACK = 10'd48; //行显示后沿
parameter H_DISP = 10'd640; //行有效数据
parameter H_FRONT = 10'd16; //行显示前沿
parameter H_TOTAL = 10'd800; //行扫描周期
parameter V_SYNC = 10'd2; //场同步
parameter V_BACK = 10'd33; //场显示后沿
parameter V_DISP = 10'd480; //场有效数据
parameter V_FRONT = 10'd10; //场显示前沿
parameter V_TOTAL = 10'd525; //场扫描周期
//reg define
reg [9:0] cnt_h;
reg [9:0] cnt_v;
//wire define
wire vga_en;
wire data_req;
//VGA行场同步信号
assign vga_hs = (cnt_h <= H_SYNC - 1'b1) ? 1'b0 : 1'b1;
assign vga_vs = (cnt_v <= V_SYNC - 1'b1) ? 1'b0 : 1'b1;
//使能数据输出
assign vga_en = (((cnt_h >= H_SYNC+H_BACK) && (cnt_h < H_SYNC+H_BACK+H_DISP))
&&((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;
//数据输出
assign vga_rgb = vga_en ? pixel_data : 16'd0;
//请求像素点颜色数据输入
assign data_req = (((cnt_h >= H_SYNC+H_BACK-1'b1) && (cnt_h < H_SYNC+H_BACK+H_DISP-1'b1))
&& ((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;
//像素点坐标
assign pixel_xpos = data_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 10'd0;
assign pixel_ypos = data_req ? (cnt_v - (V_SYNC + V_BACK - 1'b1)) : 10'd0;
//行计数器对像素时钟计数
always @(posedge vga_clk) begin
if (sys_rst_n)
cnt_h <= 10'd0;
else begin
if(cnt_h < H_TOTAL - 1'b1)
cnt_h <= cnt_h + 1'b1;
else
cnt_h <= 10'd0;
end
end
//场计数器对行计数
always @(posedge vga_clk) begin
if (sys_rst_n)
cnt_v <= 10'd0;
else if(cnt_h == H_TOTAL - 1'b1) begin
if(cnt_v < V_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= 10'd0;
end
end
endmodule
//vga显示模块
module vga_show(
input vga_clk, //VGA驱动时钟
input sys_rst_n, //复位信号
input [9:0] data_xadc, //xadc数据
input [9:0] pixel_xpos, //像素点横坐标
input [9:0] pixel_ypos, //像素点纵坐标
output reg [11:0] pixel_data //像素点数据
);
parameter H_DISP = 10'd640; //分辨率--行
parameter V_DISP = 10'd480; //分辨率--列
parameter BLACK = 12'b0000_0000_0000; // 黑色
parameter GREEN = 12'b0000_1111_0000; // 绿色
parameter RED = 12'b0000_0000_1111; // 红色
parameter BLUE = 12'b1111_0000_0000; //蓝色
parameter WHITE = 12'b1111_1111_1111; // 白色
always @(posedge vga_clk) begin
if (sys_rst_n)
pixel_data <= BLACK ;
else begin
if(((10'd480-data_xadc) >= pixel_ypos - 2'd2) && ((10'd480-data_xadc) <= pixel_ypos +2'd2))
pixel_data <= RED;
else
pixel_data <= BLACK;
end
end
endmodule
本人第一次创作,不足之处欢迎指正文章来源地址https://uudwc.com/A/d5z0