目录
STM32液晶控制代码讲解
液晶接口封装介绍
使用LCD的配置步骤
内存操作要使用volatile进行修饰
图形绘制实现
绘制矩形
重点补充
STM32液晶控制代码讲解
液晶接口封装介绍
指南者液晶接口原理图
左边DB00—DB15表示液晶屏的数据线引脚,分别对应STM32的FSMC外设的FSMC_D0—D15及对应的GPIO
霸道原理图如下
可以发现左边得到数据线都是一样的,右边的LCD_RST不同,我们GPIO只需要设置为普通的推挽输出即可。两个板子通过对比之后就可以连接,自己设计板子的时候,哪些引脚是固定不可改变的,哪些引脚是可以自己随便选择的(其中所有具有FSMC外设复用功能的GPIO都是固定的,不能改变)。
然后看右半边的控制引脚:
LCD_BL是背光引脚,在丝印里面表示是BK,该引脚也可以任意选择,设置为普通的推挽输出就可以了。
LCD_CS为片选引脚,对应着指南者的NE1、霸道的NE4,因此要注意霸道和指南者访问液晶屏的地址不同。
RD引脚和WE引脚分别为读使能和写使能
RS引脚对应着LCD的D/CX控制线,用于控制写入的是数据还是命令,根据之前的“使用FSMC模拟8080时序”章节的讲解,D/CX是需要连接到FSMC的地址线的,通过一根地址线来控制,对于指南者是连接到了A16
下面的5根LCD_TP引脚是用于触摸屏的,它们是直接连接到xpt2046(触摸控制芯片),暂时不涉及到,之后再讲解。
使用时,我们先配置好相应的结构体和GPIO输出方式,然后就可以直接使用指针进行读写操作,对应的通讯引脚会自动产生读写时序。
通过输入0Ch命令,来判断FSMC与LCD是否正常通信,正常通信会返回两个参数,第一个为无效参数,第二个参数为LCD的像素格式(16bit或18bit)
使用LCD的配置步骤
- 初始化ILI9341的IO引脚
- LCD FSMC 模式配置,初始化两个结构体
- 点亮LCD背光灯
- 复位ILI9341
- 初始化ILI9341寄存器
- 设置默认扫描方向
内存操作要使用volatile进行修饰
使用指针从内存中读取数据时,内存地址必须加__IO(在STM32中为volatile)修饰,防止编译器进行变量优化。
比如我们进行写入命令的操作(FSMC_Addr_ILI9341_CMD 为宏)
*( __IO uint16_t * ) ( FSMC_Addr_ILI9341_CMD ) = 0x0C;
如果不加__IO,由于进行读取内存操作的时候,CPU的编译器会将数据从内存(RAM)中先读取到寄存器中,再传给变量,进行重复读取的时候,编译器认为数据没有发生变化,没必要从内存中重新读取,会直接使用寄存器中缓存的值赋值给变量。
而对于写入操作,如果不加__IO,编译器第一次调用会向内存中存入数据0x0C,存入数据成功后,同时LCD会产生相应的写入命令时序;而编译器第二次调用写入命令操作,会认为内存地址FSMC_Addr_ILI9341_CMD中的数据根本没有改变(因为上一次已经写入了0x0C),所以编译器会进行优化,认为没必要再次进行赋值操作,也就不再向内存地址中存入数据0x0C,因此LCD也就不会产生相应的写入命令时序。因此也就会导致LCD写入命令无效。同样读取数据和写入数据的操作同样要加__IO。
注:为了严谨起见,所有的内存操作都应该加上volatile。
图形绘制实现
两种方法:
方法一:首先要再显示器上开辟一个窗口,然后调用填充像素命令(0x2C),接着向窗口中写入像素值,从而实现图形的绘制。
开辟窗口函数
/**
* @brief 在ILI9341显示器上开辟一个窗口
* @param usX :在特定扫描方向下窗口的起点X坐标
* @param usY :在特定扫描方向下窗口的起点Y坐标
* @param usWidth :窗口的宽度
* @param usHeight :窗口的高度
* @retval 无
*/
void ILI9341_OpenWindow ( uint16_t usX, uint16_t usY, uint16_t usWidth, uint16_t usHeight )
{
ILI9341_Write_Cmd ( CMD_SetCoordinateX ); /* 设置X坐标 */
ILI9341_Write_Data ( usX >> 8 ); /* 先高8位,然后低8位 */
ILI9341_Write_Data ( usX & 0xff ); /* 设置起始点和结束点*/
ILI9341_Write_Data ( ( usX + usWidth - 1 ) >> 8 );
ILI9341_Write_Data ( ( usX + usWidth - 1 ) & 0xff );
ILI9341_Write_Cmd ( CMD_SetCoordinateY ); /* 设置Y坐标*/
ILI9341_Write_Data ( usY >> 8 );
ILI9341_Write_Data ( usY & 0xff );
ILI9341_Write_Data ( ( usY + usHeight - 1 ) >> 8 );
ILI9341_Write_Data ( ( usY + usHeight - 1) & 0xff );
}
方法二:使用填充单个像素点的操作,对相应位置进行像素填充,从而实现图形绘制
/**
* @brief 对ILI9341显示器的某一点以某种颜色进行填充
* @param usX :在特定扫描方向下该点的X坐标
* @param usY :在特定扫描方向下该点的Y坐标
* @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
* @retval 无
*/
void ILI9341_SetPointPixel ( uint16_t usX, uint16_t usY )
{
if ( ( usX < LCD_X_LENGTH ) && ( usY < LCD_Y_LENGTH ) )
{
ILI9341_SetCursor ( usX, usY );
ILI9341_FillColor ( 1, CurrentTextColor );
}
}
绘制矩形
方法一绘制实心矩形:
其中RGB888_2_RGB565(R,G,B) 函数可以显示自定义彩色
#define RGB888_2_RGB565(R,G,B) (uint16_t)(((R&1F)
方法二绘制空心矩形:
/**
* @brief 在 ILI9341 显示器上画一个矩形
* @param usX_Start :在特定扫描方向下矩形的起始点X坐标
* @param usY_Start :在特定扫描方向下矩形的起始点Y坐标
* @param usWidth:矩形的宽度(单位:像素)
* @param usHeight:矩形的高度(单位:像素)
* @param ucFilled :选择是否填充该矩形
* 该参数为以下值之一:
* @arg 0 :空心矩形
* @arg 1 :实心矩形
* @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
* @retval 无
*/
void ILI9341_DrawRectangle ( uint16_t usX_Start, uint16_t usY_Start, uint16_t usWidth, uint16_t usHeight, uint8_t ucFilled )
{
if ( ucFilled )
{
ILI9341_OpenWindow ( usX_Start, usY_Start, usWidth, usHeight );
ILI9341_FillColor ( usWidth * usHeight ,CurrentTextColor);
}
else
{
ILI9341_DrawLine ( usX_Start, usY_Start, usX_Start + usWidth - 1, usY_Start );
ILI9341_DrawLine ( usX_Start, usY_Start + usHeight - 1, usX_Start + usWidth - 1, usY_Start + usHeight - 1 );
ILI9341_DrawLine ( usX_Start, usY_Start, usX_Start, usY_Start + usHeight - 1 );
ILI9341_DrawLine ( usX_Start + usWidth - 1, usY_Start, usX_Start + usWidth - 1, usY_Start + usHeight - 1 );
}
}
绘制直线
方法一:
方法二:
/**
* @brief 在 ILI9341 显示器上使用 Bresenham 算法画线段
* @param usX1 :在特定扫描方向下线段的一个端点X坐标
* @param usY1 :在特定扫描方向下线段的一个端点Y坐标
* @param usX2 :在特定扫描方向下线段的另一个端点X坐标
* @param usY2 :在特定扫描方向下线段的另一个端点Y坐标
* @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
* @retval 无
*/
void ILI9341_DrawLine ( uint16_t usX1, uint16_t usY1, uint16_t usX2, uint16_t usY2 )
{
uint16_t us;
uint16_t usX_Current, usY_Current;
int32_t lError_X = 0, lError_Y = 0, lDelta_X, lDelta_Y, lDistance;
int32_t lIncrease_X, lIncrease_Y;
lDelta_X = usX2 - usX1; //计算坐标增量
lDelta_Y = usY2 - usY1;
usX_Current = usX1;
usY_Current = usY1;
if ( lDelta_X > 0 )
lIncrease_X = 1; //设置单步方向
else if ( lDelta_X == 0 )
lIncrease_X = 0;//垂直线
else
{
lIncrease_X = -1;
lDelta_X = - lDelta_X;
}
if ( lDelta_Y > 0 )
lIncrease_Y = 1;
else if ( lDelta_Y == 0 )
lIncrease_Y = 0;//水平线
else
{
lIncrease_Y = -1;
lDelta_Y = - lDelta_Y;
}
if ( lDelta_X > lDelta_Y )
lDistance = lDelta_X; //选取基本增量坐标轴
else
lDistance = lDelta_Y;
for ( us = 0; us <= lDistance + 1; us ++ )//画线输出
{
ILI9341_SetPointPixel ( usX_Current, usY_Current );//画点
lError_X += lDelta_X ;
lError_Y += lDelta_Y ;
if ( lError_X > lDistance )
{
lError_X -= lDistance;
usX_Current += lIncrease_X;
}
if ( lError_Y > lDistance )
{
lError_Y -= lDistance;
usY_Current += lIncrease_Y;
}
}
}
设置显示方向
/**
* @brief 设置ILI9341的GRAM的扫描方向
* @param ucOption :选择GRAM的扫描方向
* @arg 0-7 :参数可选值为0-7这八个方向
*
* !!!其中0、3、5、6 模式适合从左至右显示文字,
* 不推荐使用其它模式显示文字 其它模式显示文字会有镜像效果
*
* 其中0、2、4、6 模式的X方向像素为240,Y方向像素为320
* 其中1、3、5、7 模式下X方向像素为320,Y方向像素为240
*
* 其中 6 模式为大部分液晶例程的默认显示方向
* 其中 3 模式为摄像头例程使用的方向
* 其中 0 模式为BMP图片显示例程使用的方向
*
* @retval 无
* @note 坐标图例:A表示向上,V表示向下,<表示向左,>表示向右
X表示X轴,Y表示Y轴
*/
void ILI9341_GramScan ( uint8_t ucOption )
{
//参数检查,只可输入0-7
if(ucOption >7 )
return;
//根据模式更新LCD_SCAN_MODE的值,主要用于触摸屏选择计算参数
LCD_SCAN_MODE = ucOption;
//根据模式更新XY方向的像素宽度
if(ucOption%2 == 0)
{
//0 2 4 6模式下X方向像素宽度为240,Y方向为320
LCD_X_LENGTH = ILI9341_LESS_PIXEL;
LCD_Y_LENGTH = ILI9341_MORE_PIXEL;
}
else
{
//1 3 5 7模式下X方向像素宽度为320,Y方向为240
LCD_X_LENGTH = ILI9341_MORE_PIXEL;
LCD_Y_LENGTH = ILI9341_LESS_PIXEL;
}
//0x36命令参数的高3位可用于设置GRAM扫描方向
ILI9341_Write_Cmd ( 0x36 );
if(lcdid == LCDID_ILI9341)
{
ILI9341_Write_Data ( 0x08 |(ucOption<<5));//根据ucOption的值设置LCD参数,共0-7种模式
}
else if(lcdid == LCDID_ST7789V)
{
ILI9341_Write_Data ( 0x00 |(ucOption<<5));//根据ucOption的值设置LCD参数,共0-7种模式
}
ILI9341_Write_Cmd ( CMD_SetCoordinateX );
ILI9341_Write_Data ( 0x00 ); /* x 起始坐标高8位 */
ILI9341_Write_Data ( 0x00 ); /* x 起始坐标低8位 */
ILI9341_Write_Data ( ((LCD_X_LENGTH-1)>>8)&0xFF ); /* x 结束坐标高8位 */
ILI9341_Write_Data ( (LCD_X_LENGTH-1)&0xFF ); /* x 结束坐标低8位 */
ILI9341_Write_Cmd ( CMD_SetCoordinateY );
ILI9341_Write_Data ( 0x00 ); /* y 起始坐标高8位 */
ILI9341_Write_Data ( 0x00 ); /* y 起始坐标低8位 */
ILI9341_Write_Data ( ((LCD_Y_LENGTH-1)>>8)&0xFF ); /* y 结束坐标高8位 */
ILI9341_Write_Data ( (LCD_Y_LENGTH-1)&0xFF ); /* y 结束坐标低8位 */
/* write gram start */
ILI9341_Write_Cmd ( CMD_SetPixel );
}
重点补充
工程中使用0x6D00 0000地址向液晶屏发送数据,使用0x6C00 0000地址向液晶屏发送命令,但实际上使得地址线FSMC_A23输出高低电平的并不是只有这两个地址,因为只是用到了对应那个地址位的0,1与其他位无关,所以只要是那个bank的地址都可以。因此可以随便改变其它位的地址,只要这个不影响这个位和在地址范围内就行