【第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  如何将触摸驱动移植到自己的板子

通过前面的讲解,移植触摸驱动到自己的板子上,最简单的办法是将开发板与触摸相关的文件全部移植过来,然后在这些文件的基础上进行修改。下面分两种情况进行说明:

  •   电容屏触摸的移植比较简单,如果用户用的触摸IC跟开发板一样,直接拿来用即可,如果不一样,需要先将触摸IC的驱动实现,然后按照开发板提供的GT911,GT811或者FT5X06的触摸扫描函数,照葫芦画瓢实现一个即可。
  •   电阻屏的移植稍麻烦些,如果用户用的触摸IC跟开发板一样,直接拿来用即可,如果不一样,需要先将触摸IC的驱动实现,然后仿照bsp_ts_stmpe811.c文件提供触摸按下状态函数,X轴,Y轴的ADC数值读取函数和清除触摸中断标志函数。最后用重新实现的这几个函数替换bsp_ts_touch.c文件中的原函数即可。另外要注意一点,这种方式实现后,虽然触摸校准依然可以使用,但是开发板的触摸校准参数是保存在EEPROM中的,用户可以根据自己的实际情况选择存储介质。另外,触摸参数的保存和读取在bsp_ts_touch.c文件末尾的函数TOUCH_SaveParam和TOUCH_LoadParam实现。

如果大家不想用开发板实现的方案,想自己重新实现一个,也是没问题的,注意跟emWin关联的方式。

8.7   第5步:emWin底层接口函数和配置

emWin的底层接口函数和配置的实现在我们第1步中添加的GUIConf.c及其头文件和LCDConf_Lin_Template.c及其头文件里面。下面我们把这四个文件分别做个介绍。

8.7.1  GUIConf.c文件

GUIConf.c文件主要实现系统动态内存的配置,我们在官方代码的基础上做了下修改,添加了一个宏定义,方便用户选择使用内部 SRAM还是外部SDRAM作为emWin的动态内存:

#include "GUI.h"
#include "bsp.h"


/*********************************************************************
*
*       Defines
*
**********************************************************************
*/
//
// Define the available number of bytes available for the GUI
//
#define EX_SRAM   1/*1 used extern sram, 0 used internal sram */   //--------------(1)

#if EX_SRAM
#define GUI_NUMBYTES  (1024*1024*24) 
#else
#define GUI_NUMBYTES  (100*1024)    
#endif

/* Define the average block size */
#define GUI_BLOCKSIZE 0x80          //--------------(2)



/*********************************************************************
*
*       Public code
*
**********************************************************************
*/
/*********************************************************************
*
*       GUI_X_Config
*
* Purpose:
*   Called during the initialization process in order to set up the
*   available memory for the GUI.
*/
void GUI_X_Config(void) 
{
#if EX_SRAM                                    //--------------(3)
    static U32 *aMemory;
    aMemory = (U32 *)SDRAM_APP_BUF;
    
    /*  Assign memory to emWin */
    GUI_ALLOC_AssignMemory(aMemory, GUI_NUMBYTES);  
    GUI_ALLOC_SetAvBlockSize(GUI_BLOCKSIZE);
#else                                         //--------------(4)
//    /* 32 bit aligned memory area */
//    static U32 aMemory[GUI_NUMBYTES / 4];
    static U32 *aMemory;
    aMemory = (U32 *)0x24000000;    
    /*  Assign memory to emWin */
    GUI_ALLOC_AssignMemory(aMemory, GUI_NUMBYTES);
    GUI_ALLOC_SetAvBlockSize(GUI_BLOCKSIZE);
#endif
}

1、通过宏定义来配置使用内部SRAM还是外部的SDRAM做为emWin的动态内存,当配置:

#define  EX_SRAM     1 表示使用外部SDRAM作为emWin动态内存,大小24MB。具体大小是由宏定义#define GUI_NUMBYTES  (1024*1024*24)来设置的。

#define  EX_SRAM     0 表示使用内部SRAM作为emWin动态内存,大小100KB。具体大小是由宏定义#define GUI_NUMBYTES  (100*1024)来设置的。

默认情况下,本教程配套的所有emWin例子都是用外部SDRAM作为emWin动态内存。

2、配置使用外部SDRAM作为emWin动态内存,其中SDRAM_APP_BUF是emWin动态内存的首地址。通过函数GUI_ALLOC_AssignMemory为emWin分配动态内存,注意第二个参数的单位是字节。

通过函数GUI_ALLOC_SetAvBlockSize配置内存块大小,官方手册推荐的大小范围是32字节到1024字节,一般情况下取0x80,即128字节即可。

3、配置使用内部SRAM作为emWin动态内存。其中定义的局部静态变量数组就是emWin动态内存。

我们这里是设置使用了AXI SRAM的100KB空间。

8.7.2  GUIConf.h文件

这个文件主要是默认的系统配置,代码如下:

#ifndef GUICONF_H
#define GUICONF_H

/*********************************************************************
*
*       Multi layer/display support
*/

#define GUI_NUM_LAYERS            2    /* Maximum number of available layers */ 
#define OS_SUPPORT
/*********************************************************************
*
*       Multi tasking support
*/
#ifdef OS_SUPPORT
 #define GUI_OS                    (1)  /* Compile with multitasking support */ 
#else
 #define GUI_OS                    (0)
#endif

/*********************************************************************
*
*       Configuration of touch support
*/
#ifndef   GUI_SUPPORT_TOUCH
  #define GUI_SUPPORT_TOUCH       (1)  /* Support touchscreen */
#endif

/*********************************************************************
*
*       Default font
*/
#define GUI_DEFAULT_FONT          &GUI_Font6x8

/*********************************************************************
*
*         Configuration of available packages
*/
#define GUI_SUPPORT_MOUSE             (1)    /* Support a mouse */
#define GUI_WINSUPPORT                (1)    /* Use window manager */
#define GUI_SUPPORT_MEMDEV            (1)    /* Memory device package available */
#define GUI_SUPPORT_DEVICES           (1)    /* Enable use of device pointers */
#define BUTTON_REACT_ON_LEVEL         (1)    /* Enable button reaction on level */
#define GUI_MEMDEV_SUPPORT_CUSTOMDRAW (1)    /* Enable use of memdev custom draw */
#define GUI_USE_ARGB                  (0)    /* Enable use of ARGB color mode */


#endif  /* Avoid multiple inclusion */

由于emWin是库文件,这些宏定义设置对库是不起作用的,只能运行时调用API配置。其中这个GUI_USE_ARGB也是不起作用(0表示使用ABGR格式,1表示使用ARGB格式),但是常用颜色值用到这个宏定义了,定义在GUI.h文件。此宏定义对下面的颜色值起作用,所以大家使用要注意:

/*********************************************************************
*
*       Standard colors
*/
#define GUI_INVALID_COLOR ((((U32)GUI_TRANS_BYTE) << 24) | 0x00ABCDEFul)  /* Invalid color (transparency + determined color) */

