LCD—STM32液晶显示(4.液晶控制代码讲解)

目录

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),接着向窗口中写入像素值,从而实现图形的绘制。

开辟窗口函数

cpp 复制代码
/**
 * @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 );
    
}

方法二:使用填充单个像素点的操作,对相应位置进行像素填充,从而实现图形绘制

cpp 复制代码
/**
 * @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)

方法二绘制空心矩形:

cpp 复制代码
/**
 * @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 );        
    }

}

绘制直线

方法一:

方法二:

cpp 复制代码
/**
 * @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; 
        } 
        
    }  
    
    
}   

设置显示方向

cpp 复制代码
/**
 * @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的地址都可以。因此可以随便改变其它位的地址,只要这个不影响这个位和在地址范围内就行

相关推荐
hairenjing11236 小时前
使用 Mac 数据恢复从 iPhoto 图库中恢复照片
windows·stm32·嵌入式硬件·macos·word
模拟IC攻城狮7 小时前
华为海思招聘-芯片与器件设计工程师-模拟芯片方向- 机试题-真题套题题目——共8套(每套四十题)
嵌入式硬件·华为·硬件架构·芯片
IT B业生7 小时前
51单片机教程(六)- LED流水灯
单片机·嵌入式硬件·51单片机
一枝小雨8 小时前
51单片机学习心得2(基于STC89C52):串口通信(UART)
单片机·嵌入式硬件·51单片机
IT B业生9 小时前
51单片机教程(一)- 开发环境搭建
单片机·嵌入式硬件·51单片机
u0101526589 小时前
STM32F103C8T6学习笔记2--LED流水灯与蜂鸣器
笔记·stm32·学习
海绵波波10711 小时前
Webserver(4.8)UDP、广播、组播
单片机·网络协议·udp
好想有猫猫11 小时前
【51单片机】串口通信原理 + 使用
c语言·单片机·嵌入式硬件·51单片机·1024程序员节
云卓科技11 小时前
无人车之路径规划篇
人工智能·嵌入式硬件·算法·自动驾驶
stm 学习ing12 小时前
C语言 循环高级
c语言·开发语言·单片机·嵌入式硬件·算法·嵌入式实时数据库