025 - STM32学习笔记 - 液晶屏控制(二) - 代码实现

025- STM32学习笔记 - 液晶屏控制(二) - 代码实现

好久没更新学习笔记了,最近工作上的事情太多了,趁着国庆中秋,多更新一点看看。
上节学习了关于LTDC与DMA2D以及显示屏的相关知识点,这节开始实操,首先了解一下LTDC的相关寄存器。

一、LTDC初始化结构体

使用LTDC需要配置很多寄存器,利用LTDC的初始化结构体可以快速的进行配置,结构体声明位于stm32f4xx_ltdc.h,LTDC初始化结构体如下:

/** 
  * @brief  LTDC初始化结构体
  * stm32f4xx_ltdc.h
  */
typedef struct
{
	uint32_t LTDC_HSPolarity;                 /* 配置行同步信号HSYNC的极性 */
	uint32_t LTDC_VSPolarity;                 /* 配置垂直同步信号VSYNC的极性 */
	uint32_t LTDC_DEPolarity;                 /* 配置数据使能信号DE的极性 */
	uint32_t LTDC_PCPolarity;                 /* 配置像素时钟信号CLK的极性 */
	uint32_t LTDC_HorizontalSync;             /* 配置行同步信号HSYNC的宽度(HSW-1) */
	uint32_t LTDC_VerticalSync;               /* 配置垂直同步信号VSYNC的宽度(VSW-1) */
	uint32_t LTDC_AccumulatedHBP;             /* 配置(HSW+HBP-1)的值 */
	uint32_t LTDC_AccumulatedVBP;             /* 配置(VSW+VBP-1)的值 */          
	uint32_t LTDC_AccumulatedActiveW;         /* 配置(HSW+HBP+有效宽度-1)的值 */
	uint32_t LTDC_AccumulatedActiveH;         /* 配置(VSW+VBP+有效高度-1)的值 */
	uint32_t LTDC_TotalWidth;                 /* 配置(HSW+HBP+有效宽度+HFP-1)的值 */
	uint32_t LTDC_TotalHeigh;                 /* 配置(VSW+VBP+有效高度+VFP-1)的值 */    
	uint32_t LTDC_BackgroundRedValue;         /* 配置背景的红色值 */
	uint32_t LTDC_BackgroundGreenValue;       /* 配置背景的绿色值 */ 
	uint32_t LTDC_BackgroundBlueValue;        /* 配置背景的蓝色值 */
} LTDC_InitTypeDef;

LTDC初始化结构体成员大部分用于定义LTDC的时序参数,包括信号有效电平及各种事件参数的宽度,配合液晶数据传输时序图中的说明更容易理解,这里对各个成员进行介绍:

LTDC_HSPolarity

用于设置行同步信号HSYNC的极性,即HSYNC有效时的电平,可以设置为高电平(LTDC_HSPolarity_AH)或者低电平(LTDC_HSPolarity_AL)。

LTDC_VSPolarity

同LTDC_HSPolarity一样,用来设置VSYNC有效时的电平,可以设置为高电平(LTDC_VSPolarity_AH)或者低电平(LTDC_VSPolarity_AL)。

LTDC_DEPolarity

用于设置数据使能信号 DE 的极性,可设置为高电平(LTDC_DEPolarity_AH)或者低电平(LTDC_DEPolarity_AL)。

LTDC_PCPolarity

用于设置像素时钟信号CLK的极性,可设置为上升沿(LTDC_DEPolarity_AH)或下降沿(LTDC_DEPolarity_AL),表示 RGB 数据信号在CLK的哪个时刻被采集。

LTDC_HorizontalSync

用于设置行同步信号HSYNC的宽度HSW, 它以像素时钟CLK的周期为单位,实际写入该参数时应写入(HSW-1),参数范围为0x000 - 0xFFF。

LTDC_VerticalSync

用于设置垂直同步信号VSYNC的宽度VSW,它以“行”为位,实际写入该参数时应写入(VSW-1) ,参数范围为0x000 - 0x7FF。

LTDC_AccumulatedHBP

用于配置“ 水平同步像素HSW” + “ 水平后沿像素HBP”的累加值,实际写入该参数时应写入(HSW+HBP-1) ,参数范围为 0x000- 0xFFF。

LTDC_AccumulatedVBP

用于配置“垂直同步行VSW”+“垂直后沿行VBP”的累加值,实际写入该参数时应写入(VSW+VBP-1) ,参数范围为 0x000- 0x7FF。

LTDC_AccumulatedActiveW

用于配置“水平同步像素HSW” +“ 水平后沿像素HBP” +“有效像素”的累加值,实际写入该参数时应写入(HSW+HBP+有效宽度-1) ,参数范围为0x000- 0xFFF。

LTDC_AccumulatedActiveH

用于配置“垂直同步行VSW”+“垂直后沿行VBP”+“有效行”的累加值,实际写入该参数时应写入(VSW+VBP+有效高度-1) ,参数范围为 0x000- 0x7FF。

LTDC_TotalWidth

本成员用于配置“水平同步像素HSW” +“水平后沿像素HBP” +“有效像素”+“水平前沿像素 HFP”的累加值,即总宽度,实际写入该参数时应写入(HSW+HBP+有效宽度+HFP-1) ,参数范围为0x000- 0xFFF。

LTDC_TotalHeigh

本成员用于配置“垂直同步行VSW”+“垂直后沿行VBP”+“有效行”+“垂直前沿行VFP”的累加值,即总高度,实际写入该参数时应写入(HSW+HBP+有效高度+VFP-1) ,参数范围为0x000- 0x7FF。

LTDC_BackgroundRedValue/ LTDC_BackgroundGreenValue/ LTDC_BackgroundBlueValue

这三个结构体成员用于配置背景的颜色值,这里说的背景层与上节内容中的“前景层与背景层”概念不一样,如下图:

025 - STM32学习笔记 - 液晶屏控制(二) - 代码实现_第1张图片

这里需要配置颜色指的背景层,指的是图中的“第2层/第1层”,而在这两层之外,还有一个最终的背景层,当第1层和第2层都透明时,这个背景层就会被显示,而这个背景层是一个纯色的矩形,其颜色值就是通过这三个结构体成员配置,各个参数的范围为0x00-0xFF。

除了LTDC初始化结构体外,还得配置LTDC层级初始化结构体。

二、LTDC层级初始化结构体

LTDC初始化结构体只是配置好与液晶屏通讯的基本时序,而LTDC层级初始化结构体则是用来配置像素格式、显存地址灯诸多参数,结构体声明位于stm32f4xx_ltdc.h,其结构如下:

/**
  * @brief LTDC Layer structure definition
  */