#if (GUI_USE_ARGB)
  #define GUI_MAKE_COLOR(ABGR)  (((((U32)ABGR) & 0xFF000000ul) ^ 0xFF000000ul) | ((((U32)ABGR) & 0x00FF0000ul) >> 16) | (((U32)ABGR) & 0x0000FF00ul) | ((((U32)ABGR) & 0x000000FFul) << 16))
  #define GUI_MAKE_TRANS(Alpha) (255 - (Alpha))
#else
  #define GUI_MAKE_COLOR(ABGR)  (ABGR)
  #define GUI_MAKE_TRANS(Alpha) (Alpha)
#endif

#if (GUI_USE_ARGB)
  #define GUI_TRANS_BYTE 0x00
#else
  #define GUI_TRANS_BYTE 0xFF
#endif

#define GUI_BLUE          GUI_MAKE_COLOR(0x00FF0000)
#define GUI_GREEN         GUI_MAKE_COLOR(0x0000FF00)
#define GUI_RED           GUI_MAKE_COLOR(0x000000FF)
#define GUI_CYAN          GUI_MAKE_COLOR(0x00FFFF00)
#define GUI_MAGENTA       GUI_MAKE_COLOR(0x00FF00FF)
#define GUI_YELLOW        GUI_MAKE_COLOR(0x0000FFFF)
#define GUI_LIGHTBLUE     GUI_MAKE_COLOR(0x00FF8080)
#define GUI_LIGHTGREEN    GUI_MAKE_COLOR(0x0080FF80)
#define GUI_LIGHTRED      GUI_MAKE_COLOR(0x008080FF)
#define GUI_LIGHTCYAN     GUI_MAKE_COLOR(0x00FFFF80)
#define GUI_LIGHTMAGENTA  GUI_MAKE_COLOR(0x00FF80FF)
#define GUI_LIGHTYELLOW   GUI_MAKE_COLOR(0x0080FFFF)
#define GUI_DARKBLUE      GUI_MAKE_COLOR(0x00800000)
#define GUI_DARKGREEN     GUI_MAKE_COLOR(0x00008000)
#define GUI_DARKRED       GUI_MAKE_COLOR(0x00000080)
#define GUI_DARKCYAN      GUI_MAKE_COLOR(0x00808000)
#define GUI_DARKMAGENTA   GUI_MAKE_COLOR(0x00800080)
#define GUI_DARKYELLOW    GUI_MAKE_COLOR(0x00008080)
#define GUI_WHITE         GUI_MAKE_COLOR(0x00FFFFFF)
#define GUI_LIGHTGRAY     GUI_MAKE_COLOR(0x00D3D3D3)
#define GUI_GRAY          GUI_MAKE_COLOR(0x00808080)
#define GUI_DARKGRAY      GUI_MAKE_COLOR(0x00404040)
#define GUI_BLACK         GUI_MAKE_COLOR(0x00000000)
#define GUI_BROWN         GUI_MAKE_COLOR(0x002A2AA5)
#define GUI_ORANGE        GUI_MAKE_COLOR(0x0000A5FF)
#define GUI_TRANSPARENT   GUI_MAKE_COLOR(0xFF000000)

#define GUI_GRAY_3F       GUI_MAKE_COLOR(0x003F3F3F)
#define GUI_GRAY_50       GUI_MAKE_COLOR(0x00505050)
#define GUI_GRAY_55       GUI_MAKE_COLOR(0x00555555)
#define GUI_GRAY_60       GUI_MAKE_COLOR(0x00606060)
#define GUI_GRAY_7C       GUI_MAKE_COLOR(0x007C7C7C)
#define GUI_GRAY_9A       GUI_MAKE_COLOR(0x009A9A9A)
#define GUI_GRAY_AA       GUI_MAKE_COLOR(0x00AAAAAA)
#define GUI_GRAY_C0       GUI_MAKE_COLOR(0x00C0C0C0)
#define GUI_GRAY_C8       GUI_MAKE_COLOR(0x00C8C8C8)
#define GUI_GRAY_D0       GUI_MAKE_COLOR(0x00D0D0D0)
#define GUI_GRAY_E7       GUI_MAKE_COLOR(0x00E7E7E7)
#define GUI_BLUE_98       GUI_MAKE_COLOR(0x00980000)

另外,LCDConf_Lin_Template.c也用到了GUI_USE_ARGB宏定义,大家使用要注意。也是说提供的驱动文件既支持ARGB格式的emWin库,也支持ABGR格式的emWin库,其中MDK提供的emWin库仅支持ABGR格式,而ST提了ARGB和ABGR两种格式库,但emWin版本较低。

8.7.3  LCDConf_Lin_Template.c文件

毫不夸张的说,这个文件是emWin移植过程中最重要的文件,主要配置了emWin的显示尺寸,显示驱动,颜色转换以及底层优化都是在这个文件实现的。当前这个驱动文件已经做得比较成熟完善了,为了完善这个驱动耗费了很多精力。emWin支持的多缓冲,多图层和STM32H7支持的常用颜色格式都可以配置。

如果用户要使用这个文件配置自己的显示屏,直接拿去使用即可,唯一要做的就是根据自己的显示屏和项目需求来设置宏定义,修改本文件中函数LCD_LL_Init里面的时序参数以及显示屏背光设置函数LCD_SetBackLight(这个函数要自己实现,教程提供的例子是在bsp_tft_lcd.c文件中实现),别的什么都不用修改。

供用户使用的选项宏定义,注释已经非常详细,默认情况下,本教程配套的emWin例子都是用的三缓冲,RGB565格式,且仅使用单图层:

/*
**********************************************************************************************************
                                    用户可以配置的选项
**********************************************************************************************************
*/
/* 0 暂时用不到这几个功能 */
#define DrawBitmapA4Enalbe    0
#define ClearCacheHookEnalbe  0
#define DrawAlpha             0
#define UsedDrawBitmap8bpp    0

/* 
  1. 显示屏的物理分辨率,驱动已经做了显示屏自适应,支持4.3寸,5寸和7寸屏
     这里填写自适应显示屏中的最大分辨率。
*/
#define XSIZE_PHYS       800
#define YSIZE_PHYS       480

/* 2. 旋转方向,暂未使用此选项,直接在工程运行时做旋转即可 */
#define ROTATION_0       0
#define ROTATION_CW      1
#define ROTATION_180     2
#define ROTATION_CCW     3

/* 
   3. STM32H7支持的颜色模式。
      (1) 如果打算使用24位色或者32位色,请选择CMS_ARGB8888,如果使用16位色,请选择CMS_RGB565,其它颜色格式不再做支持。
      (2) 如果用户选择了ARGB8888或者RGB888模式,LCD闪烁的话,优先查看是否是此贴的问题:
          http://www.armbbs.cn/forum.php?mod=viewthread&tid=16892 (适用于F429和H7系列)
          如果不是这个问题,再降低LTDC输出时钟即可,在本文件的函数LCD_ConfigLTDC里面    
*/
#define CMS_ARGB8888 1
#define CMS_RGB888   2
#define CMS_RGB565   3
#define CMS_ARGB1555 4
#define CMS_ARGB4444 5
#define CMS_L8       6
#define CMS_AL44     7
#define CMS_AL88     8

