之前看到了公众号大佬法的应该学着写点技术博客,再加上搞这个屏幕的时候网上没有找到成套的教程,所以写下了这个博客,请多指教。(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=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
最终开机logo效果图。(事实上,一是可以用字符串显示代替,二也可以拆分成三个图片分别在屏幕上不同位置显示,这样可以缩小数据大小,你看,图像中白色部分那么多,浪费了存储空间)
你还别说这ips屏还挺好用,其实也没比那个一点几寸的oled单色屏幕贵多少。
模块到这里测试就结束了,工程、代码我会上传。
后续我会把驱动进行一个移植,因为我的项目想用cubemx (HAL库)做,并且有freertos操作系统。
第一次写博客,语言也不专业,欢迎大家批评指正。