typedef struct
{
    uint32_t LTDC_HorizontalStart; 		/* 配置窗口的行起始位置 */
    uint32_t LTDC_HorizontalStop; 		/* 配置窗口的行结束位置 */
    uint32_t LTDC_VerticalStart; 		/* 配置窗口的垂直起始位置 */
    uint32_t LTDC_VerticalStop; 		/* 配置窗口的垂直束位置 */
    uint32_t LTDC_PixelFormat; 			/* 配置当前层的像素格式*/
    uint32_t LTDC_ConstantAlpha; 		/* 配置当前层的透明度Alpha常量值*/
    uint32_t LTDC_DefaultColorBlue; 	/* 配置当前层的默认蓝色值 */
    uint32_t LTDC_DefaultColorGreen; 	/* 配置当前层的默认绿色值 */
    uint32_t LTDC_DefaultColorRed; 		/* 配置当前层的默认红色值 */
    uint32_t LTDC_DefaultColorAlpha; 	/* 配置当前层的默认透明值 */
    uint32_t LTDC_BlendingFactor_1; 	/* 配置混合因子BlendingFactor1*/
    uint32_t LTDC_BlendingFactor_2; 	/* 配置混合因子BlendingFactor2 */
    uint32_t LTDC_CFBStartAdress; 		/* 配置当前层的显存起始位置*/
    uint32_t LTDC_CFBLineLength; 		/* 配置当前层的行数据长度 */
    uint32_t LTDC_CFBPitch; 		    /* 配置从某行的起始到下一行像素起始处的增量*/
    uint32_t LTDC_CFBLineNumber; 		/* 配置当前层的行数*/
}LTDC_Layer_InitTypeDef;

LTDC_HorizontalStart /LTDC_HorizontalStop/ LTDC_VerticalStart/ LTDC_VerticalStop

这四个成员用于确定该层显示窗口的边界,分别表示行起始、行结束、垂直起始和垂直结束的位置。如下图:

025 - STM32学习笔记 - 液晶屏控制(二) - 代码实现_第2张图片

这些参数包含同步HSW/VSW、后沿大小HBP/VBP和有效数据区域的内部时许发生器的配置,其可写入的值如下表:

LTDC 层级窗口配置 成员 等效于 LTDC 时序参数 配置成员的值 实际值
LTDC_HorizontalStart (LTDC_AccumulatedHBP+1) HBP + HSW
LTDC_HorizontalStop LTDC_AccumulatedActiveW HSW+HBP+LCD_PIXEL_WIDTH - 1
LTDC_VerticalStart (LTDC_AccumulatedVBP+1) VBP + VSW
LTDC_VerticalStop LTDC_AccumulatedActiveH VSW+VBP+LCD_PIXEL_HEIGHT- 1

LTDC_PixelFormat

本成员用于设置该层数据的像素格式,可以设置为LTDC_Pixelformat_ARGB8888/RGB888/RGB565/ARGB1555/ ARGB4444/L8/AL44/AL88 格式。

LTDC_ConstantAlpha

用于设置该层恒定的透明度常量Alpha,称为恒定Alpha,参数范围为0x00-0xFF,在图层混合时,可根据后面的BlendingFactor成员的配置,选择是只使用这个恒定Alpha进行混合运算还是把像素本身的Alpha值也加入到运算中。

LTDC_DefaultColorBlue/LTDC_DefaultColorGreen/LTDC_DefaultColorRed/LTDC_DefaultColorAlpha

这些成员用于配置该层的默认颜色值,分别为蓝、绿、红及透明分量,该颜色在定义的层窗口外或在层禁止时使用。

LTDC_BlendingFactor_1/LTDC_BlendingFactor_2

本成员用于设置混合系数BF1和BF2。每一层实际显示的颜色都需要使用透明度参与运算,计算出不包含透明度的直接RGB颜色值,然后才传输给液晶屏(因为液晶屏本身没有透明的概念)。混合的计算公式为:
B C = B F 1 ∗ C + B F 2 ∗ C s BC = BF1 * C + BF2 * Cs BC=BF1C+BF2Cs

参数 说明 CA PAxCA
BC 混合后的颜色 (混合结 果) - -
C 当前层颜色 - -
Cs 底层混合后的颜色 - -
BF1 混合系数 1 等于(恒 定 Alpha值) 等于(恒定Alpha x 像素Alpha 值)
BF2 混合系数 2 等于(1-恒定 Alpha) 等于(1-恒定Alpha x 像素Alpha 值)

用于设置BF1/BF2参数使用CA配置还是PAxCA 配置。配置成CA表示混合系数中只包含恒定的Alpha值, 即像素本身的Alpha不会影响混合效果,若配置成PAxCA,则混合系数中包含有像素本身的Alpha值,即把像素本身的Alpha加入到混合运算中。其中的恒定Alpha值即前面“LTDC_ConstantAlpha” 结构体配置参数的透明度百分比: (配置的Alpha值/0xFF)。

配置值为:

CA:LTDC_BlendingFactor1_CA 和 LTDC_BlendingFactor2_CA

PAxCA:LTDC_BlendingFactor1_PAxCA 和 LTDC_BlendingFactor2_PAxCA

025 - STM32学习笔记 - 液晶屏控制(二) - 代码实现_第3张图片

入上图,数据源混合时,如果数据源有两层,首先会将第1层与背景混合,然后再将第2层与前面混合的图层再次混合从而得到最终图像。

LTDC_CFBStartAdress

用于设置该层的显存首地址,该层的像素数据保存在从这个地址开始的存储空间内。

LTDC_CFBLineLength

用于设置当前层的行数据长度,即每行的有效像素点个数 x 每个像素的字节数,实际配置该参数时应写入值(行有效像素个数 x 每个像素的字节数+3),每个像素的字节数跟像素格式有关,如 RGB565 为 2 字节, RGB888 为 3 字节, ARGB8888 为 4 字节。

LTDC_CFBPitch

用于设置从某行的有效像素起始位置到下一行起始位置处的数据增量,无特殊情况的话,它一般就直接等于行的有效像素个数 x 每个像素的字节数。

LTDC_CFBLineNumber

用于设置当前层的显示行数。

LTDC_Layer_InitTypeDef层级初始化结构体配置完成后,可以调用LTDC_LayerInit()函数将配置写入到LTDC的层级控制器中完成初始化,之后LTDC就会将现存空间的图像数据不停的发送到显示屏上,下来我们就可以通过操作DMA2D来修改显存中的数据了。

三、DMA2D初始化结构体

使用DMA2D之前,需要对DMA2D进行配置初始化,结构体声明位于stm32f4xx_dma2d.h中,结构如下:

/** 
  * @brief  DMA2D Init structure definition  
  */
typedef struct
{
    uint32_t DMA2D_Mode;                           /* 配置DMA2D传输模式 */
    uint32_t DMA2D_CMode;                          /* 配置DMA2D颜色模式 */
    uint32_t DMA2D_OutputBlue;                     /* 配置输出图像的蓝色分量 */
    uint32_t DMA2D_OutputGreen;                    /* 配置输出图像的绿色分量   */      
    uint32_t DMA2D_OutputRed;                      /* 配置输出图像的红色分量  */
    uint32_t DMA2D_OutputAlpha;                    /* 配置输出图像的透明度分量 */
    uint32_t DMA2D_OutputMemoryAdd;                /* 配置显存地址 0x00000000 to 0xFFFFFFFF. */
    uint32_t DMA2D_OutputOffset;                   /* 配置输出地址偏移 */
    uint32_t DMA2D_NumberOfLine;                   /* 配置要传输多少行 */          
    uint32_t DMA2D_PixelPerLine;                   /* 配置每行有多少个像素 */
}DMA2D_InitTypeDef;

