第3版emWin教程第4章 emWin上手之STM32H7 LTDC基础知识
Posted Simon223
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第3版emWin教程第4章 emWin上手之STM32H7 LTDC基础知识相关的知识,希望对你有一定的参考价值。
教程不断更新中:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98429
第4章 emWin上手之STM32H7 LTDC基础知识
本章教程为大家讲解LTDC应用中最基本的汉字显示和2D图形显示功能实现。
目录
4.5 LCD板级支持包(bsp_ltdc_h7.c和 bsp_tft_lcd.c)
4.1 初学者重要提示
- 本章是为emWin的LTDC移植部分做准备。
- 本章的第4小节LCD驱动设计非常重要。
- 如果自己观察的话,LCD上电会有一个瞬间高亮的问题,在此贴进行了描述:http://www.armbbs.cn/forum.php?mod=viewthread&tid=82619 。这个解决方案已经应用到本章配套的例子上。
- 本章节用到的汉字方案会在下章专门为大家讲解,下面是小字库的制作方法:http://www.armbbs.cn/forum.php?mod=viewthread&tid=202 。
- 测评STM32H7的LTDC+DMA2D性能,100Hz以上无压力,刷800*480图片和色块仅需2.6ms一张:http://www.armbbs.cn/forum.php?mod=viewthread&tid=91489 。
- 调试状态或者刚下载LCD的程序到H7里面,屏幕会抖动,这个是正常现象,之前F429就有这个问题,详情看此贴:http://www.armbbs.cn/forum.php?mod=viewthread&tid=16892 。
- 屏蔽MDK AC6使用中文GBK编码的警告方法,让大家可以继续使用GBK编码汉字:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98670 。
4.2 LCD相关的基础知识
4.2.1 显示屏相关知识
显示屏的结构有必要给大家普及下,这里我们通过如下三种类型的显示屏进行说明,基本已经涵盖我们常用的方式了。
- RA8875 + RGB接口裸屏
首先RA8875是一个显示屏控制器,自带显存,它的作用就是让不支持RGB接口的MCU也可以使用RGB接口的大屏。这起到了一个桥接的作用,可以将RGB接口屏转换成8080总线接口、SPI接口或者I2C接口方式。这种情况下,甚至低速的51单片机都可以外接大屏了。另外像SSD1963也是同样的作用。
- ili9488类显示屏
这种类型是把显示控制器和显示屏都集成好了,支持8080总线接口,有些还支持SPI或者I2C接口,而且显存也都集成了,不过主要是驱动一些小屏。像ili9341,ili9326,SPFD5420等也是一样的。此外还要注意,部分这种类型显示屏也是支持RGB接口的,像ST官方的STM32F429探索板外接的ili9431就是用的RGB接口。
- STM32H7 + SDRAM + RGB接口裸屏
这个是我们本章节要讲解的,STM32H7是自带LCD控制器的,再配合SDRAM作为显示屏的显存,整体作用跟RA8875是一样的,可以直接外接RGB接口的屏了。
有了这些认识后,对于裸屏还有些知识点需要了解。首先,裸屏本身不是什么控制芯片都没有,其构成也是比较复杂的,有兴趣了解的话,可以搜索关键字“TFT结构”进行学习。其次,TFT裸屏中主要的两个IC是Gate Driver IC和Source Driver IC,这两个IC的引脚都超级多,基本都是几百个引脚。最后,不管使用的哪种裸屏,一般都有规格书,会给出时序参数,这个在配置STM32H7的LTDC时要用到,如果规格书没有直接给出时序参数,则会给出使用的Driver IC型号,用户可以搜索此Driver IC的手册,在手册中会给出。
为了让大家有个感性认识,我们来看一看TFT裸屏的实际效果,下面是SPDF5420显示屏,400*240分辨率:
下面是TFT裸屏,480*272分辨率:
下面是TFT裸屏,800*480分辨率:
4.2.2 电阻触摸和电容触摸相关知识
有了TFT裸屏后还要配套电阻触摸板或者电容触摸板才可以获取触摸信息。触摸板是贴到TFT屏上面的,然后再通过电阻触摸芯片就可以获取电阻触摸板的信息,通过电容触摸芯片采集电容触摸板的信息。教程配套开发板的显示屏使用了三种触摸IC,电阻触摸IC是STMPE811,电容触摸IC是GT811和FT5X06。其中,电阻触摸和电容触摸两者的区别是初学者务必要知道的:
- 电阻触摸芯片STMPE811其实就是ADC,返回的是ADC数值,而电容触摸芯片GT811,GT911和FT5X06返回的是显示屏实际的坐标值。
- 使用电阻触摸芯片STMPE811需要做触摸校准,而使用电容触摸芯片GT811,GT911和FT5X06是自动校准的,无需手动校准。
下面是四线电阻触摸板的效果:
下面是电容触摸板的效果:
了解了这些知识,基本已经够我们本章节使用了,更多电阻触摸和电容触摸的相关知识可以看这个文档,讲解比较全面:http://www.armbbs.cn/forum.php?mod=viewthread&tid=14898 。
4.3 LCD硬件设计
下面是RGB888硬件接口的原理图,STM32-V7开发板制作了三个硬件接口。
- 加高2层的双排母接口
- 2.54mm的排针接口
- FPC软排线接口方式
了解了原理图后,再来看下实际的接口效果:
通过上面的原理图,我们要了解以下几个问题:
- V7开发板采用的是RGB888硬件接口,也许大家会问ARGB8888这种颜色格式怎么用于这种接口?Alpha通道是软件编程的时候用的,用于设置透明度,透明度会反应到RGB颜色值上。
- STM32H7支持的8种颜色格式都可以在RGB888硬件接口上实现。
- 如果大家用的是16位色的RGB565颜色格式,那么仅需用到LCD_R[7:3]、LCD_G[7:2] 和 LCD_B[7:3]引脚即可,没有用到的引脚可以继续用作其它功能。
4.4 LCD驱动设计
下面将程序设计中的相关问题逐一为大家做个说明。
4.4.1 第1步,LTDC显存使用SDRAM
设计LTDC驱动前,要先保证显存可以正常使用,V7开发板用的外部SDRAM作为显存。所以一定要保证SDRAM大批量读写数据时是正常的,SDRAM的测试可以自己专门做一个工程测试下。对于SDRAM的驱动实现,可以学习V7开发板BSP驱动教程第49章。不管你使用的是镁光的,海力士的,三星的,ISSI的或者华邦的,实现方法基本都是一样的。
V7开发板使用ISSI的32位带宽、32MB的SDRAM,如果想最大限度的发挥STM32H7驱动SDRAM的性能,强烈建议使用32位带宽的SDRAM,或者两个16位SDRAM组成32位带宽的SDRAM也是可以的。那SDRAM主要起到什么作用呢?作用有二:
- 用作显示屏的显存
STM32H7的LTDC外接RGB接口屏是没有显存的,所以需要SDRAM用作显存。如果用户选择STM32H7 LTDC的颜色格式是32位色ARGB8888,那么所需要显存大小(单位字节)是:显示屏宽 * 显示屏高 * (32/8), 其中32/8是表示这种颜色格式的一个像素点需要4个字节来表示。又比如配置颜色格式是16位色的RGB565,那么需要的显存大小是:显示屏宽 * 显示屏高 * (16/8),其中16/8是表示这种颜色格式的一个像素点需要2个字节来表示。其它的颜色格式,依此类推。
- 用作GUI动态内存
如果想要实现炫酷效果,GUI是极其消耗动态内存的,所以用户可以将SDRAM除了用于显存以外的所有内存全部用作GUI动态内存。
如果SDRAM的驱动测试已经没有问题了,就可以将其添加到工程里面了,V7使用的SDRAM驱动文件是bsp_fmc_sdram.c。图层1占用2MB,图层2占用2MB,最后28MB可做其它使用。也许会有初学者会问,每个图层分配2MB是不是有些多了?实际上不多的,因为我们要让不同的颜色格式都通用,这里分配2MB的话,教程实例使用很方便。大家实际项目中的使用可以配置成实际大小。具体的配置如下,详情见bsp_fmc_sdram.h文件:
#define EXT_SDRAM_ADDR ((uint32_t)0xC0000000)
#define EXT_SDRAM_SIZE (32 * 1024 * 1024)
/* LCD显存,第1页, 分配2M字节 */
#define SDRAM_LCD_BUF1 EXT_SDRAM_ADDR
/* LCD显存,第2页, 分配2M字节 */
#define SDRAM_LCD_BUF2 (EXT_SDRAM_ADDR + SDRAM_LCD_SIZE)
#define SDRAM_LCD_SIZE (2 * 1024 * 1024) /* 每层2M */
#define SDRAM_LCD_LAYER 2 /* 2层 */
/* 剩下的28M字节,提供给应用程序使用 */
#define SDRAM_APP_BUF (EXT_SDRAM_ADDR + SDRAM_LCD_SIZE * SDRAM_LCD_LAYER)
#define SDRAM_APP_SIZE (EXT_SDRAM_SIZE - SDRAM_LCD_SIZE * SDRAM_LCD_LAYER)
由于用户要刷新数据到SDRAM,而且LTDC也要从SDRAM读取数据,这就属于多总线访问SDRAM,此时就要注意Cache配置。为了使用方便起见,直接将SDRAM配置为WT模式,这样用户刷新的数据就可以立即写入到SDRAM,从而不影响LTDC刷新。
4.4.2 第2步,LTDC涉及到的引脚配置
本章第3小节用到了哪些引脚,这些引脚全部要做初始化,初始化时别忘了初始化引脚对应的时钟:
static void LCDH7_ConfigLTDC(void)
{
/* 配置LCD相关的GPIO */
{
/* GPios Configuration */
/*
+------------------------+-----------------------+----------------------------+
+ LCD pins assignment +
+------------------------+-----------------------+----------------------------+
| LCDH7_TFT R0 <-> PI.15 | LCDH7_TFT G0 <-> PJ.07 | LCDH7_TFT B0 <-> PJ.12 |
| LCDH7_TFT R1 <-> PJ.00 | LCDH7_TFT G1 <-> PJ.08 | LCDH7_TFT B1 <-> PJ.13 |
| LCDH7_TFT R2 <-> PJ.01 | LCDH7_TFT G2 <-> PJ.09 | LCDH7_TFT B2 <-> PJ.14 |
| LCDH7_TFT R3 <-> PJ.02 | LCDH7_TFT G3 <-> PJ.10 | LCDH7_TFT B3 <-> PJ.15 |
| LCDH7_TFT R4 <-> PJ.03 | LCDH7_TFT G4 <-> PJ.11 | LCDH7_TFT B4 <-> PK.03 |
| LCDH7_TFT R5 <-> PJ.04 | LCDH7_TFT G5 <-> PK.00 | LCDH7_TFT B5 <-> PK.04 |
| LCDH7_TFT R6 <-> PJ.05 | LCDH7_TFT G6 <-> PK.01 | LCDH7_TFT B6 <-> PK.05 |
| LCDH7_TFT R7 <-> PJ.06 | LCDH7_TFT G7 <-> PK.02 | LCDH7_TFT B7 <-> PK.06 |
-------------------------------------------------------------------------------
| LCDH7_TFT HSYNC <-> PI.12 | LCDTFT VSYNC <-> PI.13 |
| LCDH7_TFT CLK <-> PI.14 | LCDH7_TFT DE <-> PK.07 |
-----------------------------------------------------
*/
GPIO_InitTypeDef GPIO_Init_Structure;
/*##-1- Enable peripherals and GPIO Clocks #################################*/
/* 使能LTDC时钟 */
__HAL_RCC_LTDC_CLK_ENABLE();
/* 使能GPIO时钟 */
__HAL_RCC_GPIOI_CLK_ENABLE();
__HAL_RCC_GPIOJ_CLK_ENABLE();
__HAL_RCC_GPIOK_CLK_ENABLE();
/* GPIOI 配置 */
GPIO_Init_Structure.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
GPIO_Init_Structure.Mode = GPIO_MODE_AF_PP;
GPIO_Init_Structure.Pull = GPIO_NOPULL;
GPIO_Init_Structure.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_Init_Structure.Alternate = GPIO_AF14_LTDC;
HAL_GPIO_Init(GPIOI, &GPIO_Init_Structure);
/* GPIOJ 配置 */
GPIO_Init_Structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | \\
GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | \\
GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | \\
GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
GPIO_Init_Structure.Mode = GPIO_MODE_AF_PP;
GPIO_Init_Structure.Pull = GPIO_NOPULL;
GPIO_Init_Structure.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_Init_Structure.Alternate = GPIO_AF14_LTDC;
HAL_GPIO_Init(GPIOJ, &GPIO_Init_Structure);
/* GPIOK 配置 */
GPIO_Init_Structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | \\
GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
GPIO_Init_Structure.Mode = GPIO_MODE_AF_PP;
GPIO_Init_Structure.Pull = GPIO_NOPULL;
GPIO_Init_Structure.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_Init_Structure.Alternate = GPIO_AF14_LTDC;
HAL_GPIO_Init(GPIOK, &GPIO_Init_Structure);
}
/* 其它省略未写 */
}
4.4.3 第3步,LTDC时钟和时序配置
LTDC时序配置主要分三步就可以完成:
- 行同步,场同步和DE的极性配置。
- CLK时钟配置。
- 时序参数配置。
下面将这三点分别做个说明:
- 行同步,场同步和DE的极性配置
这里以V7开发板7寸RGB屏使用的source driver ic OTA7001为例进行说明(手册下载地址:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=18528)。
这几项配置要看OTA7001手册上面的时序图,对于DE模式,行同步和场同步的极性配置为高或者为低均可。因为我们这里使用的就是DE模式,所以主要配置DE的极性。这里要特别注意一个小问题,看时序图是DE高电平时数据有效,但是配置的时候要设置为低电平才可以。
下面的是V7开发板配套的7寸裸屏使用的source driver ic OTA7001的时序图:
实际配置STM32H7的工程时,将DE配置为低有效才是上面截图的效果,这个问题的确是有些奇葩了。
大家使用的时候也特别注意。
/* 配置信号极性 */
hltdc_F.Init.HSPolarity = LTDC_HSPOLARITY_AL; /* HSYNC 低电平有效 */
hltdc_F.Init.VSPolarity = LTDC_VSPOLARITY_AL; /* VSYNC 低电平有效 */
hltdc_F.Init.DEPolarity = LTDC_DEPOLARITY_AL; /* DE 低电平有效 */
hltdc_F.Init.PCPolarity = LTDC_PCPOLARITY_IPC; /* Pixel Clock 或者Dot Clock极性 */
ps:注意LTDC_PCPOLARITY_IPC和LTDC_PCPOLARITY_IIPC两种极性,选择不当会有一些问题。比如下面这个现象,底边会有黑的。
下面是用示波器实际测量的波形效果,黄色的波形是DE信号,另一个是行同步信号Hsync:
将波形放缩后:
- LTDC的像素时钟输出配置
在OTA7001手册上面给出了支持的时钟范围:
由于USB和LTDC都是用的PLL3产生时钟,为了方便起见,直接将产生LTDC时钟的PLL3R设置为24MHz(PLL3Q输出的48MHz时钟供USB使用)。
首先,输入时钟 PLL3_VCO Input = HSE_VALUE/PLL3M = 25MHz/5 = 5MHz
输出时钟 PLL3_VCO Output = PLL3_VCO Input * PLL3N = 5MHz * 48 = 240MHz
PLLLCDCLK = PLL3_VCO Output/PLL3R = 240 / 10 = 24MHz
最后,LTDC 时钟LTDC clock frequency = PLLLCDCLK = 24MHz
这里再额外补充点知识,LCD_CLK=24MHz时
刷新率 = 24MHz /((Width + HSYNC_W + HBP + HFP)*(Height + VSYNC_W + VBP + VFP))
= 24000000/((800 + 96 + 10 + 10)*(480 + 2 + 10 + 10))
= 24000000/(916*502)
= 52Hz
根据需要可以加大,100Hz刷新率完全没问题,设置PeriphClkInitStruct.PLL3.PLL3N = 100即可,
此时的LTDC时钟是50MHz
刷新率 = 50MHz /((Width + HSYNC_W+ HBP+ HFP )*(Height + VSYNC_W +VBP +VFP ))
= 5000000/(916*502)
= 108.7Hz
- 时序参数配置
时序参数的配置也比较容易,其实就是先看STM32H7参考手册上面的公式说明,说是公式,其实就是简单的加减法。然后将OTA7001的参数代到这个公式就可以了。又因为手册一般都是给出了参数的最小值,典型值和最大值,大家可以根据实际情况做简单的调整即可。需要用到的参数:
uint16_t Width, Height, HSYNC_W, VSYNC_W, HBP, HFP, VBP, VFP;
Horizontal Synchronization (Hsync) 对应变量HSYNC_W
Horizontal Back Porch (HBP) 对应变量HBP
Active Width 对应变量Width
Horizontal Front Porch (HFP) 对应变量HFP
Vertical Synchronization (Vsync) 对应变量VSYNC_W
Vertical Back Porch (VBP) 对应变量VBP
Active Heigh 对应变量Heigh
Vertical Front Porch (VFP) 对应变量VFP
STM32H7参考手册上面的公式如下:
*********************************************************************************************************
* LCD_TFT 同步时序配置(整理自官方做的一个截图,言简意赅):
* ----------------------------------------------------------------------------
*
* Total Width
* <--------------------------------------------------->
* Hsync width HBP Active Width HFP
* <---><--><--------------------------------------><-->
* ____ ____|_______________________________________|____
* |___| | | |
* | | |
* __| | | |
* /|\\ /|\\ | | | |
* | VSYNC| | | | |
* |Width\\|/ |__ | | |
* | /|\\ | | | |
* | VBP | | | | |
* | \\|/_____|_________|_______________________________________| |
* | /|\\ | | / / / / / / / / / / / / / / / / / / / | |
* | | | |/ / / / / / / / / / / / / / / / / / / /| |
* Total | | | |/ / / / / / / / / / / / / / / / / / / /| |
* Heigh | | | |/ / / / / / / / / / / / / / / / / / / /| |
* |Active| | |/ / / / / / / / / / / / / / / / / / / /| |
* |Heigh | | |/ / / / / / Active Display Area / / / /| |
* | | | |/ / / / / / / / / / / / / / / / / / / /| |
* | | | |/ / / / / / / / / / / / / / / / / / / /| |
* | | | |/ / / / / / / / / / / / / / / / / / / /| |
* | | | |/ / / / / / / / / / / / / / / / / / / /| |
* | | | |/ / / / / / / / / / / / / / / / / / / /| |
* | \\|/_____|_________|_______________________________________| |
* | /|\\ | |
* | VFP | | |
* \\|/ \\|/_____|______________________________________________________|
*
*
* 每个LCD设备都有自己的同步时序值:
* Horizontal Synchronization (Hsync)
* Horizontal Back Porch (HBP)
* Active Width
* Horizontal Front Porch (HFP)
*
* Vertical Synchronization (Vsync)
* Vertical Back Porch (VBP)
* Active Heigh
* Vertical Front Porch (VFP)
*
* LCD_TFT 窗口水平和垂直的起始以及结束位置 :
* ----------------------------------------------------------------
*
* HorizontalStart = (Offset_X + Hsync + HBP);
* HorizontalStop = (Offset_X + Hsync + HBP + Window_Width - 1);
* VarticalStart = (Offset_Y + Vsync + VBP);
* VerticalStop = (Offset_Y + Vsync + VBP + Window_Heigh - 1);
*
*********************************************************************************************************
OTA7001手册中已经给出了我们需要的数值:
uint16_t Width, Height, HSYNC_W, HBP, HFP, VSYNC_W, VBP, VFP。
参数设置好了,直接代入公式并与行同步,场同步和DE一起初始化:
/* 时序配置 */
hltdc_F.Init.HorizontalSync = (HSYNC_W - 1);
hltdc_F.Init.VerticalSync = (VSYNC_W - 1);
hltdc_F.Init.AccumulatedHBP = (HSYNC_W + HBP - 1);
hltdc_F.Init.AccumulatedVBP = (VSYNC_W + VBP - 1);
hltdc_F.Init.AccumulatedActiveH = (Height + VSYNC_W + VBP - 1);
hltdc_F.Init.AccumulatedActiveW = (Width + HSYNC_W + HBP - 1);
hltdc_F.Init.TotalHeigh = (Height + VSYNC_W + VBP + VFP - 1);
hltdc_F.Init.TotalWidth = (Width + HSYNC_W + HBP + HFP - 1);
至此,时序配置工作就完成了。
这里特别注意,当前程序中实际使用的参数与本小节的参数略有区别。由于这些参数都有较大的容错范围,所以很多参数都可以正常使用。
4.4.4 第4步,如何验证LTDC的时序配置是否正确
下面说一个最重要的问题,配置好时序了,怎么检查自己的配置是否成功了?用户仅需在函数LCDH7_ConfigLTDC里面的如下代码后面加上两个函数:
/* 配置LTDC */
if (HAL_LTDC_Init(&hltdc_F) != HAL_OK)
{
/* 初始化错误 */
Error_Handler(__FILE__, __LINE__);
}
/* 下面是添加的 */
LCD_SetBackLight(BRIGHT_DEFAULT);
while(1);
加上这两行代码后,再将背景层设置为一个合适的颜色,建议设置成红色,方便观察:
/* 配置背景层颜色 */
hltdc_F.Init.Backcolor.Blue = 0;
hltdc_F.Init.Backcolor.Green = 0;
hltdc_F.Init.Backcolor.Red = 255;
如果背景层可以正常显示红色,说明引脚和时序配置都是没有问题的。如果不成功要从以下几个方面着手检查:
- 首先要清楚一点,当前的配置是否成功与SDRAM没有任何关系,因为背景层还用不到SDRAM,图层1和图层2才需要SDRAM做显存使用。
- 从硬件着手检查,保证STM32H7芯片焊接没问题,TFT接口一定要牢固,防止接触不良,特别是使用FPC软排线的时候,测试阶段,软排线越短越好。有时候也可能是显示屏有问题,最好可以备两个显示屏测试。
- 从软件配置着手检查,查看LTDC涉及到的所有引脚是否配置,引脚时钟是否使能。有时候无法显示也有可能是板子硬件设计不规范导致干扰较大造成的,此时,可以降低LTDC所涉及到GPIO的速度等级。
- 如果显示了,但是显示的位置不正确,可以重新调整时序参数即可。
4.4.5 第5步,LTDC图层配置
LTDC的图层配置就比较好理解了,下面是完整的驱动代码:
1. /*
2. ******************************************************************************************************
3. * 函 数 名: LCDH7_ConfigLTDC
4. * 功能说明: 配置LTDC
5. * 形 参: 无
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
9. static void LCDH7_ConfigLTDC(void)
10. {
11. /* GPIO初始化部分省略未写 */
12.
13. /*##-2- LTDC初始化 #############################################################*/
14. {
15. LTDC_LayerCfgTypeDef pLayerCfg;
16. uint16_t Width, Height, HSYNC_W, HBP, HFP, VSYNC_W, VBP, VFP;
17. RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
18.
19. /* 支持6种面板 */
20. switch (g_LcdType)
21. {
22. case LCD_35_480X320: /* 3.5寸 480 * 320 */
23. Width = 480;
24. Height = 272;
25. HSYNC_W = 10;
26. HBP = 20;
27. HFP = 20;
28. VSYNC_W = 20;
29. VBP = 20;
30. VFP = 20;
31. break;
32.
33. case LCD_43_480X272: /* 4.3寸 480 * 272 */
34. Width = 480;
35. Height = 272;
36.
37. HSYNC_W = 40;
38. HBP = 2;
39. HFP = 2;
40. VSYNC_W = 9;
41. VBP = 2;
42. VFP = 2;
43.
44. /* LCD 时钟配置 */
45. /* PLL3_VCO Input = HSE_VALUE/PLL3M = 25MHz/5 = 5MHz */
46. /* PLL3_VCO Output = PLL3_VCO Input * PLL3N = 5MHz * 48 = 240MHz */
47. /* PLLLCDCLK = PLL3_VCO Output/PLL3R = 240 / 10 = 24MHz */
48. /* LTDC clock frequency = PLLLCDCLK = 24MHz */
49. /*
50. 刷新率 = 24MHz /((Width + HSYNC_W + HBP + HFP)*(Height + VSYNC_W + VBP + VFP))
51. = 24000000/((480 + 40 + 2 + 2)*(272 + 9 + 2 + 2))
52. = 24000000/(524*285)
53. = 160Hz
54.
55. 当前这个配置方便用户使用PLL3Q输出的48MHz时钟供USB使用。
56. */
57. PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
58. PeriphClkInitStruct.PLL3.PLL3M = 5;
59. PeriphClkInitStruct.PLL3.PLL3N = 48;
60. PeriphClkInitStruct.PLL3.PLL3P = 2;
61. PeriphClkInitStruct.PLL3.PLL3Q = 5;
62. PeriphClkInitStruct.PLL3.PLL3R = 10;
63. HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
64. break;
65.
66. case LCD_50_480X272: /* 5.0寸 480 * 272 */
67. Width = 480;
68. Height = 272;
69.
70. HSYNC_W = 40;
71. HBP = 2;
72. HFP = 2;
73. VSYNC_W = 9;
74. VBP = 2;
75. VFP = 2;
76. break;
77.
78. case LCD_50_800X480: /* 5.0寸 800 * 480 */
79. case LCD_70_800X480: /* 7.0寸 800 * 480 */
80. Width = 800;
81. Height = 480;
82.
83. HSYNC_W = 96;
84. HBP = 10;
85. HFP = 10;
86. VSYNC_W = 2;
87. VBP = 10;
88. VFP = 10;
89.
90. /* LCD 时钟配置 */
91. /* PLL3_VCO Input = HSE_VALUE/PLL3M = 25MHz/5 = 5MHz */
92. /* PLL3_VCO Output = PLL3_VCO Input * PLL3N = 5MHz * 48 = 240MHz */
93. /* PLLLCDCLK = PLL3_VCO Output/PLL3R = 240 / 10 = 24MHz */
94. /* LTDC clock frequency = PLLLCDCLK = 24MHz */
95. /*
96. 刷新率 = 24MHz /((Width + HSYNC_W + HBP + HFP)*(Height + VSYNC_W + VBP + VFP))
97. = 24000000/((800 + 96 + 10 + 10)*(480 + 2 + 10 + 10))
98. = 24000000/(916*502)
99. = 52Hz
100.
101. 根据需要可以加大,100Hz刷新率完全没问题,设置PeriphClkInitStruct.PLL3.PLL3N = 100即可
102. 此时的LTDC时钟是50MHz
103. 刷新率 = 50MHz /((Width + HSYNC_W + HBP + HFP )*(Height + VSYNC_W + VBP +VFP ))
104. = 5000000/(916*502)
105. = 108.7Hz
106.
107. 当前这个配置方便用户使用PLL3Q输出的48MHz时钟供USB使用。
108. */
109. PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
110. PeriphClkInitStruct.PLL3.PLL3M = 5;
111. PeriphClkInitStruct.PLL3.PLL3N = 48;
112. PeriphClkInitStruct.PLL3.PLL3P = 2;
113. PeriphClkInitStruct.PLL3.PLL3Q = 5;
114. PeriphClkInitStruct.PLL3.PLL3R = 10;
115. HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
116. break;
117.
118. case LCD_70_1024X600: /* 7.0寸 1024 * 600 */
119. /* 实测像素时钟 = 53.7M */
120. Width = 1024;
121. Height = 600;
122.
123. HSYNC_W = 2;
124. HBP = 157;
125. HFP = 160;
126. VSYNC_W = 2;
127. VBP = 20;
128. VFP = 12;
129.
130. PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
131. PeriphClkInitStruct.PLL3.PLL3M = 5;
132. PeriphClkInitStruct.PLL3.PLL3N = 48;
133. PeriphClkInitStruct.PLL3.PLL3P = 2;
134. PeriphClkInitStruct.PLL3.PLL3Q = 5;
135. PeriphClkInitStruct.PLL3.PLL3R = 10;
136. HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
137. break;
138.
139. default:
140. Width = 800;
141. Height = 480;
142.
143. HSYNC_W = 80;
144. HBP = 10;
145. HFP = 10;
146. VSYNC_W = 10;
147. VBP = 10;
148. VFP = 10;
149.
150. /* LCD 时钟配置 */
151. /* PLL3_VCO Input = HSE_VALUE/PLL3M = 25MHz/5 = 5MHz */
152. /* PLL3_VCO Output = PLL3_VCO Input * PLL3N = 5MHz * 48 = 240MHz */
153. /* PLLLCDCLK = PLL3_VCO Output/PLL3R = 240 / 10 = 24MHz */
154. /* LTDC clock frequency = PLLLCDCLK = 24MHz */
155. /*
156. 刷新率 = 24MHz /((Width + HSYNC_W + HBP + HFP)*(Height + VSYNC_W + VBP + VFP))
157. = 24000000/((800 + 96 + 10 + 10)*(480 + 2 + 10 + 10))
158. = 24000000/(916*502)
159. = 52Hz
160.
161. 根据需要可以加大,100Hz刷新率完全没问题,设置PeriphClkInitStruct.PLL3.PLL3N = 100即可
162. 此时的LTDC时钟是50MHz
163. 刷新率 = 50MHz /((Width + HSYNC_W + HBP + HFP )*(Height + VSYNC_W + VBP +VFP ))
164. = 5000000/(916*502)
165. = 108.7Hz
166.
167. 当前这个配置方便用户使用PLL3Q输出的48MHz时钟供USB使用。
168. */
169. PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
170. PeriphClkInitStruct.PLL3.PLL3M = 5;
171. PeriphClkInitStruct.PLL3.PLL3N = 48;
172. PeriphClkInitStruct.PLL3.PLL3P = 2;
173. PeriphClkInitStruct.PLL3.PLL3Q = 5;
174. PeriphClkInitStruct.PLL3.PLL3R = 10;
175. HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
176. break;
177. }
178.
179. g_LcdHeight = Height;
180. g_LcdWidth = Width;
181.
182. /* 配置信号极性 */
183. hltdc_F.Init.HSPolarity = LTDC_HSPOLARITY_AL; /* HSYNC 低电平有效 */
184. hltdc_F.Init.VSPolarity = LTDC_VSPOLARITY_AL; /* VSYNC 低电平有效 */
185. hltdc_F.Init.DEPolarity = LTDC_DEPOLARITY_AL; /* DE 低电平有效 */
186. hltdc_F.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
187.
188. /* 时序配置 */
189. hltdc_F.Init.HorizontalSync = (HSYNC_W - 1);
190. hltdc_F.Init.VerticalSync = (VSYNC_W - 1);
191. hltdc_F.Init.AccumulatedHBP = (HSYNC_W + HBP - 1);
192. hltdc_F.Init.AccumulatedVBP = (VSYNC_W + VBP - 1);
193. hltdc_F.Init.AccumulatedActiveH = (Height + VSYNC_W + VBP - 1);
194. hltdc_F.Init.AccumulatedActiveW = (Width + HSYNC_W + HBP - 1);
195. hltdc_F.Init.TotalHeigh = (Height + VSYNC_W + VBP + VFP - 1);
196. hltdc_F.Init.TotalWidth = (Width + HSYNC_W + HBP + HFP - 1);
197.
198. /* 配置背景层颜色 */
199. hltdc_F.Init.Backcolor.Blue = 0;
200. hltdc_F.Init.Backcolor.Green = 0;
201. hltdc_F.Init.Backcolor.Red = 0;
202.
203. hltdc_F.Instance = LTDC;
204.
205. /* 开始配置图层 ------------------------------------------------------*/
206. /* 窗口显示区设置 */
207. pLayerCfg.WindowX0 = 0;
208. pLayerCfg.WindowX1 = Width;
209. pLayerCfg.WindowY0 = 0;
210. pLayerCfg.WindowY1 = Height;
211.
212. /* 配置颜色格式 */
213. pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
214.
215. /* 显存地址 */
216. pLayerCfg.FBStartAdress = LCDH7_FRAME_BUFFER;
217.
218. /* Alpha常数 (255 表示完全不透明) */
219. pLayerCfg.Alpha = 255;
220.
221. /* 无背景色 */
222. pLayerCfg.Alpha0 = 0; /* 完全透明 */
223. pLayerCfg.Backcolor.Blue = 0;
224. pLayerCfg.Backcolor.Green = 0;
225. pLayerCfg.Backcolor.Red = 0;
226.
227. /* 配置图层混合因数 */
228. pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA;
229. pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA;
230.
231. /* 配置行列大小 */
232. pLayerCfg.ImageWidth = Width;
233. pLayerCfg.ImageHeight = Height;
234.
235. /* 配置LTDC */
236. if (HAL_LTDC_Init(&hltdc_F) != HAL_OK)
237. {
238. /* 初始化错误 */
239. Error_Handler(__FILE__, __LINE__);
240. }
241.
242. /* 配置图层1 */
243. if (HAL_LTDC_ConfigLayer(&hltdc_F, &pLayerCfg, LTDC_LAYER_1) != HAL_OK)
244. {
245. /* 初始化错误 */
246. Error_Handler(__FILE__, __LINE__);
247. }
248.
249. #if 0
250. /* 配置图层2 */
251. if (HAL_LTDC_ConfigLayer(&hltdc_F, &pLayerCfg, LTDC_LAYER_2) != HAL_OK)
252. {
253. /* 初始化错误 */
254. Error_Handler(__FILE__, __LINE__);
255. }
256. #endif
257. }
258.
259. #if 1
260. HAL_NVIC_SetPriority(LTDC_IRQn, 0xE, 0);
261. HAL_NVIC_EnableIRQ(LTDC_IRQn);
262. #endif
263. }
下面将几个关键的地方做个阐释:
- 第20行,六种显示面板的LTDC输出时钟和时序参数配置,六种面板的识别是在bsp_ts_touch.c文件中实现的。大家自己配置时用不到这个,仅需提供一组时序参数和输出时钟即可,除非项目中需要切换不同显示屏。
- 第78-116行,这里是7寸面板的LTDC时钟输出配置和时序参数配置,配置方法在前面几步已经介绍,这里不再赘述,其它面板的设置方法是一样的。
- 第179-180行,全局变量g_LcdWidth和g_LcdHeight在文件bsp_tft_lcd.c文件定义,用来记录显示屏的分辨率。
- 第183-186行,用来配置行同步,场同步,DE数据使能和LTDC像素时钟的极性。
- 第189-196行,配置时序。
- 第199-201行,配置背景层颜色。
- 第207-210行,用来设置图层在LCD显示区的起始位置和结束位置。
- 第213行,用来设置图层的颜色格式,STM32H7支持8种颜色格式,这里是设置为RGB565。
- 第216行,用来设置图层的显示首地址。
- 第219-229行,在第50章的2.5小节详细讲解了这行代码的作用。
- 第232-233行,用于设置从显存中要读取的行长和行数。
- 第236-240行,配置LTDC的基本参数。
- 第243-247行,配置图层1。
- 第251-255行,配置图层2,暂时未用到图层2(通过条件编译取消执行)。
- 第260-261行,配置LTDC中断的优先级并使能。
4.4.6 第6步,LCD背光实现
LCD的背光是PWM驱动方式,涉及到的代码如下:
/*
*********************************************************************************************************
* 函 数 名: LCD_SetBackLight
* 功能说明: 初始化控制LCD背景光的GPIO,配置为PWM模式。
* 当关闭背光时,将CPU IO设置为浮动输入模式(推荐设置为推挽输出,并驱动到低电平);将TIM3关闭 省电
* 形 参: _bright 亮度,0是灭,255是最亮
* 返 回 值: 无
*********************************************************************************************************
*/
void LCD_SetBackLight(uint8_t _bright)
{
s_ucBright = _bright; /* 保存背光值 */
LCD_SetPwmBackLight(s_ucBright);
}
/*
*********************************************************************************************************
* 函 数 名: LCD_SetPwmBackLight
* 功能说明: 初始化控制LCD背景光的GPIO,配置为PWM模式。
* 当关闭背光时,将CPU IO设置为浮动输入模式(推荐设置为推挽输出,并驱动到低电平);将TIM3关闭 省电
* 形 参: _bright 亮度,0是灭,255是最亮
* 返 回 值: 无
*********************************************************************************************************
*/
static void LCD_SetPwmBackLight(uint8_t _bright)
{
/* 背光有CPU输出PWM控制,PA0/TIM5_CH1/TIM2_CH1 */
//bsp_SetTIMOutPWM(GPIOA, GPIO_PIN_0, TIM5, 1, 100, (_bright * 10000) /255);
//bsp_SetTIMOutPWM(GPIOA, GPIO_PIN_0, TIM5, 1, 20000, (_bright * 10000) /255);
bsp_SetTIMOutPWM(GPIOA, GPIO_PIN_8, TIM1, 1, 20000, (_bright * 10000) /255);
}
函数的注释已经比较详细。另外,背光是基于V7开发板BSP驱动教程第34章的API:bsp_SetTIMOutPWM实现,关于这个函数可以看第34章节。
4.5 LCD板级支持包(bsp_ltdc_h7.c和 bsp_tft_lcd.c)
bsp_ltdc_h7.c是H7的LTDC驱动文件,涉及到的函数比较多。而bsp_tft_lcd.c文件是在LTDC的API基础上面封装出更通用的函数,不仅仅LTDC,像RA8875,ili9488等也可通过此文件进行封装。
本章节主要给几个常用的基本API做个介绍:
- LCD_InitHard
- LCD_ClrScr
- LCD_SetBackLight
- LCD_DispStr
- LCD_PutPixel
- LCD_DrawLine
- LCD_DrawRect
- LCD_DrawCircle
- LCD_Fill_Rect
4.5.1 函数LCD_InitHard
函数原型:
/*
*********************************************************************************************************
* 函 数 名: LCD_InitHard
* 功能说明: 初始化LCD
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void LCD_InitHard(void)
{
LCD_HardReset();
LCDH7_InitHard();
LCD_SetDirection(0);
LCD_ClrScr(CL_BLACK); /* 清屏,显示全黑 */
// LCD_SetBackLight(BRIGHT_DEFAULT);
}
函数描述:
此函数用于初始化LCD,配置了STM32H7的LTDC控制器,设置横向显示,默认清屏为黑色。
注意事项:
- 调用此函数前,务必优先调用函数TOUCH_InitHard识别不同分辨率面板。
- 通过函数LCD_SetDirection可以设置横向,竖向,横向180度,竖向180度显示。在后面章节再为大家介绍具体的实现方法。
使用举例:
作为初始化函数,直接在bsp.c文件的bsp_Init函数里面调用即可。
4.5.2 函数LCD_ClrScr
函数原型:
void LCD_ClrScr(uint16_t _usColor)
函数描述:
此函数用于清屏操作。
函数参数:
- 第1个参数是清屏的颜色值设置,在bsp_tft_lcd.h文件中定义了几个常用的颜色:
/*
LCD 颜色代码,CL_是Color的简写
16Bit由高位至低位, RRRR RGGG GGGB BBBB
下面的RGB 宏将24位的RGB值转换为16位格式。
启动windows的画笔程序,点击编辑颜色,选择自定义颜色,可以获得的RGB值。
推荐使用迷你取色器软件获得你看到的界面颜色。
*/
#define RGB(R,G,B) (((R >> 3) << 11) | ((G >> 2) << 5) | (B >> 3))/* 将8位R,G,B转化为 16位RGB565格式 */
/* 解码出 R=8bit G=8bit B=8bit */
#define RGB565_R(x) ((x >> 8) & 0xF8)
#define RGB565_G(x) ((x >> 3) & 0xFC)
#define RGB565_B(x) ((x << 3) & 0xF8)
/* 解码出 R=5bit G=6bit B=5bit */
#define RGB565_R2(x) ((x >> 11) & 0x1F)
#define RGB565_G2(x) ((x >> 5) & 0x3F)
#define RGB565_B2(x) ((x >> 0) & 0x1F)
enum
{
CL_WHITE = RGB(255,255,255), /* 白色 */
CL_BLACK = RGB( 0, 0, 0), /* 黑色 */
CL_RED = RGB(255, 0, 0), /* 红色 */
CL_GREEN = RGB( 0,255, 0), /* 绿色 */
CL_BLUE = RGB( 0, 0,255), /* 蓝色 */
CL_YELLOW = RGB(255,255, 0), /* 黄色 */
CL_GREY = RGB( 98, 98, 98), /* 深灰色 */
CL_GREY1 = RGB( 150, 150, 150), /* 浅灰色 */
CL_GREY2 = RGB( 180, 180, 180), /* 浅灰色 */
CL_GREY3 = RGB( 200, 200, 200), /* 最浅灰色 */
CL_GREY4 = RGB( 230, 230, 230), /* 最浅灰色 */
CL_BUTTON_GREY = RGB( 220, 220, 220), /* WINDOWS 按钮表面灰色 */
CL_MAGENTA = 0xF81F, /* 红紫色,洋红色 */
CL_CYAN = 0x7FFF, /* 蓝绿色,青色 */
CL_BLUE1 = RGB( 0, 0, 240), /* 深蓝色 */
CL_BLUE2 = RGB( 0, 0, 128), /* 深蓝色 */
CL_BLUE3 = RGB( 68, 68, 255), /* 浅蓝色1 */
CL_BLUE4 = RGB( 0, 64, 128), /* 浅蓝色1 */
/* UI 界面 Windows控件常用色 */
CL_BTN_FACE = RGB(236, 233, 216), /* 按钮表面颜色(灰) */
CL_BTN_FONT = CL_BLACK, /* 按钮字体颜色(黑) */
CL_BOX_BORDER1 = RGB(172, 168,153), /* 分组框主线颜色 */
CL_BOX_BORDER2 = RGB(255, 255,255), /* 分组框阴影线颜色 */
CL_MASK = 0x9999 /* 颜色掩码,用于文字背景透明 */
};
使用举例:
比如将LCD清为红色,即LCD_ClrSrc(CL_RED)。
4.5.3 函数LCD_SetBackLight
函数原型:
/*
*********************************************************************************************************
* 函 数 名: LCD_SetBackLight
* 功能说明: 初始化控制LCD背景光的GPIO,配置为PWM模式。
* 当关闭背光时,将CPU IO设置为浮动输入模式(推荐设置为推挽输出,并驱动到低电平);将TIM3关闭 省电
* 形 参: _bright 亮度,0是灭,255是最亮
* 返 回 值: 无
*********************************************************************************************************
*/
void LCD_SetBackLight(uint8_t _bright)
{
s_ucBright = _bright; /* 保存背光值 */
LCD_SetPwmBackLight(s_ucBright);
}
函数描述:
此函数主要用于LCD背光设置。
函数参数:
- 第1个参数是背光参数,0表示灭,255表示最亮。
使用举例:
比如设置LCD最亮LCD_SetBackLight(255)。
4.5.4 函数LCD_DispStr
函数原型:
/*
*********************************************************************************************************
* 函 数 名: LCD_DispStr
* 功能说明: 在LCD指定坐标(左上角)显示一个字符串
* 形 参:
* _usX : X坐标
* _usY : Y坐标
* _ptr : 字符串指针
* _tFont : 字体结构体,包含颜色、背景色(支持透明)、字体代码、文字间距等参数
* 返 回 值: 无
*********************************************************************************************************
*/
void LCD_DispStr(uint16_t _usX, uint16_t _usY, char *_ptr, FONT_T *_tFont)
{
LCD_DispStrEx(_usX, _usY, _ptr, _tFont, 0, 0);
}
函数描述:
此函数用于在LCD指定位置显示字符串,中英文均支持。由于这个函数涉及到的知识点比较多,下章节会专门为大家讲解。
函数参数:
- 第1个参数是x轴坐标位置。
- 第2个参数是y轴坐标位置。
- 第3个参数是要显示的字符串。
- 第4个参数是FONT_T类型结构体,定义如下:
/* 字体属性结构, 用于LCD_DispStr() */
typedef struct
{
FONT_CODE_E FontCode; /* 字体代码 FONT_CODE_E */
uint16_t FrontColor; /* 字体颜色 */
uint16_t BackColor; /* 文字背景颜色,透明 */
uint16_t Space; /* 文字间距,单位 = 像素 */
}FONT_T;
使用举例:
比如显示12点阵和16点阵字符。
FONT_T tFont12; /* 定义一个字体结构体变量,用于设置字体参数 */
FONT_T tFont16; /* 定义一个字体结构体变量,用于设置字体参数 */
/* 设置字体参数 */
{
tFont12.FontCode = FC_ST_12; /* 字体代码 12点阵 */
tFont12.FrontColor = CL_WHITE; /* 字体颜色 */
tFont12.BackColor = CL_BLUE; /* 文字背景颜色 */
tFont12.Space = 0; /* 文字间距,单位 = 像素 */
}
/* 设置字体参数 */
{
tFont16.FontCode = FC_ST_16; /* 字体代码 16点阵 */
tFont16.FrontColor = CL_WHITE; /* 字体颜色 */
tFont16.BackColor = CL_BLUE; /* 文字背景颜色 */
tFont16.Space = 0; /* 文字间距,单位 = 像素 */
}
LCD_ClrScr(CL_BLUE);
LCD_DispStr(5, 3, "故人西辞黄鹤楼,烟花三月下扬州。", &tFont12);
LCD_DispStr(5, 18, "孤帆远影碧空尽,唯见长江天际流。", &tFont12);
LCD_DispStr(5, 38, "故人西辞黄鹤楼,烟花三月下扬州。", &tFont16);
LCD_DispStr(5, 58, "孤帆远影碧空尽,唯见长江天际流。", &tFont16);
4.5.5 函数LCD_PutPixel
函数原型:
/*
*********************************************************************************************************
* 函 数 名: LCD_PutPixel
* 功能说明: 画1个像素
* 形 参:
* _usX,_usY : 像素坐标
* _usColor : 像素颜色
* 返 回 值: 无
*********************************************************************************************************
*/
void LCD_PutPixel(uint16_t _usX, uint16_t _usY, uint16_t _usColor)
{
LCDH7_PutPixel(_usX, _usY, _usColor);
}
函数描述:
此函数用于在指定位置显示一个像素点。
函数参数:
- 第1个参数是x轴坐标位置。
- 第2个参数是y轴坐标位置。
- 第3个参数是像素点颜色。
使用举例:
比如在坐标(0, 0)显示红色,那就是LCD_PutPixel(0, 0, CL_RED)。
4.5.6 函数LCD_DrawLine
函数原型:
/*
*********************************************************************************************************
* 函 数 名: LCD_DrawLine
* 功能说明: 采用 Bresenham 算法,在2点间画一条直线。
* 形 参:
* _usX1, _usY1 : 起始点坐标
* _usX2, _usY2 : 终止点Y坐标
* _usColor : 颜色
* 返 回 值: 无
*********************************************************************************************************
*/
void LCD_DrawLine(uint16_t _usX1 , uint16_t _usY1 以上是关于第3版emWin教程第4章 emWin上手之STM32H7 LTDC基础知识的主要内容,如果未能解决你的问题,请参考以下文章
第3版emWin教程第6章 emWin上手之STM32H7 DMA2D加速
第3版emWin教程第5章 emWin上手之电阻触摸和电容触摸
第3版emWin教程第5章 emWin上手之电阻触摸和电容触摸
第3版emWin教程第8章 emWin6.x的带OS方式移植(STM32H7之RGB接口)