/* 4. 多缓冲 / 虚拟屏,多缓冲和虚拟屏不可同时使用,emWin不支持 */
#define NUM_BUFFERS      3 /* 定义多缓冲个数,仅可以设置1,2和3,也就是最大支持三缓冲 */
#define NUM_VSCREENS     1 /* 定义虚拟屏个数 */

/* 
   5. 重定义图层数,对于STM32H7,用户可以选择一个图层或者两个图层,不支持三图层 
      (1). 设置GUI_NUM_LAYERS = 1时,即仅使用图层1时,默认触摸值是发送给图层1的。
      (2). 设置GUI_NUM_LAYERS = 2时,即图层1和图层2都已经使能,此时图层2是顶层,
           用户需要根据自己的使用情况设置如下两个地方。
           a. 在bsp_touch.c文件中的函数TOUCH_InitHard里面设置参数State.Layer = 1,1就表示
              给图层2发送触摸值。
           b. 调用GUI_Init函数后,调用函数GUI_SelectLayer(1), 设置当前操作的是图层2。
*/
#undef  GUI_NUM_LAYERS
#define GUI_NUM_LAYERS 1

/* 
   6. 设置图层1和图层2对应的显存地址
      (1) EXT_SDRAM_ADDR 是SDRAM的首地址。
      (2) LCD_LAYER0_FRAME_BUFFER 是图层1的显存地址。
      (3) LCD_LAYER1_FRAME_BUFFER 是图层2的显存地址。
      (4) 每个图层的显存大小比较考究,这里进行下简单的说明。
          如果用户选择的颜色模式 = 32位色ARGB8888,显存的大小:
          XSIZE_PHYS * YSIZE_PHYS * 4 * NUM_VSCREENS * NUM_BUFFERS
          
          颜色模式 = 24位色RGB888,显存的大小:
          XSIZE_PHYS * YSIZE_PHYS * 3 * NUM_VSCREENS * NUM_BUFFERS
          
          颜色模式 = 16位色RGB566,ARGB1555, ARGB4444,AL88,那么显存的大小就是:
          XSIZE_PHYS * YSIZE_PHYS * 2 * NUM_VSCREENS * NUM_BUFFERS

          颜色模式 = 8位色L8,AL44,那么显存的大小就是:
          XSIZE_PHYS * YSIZE_PHYS * 1 * NUM_VSCREENS * NUM_BUFFERS    
      
      这里为了方便起见,将开发板配套的24MB的SDRAM前8MB分配给LCD显存使用,后24MB用于emWin动态内存。
      对于24位色,16位色,8位色,用户可以对其使能三缓冲,并且使能双图层。但是32位色也使能三缓冲和双
      图层的话会超出8MB,所以用户根据自己的情况做显存和emWin动态内存的分配调整。
        举一个例子,对于800*480分辨率的显示屏,使能32位色,三缓冲,那么最终一个图层需要的大小就是
      800 * 480 * 4 * 3  = 4.394MB的空间,如果是双图层,已经超出8MB的分配范围。

      (5)为了方便起见,图层2的宏定义LCD_LAYER1_FRAME_BUFFER中的参数4是按照32位色设置的,如果用户的图层1
         使用的是8位色,这里填数字1,如果是16位色,这里填2,如果是24位色,这里填3。
*/
#define LCD_LAYER0_FRAME_BUFFER  EXT_SDRAM_ADDR
#define LCD_LAYER1_FRAME_BUFFER  (LCD_LAYER0_FRAME_BUFFER + XSIZE_PHYS * YSIZE_PHYS * 4 * NUM_VSCREENS * NUM_BUFFERS)


/* 7. 配置图层1的颜色模式和分辨率大小 */
#define COLOR_MODE_0  CMS_RGB565
#define ORIENTATION_0 ROTATION_0
#define XSIZE_0       XSIZE_PHYS
#define YSIZE_0       YSIZE_PHYS

/* 8. 配置图层2的的颜色模式和分辨率大小 */
#define COLOR_MODE_1  CMS_RGB565
#define ORIENTATION_1 ROTATION_0
#define XSIZE_1       XSIZE_PHYS
#define YSIZE_1       YSIZE_PHYS

/* 9. 没有图层激活时,背景色设置, 暂时未用到 */
#define BK_COLOR      GUI_DARKBLUE


/* 10. 单图层情况下,根据用户选择的颜色模式可自动选择图层1的emWin的颜色模式 */
#if   (COLOR_MODE_0 == CMS_ARGB8888)
  #define COLOR_CONVERSION_0 GUICC_M8888I
#elif (COLOR_MODE_0 == CMS_RGB888)
  #define COLOR_CONVERSION_0 GUICC_M888
#elif (COLOR_MODE_0 == CMS_RGB565)
  #define COLOR_CONVERSION_0 GUICC_M565
#elif (COLOR_MODE_0 == CMS_ARGB1555)
  #define COLOR_CONVERSION_0 GUICC_M1555I
#elif (COLOR_MODE_0 == CMS_ARGB4444)
  #define COLOR_CONVERSION_0 GUICC_M4444I
#elif (COLOR_MODE_0 == CMS_L8)
  #define COLOR_CONVERSION_0 GUICC_8666
#elif (COLOR_MODE_0 == CMS_AL44)
  #define COLOR_CONVERSION_0 GUICC_1616I
#elif (COLOR_MODE_0 == CMS_AL88)
  #define COLOR_CONVERSION_0 GUICC_88666I
#else
  #error Illegal color mode 0!
#endif

/* 11. 单图层情况下,根据用户选择的颜色模式可自动选择图层1的emWin的驱动 */
#if   (COLOR_MODE_0 == CMS_ARGB8888)
  #if   (ORIENTATION_0 == ROTATION_0)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_32
  #elif (ORIENTATION_0 == ROTATION_CW)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OSX_32
  #elif (ORIENTATION_0 == ROTATION_180)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OXY_32
  #elif (ORIENTATION_0 == ROTATION_CCW)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OSY_32
  #endif
#elif (COLOR_MODE_0 == CMS_RGB888)
  #if   (ORIENTATION_0 == ROTATION_0)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_24
  #elif (ORIENTATION_0 == ROTATION_CW)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OSX_24
  #elif (ORIENTATION_0 == ROTATION_180)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OXY_24
  #elif (ORIENTATION_0 == ROTATION_CCW)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OSY_24
  #endif
#elif (COLOR_MODE_0 == CMS_RGB565)   
   || (COLOR_MODE_0 == CMS_ARGB1555) 
   || (COLOR_MODE_0 == CMS_ARGB4444) 
   || (COLOR_MODE_0 == CMS_AL88)
  #if   (ORIENTATION_0 == ROTATION_0)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_16
  #elif (ORIENTATION_0 == ROTATION_CW)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OSX_16
  #elif (ORIENTATION_0 == ROTATION_180)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OXY_16
  #elif (ORIENTATION_0 == ROTATION_CCW)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OSY_16
  #endif