DMA2D_Mode

用于配置DMA2D的工作模式,可以设置的值如下:

说明
DMA2D_M2M 从存储器到存储器(仅限FG获取数据源)
DMA2D_M2M_PFC 存储器到存储器并执行 PFC(仅限FG PFC激活时的FG获 取)
DMA2D_M2M_BLEND 存储器到存储器并执行混合(执行PFC和混合时的FG和BG获取)
DMA2D_R2M 寄存器到存储器(无FG和BG,仅输出阶段激活)

以上这四种模式主要区分数据的来源、是否使能PFC以及是否使能混合器,使用DMA2D时,可以将数据从某个位置搬运到现存,源位置可以时DMA2D本身的寄存器,也可以是设置好的DMA2D前景地址、背景地址。如果是能了PFC,则存储器中的数据源会经过转换后在送到显存,如果使能混合器,DMA2D会把两个数据源中的数据混合后再送到显存。

若使用存储器到存储器模式,需要调用库函数DMA2D_FGConfig,使用初始化结构体DMA2D_FG_InitTypeDef配置数据源的格式、地址等参数。背景层使用函数DMA2D_BGConfig和结构体DMA2D_BG_InitTypeDef)

DMA2D_CMode

用于配置DMA2D的输出PFC颜色格式,也就是将要传输给显存的格式。

DMA2D_OutputBlue/DMA2D_OutputGreen/DMA2D_OutputRed/DMA2D_OutputAlpha

用于配置DMA2D的输出颜色值,如果DMA2D工作模式设置为寄存器到存储器模式,则此颜色值作为数据源,会被DA2D复制到显存空间,目标空间就会被填入这一种颜色。

DMA2D_OutputMemoryAdd

用于配置DMA2D的输出FIFO的地址,DMA2D的数据会被搬运到该空间,一般将此设置为传输显示位置的起始地址。

DMA2D_OutputOffset

用于配置行偏移,行偏移会被添加到各行的结尾,用于确定下一行的起始地址,如下,绿色表示要显示的像素列,黄色表示行偏移,,假如左边显示的是一条竖线,竖线的宽度为1像素,所以行偏移的值为7-1=6,右边的线宽为2像素,行偏移的值为7-2=5,这样可以总结
行偏移的值 = 行宽度 − 线宽 行偏移的值 = 行宽度 - 线宽 行偏移的值=行宽度线宽
025 - STM32学习笔记 - 液晶屏控制(二) - 代码实现_第4张图片

DMA2D_NumberOfLine

用于配置 DMA2D 一共要传输多少行数,如上图中,一共有8行数据。

DMA2D_PixelPerLine

用于配置每行有多少个像素点,如上图左侧表示每行有1个像素点,右侧表示每行有2个像素点。

四、硬件实现

野火F429上使用的LCD屏幕型号为“STD800480”的5寸液晶触摸屏,其显示分辨率为800x480,支持RGB888格式。关于其供电部分这里不说明了,感兴趣的可以看看野火官方原理图,这里对液晶触摸屏的接口部分大致说明一下:

025 - STM32学习笔记 - 液晶屏控制(二) - 代码实现_第5张图片

接口电路中,使用了I2C通讯获取触摸屏的触控信号,排线包含了I2C的通讯引脚SCL(1)、SDA(2),同时还有控制触摸屏芯片复位的RSTN(4)以及触摸中断信号INT(3)。

从引脚6到引脚35,就是上面我们学到的所有关于LCD的控制引脚了,里面包含了RGB信号线[35:12]、同步时钟信号[11]\垂直/水平同步[10:9]、使能信号[8]、显示器开关[7]、背光控制[6]。

液晶屏部分引脚(B[6]、B[7])两个引脚同时也是CAN通讯的引脚,所以再使用液晶屏的时候不能使用CAN通讯。

五、软件设计

上面内容学习完后,就可以开始设计控制代码了,这里跟以前一样,还是通过建立板级支持包(bsp_lcd.c和bsp_lcd.h)来对LCD屏幕控制,方便以后移植。

编程要点:

a、初始化LTDC时钟、DMA2D时钟、GPIO时钟;

b、初始化SDRAM,用作为显存;

c、根据液晶屏的参数配置LTDC外设的通讯时序;

d、配置LTDC层级控制参数,配置显存地址;

e、初始化DMA2D,使用DMA2D辅助显示;

f、编写测试程序,控制液晶输出。

1、LTDC硬件宏定义

本来想像之前做SDRAM的时候一样,把所有引脚都做个表列出来,但是上次引脚太多了,列个表太痛苦了。这次就不列表格了,直接把宏定义贴出来各位看一下吧。

/* 红色数据线 */
//R0 - PH2
#define LTDC_R0_GPIO_PORT                   GPIOH
#define LTDC_R0_GPIO_CLK                    RCC_AHB1Periph_GPIOH
#define LTDC_R0_GPIO_PIN                    GPIO_Pin_2
#define LTDC_R0_GPIO_PINSOURCE              GPIO_PinSource2
#define LTDC_R0_AF                          GPIO_AF_LTDC

//R1 - PH3
#define LTDC_R1_GPIO_PORT                   GPIOH
#define LTDC_R1_GPIO_CLK                    RCC_AHB1Periph_GPIOH
#define LTDC_R1_GPIO_PIN                    GPIO_Pin_3
#define LTDC_R1_GPIO_PINSOURCE              GPIO_PinSource3
#define LTDC_R1_AF                          GPIO_AF_LTDC

//R2 - PH8
#define LTDC_R2_GPIO_PORT                   GPIOH
#define LTDC_R2_GPIO_CLK                    RCC_AHB1Periph_GPIOH
#define LTDC_R2_GPIO_PIN                    GPIO_Pin_8
#define LTDC_R2_GPIO_PINSOURCE              GPIO_PinSource8
#define LTDC_R2_AF                          GPIO_AF_LTDC

//R3 - PB0
#define LTDC_R3_GPIO_PORT                   GPIOB
#define LTDC_R3_GPIO_CLK                    RCC_AHB1Periph_GPIOB
#define LTDC_R3_GPIO_PIN                    GPIO_Pin_0
#define LTDC_R3_GPIO_PINSOURCE              GPIO_PinSource0
#define LTDC_R3_AF                          GPIO_AF9_LTDC

//R4 - PA11
#define LTDC_R4_GPIO_PORT                   GPIOA
#define LTDC_R4_GPIO_CLK                    RCC_AHB1Periph_GPIOA
#define LTDC_R4_GPIO_PIN                    GPIO_Pin_11
#define LTDC_R4_GPIO_PINSOURCE              GPIO_PinSource11
#define LTDC_R4_AF                          GPIO_AF_LTDC

//R5 - A12
#define LTDC_R5_GPIO_PORT                   GPIOA
#define LTDC_R5_GPIO_CLK                    RCC_AHB1Periph_GPIOA
#define LTDC_R5_GPIO_PIN                    GPIO_Pin_12
#define LTDC_R5_GPIO_PINSOURCE              GPIO_PinSource12
#define LTDC_R5_AF                          GPIO_AF_LTDC

