ILI9431的LCD屏使用,STM32F1控制

之前看到了公众号大佬法的应该学着写点技术博客,再加上搞这个屏幕的时候网上没有找到成套的教程,所以写下了这个博客,请多指教。(STM32F103RCT6,测试时使用的是正点原子的mini开发板,其实用谁的开发版并不重要)

目录

一、模块的使用

二、编写驱动

三、显示图片


一、模块的使用

最近在做毕设,想用一块大一点的屏幕,最后挑了这款ips屏,ili9431驱动,模块出厂40pin。(具体某宝哪家买的就不说了)

测试屏幕时我买了转接板,方便测试。

厂家给出的引脚图是这样的(手册中的详细描述略)

显然 ,6 7 脚是供电脚,接上3.3V ;5脚GND

9脚cs 是片选脚,低电平有效  ;10脚RS/SPI SCL/SCK  使用SPI的时候是时钟线 SCK;

11脚WR/A0   换了个说法,其实就是CMD/DATA引脚(命令/数据)引脚 ;12脚RD读控制脚

13脚 是串口数据的输入信号,接stm32的MOSI(单片机是主机往从机发);同理,14脚接单片机MISO

15屏复位脚 常见说法就是RST;

33脚A,34-36脚K,分别是背光的正极和负极,这两个一个接3.3,一个接地,屏幕才能亮起来,否则哪怕显示出来了,你得打折强光手电才看到的。

我们用4线spi,所以IM0 IM1 IM2 分别接地,3.3,3.3 ;

以上就是屏幕正常工作,必须要使用的引脚,其余的按手册上处理(要么接地 要么悬空即可).

引脚资源分配如下

RST(复位) PC1
DC/A0(命令/数据) PC2
 CS(片选)  PC3
BLK(背光控制) (就是上面的K引脚) PB9
Spi_sck   PA5
Spi_miso    PA6
Spi_mosi    PA7
  PA5 6 7 均为复用,注意配置

显示屏模块电路

二、编写驱动

到csdn上下载了一个别人写好了的驱动,那位老哥的驱动只有驱动文件,具体单片机实现留好了接口,十分感谢,但是文件上没有具体写作者是哪位,太可惜了。

函数里对于引脚的操作通过宏定义进行了一次封装,如果单片机不是stm32 只需修改为自己对应的引脚操作函数即可。

屏幕的初始化,大致是: 初始化引脚(GPIO,SPI),初始化外设 ,然后就可以调用函数看看效果了

void SPI1_idev_Init(void)
{	
  SPI_InitTypeDef  SPI_InitStructure;

	SPI1_IO_Init();
	
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_SPI1,  ENABLE );//SPI1时钟使能 	
 
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//设置SPI的数据大小:SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//串行同步时钟的空闲状态为High电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步时钟的第2个跳变沿(上升或下降)数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;		//定义波特率预分频的值:波特率预分频值为,
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI1, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器

	//DMA_SPI1_idev_init();	//配置SPI1的DMA功能
	
	SPI_Cmd(SPI1, ENABLE); //使能SPI外设
} 
//csdn上卸载的驱动这个函数只留了接口,所以我们把内容填上
void ILI9341_io_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOC, ENABLE );
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 |GPIO_Pin_2 |GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	GPIO_SetBits(GPIOC, GPIO_Pin_1 |GPIO_Pin_2 |GPIO_Pin_3);  
    
    RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 ;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOB, GPIO_Pin_9 );  
    
}
//初始化外设
void SPI1_idev_Init(void)
{	
  SPI_InitTypeDef  SPI_InitStructure;

	SPI1_IO_Init();
	
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_SPI1,  ENABLE );//SPI1时钟使能 	
 
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//设置SPI的数据大小:SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//串行同步时钟的空闲状态为High电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步时钟的第2个跳变沿(上升或下降)数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;		//定义波特率预分频的值:波特率预分频值为,
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI1, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器

	//DMA_SPI1_idev_init();	//配置SPI1的DMA功能
	
	SPI_Cmd(SPI1, ENABLE); //使能SPI外设
} 
//初始化外设
void SPI1_idev_Init(void)
{	
  SPI_InitTypeDef  SPI_InitStructure;

	SPI1_IO_Init();
	
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_SPI1,  ENABLE );//SPI1时钟使能 	
 
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//设置SPI的数据大小:SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//串行同步时钟的空闲状态为High电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步时钟的第2个跳变沿(上升或下降)数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;		//定义波特率预分频的值:波特率预分频值为,
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI1, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器

	//DMA_SPI1_idev_init();	//配置SPI1的DMA功能
	
	SPI_Cmd(SPI1, ENABLE); //使能SPI外设
} 