#elif (COLOR_MODE_0 == CMS_L8)   
   || (COLOR_MODE_0 == CMS_AL44)
  #if   (ORIENTATION_0 == ROTATION_0)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_8
  #elif (ORIENTATION_0 == ROTATION_CW)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OSX_8
  #elif (ORIENTATION_0 == ROTATION_180)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OXY_8
  #elif (ORIENTATION_0 == ROTATION_CCW)
    #define DISPLAY_DRIVER_0   GUIDRV_LIN_OSY_8
  #endif
#endif


/* 12. 双图层情况下,根据用户选择的颜色模式可自动选择图层2的emWin的颜色模式 */
#if (GUI_NUM_LAYERS > 1)
#if   (COLOR_MODE_1 == CMS_ARGB8888)
  #define COLOR_CONVERSION_1 GUICC_M8888I
#elif (COLOR_MODE_1 == CMS_RGB888)
  #define COLOR_CONVERSION_1 GUICC_M888
#elif (COLOR_MODE_1 == CMS_RGB565)
  #define COLOR_CONVERSION_1 GUICC_M565
#elif (COLOR_MODE_1 == CMS_ARGB1555)
  #define COLOR_CONVERSION_1 GUICC_M1555I
#elif (COLOR_MODE_1 == CMS_ARGB4444)
  #define COLOR_CONVERSION_1 GUICC_M4444I
#elif (COLOR_MODE_1 == CMS_L8)
  #define COLOR_CONVERSION_1 GUICC_8666
#elif (COLOR_MODE_1 == CMS_AL44)
  #define COLOR_CONVERSION_1 GUICC_1616I
#elif (COLOR_MODE_1 == CMS_AL88)
  #define COLOR_CONVERSION_1 GUICC_88666I
#else
  #error Illegal color mode 0!
#endif

/* 13. 双图层情况下,根据用户选择的颜色模式可自动选择图层2的emWin的驱动 */
#if   (COLOR_MODE_1 == CMS_ARGB8888)
  #if   (ORIENTATION_1 == ROTATION_0)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_32
  #elif (ORIENTATION_1 == ROTATION_CW)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OSX_32
  #elif (ORIENTATION_1 == ROTATION_180)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OXY_32
  #elif (ORIENTATION_1 == ROTATION_CCW)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OSY_32
  #endif
#elif (COLOR_MODE_1 == CMS_RGB888)
  #if   (ORIENTATION_1 == ROTATION_0)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_24
  #elif (ORIENTATION_1 == ROTATION_CW)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OSX_24
  #elif (ORIENTATION_1 == ROTATION_180)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OXY_24
  #elif (ORIENTATION_1 == ROTATION_CCW)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OSY_24
  #endif
#elif (COLOR_MODE_1 == CMS_RGB565)   
   || (COLOR_MODE_1 == CMS_ARGB1555) 
   || (COLOR_MODE_1 == CMS_ARGB4444) 
   || (COLOR_MODE_1 == CMS_AL88)
  #if   (ORIENTATION_1 == ROTATION_0)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_16
  #elif (ORIENTATION_1 == ROTATION_CW)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OSX_16
  #elif (ORIENTATION_1 == ROTATION_180)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OXY_16
  #elif (ORIENTATION_1 == ROTATION_CCW)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OSY_16
  #endif
#elif (COLOR_MODE_1 == CMS_L8)   
   || (COLOR_MODE_1 == CMS_AL44)
  #if   (ORIENTATION_1 == ROTATION_0)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_8
  #elif (ORIENTATION_1 == ROTATION_CW)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OSX_8
  #elif (ORIENTATION_1 == ROTATION_180)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OXY_8
  #elif (ORIENTATION_1 == ROTATION_CCW)
    #define DISPLAY_DRIVER_1   GUIDRV_LIN_OSY_8
  #endif
#endif

#else

#undef XSIZE_0
#undef YSIZE_0
#define XSIZE_0 XSIZE_PHYS
#define YSIZE_0 YSIZE_PHYS
     
#endif

/*14. 配置选项检测,防止配置错误或者某些选项没有配置 */
#if NUM_BUFFERS > 3
  #error More than 3 buffers make no sense and are not supported in this configuration file!
#endif
#ifndef   XSIZE_PHYS
  #error Physical X size of display is not defined!
#endif
#ifndef   YSIZE_PHYS
  #error Physical Y size of display is not defined!
#endif
#ifndef   NUM_BUFFERS
  #define NUM_BUFFERS 1
#else
  #if (NUM_BUFFERS <= 0)
    #error At least one buffer needs to be defined!
  #endif
#endif
#ifndef   NUM_VSCREENS
  #define NUM_VSCREENS 1
#else
  #if (NUM_VSCREENS <= 0)
    #error At least one screeen needs to be defined!
  #endif
#endif
#if (NUM_VSCREENS > 1) && (NUM_BUFFERS > 1)
  #error Virtual screens together with multiple buffers are not allowed!
#endif

==============

这里就不把这个文件展开为大家讲解了,因为展开后涉及到的内容非常多,工作量稍大。但为了满足爱研究的同学,把如何学习这个文件的方法说一说,这样就能事半功倍了,要不不知道从哪里着手:

  •   这个文件里面最主要的就是函数LCD_X_Config和函数LCD_X_DisplayDriver,除了LTDC行中断函数以外,所有其它的函数都是直接或者间接的被这两个函数所调用,有兴趣的同学可以在纸上把这两个函数所调用函数的层层关系全部关联起来,然后逐个进行击破,这样就有一个比较全面的认识了。
  •   把这个文件里面各个函数的关系搞清楚之后,有必要了解下函数LCD_X_Config和函数LCD_X_DisplayDriver是如何被emWin调用的。对于这个问题,emWin官方手册的配置章节已经给出了答案,就是下面这个截图:

 

由上面的截图可以看出,函数LCD_X_Config和函数LCD_X_DisplayDriver在用户所调用的初始化函数GUI_Init里面依次被调用。

(特别补充,大部分API都是DMA2D的优化加速,在本教程的第6章有详细说明)

8.8   第6步:emWin的RTOS方式接口文件

emWin的RTOS方式接口文件就是第1步中添加的GUI_X_uCOS-III.c文件,关于RTOS方式的接口文件,在MDK的安装目录中给的比较全面,像embOS,RTX,FreeRTOS,uEZ等小型操作系统都提供接口了,其中GUI_X_uCOS.c文件是基于uCOS-II的,并没有uCOS-III的接口文件,所以需要自己实现。我们的GUI_X_uCOS-III.c接口文件就是仿照GUI_X_uCOS.c实现的。

GUI_X_uCOS-III.c的代码实现如下:

#include <os.h>
#include "GUI_Private.H"

/*
*********************************************************************************************************
*                                         GLOBAL VARIABLES
*********************************************************************************************************
*/

static  OS_SEM  DispSem;
static  OS_SEM  EventSem;

static  OS_SEM  KeySem;
static  int     KeyPressed;
static  char    KeyIsInited;


/*
*********************************************************************************************************
*                                        TIMING FUNCTIONS
*
* Notes: Some timing dependent routines of uC/GUI require a GetTime and delay funtion. 
*        Default time unit (tick), normally is 1 ms.
*********************************************************************************************************
*/