//R6 - PB1
#define LTDC_R6_GPIO_PORT                   GPIOB
#define LTDC_R6_GPIO_CLK                    RCC_AHB1Periph_GPIOB
#define LTDC_R6_GPIO_PIN                    GPIO_Pin_1
#define LTDC_R6_GPIO_PINSOURCE              GPIO_PinSource1
#define LTDC_R6_AF                          GPIO_AF9_LTDC

//R7 - PG6
#define LTDC_R7_GPIO_PORT                   GPIOG
#define LTDC_R7_GPIO_CLK                    RCC_AHB1Periph_GPIOG
#define LTDC_R7_GPIO_PIN                    GPIO_Pin_6
#define LTDC_R7_GPIO_PINSOURCE              GPIO_PinSource6
#define LTDC_R7_AF                          GPIO_AF_LTDC

/* 绿色数据线 */
//G0 - PE5
#define LTDC_G0_GPIO_PORT                   GPIOE
#define LTDC_G0_GPIO_CLK                    RCC_AHB1Periph_GPIOE
#define LTDC_G0_GPIO_PIN                    GPIO_Pin_5
#define LTDC_G0_GPIO_PINSOURCE              GPIO_PinSource5
#define LTDC_G0_AF                          GPIO_AF_LTDC

//G1 - PE6
#define LTDC_G1_GPIO_PORT                   GPIOE
#define LTDC_G1_GPIO_CLK                    RCC_AHB1Periph_GPIOE
#define LTDC_G1_GPIO_PIN                    GPIO_Pin_6
#define LTDC_G1_GPIO_PINSOURCE              GPIO_PinSource6
#define LTDC_G1_AF                          GPIO_AF_LTDC

//G2 - PH13
#define LTDC_G2_GPIO_PORT                   GPIOH
#define LTDC_G2_GPIO_CLK                    RCC_AHB1Periph_GPIOH
#define LTDC_G2_GPIO_PIN                    GPIO_Pin_13
#define LTDC_G2_GPIO_PINSOURCE              GPIO_PinSource13
#define LTDC_G2_AF                          GPIO_AF_LTDC

//G3 - PG10
#define LTDC_G3_GPIO_PORT                   GPIOG
#define LTDC_G3_GPIO_CLK                    RCC_AHB1Periph_GPIOG
#define LTDC_G3_GPIO_PIN                    GPIO_Pin_10
#define LTDC_G3_GPIO_PINSOURCE              GPIO_PinSource10
#define LTDC_G3_AF                          GPIO_AF9_LTDC

//G4 - PH15
#define LTDC_G4_GPIO_PORT                   GPIOH
#define LTDC_G4_GPIO_CLK                    RCC_AHB1Periph_GPIOH
#define LTDC_G4_GPIO_PIN                    GPIO_Pin_15
#define LTDC_G4_GPIO_PINSOURCE              GPIO_PinSource15
#define LTDC_G4_AF                          GPIO_AF_LTDC

//G5 - PI0
#define LTDC_G5_GPIO_PORT                   GPIOI
#define LTDC_G5_GPIO_CLK                    RCC_AHB1Periph_GPIOI
#define LTDC_G5_GPIO_PIN                    GPIO_Pin_0
#define LTDC_G5_GPIO_PINSOURCE              GPIO_PinSource0
#define LTDC_G5_AF                          GPIO_AF_LTDC

//G6 - PC7
#define LTDC_G6_GPIO_PORT                   GPIOC
#define LTDC_G6_GPIO_CLK                    RCC_AHB1Periph_GPIOC
#define LTDC_G6_GPIO_PIN                    GPIO_Pin_7
#define LTDC_G6_GPIO_PINSOURCE              GPIO_PinSource7
#define LTDC_G6_AF                          GPIO_AF_LTDC

//G7 - PI2
#define LTDC_G7_GPIO_PORT                   GPIOI
#define LTDC_G7_GPIO_CLK                    RCC_AHB1Periph_GPIOI
#define LTDC_G7_GPIO_PIN                    GPIO_Pin_2
#define LTDC_G7_GPIO_PINSOURCE              GPIO_PinSource2
#define LTDC_G7_AF                          GPIO_AF_LTDC

/* 蓝色数据线 */
//B0 - PE4
#define LTDC_B0_GPIO_PORT                   GPIOE
#define LTDC_B0_GPIO_CLK                    RCC_AHB1Periph_GPIOE
#define LTDC_B0_GPIO_PIN                    GPIO_Pin_4
#define LTDC_B0_GPIO_PINSOURCE              GPIO_PinSource4
#define LTDC_B0_AF                          GPIO_AF_LTDC

//B1 - PG12
#define LTDC_B1_GPIO_PORT                   GPIOG
#define LTDC_B1_GPIO_CLK                    RCC_AHB1Periph_GPIOG
#define LTDC_B1_GPIO_PIN                    GPIO_Pin_12
#define LTDC_B1_GPIO_PINSOURCE              GPIO_PinSource12
#define LTDC_B1_AF                          GPIO_AF_LTDC

//B2 - PD6
#define LTDC_B2_GPIO_PORT                   GPIOD
#define LTDC_B2_GPIO_CLK                    RCC_AHB1Periph_GPIOD
#define LTDC_B2_GPIO_PIN                    GPIO_Pin_6
#define LTDC_B2_GPIO_PINSOURCE              GPIO_PinSource6
#define LTDC_B2_AF                          GPIO_AF_LTDC

//B3 - PG11
#define LTDC_B3_GPIO_PORT                   GPIOG
#define LTDC_B3_GPIO_CLK                    RCC_AHB1Periph_GPIOG
#define LTDC_B3_GPIO_PIN                    GPIO_Pin_11
#define LTDC_B3_GPIO_PINSOURCE              GPIO_PinSource11
#define LTDC_B3_AF                          GPIO_AF_LTDC

//B4 - PI4
#define LTDC_B4_GPIO_PORT                   GPIOI
#define LTDC_B4_GPIO_CLK                    RCC_AHB1Periph_GPIOI
#define LTDC_B4_GPIO_PIN                    GPIO_Pin_4
#define LTDC_B4_GPIO_PINSOURCE              GPIO_PinSource4
#define LTDC_B4_AF                          GPIO_AF_LTDC

//B5 - PA3
#define LTDC_B5_GPIO_PORT                   GPIOA
#define LTDC_B5_GPIO_CLK                    RCC_AHB1Periph_GPIOA
#define LTDC_B5_GPIO_PIN                    GPIO_Pin_3
#define LTDC_B5_GPIO_PINSOURCE              GPIO_PinSource3
#define LTDC_B5_AF                          GPIO_AF_LTDC

//B6 - PB8   CAN1_RX
#define LTDC_B6_GPIO_PORT                   GPIOB
#define LTDC_B6_GPIO_CLK                    RCC_AHB1Periph_GPIOB
#define LTDC_B6_GPIO_PIN                    GPIO_Pin_8
#define LTDC_B6_GPIO_PINSOURCE              GPIO_PinSource8
#define LTDC_B6_AF                          GPIO_AF_LTDC

