在生成VDMA,GPIO,IIC后,会生成对于的bsp板级包。这里我不打算进行深入学习(不会尝试自己去写这份ps代码),要求能较为深入理解ps工作的原理即可,依旧不去细看HDMI模块相关。
首先导入了几个库,然后定义了几个宏,这里有一个要注意的,define这里有的根据英文意思就能知道分别对应什么功能,但是#define VGA_VDMA_ID XPAR_AXIVDMA_0_DEVICE_ID中,需要看自己生成的VDMA(传给HDMI的通道)是编号0还是编号1,这里VGA实际上指的是HDMI,要把编号对上。下面四个define并没有被引用,因此作用我没去探究。
接下来就是几个结构体变量的声明,
基本都是类似这样,这个指针指向的还有点泛,结构体下还有结构体变量,但是大致意思能知道就行。都有英文解释的。然后就是声明两个数组,第一个是用于存储三个帧图像,第二是指针数组变量。
简单说一下这部分,DISPLAY_NUM_FRAMES是3,意味着有三帧缓冲,而由于我c语言很多都忘记了,所以这里插入两道指针题目:
这是利用指针修改数据
这是指针数组打印,值得一提的是,指针数组在作为参数引入函数时,不需要&符号,就会传递指向第一行的地址,然后函数接收时,由于接收来自第一行的指针变量,但是实际上数组每行有三个元素,因此传递时还是需要[3]作为后缀。理解不了也没事不影响。
然后pFrames[i] = frameBuf[i];相当于指向第i行的数据被赋值为右边的数组,这里也需要注意,等号右边的数组是不需要解引用*的。然后memset就是【指向的基地址,赋值,大小】,所以这样看会发现这三帧图像的数据都被设置为0。接下来是DCacheFlush,由于DDR3内存量过大,因此CPU一般访问DDR3时,会顺手把这个数据放在Cache上,缓存离CPU比较近,所以访问快,这是一个提高效率的方法,如果CPU要写入什么数据,也会先把数据写到缓存,然后后面如果CPU对同个地址再次写入,就会把原来的数据挤压到DDR3里面,然后缓存就存储新数据。这是一种常见的加速,但是会导致如果其他主机访问DDR3的数据,就可能这个数据还在缓存上没更新,所以这里需要缓存冲刷,将缓存上的数据更新到DDR3上,因为DDR3要被VDMA读取。
i2c_init(&ps_i2c0, XPAR_XIICPS_0_DEVICE_ID,40000);我觉得应该和串口很像,串口的话有一个Tx_BUF,往这个寄存器写入数据串口就会以串口协议发送这个1字节的数据,因此协议是硬件上实现的,然后我看了IIC.c和.h文件,发现也都是往寄存器写入数据的函数或者读取,所以我猜对了。但是这里是初始化,和配置iic还不一样。我们深入看一下代码:
主函数传入了一个空的iic结构体,ps端生成的iic0的ID,以及40000。可以发现该函数借助这个ID找到了iic0的配置这一结构体(Lookup函数)
可以发现这个函数只是简单的返回一下iic的配置config。然后就进入XIicPs_CfgInitialize(Iic, Config, Config->BaseAddress);函数了,点进去稍微看一下,不难发现它在把config的各个变量全部交给iic,可是由于iic的变量名和config不一样,所以更细节的东西不了解,只是知道config把它的配置,基地址啥的全部告诉了iic指针。相当于说,这个函数让iic指针成为我们IIC0设备的新词根我们可以直接拿来用它内部的结构体。
接下来就是setclk,这个姑且理解为设置为40KHz时钟吧(由传入参数决定),然后就是wait到iic空闲即可返回。
接下来是gpio,由前面的能知道,这里的初始化,实际上是将设备名称ID的结构体往新建的指针变量上代入,所以我们只要知道后面根据这个新结构体指针cmos_rstn就能控制这个gpio就行。
由于gpio是inout通道,要先配置好,XGpio_SetDataDirection(&cmos_rstn, 1, 0x0); 是往一号通道写入0,gpio可以设置为双通道,所以用1,2来编号,0是输出,1是输入。后面就是简单的往1号通道写入1,然后等一会,写入0,等一会,写入1开启cmos即可。
sensor_init(&ps_i2c0);由于我们前面知道i2c_init函数以及将XPAR_XIICPS_0_DEVICE_ID对应的各参数给了ps_i2c0结构体指针,并且配置了时钟频率为40KHz,这里比较简单,直接看
sensor_init就是往iic指针里面的各个地址写入各个数据来配置cmos,看一下他们的递进关系从底层开始:iic_reg16_write->ov5640_write->sensor_write_array->sensor_init
传入一个指针结构体,iic的地址,以及iic内部寄存器的地址和要写入的数据。
ov5640_write只是简单调用一下,不过iic地址写为3c这里我也不理解,可能在数据手册里,等我后面看看。然后就是sensor_write_array函数,也都比较简单,顶层也只是调用这个函数,看一下知道怎么用结构体指针实现即可。
vdmaConfig = XAxiVdma_LookupConfig(VGA_VDMA_ID);这个更是演都不演了,直接在主函数调用,就是找到VGA对应VDMA的配置然后传给vdmaconfig,XAxiVdma_CfgInitialize(&vdma, vdmaConfig, vdmaConfig->BaseAddress);也是一样,把这个vdma的所有信息全部传给vdma指针。
Status = DisplayInitialize(&dispCtrl, &vdma, DISP_VTC_ID, DYNCLK_BASEADDR,pFrames, DEMO_STRIDE);这个的话就只是把对应的信息传给dispctrl指针结构体而已。
就只是把这些信息配置好而已。为HDMI显示部分不做赘述。只知道是用来配置vtc,mmcm,对应vdma的即可。
然后就是DisplayStart(&dispCtrl);是为了让他们协调工作把视频流输出。
memset(dispCtrl.framePtr[dispCtrl.curFrame], 0, 1920 * 1080 * 3);这里发现将dispctrl(HDMI显示相关)的帧指针下的当前指针全部清0了,应该是再加一层保障吧,由于前面清0过了我觉得无所谓这个函数。
最后开始开启视频流→DDR3的VDMA:vdma_write_init(XPAR_AXIVDMA_1_DEVICE_ID,1280 * 3,720,1280 * 3,(unsigned int)dispCtrl.framePtr[dispCtrl.curFrame]);这个就是上一节提到的,把这些参数给VDMA它会更好的布局然后传给DDR3至于为什么要乘3,是因为3字节。配置的东西,直接调用即可,然后你会发现这里好像并没有给VDMA配置3个缓冲帧,可以初步判断应该是在硬件上去配置,就是在VDMA上设置三个缓冲帧(两个都要),然后记得初始化3个帧的空间即可。
再接再厉!