int  GUI_X_GetTime (void)  //--------------(1)
{
    OS_TICK  time_cur;
    OS_ERR   os_err;
    
    time_cur = OSTimeGet(&os_err); 
   (void)&os_err;

    return ((int)time_cur);
}

void  GUI_X_Delay (int period)  //--------------(2)
{
    OS_ERR      err;
    OSTimeDly(period, OS_OPT_TIME_DLY, &err);
}


/*
*********************************************************************************************************
*                                          GUI_X_ExecIdle()
*********************************************************************************************************
*/
void GUI_X_ExecIdle (void)  //--------------(3)
{
    GUI_X_Delay(1);
}


/*
*********************************************************************************************************
*                                    MULTITASKING INTERFACE FUNCTIONS
*
* Note(1): 1) The following routines are required only if uC/GUI is used in a true multi task environment, 
*             which means you have more than one thread using the uC/GUI API.  In this case the #define 
*             GUI_OS 1   needs to be in GUIConf.h
*********************************************************************************************************
*/

void  GUI_X_InitOS (void)  //--------------(4)
{ 
    OS_ERR     err;

    /* 用于资源共享 cnt = 1*/
    OSSemCreate((OS_SEM    *)&DispSem,
                (CPU_CHAR  *)"DispSem",
                (OS_SEM_CTR )1,
                (OS_ERR    *)&err);
    /* 用于事件触发 cnt = 0*/
    OSSemCreate((OS_SEM    *)&EventSem,
                (CPU_CHAR  *)"EventSem",
                (OS_SEM_CTR )0,
                (OS_ERR    *)&err);
}

void  GUI_X_Lock (void)   //--------------(5)
{ 
    OS_ERR     err;
       
    OSSemPend((OS_SEM *)&DispSem,
              (OS_TICK )0,
              (OS_OPT  )OS_OPT_PEND_BLOCKING,
              (CPU_TS  )0,
              (OS_ERR *)&err);
}

void  GUI_X_Unlock (void)  //--------------(6)
{ 
    OS_ERR     err;

    OSSemPost((OS_SEM *)&DispSem,
              (OS_OPT  )OS_OPT_POST_1,
              (OS_ERR *)&err);
}


U32  GUI_X_GetTaskId (void)   //--------------(7)
{ 
    CPU_INT16U  id;

    /* 由于存在同优先级的任务,这里不知道是不是可以, uCOS-III中已经没有任务的ID */
    id = (CPU_INT16U)OSTCBCurPtr->Prio;     
    
    return ((U32)id);
}

/*
*********************************************************************************************************
*                                        GUI_X_WaitEvent()
*                                        GUI_X_SignalEvent()
*********************************************************************************************************
*/


void GUI_X_WaitEvent (void)  //--------------(8)
{
    OS_ERR     err;
       
    OSSemPend((OS_SEM *)&EventSem,
    (OS_TICK )0,
    (OS_OPT  )OS_OPT_PEND_BLOCKING,
    (CPU_TS  )0,
    (OS_ERR *)&err);
}


void GUI_X_SignalEvent (void)  //--------------(9)
{
    OS_ERR     err;

    OSSemPost((OS_SEM *)&EventSem,
          (OS_OPT  )OS_OPT_POST_1,
          (OS_ERR *)&err);
}

/*
*********************************************************************************************************
*                                      KEYBOARD INTERFACE FUNCTIONS
*
* Purpose: The keyboard routines are required only by some widgets.
*          If widgets are not used, they may be eliminated.
*
* Note(s): If uC/OS-II is used, characters typed into the log window will be placed    in the keyboard buffer. 
*          This is a neat feature which allows you to operate your target system without having to use or 
*          even to have a keyboard connected to it. (useful for demos !)
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                           CheckInit()
*
* Description : Initialize the GUI keyboard if it is not already done.
*
* Argument(s) : none.
*
* Return(s)   : none.
*
* Caller(s)   : GUI_X_WaitKey().
*               GUI_X_GetKey().
*
* Note(s)     : none.
*********************************************************************************************************
*/
void  CheckInit (void)  //--------------(10)
{
    if (KeyIsInited == DEF_FALSE) {
        KeyIsInited = DEF_TRUE;
        GUI_X_Init();
    }
}

/*
*********************************************************************************************************
*                                           GUI_X_Init()
*
* Description : (1) Perform keyboard initialization :
*
*                   (a) Implement keyboard initialization signal by creating a counting semaphore.
*
*                       (1) Initialize keyboard initialization signal with no signal by setting the
*                           semaphore count to 0 to block the semaphore.
*
* Argument(s) : none.
*
* Return(s)   : none.
*
* Caller(s)   : CheckInit().
*
* Note(s)     : none.
*********************************************************************************************************
*/
void GUI_X_Init (void)  //--------------(11)
{
    OS_ERR err;
    
    OSSemCreate((OS_SEM    *)&KeySem,
                (CPU_CHAR  *)"KeySem",
                (OS_SEM_CTR )0,
                (OS_ERR    *)&err);
}

/*
*********************************************************************************************************
*                                           GUI_X_GetKey()
*
* Description : Get the pressed key.
*
* Argument(s) : none.
*
* Return(s)   : Pressed key.
*
* Caller(s)   : various.
*
* Note(s)     : none.
*********************************************************************************************************
*/
int  GUI_X_GetKey (void)  //--------------(12)
{
    int r;

    r          = KeyPressed;
    CheckInit();
    KeyPressed = 0;
    return (r);
}

/*
*********************************************************************************************************
*                                           GUI_X_WaitKey()
*
* Description : Wait for a key to be pressed and return it.
*
* Argument(s) : none.
*
* Return(s)   : Pressed key.
*
* Caller(s)   : various.
*
* Note(s)     : none.
*********************************************************************************************************
*/
int  GUI_X_WaitKey (void)  //--------------(13)
{
    int    r;
    OS_ERR err;


    CheckInit();
    if (KeyPressed == 0) {   
        OSSemPend((OS_SEM *)&EventSem,
        (OS_TICK )0,
        (OS_OPT  )OS_OPT_PEND_BLOCKING,
        (CPU_TS  )0,
        (OS_ERR *)&err);
    }
    r          = KeyPressed;
    KeyPressed = 0;
    return (r);           
}

/*
*********************************************************************************************************
*                                          GUI_X_StoreKey()
*
* Description : Store the pressed key.
*
* Argument(s) : Pressed key.
*
* Return(s)   : none.
*
* Caller(s)   : various.
*
* Note(s)     : none.
*********************************************************************************************************
*/
void  GUI_X_StoreKey (int k)   //--------------(14)
{
    OS_ERR     err;
    
    KeyPressed = k;
    OSSemPost((OS_SEM *)&KeySem,
          (OS_OPT  )OS_OPT_POST_1,
          (OS_ERR *)&err);
}