//B7 - PB9   CAN1_TX
#define LTDC_B7_GPIO_PORT                   GPIOB
#define LTDC_B7_GPIO_CLK                    RCC_AHB1Periph_GPIOB
#define LTDC_B7_GPIO_PIN                    GPIO_Pin_9
#define LTDC_B7_GPIO_PINSOURCE              GPIO_PinSource9
#define LTDC_B7_AF                          GPIO_AF_LTDC

//像素时钟信号CLK - PG7
#define LTDC_CLK_GPIO_PORT                   GPIOG
#define LTDC_CLK_GPIO_CLK                    RCC_AHB1Periph_GPIOG
#define LTDC_CLK_GPIO_PIN                    GPIO_Pin_7
#define LTDC_CLK_GPIO_PINSOURCE              GPIO_PinSource7
#define LTDC_CLK_AF                          GPIO_AF_LTDC

//水平同步信号HSYNC - PI10
#define LTDC_HSYNC_GPIO_PORT                   GPIOI
#define LTDC_HSYNC_GPIO_CLK                    RCC_AHB1Periph_GPIOI
#define LTDC_HSYNC_GPIO_PIN                    GPIO_Pin_10
#define LTDC_HSYNC_GPIO_PINSOURCE              GPIO_PinSource10
#define LTDC_HSYNC_AF                          GPIO_AF_LTDC

//垂直同步信号VSYNC - PI9
#define LTDC_VSYNC_GPIO_PORT                   GPIOI
#define LTDC_VSYNC_GPIO_CLK                    RCC_AHB1Periph_GPIOI
#define LTDC_VSYNC_GPIO_PIN                    GPIO_Pin_9
#define LTDC_VSYNC_GPIO_PINSOURCE              GPIO_PinSource9
#define LTDC_VSYNC_AF                          GPIO_AF_LTDC

//数据使能信号DE - PF10
#define LTDC_DE_GPIO_PORT                   GPIOF
#define LTDC_DE_GPIO_CLK                    RCC_AHB1Periph_GPIOF
#define LTDC_DE_GPIO_PIN                    GPIO_Pin_10
#define LTDC_DE_GPIO_PINSOURCE              GPIO_PinSource10
#define LTDC_DE_AF                          GPIO_AF_LTDC

//液晶屏使能信号,高电平使能,DISP - PD4
#define LCD_DISP_GPIO_PORT                   GPIOD
#define LCD_DISP_GPIO_CLK                    RCC_AHB1Periph_GPIOD
#define LCD_DISP_GPIO_PIN                    GPIO_Pin_4
#define LCD_DISP_GPIO_PINSOURCE              GPIO_PinSource4

//液晶屏背光信号高电平使能 BL - PD7
#define LCD_BL_GPIO_PORT                   GPIOD
#define LCD_BL_GPIO_CLK                    RCC_AHB1Periph_GPIOD
#define LCD_BL_GPIO_PIN                    GPIO_Pin_7
#define LCD_BL_GPIO_PINSOURCE              GPIO_PinSource7

这里需要注意,在引脚的复用功能选择上,都选择为LTDC,在标准库中,GPIO_AF_LTDC默认映射为AF14,而我们使用到的部分引脚映射为AF9,区别如下:

#define GPIO_AF_LTDC          ((uint8_t)0x0E)			//默认映射为AF14
#define GPIO_AF9_LTDC         ((uint8_t)0x09)			//部分映射为AF9

因此此处需要注意,要根据实际使用的引脚来做映射。

2、引脚初始化

在宏定义好这些GPIO引脚后,就可以编写引脚初始化函数了,此段代码基本就是GPIO的初始化代码,比较简单,这里就不讲解了,大家看一下就行,代码如下:

/**
  * @brief  初始化LCD GPIO引脚
  * @parm   无
  * @retval 无
  */
