EBAZ4205 第十个工程 用zynq的PS IO资源去模拟SPI协议来点亮彩色LCD屏(紫色板子)

和第十三个工程有所不同,第十三个工程是用硬件SPI 去点亮LCD 屏,这里是用IO的方式去模拟点亮LCD屏幕, 过程会写的比较详细。

第十三个工程硬件SPI的兼容性问题已经解决了,硬件SPI的方案执行效率更高,速率更快,如果要用硬件SPI的可以参考工程十三 20220505

为什么有了硬件SPI 工程还要用这个模拟的方式去点亮屏幕呢? 因为本转接板使用的屏幕的SPI接口上并未引出CS脚(屏幕厂商的现成DEMO也是没有拉出CS脚的),正常使用的时候,或者用其他MCU的硬件SPI 去驱动的时候没有碰到问题,但是我自己在做项目的时候遇到了,当ZYNQ点亮屏幕后长时间不去操作屏幕的时候 ,再去点亮屏幕会出现屏幕死机不听指令的情况,怀疑是因为没有CS信号,无法作位校准,然后ZYNQ的硬件SPI有可能在长时间不调用情况下,对总线进行了释放,导致之后收到的数据屏幕都是错位的情况。 所以为了解决这个问题,特地写了用IO口模拟的方式去驱动屏幕的教程,经过测试,在扩展板下,此方法比硬件SPI去点亮屏幕更稳定

以下部分内容和第七个工程雷同, 可选择性看

1.硬件介绍

转接板部分硬件LCD 接口如下图所示

BL 负责控制背光,高:屏幕点亮, 低:屏幕熄灭

CS 片选信号 并未引出, 这里直接接GND 了,一直使能

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_BL    	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_BL, 1);
	XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_BL, 1);
	XGpioPs_WritePin(&Gpio, EMIO_LCD_BL, 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_BL, 1);//设置成输出模式

XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_BL, 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 部分代码编写

void delay_spi_nop(){
	volatile int Delay;
	for (Delay = 0; Delay < 1; Delay++);
}

void spi_send(unsigned char dat){
	unsigned char i;
	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;
	}
}

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_BLK    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_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)


void delay_spi_nop(){
	volatile int Delay;
	for (Delay = 0; Delay < 1; Delay++);
}

void spi_send(unsigned char dat){
	unsigned char i;
	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;
	}
}


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_BLK, 1);
	XGpioPs_SetOutputEnablePin(&Gpio, EMIO_LCD_BLK, 1);
	XGpioPs_WritePin(&Gpio, EMIO_LCD_BLK, 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);

  LCD_BLK_HIGH;
  }




 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);
    }
}



int main(void)
{
	Lcd_Gpio_Init();

	Lcd_Init();
	LCD_Test();

	while(1){
		
	};

	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 GPIO模拟SPI和硬件SPI都可以稳定的点亮LCD屏幕,根据自己的需求去选择(硬件SPI执行效率比GPIO模拟SPI会更高一些, GPIO模拟的方式更直观一些)

“EBAZ4205 第十个工程 用zynq的PS IO资源去模拟SPI协议来点亮彩色LCD屏(紫色板子)”的一个回复

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注