STM32F103软件模拟SPI接口驱动ILI9486液晶屏

STM32F103软件模拟SPI接口驱动ILI9486液晶屏

  • ILI9486的工作模式
    • ILI9486的SPI总线方式简介
    • ILI9486的3线SPI总线底层驱动配置步骤

ILI9486的工作模式

ILI9486是ILITEC推出的一款LCD驱动器,支持262144种色彩,支持的显示分辨率为320X480,内部GRAM显存为345600Bytres(320X480X18bit)。ILI9486支持8种总线方式,由硬件决定,见下表。硬件设计时,可以给IM0,IM1,IM2这3个引脚都设计上拉至电源、下拉至GND的电阻,这样可以通过选择焊接这6个上下拉电阻来灵活选择芯片的总线方式。
  STM32F103软件模拟SPI接口驱动ILI9486液晶屏_第1张图片

ILI9486的SPI总线方式简介

ILI9486的8080并行总线这里不做详述,重点介绍其SPI总线。ILI9486的SPI总线比我们通常用到的SPI总线稍复杂一些。其SPI总线有两种方式:3线方式和4线方式。这两种方式的区别是3线方式中,只有一根数据线,ILITEC官方称为DIN/SDA,这根线既做MOSI,又做MISO;4线方式中,有两根数据线,ILITEC官方称此为DIN/SDA和DOUT,这里SPI的MOSI和MISO是分开的。4线方式用法和我们通常使用的SPI总方式基本一样。3 线方式稍有区别,这里根据本人的应用经验,重点介绍一下3线SPI总线的驱动。

ILI9486的3线SPI总线底层驱动配置步骤

第一步:根据上表,从硬件上将IM[2:0]设置为101,选择3线SPI总线方式

第二步:初始化3个GPIO口:片选(CS),时钟(CLK),数据线(DIN/SDA)。其它线如:复位、数据/命令选择、背光、显示开/关等根据自己的硬件去配置。
注意:虽然数据线在使用中,要根据收、发而改变方向,但在LCD初始化时,MCU需要给ILI9486发送初始化序列,因此初始化过程中,要将其初始化为输出状态!

第三步:写底层SPI发送、接收函数
这里要注意:
(1)发送顺序是MSB在前,LSB在后。
(2)在发送是DIN/SDA要置为输出;接收时DIN/SDA要置为输入。实际代码中,尽量不使用库函数,可直接操作寄存器,以提高程序运行速度。本人在应用中,是用宏定义实现的。如下(这里DIN/SDA线用PB15):

#define SDA_IN   {GPIOB->CRH&=0X0FFFFFFF;GPIOB->CRH|=(u32)8<<28;}  //PB15为输入 CRH的[31:28]为 1 0 0 0
#define SDA_OUT  {GPIOB->CRH&=0X0FFFFFFF;GPIOB->CRH|=(u32)3<<28;}  //PB15为输出 CRH的[31:28]为 0 0 1 1
#define READ_SDA  GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)    //读数据,这时PB15是在输入状态。发送时,PB15为输出,接收(读)时PB15为输入

第四步:给ILI9486发送初始化序列
LCD的初始化序列包括屏的像素时钟,同步参数,扫描时间,扫描方向,颜色格式,辉度,工作模式等类初始化设置,其它不做叙述,这里只讲SPI的配置。查阅 芯片手册,做如下配置:


	SPILCD_WriteReg(0xB0);   //接口模式设置
	SPILCD_WriteData8(0x80); //SDA_EN=1,只用DIN;DOUT不用