void GUI_X_Log     (const char *s) { GUI_USE_PARA(s); }  //--------------(15)
void GUI_X_Warn    (const char *s) { GUI_USE_PARA(s); }
void GUI_X_ErrorOut(const char *s) { GUI_USE_PARA(s); }
  1. 函数GUI_X_GetTime用于获取系统时间基准。通过调用uCOS-III的系统时钟节拍计数函数实现。
  2. 函数GUI_X_Delay用于实现延迟,通过调用uCOS-III的延迟函数OSTimeDly实现。
  3. emWin的窗口管理器处于空闲状态时被调用。
  4. 用于实现emWin多任务的接口函数GUI_X_InitOS,此函数内创建了两个信号量,一个用于实现资源共享,另一个用于实现事件触发或者说是信号同步。
  5. 资源锁函数GUI_X_Lock。
  6. 资源开锁函数GUI_X_Unlock。
  7. 函数GUI_X_GetTaskId用于获取任务ID,由于uCOS-III里面已经没有任务ID的概念了,此函数里面是将任务优先级做任务ID,这种方法也是可以的,因为Micrium官方就是这么做的,详见此贴:http://www.armbbs.cn/forum.php?mod=viewthread&tid=18009
  8. 函数GUI_X_WaitEvent用于等待事件。
  9. 函数GUI_X_SignalEvent用于释放事件。
  10. 下面的都是键盘接口函数,这里的键盘是指实体键盘或者类似实体键盘的外部输入设备。函数CheckInit用于检测键盘是否已经初始化,如果没有初始化,调用函数GUI_X_Init进行初始化,并设置全局变量KeyIsInited = DEF_TRUE,表示已经初始化过了。
  11. 函数GUI_X_Init中创建了一个信号量,用于实现按键值的同步。
  12. 函数GUI_X_GetKey以非阻塞方式获取键值。
  13. 函数GUI_X_WaitKey以阻塞方式等待按键被按下。
  14. 函数GUI_X_StoreKey用于存储键值,实际上就是调用信号量Post函数,让等待键值的任务可以及时获得。
  15. 剩下的这三个函数GUI_X_Log,GUI_X_Warn和GUI_X_ErrorOut未用到。

8.9   第7步:SDRAM的MPU Cache配置

默认情况下SDRAM空间的MPU配置仅开启了读Cache,因为读性能的提升对于GUIX性能的提升很重要。另外还通过条件编译设置了个最低性能配置,即读Cache和写Cache都关闭了。方便大家测试阶段做选择(函数位置在bsp.c文件里面的函数MPU_Config):

    /* 开启了读Cache */
#if 1
    /* 配置SDRAM的MPU属性为Write through, read allocate,no write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0xC0000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_32MB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
    /* 最低性能,读Cache和写Cache都关闭 */
#else
    /* 配置SDRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0xC0000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_32MB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
#endif

8.10 第8步:添加emWin应用进行测试

为了测试移植的工程是否可用,为其做了一个简单的测试界面,显示效果如下:

 

界面能够显示出来只能说明显示驱动没问题了,还要测试触摸。这个例子已经开启了游标显示,用户可以水平或者垂直地在界面上滑动,看看游标如何移动,如果能够跟着手一起移动说明触摸也没有问题。如果不能,就要分两种情况进行分析:

  •   对于电阻屏需要做触摸校准,校准方法看教程第66章附件A中的说明。另外,如果这个是大家自己设计的,就要排查代码和触摸板的硬件设计是否有问题。
  •   由于电容屏是自动触摸校准的,出现这种情况的话需要排查代码和触摸板的硬件设计。

下面是编写的测试代码,配套的测试例子完整版是:V7-505_emWin6.x实验_RTOS方式移植模板。

实验内容:

  1. K1按键按下,串口打印任务执行情况(波特率115200,数据位8,奇偶校验位无,停止位1)。
  2. 各个任务实现的功能如下:

App Task Start   任务 :实现按键和触摸扫描。

App Task MspPro任务 :暂未使用。

App Task UserIF  任务 :按键消息处理。

App Task COM   任务 :暂未使用。

App Task GUI    任务 :GUI任务。

μCOS-III任务调试信息(按K1按键,串口打印):

 

RTT 打印信息方式:

 

程序设计:

  任务栈大小分配:

  • μCOS-III任务栈大小在app_cfg.h文件中配置:

#define  APP_CFG_TASK_START_STK_SIZE                      512u

#define  APP_CFG_TASK_MsgPro_STK_SIZE                     2048u

#define  APP_CFG_TASK_COM_STK_SIZE                        512u

#define  APP_CFG_TASK_USER_IF_STK_SIZE                    512u

#define  APP_CFG_TASK_GUI_STK_SIZE                        2048u

  • 任务栈大小的单位是4字节,那么每个任务的栈大小如下:

App Task Start   任务 :2048字节。

App Task MspPro任务 :8192字节。

App Task UserIF  任务 :2048字节。

App Task COM   任务 :2048字节。

App Task GUI    任务 :8192字节。

  系统栈大小分配:

μCOS-III的系统栈大小在os_cfg_app.h文件中配置:

#define  OS_CFG_ISR_STK_SIZE                      512u     

系统栈大小的单位是4字节,那么这里就是配置系统栈大小为2KB

  μCOS-III初始化:

/*
*********************************************************************************************************
*    函 数 名: main
*    功能说明: 标准c程序入口。
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
int main(void)
{
    OS_ERR  err;  
    
    /* 初始化uC/OS-III 内核 */
    OSInit(&err);  

    /* 创建一个启动任务(也就是主任务)。启动任务会创建所有的应用程序任务 */
    OSTaskCreate((OS_TCB       *)&AppTaskStartTCB,  /* 任务控制块地址 */           
                 (CPU_CHAR     *)"App Task Start",  /* 任务名 */
                 (OS_TASK_PTR   )AppTaskStart,      /* 启动任务函数地址 */
                 (void         *)0,                 /* 传递给任务的参数 */
                 (OS_PRIO       )APP_CFG_TASK_START_PRIO, /* 任务优先级 */
                 (CPU_STK      *)&AppTaskStartStk[0],     /* 堆栈基地址 */
                 (CPU_STK_SIZE  )APP_CFG_TASK_START_STK_SIZE / 10, /* 堆栈监测区,这里表示后10%作为监测区 */
                 (CPU_STK_SIZE  )APP_CFG_TASK_START_STK_SIZE,      /* 堆栈空间大小 */
                 (OS_MSG_QTY    )0,  /* 本任务支持接受的最大消息数 */
                 (OS_TICK       )0,  /* 设置时间片 */
                 (void         *)0,  /* 堆栈空间大小 */  
                 (OS_OPT        )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
        
                 /*  定义如下:
                    OS_OPT_TASK_STK_CHK      使能检测任务栈,统计任务栈已用的和未用的
                    OS_OPT_TASK_STK_CLR      在创建任务时,清零任务栈
                    OS_OPT_TASK_SAVE_FP      如果CPU有浮点寄存器,则在任务切换时保存浮点寄存器的内容
                 */  
                 (OS_ERR       *)&err);

    /* 启动多任务系统,控制权交给uC/OS-III */
    OSStart(&err);                                               
    
    (void)&err;
    
    return (0);
}

