【FPGA的小娱乐】tft显示屏生成信号辅助测试阵列
时间:2023-10-20 12:37:01
目录
前言
tft屏控制
生成测试阵列
其他说明
前言
疫情能把一个人逼到什么程度?甚至让我无聊到四年前。FPGA拿出板子做点事。
我想在一个tft在屏幕上做一个阵列,可以在屏幕上输出一些内部信号,比如状态机的状态,帮助我定位一些后续问题。这个功能有点像状态灯,但是这个板子只有四个状态灯,肯定不够,所以我想做这样的事情。
其实有些内部状态是不需要这个阵列的,用逻辑分析仪应该是比较常见的做法,quartus ii还有内置的逻辑分析仪:
但是在一些简单的场景中,一般看状态就够了,比如信号是0还是1,所以做这个测试阵列挺有意思的。
我用的开发板是小梅哥家的AC620,芯片型号EP4CE10F17C8(N)。
tft屏控制
tft屏幕控制逻辑和VGA逻辑是一样的:
再简单复习一下,tft屏幕提供的用户界面如下:
TP_XXX这些信号不需要注意,显然是一组SPI我不知道如何在屏幕上使用接口。LCD这一组就够了。LCD_XXX信号功能:
LCD_HSYNC | 同步有效信号 |
LCD_VSYNC | 场同步有效信号 |
LCD_DE | 背光可以有效地输出像素点 |
LCD_BL | 背光控制,直接连接到复位信号,即复位后打开背光 |
LCD_CLK | TFT屏像素时钟 |
LCD_Rx | RGB中R的数值 |
LCD_Gx | RGB中G的数值 |
LCD_Bx | RGB中B的数值 |
控制信号太多了,最关键的三个信号是LCD_HSYNC、LCD_VSYNC、LCD_CLK。我用的屏幕是480 * 272像素,因此相应的市场信息如下:
LCD_CLK = 525 * 286 * 60(屏幕刷新率/Hz) = 9009000Hz= 9MHz,因此,模块的控制时钟是9M。板上只有50块M所以要例化一个时钟PLL生成所需的时钟。
所以我最后设计的模块,初始版的接口是这样的:
module tft_ctrl ( input clk, input rst_n, input chan_in, input [24 -1:0]chan_data, output [8-1:0]tft_r, output [8-1:0]tft_g, output [8-1:0]tft_b, output tft_hs, output tft_vs, output tft_pwm, output tft_de, output tft_clk, output [10-1:0]hcount, output [10-1:0]vcount );
hcount和vcount取值范围分别为0~479和0~271是向前级图形生成模块,看当前扫描位置点。然后,前图形生成模块可以根据当前位置决定是否输出。如果您想在此点输出,请输出chan_in置为H,同时给一个{r,g,b}的24bit像素值。例如,前模块是一个方块图形生成器,代码可以这样写:
module square_gen( input [10-1:0]x_point, input [10-1:0]y_point, input [10-1:0]x_size, input [10-1:0]y_size, input [8 -1:0]r, input [8 -1:0]g, input [8 -1:0]b, input [10-1:0]hcount, input [10-1:0]vcount, input power_en, output chan_en, output[24-1:0]chan_data ); wire [10-1:0]x_left = x_point; wire [10-1:0]x_right = (x_point x_size >= 11'd480) ? 10'd480 : x_point x_size; wire [10-1:0]y_top = y_point; wire [10-1:0]y_bottom = (y_point y_size >= 11'd272) ? 10'd272 : y_point y_size; assign chan_en = power_en && (hcount >= x_left) && (hcount < x_right) && (vcount >= y_top) && (vcount < y_bottom); assign chan_data = {r,g,b}; endmodule
对应的图形如下:
基于这种控制,你可以把它放在一起tft_ctrl扩展模块:
module tft_ctrl #( parameter CHAN_NUM = 1 )( input clk, input rst_n, input [CHAN_NUM -1:0]chan_in, input [CHAN_NUM*24 -1:0]chan_data, output [8-1:0]tft_r, output [8-1:0]tft_g, output [8-1:0]tft_b, output tft_hs, output tft_vs, output tft_pwm, output tft_de, output tft_clk, output [10-1:0]hcount, output [10-1:0]vcount );
允许大量输入chan_in,哪个bit如果有效,输出相应的颜色,以实现多个色块的输出。因此,这样的模块可以用作一个通用接口。只要把它放在前面CHAN_NUM给对,一切都好说。
而且这样做接口的好处是可以简单判断两个物体色块的碰撞。我以前在那里FPGA上做碰撞球、小鸟跑酷包括课设跟大佬做俄罗斯方块游戏,里面都涉及到物体碰撞的事:
然而,我当时并没有想到通过这个chan_in来做判断(主要原因我对外提供的接口不是这样的):
OK,我把具体的代码放在那里work目录的src文件被夹下,这里就不赘述了。
生成测试阵列
我在square_gen该模块提供了一个接口power_en,该信号直接作用于chan_en也就是说,如果power_en如果是0,即使是要输出的色块也不会输出。因此,待测信号可以将值连接到该接口,并通过屏幕观察当前值。
在定层的work(具体文件在工程中),tft_ctrl例化如下:
wire [16*8 -1:0]chan_in; wire [16*8*24 -1:0]chan_data; tft_ctrl #(.CHAN_NUM(16*8)) u_tft_ctrl( .clk (clk), .rst_n (rst_n), .chan_in(chan_in), .chan_data(can_data),
.tft_r (tft_r),
.tft_g (tft_g),
.tft_b (tft_b),
.tft_hs (tft_hs),
.tft_vs (tft_vs),
.tft_pwm(tft_pwm),
.tft_de (tft_de),
.tft_clk(tft_clk),
.hcount (hcount),
.vcount (vcount)
);
目的就是做8行16列的测试阵列。
阵列生成模块的例化使用generate完成:
reg[16*8 -1:0]power;
genvar i;
genvar j;
generate
for(i=0;i<8;i=i+1)begin:GEN0
for(j=0;j<16;j=j+1)begin:GEN1
square_gen u_squ(
.x_point (20*(j+1)),
.y_point (20*(i+1)),
.x_size (10),
.y_size (10),
.r (8'b10101010),
.g (8'b10101010),
.b (8'b10101010),
.hcount (hcount),
.vcount (vcount),
.power_en (power[i*16+j]),
.chan_en (chan_in[i*16+j]),
.chan_data (chan_data[(i*16+j)*24 +:24])
);
end
end
endgenerate
对外提供了power信号作为控制端。比如我有一个阵列键盘的控制模块,里面有很多控制信号,行为一直不对的话我就可以这样连接:
always @*begin
power = {128{1'b1}};
power[0*16 +: 16] = 16'hffff;//第一行全开,作为参考行
power[1*16 +: 16] = key;
power[2*16 +: 4] = row_i;
power[3*16 +: 4] = col_o;
power[4*16 +: 4] = row_stable;
power[4*16 +: 4] = row_change;
power[5*16 +: 4] = state_out;
end
最后看到的结果就是开头的那个图了:
其他说明
工程的路径:
链接:https://pan.baidu.com/s/1RE7caFvP-fKDPkwZv1B3yw
提取码:2ooo
工程中使用了PLL,50M to 9M,在工程中创建即可,我太久没用quartus ii了,找了半天一直跟Qsys那较劲:
使用了以下管脚,具体的摆放在工程里有:
没有做时序约束,所以可以看编译区对应时序分析是红的,9M时钟的一个系统,我觉得不做也无所谓,没有啥违规的风险:
具体TFT的管脚对应关系,我习惯写一个.v文件,在.v里做分配,先对齐到FPGA的输出,再对到TFT屏幕的管脚:
//assign PIN_F16 = rst_n;//TFT_RESET
assign PIN_F13 = 1'b0;//TFT TS_PEN, SPI
assign PIN_G15 = 1'b0;//TFT TS_CLK, SPI
assign PIN_F15 = 1'b0;//TFT TS_MISO, SPI
assign PIN_G11 = 1'b0;//TFT TS_MOSI, SPI
assign PIN_F14 = 1'b0;//TFT TS CS, SPI
assign PIN_J12 = tft_pwm;
assign PIN_J11 = tft_de;
assign PIN_J14 = tft_vs;
assign PIN_K11 = tft_hs;
assign PIN_J15 = tft_clk;
assign PIN_J16 = tft_b[7];
assign PIN_K15 = tft_b[6];
assign PIN_K16 = tft_b[5];
assign PIN_J13 = tft_b[4];
assign PIN_L15 = tft_b[3];
assign PIN_L12 = tft_g[7];
assign PIN_K12 = tft_g[6];
assign PIN_L13 = tft_g[5];
assign PIN_M12 = tft_g[4];
assign PIN_L14 = tft_g[3];
assign PIN_N16 = tft_g[2];
assign PIN_P16 = tft_r[7];
assign PIN_N15 = tft_r[7];
assign PIN_R16 = tft_r[6];
assign PIN_P15 = tft_r[4];
assign PIN_N14 = 1'b0;//TFT NC
assign PIN_N13 = tft_r[3];