有兴趣的读者可以仔细查阅芯片手册中与下图相关资料(红色标记是本文所描述的相关配置):
STM32F103软件模拟SPI接口驱动ILI9486液晶屏_第2张图片
STM32F103软件模拟SPI接口驱动ILI9486液晶屏_第3张图片通过上述4步,即可对ILI9486进行正常操作,实现LCD的显示。
这里再补充如下内容:通常,我们在操作LCD时,大部分是给其GRAM里写数据而实现在LCD屏上的显示。但是,在需要实现复杂、精美的显示界面时,我们就需要使用一些图形化界面开发环境,如emWin等。emWin在移植过程中,最基本的工作是把LCD的底层读写函数交给emWin,ILI9486在不同模式下,读GRAM数据的指令都是0x2E,但时序是有区别的,若时序不对,读出的颜色值就是错误的。下图是3线SPI总线,18bit颜色格式。(3线SPI有两种格式:8色RGB111和262K色GB666)
STM32F103软件模拟SPI接口驱动ILI9486液晶屏_第4张图片
从此时序图可以看出,每次读取的GRAM颜色数据是24bit,RGB各6bit,即18bit颜色(颜色格式是初始化序列中,用0x3A指令设置的,值为0x66)。那么,是不是我们发送了0x2E指令后就直接读取24bit,即3个字节数据就可以了呢。答案是否的!见下图:STM32F103软件模拟SPI接口驱动ILI9486液晶屏_第5张图片

MCU给ILI9486发送0x2E指令(此指令不要参数)后,ILI9486返回24bit颜色数据前,有9个时钟的哑数据,因此必须在发送指令后,要连续读取32bit,而不是24bit。这样才能读取到正确的GRAM数据。否则读取的值就是错误的。

##以下是我的源代码,供大家参考(MCU是STM32F103C8T6)

#include “ili9486.h”
#include “delay.h”

u16 BACK_COLOR; //背景色

/----------------------------------------------------------------------------
屏的控制接口初始化
-----------------------------------------------------------------------------
/
static void ILI9486_GpioInit()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能PORTA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//使能PORTB时钟

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出模式
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz; //100MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOA, &GPIO_InitStructure);//SPI_CS2

SPI_CS2_H;
SPI_CS1_H;		  //初始时SPI都不选中
LCD_RST_L;    
delay_ms(200);
LCD_RST_H;       //复位
delay_ms(200);
LCD_BLK_H;       //背光开
delay_ms(200);

}

/----------------------------------------------------------------------------
SPI总线向屏发送1个字节
-----------------------------------------------------------------------------
/
static void SPILCD_WriteByte(u8 dat)
{
u8 i;
SDA_OUT;
SPI_CS1_L; //选择LCD
for(i=0;i<8;i++)
{
SPI_SCK=0; //时钟拉低
if(dat&0x80)
SPI_MOSI_H; //发送1
else
SPI_MOSI_L; //发送0
SPI_SCK=1; //时钟拉高,屏上升沿接收
dat<<=1; //准备下一位
}
SPI_CS1_H; //禁止LCD
}
/----------------------------------------------------------------------------
SPI总线从屏读取1个字节
-----------------------------------------------------------------------------
/
u8 SPILCD_ReadByte(void)
{
u8 i,dat;
SDA_IN;
LCD_DC_H;
SPI_CS1_L; //选择LCD
SPI_SCK_L;//先将时钟拉低,
for(i=0;i<8;i++)
{
SPI_SCK_H; //时钟拉高,屏会在这个上升沿输出数据
dat=dat<<1;
if(READ_SDA)
{
dat=dat+1;
}
else
{
dat=dat;
}
SPI_SCK_L; //时钟拉低,准备下一次上升沿
}
SPI_CS1=1; //禁止LCD
return (dat);
}

/----------------------------------------------------------------------------
SPI总线从屏读取4个字节
-----------------------------------------------------------------------------
/
u32 SPILCD_ReadData(void)
{
u8 i;
u32 dat;
SDA_IN;
LCD_DC_H;
SPI_CS1_L; //选择LCD
SPI_SCK_L;//先将时钟拉低,
for(i=0;i<32;i++)
{
SPI_SCK_H; //时钟拉高,屏会在这个上升沿输出数据
dat=dat<<1;
if(READ_SDA)
{
dat=dat+1;
}
else
{
dat=dat;
}
SPI_SCK_L; //时钟拉低,准备下一次上升沿
}
SPI_CS1=1; //禁止LCD
return (dat);
}

