基于EGO1 FPGA的心电波形可视化系统设计与实现
在生物医学工程和电子工程的教学实验中,心电信号采集与可视化是常见的实践项目。传统方案通常依赖昂贵的示波器或专业数据采集设备,而本文将展示如何利用EGO1 FPGA开发板和普通VGA显示器,构建一套低成本的心电波形显示系统。这个方案不仅成本低廉,还能让学生深入理解信号采集、处理和显示的全流程。
1. 系统架构与核心组件
本系统的核心在于利用Xilinx FPGA内置的XADC模块进行信号采集,通过双口RAM实现数据缓冲,最终在VGA显示器上实时呈现心电波形。整个系统由三个关键部分组成:
- 信号采集模块:XADC负责将模拟心电信号转换为数字量
- 数据处理模块:双口RAM作为数据缓冲区,解决采样率与显示速率的匹配问题
- 显示输出模块:VGA控制器将处理后的数据转换为显示器可识别的时序信号
系统工作流程如下:
- 心电信号经过分压电路适配到XADC的输入范围(0-1V)
- XADC以200Hz的采样率将模拟信号转换为12位数字量
- 采样数据写入双口RAM的A端口
- VGA控制器从RAM的B端口读取数据,并映射到屏幕的相应像素位置
2. XADC配置与信号采集
XADC(Xilinx Analog-to-Digital Converter)是7系列FPGA内置的模拟混合信号模块,具有12位精度和最高1MSPS的转换速率。在本应用中,我们将其配置为单端输入模式,使用VAUXP2通道采集心电信号。
2.1 XADC接口配置
XADC模块通过DRP(Dynamic Reconfiguration Port)接口与FPGA逻辑交互。关键信号包括:
| 信号名称 | 方向 | 描述 |
|---|---|---|
| daddr_in | 输入 | DRP地址总线,用于选择读取的通道 |
| den_in | 输入 | DRP使能信号,控制数据读取时机 |
| drdy_out | 输出 | 数据就绪指示信号 |
| do_out | 输出 | 转换结果数据总线 |
| eoc_out | 输出 | 转换完成脉冲信号 |
配置XADC时需要注意:
- 将vauxn2引脚接地以实现单端输入模式
- 设置daddr_in为通道地址(心电信号对应h12)
- 使用eoc_out信号触发DRP读取操作
xadc_wiz_0 u_xadc_wiz_0 ( .di_in(16'b0), .daddr_in({2'b0,CHANNEL_OUT}), .den_in(EOC_OUT), .dwe_in(1'b0), .drdy_out(DRDY_OUT), .do_out(DO_OUT), .dclk_in(clock), .reset_in(reset), .vp_in(), .vn_in(), .vauxp2(IN1), .vauxn2(1'd0), .channel_out(CHANNEL_OUT), .eoc_out(EOC_OUT) );2.2 采样率适配
心电信号频率通常在0.5Hz-100Hz之间,而XADC支持最高1MSPS的采样率。直接使用最高采样率会导致:
- 单个屏幕显示的时间窗口过短
- 波形在水平方向上过度压缩
- RAM存储空间快速耗尽
解决方案是采用200Hz的采样率,这样:
- 对于1Hz的心电信号,一个周期用200个点表示
- 640×480分辨率的屏幕可显示约3个完整周期
- RAM深度设置为640,刚好匹配屏幕水平分辨率
实现方法是通过分频电路将系统时钟转换为200Hz,作为RAM A端口的写入时钟:
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 endmodule3. 双口RAM的数据缓冲设计
双口RAM是本系统的关键组件,它解决了两个核心问题:
- 桥接不同时钟域(200Hz采样时钟和25MHz VGA时钟)
- 实现数据速率匹配(低频写入,高频读取)
3.1 RAM配置参数
| 参数 | 值 | 说明 |
|---|---|---|
| 数据宽度 | 12位 | 匹配XADC输出精度 |
| 深度 | 640 | 对应VGA水平分辨率 |
| 端口A时钟 | 200Hz | 采样时钟 |
| 端口B时钟 | 25MHz | VGA像素时钟 |
在FPGA中,可以通过IP核生成器配置这样的双口RAM。关键配置如下:
- 端口A只写,端口B只读
- 使用独立的时钟和地址总线
- 不启用复位功能以简化设计
3.2 数据写入逻辑
RAM的A端口负责接收XADC的采样数据。写入时需要注意:
- 仅在DRDY_OUT有效且通道正确时捕获数据
- 地址自动递增,达到639后回绕到0
- 只保留DO_OUT的高12位有效数据
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 end3.3 数据读取与缩放
RAM的B端口输出需要经过两个处理步骤:
- 数据缩放:将12位ADC值(0-4095)映射到480行显示范围
- 坐标转换:将电压值转换为屏幕Y坐标,并考虑坐标系方向
简单的实现方法是除以比例因子(本设计采用除以10),更优化的方案是使用移位操作:
assign data_xadc = doutb_1 / 10; // 简单实现 // 更优方案:assign data_xadc = doutb_1[11:4]; // 取高8位4. VGA显示控制器实现
VGA控制器负责生成符合VGA标准的时序信号,并将RAM中的数据映射到屏幕相应位置。
4.1 VGA时序参数
标准640×480@60Hz的VGA时序参数如下:
| 参数 | 行时序(像素) | 场时序(行) |
|---|---|---|
| 同步脉冲 | 96 | 2 |
| 后沿 | 48 | 33 |
| 有效显示 | 640 | 480 |
| 前沿 | 16 | 10 |
| 总计 | 800 | 525 |
对应的Verilog实现需要两个计数器:
- 水平计数器:0-799循环计数
- 垂直计数器:每完成一行(800像素)递增一次
// 行计数器 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 end4.2 波形绘制算法
波形显示的核心是将RAM中的数据转换为屏幕上的像素点。本设计采用以下策略:
- 当前X坐标对应RAM地址
- 数据值决定Y坐标位置
- 在数据点上下各扩展2个像素,形成5像素粗的波形线
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 end4.3 时钟生成
VGA标准要求25.175MHz的像素时钟,实际应用中25MHz也能正常工作。本设计使用FPGA的PLL IP核生成精确的时钟:
- 输入系统时钟(如100MHz)
- 通过PLL分频得到25MHz VGA时钟
- 确保时钟稳定后再启用显示逻辑
5. 系统集成与优化建议
将各模块集成后,完整的系统框图如下:
心电信号 → 分压电路 → XADC → 双口RAM → VGA控制器 → 显示器 ↑ ↑ ↑ 保护电路 采样时钟 像素时钟5.1 信号调理电路
实际应用中,心电信号需要经过适当调理才能接入XADC:
- 分压电路:使用三个10kΩ电阻将信号幅度限制在0-1V范围内
- 滤波电路:添加低通滤波器消除高频噪声
- 保护电路:防止过压损坏FPGA引脚
5.2 性能优化方向
当前设计有几个可以改进的方面:
数据缩放算法:用移位代替除法节省资源
// 替代 data_xadc = doutb_1 / 10; assign data_xadc = doutb_1 >> 3; // 近似除以8波形插值:在点之间绘制线段而非离散点
网格背景:添加参考网格提高可读性
多通道支持:同时显示多个生物电信号
测量功能:添加心率计算等实用功能
5.3 调试技巧
开发过程中可能会遇到以下问题及解决方法:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无波形显示 | XADC配置错误 | 检查通道选择和DRP地址 |
| 波形闪烁 | RAM读写冲突 | 确保读写地址不重叠 |
| 垂直线条 | 数据缩放不当 | 调整缩放算法或分频比 |
| 水平偏移 | 时序不同步 | 检查VGA时序参数 |
6. 完整系统代码解析
顶层模块整合了所有子模块,主要完成以下功能:
- 实例化XADC、RAM、时钟生成和VGA控制器
- 连接各模块间的数据和控制信号
- 处理全局复位和时钟域交叉问题
module comprehensive_lib( input clock, // 系统时钟 input reset, // 复位信号 input IN1, // 心电信号输入 output vga_hs, // VGA行同步 output vga_vs, // VGA场同步 output [11:0] vga_rgb // VGA颜色输出 ); // 信号声明 wire [15:0] DO_OUT; wire [4:0] CHANNEL_OUT; wire EOC_OUT, DRDY_OUT; wire vga_clk, locked, ram_clk; wire [11:0] doutb_1; wire [9:0] data_xadc; wire [11:0] pixel_data; wire [9:0] pixel_xpos, pixel_ypos; // XADC实例化 xadc_wiz_0 u_xadc_wiz_0 ( .di_in(16'b0), .daddr_in({2'b0,CHANNEL_OUT}), .den_in(EOC_OUT), .dwe_in(1'b0), .drdy_out(DRDY_OUT), .do_out(DO_OUT), .dclk_in(clock), .reset_in(reset), .vp_in(), .vn_in(), .vauxp2(IN1), .vauxn2(1'd0), .channel_out(CHANNEL_OUT), .eoc_out(EOC_OUT) ); // PLL时钟生成 ip_pll u_ip_pll( .vga_clk(vga_clk), .reset(reset), .locked(locked), .clock(clock) ); // 双口RAM实例化 ram_12x640d u_ram_12x640d ( .clka(ram_clk), .wea(1'b1), .addra(ram_addr_a), .dina(ram_data_a), .clkb(vga_clk), .addrb(ram_addr_b), .doutb(doutb_1) ); // 200Hz分频器 count u_count( .clock(clock), .reset(reset), .clk(ram_clk) ); // VGA显示模块 vga_show u_vga_show( .vga_clk(vga_clk), .sys_rst_n(reset), .data_xadc(data_xadc), .pixel_xpos(pixel_xpos), .pixel_ypos(pixel_ypos), .pixel_data(pixel_data) ); // VGA控制器 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) ); assign data_xadc = doutb_1 / 10; assign ram_addr_b = pixel_xpos + 1'b1; // RAM写入控制逻辑 reg [9:0] ram_addr_a; reg [11:0] ram_data_a; 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]; 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 endmodule7. 实际应用与教学价值
这套基于EGO1 FPGA的心电显示系统在实际教学和项目中具有多重价值:
- 成本效益:总成本不足千元,远低于专业示波器
- 教育意义:完整覆盖信号链的各个环节
- 模拟信号调理
- 数据采集与转换
- 数字信号处理
- 图形显示技术
- 扩展性强:可轻松修改为其他生物电信号采集系统
- 实践性强:学生可以亲手构建完整系统,而非仅使用现成设备
在实验室环境中,这套系统可以帮助学生:
- 理解心电信号的特性与采集要求
- 掌握FPGA在实时系统中的应用
- 学习跨时钟域设计技巧
- 实践数字信号处理基础算法