int main(void)
{
    OS_ERR  err;  
    
    /* HAL库,MPU,Cache,时钟等系统初始化 */
    System_Init();

    /* 内核开启前关闭HAL的时间基准 */
    HAL_SuspendTick();
    
    /* 初始化滴答时钟,在启动任务里面开启 */    
    BSP_OS_TickInit();
    
    /* 初始化uC/OS-III 内核 */
    OSInit(&err);  

    /* 创建一个启动任务(也就是主任务)。启动任务会创建所有的应用程序任务 */
    OSTaskCreate((OS_TCB       *)&AppTaskStartTCB,  /* 任务控制块地址 */           
                 (CPU_CHAR     *)"App Task Start",  /* 任务名 */
                 (OS_TASK_PTR   )AppTaskStart,      /* 启动任务函数地址 */
                 (void         *)0,                 /* 传递给任务的参数 */
                 (OS_PRIO       )APP_CFG_TASK_START_PRIO, /* 任务优先级 */
                 (CPU_STK      *)&AppTaskStartStk[0],     /* 堆栈基地址 */
                 (CPU_STK_SIZE  )APP_CFG_TASK_START_STK_SIZE / 10, /* 堆栈监测区,这里表示后10%作为监测区 */
                 (CPU_STK_SIZE  )APP_CFG_TASK_START_STK_SIZE,      /* 堆栈空间大小 */
                 (OS_MSG_QTY    )0,  /* 本任务支持接受的最大消息数 */
                 (OS_TICK       )0,  /* 设置时间片 */
                 (void         *)0,  /* 堆栈空间大小 */  
                 (OS_OPT        )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
        
                 /*  定义如下:
                    OS_OPT_TASK_STK_CHK      使能检测任务栈,统计任务栈已用的和未用的
                    OS_OPT_TASK_STK_CLR      在创建任务时,清零任务栈
                    OS_OPT_TASK_SAVE_FP      如果CPU有浮点寄存器,则在任务切换时保存浮点寄存器的内容
                 */  
                 (OS_ERR       *)&err);

    /* 启动多任务系统,控制权交给uC/OS-III */
    OSStart(&err);                                               
    
    (void)&err;
    
    return (0);
}

  硬件外设初始化

硬件外设的初始化是在bsp.c文件实现:

/*
*********************************************************************************************************
*    函 数 名: System_Init
*    功能说明: 系统初始化,主要是MPU,Cache和系统时钟配置
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
void System_Init(void)
{
    
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
       - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
       - 设置NVIC优先级分组为4。
     */
    HAL_Init();

    /* 
       配置系统时钟到400MHz
       - 切换使用HSE。
       - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第8章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder并开启 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif

#if Enable_RTTViewer == 1  
    /* 配置通道0,上行配置*/
    SEGGER_RTT_ConfigUpBuffer(0, "RTTUP", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP);
    
    /* 配置通道0,下行配置*/    
    SEGGER_RTT_ConfigDownBuffer(0, "RTTDOWN", NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP);
#endif
}

/*
*********************************************************************************************************
*    函 数 名: bsp_Init
*    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void bsp_Init(void)
{
    bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    bsp_InitTimer();      /* 初始化滴答定时器 */
    bsp_InitUart();        /* 初始化串口 */
    bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    
    bsp_InitLed();        /* 初始化LED */    
    bsp_InitExtSDRAM(); /* 初始化SDRAM */
    
    bsp_InitI2C();        /* 初始化I2C总线 */
    TOUCH_InitHard();   /* 初始化触摸芯片,LCD面板型号的检查也在此函数,所以要在函数LCD_InitHard前调用 */ 
    
    /* 延迟200ms再点亮背光,避免瞬间高亮 */
//    bsp_DelayMS(200); 
//    LCD_SetBackLight(255);
}

  五个μCOS-III任务的实现:

/*
*********************************************************************************************************
*    函 数 名: 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);
    }
}

/*
*********************************************************************************************************
*    函 数 名: AppTaskMsgPro
*    功能说明: 消息处理,这里用作LED闪烁
*    形    参: p_arg 是在创建该任务时传递的形参
*    返 回 值: 无
    优 先 级: 3
*********************************************************************************************************
*/
static void AppTaskMsgPro(void *p_arg)
{
    OS_ERR  err;  
    
    (void)p_arg;
          
    while(1)
    {
        bsp_LedToggle(2);
        OSTimeDly(200, OS_OPT_TIME_DLY, &err);        
    }   
}

/*
*********************************************************************************************************
*    函 数 名: AppTaskUserIF
*    功能说明: 按键消息处理
*    形    参: p_arg 是在创建该任务时传递的形参
*    返 回 值: 无
    优 先 级: 4
*********************************************************************************************************
*/
static void AppTaskUserIF(void *p_arg)
{
    OS_ERR  err;  
    uint8_t ucKeyCode;    /* 按键代码 */
    
    (void)p_arg;
          
    while(1)
    {        
        ucKeyCode = bsp_GetKey();
        
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:              /* K1键按下 打印任务执行情况 */
                    DispTaskInfo();         
                    break;
                
                default:                     /* 其他的键值不处理 */
                    break;
            }
        }
        
        OSTimeDly(20, OS_OPT_TIME_DLY, &err);
    }
}

/*
*********************************************************************************************************
*    函 数 名: AppTaskCom
*    功能说明: 暂未使用
*    形    参: p_arg 是在创建该任务时传递的形参
*    返 回 值: 无
    优 先 级: 5
*********************************************************************************************************
*/
static void AppTaskCOM(void *p_arg)
{
    OS_ERR  err;      

    (void)p_arg;
    
    while(1)
    {
        OSTimeDly(1000, OS_OPT_TIME_DLY, &err);
    }                                                                                          
}

/*
*********************************************************************************************************
*    函 数 名: AppTaskGUI
*    功能说明: GUI任务,最低优先级                        
*    形    参:p_arg 是在创建该任务时传递的形参
*    返 回 值: 无
*   优 先 级:OS_CFG_PRIO_MAX - 4u
*********************************************************************************************************
*/
static void AppTaskGUI(void *p_arg)
{    
    (void)p_arg;        /* 避免编译器告警 */
        
    while (1) 
    {
        MainTask();
    }
}

  emWin任务的具体实现(在MainTask.c文件里面):

#include "includes.h"
#include "MainTask.h"


/*
*********************************************************************************************************
*                           GUI_WIDGET_CREATE_INFO类型数组
*********************************************************************************************************
*/
static const GUI_WIDGET_CREATE_INFO _aDialogCreate[] = {
    { FRAMEWIN_CreateIndirect,  "armfly",            0,           0,  0,  480,272,FRAMEWIN_CF_MOVEABLE,0},
    { BUTTON_CreateIndirect,    "BUTTON0",           GUI_ID_BUTTON0,          29, 20, 200,50, 0,0},
    { SLIDER_CreateIndirect,     NULL,               GUI_ID_SLIDER0,          29, 143,218,40, 0,0}
};

/*
*********************************************************************************************************
*    函 数 名: PaintDialog
*    功能说明: 对话框回调函数的WM_PAINT消息处理    
*    形    参: pMsg  回调参数 
*    返 回 值: 无
*********************************************************************************************************
*/
void PaintDialog(WM_MESSAGE * pMsg)
{
    //WM_HWIN hWin = pMsg->hWin;

}