/----------------------------------------------------------------------------
通过SPI总线向屏发送1个字节数据
-----------------------------------------------------------------------------
/
void SPILCD_WriteData8(u8 dat)
{
LCD_DC_H;//写数据
SPILCD_WriteByte(dat);
}

/----------------------------------------------------------------------------
通过SPI总线向屏发送3个字节数据(发送dat的低24位【23:0】)
-----------------------------------------------------------------------------
/
void SPILCD_WriteData32(u32 dat)
{
LCD_DC_H;//写数据
SPILCD_WriteByte(dat>>16);
SPILCD_WriteByte(dat>>8);
SPILCD_WriteByte(dat);
}

/----------------------------------------------------------------------------
通过SPI总线向屏发送1个字节命令
-----------------------------------------------------------------------------
/
void SPILCD_WriteReg(u8 dat)
{
LCD_DC_L;//写命令
SPILCD_WriteByte(dat);
}

///----------------------------------------------------------------------------
//LCD初始化函数
//-----------------------------------------------------------------------------
/
void SPILCD_Init(void)
{
ILI9486_GpioInit();
SPILCD_WriteReg(0x11); //Exit Sleep
delay_ms(60);

SPILCD_WriteReg(0XF2);
SPILCD_WriteData8(0x18);
SPILCD_WriteData8(0xA3);
SPILCD_WriteData8(0x12);
SPILCD_WriteData8(0x02);
SPILCD_WriteData8(0XB2);
SPILCD_WriteData8(0x12);
SPILCD_WriteData8(0xFF);
SPILCD_WriteData8(0x10);
SPILCD_WriteData8(0x00);
SPILCD_WriteData8(0XF8);
SPILCD_WriteData8(0x21);
SPILCD_WriteData8(0x04);

SPILCD_WriteReg(0X13);

SPILCD_WriteReg(0x36); // Memory Access Control
SPILCD_WriteData8(0x78);

SPILCD_WriteReg(0xB4);
SPILCD_WriteData8(0x02);

SPILCD_WriteReg(0xB6);
SPILCD_WriteData8(0x02);
SPILCD_WriteData8(0x22);

SPILCD_WriteReg(0xC1);
SPILCD_WriteData8(0x41);

SPILCD_WriteReg(0xC5);
SPILCD_WriteData8(0x00);
SPILCD_WriteData8(0x18);

SPILCD_WriteReg(0x3a);   //像素格式:18bits/pixel
SPILCD_WriteData8(0x66);
delay_ms(50);

SPILCD_WriteReg(0xB0);   //接口模式设置
SPILCD_WriteData8(0x80); //SDA_EN=1,只用DIN;DOUT不用

SPILCD_WriteReg(0xE0);
SPILCD_WriteData8(0x0F);
SPILCD_WriteData8(0x1F);
SPILCD_WriteData8(0x1C);
SPILCD_WriteData8(0x0C);
SPILCD_WriteData8(0x0F);
SPILCD_WriteData8(0x08);
SPILCD_WriteData8(0x48);
SPILCD_WriteData8(0x98);
SPILCD_WriteData8(0x37);
SPILCD_WriteData8(0x0A);
SPILCD_WriteData8(0x13);
SPILCD_WriteData8(0x04);
SPILCD_WriteData8(0x11);
SPILCD_WriteData8(0x0D);
SPILCD_WriteData8(0x00);
SPILCD_WriteReg(0xE1);
SPILCD_WriteData8(0x0F);
SPILCD_WriteData8(0x32);
SPILCD_WriteData8(0x2E);
SPILCD_WriteData8(0x0B);
SPILCD_WriteData8(0x0D);
SPILCD_WriteData8(0x05);
SPILCD_WriteData8(0x47);
SPILCD_WriteData8(0x75);
SPILCD_WriteData8(0x37);
SPILCD_WriteData8(0x06);
SPILCD_WriteData8(0x10);
SPILCD_WriteData8(0x03);
SPILCD_WriteData8(0x24);
SPILCD_WriteData8(0x20);
SPILCD_WriteData8(0x00);

SPILCD_WriteReg(0x11);
delay_ms(120);
SPILCD_WriteReg(0x29);
SPILCD_WriteReg(0x2C);
} 

