和第十三个工程有所不同,第十三个个工程是用硬件SPI 去点亮LCD 屏,这里是用IO的方式去模拟点亮LCD屏幕, 过程会写的比较详细。
黑色版的转接板和紫色版的转接板有一处有区别,那就是黑色的转接板在屏幕部分 ,将原本的背光信号删除(改成常亮),将原先控制背光的信号线T20改成屏幕SPI的CS信号线,这样可以兼容ZYNQ的硬件SPI功能(本章是软件SPI)
彩色液晶屏资料(厂家提供的资料) 店铺 中景园电子
https://pan.baidu.com/s/1sqzKVlQiuvcBzXB_MOAR5w 提取码:8888 (如链接失效可以找 店铺厂家要)
1.硬件介绍
转接板部分硬件LCD 接口如下图所示
LCD背光常亮(紫色的转接板,背光部分可控制)
CS 片选信号 (紫色的板子这部分直接接GND,而黑色的CS有接)
SCL 时钟
SDA 数据
D/C 数据/命令指示
RES 屏幕复位
2.驱动思路
屏幕本身是连接到ZYNQ的PL资源上,点亮屏幕可以用纯fpga去驱动SPI的方式点亮,也可以用PS资源去点亮,为了方便演示,这里采用PS资源去实现
本文用ZYNQ的IO口去模拟硬件SPI ,来驱动LCD屏, IO口则是PS端通过EMIO的方式映射到PL接口上
3.创建工程
1)新建一个项目,芯片型号选择 XC7Z010CLG400-1
2) 创建一个BLOCK设计,并添加ZYNQ7 PROCESSING SYSTEM模块,软件自动生成了一个 zynq的block 如下图所示,接下来要做一些相应的设置,双击下图中的ZYNQ核
3)在ZYNQ中设置时钟功能:
找到 设置项目中的 Clock Configuration 选项, 在PL Fabric Clocks 设置自己需要的时钟频率,这里一共有4种频率可以设置 类似于我们的PLL功能。这里我们设置50M时钟
4)在zynq中设置DDR功能:
依次在弹窗里找到DDR Configuration→DDR Controller Configuration→DDR3,在Memory Part下拉菜单中根据自己板子上的DDR来选择相应的DDR3,本实验所用到型号:MT41K128M16JT 125,数据位宽选择16bit 最后点击“OK”,如下图所示。
5)EMIO的设置,屏幕驱动除了时钟信号,数据信号以外,还有3个额外的GPIO口来分别控制背光BL ,复位RES和 D/C信号,所以这里共增加5个GPIO口,这里GPIO资源选择EMIO GPIO 位宽Width选择5(因为是5个IO口)
6)完成上述操作后, 点击“Run Block Automation”如下图所示。在弹出的选项中保持默认,点击“OK”,即可完成对ZYNQ7 Processing System的配置,并用鼠标连接FCLK_CLK和 M_AXI_GP0_ACLK,得到下图
在上图中分别点击IO口进行以下操作:
右键GPIO_0选择Make External
7)source→Design Source ,右键我们创建的BLOCK工程,点击create HDL wrapper,打包BLOCK文件并生成.v代码
8) 点击绿色箭头RUN 对代码进行编译
9) 点击RTL 中的SCHEMATIC , 并选择右边出现的 IO Ports 来增加SPI的管脚定义(这一步也可以在约束文件中定义, 可看之前的例子)
修改GPIO 管脚定义 和SPI管脚定义,如下图所示 ,修改后保存(如弹出 窗口需要,则在窗口中输入约束文件名,然后保存)
BL T20
D/C R18
SCL R19
SDA P20
RES N17
10) 生成bit文件 :按下Generate Bitstream 完成综合以及生成bit文件
5.SDK程序编写
1)File→Export→Export hardware…,在弹出的对话框中勾选“include bitstream”,点击“OK”确认,如下图所示。
2)File→Lauch SDK,在弹出的对话框中,保存默认,点击“OK”,如下图所示。
系统将自动打开SDK开发环境
3)新建一个工程 file→new→Application Project,来新建一个“Application Project”,如下图所示。
4)在新建工程名中输入自己的工程名称,点击NEXT
5)选择一个空工程,点击完成
6) 在空工程中创建我们自己的代码
展开我们创建的工程,在src目录上右键,选择New->Source File,如下图所示:
在弹出的窗口中创建一个main.c文件
6)书写自己的代码
6.1小贴士:
在初次写PS部分的代码时,如GPIO代码 不知道怎么写的情况下,其实可以通过打开 BSP工程下的 system.mss文件, 然后在右边导入需要的参考例程, 然后将例程中自己需要的部分(如GPIO初始化,或者SPI的写和读代码COPY到自己工程的main函数中)
6.2定义GPIO部分
下面进入正题, 我们要点亮屏幕, 屏幕这里用了5个GPIO 都是用了ZYNQ PS端的EMIO资源, EMIO的GPIO资源是从54开始的, 所以我们根据FPGA管脚约束中的 GPIO 0-4 分别对应 EMIO的54-55-56-57-58,如下图对GPIO进行定义
(以上部分黑色和紫色板子操作相同,下面专属黑色板子的软件内容)
#define EMIO_LCD_CS 54 #define EMIO_LCD_CD 55 #define EMIO_LCD_RES 56 #define EMIO_LCD_SCL 57 #define EMIO_LCD_SDA 58
6.3对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_CS, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_CS, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_CS, 0); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_CD, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_CD, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 0); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_RES, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_RES, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 0); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_SCL, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_SCL, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 0); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_SDA, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_SDA, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 0); }
先定义一个XgpioPs 的结构体指针, 这个指针的内容包括gpio分配的ID和基地址,这些信息通常在xparameter.h头文件中, 可以通过使用XGpioPs_LookupConfig函数,它能够在配置信息中找到对应ID的配置信息
ConfigPtr=XGpioPs_LookupConfig(GPIO_DEVICE_ID); 就相当于把获得到的信息赋予给之前定义的ConfigPtr结构体
最终再用 XGpioPs_CfgInitialize 函数来初始化GPIO
GPIO的使用 (EMIO_LCD_BL为之前定义的 EMIO 54 的管脚)
XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_CS, 1);//设置成输出模式
XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_CS, 1);//打开输出使能
XGpioPs_WritePin(&Gpio, EMIO_LCD_BL, 0);// 0置低
XGpioPs_WritePin(&Gpio, EMIO_LCD_BL, 1);// 1拉高
为了简化代码的编写 这里用 define 来简化拉高拉低的操作
#define LCD_SDA_HIGH XGpioPs_WritePin(&Gpio, EMIO_LCD_SDA, 1)
#define LCD_SDA_LOW XGpioPs_WritePin(&Gpio, EMIO_LCD_SDA, 0)
#define LCD_SCL_HIGH XGpioPs_WritePin(&Gpio, EMIO_LCD_SCL, 1)
#define LCD_SCL_LOW XGpioPs_WritePin(&Gpio, EMIO_LCD_SCL, 0)
#define LCD_BLK_HIGH XGpioPs_WritePin(&Gpio, EMIO_LCD_BLK, 1)
#define LCD_BLK_LOW XGpioPs_WritePin(&Gpio, EMIO_LCD_BLK, 0)
#define LCD_CD_HIGH XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 1)
#define LCD_CD_LOW XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 0)
#define LCD_RES_HIGH XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 1)
#define LCD_RES_LOW XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 0)
IO模拟SPI 部分代码编写(CS信号脚部分增加在这里)
void delay_spi_nop(){
volatile int Delay;
for (Delay = 0; Delay < 1; Delay++);
}
void spi_send(unsigned char dat){
unsigned char i;
LCD_CS_LOW;
for(i=0;i<8;i++){
LCD_SCL_LOW;
delay_spi_nop();
if(dat&0x80)LCD_SDA_HIGH;
else LCD_SDA_LOW;
delay_spi_nop();
LCD_SCL_HIGH;
delay_spi_nop();
dat=dat<<1;
}
LCD_CS_HIGH;
}
delay_spi_nop()函数中的一次循环,只是为了产生一个短暂的延时, 类似单片机的_nop_();函数,只是在 zynq sdk的库中暂时没找到这个函数
7)将代码整合后如下,下面是完整的屏幕驱动代码,将代码复制到main.c中
#include "xparameters.h" #include "xgpiops.h" #include "xstatus.h" #include "xplatform_info.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_CS 54 #define EMIO_LCD_CD 55 #define EMIO_LCD_RES 56 #define EMIO_LCD_SCL 57 #define EMIO_LCD_SDA 58 #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. */ #define LCD_SDA_HIGH XGpioPs_WritePin(&Gpio, EMIO_LCD_SDA, 1) #define LCD_SDA_LOW XGpioPs_WritePin(&Gpio, EMIO_LCD_SDA, 0) #define LCD_SCL_HIGH XGpioPs_WritePin(&Gpio, EMIO_LCD_SCL, 1) #define LCD_SCL_LOW XGpioPs_WritePin(&Gpio, EMIO_LCD_SCL, 0) #define LCD_CS_HIGH XGpioPs_WritePin(&Gpio, EMIO_LCD_CS, 1) #define LCD_CS_LOW XGpioPs_WritePin(&Gpio, EMIO_LCD_CS, 0) #define LCD_CD_HIGH XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 1) #define LCD_CD_LOW XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 0) #define LCD_RES_HIGH XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 1) #define LCD_RES_LOW XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 0) void delay_spi_nop(){ volatile int Delay; for (Delay = 0; Delay < 1; Delay++); } void spi_send(unsigned char dat){ unsigned char i; LCD_CS_LOW; for(i=0;i<8;i++){ LCD_SCL_LOW; delay_spi_nop(); if(dat&0x80)LCD_SDA_HIGH; else LCD_SDA_LOW; delay_spi_nop(); LCD_SCL_HIGH; delay_spi_nop(); dat=dat<<1; } LCD_CS_HIGH; } 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_CS, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_CS, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_CS, 0); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_CD, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_CD, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_CD, 1); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_RES, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_RES, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_RES, 1); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_SCL, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_SCL, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_SCL, 1); XGpioPs_SetDirectionPin(&Gpio, EMIO_LCD_SDA, 1); XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_SDA, 1); XGpioPs_WritePin(&Gpio, EMIO_LCD_SDA, 1); } void delay(unsigned int 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){ LCD_CD_HIGH; spi_send(dat); } void LCD_WR_REG(u8 dat){ LCD_CD_LOW; spi_send(dat); } void Lcd_Init(void){ LCD_RES_HIGH; delay(500); LCD_RES_LOW; delay(500); LCD_RES_HIGH; delay(500); LCD_WR_REG(0x36); LCD_WR_DATA8(0x00); 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(0x19); LCD_WR_REG(0xC0); LCD_WR_DATA8(0x2C); LCD_WR_REG(0xC2); LCD_WR_DATA8(0x01); LCD_WR_REG(0xC3); LCD_WR_DATA8(0x12); 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(0xE0); LCD_WR_DATA8(0xD0); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x0D); LCD_WR_DATA8(0x11); LCD_WR_DATA8(0x13); LCD_WR_DATA8(0x2B); LCD_WR_DATA8(0x3F); LCD_WR_DATA8(0x54); LCD_WR_DATA8(0x4C); LCD_WR_DATA8(0x18); LCD_WR_DATA8(0x0D); LCD_WR_DATA8(0x0B); LCD_WR_DATA8(0x1F); LCD_WR_DATA8(0x23); LCD_WR_REG(0xE1); LCD_WR_DATA8(0xD0); LCD_WR_DATA8(0x04); LCD_WR_DATA8(0x0C); LCD_WR_DATA8(0x11); LCD_WR_DATA8(0x13); LCD_WR_DATA8(0x2C); LCD_WR_DATA8(0x3F); LCD_WR_DATA8(0x44); LCD_WR_DATA8(0x51); LCD_WR_DATA8(0x2F); LCD_WR_DATA8(0x1F); LCD_WR_DATA8(0x1F); LCD_WR_DATA8(0x20); LCD_WR_DATA8(0x23); LCD_WR_REG(0x21); LCD_WR_REG(0x11); LCD_WR_REG(0x29); } void LCD_WR_DATA(u16 dat) { u8 spi_dat; LCD_CD_HIGH; spi_dat=dat>>8; spi_send(spi_dat); spi_dat=dat; spi_send(spi_dat); } void Address_set(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2) { LCD_WR_REG(0x2a); LCD_WR_DATA8(x1>>8); LCD_WR_DATA8(x1); LCD_WR_DATA8(x2>>8); LCD_WR_DATA8(x2); LCD_WR_REG(0x2b); LCD_WR_DATA8(y1>>8); LCD_WR_DATA8(y1); LCD_WR_DATA8(y2>>8); LCD_WR_DATA8(y2); LCD_WR_REG(0x2C); } void LCD_Test() { unsigned int i,j; Address_set(0,0,240-1,240-1); for(i=0;i<240;i++){ if(i>=0&&i<60) for (j=0;j<240;j++)LCD_WR_DATA(WHITE); else if(i>=60&&i<120) for (j=0;j<240;j++)LCD_WR_DATA(RED); else if(i>=120&&i<180) for (j=0;j<240;j++)LCD_WR_DATA(GREEN); else if(i>=180&&i<240) for (j=0;j<240;j++)LCD_WR_DATA(BLUE); } } unsigned char k=0; void LCD_Test2() { unsigned int i,j; Address_set(0,0,240-1,240-1); if(k==0){ for(i=0;i<240;i++){ for (j=0;j<240;j++)LCD_WR_DATA(WHITE); } k=1; } else { for(i=0;i<240;i++){ for (j=0;j<240;j++)LCD_WR_DATA(BLUE); } k=0; } } int main(void) { Lcd_Gpio_Init(); Lcd_Init(); LCD_Test(); while(1){ //LCD_Test2(); }; return XST_SUCCESS; }
8)用之前生成的二进制文件对FPGA进行编程,Xilinx Tools
-> Program FPGA
然后点击 “Program”
9)当FPGA编程成功后,我们需要初始化zynq中的处理器,右键点击刚才创建的空工程,选择Run As
-> Launch on Hardware (System Debugger)
或者 Launch on Hardware (GDB).
经过上述操作,屏幕就被成功点亮了, 正常显示 4色彩条纹,如下图所示
代码解释
以上是完整的工程图文创建过程, 除了上面提到的GPIO代码外,下面简单介绍下 工程中的其余的一些函数代码
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-60行显示白色,60-120显示红色,120-180显示绿色,180-240显示蓝色
Address_set(0,0,240-1,240-1); 相当于是定义一块矩形区域,0,0,240,240分别是这个矩形的四个坐标点(这里涵盖了整个屏幕的空间,也可以自定义部分区域,详细看屏幕手册)
void LCD_Test() { unsigned int i,j; Address_set(0,0,240-1,240-1); for(i=0;i<240;i++){ if(i>=0&&i<60) for (j=0;j<240;j++)LCD_WR_DATA(WHITE); else if(i>=60&&i<120) for (j=0;j<240;j++)LCD_WR_DATA(RED); else if(i>=120&&i<180) for (j=0;j<240;j++)LCD_WR_DATA(GREEN); else if(i>=180&&i<240) for (j=0;j<240;j++)LCD_WR_DATA(BLUE); } }
最后介绍main 函数,其实特别简单,包括GPIO的初始化,以及屏幕的初始化(屏幕内部寄存器的配置 配置信息由厂家直接提供),以及屏幕测试函数的调用
int main(void) { Lcd_Gpio_Init(); Lcd_Init(); LCD_Test(); while(1); return XST_SUCCESS; }
以下附件为 本页内容的完整工程
PS 上面介绍了完整的用IO口模拟SPI 的方式去点亮 LCD屏幕,对于黑色版本来说,IO模拟SPI 和PS部分原生硬件SPI都可以实现功能,硬件SPI 在传输过程中不消耗CPU资源,软件SPI 传输过程中会消耗CPU系统资源,可根据需求选择使用
请教一下,为什么没有pl端直接模拟spi的例程?网上也很少fpga实现spi的,是用verilog实现spi比较复杂吗?