/*
*********************************************************************************************************
*    函 数 名: InitDialog
*    功能说明: 对话框回调函数的WM_INIT_DIALOG消息处理    
*    形    参: pMsg  回调参数 
*    返 回 值: 无
*********************************************************************************************************
*/
void InitDialog(WM_MESSAGE * pMsg)
{
    WM_HWIN hWin = pMsg->hWin;
    WM_HWIN hItem;
    
    //
    //FRAMEWIN
    //
    FRAMEWIN_SetFont(hWin,&GUI_Font32B_ASCII);
    FRAMEWIN_AddCloseButton(hWin, FRAMEWIN_BUTTON_RIGHT, 0);
    FRAMEWIN_AddMaxButton(hWin, FRAMEWIN_BUTTON_RIGHT, 1);
    FRAMEWIN_AddMinButton(hWin, FRAMEWIN_BUTTON_RIGHT, 2);
    FRAMEWIN_SetTitleHeight(hWin,32);
    
    
    hItem = WM_GetDialogItem(pMsg->hWin, GUI_ID_BUTTON0);
    BUTTON_SetFont(hItem, GUI_FONT_16_1);
}

/*
*********************************************************************************************************
*    函 数 名: _cbCallback
*    功能说明: 对话框回调函数        
*    形    参: pMsg  回调参数 
*    返 回 值: 无
*********************************************************************************************************
*/
static void _cbCallback(WM_MESSAGE * pMsg) 
{
    int NCode, Id;
    WM_HWIN hWin = pMsg->hWin;
    
    switch (pMsg->MsgId) 
    {
        case WM_PAINT:
            PaintDialog(pMsg);
            break;
        
        case WM_INIT_DIALOG:
            InitDialog(pMsg);
            break;
        
        case WM_KEY:
            switch (((WM_KEY_INFO*)(pMsg->Data.p))->Key) 
            {
                case GUI_KEY_ESCAPE:
                    GUI_EndDialog(hWin, 1);
                    break;
                case GUI_KEY_ENTER:
                    GUI_EndDialog(hWin, 0);
                    break;
            }
            break;
            
        case WM_NOTIFY_PARENT:
            Id = WM_GetId(pMsg->hWinSrc); 
            NCode = pMsg->Data.v;        
            switch (Id) 
            {
                case GUI_ID_OK:
                    if(NCode==WM_NOTIFICATION_RELEASED)
                        GUI_EndDialog(hWin, 0);
                    break;
                case GUI_ID_CANCEL:
                    if(NCode==WM_NOTIFICATION_RELEASED)
                        GUI_EndDialog(hWin, 0);
                    break;

            }
            break;
            
        default:
            WM_DefaultProc(pMsg);
    }
}

/*
*********************************************************************************************************
*    函 数 名: MainTask
*    功能说明: GUI主函数
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void MainTask(void) 
{
    
    /* 初始化 */
    GUI_Init();
    
    /*
     关于多缓冲和窗口内存设备的设置说明
       1. 使能多缓冲是调用的如下函数,用户要在LCDConf_Lin_Template.c文件中配置了多缓冲,调用此函数才有效:
          WM_MULTIBUF_Enable(1);
       2. 窗口使能使用内存设备是调用函数:WM_SetCreateFlags(WM_CF_MEMDEV);
       3. 如果emWin的配置多缓冲和窗口内存设备都支持,二选一即可,且务必优先选择使用多缓冲,实际使用
          STM32H7 + 32位SDRAM + RGB565/RGB888平台测试,多缓冲可以有效的降低窗口移动或者滑动时的撕裂
          感,并有效的提高流畅性,通过使能窗口使用内存设备是做不到的。
       4. 所有emWin例子默认是开启三缓冲。
    */
    WM_MULTIBUF_Enable(1);
    
    /*
       触摸校准函数默认是注释掉的,电阻屏需要校准,电容屏无需校准。如果用户需要校准电阻屏的话,执行
       此函数即可,会将触摸校准参数保存到EEPROM里面,以后系统上电会自动从EEPROM里面加载。
    */
#if 0
    LCD_SetBackLight(255);
    TOUCH_Calibration(2);
#endif

    /* 设置桌面窗口的背景色是白色,并且支持重绘 */
    WM_SetDesktopColor(GUI_BLUE); 
    
    /* 创建对话框 */
    GUI_CreateDialogBox(_aDialogCreate, GUI_COUNTOF(_aDialogCreate), &_cbCallback, 0, 0, 0);
    
    /* 屏幕显示后点亮,有效防止瞬间高亮 */
    GUI_Delay(200);
    LCD_SetBackLight(255);
        
    while(1) 
    {
        GUI_Delay(10);
    }
}

8.11 显示屏闪烁问题解决方法

如果大家调试状态下或者刚下载emWin的程序到STM32H7/STM32F429里面时,出现屏幕会闪烁,或者说抖动,这个是正常现象。详见此贴的说明:

http://www.armbbs.cn/forum.php?mod=viewthread&tid=16892

如果显示屏长时间处于抖动状态,说明LTDC的时钟配置高了或者低了(高的概率居多),可以将LTDC时钟降低一半或者提高一倍进行测试。配置方法看本教程第4章的4.4.3小节。

8.12 避免显示屏上电瞬间高亮和撕裂感

大家使用显示屏的时候,这两个问题很容易遇到,这里为大家提供个解决办法,提升用户体验。

这个问题并不是软件配置造成的,通过调节PWM背光也是无法解决的。解决办法是板子上后,先不要开启PWM,延迟200ms后再打开LCD的背光即可,注意时间不可以太短,太短没效果,大家可以根据实际情况做调节。

有时候界面设计比较复杂时,开机后不能保证所有的控件同时加载出来,界面会有种撕裂的感觉,这个时候有个比较好的解决思路,emWin初始化配置前关闭背光,初始化完毕并且首界面绘制完毕后再打开背光,用户体验就会好很多。

本章配套程序是放在MainTask.c文件的函数MainTask里面做了处理。

/*
*********************************************************************************************************
*    函 数 名: MainTask
*    功能说明: GUI主函数
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
void MainTask(void) 
{
    
    省略未写
    
界面初始化

    /* 屏幕显示后点亮,有效防止瞬间高亮和撕裂感 */
    GUI_Delay(200);
LCD_SetBackLight(255);
}

8.13 实验例程

(注,如果是电阻屏,需要做触摸校准,校准方法看本教程附件章节A)

本章节配套了如下几个例子供大家移植参考:

  •   V7-505_emWin6.x实验_RTOS方式移植模板

RTOS方式模板,用于大家移植emWin的参考Demo。

8.14 总结

本章节为大家讲解的内容涉及到的知识较多,信息量较大,部分知识点没有弄明白是没有关系的,但是一定要按照本章节提供的移植方法,动手移植一遍。另外,最好移植一个与教程配套开发板不一样的显示屏和触摸IC,这样对于本章的认识将更加全面。

微信公众号:armfly_com 安富莱论坛:www.armbbs.cn 安富莱淘宝:https://armfly.taobao.com
原文地址:https://www.cnblogs.com/armfly/p/14890599.html