笔者最近在项目中需要使用FPGA驱动显示HUB75接口的LED模组,但是该模组网上资料很少,而且这种LED模组的扫描方式不是常规的从上到下从左到右。而是先从左到右扫描四个像素点,然后跳到下一行再扫描四个像素点。单板像素地址映射表如图所示
绿色部分为正常扫描顺序,红色部分为LED模组扫描顺序。经过归纳总结后发现满足以下规律:
令输入为自然数序列
1、当输入为4的倍数 输出yt = yt_last + 16(图像宽度)
其中yt_last 等于上次满足输入为4或16的倍数时对应的输出。
2、当输入为16的倍数,时输出 yt = 4*n;
其中n为当前 第n次满足16的倍数
写为C语言如下
void MapOriginalData()
{
unsigned int iMaxData;//最大像素点个数(32bit)
unsigned int OutLast4or16=0;//上次满足4(行扫)或16(行扫*上下屏间隔)倍数的输出
unsigned int iCount16mul=0;//满足16(行扫*上下屏间隔)倍数的次数
unsigned int iMapAddr=0;//映射地址
unsigned int i;
unsigned int j;
iMaxData = MaxWidth * MaxHigh / Parallel;//计算最大数据包数
for (i = 0; i < iMaxData; i++)
{
if (i != 0)
{
if ((i % 16) == 0)//是否是16的倍数
{
iCount16mul++;
iMapAddr = HJNum * iCount16mul;//计算映射地址
OutLast4or16 = iMapAddr;//记录映射地址
}
else if ((i % 4) == 0)//是否是4的倍数
{
iMapAddr = OutLast4or16 + MaxWidth;//计算映射地址
OutLast4or16 = iMapAddr;//记录映射地址
}
else
{
iMapAddr++;
}
}
/*********写入一包数据**********/
for (j = 0; j < Parallel; j++)//Parallel个双字
{
MapData[(i * Parallel) + j] = OriginalData[iMapAddr + (MaxWidth << 2) * j];
}
}
}
PS:这里一次性封装了Parallel行的数据。
使用Verilog语言实现映射如下:
module PixelCtrl(
input clk_50M,
input Rst_n,
input [31:0]Pcount,//第0~N个像素 扫描顺序
output [15:0]PixeData,//输出的像素数据
output [15:0]PixeData_x
);
/*
模块简介:
%解释 令输入为自然数序列
%1、当输入为4的倍数 输出yt = yt_last + 16
%其中yt_last 等于上次满足输入为4或16的倍数时对应的输出。
%2、当输入为16的倍数,时输出 yt = 4*n;
%其中n为当前 第n次满足16的倍数
*/
parameter PixelX_Add = 31'd16; //根据屏幕列数(X坐标最大数)定义的累加系数
//64像素为16 128像素为32 以此类推
parameter PixelSub = 31'd64; //上半个屏幕和下半个屏幕像素差
reg [31:0]LastPcount;//t-1时刻的Pcount的状态
reg [31:0]PixeAddr;//图像像素地址
reg [31:0]Bs16;//16倍数的次数
reg [31:0]yBs4;//t-1时刻4或16倍数对应的输出
always@(posedge clk_50M or negedge Rst_n)//将Pcount映射到PixAddr
if(!Rst_n)
PixeAddr <= 32'd0;
else if(Pcount == 0)
PixeAddr <= 32'd0;
else if(Pcount[0]==0 && Pcount[1]==0 && Pcount[2]==0 && Pcount[3]==0 && LastPcount != Pcount)//是否为16的倍数
PixeAddr <= Bs16<<2;//*4
else if(Pcount[0]==0 && Pcount[1]==0 && LastPcount != Pcount)//是否为4的倍数
PixeAddr <= yBs4 + PixelX_Add;
else if(LastPcount != Pcount)//Pcount是否有变化
PixeAddr <= PixeAddr + 1'd1;
always@(posedge clk_50M or negedge Rst_n)//16倍数次数累计(每次需要清零)
if(!Rst_n)
Bs16 <= 32'd0;
else if(Pcount == 0)//当有新一轮输入时,清空Bs16累计
Bs16<=32'd1;
else if(Pcount[0]==0 && Pcount[1]==0 && Pcount[2]==0 && Pcount[3]==0 && LastPcount != Pcount)//是否为16的倍数
Bs16<=Bs16 + 1'd1;//自增
else
Bs16 <= Bs16;
reg [1:0]State;//状态机用于延迟一个时钟
parameter Wdata_1 =2'b01;
parameter Wdata_2 =2'b10;
always@(posedge clk_50M or negedge Rst_n)//t-1时刻4或16倍数对应的输出
if(!Rst_n)begin
yBs4<=32'd0;
State <= Wdata_1;
end
else if(Pcount[0]==0 && Pcount[1]==0)begin//是否为4或16的倍数
case(State)
Wdata_1:begin
State <= Wdata_2;
end
Wdata_2:begin
yBs4 <= PixeAddr;//保存此时的值
State <= Wdata_1;
end
endcase
end
else begin
yBs4 <= yBs4;
State <= Wdata_1;
end
always@(posedge clk_50M or negedge Rst_n)//输入变化检测
if(!Rst_n)
LastPcount <= 8'd0;
else if(LastPcount != Pcount)//Pcount是否有变化
LastPcount <= Pcount;
//=============================读取图现象数据====================//
//always@(posedge clk_50M or negedge Rst_n)
//if(!Rst_n)
// PixeData <= 15'd0;
//else if(PixeAddr == 0)
// PixeData <= 15'b00000_00000_11111;
//else if(PixeAddr == 1)
// PixeData <= 15'b11111_00000_00000;
//else if(PixeAddr == 125)
// PixeData <= 15'b00000_11111_00000;
//else if(PixeAddr == 126)
// PixeData <= 15'b11111_11111_11111;
//else
// PixeData <= 15'd0;
ROM RomU1(
.address(PixeAddr[6:0]),
.clock(clk_50M),
.q(PixeData)
);
ROM RomU2(
.address(PixeAddr[6:0]+PixelSub),
.clock(clk_50M),
.q(PixeData_x)
);
endmodule
RTL图如图
结尾
此文章为笔者项目中遇到的一些问题笔记,笔者水平有限,如有纰漏敬请指正。