ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FPGA驱动RGB屏实战:从行场时序到I2C触摸调试

FPGA驱动RGB屏实战:从行场时序到I2C触摸调试 1. 点亮一块RGB屏之前先弄清楚屏幕到底要什么1.1 行场同步与DE模式你的FPGA先要选边站先说句大实话第一次拿到4.3寸RGB触摸屏的数据手册时我盯着引脚定义愣了好一会儿。RGB888接口加触摸I2C加起来五六十个引脚比之前玩过的SPI屏复杂了不止一个量级。但你只要把原理捋顺就会发现这批屏幕的驱动思路高度统一甚至可以说学会了这一块屏整个RGB系列你都能上手。RGB屏和SPI屏最本质的区别在于它没有写命令写数据这种寄存器操作流程。屏幕上每一个像素的位置是靠行同步HSYNC、场同步VSYNC、像素时钟PCLK三根线配合来确定位的。数据线上的RGB信号只有在对的时刻、对的位置出现屏幕才会显示正确的内容。这里FM的VGA时序大家应该不陌生RGB屏的时序逻辑和VGA如出一辙可以说是同一个体系一帧画面由若干行组成一行由若干个像素组成行与行之间有行消隐帧与帧之间有场消隐像素数据在有效显示区域内逐点送出去。实际场景中4.3寸RGB屏常见的有两种工作模式需要你在初始化时明确选择SYNC模式行场同步模式用HSYNC和VSYNC两根线直接指示行列位置DE信号可以接固定电平或不用。DE模式数据使能模式只在DE拉高时数据线上的RGB信号才有效行场同步信号也能继续发送但屏幕以DE为准。绝大多数4.3寸工业屏默认推荐DE模式逻辑上比SYNC模式好处理因为省掉了消隐期间数据必须为黑这种约束。但有些屏的数据手册给出的示例时序是SYNC模式初始化寄存器里可以切换。我自己在DE模式下驱动800x480分辨率的屏幕时钟、行场、DE四路信号全由FPGA产生代码结构简洁出问题也好定位。1.2 像素时钟、刷新率、时序参数三者怎么配这是个特别容易翻车的环节。很多人拿到屏幕第一件事就是写代码结果图像扭曲、闪烁、上下跳动折腾半天发现是时钟频率不对或者时序参数和屏幕要求不匹配。RGB屏的数据手册里通常会给一个时序表类似下面这样参数含义800x480典型值HPW行同步脉冲宽度1~40 PCLKHBP行后沿1~255 PCLKHFP行前沿1~255 PCLKVPW场同步脉冲宽度1~20 行VBP场后沿1~255 行VFP场前沿1~255 行DCLK像素时钟频率30~33 MHz像素时钟的选取逻辑很直接一帧总行数乘以一行总像素数再乘以刷新率就得出需要的最低DCLK。以800x480为例如果一行总计928个PCLK、一帧总计525行、目标刷新率60Hz那么时钟至少需要928×525×60 ≈ 29.2MHz。数据手册若是给了33MHz的典型值直接用它就好不用自己算得太极限。我当时踩过一个低级坑用AlteraIntel的PLL把50MHz晶振倍频到33MHz但忘了检查PLL输出的占空比规格。后来用示波器看才发现输出正常问题其实出在时序参数配置和初始化顺序上。这块后面在调试章节细说。需要留意的是部分4.3寸屏的兼容性很强30MHz也能出图只是刷新率略降。但有一种情况必须严格按手册带电容触摸的一体式屏幕触摸IC的上电时序如果和显示屏时序错得太离谱会造成触摸失灵这会在第三章展开。2. Verilog时序生成核心代码拆解2.1 用计数器把时序切出来——行场同步发生器时序生成器的本质就是计数器。横向一个计数器数PCLK纵向一个计数器数行数两个计数器配合行场同步脉冲、消隐区间、有效显示区间全都能对应到具体的计数值上。先看行方向的计数器核心代码大致是这样的// 以800x480、33MHz为例 localparam H_TOTAL 928; // 一行总时钟数 HFP HPW HBP 800 localparam H_START 0; // 计数起点 localparam H_SYNC 40; // 同步脉冲宽度 localparam H_BACK 40; // 后沿 localparam H_ACT 800; // 有效像素数 localparam H_FRONT 48; // 前沿 reg [10:0] h_cnt; always (posedge pclk or negedge rst_n) begin if (!rst_n) h_cnt 0; else if (h_cnt H_TOTAL - 1) h_cnt 0; else h_cnt h_cnt 1; end assign hsync (h_cnt H_SYNC) ? 1b0 : 1b1; // 低有效或高有效看手册 assign hde (h_cnt H_SYNC H_BACK) (h_cnt H_SYNC H_BACK H_ACT);行同步脉冲的极性不是随便定的这在数据手册里都有标注。有的屏幕是低有效有的是高有效务必按手册来。HDE信号就是行方向的数据使能它相当于一个精度到像素的窗口信号。再来是场方向localparam V_TOTAL 525; // 总行数 localparam V_SYNC 3; localparam V_BACK 8; localparam V_ACT 480; localparam V_FRONT 34; reg [9:0] v_cnt; always (posedge pclk or negedge rst_n) begin if (!rst_n) v_cnt 0; else if (h_cnt H_TOTAL - 1) begin if (v_cnt V_TOTAL - 1) v_cnt 0; else v_cnt v_cnt 1; end end assign vsync (v_cnt V_SYNC) ? 1b0 : 1b1; assign vde (v_cnt V_SYNC V_BACK) (v_cnt V_SYNC V_BACK V_ACT); assign de hde vde;这样DE信号就是行有效和场有效的逻辑与只有同时在水平和垂直有效区间内数据线上的RGB才对屏幕有意义。这个思路和VGA显示完全一致如果你之前做过VGA彩条实验这里基本上是同级复杂度。但是要提醒一点别像我最初那样把行场计数器和DE信号分开做两个模块再拼起来直接在同一个模块里用HDE和VDE组合直接产生最终DE代码逻辑会更顺综合后时序路径也更短。2.2 打拍与FIFO像素数据流的节奏控制时序信号生成了接下来就是数据通道。这一步很多人想简单了觉得直接把图像数据送到RGB数据线上就行。但实际项目中数据几乎不可能正好和PCLK同拍——要么来自ROM/RAM要么来自摄像头采集模块频率和相位都可能有偏差。最稳妥的做法有两种寄存器打拍对齐数据源的时钟和PCLK同源比如同一PLL输出的不同分频靠打两拍把数据和DE对齐。适合ROM图片、FPGA内部生成的测试图案。异步FIFO跨时钟数据源时钟和PCLK完全异步时用FIFO缓冲读侧用PCLK和DE配合保证像素按节奏输出。适合摄像头、HDMI接收等场景。我这里做了个彩条测试图案采用打拍方式// 测试图案生成8条彩色竖条 reg [23:0] rgb_data; always (posedge pclk or negedge rst_n) begin if (!rst_n) rgb_data 24h000000; else if (de) begin case (h_cnt[9:7]) // 取高3位切成8段 3d0: rgb_data 24hFF0000; // 红 3d1: rgb_data 24h00FF00; // 绿 3d2: rgb_data 24h0000FF; // 蓝 3d3: rgb_data 24hFFFF00; // 黄 3d4: rgb_data 24hFF00FF; // 品红 3d5: rgb_data 24h00FFFF; // 青 3d6: rgb_data 24hFFFFFF; // 白 default: rgb_data 24h000000; endcase end else rgb_data 24h000000; end // 输出打一拍消除组合逻辑的毛刺 reg [23:0] rgb_out; reg de_out, hsync_out, vsync_out; always (posedge pclk or negedge rst_n) begin if (!rst_n) begin rgb_out 24h000000; de_out 1b0; hsync_out 1b1; vsync_out 1b1; end else begin rgb_out rgb_data; de_out de; hsync_out hsync; vsync_out vsync; end end输出打一拍这段代码非常关键。时序信号和RGB数据在组合逻辑里生成后直接驱动外部引脚容易因为布线延迟产生毛刺有些屏幕会间歇性花屏。打一拍后所有信号统一在PCLK上升沿跳变源同步接口的质量会好很多。这也是从能出图到稳定出图之间很重要的一道工序。2.3 完整驱动模块代码把上面的思路组合起来就是一个完整的RGB屏幕驱动模块以800x480 DE模式为例代码放到一个文件里管理module rgb_lcd_ctrl #( parameter H_ACT 800, parameter V_ACT 480, parameter H_SYNC 40, parameter H_BACK 40, parameter H_FRONT 48, parameter V_SYNC 3, parameter V_BACK 8, parameter V_FRONT 34 )( input clk, // 33MHz PCLK input rst_n, output lcd_hs, output lcd_vs, output lcd_de, output [23:0] lcd_rgb, input [23:0] pixel_data_in // 实际图像数据 ); localparam H_TOTAL H_ACT H_SYNC H_BACK H_FRONT; localparam V_TOTAL V_ACT V_SYNC V_BACK V_FRONT; // ... 行场计数器与DE生成逻辑见2.1节 ... // 数据窗口内直接把外部图像数据透传 reg [23:0] rgb_data; always (posedge clk or negedge rst_n) begin if (!rst_n) rgb_data 24h000000; else if (de) rgb_data pixel_data_in; else rgb_data 24h000000; end // 打拍输出 reg [23:0] lcd_rgb_r; reg lcd_de_r, lcd_hs_r, lcd_vs_r; always (posedge clk or negedge rst_n) begin if (!rst_n) begin lcd_rgb_r 24h000000; lcd_de_r 1b0; lcd_hs_r 1b1; lcd_vs_r 1b1; end else begin lcd_rgb_r rgb_data; lcd_de_r de; lcd_hs_r hsync; lcd_vs_r vsync; end end assign lcd_hs lcd_hs_r; assign lcd_vs lcd_vs_r; assign lcd_de lcd_de_r; assign lcd_rgb lcd_rgb_r; endmodule模块的实际使用范围很宽参数化设置在800x480、640x480、480x272这些常见分辨率之间切换只用改参数不需要动逻辑。当初我为了适配不同屏幕把参数都提到顶层直到现在做新项目时拉出来就改省了非常多重复劳动。3. 触摸屏那块玻璃后面藏着的I2C世界3.1 解析坐标数据包FT5206是怎么把手指位置报出来的RGB屏搞定了接下来是触摸部分。4.3寸屏的电容触摸方案最常见的是汇顶的GT911系列、敦泰的FT5x06系列还有一部分用的是台湾奇景的HX8527。这些芯片从协议角度讲并没有本质区别I2C接口、查询寄存器、读坐标逻辑上高度统一。我项目里用的是FT5206拿它举例最合适。FT5206上电后FPGA通过I2C接口定期读取它的坐标寄存器。芯片内部的寄存器映射大致分三块0x00~0x01设备状态和点数寄存器0x00的低4位表示当前触摸点数。0x02~0x06第一个触摸点的X/Y坐标和压力值X坐标12位Y坐标12位分布在连续的字节里。0x08~0x0C第二个触摸点的数据布局和第一个点相同。坐标读取的流程非常固定先写一个寄存器地址0x02然后连续读取5个字节FT5206支持I2C连续读解析出12位X和12位Y坐标。寄存器位域含义0x02[11:8] XH触摸点1 X坐标高4位0x03[7:0] XL触摸点1 X坐标低8位0x04[11:8] YH触摸点1 Y坐标高4位0x05[7:0] YL触摸点1 Y坐标低8位0x06[7:0]触摸点1压力值这会带来一个很隐蔽的坑X坐标不是单独一个寄存器而是横跨0x02和0x03两个字节。你要是只读了0x03的低8位高位永远丢失坐标在屏幕边缘部分会错得离谱。我第一次调试时就吃了这个亏触摸点只在屏幕左半边有反应换到右边直接跳变折腾了一下午发现是高4位被丢掉了。3.2 I2C控制器设计起始、停止、应答FPGA驱动触摸IC的核心是一个可靠的I2C主机控制器。某种程度上可以说读懂I2C时序比读懂触摸屏数据手册更重要——因为FT5206也好、GT911也好外设逻辑都跑在I2C这条总线上。I2C的四个关键时序段起始条件STARTSCL为高时SDA从高拉低。停止条件STOPSCL为高时SDA从低拉高。数据位SCL低电平时SDA变化SCL高电平时SDA必须保持稳定。应答ACK接收方在第9个时钟周期拉低SDA表示成功接收。在FPGA里做I2C控制器两种状态机风格比较主流位状态机每状态处理一个SCL周期内的SDA变化。状态数多但时序控制精确适合高频PCLK。字节状态机每状态处理一个完整字节内部细分位计数。更直观适合50MHz以下的系统时钟。我的FT5206控制器挂在50MHz时钟下走的字节状态机。核心状态跳转localparam IDLE 4d0; localparam START 4d1; localparam SEND_ADDR 4d2; // 写地址或读地址 localparam CHECK_ACK 4d3; localparam SEND_REG 4d4; // 写寄存器地址 localparam CHECK_ACK2 4d5; localparam RESTART 4d6; // 读操作时需要重发起始条件 localparam SEND_RADDR 4d7; // 读地址 localparam CHECK_ACK3 4d8; localparam READ_DATA 4d9; // 连续读 localparam SEND_NACK 4d10; localparam STOP 4d11;读寄存器序列实际上包含两次I2C事务第一次发START→写设备地址→写寄存器地址第二次发RESTART→读设备地址→读数据→发NACK→STOP。这个重复起始条件Restart是I2C协议里特别基础的用法但也是很多新手最容易漏的地方。漏掉RESTART直接发STOP再接START部分触摸IC也能容错工作但行为会变得不可预期时不时的通信超时就成了玄学问题。SDA是开漏输出FPGA侧必须有上拉电阻。很多4.3寸触摸屏模组自带上拉但有些廉价款没有需要自己在PCB上加2.2K~4.7K上拉电阻到3.3V。这个如果你忽略表现就是I2C读写偶尔成功偶尔失败用示波器抓波形发现SDA低电平像斜坡一样缓降。务必记住I2C总线不是靠推挽输出拉高电平的而是靠上拉电阻。3.3 校准不存在的但你得处理坐标映射触摸坐标和屏幕像素坐标多数情况下不是一一对应的。以FT5206为例它输出的坐标范围通常是0~102312位而屏幕分辨率是800x480不做换算的话点哪里都错位。换算公式很直接pixel_x touch_x * 800 / 1024 pixel_y touch_y * 480 / 1024但还有个更隐蔽的问题触摸屏的X轴方向和屏幕显示可能正好相反。我手上的这块屏触摸IC输出的X坐标是按模组的物理方向定义的屏幕显示时是从左到右扫描如果不加处理你点在屏幕左上角系统会认为点在右上角。解决办法就是在代码里加一个方向标志位要么在算坐标时做镜像要么在硬件layout时翻转屏的方向。软件方式好用加两句assign pixel_x (mirror_x) ? (800 - touch_x * 800 / 1024) : (touch_x * 800 / 1024); assign pixel_y (mirror_y) ? (480 - touch_y * 480 / 1024) : (touch_y * 480 / 1024);坐标滤波则是另一个不能忽略的环节。电容屏的原始数据会有抖动尤其是边缘区域和手指按压力度不均时坐标可能在几个像素间跳变。嵌入式Linux里常用中值滤波或滑动平均Verilog里实现滑动平均更简单寄存器组存最近4次X、Y坐标输出取平均值。代价是多几个周期延迟但触摸手感会稳定很多。具体实现有很多方案资源足够的话用移位寄存器加累加器做个4点平均就够用。4. 调试中的那些坑逻辑分析仪教我的事4.1 上电点不亮的常见原因从电源到时序逐一排查RGB屏调试和单片机点灯完全是两个难度。屏幕不亮时问题可能出现在任何一环而你不会立刻知道是哪一环。我把自己排查顺序整理成了固定流程每次遇到新屏都按这个顺序走排查速度快了很多第一步量电源。4.3寸RGB屏模组通常需要3.3V逻辑电源部分带背光的还需要单独的背光LED电源甚至有些模组要求5V。上电后用万用表量屏接口处的电压实际值很多故障其实出在电源适配器压降上——电源标称3.3V接上屏后实际只有2.9VIC就死活不工作。第二步确认背光。背光不亮和屏幕不显示是两回事。如果背光控制引脚常标为BL_EN或LED_EN没有拉高或者PWM调光引脚悬空屏幕看起来就是黑的但这不代表RGB信号有问题。第三步逻辑分析仪抓时序。到现在这一步如果你有逻辑分析仪哪怕是十来块钱的24MHz USB逻辑分析仪别犹豫直接抓HSYNC、VSYNC、DE、PCLK四根线。看它们是否按你代码里设计的周期在翻转。这里有个关键判断点像素时钟要采样得到普通逻辑分析仪带宽有限直接用示波器看更靠谱但HSYNC和DE这些低频信号逻辑分析仪完全够用。如果四根线都有波形屏还是黑屏大概率是背光或者电源问题。如果波形频率不对多半是PLL配置错了回来检查时钟。4.2 显示错位与花屏的波形级分析显示错位这问题表现形式很典型屏幕左移了一截右边多出一条垂直黑带或者画面上下滚动。这通常是时序参数和屏的实际规格不一致导致的。举个例子如果HBP配小了图像会整体左移右侧是黑边如果HFP配小了图像整体右移。这其实代表了消隐区间的分配出了问题。解决办法是静下心来看数据手册把HFP、HPW、HBP、VFP、VPW、VBP都核对一遍尤其是注意单位有些手册写的是像素时钟个数有些写的是基准时钟个数换算错了也会出现类似错位。花屏的情况一般有两种花屏加闪烁像素时钟频率不对或者PCLK的相位/极性配反了。数据手册里会标注PCLK是上升沿采数据还是下降沿采数据如果代码在时钟下降沿打拍而屏幕在上升沿采集显示就会错乱。花屏但画面稳定数据线连接问题居多。RGB888需要24根数据线排线虚焊、杜邦线接触不良、PCB走线串扰都会导致某几位固定错误。这时候代码解决不了问题得用万用表一根一根量通断。排线松了这类看似弱智的问题我至少遇到三次。4.3 触摸乱跳、漂移的处理思路触摸IC调通后真正闹心的是坐标漂移和触摸乱跳。电容触摸乱跳常见原因有三类1. 电源噪声干扰。I2C边上的开关电源纹波大触摸IC参考电压跟着抖报点坐标就乱跳。处理方式是给触摸IC的电源单独加LC滤波避开开关电源辐射区域。2. I2C时序不标准。SCL频率太高或者上升沿过缓触摸IC采样出错报出异常坐标。我在FPGA里做I2C时SCL频率压在100KHz附近逻辑分析仪看波形发现上升沿差不多50ns后来在SDA和SCL上加了上拉电阻后稳定了。3. 触摸IC固件重新握手失败。这个排查起来是最费时间的。某些电容触摸IC在开机时主控需要主动向它发送配置数据如果上电时序不对或者I2C通信失败触摸IC会运行在默认状态表现就是触摸不响应或者报点不对。碰到这种情况我的建议是先断开所有其他I2C设备只留触摸IC然后反复上电看能否恢复。能恢复的话问题大概率在握手时序不能恢复换一颗同型号触摸IC再试排除芯片本身损坏的可能。还有一个很实用的技巧触摸屏模组自带复位引脚RESET/RST的话可以在FPGA初始化时做一个上电延迟复位流程先拉低复位脚保持10ms以上拉高后等待至少50ms再开始I2C通信。这个流程能解决相当一部分摸着摸着突然失灵的疑难杂症。因为触摸IC内部的固件上电后需要一个稳定的供电和复位过程等它完全进入工作状态再通信成功率最高。5. 这套方案能扩展到哪里去RGB屏驱动这事儿最大的价值不在于点亮一块屏而在于打通了一条图像数据通路。很多项目只要换了输入源整块屏的用途就完全不同了。我在项目里接过一个OV5640摄像头模组图像数据从摄像头传感器输出经过色彩插值、格式转换最后填进一个异步FIFO读侧用RGB屏的PCLK和DE信号去取数。改造过程非常直观摄像头模块替代了2.2节的测试图案生成器FIFO桥接了两个不同时钟域其余的行场同步、打拍输出逻辑完全复用。后来又做过一次把图像数据换成HDMI输入的实验也是同一个套路——解码后的RGB数据经异步FIFO再送到屏控模块。整个工程的可复用性非常高因为屏幕驱动模块永远只关心三件事PCLK节奏、DE窗口、RGB数据对齐。至于数据是哪来的它不在乎。如果你接下来想做更复杂的图形界面比如菜单系统、仪表盘、波形显示建议在屏控模块外面套一层简单的总线协议让单片机或软核CPU来写显存。显存用双端口RAM实现一边是CPU写数据另一边是屏控模块按固定节奏读数据互不干扰。这种架构再往上走就是成熟的GPU工作了逻辑雏形理解这一点之后再去学MIPI、LVDS这些接口很多概念都会一通百通。另外我建议你在工程里预留一组调试用的数码管或LED指示灯把触摸状态、显示帧同步状态都拉出来亮灯。FPGA调试最怕的就是黑盒不知道内部跑到哪一步了。多个指示灯调试效率会明显改善。我自己在实际项目中养成的习惯是屏幕驱动模块单独建一个工程目录参数、模块、仿真testbench全部独立管理换项目时直接拷过去只改顶层连线。做了三个项目之后这个模块已经几乎不再改动省下来的时间都花在业务逻辑上了。这个做法比重新从零写一遍驱动值得多。
返回列表