用ZYNQ做VGA显示确实有点大材小用,一般都是直接用VDMA、VTC等IP来驱动HDMI接口,输出图像视频。这里的VGA驱动仅用作练习。

VGA驱动电路

  VGA驱动芯片用的是ADV7123,实际上是三路10bit的DAC。

VGA驱动

  在写驱动之前需要确定具体的时序,可以通过这个网站查到,还可以参考知乎的这篇文章
  在PL的驱动中可以把一些具体的时序参数都提前准备好,然后把这个驱动封装成IP时,可以直接通过下拉菜单选择,就像下面这样。

  实际的设计需求是,图像大小和实际的显示分辨率大小不一致,实际图像的分辨率小于等于屏幕的分辨率,而且屏幕的分辨率是实际图像分辨率的整数倍。一个简单的拉伸图像的方法就是对像素进行复制。比如我想将图像的长和宽都拉伸成原来的两倍,那么就只需要将单个像素在重复显示4次(行列各两次)即可。
  具体实现采用行列两个方向的状态机进行控制,以下是行方向上的状态机:

  以下是列方向上的状态机:

  在代码中还实例化了一块BRAM,用作显存,因此只有在显存初始化完成后,状态机才能真正进入工作。
  列方向的状态机就是单纯地按照时序进行切换。
  行方向上的状态机,还控制了具体的工作模式。

  • 正常模式 下,就是按比例缩放图像,并显示图像。每次HREAD状态从BRAM中读一次数据,一次是32bit,每个像素8bit,所以一次读数据至少能够用于显示4个像素点。再加上有时候图像要放大,一次读的数据可能需要被重复显示。所以后面一个HWTSCL状态等待的时钟周期数可能会更多,这个取决于实际图像和显示分辨率的比例。
  • 测试模式 就是显示不同灰度级的条纹。

  驱动中的模式控制位也是有处理器通过存储器读写特定的bit来实现的。我的做法是,在存储图像数据之后存放模式控制位。比如一幅图像是640×512个字节,在BRAM中的寻址空间就是0~640×512-1,而模式控制位就是地址为640×512处的第0位。处理器只要修改这一位就可以改变它的工作模式。

软件部分

  软件部分比较简单,就是往显存对应的那块地址写事先准备好的图像数据,然后开启GPIO中断。用户在按下按键后可以切换工作模式。

1
2
3
4
5
6
7
8
9
10
11
int main(){
int* p;
p = (int*)0x60000000;
for(int i = 0; i<C_MAX_DW_ADDR; i++) {
Xil_Out32((UINTPTR)(p+i), data[i]);
}

GpioInit(&Intc, &Gpio, GPIO_DEVICE_ID, GPIO_INTERRUPT_ID);

while(1);
}

实际效果

正常工作模式:

测试模式