/----------------------------------------------------------------------------
设置起始和结束地址(设置坐标)
-----------------------------------------------------------------------------
/
void SPILCD_Address_Set(u16 x1,u16 y1,u16 x2,u16 y2)
{
SPILCD_WriteReg(0x2a); //列地址设置
SPILCD_WriteData8(x1>>8);
SPILCD_WriteData8(x1);
SPILCD_WriteData8(x2>>8);
SPILCD_WriteData8(x2);
SPILCD_WriteReg(0x2b); //行地址设置
SPILCD_WriteData8(y1>>8);
SPILCD_WriteData8(y1);
SPILCD_WriteData8(y2>>8);
SPILCD_WriteData8(y2);
SPILCD_WriteReg(0x2c); //储存器写
}

/----------------------------------------------------------------------------
LCD清屏函数
-----------------------------------------------------------------------------
/
void SPILCD_Clear(u32 color)
{
u16 i,j;
SPILCD_Address_Set(0,0,SPILCD_W-1,SPILCD_H-1);
for(i=0;i {
for (j=0;j {
SPILCD_WriteData32(color);
}
}
}

/----------------------------------------------------------------------------
LCD画点函数
-----------------------------------------------------------------------------
/
void SPILCD_DrawPoint(u16 x,u16 y,u32 color)
{
SPILCD_Address_Set(x,y,x,y);//设置光标位置
SPILCD_WriteData32(color);
}

/----------------------------------------------------------------------------
LCD读点函数
-----------------------------------------------------------------------------
/
u32 SPILCD_ReadPoint(u16 x,u16 y)
{
u32 data;
SPILCD_Address_Set(x,y,x,y);//设置光标位置
SPILCD_WriteReg(0x2E);
data=SPILCD_ReadData();//读24bit颜色数据
return data;
}

/----------------------------------------------------------------------------
LCD在指定区域填充颜色
-----------------------------------------------------------------------------
/
void SPILCD_Fill(u16 xsta,u16 ysta,u16 xend,u16 yend,u32 color)
{
u16 i,j;
SPILCD_Address_Set(xsta,ysta,xend,yend); //设置光标位置
for(i=ysta;i<=yend;i++)
{
for(j=xsta;j<=xend;j++)SPILCD_WriteData32(color);//设置光标位置
}
}

/----------------------------------------------------------------------------
画线
-----------------------------------------------------------------------------
/
void SPILCD_DrawLine(u16 x1,u16 y1,u16 x2,u16 y2,u16 color)
{
u16 t;
int xerr=0,yerr=0,delta_x,delta_y,distance;
int incx,incy,uRow,uCol;
delta_x=x2-x1; //计算坐标增量
delta_y=y2-y1;
uRow=x1;//画线起点坐标
uCol=y1;
if(delta_x>0)incx=1; //设置单步方向
else if (delta_x0)incx=0;//垂直线
else {incx=-1;delta_x=-delta_x;}
if(delta_y>0)incy=1;
else if (delta_y
0)incy=0;//水平线
else {incy=-1;delta_y=-delta_x;}
if(delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴
else distance=delta_y;
for(t=0;t {
SPILCD_DrawPoint(uRow,uCol,color);//画点
xerr+=delta_x;
yerr+=delta_y;
if(xerr>distance)
{
xerr-=distance;
uRow+=incx;
}
if(yerr>distance)
{
yerr-=distance;
uCol+=incy;
}
}
}

//---------------End of this file-------------------------------

你可能感兴趣的:(STM32F103软件模拟SPI接口驱动ILI9486液晶屏)