void LCD_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    /* 开启所有LCD相关GPIO的时钟,再次声明,只要是外设操作,一定要先开时钟 */
    RCC_AHB1PeriphClockCmd(
                           /* 红色信号线 */
                           LTDC_R0_GPIO_CLK | LTDC_R1_GPIO_CLK | LTDC_R2_GPIO_CLK
                          |LTDC_R3_GPIO_CLK | LTDC_R4_GPIO_CLK | LTDC_R5_GPIO_CLK
                          |LTDC_R6_GPIO_CLK | LTDC_R7_GPIO_CLK
                           /* 绿色信号线 */
                          |LTDC_G0_GPIO_CLK | LTDC_G1_GPIO_CLK | LTDC_G2_GPIO_CLK
                          |LTDC_G3_GPIO_CLK | LTDC_G4_GPIO_CLK | LTDC_G5_GPIO_CLK
                          |LTDC_G6_GPIO_CLK | LTDC_G7_GPIO_CLK
                           /* 蓝色信号线 */
                          |LTDC_B0_GPIO_CLK | LTDC_B1_GPIO_CLK | LTDC_B2_GPIO_CLK
                          |LTDC_B3_GPIO_CLK | LTDC_B4_GPIO_CLK | LTDC_B5_GPIO_CLK
                          |LTDC_B6_GPIO_CLK | LTDC_B7_GPIO_CLK
                           /* 控制、使能及背光控制信号线 */
                          |LTDC_CLK_GPIO_CLK | LTDC_HSYNC_GPIO_CLK | LTDC_VSYNC_GPIO_CLK
                          |LTDC_DE_GPIO_CLK  | LCD_DISP_GPIO_CLK   | LCD_BL_GPIO_CLK
                          ,ENABLE);
    /* GPIO通用配置 */
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    
    /* R0配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_R0_GPIO_PIN;
    GPIO_Init(LTDC_R0_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_R0_GPIO_PORT,LTDC_R0_GPIO_PINSOURCE,LTDC_R0_AF);

    /* R1配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_R1_GPIO_PIN;
    GPIO_Init(LTDC_R1_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_R1_GPIO_PORT,LTDC_R1_GPIO_PINSOURCE,LTDC_R1_AF);

    /* R2配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_R2_GPIO_PIN;
    GPIO_Init(LTDC_R2_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_R2_GPIO_PORT,LTDC_R2_GPIO_PINSOURCE,LTDC_R2_AF);

    /* R3配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_R3_GPIO_PIN;
    GPIO_Init(LTDC_R3_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_R3_GPIO_PORT,LTDC_R3_GPIO_PINSOURCE,LTDC_R3_AF);

    /* R4配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_R4_GPIO_PIN;
    GPIO_Init(LTDC_R4_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_R4_GPIO_PORT,LTDC_R4_GPIO_PINSOURCE,LTDC_R4_AF);

    /* R5配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_R5_GPIO_PIN;
    GPIO_Init(LTDC_R5_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_R5_GPIO_PORT,LTDC_R5_GPIO_PINSOURCE,LTDC_R5_AF);
    
    /* R6配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_R6_GPIO_PIN;
    GPIO_Init(LTDC_R6_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_R6_GPIO_PORT,LTDC_R6_GPIO_PINSOURCE,LTDC_R6_AF);
    
    /* R7配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_R7_GPIO_PIN;
    GPIO_Init(LTDC_R7_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_R7_GPIO_PORT,LTDC_R7_GPIO_PINSOURCE,LTDC_R7_AF);
    
    /* G0配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_G0_GPIO_PIN;
    GPIO_Init(LTDC_G0_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_G0_GPIO_PORT,LTDC_G0_GPIO_PINSOURCE,LTDC_G0_AF);
    
    /* G1配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_G1_GPIO_PIN;
    GPIO_Init(LTDC_G1_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_G1_GPIO_PORT,LTDC_G1_GPIO_PINSOURCE,LTDC_G1_AF);
    
    /* G2配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_G2_GPIO_PIN;
    GPIO_Init(LTDC_G2_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_G2_GPIO_PORT,LTDC_G2_GPIO_PINSOURCE,LTDC_G2_AF);
    
    /* G3配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_G3_GPIO_PIN;
    GPIO_Init(LTDC_G3_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_G3_GPIO_PORT,LTDC_G3_GPIO_PINSOURCE,LTDC_G3_AF);
    
    /* G4配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_G4_GPIO_PIN;
    GPIO_Init(LTDC_G4_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_G4_GPIO_PORT,LTDC_G4_GPIO_PINSOURCE,LTDC_G4_AF);
    
    /* G5配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_G5_GPIO_PIN;
    GPIO_Init(LTDC_G5_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_G5_GPIO_PORT,LTDC_G5_GPIO_PINSOURCE,LTDC_G5_AF);
    
    /* G6配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_G6_GPIO_PIN;
    GPIO_Init(LTDC_G6_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_G6_GPIO_PORT,LTDC_G6_GPIO_PINSOURCE,LTDC_G6_AF);
    
    /* G7配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_G7_GPIO_PIN;
    GPIO_Init(LTDC_G7_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_G7_GPIO_PORT,LTDC_G7_GPIO_PINSOURCE,LTDC_G7_AF);
    
    /* B0配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_B0_GPIO_PIN;
    GPIO_Init(LTDC_B0_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_B0_GPIO_PORT,LTDC_B0_GPIO_PINSOURCE,LTDC_B0_AF);
    
    /* B1配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_B1_GPIO_PIN;
    GPIO_Init(LTDC_B1_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_B1_GPIO_PORT,LTDC_B1_GPIO_PINSOURCE,LTDC_B1_AF);
    
    /* B2配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_B2_GPIO_PIN;
    GPIO_Init(LTDC_B2_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_B2_GPIO_PORT,LTDC_B2_GPIO_PINSOURCE,LTDC_B2_AF);
    
    /* B3配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_B3_GPIO_PIN;
    GPIO_Init(LTDC_B3_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_B3_GPIO_PORT,LTDC_B3_GPIO_PINSOURCE,LTDC_B3_AF);
    
    /* B4配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_B4_GPIO_PIN;
    GPIO_Init(LTDC_B4_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_B4_GPIO_PORT,LTDC_B4_GPIO_PINSOURCE,LTDC_B4_AF);
    
    /* B5配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_B5_GPIO_PIN;
    GPIO_Init(LTDC_B5_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_B5_GPIO_PORT,LTDC_B5_GPIO_PINSOURCE,LTDC_B5_AF);
    
    /* B6配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_B6_GPIO_PIN;
    GPIO_Init(LTDC_B6_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_B6_GPIO_PORT,LTDC_B6_GPIO_PINSOURCE,LTDC_B6_AF);
    
    /* B7配置,并连接到复用功能 */
    GPIO_InitStruct.GPIO_Pin = LTDC_B7_GPIO_PIN;
    GPIO_Init(LTDC_B7_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_B7_GPIO_PORT,LTDC_B7_GPIO_PINSOURCE,LTDC_B7_AF);

    /* 时钟信号线 */
    GPIO_InitStruct.GPIO_Pin = LTDC_CLK_GPIO_PIN;
    GPIO_Init(LTDC_CLK_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_CLK_GPIO_PORT,LTDC_CLK_GPIO_PINSOURCE,LTDC_CLK_AF);

    /* 水平同步信号线 */
    GPIO_InitStruct.GPIO_Pin = LTDC_HSYNC_GPIO_PIN;
    GPIO_Init(LTDC_HSYNC_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_HSYNC_GPIO_PORT,LTDC_HSYNC_GPIO_PINSOURCE,LTDC_HSYNC_AF);
    
    /* 垂直同步信号线 */
    GPIO_InitStruct.GPIO_Pin = LTDC_VSYNC_GPIO_PIN;
    GPIO_Init(LTDC_VSYNC_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_VSYNC_GPIO_PORT,LTDC_VSYNC_GPIO_PINSOURCE,LTDC_VSYNC_AF);

    /* 数据使能信号线 */
    GPIO_InitStruct.GPIO_Pin = LTDC_DE_GPIO_PIN;
    GPIO_Init(LTDC_DE_GPIO_PORT,&GPIO_InitStruct);
    GPIO_PinAFConfig(LTDC_DE_GPIO_PORT,LTDC_DE_GPIO_PINSOURCE,LTDC_DE_AF);
    
    /* 液晶屏信号线 */
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
    
    /* 液晶屏使能信号线 */
    GPIO_InitStruct.GPIO_Pin = LCD_DISP_GPIO_PIN;
    GPIO_Init(LCD_DISP_GPIO_PORT,&GPIO_InitStruct);
    
    /* 液晶屏背光信号线 */
    GPIO_InitStruct.GPIO_Pin = LCD_BL_GPIO_PIN;
    GPIO_Init(LCD_BL_GPIO_PORT,&GPIO_InitStruct);

    /* 拉高使能LCD */
    GPIO_SetBits(LCD_DISP_GPIO_PORT,LCD_DISP_GPIO_PIN);
    GPIO_SetBits(LCD_BL_GPIO_PORT,LCD_BL_GPIO_PIN);
}
3、LCD参数配置

首先看一下LCD的时钟树,在上节中我们也大概看过,这里针对性的说明一下:

025 - STM32学习笔记 - 液晶屏控制(二) - 代码实现_第6张图片

