ADV模拟图像实现,最难的是实现ADV数据结构和跨时钟域处理。数据结构需要严格遵循协议去写各个位置的非图像数据,例如EAV,SAV等等。数据跨时钟要最要注意两边图像帧频是否一致,FIFO大小是否满足要求。
本文讨论的项目中使用ADV7391的PAL Square模式把20M时钟50Hz帧频的640*512数字视频转换为模拟视频。(注:本文不详细说明配置ADV7391,配置在软核中完成)
1.ADV分析
PAL Square图像区为768*576,隔行扫描(I),帧频为25Hz,时钟为29.5M时钟。原图像隔行扫描,一帧取一半,采用奇帧偶行偶帧奇行的方式。
50Hz的图像帧频,图像一帧为20ms,PAL Square29.5M一行1888个数据,时长为64us,可算得一共312.5行,当然ADV不存在半行,只要保证两帧图像平均帧率符合25Hz即可,例如奇帧313行偶帧312行。
现在可以设计ADV的结构了:
313/312行可以划分为三部分:PRE_INTEG INTEG IDLE 三部分 分别为22行 288行 3/2行。288行为数据区。解释一下768*576,576行为两帧拼起来的288*2,每帧图像仅为256行(512隔行扫描),剩余32行可作为消隐,前后各16行,数据区每行768个数据,此处采用YCrCb协议,灰度图像中Y为真实数据,CrCb无数据,故真实数据为50%,768*2 = 1536,真实数据每行640, 640*2=1280,其余区域256列为消隐,左右各128列。
PAL制具体数据模式如下:
EAV和SAV为嵌入式控制字,分别表示有效视频的终点和起点。EAV和SAV均为4个字节构成,前3个字节FF、00、00为固定头,“XY”为控制字。“XY”的8个bit含义如下:
Bit7(Const):常数,总为1。 Bit6(F):场同步信号,表示该行数据处于奇场还是偶场。 Bit5(V):垂直同步信号,表示处于场消隐区间还是正程区间(有效数据行)。 Bit4(H):水平同步信号,表示是“SAV”还是“EAV”。 Bit3 :纠错位 B3 = V (XOR) H Bit2 :纠错位 B2 = F (XOR) H Bit1 :纠错位 B1 = F (XOR) V Bit0 :纠错位 B0 = F (XOR) V (XOR) H
解析出所有的数据,写出adv具体代码,如下:
begin
if(count_x==0)
begin
data_outbuf<=8'hff;
HS<=0;
end
else if(count_x<=2)
begin
data_outbuf<=8'h00;
HS<=1;
end
else if(count_x==3)
begin
HS<=HS;
if(odd_even_flag==1)
begin
case(state)
PRE_INTEG:data_outbuf<=8'hb6;
INTEG: data_outbuf<=8'h9d;
IDLE: data_outbuf<=8'hb6;
endcase
end
else
begin
case(state)
PRE_INTEG:data_outbuf<=8'hf1;
INTEG: data_outbuf<=8'hda;
IDLE: data_outbuf<=8'hf1;
endcase
end
end
else if(count_x<=347)
begin
HS<=HS;
data_outbuf<=(count_x%2)?8'h10:8'h80;
end
else if(count_x==348)
begin
HS<=HS;
data_outbuf<=8'hff;
end
else if(count_x<=350)
begin
HS<=HS;
data_outbuf<='h00;
end
else if(count_x==351)
begin
HS<=HS;
if(odd_even_flag==1)
begin
case(state)
PRE_INTEG:data_outbuf<=8'hab;
INTEG: data_outbuf<=8'h80;
IDLE: data_outbuf<=8'hab;
endcase
end
else
begin
case(state)
PRE_INTEG:data_outbuf<=8'hec;
INTEG: data_outbuf<=8'hc7;
IDLE: data_outbuf<=8'hec;
endcase
end
end
else if(count_x<=1888-1)
begin
HS<=HS;
case(state)
PRE_INTEG:data_outbuf<=(count_x%2)?8'h10:8'h80;
INTEG:
if((count_x>=480&&count_x<=1888-1-32*4)&&(count_y>=0+16&&count_y<=287-16))
begin
if(count_x[1:0]==2'b00)
begin
data_outbuf<=8'h80;//Cb
Rd_req<=1;
end
else if(count_x[1:0]==2'b10)
begin
data_outbuf<=8'h80;//Cr
Rd_req<=1;
end
else
begin
data_outbuf<=datain;
Rd_req<=0;
end
end
else
begin
Rd_req<=0;
case(state_tmp)
Cb:
begin
data_outbuf<=8'h80;
state_tmp <= Y1;
end
Y1:
begin
data_outbuf<=8'h00;
state_tmp <= Cr;
end
Cr:
begin
data_outbuf<=8'h80;
state_tmp <= Y2;
end
Y2:
begin
data_outbuf<=8'h00;
state_tmp <= Cb;
end
endcase
end
IDLE: data_outbuf<=(count_x%2)?8'h10:8'h80;
endcase
end
end
count_x,count_y分别为列行计数,在三个区域分别计数,区域间无间隔。实现代码简单,此处不贴。
2.跨时钟域分析
20M时钟的图像转换为29.5M时钟的ADV数据。原始图像为640*512,具体如图。
此前已说明每两帧图像的平均帧率是相匹配的,在数据有效时,把奇帧偶行,偶帧奇行写入FIFO等待,ADV读出。
FIFOwr_en = (((~OEFlag)&RowNum[0])|((OEFlag&(~RowNum[0]))))? iDataValid : 0;//写使能
OEFlag标志奇偶帧,帧有效上升沿取反,RowNum为行计数,最低位可标志奇偶行。
way1.
ADV在读出FIFO之前需要经过22行消隐和16行黑色边框,38*64000/50=48640clk(换算为20M时钟),原始数据此时需要存在FIFO中,48640/645=75.4 38*640=24320 通过计算48640clk最多存放24320个数据,需要官方ip核最小为32768,消耗资源较大。
way2.考虑另一种方式:舍弃第一帧,可以大大节约资源。
第一帧图像写入FIFO,FIFO会写满,直到原始图像下一帧到来,frame_valid的上升沿作为FIFO清空信号。由于第一帧没有读出,写入的数据会被清除。
原始数据一共需要645*512*50ns = 16512000ns,ADV数据区256*64000ns = 16384000ns 可见ADV速度略快,这就要求FIFO中需要预存一定量的数据,以免FIFO发生读空。
16512000 - 16384000 = 128000ns 2560clk(20M)
取整,3000clk,48640 + 3000 = 45640 399999 - 45640 = 354359
在原始图像上循环计数,frame_valid开始从0到399999(每帧400000clk),在第一次计数达到354359 时对ADV模块复位,可以保证图像不会读空,不会写满。开始产生第一个数据,经过38行过后开始读FIFO,计数在353000的时候原始数据已经结束,注意此时原始帧已经进入消隐,数据个数为640*512=327680 < 354359。
ADV读出之前,原始图像已写入3000clk,3000/645= 4.65,最多存入2.65行按3行算即 640*3= 1920个数据,此时需要FIFO最小取4096。
可见FIFO大大缩小,节约大量资源。
3.后续问题
注意两点
第一,产生29.5M时钟的pll不可以与其他时钟同pll,尤其20M,同一pll的输出时钟最好是相互之间成倍数关系,不然生成的时钟不准确,就会造成两边帧率不统一,以上计算都是严格按照准确时钟计算的,如果出现时钟不准确,必然导致图像不同步然后移动。
第二,也是时钟问题,一般情况下不出现,时钟不同源的问题。例如原始图像以及其时钟是由此前的硬件中传输过来的,而ADV29.5M时钟是在此块fpga上生成的,这样的时钟也必然会导致图像同步失败产生移动,因为非同源时钟有误差。我在设计中遇到过这个问题。
此时的解决办法是人为给出同步信号,承接2中的分析,在计数为354359时,复位ADV行列计数清零,虽然只复位一次,由于帧频一致,每次计数到354359时,ADV的行列计数都归零了,标志ADV新的一帧开始,然而时钟如果有问题,此时ADV所处的位置就会超前或落后。每两帧在计数354359处复位ADV就不会出现未同步的问题,注意不可以每帧都复位,因为ADV奇偶帧长度不一,此前已说过,当然平均帧频与原始帧是一致的。
感谢阅读 😁
o_e o_e o_e