FPGA实战:手把手教你用Verilog驱动4.3寸RGB触摸屏(附完整代码)
做FPGA项目最怕的不是逻辑写不出来,而是写了一大堆代码,一上板子画面不出来,找不到问题在哪。最近一个图像处理的小项目正好需要一块带触摸的4.3寸RGB屏做人机交互,索性把显示和触摸全部用Verilog从零写了一遍。驱动部分先后调了小一周,中间踩了不少坑,也把原来文档里写得含糊的地方全摸清楚了。这里的思路和代码具有一定的通用性,常见的4.3寸、5寸、7寸RGB接口屏都可以直接改参数套用,想入门FPGA显示控制或者做图像处理项目的朋友,完全可以拿来当参考底板。
先说下这个项目最终实现的效果:一块4.3寸RGB电容触摸屏,通过FPGA输出565格式的RGB像素数据,先跑纯色块和色条测试图案,再用简单的状态机画了一个类似“按钮”的界面,触摸点击之后能准确识别触点位置,并在屏幕上显示按下状态。最后整个工程所有代码加起来不到600行,开发板上电就能跑,不需要处理器参与,纯逻辑完成显示和触摸读取,非常直观。
1. 项目整体设计与思路拆解
1.1 为什么选择RGB接口屏而不是MCU屏
很多新手开始接触FPGA显示,容易直接选带控制器芯片的MCU屏,比如用ILI9341之类驱动的屏。这种屏的好处是SPI或8080并口时序简单,缺点也很明显:刷新率低、需要持续刷一整帧内存、大屏完全跑不动。而RGB接口屏不一样,它内部没有显存,所有像素数据必须由FPGA或者其他主控严格按照时序实时送过去,本质上就是一个带背光的高速并口设备,适合做实时图像处理输出。
4.3寸RGB屏常见的分辨率有480x272,也有800x480的。这次用的是480x272,像素时钟大概9MHz左右,对于FPGA来说压力非常小,哪怕是入门级的Cyclone IV、Artix-7都能跑。整个项目如果只是点亮屏幕,资源占用几乎可以忽略不计,主要逻辑都花在时序控制和触摸坐标解析上。
从学习和上手角度来说,RGB屏其实更“硬核”一些,因为它逼着你搞清楚行场同步、像素有效区、消隐区这些显示底层概念。这些东西搞懂了,再去理解HDMI、LVDS、MIPI这些高速显示接口,会发现底层逻辑都是相似的。
1.2 显示时序的核心概念:DE模式
RGB屏一般支持两种驱动模式:同步模式和DE模式。
- 同步模式:需要提供行同步信号(HSYNC)、场同步信号(VSYNC)和像素使能信号(DE)。DE为高时,总线上的RGB数据是有效像素。
- DE模式:不需要行场同步,只要DE拉高期间发数据就行,非常适合FPGA实现。
这次选用了DE模式。原因是FPGA做时序控制本质上是计数器状态机,DE模式只需要把像素计数映射到有效区间和消隐区间,逻辑直观,调试方便。如果以后要转HDMI输出,这个DE信号也能直接喂给HDMI编码器,几乎是零转换成本。
显示一帧画面的流程可以这样理解:像素时钟每来一个上升沿,驱动模块就把一个像素的RGB数据放到总线上。当扫描完一行之后,进入水行消隐区,再换行。扫完所有行之后,进入场消隐区,再开始新的一帧。整个过程和CRT时代的电子束扫描是一样的,虽然现在是液晶屏,但时序协议完全继承下来了。
1.3 整体模块划分
拿到这个项目之后,我先想清楚几个必须解决的问题,按功能拆成模块:
- 像素时钟生成:需要为屏幕提供精确的像素时钟,最方便的做法是用PLL锁相环。板载50MHz时钟,PLL输出9MHz或者18MHz都可以。如果用的9MHz正好是像素时钟,直接用;如果用18MHz,就再用计数器二分频。
- LCD驱动模块(lcd_driver):负责生成时序信号和像素坐标(x, y),告诉上层逻辑“当前正在扫描哪个像素点”。
- 显示内容模块(显示测试图案/界面):根据坐标产生对应的RGB数据。这个模块就是我之后要画画、显示图片、显示字符的接口位置。
- 触摸控制模块(I2C接口):读取电容触摸屏控制芯片的坐标信息。
- 顶层模块:把上面这些模块组合起来,完成数据通路和复位控制。
这种“显示时序”与“显示内容”分离的设计,是很多成熟显示IP的通用做法。好处很明显:测试图案、图片显示、OSD菜单、游戏画面都可以复用同一个LCD驱动,只需要改显示内容模块的内部逻辑就行。
2. 硬件准备与接线细节
2.1 需要准备哪些东西
先说硬件清单,并不是所有RGB屏都能直接接开发板,需要确认几点。
- FPGA开发板,建议带40pin以上扩展口,方便引出24根RGB信号线和同步信号。
- 4.3寸480x272 RGB接口电容触摸屏,最好带GT911或者FT5x06触摸芯片,因为I2C读取方便,资料多。
- 一个能输出3.3V电平的逻辑分析仪,或者直接利用FPGA内部的ILA逻辑分析仪,调试触摸时特别好用。
在选屏的时候特别要注意,有些4.3寸屏是RGB888接口(24bit),有些是RGB666(18bit),还有RGB565(16bit)。FPGA管脚数量有限,大多数入门开发板会选择RGB565或RGB666。我这次用的是RGB565,总共5根蓝色、6根绿色、5根红色,加在一起16根数据线,加上HSYNC、VSYNC、DE、PCLK,管脚占用很少,走线也方便。
2.2 引脚分配与连接方式
先看一张引脚连接的参考表,不同开发板和屏幕型号引脚顺序会略有差异,接之前一定要以自己板子的原理图为准。
| LCD信号 | FPGA管脚方向 | 说明 |
|---|---|---|
| PCLK | 输出 | 像素时钟 |
| DE | 输出 | 数据有效信号 |
| HSYNC | 输出 | 行同步信号,DE模式下可以不接 |
| VSYNC | 输出 | 场同步信号,DE模式下可以不接 |
| DATA[15:0] | 输出 | RGB565像素数据 |
| LCD_BL | 输出 | 背光控制,拉高点亮 |
| SDA | 双向(开漏) | 触摸芯片I2C数据线 |
| SCL | 输出 | 触摸芯片I2C时钟线 |
| INT | 输入 | 触摸中断,可用来判断是否有触摸事件 |
| RST | 输出 | 触摸芯片复位 |
关于背光,有的屏幕模组背光引脚是独立控制的,直接给高电平就行。如果开发板上有三极管驱动电路,记得看它是高有效还是低有效,很多“屏幕不亮”的问题其实只是背光极性搞反了。可以先写一行代码把背光信号固定拉高,排除背光问题再调显示时序。
I2C引脚是开漏结构,务必接上拉电阻,一般4.7k或10k都可以。如果开发板上自带上拉就不用管了。触摸芯片的INT引脚有个小技巧,它可以在触摸发生时拉低,也可以配置成高电平有效。在FPGA里我直接用轮询方式读I2C,INT只是辅助定位问题,不是必需信号。
2.3 电源与电平注意事项
4.3寸RGB屏需要的供电一般有3.3V和背光用的5V或者12V。大多数开发板的扩展口会直接提供这两种电压,但前提是要看清规格,别把5V接到3.3V的电源引脚上。LCD的信号电平通常是3.3V TTL,与FPGA管脚兼容,不需要额外的电平转换。
有个容易忽略的地方:多根数据线在PCB上并行走线,容易产生串扰。如果是自己做转接板,注意数据线之间加地线隔离;如果是买现成的模组和转接板,基本不用操心,只要线别接错就行。
3. 核心Verilog代码实现
3.1 LCD时序参数与驱动模块
开始写代码之前,先查屏的规格书,找到时序参数表,核心是下面几个值:
- 水平总周期(H_TOTAL):一行总共多少个像素时钟
- 水平有效像素(H_ACTIVE):480
- 行前肩(H_FRONT_PORCH):行消隐开始前的等待时间
- 行同步脉冲宽度(H_SYNC_PULSE):行同步信号保持低电平的时间
- 行后肩(H_BACK_PORCH):行同步脉冲结束后,到有效数据开始前的等待时间
- 同理,垂直方向也有V_TOTAL、V_ACTIVE、V_FRONT_PORCH、V_SYNC_PULSE、V_BACK_PORCH
常见的480x272屏幕参数一般是这样:
| 参数 | 数值 |
|---|---|
| 像素时钟 | 9 MHz |
| H_ACTIVE | 480 |
| H_FRONT_PORCH | 2 |
| H_SYNC_PULSE | 41 |
| H_BACK_PORCH | 2 |
| H_TOTAL | 525 |
| V_ACTIVE | 272 |
| V_FRONT_PORCH | 2 |
| V_SYNC_PULSE | 10 |
| V_BACK_PORCH | 2 |
| V_TOTAL | 286 |
为什么像素时钟选9MHz?因为刷新率大概等于9MHz除以水平总周期再除以垂直总周期,算下来就是9000000 / (525 * 286),大约60Hz。正好符合常规液晶屏的内部扫描需求。
下面是我的LCD驱动核心代码,我用一个计数器同时追踪像素位置和行列坐标,代码比传统双计数器写法更简洁:
// 480x272@60Hz LCD驱动模块,DE模式 module lcd_driver #( parameter H_ACTIVE = 480, // 水平有效像素数 parameter H_FP = 2, // 行前肩 parameter H_SYNC = 41, // 行同步宽度 parameter H_BP = 2, // 行后肩 parameter H_TOTAL = 525, // 水平总周期 parameter V_ACTIVE = 272, // 垂直有效像素数 parameter V_FP = 2, // 场前肩 parameter V_SYNC = 10, // 场同步宽度 parameter V_BP = 2, // 场后肩 parameter V_TOTAL = 286 // 垂直总周期 )( input wire clk, // 像素时钟,9MHz input wire rst_n, output reg hsync, // 行同步信号 output reg vsync, // 场同步信号 output reg de, // 数据使能信号 output reg [15:0] rgb, // RGB565像素数据 output wire [9:0] x, // 当前像素x坐标 output wire [9:0] y // 当前像素y坐标 ); reg [9:0] h_cnt; reg [9:0] v_cnt; wire h_active = (h_cnt < H_ACTIVE); // 水平有效数据区 wire v_active = (v_cnt < V_ACTIVE); // 垂直有效数据区 assign x = h_active ? h_cnt : 10'd0; assign y = v_active ? v_cnt : 10'd0; // 水平计数器扫描 always @(posedge clk or negedge rst_n) begin if (!rst_n) h_cnt <= 10'd0; else if (h_cnt == H_TOTAL - 1) h_cnt <= 10'd0; else h_cnt <= h_cnt + 1'b1; end // 垂直计数器扫描,一行结束才加1 always @(posedge clk or negedge rst_n) begin if (!rst_n) v_cnt <= 10'd0; else if (h_cnt == H_TOTAL - 1) begin if (v_cnt == V_TOTAL - 1) v_cnt <= 10'd0; else v_cnt <= v_cnt + 1'b1; end end // 生成同步信号和DE always @(posedge clk or negedge rst_n) begin if (!rst_n) begin hsync <= 1'b1; vsync <= 1'b1; de <= 1'b0; rgb <= 16'd0; end else begin hsync <= ~(h_cnt >= (H_ACTIVE + H_FP) && h_cnt < (H_ACTIVE + H_FP + H_SYNC)); vsync <= ~(v_cnt >= (V_ACTIVE + V_FP) && v_cnt < (V_ACTIVE + V_FP + V_SYNC)); de <= h_active & v_active; if (h_active & v_active) rgb <= pixel_data_in; // 从显示内容模块取数据 else rgb <= 16'd0; end end endmodule这段代码里面最关键的是水平计数器和垂直计数器的配合。水平计数器从头走到尾,每一行重新循环;垂直计数器只有在一行扫描完成后才加1。这样写的好处是时序逻辑非常清晰,用一两个always块就能完成所有同步信号的生成,调试的时候用仿真波形看一眼h_cnt和v_cnt就能秒懂。
DE信号只有在h_active和v_active同时为真时才拉高,这就是有效像素区。x和y坐标也是在这个区间内有意义,显示内容模块只需要根据x和y生成实时像素数据,完全不用关心消隐期在干什么。
注意代码里输出rgb用了寄存器输出,其实为了满足LCD的建立保持时间,这样做更稳妥。如果组合逻辑直接输出,布线延迟和数据变化容易导致屏幕边缘出现雪花或噪点。
3.2 显示测试图案:色条与彩色方块
有了坐标x和y,显示图案就变成一件非常简单的事。比如显示一个标准彩条,红色分量在屏幕左1/3为高,绿色分量在中间1/3为高,蓝色分量在右1/3为高,出来的就是红绿蓝三条色带。
为了让效果更直观,我写了一个显示模块,直接根据坐标生成一个类似“界面”的画面:顶部一条深蓝色标题栏,中间一个圆角矩形按钮,底部显示当前触摸坐标值。当然圆角矩形用组合逻辑稍微费点资源,但在这个分辨率下完全不是问题。
module display_test( input wire clk, input wire [9:0] x, input wire [9:0] y, input wire [9:0] touch_x, // 触摸坐标 input wire [9:0] touch_y, output reg [15:0] pixel ); // 坐标范围480x272 // 按钮区域:x=160~319, y=100~179 parameter BTN_X0 = 160; parameter BTN_X1 = 320; parameter BTN_Y0 = 100; parameter BTN_Y1 = 180; wire in_btn = (x >= BTN_X0 && x < BTN_X1 && y >= BTN_Y0 && y < BTN_Y1); always @(posedge clk) begin // 标题栏:占屏高 y<40 if (y < 40) pixel <= 16'h001F; // 蓝色标题栏 // 按钮区域,按下时变色 else if (in_btn) begin if (touch_pressed) pixel <= 16'h07E0; // 绿色 = 按下 else pixel <= 16'hF800; // 红色 = 未按下 end // 底部显示触摸坐标区域:y > 232 else if (y > 232) pixel <= 16'hFFFF; // 白色区域 else pixel <= 16'h0000; // 黑色背景 end endmodule这里用到了触摸按下标志touch_pressed。按下按钮时按钮会从红色变成绿色,视觉反馈非常明显,一眼就能看出触摸有没有正常工作。如果只是想在屏幕上显示一副图片,操作方式也类似:先把图片用脚本转换成RGB565的十六进制文本,然后用$readmemh读入FPGA内部的ROM/RAM,再根据x和y算出地址,取出对应的像素值输出即可。
3.3 GT911触摸芯片的I2C读取
4.3寸屏配的电容触摸芯片很多是GT911,支持5点触控,最大分辨率可以到1080P,但驱动起来其实不复杂,本质就是一个从I2C寄存器读数据的过程。这里要摸清楚一套初始化和读取流程。
GT911的I2C地址比较特殊,7位地址通常是0x5D或0x28,取决于引脚配置。我用的这块屏幕默认是0x5D。它的寄存器布局中,0x814E是触摸点状态和数据区的起始地址。每次检测到触摸,触摸芯片会更新一组数据:
- 0x814E:点数状态,低4位表示有效触摸点数
- 0x8150:第一个触摸点的X坐标
- 0x8152:第一个触摸点的Y坐标
所以读取触摸点的核心流程就三步:写寄存器地址0x814E,连续读取6个字节,解析坐标和是否有触摸。GT911需要初始化配置,比如切换寄存器映射到正常模式。实际上不少模组出厂时已经把配置写好了,上电复位之后可以直接读坐标数据,不需要额外配置,只有坐标方向不对的时候才需要改寄存器或做重新映射。
我之前写过一个极简的GT911读取模块思路,关键伪代码如下:
// GT911触摸读取流程,伪代码描述状态机 localparam IDLE = 4'd0; localparam WR_ADDR = 4'd1; // 写目标寄存器地址0x814E localparam RESTART = 4'd2; // 重启I2C localparam RD_DATA = 4'd3; // 连续读6字节 localparam PARSE = 4'd4; // 解析坐标 always @(posedge clk) begin case(state) IDLE: begin if (poll_tick) begin // 启动一次触摸读取 i2c_start(); state <= WR_ADDR; end end WR_ADDR: begin send_byte(8'h81); // 写入寄存器高位地址 send_byte(8'h4E); // 写入寄存器低位地址 state <= RESTART; end RESTART: begin i2c_start(); // 发送I2C重启信号 send_byte(8'hBB); // 0x5D左移一位=0xBA,加上读位=0xBB state <= RD_DATA; end RD_DATA: begin // 依次读回6个字节 rd_byte(); state <= PARSE; end PARSE: begin touch_cnt <= buf[0] & 4'h0F; touch_x <= {buf[2], buf[1]}; // 低字节在前 touch_y <= {buf[4], buf[3]}; state <= IDLE; end endcase endI2C时序的Verilog实现一定要小心,SCL高电平期间SDA电平不能变化。实际操作时最好是写一个I2C总线底层驱动模块,把启动、停止、写字节、读字节这些基本操作封装成子任务或状态机子状态,上层触摸解析模块只负责组织数据的读写顺序,这样代码结构会清晰很多。
理论上GT911在FPGA上直接用IO模拟I2C即可,因为触摸数据的更新频率只有几十赫兹,I2C时钟设到100kHz—400kHz都足够。没必要去追求高速模式,反而容易时序不稳。
3.4 顶层模块连线
最后是顶层模块,把时钟、LCD驱动、显示内容、触摸读取全部连起来。注意PLL的调用,不同FPGA厂商IP例化名称不同,但思路都是一样的。
module top( input wire clk_50m, input wire rst_n, // LCD接口 output wire lcd_pclk, output wire lcd_hsync, output wire lcd_vsync, output wire lcd_de, output wire [15:0] lcd_rgb, output wire lcd_bl, // 触摸I2C inout wire touch_sda, output wire touch_scl, input wire touch_int ); wire clk_pix; wire locked; // PLL生成9MHz像素时钟 pll u_pll( .inclk0(clk_50m), .c0(clk_pix), .locked(locked) ); wire [9:0] x_pos, y_pos; wire [15:0] disp_data; wire [9:0] t_x, t_y; wire t_pressed; // LCD驱动 lcd_driver u_lcd( .clk(clk_pix), .rst_n(locked & rst_n), .hsync(lcd_hsync), .vsync(lcd_vsync), .de(lcd_de), .rgb(lcd_rgb), .x(x_pos), .y(y_pos) ); // 触摸解析模块 gt911_top u_touch( .clk(clk_50m), .rst_n(locked & rst_n), .scl(touch_scl), .sda(touch_sda), .touch_x(t_x), .touch_y(t_y), .touch_pressed(t_pressed) ); // 显示内容:这里简化处理,实际需要把pixel连到lcd_driver输入 display_test u_display( .clk(clk_pix), .x(x_pos), .y(y_pos), .touch_x(t_x), .touch_y(t_y), .pixel(disp_data) ); // 驱动模块里的pixel应该改为disp_data,这里可以用wire直接连入 assign lcd_bl = 1'b1; // 背光点亮 endmodule顶层模块的使用体验:工程里就这几个模块,时序清晰,链接紧密。代码里有个细节,复位信号用了locked & rst_n,PLL没有锁定之前,所有模块保持复位状态,避免输出不确定信号给屏幕,这个习惯很重要。
4. 仿真与上板调试方法
4.1 用仿真验证时序
FPGA调试显示接口最痛苦的是,问题一旦上板就难定位。所以我的习惯是先把LCD驱动模块放在ModelSim或者Vivado Simulator里仿真,确认时序波形没问题,再上板。这里写一个简单的testbench,模拟像素时钟,观察hsync、vsync、de和坐标信号:
module tb_lcd_driver; reg clk; reg rst_n; wire hsync, vsync, de; wire [15:0] rgb; wire [9:0] x, y; lcd_driver uut( .clk(clk), .rst_n(rst_n), .hsync(hsync), .vsync(vsync), .de(de), .rgb(rgb), .x(x), .y(y) ); initial begin clk = 0; forever #55.5 clk = ~clk; // 9MHz约55.5ns end initial begin rst_n = 0; #200; rst_n = 1; #1000000; $finish; end endmodule仿真时重点看两个点:
- 看vsync拉高的周期时长,算出来应该在286行左右,每一行就是h_cnt从0到524走完一个周期。
- 看de信号与坐标x、y的关系,在de为高时x应该始终小于480,y始终小于272。
只要这些对应关系正确,上板基本就能正常显示了。屏幕白屏或者花屏时,先跑仿真比盲改代码高效很多。
4.2 上板前的检查清单
在把程序烧进FPGA之前,按照下面的清单过一遍,能省下大量排查时间:
- 确认PLL输出频率和屏幕需求的像素时钟一致。
- 确认管脚分配和开发板原理图一致,特别是数据线顺序,别把R和B搞反了。
- 确认背光控制信号极性,先用固定值点亮背光。
- 确认屏幕排线连接稳固,很多花屏是接触不良引起的。
- 如果屏幕有初始化配置,确认复位时序是否正确。
实际项目的经验,大约70%的“屏幕不亮”问题出在管脚分配和背光控制上,30%出在时序参数和像素时钟频率上。
4.3 触摸坐标的确认与映射
GT911读回来的坐标通常是0到屏的宽度之间的原始值,比如X范围0-480,Y范围0-272。但坐标方向可能和屏幕物理方向不一致。实现中我直接在触摸解析里加了一个参数化校正,每次只需改两个参数就能翻转X轴或Y轴:
// 坐标轴校正:视情况选择原值或者翻转 assign map_x = FLIP_X ? (479 - touch_x_raw) : touch_x_raw; assign map_y = FLIP_Y ? (271 - touch_y_raw) : touch_y_raw;上板之后可以摸一下屏幕四个角,看高亮光标的位置是否跟着手指走。如果不一致,就把对应轴翻转过来。还有一个细节,触摸坐标的X和Y可能和读取到的寄存器顺序对调,数据手册上会写,但有时实际操作发现个别批次屏幕就是反的,这个只能实测确认,别全信手册。
5. 常见问题与排查技巧实录
5.1 白屏/黑屏类问题
这个是遇到最多的,现象是屏幕有背光但没有任何显示,或者全白、全黑。按优先级排查:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 有背光但全黑 | DE或RGB数据为0 | 确认de是否有波形,RGB是否在变化 |
| 有背光但全白 | RGB一直输出最大值或DE常高 | 检查显示内容模块的pixel是否被赋值成固定大值 |
| 无背光 | 背光控制极性不对或供电问题 | 测量背光电压,确认控制信号正确 |
| 屏幕闪烁或画面滚动 | 垂直同步参数错误 | 核对V_TOTAL、V_SYNC、V_FP等参数 |
真正玄学的地方是,有时DE的极性选择不对也会导致黑屏或花屏。有些LCD模组DE是低有效,虽然大多数是高有效,这个要以屏的规格书为准。
5.2 花屏和错位问题
花屏的原因一般有三种可能:像素时钟频率不对、RGB位数没对齐、或者行同步参数有误差。
如果你用50MHz时钟直接当PCLK输出,屏幕大概率显示雪花点或者倾斜的条纹,因为刷新率远远超了。像素时钟应该通过PLL精确生成,并且最好把参数在代码里做成Parameter方便调整。RGB位数没对齐,比如屏幕上显示的颜色明显偏红或偏蓝,多半是数据线的某一位接到了错误的管脚,先用“全屏纯色”测试图案逐通道验证,这样能快速定位是哪根线接错。
还有一种错位现象是画面整体偏移,左右偏而上下正常,或者反过来。原因是同步脉冲宽度和后肩参数与屏幕实际要求不符,调整H_BP和H_FP会有立竿见影的效果。
5.3 触摸失灵或坐标漂移
触摸不响应时,先确认I2C地址是否正确。GT911的地址可以通过触摸芯片的引脚电平设置,不同批次默认值可能有差异。我遇到过一块屏幕读地址是0x28,而代码里写的是0x5D,导致I2C响应一直不正常。
坐标漂移或者点位跳动,多半是I2C读取时序存在问题,比如SCL频率太高、读取字节的时候没有处理ACK信号、或者连续读字节时主机没有正确发送NACK来终止本轮读取。还有一种情况是GT911内部固件在跑校准算法,第一次上电接触屏幕时坐标会飘一下,等几秒就正常了。
建议调试时把I2C时钟降到100kHz,把读取周期降到10ms以上,稳定性会明显改善。等到一切工作正常了,再逐步提高频率也不迟。
5.4 画面闪烁和拖影
画面闪烁一般是因为帧率太低或者DE信号不连续。LCD驱动模块里,DE应当在一个完整帧内持续保持规律的高低变化,如果垂直计数器和水平计数器逻辑写得有问题,可能会在一帧内出现两个DE脉冲群,视觉效果就是一条亮线从上往下滚。
拖影则通常不是FPGA的问题,而是液晶屏本身的响应时间,特别是廉价的TN屏,在纯色切换的时候拖影尤其明显。这时可以降低刷新率到50Hz试一下,或者干脆接受这个特性。
6. 扩展思路与工程实践体会
4.3寸RGB触摸屏点亮之后,能做的事非常多。比如可以在此基础上再接一个OV5640摄像头,显示实时画面,或者做一个基于FPGA的简易示波器,用屏幕显示波形,触摸控制缩放。我自己的下一步打算是在这个工程里加入简单的图像缓存,用片内Block RAM存一帧二值图像,然后做一个边缘检测的效果实时显示,这样整个系统就变成一个典型的FPGA图像处理教学平台了。
有一点想单独说一下,工程中如果使用了Altera/Intel的开发板,用Quartus打开工程时要注意代码里的PLL例化名称,不同版本的Quartus对PLL IP的生成会有细微差别。如果是Xilinx的板子,记得在Vivado里把管脚约束文件XDC写完整,不然后面改动管脚会很痛苦。类似的代码兼容性问题还有$readmemh路径的写法,不同仿真器可能对相对路径的解析不一样,如果图片数据读不出来,试试改成绝对路径或者调整仿真工作目录。
调试显示和触摸的过程中,我最深的感触是“不迷信手册,一切以实测为准”。有的屏幕标称9MHz,实际在11MHz也能稳定显示;有的GT911寄存器手册说默认I2C地址是0x5D,实际拿到的是0x28。很多坑,都只能靠示波器和逻辑分析仪一点点扒出来。好在FPGA调试本身就是这个玩法,代码和硬件之间永远隔着一条需要耐心验证的鸿沟。
如果你也想做类似的项目,建议先别急着上复杂功能,就从点亮RGB彩条开始,一步步把时序摸熟。等色块正常了再加触摸,触摸正常了再考虑图像缓存和算法加速,这样每个阶段都有明确的目标和成就,不会一上来就被成堆的问题劝退。搞显示这条路,入门靠时序,进阶靠带宽,最后拼的还是系统设计能力。希望这篇记录能帮你少走几步弯路。