从上图中可以看到,LCD的时钟控制是通过HSE,经过M分频因子输出到PLLSAI分频器,有PLLSAI中的倍频因子N得到”LCD-TFT clock“,这里的”LCD-TFT clock“就是通讯中的同步时钟LCD_CLK,通过LCD_CLK引脚输出,按照默认设置,分频因子会把HSE分频得到1MHz的时钟,这里HSE外部高速时钟为25MHz,把M设置为25,经过M分频得到的1MHz时钟输入到PLLSAI分频器后,使用倍频因子N倍频,在经过R因子分频,得到PLLLCDCLK时钟,再由DIV因子分频得到LTDC通讯的同步时钟LCD_CLK,公式如下:
f L C D C L K = f H S E / M ∗ N / R / D I V fLCDCLK = fHSE/M*N/R/DIV fLCDCLK=fHSE/MN/R/DIV
经过M分频后,得到的时钟为1MHz的时钟,所以上面公式等价为:
f L C D C L K = 1 ∗ N / R / D I V fLCDCLK = 1*N/R/DIV fLCDCLK=1N/R/DIV
利用库函数RCC_PLLSAIConfig及RCC_LTDCCLKDivConfig函数可以配置PLLSAI分频器的这些参数,其中库函数RCC_PLLSAIConfig的三个输入参数分别为倍频因子N、分频因子Q和分频因子R,其中Q因子是作为SAI接口的分频时钟,与LTDC无关,RCC_LTDCCLKDivConfig函数的输入参数为分频因子DIV,在配置后,需要调用RCC_PLLSAICmd使能PLLSAI的时钟,并在检测位等待时钟初始化完成,根据库函数中可知,PLLSAI_N设置值范围为[50,432],PLLSAI_R设置范围为[2,7],PLLSAI_R设置范围为[2,15],PLLSAI_DIV可选值为2、4、8、16,在野火提供的5寸屏关于时钟频率的要求上限为50MHz,典型值为40MHz,无下限要求,但是野火测试下来,实际频率应该在24.5MHz以下,太高容易出现花屏的情况,这里我们按照野火提供的数据来测试,设置N = 420,R = 6,Q值不参与LCD_CLK的运行,随意设置即可,DIV设置为8分频,因此计算下来最终LCD_CLK = 420 / 6 / 8 = 8.75。

025 - STM32学习笔记 - 液晶屏控制(二) - 代码实现_第7张图片

RCC_PLLSAIConfig(420, 7, 6);
RCC_LTDCCLKDivConfig(RCC_PLLSAIDivR_Div8);               /* DIV选择8分频 */
RCC_PLLSAICmd(ENABLE);					               /* 使能PLLSAI时钟 */
while (RCC_GetFlagStatus(RCC_FLAG_PLLSAIRDY) == RESET);  /* 等待PLLSAI初始化完成 */

时钟配置完成后,根据液晶屏的时序要求,配置LTCD与液晶屏通讯时序的信号极性,配置过程参照如下图:

025 - STM32学习笔记 - 液晶屏控制(二) - 代码实现_第8张图片

液晶屏时时序有效电平

根据上图中可以看到,HSYNC、VSYNC、DE的有效信号均为低电平,同步时钟信号极性配置为上升沿,这里需要注意的是野火提到文档中DE的有效电平为高电平,但是实测DE低电平时屏幕才能正常工作,这里我实测了一下确实如此,因此按照野火的实测值来处理。

接下来需要配置时间参数

这里需要与上面内容,需要根据液晶屏手册给出的时间参数,配置HSW、VSW、HBP、HFP、VBP、VFP、有效像素宽度及有效行数,从下图中得出,HSW = 1,VSW = 1 , HBP = 46 , VBP = 23 , HFP = 16 , VFP = 22:

025 - STM32学习笔记 - 液晶屏控制(二) - 代码实现_第9张图片

/* 相关参数可查询液晶屏数据手册 */
#define HBP                                 46  //HSYNC 后的无效像素
#define VBP                                 23  //VSYNC 后的无效行数
#define HSW                                 1   //HSYNC 宽度
#define VSW                                 1   //VSYNC 宽度
#define HFP                                 20  //HSYNC 前的无效像素
#define VFP                                 22  //VSYNC 前的无效行数
#define LCD_WIDTH                           800	// LCD宽度
#define LCD_HEIGHT                          480	// LCD高度

下来对LTDC外设进行配置初始化

static void LCD_Init(void)
{
    LTDC_InitTypeDef LTDC_InitStruct;
    /* 使能 LTDC 外设时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_LTDC, ENABLE);
    /* 使能DMA2D时钟,后面会用到 */
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2D, ENABLE);
    /* 配置N = 420,R = 6,Q不参与运算,随意配置 */
    RCC_PLLSAIConfig(420, 7, 6);
    RCC_LTDCCLKDivConfig(RCC_PLLSAIDivR_Div8); //这个函数的参数值为 DIV
    /* 使能 PLLSAI 时钟 */
    RCC_PLLSAICmd(ENABLE);
    /* 等待 PLLSAI 初始化完成 */
    while (RCC_GetFlagStatus(RCC_FLAG_PLLSAIRDY) == RESET);
    
    /* 行同步信号极性 */
    LTDC_InitStruct.LTDC_HSPolarity = LTDC_HSPolarity_AL;
    /* 垂直同步信号极性 */
    LTDC_InitStruct.LTDC_VSPolarity = LTDC_VSPolarity_AL;
    /* 数据使能信号极性 */
    LTDC_InitStruct.LTDC_DEPolarity = LTDC_DEPolarity_AL;
    /* 像素同步时钟极性 */
    LTDC_InitStruct.LTDC_PCPolarity = LTDC_PCPolarity_IPC;
    
    /* 配置LCD背景颜色为黑色 */
    LTDC_InitStruct.LTDC_BackgroundRedValue = 0x00;		//背景红通道设置为 0
    LTDC_InitStruct.LTDC_BackgroundGreenValue = 0x00; 	//背景绿通道设置为 0
    LTDC_InitStruct.LTDC_BackgroundBlueValue = 0x00;	//背景蓝通道设置为 0
    
    /* 时间参数配置,此部分根据上节内容中关于LTDC初始化结构体说明来配置 */
    /* 配置行同步信号宽度(HSW-1) */
    LTDC_InitStruct.LTDC_HorizontalSync =HSW-1;
    /* 配置垂直同步信号宽度(VSW-1) */
    LTDC_InitStruct.LTDC_VerticalSync = VSW-1;
    /* 配置(HSW+HBP-1) */
    LTDC_InitStruct.LTDC_AccumulatedHBP =HSW+HBP-1;
    /* 配置(VSW+VBP-1) */
    LTDC_InitStruct.LTDC_AccumulatedVBP = VSW+VBP-1;
    /* 配置(HSW+HBP+有效像素宽度-1) */
    LTDC_InitStruct.LTDC_AccumulatedActiveW = HSW+HBP+LCD_WIDTH-1;
    /* 配置(VSW+VBP+有效像素高度-1) */
    LTDC_InitStruct.LTDC_AccumulatedActiveH = VSW+VBP+LCD_HEIGHT-1;
    /* 配置总宽度(HSW+HBP+有效像素宽度+HFP-1) */
    LTDC_InitStruct.LTDC_TotalWidth =HSW+ HBP+LCD_WIDTH + HFP-1;
    /* 配置总高度(VSW+VBP+有效像素高度+VFP-1) */
    LTDC_InitStruct.LTDC_TotalHeigh =VSW+ VBP+LCD_HEIGHT + VFP-1;
    
    LTDC_Init(&LTDC_InitStruct);				//初始化LTDC结构体
    LTDC_Cmd(ENABLE);						   //使能LTDC
}

LTDC初始化完成后,需要进行层级初始化

/* 相关宏定义 */
// LCD实际显示宽度
#define LCD_WIDTH                           800
 // LCD实际显示高度
