第3版emWin教程第8章 emWin6.x的带OS方式移植(STM32H7之RGB接口)
Posted 安富莱电子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第3版emWin教程第8章 emWin6.x的带OS方式移植(STM32H7之RGB接口)相关的知识,希望对你有一定的参考价值。
教程不断更新中:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98429
第8章 emWin6.x的带OS方式移植(STM32H7之RGB接口)
本章节为大家讲解emWin6.x的裸机方式移植。提供的移植方法支持emWin的多图层配置,多缓冲配置以及各种颜色格式的实现。同时可以自适应我们生产的4.3寸,5寸和7寸的电阻屏和电容屏。
虽然本章节是以我们开发板为例进行移植的,但是教会大家如何移植到自己的板子上以及移植过程中的注意事项是本章节的重点。
8.1初学者重要提示
8.2移植前的准备工作以及移植emWin的流程
8.3第1步:下载emWin库并添加到工程模板
8.4第2步:SDRAM驱动的实现
8.5第3步:LTDC涉及到的引脚配置和时序配置
8.6第4步:电阻屏和电容屏触摸驱动的实现
8.7第5步:emWin底层接口和配置
8.8第6步:emWin裸机方式的接口文件
8.9 第7步: SDRAM的MPU Cache配置
8.10第8步:添加emWin应用进行测试
8.11 显示屏闪烁问题解决办法
8.12 避免显示屏上电瞬间高亮和撕裂感。
8.13 实验例程
8.14 总结
8.1 初学者重要提示
1、 学习本章节前,务必保证已经学习了本教程的第4章,第5章和第6章,这三章是移植前的必备知识。
2、 为了方便大家移植,推荐直接添加我们的工程文件到自己的工程或者直接使用我们的工程模板,按照本章的修改说明移植即可,本章是以uCOS-III移植为例进行说明。
3、 本章节是以移植到MDK上面为例进行讲解的,如果是移植到IAR上,方法是一样的。
4、 本章教程使用的emWin库来源和注意事项:
- 本章教程使用的emWin库来自MDK的安装目录的emWin6.x.
- 由于STemWin的版本一直停留在V5.44版本,所以我们本章教程没有使用。如果大家使用STemWin,初始化emWin前,务必记得使能STM32的硬件CRC时钟,因为ST官方对他们的emWin库做了保护,否则emWin无法启动。
5、 由于开发板要自适应4.3寸,5寸和7寸显示屏,而且还分电阻触摸和电容触摸,所以移植过程中添加的文件稍多。虽然移植是以我们的开发板为例进行讲解的,但是重点依然是告诉大家如何移植自己的板子以及移植过程中需要注意的事项。
6、 对于本章节的移植,我们需要先从整体上把控。由于开发板已经把需要移植的文件都整理好了,用户仅需添加文件就可以使用。我们这里着重介绍如何移植到自己的板子上面,这个才是本章节的重点。
- 显示屏的移植
emWin需要的底层接口函数已经全部集成在LCDConf_Lin_Template.c文件里面,此文件已经比较成熟了,基本没有bug。对于这个文件,用户仅需学会使用里面的几个宏配置以及提供一个显示屏背光调节函数LCD_SetBackLight即可,其它都不用做任何修改。
另外还有一个LTDC涉及到的引脚和时序配置的问题,这个是需要用户自己去实现的,配置方法已经在本章节的4.6小节进行讲解。如果这个也配置完成了,emWin的显示屏移植就完成了。
- 触摸的移植
电容触摸的移植比较容易,因为电容触摸芯片可以自动触摸校准,所以仅需配置完触摸芯片后将触摸芯片返回的触摸坐标(电容触摸芯片返回的就是实际的坐标值)和触摸按下状态通过函数GUI_PID_StoreState存储到指针输入设备的FIFO里面即可。
电阻触摸的移植要稍麻烦些,由于电阻触摸板的线性度不是很好,如果不做触摸校准和滤波处理会有点击不准确和飞点问题。emWin本身是支持两点触摸校准的,实际测试发现效果并不是很好,容易出现飞点,特别是使用线性度比较差的电阻触摸板。针对此问题,我们专门做了一个四点触摸校准,实际效果好了很多。其中触摸滤波方法是检测到触摸后先延迟30ms,消除抖动,然后采集10组坐标值做升序排列,去掉最大的几组坐标和最小的几组坐标,对中间的几组求平均作为最终的数值(电容触摸芯片返回的是ADC数值,不是实际坐标值)。然后将最终的数值代入通过触摸校准建立的线性公式来获得实际的坐标值,此时就可以将触摸坐标和触摸按下状态通过函数GUI_PID_StoreState存储到指针输入设备的FIFO里面。
8.2 移植前的准备工作以及移植emWin的流程
移植前注意以下两个问题:
- 本章节的IDE开发环境务必是MDK5.30及其以上版本,镜像下载地址:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=96992 。。
- 准备一个简单的uCOS-III工程(其它RTOS是一样的),越简单越好,我们就在这个简单的工程上面移植即可。
http://www.armbbs.cn/forum.php?mod=viewthread&tid=96918 。
emWin库的移植通过以下8步完成,下面各个小节详细讲解每一步:
8.3 第1步:下载emWin库并添加到工程模板
(提示:本小节要实现的操作,最简单的方法是复制教程配套例子里面的emWin文件夹到自己的工程目录即可)。
首先准备好一个简单的裸机工程模板,工程模板的制作就不做讲解了,这里的重点是教大家移植。准备好的工程模板如下图所示(大家也可以制作其它任意的工程模板,不限制):
http://www.armbbs.cn/forum.php?mod=viewthread&tid=96918
准备好工程模板后,就可以开始移植了。首先要做的就是将所有需要的文件放到工程模板里面。下面分四步和大家进行说明。当然,不限制必须使用下面的方法添加源码到工程,只要将需要的文件添加到工程模板即可。
第1步:按照第2章2.3.5小节讲解的方法找到emWin软件包,下面是emWin软件包内容:
我们将其做了简单整理,推荐大家移植阶段直接使用我们本章教程配套例子的emWin文件夹,将其复制到自己的工程里面即可。
第2步:在工程模板创建emWin文件夹
emWin文件夹里面再创建如下几个子文件夹,方便我们管理:
- Config文件夹用于添加配置文件和emWin底层驱动接口文件。
这四个文件来自emWin软件包里面的Config文件夹。
- DisplayDriver默认驱动,未使用。
- emWinTask文件用于添加用户自己的应用代码文件。
这四个文件是需要用户自己实现的测试代码,移植仅用到了MainTask.c和MainTask.h。这里大家可以直接复制本章配套例子在此文件夹下的这两个文件:
- GUI_X文件用于添加裸机或者RTOS方式的接口文件。
其中GUI_X_uCOS-III.c是用于RTOS的。
- GUILib文件用于添加emWin库文件。
GUI_CM0_L.lib用于M0内核芯片,GUI_CM3_L.lib用于M3内核新品,GUI_CM4_L.lib用于M4和M7内核芯片。这三个库都是小端格式。
- HanZi中文字库文件。
用于添加emWin汉子字库,当前移植阶段还用不到。
- Include文件用于添加头文件。
emWin的大部分头文件都在这个文件中,部分截图:
- JPEGConf用于STM32H7的硬JPEG解码。
这个是专门为H7设计的硬件解码接口文件(非SEGGER设计,是我们自己设计的)。
第3步:将源码文件添加到MDK的工程项目中,添加后的效果如下:
添加完毕后,别忘了添加头文件的路径:
至此,我们需要的emWin文件都已经添加完毕。
8.4 第2步:SDRAM驱动的实现
(提示:本小节要实现的操作,最简单的方法是复制教程配套例子里面的SDRAM驱动到自己的工程目录进行调试修改即可)
一定要保证SDRAM大批量读写数据时是正常的,SDRAM的测试可以自己专门做一个工程测试下。对于SDRAM的驱动实现,可以看BSP驱动教程的SDRAM章节:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
不管你使用的是镁光的,海力士的,三星的,ISSI的或者华邦的,实现方法基本都是一样的。教程配套的板子使用的是镁光32位带宽的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个字节来表示。其它的颜色格式,依此类推。
- 用作emWin动态内存
emWin是极其消耗动态内存的,所以用户可以将SDRAM除了用于显存以外的所有内存全部用作emWin动态内存。
============================================================
如果SDRAM的驱动测试已经没有问题了,就可以将其添加到工程里面了,开发板使用的SDRAM驱动文件是bsp_fmc_sdram.c。
添加到工程里面后要分配SDRAM的使用,教程配套开发板使用的是32MB,32位带宽的SDRAM,图层1占用4MB,图层2占用4MB,最后24MB给emWin动态内存使用。也许会有初学者会问,每个图层分配4MB是不是有些多了?实际上不多的,因为我们要让不同的颜色格式都通用,而且要满足三缓冲对显存的需求,这里分配4MB的话,教程实例使用很方便。大家实际项目中的使用可以配置成实际大小。具体的配置如下,详情见bsp_fmc_sdram.h文件:
#define EXT_SDRAM_ADDR ((uint32_t)0xC0000000) #define EXT_SDRAM_SIZE (32 * 1024 * 1024) /* LCD显存,第1页, 分配4M字节 */ #define SDRAM_LCD_BUF1 EXT_SDRAM_ADDR /* LCD显存,第2页, 分配4M字节 */ #define SDRAM_LCD_BUF2 (EXT_SDRAM_ADDR + SDRAM_LCD_SIZE) #define SDRAM_LCD_SIZE (4 * 1024 * 1024) /* 每层4M */ #define SDRAM_LCD_LAYER 2 /* 2层 */ /* 剩下的24M字节,提供给应用程序使用 */ #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的驱动配置也讲解完毕。
8.5 第3步:LTDC涉及到的引脚配置和时序配置
8.5.1 LTDC时序配置
(提示:本小节要实现的操作,最简单的方法是复制教程配套例子整理好的驱动文件LCDConf_Lin_Template.c到自己的工程目录,然后调试修改文件中的函数LCD_LL_Init即可,最后开启显示屏背光即可,)
用户仅需配置LTDC涉及到的引脚和时序即可,LTDC其余的配置已经在文件LCDConf_Lin_Template.c全部封好了。将引脚配置预留出来供用户配置是因为硬件设计不同,比如可能使用RGB888接口,也可能是RGB565接口,所以用户仅需把需要的引脚初始化即可。将时序配置也预留出来是因为不同厂家的裸屏,驱动时序是不同的。
由于开发板配套了4.3寸,5寸和7寸屏显示屏,所以要对这几种尺寸的显示屏做自适应。每个屏的时序配置都是不一样的,具体实现在LCDConf_Lin_Template.c文件中,即函数LCD_LL_Init。大家在给自己的显示屏移植时仅需提供这个LCD_LL_Init函数即可,引脚配置需要在这个函数里面实现。
引脚的配置还比较容易,硬件上用到哪些引脚了就把那些引脚配置下即可,关键是LTDC的时序配置。针对这个问题,本教程第4章的4.4小结有详细说明(必看)。我们这里就不再赘述了,具体代码如下:
/* ********************************************************************************************************* * 函 数 名: LCD_LL_Init * 功能说明: 配置LTDC * 形 参: 无 * 返 回 值: 无 * 笔 记: * 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); * ********************************************************************************************************* */ static void LCD_LL_Init(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和DMA2D时钟 */ __HAL_RCC_LTDC_CLK_ENABLE(); __HAL_RCC_DMA2D_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); } /*##-2- LTDC初始化 #############################################################*/ { uint16_t Width, Height, HSYNC_W, HBP, HFP, VSYNC_W, VBP, VFP; RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0}; /* 支持6种面板 */ switch (g_LcdType) { case LCD_35_480X320: /* 3.5寸 480 * 320 */ Width = 480; Height = 272; HSYNC_W = 10; HBP = 20; HFP = 20; VSYNC_W = 20; VBP = 20; VFP = 20; break; case LCD_43_480X272: /* 4.3寸 480 * 272 */ Width = 480; Height = 272; HSYNC_W = 40; HBP = 2; HFP = 2; VSYNC_W = 9; VBP = 2; VFP = 2; /* LCD 时钟配置 */ /* 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 clock frequency = PLLLCDCLK = 24MHz */ /* 刷新率 = 24MHz /((Width + HSYNC_W + HBP + HFP)*(Height + VSYNC_W + VBP + VFP)) = 24000000/((480 + 40 + 2 + 2)*(272 + 9 + 2 + 2)) = 24000000/(524*285) = 160Hz 当前这个配置方便用户使用PLL3Q输出的48MHz时钟供USB使用。 */ PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC; PeriphClkInitStruct.PLL3.PLL3M = 5; PeriphClkInitStruct.PLL3.PLL3N = 24; PeriphClkInitStruct.PLL3.PLL3P = 2; PeriphClkInitStruct.PLL3.PLL3Q = 5; PeriphClkInitStruct.PLL3.PLL3R = 10; HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct); break; case LCD_50_480X272: /* 5.0寸 480 * 272 */ Width = 480; Height = 272; HSYNC_W = 40; HBP = 2; HFP = 2; VSYNC_W = 9; VBP = 2; VFP = 2; break; case LCD_50_800X480: /* 5.0寸 800 * 480 */ case LCD_70_800X480: /* 7.0寸 800 * 480 */ Width = 800; Height = 480; HSYNC_W = 96; /* =10时,显示错位,20时部分屏可以的,80时全部OK */ HBP = 10; HFP = 10; VSYNC_W = 2; VBP = 10; VFP = 10; · PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC; PeriphClkInitStruct.PLL3.PLL3M = 5; PeriphClkInitStruct.PLL3.PLL3N = 48; PeriphClkInitStruct.PLL3.PLL3P = 2; PeriphClkInitStruct.PLL3.PLL3Q = 5; PeriphClkInitStruct.PLL3.PLL3R = 10; HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct); break; case LCD_70_1024X600: /* 7.0寸 1024 * 600 */ /* 实测像素时钟 = 53.7M */ Width = 1024; Height = 600; HSYNC_W = 2; /* =10时,显示错位,20时部分屏可以的,80时全部OK */ HBP = 157; HFP = 160; VSYNC_W = 2; VBP = 20; VFP = 12; PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC; PeriphClkInitStruct.PLL3.PLL3M = 5; PeriphClkInitStruct.PLL3.PLL3N = 48; PeriphClkInitStruct.PLL3.PLL3P = 2; PeriphClkInitStruct.PLL3.PLL3Q = 5; PeriphClkInitStruct.PLL3.PLL3R = 10; HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct); break; default: Width = 800; Height = 480; HSYNC_W = 80; /* =10时,显示错位,20时部分屏可以的,80时全部OK */ HBP = 10; HFP = 10; VSYNC_W = 10; VBP = 10; VFP = 10; /* LCD 时钟配置 */ /* 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 clock frequency = PLLLCDCLK = 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 当前这个配置方便用户使用PLL3Q输出的48MHz时钟供USB使用。 */ PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC; PeriphClkInitStruct.PLL3.PLL3M = 5; PeriphClkInitStruct.PLL3.PLL3N = 48; PeriphClkInitStruct.PLL3.PLL3P = 2; PeriphClkInitStruct.PLL3.PLL3Q = 5; PeriphClkInitStruct.PLL3.PLL3R = 10; HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct); break; } g_LcdHeight = Height; g_LcdWidth = Width; /* 配置信号极性 */ hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AL; /* HSYNC 低电平有效 */ hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL; /* VSYNC 低电平有效 */ hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL; /* DE 低电平有效 */ hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC; /* 时序配置 */ hltdc.Init.HorizontalSync = (HSYNC_W - 1); hltdc.Init.VerticalSync = (VSYNC_W - 1); hltdc.Init.AccumulatedHBP = (HSYNC_W + HBP - 1); hltdc.Init.AccumulatedVBP = (VSYNC_W + VBP - 1); hltdc.Init.AccumulatedActiveH = (Height + VSYNC_W + VBP - 1); hltdc.Init.AccumulatedActiveW = (Width + HSYNC_W + HBP - 1); hltdc.Init.TotalHeigh = (Height + VSYNC_W + VBP + VFP - 1); hltdc.Init.TotalWidth = (Width + HSYNC_W + HBP + HFP - 1); /* 配置背景层颜色 */ hltdc.Init.Backcolor.Blue = 0; hltdc.Init.Backcolor.Green = 0; hltdc.Init.Backcolor.Red = 0; hltdc.Instance = LTDC; /* 配置LTDC */ if (HAL_LTDC_Init(&hltdc) != HAL_OK) { /* 初始化错误 */ Error_Handler(__FILE__, __LINE__); } } /* 使能行中断 */ HAL_LTDC_ProgramLineEvent(&hltdc, 0); /* 使能Dither */ //HAL_LTDC_EnableDither(&hltdc); /* 使能LTDC中断,并配置其优先级 */ HAL_NVIC_SetPriority(LTDC_IRQn, 0x2, 0); HAL_NVIC_EnableIRQ(LTDC_IRQn); }
8.5.2 如何验证LTDC的时序配置是否正确
下面说一个最重要的问题,配置好时序了,怎么检查自己的配置是否成功了?用户仅需在函数LCD_LL_Init里面的如下代码后面加上两个函数:
/* 配置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的速度等级。
如果显示了,但是显示的位置不正确,可以重新调整时序参数即可。
8.5.3 添加涉及到的所有文件到工程
实际上大家自己实现的话,仅需修改函数LCD_LL_Init,自己实现此函数即可,无需其它任何配置。由于我们开发板要做不同显示屏的自适应,所以关联了好多个文件,所有关于TFT,触摸,触摸校准参数保存和字体的文件都要添加进来:
下面把这些新添加的文件做一个简单的介绍:
- GUI/Driver分组中的文件
bsp_tft_h7.c --- STM32H7的LTDC驱动文件。
bsp_tft_lcd.c --- TFT驱动和相关API函数汇总文件,比如用户的RA8875显示屏,ili9488显示屏,F429/H7所带TFT控制器驱动显示屏都可以有一个单独的文件,然后将这些显示屏相同功能的函数汇总成一个函数。这个文件就起到这个作用。emWin仅用到这个文件里面的全局变量g_LcdHeight,g_LcdWidth以及背光函数LCD_SetBackLight,其它所有函数都没有用到。
bsp_ts_touch.c --- 触摸芯片自适应驱动,根据用户使用的触摸IC选择不同的驱动。另外,电阻屏的触摸扫描,触摸校准和触摸滤波也是在这个文件里面实现。
bsp_ts_gt811.c --- 电容触摸芯片GT811的驱动以及触摸扫描。
bsp_ts_gt911.c --- 电容触摸芯片GT911的驱动以及触摸扫描。
bsp_ts _ft5x06.c --- 电容触摸芯片FT5X06的驱动以及触摸扫描。
bsp_ts_stmpe811.c --- 电阻触摸芯片STMPE811的驱动。
- GUI/GUI_OS分组中的文件
GUI_X.c --- emWin裸机接口文件,主要是emWin的时间基准和延迟实现。
- GUI/GUIConfig分组中的文件
GUIConf.c 和.h--- emWin动态内存的设置。
LCDConf_Lin_Template.c和.h --- emWin的LCD接口文件。
- GUI/Demo分组中的文件
MainTask.c 和.h--- emWin应用设计文件。
- GUI/JPEG分组中的文件
这几个文件全部用于STM32H7的硬件JPEG实现。
- GUI/Lib分组中的文件
emWin的库文件。
- Fonts分组中的文件
所有这些文件,emWin都用不上,只是因为被文件bsp_tft_lcd.c文件关联了。
asc12.c ---12点阵ASCII字符字库
asc16.c---16点阵ASCII字符字库
asc24.c ---24点阵ASCII字符字库
asc32.c---23点阵ASCII字符字库
hz12.c --- 12点阵宋体小字库
hz16.c --- 16点阵宋体小字库
hz24.c --- 24点阵宋体小字库
hz32.c --- 32点阵宋体小字库
ra8875_asc_width.c -- RA8875 ASCII字体的宽度表
- bsp分组中的文件
这个分组里面的三个文件,emWin都要间接用到。
bsp_tim_pwm.c --- 定时器驱动,显示屏的背光要用到PWM。
bsp_i2c_gpio.c --- I2C接口驱动,EEPROM,GT811,GT911,STMPE811和FT5X06都要用到,因为他们的接口都是I2C方式。
bsp_eeprom_24xx.c --- EEPROM驱动,用于存储电阻屏触摸校准参数。
bsp_fmc_sdram.c --- SDRAM驱动文件。
- HAL_Driver分组中的文件
圈出来的几个文件,emWin都要间接用到。
8.6 第4步:电阻屏和电容屏触摸驱动的实现
本小节的实现基于本教程的第5章,当前驱动对电阻触摸芯片STMPE811和电容触摸芯片FT5X06、GT911和GT811的显示屏都进行了支持。
实现比较简单,因为GUIX的触摸分按下,松手和移动三个事件,正好这几款触摸芯片的驱动也是分这三个事件,所以仅需修改下函数TOUCH_PutKey,所有显示屏触摸就都可以完美融合了。
8.6.1 添加GUIX的按下,松手和移动三个事件
文件bsp_ts_touch.c里的函数TOUCH_PutKey修改如下:
/* ********************************************************************************************************* * 函 数 名: TOUCH_PutKey * 功能说明: 将1个触摸点坐标值压入触摸FIFO缓冲区。电阻触摸屏形参是ADC值,电容触摸屏形参是坐标值 * 形 参: _usX, _usY 坐标值 * 返 回 值: 无 ********************************************************************************************************* */ GUI_PID_STATE State; void TOUCH_PutKey(uint8_t _ucEvent, uint16_t _usX, uint16_t _usY) { #if 1 uint16_t xx, yy; if (g_tTP.Enable == 1) /* 电阻屏。 形参是ADC值 */ { xx = TOUCH_TransX(_usX, _usY); yy = TOUCH_TransY(_usX, _usY); } else /* GT811,FTX06,GT911 电容触摸走此分之 */ { /* 无需转换, 直接是坐标值 */ xx = _usX; yy = _usY; } /* 按下, 移动和松手事件 */ switch (_ucEvent) { case TOUCH_DOWN: State.x = xx; State.y = yy; State.Pressed = 1; GUI_PID_StoreState(&State); break; case TOUCH_MOVE: State.x = xx; State.y = yy; State.Pressed = 1; GUI_PID_StoreState(&State); break; case TOUCH_RELEASE: State.Pressed = 0; GUI_PID_StoreState(&State); break; default: break; } #else 省略未写 #endif }
通过函数GUI_PID_StoreState将触摸坐标轴存储到emWin中。
8.6.2 周期性调用触摸扫描函数
电阻触摸和电容触摸的扫描函数是TOUCH_Scan和TOUCH_CapScan,为了实现使用了ThreadX和裸机时的一样的调用方式,专门在启动任务里面周期性的调用函数bsp_ProPer1ms(SysTick_ISR),而SysTick_ISR里面调用了bsp_RunPer1ms,然后bsp_RunPer1ms里面调用扫描函数,即如下的调用关系:
代码如下:
/* ********************************************************************************************************* * 函 数 名: AppTaskStart * 功能说明: 这是一个启动任务,在多任务系统启动后,必须初始化滴答计数器。本任务主要实现按键检测。 * 形 参: p_arg 是在创建该任务时传递的形参 * 返 回 值: 无 优 先 级: 2 ********************************************************************************************************* */ static void AppTaskStart (void *p_arg) { OS_ERR err; (void)p_arg; HAL_ResumeTick(); CPU_Init(); /* 此函数要优先调用,因为外设驱动中使用的us和ms延迟是基于此函数的 */ bsp_Init(); BSP_OS_TickEnable(); #if OS_CFG_STAT_TASK_EN > 0u OSStatTaskCPUUsageInit(&err); #endif #ifdef CPU_CFG_INT_DIS_MEAS_EN CPU_IntDisMeasMaxCurReset(); #endif /* 创建任务 */ AppTaskCreate(); /* 创建任务间通信机制 */ AppObjCreate(); while (1) { /* 需要周期性处理的程序,对应裸机工程调用的SysTick_ISR */ bsp_ProPer1ms(); OSTimeDly(1, OS_OPT_TIME_PERIODIC, &err); } }
8.6.3 如何将触摸驱动移植到自己的板子
通过前面的讲解,移植触摸
以上是关于第3版emWin教程第8章 emWin6.x的带OS方式移植(STM32H7之RGB接口)的主要内容,如果未能解决你的问题,请参考以下文章
第3版emWin教程第35章 emWin6.x的AppWizard中文实现方法