然后就可以开始愉快的调用了;

调用发现,屏幕上想要显示字符的部分是一个黑色马赛克,并未成功,所以我硬着头皮debug,屏幕显示的基本原理就是画点

通过debug发现,原作者的函数中,画笔的颜色和当前背景颜色是通过lcddev中的cur_brushcl ,cur_backcl两个成员来表示的,在调用函数前对这两个变量没有赋值,所以程序出了bug。经过多重考虑,我把这两个变量直接写死,反正我只需要白底黑字;

void LCD_ShowChar(unsigned short int x,unsigned short int y,unsigned char ch,unsigned char csize,unsigned char mode)
{
	unsigned char temp,t1,t;
	unsigned short int y0=y;
	
	unsigned char sz=(csize/8+((csize%8)?1:0))*(csize/2);		//得到字体一个字符对应点阵集所占的字节数
	
 	ch=ch-' ';//得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
	
	for(t=0;t<sz;t++)
	{   
		if(csize==12)temp=ascii_1206[ch][t]; 	 	//调用1206字体
		else if(csize==16)temp=ascii_1608[ch][t];	//调用1608字体
		else if(csize==24)temp=ascii_2412[ch][t];	//调用2412字体
		else return;								//没有的字库
		
		for(t1=0;t1<8;t1++)
		{			    
			if(temp&0x80)
			{
				LCD_DrawPoint_Color(x,y,BLACK);//lcddev.cur_brushcl;我这里直接写成了黑色
			}
			else if(mode==0)
			{
				LCD_DrawPoint_Color(x,y,WHITE);//lcddev.cur_backcl;写死,白色
			}	
			
			temp<<=1;
			y++;
			if(y>=lcddev.height)return;		//超区域了
			if((y-y0)==csize)
			{
				y=y0;
				x++;
				if(x>=lcddev.width)return;	//超区域了
				break;
			}
		}  	 
	}
}

三、显示图片

对于这类屏幕要想显示图片,肯定得先取模,经过调查,取模应该采用水平扫描,不带数据头,16位彩色格式进行取模

取模后获得数据(上图仅为展示注意参数),我实际上取模了一个320*240的图片,16位rgb格式的话这个对存储空间的需求还是很大的,所以测试成功一次后,我加了一个宏定义,需要的时候才编译进去,缩短每次下载时间。(我用的dap下载器,文件大了就很慢)

使用网上老哥提供的函数进行调用

/*************************************************************************************
* 名    称:void LCD_DrawPicture(unsigned short int StartX,unsigned short int StartY,unsigned short int PicXend,unsigned short int PicYend,const unsigned char *pic)
* 功    能:在指定座标范围显示一副图片
* 入口参数:StartX     行起始座标
*           StartY     列起始座标
*           PicXend      图片的X像素
*           PicYend       图片的Y像素
            pic             图片头指针
* 出口参数:无
* 说    明:图片取模格式为水平扫描,16位颜色模式
*LCD_DrawPicture(0,0,320,240,gImage_logo);
*************************************************************************************/
void LCD_DrawPicture(unsigned short int StartX,unsigned short int StartY,unsigned short int PicXend,unsigned short int PicYend,const unsigned char *pic)
{
  unsigned long j=0;	
	unsigned char *pdata = (unsigned char *)pic;

	LCD_set_windows(StartX,StartY,StartX+PicXend-1,StartY+PicYend-1);	//设置显示窗口
	
	for(j=0;j<PicXend*PicYend;j++)	//向窗口中填入内容,即图片的模值
	{
			LCD_wt8bitData(pdata[j*2+1]);
			LCD_wt8bitData(pdata[j*2]);
	}            
}

最终开机logo效果图。(事实上,一是可以用字符串显示代替,二也可以拆分成三个图片分别在屏幕上不同位置显示,这样可以缩小数据大小,你看,图像中白色部分那么多,浪费了存储空间)

你还别说这ips屏还挺好用,其实也没比那个一点几寸的oled单色屏幕贵多少。

模块到这里测试就结束了,工程、代码我会上传。

后续我会把驱动进行一个移植,因为我的项目想用cubemx (HAL库)做,并且有freertos操作系统。

第一次写博客,语言也不专业,欢迎大家批评指正。

猜你喜欢

转载自blog.csdn.net/qq_40550914/article/details/103917253
今日推荐