#define LCD_HEIGHT                          480
// 第一层首地址
#define LCD_LAYER1_START_ADDR               0xD0000000 										   
// 第一层液晶的数据量,像素格式选择为RHB565,则此处×2
#define LCD_LAYER1_BUFFER_SIZE              (LCD_WIDTH * LCD_HEIGHT * 2)
// 第二层首地址
#define LCD_LAYER2_START_ADDR               (LCD_LAYER1_START_ADDR + LCD_LAYER1_BUFFER_SIZE)
// 第二层液晶的数据量,像素格式选择为ARGB8888,则此处×4
#define LCD_LAYER2_BUFFER_SIZE              (LCD_WIDTH * LCD_HEIGHT * 4)

/**
  * @brief 初始化 LTD 的 层 参数
  */
static void LCD_LayerInit(void)
{
    LTDC_Layer_InitTypeDef LTDC_Layer_InitStruct;
    /* 层窗口配置 */
    //一行的第一个起始像素,该值应为 (LTDC_InitStruct.LTDC_AccumulatedHBP+1)的值
    LTDC_Layer_InitStruct.LTDC_HorizontalStart = HBP + HSW;
    //一行的最后一个像素,该值应为 (LTDC_InitStruct.LTDC_AccumulatedActiveW)的值
    LTDC_Layer_InitStruct.LTDC_HorizontalStop = HSW+HBP+LCD_WIDTH-1;
    //一列的第一个起始像素,该值应为 (LTDC_InitStruct.LTDC_AccumulatedVBP+1)的值
    LTDC_Layer_InitStruct.LTDC_VerticalStart = VBP + VSW;
    //一列的最后一个像素,该值应为 (LTDC_InitStruct.LTDC_AccumulatedActiveH)的值
    LTDC_Layer_InitStruct.LTDC_VerticalStop = VSW+VBP+LCD_HEIGHT-1;
    
    /* 像素格式配置为RGB565 */
    LTDC_Layer_InitStruct.LTDC_PixelFormat = LTDC_Pixelformat_RGB565;
    
    /* 恒定 Alpha 值配置, 0-255,0为透明,255为完全不透明 */
    LTDC_Layer_InitStruct.LTDC_ConstantAlpha = 0xFF;
    LTDC_Layer_InitStruct.LTDC_DefaultColorAlpha = 0x00;
    LTDC_Layer_InitStruct.LTDC_DefaultColorBlue = 0x00;
    LTDC_Layer_InitStruct.LTDC_DefaultColorGreen = 0x00;
    LTDC_Layer_InitStruct.LTDC_DefaultColorRed = 0x00;

    /* 配置混合因子 CA 表示使用恒定 Alpha 值, PAxCA 表示使用像素 Alpha x 恒定 Alpha值 */
    LTDC_Layer_InitStruct.LTDC_BlendingFactor_1 = LTDC_BlendingFactor1_CA;
    LTDC_Layer_InitStruct.LTDC_BlendingFactor_2 = LTDC_BlendingFactor2_CA;
    
    /* 配置本层的显存首地址,这里显存地址就定义为SDRAM的起始地址0xD0000000 */
    LTDC_Layer_InitStruct.LTDC_CFBStartAdress = LCD_LAYER1_START_ADDR;	
    //设置行宽实际占用的大小,如果像素格式选择为RGB565,则×2,RGB888则×3
    LTDC_Layer_InitStruct.LTDC_CFBLineLength = LCD_WIDTH * 2 + 3;		    
    LTDC_Layer_InitStruct.LTDC_CFBLineNumber = LCD_HEIGHT ;
    LTDC_Layer_InitStruct.LTDC_CFBPitch = LCD_WIDTH * 2;

    /* 以上面的配置初始化第 1 层*/
    LTDC_LayerInit(LTDC_Layer1, &LTDC_Layer_InitStruct);
    
    //设置像素格式为ARGB8888
    LTDC_Layer_InitStruct.LTDC_PixelFormat = LTDC_Pixelformat_ARGB8888;
    /* 配置混合因子,使用像素 Alpha 参与混合 */
    LTDC_Layer_InitStruct.LTDC_BlendingFactor_1 = LTDC_BlendingFactor1_PAxCA;
    LTDC_Layer_InitStruct.LTDC_BlendingFactor_2 = LTDC_BlendingFactor2_PAxCA;
    
    /* 配置本层的显存首地址,这里配置它紧挨在第 1 层的后面*/
    LTDC_Layer_InitStruct.LTDC_CFBStartAdress = LCD_LAYER2_START_ADDR;
    //设置行宽实际占用的大小,如果像素格式选择为ARGB8888,则×4
    LTDC_Layer_InitStruct.LTDC_CFBLineLength = LCD_WIDTH * 4 + 3;
    LTDC_Layer_InitStruct.LTDC_CFBLineNumber = LCD_HEIGHT ;
    LTDC_Layer_InitStruct.LTDC_CFBPitch = LCD_WIDTH * 4;
    
    /* 初始化第 2 层 */
    LTDC_LayerInit(LTDC_Layer2, &LTDC_Layer_InitStruct);
    /*使能前景及背景层 */
    LTDC_LayerCmd(LTDC_Layer1, ENABLE);
    LTDC_LayerCmd(LTDC_Layer2, ENABLE);
 
    /* 立即重载配置 */
    LTDC_ReloadConfig(LTDC_IMReload);
    //使能抖动单元
    LTDC_DitherCmd(ENABLE);
}

以上所有程序写好之后,就可以实现对液晶屏的初步操作了:

/**
  * @brief 显存初始化,就是将显存数据全部清为白色
  */
void VRAM_Init(void)
{
    uint16_t i = 0,j = 0;
    uint32_t *p = (uint32_t *)(LCD_LAYER2_START_ADDR);			//p指针指向第二层首地址,此处需要转换为地址
    for(i = 0;i<LCD_WIDTH;i++)								  //宽高都刷成不透明白色
        for(j = 0;j < LCD_HEIGHT;j++)
        {
            *p = 0xffffffff;
            p++;
        }
}
void Draw_Line(void)
{
    uint16_t i = 0,j = 0;
    uint32_t *p = (uint32_t *)(LCD_LAYER2_START_ADDR);
    for(i = 0;i<LCD_WIDTH;i++)
    for(j = 0;j < LCD_HEIGHT;j++)
    {
        if((i > 200 && i < 220))
            *p = 0xffff0000;
        p++;
    }
}
void LCD_UpLoad(void)
{
    LCD_GPIO_Config();
    LCD_Init();
    LCD_LayerInit();
    VRAM_Init();
    Draw_Line();
}

看一下最终实验效果,忽略屏幕最下面的那条细线(显示屏硬件问题)。

025 - STM32学习笔记 - 液晶屏控制(二) - 代码实现_第10张图片

六、总结

大概总结一下这节的内容,通过LTDC操作液晶屏步骤如下:

  1. 初始化 LTDC 时钟、DMA2D 时钟(下节再学)、 GPIO 时钟;
  2. 初始化 SDRAM,以便用作显存;
  3. 根据液晶屏的参数配置 LTDC 外设的通讯时序;
  4. 配置 LTDC 层级控制参数,配置显存地址;
  5. 初始化 DMA2D,使用DMA2D辅助显示(下节再学);
  6. 编写测试程序,控制液晶输出

你可能感兴趣的:(stm32,stm32,学习,笔记)