使用EGO1的XADC采集心电模块输出,通过VGA显示

最近刚做完学校的实验,记录一下自己的实验过程,也可以给需要的朋友一个参考,不对之处欢迎指正。

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,如图。

preview

 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的电压)最后的上机效果如下

 完整代码如下

头文件

`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 

本人第一次创作,不足之处欢迎指正

物联沃分享整理
物联沃-IOTWORD物联网 » 使用EGO1的XADC采集心电模块输出,通过VGA显示

发表评论