STM32液晶显示控制代码讲解

目录

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

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32液晶显示控制代码讲解

    发表评论