Smart ZYNQ主板上自带一块1.47寸彩色显示屏,分辨率是320X172,本文将通过ZYNQ的PS资源去点亮这个液晶屏幕,并显示彩色条纹
1.硬件介绍
LCD 接口如下图所示
![](http://www.hellofpga.com/wp-content/uploads/2023/01/image-87.png)
LCD_BLK:LCD背光(默认常亮)
SPI 信号: CS 片选信号 SCL 时钟 SDA 数据
DC :数据/命令 信号
RES : 屏幕复位 信号
管脚定义:CS H18 ,D/C J15 ,SCL H17 ,SDA F16 ,RES G14 ,LCD_BLK MIO27(LCD_BLK默认不去控制 背光也会默认常亮输出)
除了BLK背光控制信号接在PS端,其余信号都是接在ZYNQ的PL端(BLK信号硬件电路上默认使能,PS不配置BLK的情况下 ZYNQ的屏幕背光仍然常亮)
彩色液晶屏资料(厂家提供的资料) 店铺 中景园电子
链接:https://pan.baidu.com/s/110uz88iQMA7wl9vdqG9R9A 提取码:8888
2.驱动思路
屏幕本身是连接到ZYNQ的PL资源上,点亮屏幕可以用纯fpga去驱动SPI的方式点亮,也可以用PS资源去点亮,为了方便演示,这里采用PS资源去实现
通过EMIO的方式 将PS部分硬件SPI资源映射到 ZYNQ的PL口上去点亮屏幕
3.创建工程
1)新建一个项目,芯片型号选择按下面选择(板子生产了多个芯片型号,请根据自己板子的型号来进行选择)
- XA7Z020 (直接选择xc7z020CLG400-1,因为系统识别到的是xc7z020-1)
- XC7Z010-1 (xc7z010CLG400-1) (7010 芯片)
- XC7Z020-1 (xc7z020CLG400-1) 后续生产
- XC7Z020-2 (xc7z020CLG400-2) 后续生产
备注 (7020 车规级版在vivado2018.3识别出的是XC7Z020,如果工程选择xa7z020 ,在SDK环境下有时候会弹出警告,所以这里直接选择XC7Z020CLG400-1)
2) 创建一个BLOCK设计,并添加ZYNQ7 PROCESSING SYSTEM模块,软件自动生成了一个 zynq的block 如下图所示,接下来要做一些相应的设置,双击下图中的ZYNQ核
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-3.png)
3)在ZYNQ中设置时钟功能:
找到 设置项目中的 Clock Configuration 选项, 在PL Fabric Clocks 设置自己需要的时钟频率,这里一共有4种频率可以设置 类似于我们的PLL功能。这里我们设置50M时钟
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-50.png)
4)在zynq中设置DDR功能:
依次在弹窗里找到DDR Configuration→DDR Controller Configuration→DDR3,在Memory Part下拉菜单中根据自己板子上的DDR来选择相应的DDR3,本实验所用到型号:MT41K256M16RE-125,数据位宽选择16bit 最后点击“OK”,如下图所示。
![](http://www.hellofpga.com/wp-content/uploads/2023/01/image-70.png)
5)在ZYNQ中配置SPI功能
因为屏幕的接口是连接到ZYNQ的PL口上,所以这里我们调用EMIO的SPI功能(即使用PS的SPI功能,但是内部连接映射到PL的GPIO口上)
MIO Configuration→SPI 0勾上 并选择EMIO
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-60.png)
6)由于屏幕的驱动除了SPI外,还需要2个额外的GPIO口来分别控制复位RES和 D/C信号,所以这里增加两个GPIO口,这里GPIO资源选择EMIO GPIO 位宽Width选择2(因为是2个IO口)
5)完成上述操作后, 点击“Run Block Automation”如下图所示。在弹出的选项中保持默认,点击“OK”,即可完成对ZYNQ7 Processing System的配置,并用鼠标连接FCLK_CLK和 M_AXI_GP0_ACLK,得到下图
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-64.png)
在上图中分别点击IO口进行以下操作:
右键GPIO_0选择Make External
右键SPI_0 选择Make External
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-65.png)
6)source→Design Source ,右键我们创建的BLOCK工程,点击create HDL wrapper,打包BLOCK文件并生成.v代码
这时候我们可以看到软件的TOP层 的verilog文件,打开后观察管脚信号,如下图
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-66.png)
圈内的几个管脚是我们之前定义的SPI 管脚,和GPIO管脚, 其中
其中spi_0_io0为 SPI的MOSI信号, spi_0_io1_io 为MISO信号 ,SCK 为SPI时钟信号, SS,SS1,SS2分别为三组SPI 的CS信号
GPIO_0_0为刚才定义的三个GPIO (控制屏幕的背光, 复位 和D/C用)
7) 因为我们的屏幕只用到了一组CS ,所以我们删除多余的CS
屏幕实际只用到了 SPI的数据SDA和时钟SCL和CS三个信号, 所以SPI 我们只需要引出 MOSI (SDA) 和 SCK(SCL),和CS(SS)即可
删除如下框图中的信号的位置
![](http://www.hellofpga.com/wp-content/uploads/2022/04/image-2.png)
同样也需要删除内部 buf(否则 外部管脚不调用会报错)
![](http://www.hellofpga.com/wp-content/uploads/2022/04/image-4.png)
8) 点击绿色箭头RUN 对代码进行编译
9) 点击RTL 中的SCHEMATIC , 并选择右边出现的 IO Ports 来增加SPI的管脚定义(这一步也可以在约束文件中定义, 可看之前的例子)
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-69-1024x411.png)
修改GPIO 管脚定义 和SPI管脚定义,如下图所示 ,修改后保存(如弹出 窗口需要,则在窗口中输入约束文件名,然后保存)
![](http://www.hellofpga.com/wp-content/uploads/2023/01/image-84-1024x536.png)
CS H18 D/C J15 SCL H17 SDA F16 RES G14
10) 生成bit文件 :按下Generate Bitstream 完成综合以及生成bit文件
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-18.png)
5.SDK程序编写
1)File→Export→Export hardware…,在弹出的对话框中勾选“include bitstream”,点击“OK”确认,如下图所示。
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-20.png)
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-24.png)
2)File→Lauch SDK,在弹出的对话框中,保存默认,点击“OK”,如下图所示。
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-21.png)
系统将自动打开SDK开发环境
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-22.png)
3)新建一个工程 file→new→Application Project,来新建一个“Application Project”,如下图所示。
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-23.png)
4)在新建工程名中输入自己的工程名称,点击NEXT
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-75.png)
5)选择一个空工程,点击完成
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-73.png)
6) 在空工程中创建我们自己的代码
展开我们创建的工程,在src目录上右键,选择New->Source File,如下图所示:
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-76.png)
在弹出的窗口中创建一个main.c文件
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-77.png)
6)书写自己的代码
6.1小贴士:
在初次写PS部分的代码时,如GPIO代码 和SPI代码,不知道怎么写的情况下,其实可以通过打开 BSP工程下的 system.mss文件, 然后在右边导入需要的参考例程, 然后将例程中自己需要的部分(如GPIO初始化,或者SPI的写和读代码COPY到自己工程的main函数中)
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-78-1024x528.png)
6.2定义GPIO部分
下面进入正题, 我们要点亮屏幕, 屏幕这里用了3个GPIO 都是用了ZYNQ PS端的EMIO资源, EMIO的GPIO资源是从54开始的, 所以我们根据FPGA管脚约束中的 GPIO 0-3 分别对应 EMIO的54-55-56,如下图对GPIO进行定义
#define EMIO_LCD_CD 54 #define EMIO_LCD_RES 55
接下来 简单介绍下GPIO的调用
a. GPIO的初始化
如下图所示,需要先定义一个XgpioPs 的结构体指针, 这个指针的内容包括gpio分配的ID和基地址,这些信息通常在xparameter.h头文件中, 可以通过使用XGpioPs_LookupConfig函数,它能够在配置信息中找到对应ID的配置信息
ConfigPtr=XGpioPs_LookupConfig(GPIO_DEVICE_ID); 就相当于把获得到的信息赋予给之前定义的ConfigPtr结构体
最终再用 XGpioPs_CfgInitialize 函数来初始化GPIO
void Lcd_Gpio_Init(void){ XGpioPs_Config *ConfigPtr; ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID); XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_CD, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_CD, 1); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_RES, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_RES, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 0); XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 0); }
b.GPIO的使用 (EMIO_LCD_RES为之前定义的 EMIO 54 的管脚)
XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_RES, 1);//设置成输出模式
XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_RES, 1);//打开输出使能
XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 0);// 0置低
XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 1);// 1拉高
6.3 SPI部分的定义 ,和GPIO类似 SPI也有初始化和使用两部分
a.SPI的初始化 和GPIO类似,也是定义了一个结构体,也同样通过 LookupConfig 来获得对应的硬件配置信息, 最终用 XSpiPs_CfgInitialize来完成初始化,值得注意的是程序当中 XSpiPs_SetClkPrescaler 函数是用来为SPI功能提供主时钟的分频用的,可以根据自己需要的速率选择分频参数
void Lcd_Spi_Init(){ XSpiPs_Config *SpiConfig; SpiConfig = XSpiPs_LookupConfig(SPI_DEVICE_ID); XSpiPs_CfgInitialize(&SpiInstance, SpiConfig, SpiConfig->BaseAddress); XSpiPs_SetOptions(&SpiInstance, XSPIPS_MASTER_OPTION | XSPIPS_FORCE_SSELECT_OPTION); XSpiPs_SetClkPrescaler(&SpiInstance,XSPIPS_CLK_PRESCALE_128); }
b.传输 传输是用 XSpiPs_PolledTransfer 函数进行的, 其中 SendBufPtr 是发送的数据, RecvBufPtr 是接受的数据, ByteCount 可以定义我们的SPI 是8位 16位或者32位,下面是这个函数的定义
s32 XSpiPs_PolledTransfer(XSpiPs *InstancePtr, u8 *SendBufPtr,
u8 *RecvBufPtr, u32 ByteCount)
7)上面简单介绍了PS部分的GPIO和,SPI功能,接下来在main.c中 完善屏幕的代码, 将下面的完整代码复制到工程中
#include "xparameters.h" #include "xgpiops.h" #include "xstatus.h" #include "xplatform_info.h" #include "xspips.h" #include <xil_printf.h> #define WHITE 0xFFFF #define BLACK 0x0000 #define BLUE 0x001F #define BRED 0XF81F #define GRED 0XFFE0 #define GBLUE 0X07FF #define RED 0xF800 #define MAGENTA 0xF81F #define GREEN 0x07E0 #define CYAN 0x7FFF #define YELLOW 0xFFE0 #define BROWN 0XBC40 #define BRRED 0XFC07 #define GRAY 0X8430 #define EMIO_LCD_CD 54 #define EMIO_LCD_RES 55 #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID #define SPI_DEVICE_ID XPAR_XSPIPS_0_DEVICE_ID XGpioPs Gpio; /* The driver instance for GPIO Device. */ static XSpiPs SpiInstance; void Lcd_Gpio_Init(void){ XGpioPs_Config *ConfigPtr; ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID); XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_CD, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_CD, 1); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_RES, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_RES, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 0); XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 0); } void Lcd_Spi_Init(){ XSpiPs_Config *SpiConfig; SpiConfig = XSpiPs_LookupConfig(SPI_DEVICE_ID); XSpiPs_CfgInitialize(&SpiInstance, SpiConfig, SpiConfig->BaseAddress); XSpiPs_SetOptions(&SpiInstance, XSPIPS_MASTER_OPTION | XSPIPS_FORCE_SSELECT_OPTION); XSpiPs_SetClkPrescaler(&SpiInstance, XSPIPS_CLK_PRESCALE_64); } void delay(unsigned char i){ volatile int Delay; volatile int k; for(k=0;k<i;k++) for (Delay = 0; Delay < 10000; Delay++); } void LCD_WR_DATA8(u8 dat){ XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 1); XSpiPs_PolledTransfer(&SpiInstance, &dat, NULL, 1); } void LCD_WR_REG(u8 dat){ XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 0); XSpiPs_PolledTransfer(&SpiInstance, &dat, NULL, 1); } #define USE_HORIZONTAL 2 void Lcd_Init(void){ XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 0); delay(10); XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 1); delay(20); LCD_WR_REG(0x11); delay(20); LCD_WR_REG(0x36); if(USE_HORIZONTAL==0)LCD_WR_DATA8(0x00); else if(USE_HORIZONTAL==1)LCD_WR_DATA8(0xC0); else if(USE_HORIZONTAL==2)LCD_WR_DATA8(0x70); else LCD_WR_DATA8(0xA0); LCD_WR_REG(0x3A); LCD_WR_DATA8(0x05); LCD_WR_REG(0xB2); LCD_WR_DATA8(0x0C); LCD_WR_DATA8(0x0C); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x33); LCD_WR_REG(0xB7); LCD_WR_DATA8(0x35); LCD_WR_REG(0xBB); LCD_WR_DATA8(0x35); LCD_WR_REG(0xC0); LCD_WR_DATA8(0x2C); LCD_WR_REG(0xC2); LCD_WR_DATA8(0x01); LCD_WR_REG(0xC3); LCD_WR_DATA8(0x13); LCD_WR_REG(0xC4); LCD_WR_DATA8(0x20); LCD_WR_REG(0xC6); LCD_WR_DATA8(0x0F); LCD_WR_REG(0xD0); LCD_WR_DATA8(0xA4); LCD_WR_DATA8(0xA1); LCD_WR_REG(0xD6); LCD_WR_DATA8(0xA1); LCD_WR_REG(0xE0); LCD_WR_DATA8(0xF0); LCD_WR_DATA8(0x00); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x05); LCD_WR_DATA8(0x29); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x3E); LCD_WR_DATA8(0x38); LCD_WR_DATA8(0x12); LCD_WR_DATA8(0x12); LCD_WR_DATA8(0x28); LCD_WR_DATA8(0x30); LCD_WR_REG(0xE1); LCD_WR_DATA8(0xF0); LCD_WR_DATA8(0x07); LCD_WR_DATA8(0x0A); LCD_WR_DATA8(0x0D); LCD_WR_DATA8(0x0B); LCD_WR_DATA8(0x07); LCD_WR_DATA8(0x28); LCD_WR_DATA8(0x33); LCD_WR_DATA8(0x3E); LCD_WR_DATA8(0x36); LCD_WR_DATA8(0x14); LCD_WR_DATA8(0x14); LCD_WR_DATA8(0x29); LCD_WR_DATA8(0x32); LCD_WR_REG(0x21); LCD_WR_REG(0x11); delay(50); LCD_WR_REG(0x29); } void LCD_WR_DATA(u16 dat) { u8 spi_dat[2]; XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 1); spi_dat[0]=dat>>8; spi_dat[1]=dat; XSpiPs_PolledTransfer(&SpiInstance, spi_dat, NULL, 2); } void Address_set(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2) { LCD_WR_REG(0x2a); LCD_WR_DATA(x1); LCD_WR_DATA(x2); LCD_WR_REG(0x2b); LCD_WR_DATA(y1+34); LCD_WR_DATA(y2+34); LCD_WR_REG(0x2c); } void LCD_Test() { unsigned int i,j; Address_set(0,0,320-1,172-1); for(i=0;i<172;i++){ if(i>=0&&i<43) for (j=0;j<320;j++)LCD_WR_DATA(WHITE); else if(i>=43&&i<86) for (j=0;j<320;j++)LCD_WR_DATA(RED); else if(i>=86&&i<129) for (j=0;j<320;j++)LCD_WR_DATA(GREEN); else if(i>=129&&i<172) for (j=0;j<320;j++)LCD_WR_DATA(BLUE); } } int main(void) { Lcd_Gpio_Init(); Lcd_Spi_Init(); Lcd_Init(); LCD_Test(); while(1){ }; return XST_SUCCESS; }
8)用之前生成的二进制文件对FPGA进行编程,Xilinx Tools
-> Program FPGA
然后点击 “Program”
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-57.png)
9)当FPGA编程成功后,我们需要初始化zynq中的处理器,右键点击刚才创建的空工程,选择Run As
-> Launch on Hardware (System Debugger)
或者 Launch on Hardware (GDB).
![](http://www.hellofpga.com/wp-content/uploads/2021/08/image-79.png)
经过上述操作,屏幕就被成功点亮了, 正常显示 4色彩条纹,如下图所示
![](http://www.hellofpga.com/wp-content/uploads/2023/01/image-88.png)
代码解释
以上是完整的工程图文创建过程, 除了上面提到的GPIO和 SPI部分的代码外,下面简单介绍下 工程中的其余的一些函数代码
1)关于颜色
屏幕本身是16色 也就是RGB565的颜色驱动方式 ,所以这里可以用 16字节来定义不同的颜色,如程序中的红绿蓝白,以及下面标注的其他颜色, 当然也可以根据RGB的颜色组合来合成自己需要的颜色
#define WHITE 0xFFFF #define BLACK 0x0000 #define BLUE 0x001F #define BRED 0XF81F #define GRED 0XFFE0 #define GBLUE 0X07FF #define RED 0xF800 #define MAGENTA 0xF81F #define GREEN 0x07E0 #define CYAN 0x7FFF #define YELLOW 0xFFE0 #define BROWN 0XBC40 #define BRRED 0XFC07 #define GRAY 0X8430
2)关于彩色条纹 ,如下代码,0-42行显示白色,43-85显示红色,86-128显示绿色,129-172显示蓝色
Address_set(0,0,320-1,172-1); 相当于是定义一块矩形区域,0,0,320,172分别是这个矩形的四个坐标点(这里涵盖了整个屏幕的空间,也可以自定义部分区域,详细看屏幕手册)
void LCD_Test() { unsigned int i,j; Address_set(0,0,320-1,172-1); for(i=0;i<172;i++){ if(i>=0&&i<43) for (j=0;j<320;j++)LCD_WR_DATA(WHITE); else if(i>=43&&i<86) for (j=0;j<320;j++)LCD_WR_DATA(RED); else if(i>=86&&i<129) for (j=0;j<320;j++)LCD_WR_DATA(GREEN); else if(i>=129&&i<172) for (j=0;j<320;j++)LCD_WR_DATA(BLUE); } }
最后介绍main 函数,其实特别简单,包括GPIO的初始化,SPI的初始化, 以及屏幕的初始化(屏幕内部寄存器的配置 配置信息由厂家直接提供),以及屏幕测试函数的调用
int main(void) { Lcd_Gpio_Init(); Lcd_Spi_Init(); Lcd_Init(); LCD_Test(); while(1); return XST_SUCCESS; }
以下附件为 本页内容的完整工程
另外屏幕可以根据需求设置成横屏和竖屏模式
写在最后关于背光部分:
板子上PL部分的BANK34和BANK35所有IO都用完了,所以板子设计的时候 背光单独由MIO控制,而不是PL控制, 正常调试的时候 LCD背光默认打开(即仅仅用PL对应脚就能实现整个LCD的点屏操作,即背光脚不需要管它),当有低功耗要求,或上电需要关闭LCD再打开的要求时,可以在PS上将背光对应的MIO引脚拉低,这样LCD屏幕就不再发光。
![](http://www.hellofpga.com/wp-content/uploads/2023/01/image-85-1024x552.png)
LCD_BLK对应的管脚是MIO27
![](http://www.hellofpga.com/wp-content/uploads/2023/01/image-86.png)