1. 程式人生 > 實用技巧 >【STM32H7】第7章 ThreadX GUIX移植到STM32H7(MDK AC5)

【STM32H7】第7章 ThreadX GUIX移植到STM32H7(MDK AC5)

最新教程下載:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98429

第7章 ThreadX GUIX移植到STM32H7(MDK AC5)

本章節將為大家介紹ThreadX GUIX的MDK AC5方式移植和設計框架,理論上不建議初學者直接學習,因為本章節涉及到的知識點很多,建議對GUIX的應用有一些瞭解後再來看,這樣將事半功倍。但是本章的工程模板框架一定要學習。本章節提供的移植方法支援RGB565和ARGB8888兩種顏色格式的實現。同時可以自適應我們生產的4.3寸,5寸和7寸的電阻屏和電容屏。

雖然本章節是以我們開發板為例進行移植的,但是教會大家如何移植到自己的板子上以及移植過程中的注意事項是本章節的重點。

7.1初學者重要提示

7.2移植前的準備工作以及移植GUIX的流程

7.3第1步:ThreadX核心模板框架設計

7.4第2步:GUIX模板框架設計(重要)

7.5第3步:下載GUIX庫並新增到ThreadX核心工程模板

7.6第4步:SDRAM驅動的實現

7.7第5步:LTDC涉及到的引腳配置和時序配置

7.8第6步:電阻屏和電容屏觸控驅動的實現

7.9第7步:GUIX底層介面函式和配置

7.10第8步:SDRAM的MPU Cache配置

7.11第9步:新增GUI應用進行測試。

7.12顯示屏閃爍檔案解決方法

7.13避免顯示屏上電瞬間高亮和撕裂感

7.14實驗例程

7.15 總結

7.1 初學者重要提示

  1. 學習本章節前,務必保證已經學習了本教程的第4章,第5章和第6章,這三章是移植前的必備知識。
  2. 注意ThreadX MDK AC5工程提供的Port檔案問題,移植必讀:http://www.armbbs.cn/forum.php?mod=viewthread&tid=99306
  3. 為了方便大家移植,推薦直接新增我們的工程檔案到自己的工程或者直接使用我們的工程模板,按照本章的修改說明移植即可。
  4. 提供了ARGB8888和RGB565兩種顏色格式的移植工程,移植方法是一樣的,僅新增的介面檔案不同。
  5. 本章節是以移植到ThreadX上為例進行說明的,移植到其它小型RTOS方法,後面章節再為大家介紹。
  6. 由於開發板要自適應4.3寸,5寸和7寸顯示屏,而且還分電阻觸控和電容觸控,所以移植過程中新增的檔案稍多。雖然移植是以我們的開發板為例進行講解的,但是重點依然是告訴大家如何移植自己的板子以及移植過程中需要注意的事項。
  7. 對於本章節的移植,我們需要先從整體上把控。由於開發板已經把需要移植的檔案都整理好了,使用者僅需新增檔案就可以使用。我們這裡著重介紹如何移植到自己的板子上面,這個才是本章節的重點。
  • 顯示屏的移植

GUIX需要的底層介面函式已經全部整合在gx_display_driver_stm32h7_24xrgb.c檔案和gx_display_driver_stm32h7_565rgb.c裡面。對於這兩個檔案,使用者僅需學會使用裡面的兩個巨集配置以及LTDC涉及到的引腳和時序配置函式,這個是需要使用者自己去實現的,配置方法已經在本章節的7.7小節進行講解。

另外還有一個顯示屏背光調節函式LCD_SetBackLight的呼叫,其它都不用做任何修改。這三個地方都設定了,GUIX的顯示屏移植就完成了。

  • 觸控的移植

電容觸控的移植比較容易,因為電容觸控晶片可以自動觸控校準,所以僅需配置完觸控晶片後將觸控晶片返回的觸控座標(電容觸控晶片返回的就是實際的座標值),按下,鬆手和移動三種狀態傳送給GUIX即可。

電阻觸控的移植要稍麻煩些,由於電阻觸控板的線性度不是很好,如果不做觸控校準和濾波處理會有點選不準確和飛點問題。當前配套2點和4點觸控校準演算法,大家可以根據需要選擇,預設是用的2點觸控校準演算法。其中觸控濾波方法是檢測到觸控後先延遲30ms,消除抖動,然後採集10組座標值做升序排列,去掉最大的幾組座標和最小的幾組座標,對中間的幾組求平均作為最終的數值(電容觸控晶片返回的是ADC數值,不是實際座標值)。然後將最終的數值代入通過觸控校準建立的線性公式來獲得實際的座標值,此時就可以將觸控座標和觸控按下,鬆手和移動狀態傳送給GUIX。

7.2 移植前的準備工作以及移植GUIX的流程

移植前注意以下兩個問題:

  • 本章節的IDE開發環境務必是MDK5.30及其以上版本,映象下載地址:

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

  • 準備一個簡單的ThreadX工程,越簡單越好,我們就在這個簡單的工程上面移植即可:

配套模板名稱:V7-2004_ThreadX Kernel Template

GUIX的移植通過以下9步完成,下面各個小節詳細講解每一步:

  • 第1步:ThreadX核心模板框架設計
  • 第2步:GUIX模板框架設計(重要)
  • 第3步:下載GUIX庫並新增到ThreadX核心工程模板
  • 第4步:SDRAM驅動的實現
  • 第5步:LTDC涉及到的引腳配置和時序配置
  • 第6步:電阻屏和電容屏觸控驅動的實現
  • 第7步:GUIX底層介面函式和配置
  • 第8步:SDRAM的MPU Cache配置
  • 第9步:新增GUI應用進行測試。

7.3 第1步:瞭解ThreadX核心模板框架設計

移植GUIX前,我們優先了解下ThreadX核心模板程式的框圖。

7.3.1 準備一個ThreadX核心工程模板

首先準備好一個簡單的ThreadX工程模板,工程模板的製作在ThreadX核心教程裡面有詳細說明,這裡的重點是教大家移植GUIX,對應的例子名稱:V7-2004_ThreadX Kernel Template。準備好的工程模板如下圖所示(特別注意,我們這個模板已經新增裸機LCD操作所需的檔案)。

7.3.2 核心框架整體把控(重要)

為了幫助大家更好的理解ThreadX核心例子模板,專門製作了一個框圖,可以讓大家整體把控模板設計:

下面把幾個關鍵點逐一為大家做個說明。

7.3.3 各種標頭檔案彙總includes.h

這個檔案主要實現工程中各種標頭檔案的彙總,大家用到的都可以將其放到這個標頭檔案裡面。其它應用原始檔有呼叫到的,直接呼叫這個標頭檔案includes.h即可。

使用這個標頭檔案主要是方便各種標頭檔案的管理。

/*
*********************************************************************************************************
*                                         標準庫
*********************************************************************************************************
*/
#include  <stdarg.h>
#include  <stdio.h>
#include  <stdlib.h>
#include  <math.h>

/*
*********************************************************************************************************
*                                           OS
*********************************************************************************************************
*/
#include "tx_api.h"
#include "tx_timer.h"


/*
*********************************************************************************************************
*                                        APP / BSP
*********************************************************************************************************
*/

#include  <bsp.h>

/*
*********************************************************************************************************
*                                          變數和函式
*********************************************************************************************************
*/
/* 方便RTOS裡面使用 */
extern void SysTick_ISR(void);

#define bsp_ProPer1ms  SysTick_ISR

7.3.4 TheadX配置檔案tx_user.h

此檔案主要用於ThreadX核心的配置,核心相關的幾個巨集配置基本都已經整理到這個檔案裡面。

/*
*********************************************************************************************************
*                                           巨集定義
*********************************************************************************************************
*/
/*   
   最快速度優化需要開啟的選項 :

        TX_MAX_PRIORITIES                       32
        TX_DISABLE_PREEMPTION_THRESHOLD
        TX_DISABLE_REDUNDANT_CLEARING
        TX_DISABLE_NOTIFY_CALLBACKS
        TX_NOT_INTERRUPTABLE
        TX_TIMER_PROCESS_IN_ISR
        TX_REACTIVATE_INLINE
        TX_DISABLE_STACK_FILLING
        TX_INLINE_THREAD_RESUME_SUSPEND
   
   最小程式碼優化需要開啟的選項:
   
        TX_MAX_PRIORITIES                       32
        TX_DISABLE_PREEMPTION_THRESHOLD
        TX_DISABLE_REDUNDANT_CLEARING
        TX_DISABLE_NOTIFY_CALLBACKS
        TX_NOT_INTERRUPTABLE
        TX_TIMER_PROCESS_IN_ISR
 */


/* 覆蓋tx_port.h 裡面的巨集定義  */
/*
#define TX_MAX_PRIORITIES                       32
#define TX_MINIMUM_STACK                        ????
#define TX_THREAD_USER_EXTENSION                ????
#define TX_TIMER_THREAD_STACK_SIZE              ????
#define TX_TIMER_THREAD_PRIORITY                ????
*/

/* 
   確定定時器是否到期的處理,比如應用定時器,溢位時間和函式tx_thread_sleep呼叫等,是在系統定時器任務裡面還是在定時器中斷裡面呼叫。
   預設是在定時任務裡面,當定義了下面函式後,將直接在定時器中斷裡面處理,可以去掉定時器任務所消耗資源。 */
//#define TX_TIMER_PROCESS_IN_ISR


/* 用於設定定時器啟用是否採用內聯方式,預設此功能是關閉的。如果使能後,內聯方式的執行速度快,但增加程式碼量 */
//#define TX_REACTIVATE_INLINE


/* 用於設定是否關閉棧填充,預設情況下是使能的,所有任務的棧空間全部填充為0xEF,
*  帶有ThreadX除錯元件或者執行時棧檢測會用到。
*/
//#define TX_DISABLE_STACK_FILLING


/* 用於使能棧檢測,預設是關閉的。此選項使能後,而TX_DISABLE_STACK_FILLING沒使能時,棧填充將開啟,方便棧檢測 */
//#define TX_ENABLE_STACK_CHECKING


/* 用於設定是否關閉搶佔閥值,預設是開啟的。如果應用程式不需要此功能,關閉後可以降低程式碼需求,提升效能 */
//#define TX_DISABLE_PREEMPTION_THRESHOLD


/* 用於設定是否清零ThreadX全域性變數,如果編譯器啟動程式碼在ThreadX執行前清除了.bss段,那麼可以關閉不必要的清零 */
//#define TX_DISABLE_REDUNDANT_CLEARING


/* 確定是否不需要定時器組,禁止後需要使用者註釋掉tx_initialize_low_level檔案裡面tx_timer_interrupt的呼叫。
   另外,禁止後,必須使能TX_TIMER_PROCESS_IN_ISR */
/*
#define TX_NO_TIMER
#ifndef TX_TIMER_PROCESS_IN_ISR
#define TX_TIMER_PROCESS_IN_ISR
#endif
*/

/* 用於設定是否關閉通知回撥,預設是使能的。如果應用程式沒有用到訊息回撥,關閉掉後可以減小程式碼,並且可以提升效能。 */
//#define TX_DISABLE_NOTIFY_CALLBACKS


/* 使能tx_thread_resume和tx_thread_suspend使用內聯程式碼,優勢是提升這兩個函式的執行效能,劣勢是增加程式碼量 */
//#define TX_INLINE_THREAD_RESUME_SUSPEND


/* 設定TreadX核心不可中斷,好處是降低處理負擔,並且產生的程式碼小。但增加鎖時間 */
//#define TX_NOT_INTERRUPTABLE


/* 使能事件Trace,會稍微增加點程式碼 */
//#define TX_ENABLE_EVENT_TRACE


/* 使能BLOCK_POOL資訊獲取 */
//#define TX_BLOCK_POOL_ENABLE_PERFORMANCE_INFO


/* 使能BYTE_POOL資訊獲取 */
//#define TX_BYTE_POOL_ENABLE_PERFORMANCE_INFO


/* 使能事件標誌資訊獲取 */
//#define TX_EVENT_FLAGS_ENABLE_PERFORMANCE_INFO


/* 使能互斥訊號量資訊獲取  */
//#define TX_MUTEX_ENABLE_PERFORMANCE_INFO


/* 使能訊息物件資訊獲取 */
//#define TX_QUEUE_ENABLE_PERFORMANCE_INFO

/* 使能訊號量資訊獲取  */
//#define TX_SEMAPHORE_ENABLE_PERFORMANCE_INFO


/* 使能任務資訊獲取 */
//#define TX_THREAD_ENABLE_PERFORMANCE_INFO


/* 使能定時器資訊獲取 */
//#define TX_TIMER_ENABLE_PERFORMANCE_INFO

7.3.5 系統時鐘節拍配置tx_initialize_low_level.s

這個彙編檔案裡面有個重要引數需要大家配置,即晶片主頻和系統時鐘節拍。

SYSTEM_CLOCK       EQU     400000000
SYSTICK_CYCLES     EQU     ((SYSTEM_CLOCK / 1000) -1)

400000000是系統時鐘主頻,1000對應的就是系統時鐘節拍,這裡1000就表示1000Hz。

7.3.6 TheadX任務管理main.c

ThreadX所有任務基本都在main.c裡面建立,方便統一管理。如果有GUIX,FileX等元件的任務需要執行,實際執行函式會在其它原始檔裡面,並將這個函式extern到main.C檔案裡面,放到相應的任務裡面執行。

另外,任務優先順序,任務棧大小,任務控制塊等也都放到main.C檔案裡面,方便管理:

/*
*********************************************************************************************************
*                                 任務優先順序,數值越小優先順序越高
*********************************************************************************************************
*/
#define  APP_CFG_TASK_START_PRIO                          2u
#define  APP_CFG_TASK_MsgPro_PRIO                         3u
#define  APP_CFG_TASK_USER_IF_PRIO                        4u
#define  APP_CFG_TASK_COM_PRIO                            5u
#define  APP_CFG_TASK_STAT_PRIO                           30u
#define  APP_CFG_TASK_IDLE_PRIO                           31u


/*
*********************************************************************************************************
*                                    任務棧大小,單位位元組
*********************************************************************************************************
*/
#define  APP_CFG_TASK_START_STK_SIZE                    4096u
#define  APP_CFG_TASK_MsgPro_STK_SIZE                   4096u
#define  APP_CFG_TASK_COM_STK_SIZE                      4096u
#define  APP_CFG_TASK_USER_IF_STK_SIZE                  4096u
#define  APP_CFG_TASK_IDLE_STK_SIZE                      1024u
#define  APP_CFG_TASK_STAT_STK_SIZE                      1024u

/*
*********************************************************************************************************
*                                       靜態全域性變數
*********************************************************************************************************
*/                                                        
static  TX_THREAD   AppTaskStartTCB;
static  uint64_t    AppTaskStartStk[APP_CFG_TASK_START_STK_SIZE/8];

static  TX_THREAD   AppTaskMsgProTCB;
static  uint64_t    AppTaskMsgProStk[APP_CFG_TASK_MsgPro_STK_SIZE/8];

static  TX_THREAD   AppTaskCOMTCB;
static  uint64_t    AppTaskCOMStk[APP_CFG_TASK_COM_STK_SIZE/8];

static  TX_THREAD   AppTaskUserIFTCB;
static  uint64_t    AppTaskUserIFStk[APP_CFG_TASK_USER_IF_STK_SIZE/8];

static  TX_THREAD   AppTaskIdleTCB;
static  uint64_t    AppTaskIdleStk[APP_CFG_TASK_IDLE_STK_SIZE/8];

static  TX_THREAD   AppTaskStatTCB;
static  uint64_t    AppTaskStatStk[APP_CFG_TASK_STAT_STK_SIZE/8];

7.3.7 TheadX啟動任務

啟動任務裡面主要做了四個工作:

  • 優先執行一次任務統計OSStatInit。
  • 外設初始化bsp_Init。
  • 任務建立AppTaskCreate和通訊元件建立AppObjCreate。
  • 需要週期性處理的程式bsp_ProPer1ms,對應裸機工程呼叫的SysTick_ISR。這個的實現非常重要,這樣之前裸機裡面使用的API,就可以直接在ThreadX裡面直接呼叫。

程式碼如下:

/*
*********************************************************************************************************
*    函 數 名: AppTaskStart
*    功能說明: 啟動任務。
*    形    參: thread_input 是在建立該任務時傳遞的形參
*    返 回 值: 無
    優 先 級: 2
*********************************************************************************************************
*/
static  void  AppTaskStart (ULONG thread_input)
{
    (void)thread_input;

    /* 先掛起定時器組 */
#ifndef TX_NO_TIMER
    tx_thread_suspend(&_tx_timer_thread);
#endif
    
    /* 優先執行任務統計 */
    OSStatInit();

    /* 恢復定時器組 */
#ifndef TX_NO_TIMER
    tx_thread_resume(&_tx_timer_thread);
#endif    

    /* 核心開啟後,恢復HAL裡的時間基準 */
    HAL_ResumeTick();
    
    /* 外設初始化 */
    bsp_Init();
    
    /* 建立任務 */
    AppTaskCreate(); 

    /* 建立任務間通訊機制 */
    AppObjCreate();    

    while (1)
    {  
        /* 需要週期性處理的程式,對應裸機工程呼叫的SysTick_ISR */
        bsp_ProPer1ms();
        tx_thread_sleep(1);
    }
}

7.3.8 HAL庫時間基準stm32h7xx_hal_timbase_tim.c

ThreadX系統時鐘節拍預設是用的滴答定時器,STM32的HAL庫時間基準也是用的滴答定時器。對於這種情況,我們一般的情況下是使用其他的通用定時器替代,不過要額外的佔用一點系統性能。簡單的處理辦法是重新實現下面兩個函式即可,讓HAL庫和ThreadX都使用滴答定時器:

/*
*********************************************************************************************************
*    函 數 名: HAL_Delay
*    功能說明: 重定向毫秒延遲函式。替換HAL中的函式。因為HAL中的預設函式依賴於Systick中斷,如果在USB、SD
*              卡中斷中有延遲函式,則會鎖死。也可以通過函式HAL_NVIC_SetPriority提升Systick中斷
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void HAL_Delay(uint32_t Delay)
{
    bsp_DelayMS(Delay);
}

HAL_StatusTypeDef HAL_InitTick (uint32_t TickPriority)
{
        return HAL_OK;
}

uint32_t HAL_GetTick (void)
{
        static uint32_t ticks = 0U;
        uint32_t i;

        if (_tx_thread_system_state == TX_INITIALIZE_IS_FINISHED)
        {
                return ((uint32_t)_tx_time_get());
        }

        /* 如果ThreadX還沒有執行,採用下面方式 */
        for (i = (SystemCoreClock >> 14U); i > 0U; i--)
        {
                __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP();
                __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP();
        }
       
        return ++ticks;
}

7.3.9 ThreadX使能硬體浮點

這個是移植的坑王,大家移植後,可以測試下多工的FPU計算是否有異常。比如兩個任務執行相同的浮點運算和重新整理速度,看看兩個任務的輸出是否同步變化,這個測試非常重要:

那麼問題來了,正確的使能姿勢是什麼?務必保證C和彙編的預定義巨集裡面都使能。

C裡面對應的使能:

彙編裡面對應的使能:

7.4 第2步:瞭解GUIX模板框架設計

以往我們做教程都是先介紹如何移植,然後看最後的移植效果。這次我們反過來,先看移植完成的效果,然後移植。

7.4.1 GUIX工程模板

GUIX工程模板移植完成後,大體是下面這種效果:

7.4.2 GUIX框架整體把控(重要)

為了幫助大家更好的理解GUIX核心例子模板,專門製作了一個框圖,可以讓大家整體把控模板設計:

下面把幾個關鍵點逐一為大家做個說明。

7.4.3 GUIX配置檔案gx_user.h

此檔案主要用於GUIX的配置,GUIX相關的巨集定義配置非常多,當前先把這個檔案預留出來,隨著後面章節的進行,用到那些巨集定義了再新增。

7.4.4 外設驅動初始化檔案bsp.c

使用GUIX,主要涉及到SDRAM初始化,觸控初始化,LTDC初始化(放到了GUIX底層驅動介面檔案裡面了),背光開啟和EEPROM初始化(用於儲存電阻觸控式螢幕校準引數):

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    bsp_InitDWT();      /* 初始化DWT時鐘週期計數器 */       
    bsp_InitKey();         /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitUart();     /* 初始化串列埠 */
    bsp_InitExtIO();    /* 初始化FMC匯流排74HC574擴充套件IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();        /* 初始化LED */    
    bsp_InitTimer();      /* 初始化滴答定時器 */

    bsp_InitExtSDRAM(); /* 初始化SDRAM */

    bsp_InitI2C();     /* 初始化I2C匯流排,用於EEPROM */
    TOUCH_InitHard();

    /* 延遲200ms再點亮背光,避免瞬間高亮 */
    bsp_DelayMS(200); 
    LCD_SetBackLight(255);
}

7.4.5 外設驅動標頭檔案彙總bsp.h

此檔案主要用於添加了那些外設驅動檔案後,使能相應標頭檔案,特別工程GUIX工程裡面新增的一批LCD驅動和觸控驅動檔案,支援的標頭檔案如下:

/* 通過取消註釋或者添加註釋的方式控制是否包含底層驅動模組 */
//#include "bsp_msg.h"
//#include "bsp_user_lib.h"
#include "bsp_timer.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_dwt.h"

//#include "bsp_cpu_rtc.h"
//#include "bsp_cpu_adc.h"
//#include "bsp_cpu_dac.h"
#include "bsp_uart_fifo.h"
//#include "bsp_uart_gps.h"
//#include "bsp_uart_esp8266.h"
//#include "bsp_uart_sim800.h"

//#include "bsp_spi_bus.h"
//#include "bsp_spi_ad9833.h"
//#include "bsp_spi_ads1256.h"
//#include "bsp_spi_dac8501.h"
//#include "bsp_spi_dac8562.h"
//#include "bsp_spi_flash.h"
//#include "bsp_spi_tm7705.h"
//#include "bsp_spi_vs1053b.h"

#include "bsp_fmc_sdram.h"
//#include "bsp_fmc_nand_flash.h"
//#include "bsp_fmc_ad7606.h"
//#include "bsp_fmc_oled.h"
#include "bsp_fmc_io.h"

#include "bsp_i2c_gpio.h"
//#include "bsp_i2c_bh1750.h"
//#include "bsp_i2c_bmp085.h"
#include "bsp_i2c_eeprom_24xx.h"
//#include "bsp_i2c_hmc5883l.h"
//#include "bsp_i2c_mpu6050.h"
//#include "bsp_i2c_si4730.h"
//#include "bsp_i2c_wm8978.h"

#include "bsp_tft_h7.h"
#include "bsp_tft_lcd.h"
#include "bsp_ts_touch.h"
#include "bsp_ts_ft5x06.h"
#include "bsp_ts_gt811.h"
#include "bsp_ts_gt911.h"
#include "bsp_ts_stmpe811.h"

#include "bsp_beep.h"
#include "bsp_tim_pwm.h"
//#include "bsp_sdio_sd.h"
//#include "bsp_dht11.h"
//#include "bsp_ds18b20.h"
//#include "bsp_ps2.h"
//#include "bsp_ir_decode.h"
//#include "bsp_camera.h"
//#include "bsp_rs485_led.h"
//#include "bsp_can.h"

7.4.6 LCD裸機驅動檔案

STM32H7的LCD控制器LTDC控制有兩個相關的驅動檔案,即bsp_tft_h7.c和bsp_tft_lcd.c。移植GUIX時,這兩個檔案基本用不上了,已經把這兩個檔案實現的LTDC配置全部整合到了GUIX的底層介面驅動檔案裡面,方便大家移植。

7.4.7 電阻和電容觸控驅動檔案

電阻和電容觸控主要用的以下幾個檔案:

  • 總觸控檔案,用於識別各種觸控:bsp_ts_touch.c
  • 電容觸控檔案bsp_ts_ft5x06.c
  • 電容觸控檔案bsp_ts_gt811.c
  • 電容觸控檔案bsp_ts_gt911.c
  • 電阻觸控檔案bsp_ts_stmpe811

7.4.8 電阻觸控校準引數儲存到EEPROM

EEPROM的驅動主要涉及到兩個檔案:

  • bsp_i2c_gpio.c

配置EEPROM用到的兩個引腳。

  • bsp_i2c_eeprom_24xx.c

EEPROM的讀寫API。

而觸控校準引數的實現是在bsp_ts_touch.c檔案末尾封裝好的兩個函式裡面:

/*
*********************************************************************************************************
*    函 數 名: TOUCH_SaveParam
*    功能說明: 儲存校準引數    s_usAdcX1 s_usAdcX2 s_usAdcY1 s_usAdcX2
*    形    參:  無
*    返 回 值: 無
*********************************************************************************************************
*/
static void TOUCH_SaveParam(void)
{
    
    g_tTPParam.TouchDirection = g_LcdDirection;/* 2014-09-11 新增螢幕方向, 用於螢幕旋轉時無需再次校準 */

    #if 1
        /* 寫入EEPROM */
        ee_WriteBytes((uint8_t *)&g_tTPParam, TP_PARAM_EE_ADDR, sizeof(g_tTPParam));
    #else
        /* 寫入CPU Flash */
        bsp_WriteCpuFlash(TP_PARAM_FLASH_ADDR, (uint8_t *)&g_tTPParam, sizeof(g_tTPParam));
    #endif    
}

/*
*********************************************************************************************************
*    函 數 名: TOUCH_LoadParam
*    功能說明: 讀取校準引數
*    形    參:  無
*    返 回 值: 無
*********************************************************************************************************
*/
static void TOUCH_LoadParam(void)
{
    #if 1
        /* 讀取EEPROM中的引數 */
        ee_ReadBytes((uint8_t *)&g_tTPParam, TP_PARAM_EE_ADDR, sizeof(g_tTPParam));
    #else
        /* 讀取CPU Flash中的引數 */
        bsp_ReadCpuFlash(TP_PARAM_FLASH_ADDR, (uint8_t *)&g_tTPParam, sizeof(g_tTPParam
    #endif    
    
    if (g_tTPParam.TouchDirection > 4)
    {
        g_tTPParam.TouchDirection = 0;
        TOUCH_SaveParam();
    }
}

7.4.9 GUIX底層驅動介面檔案

當前製作了兩個底層驅動介面檔案:

  • gx_display_driver_stm32h7_565rgb.c 對應硬體RGB565介面。
  • gx_display_driver_stm32h7_24xrgb.c 對應硬體RGB888介面。

大家可以根據需要選擇相應驅動。

7.4.10 GUIX原始碼檔案

GUIX的原始碼檔案非常多,一個檔案一個API,有1200個左右,大家移植的時候最好都加上。

7.4.11 GUIX應用檔案

幾個應用檔案的作用如下:

隨著後面的章節的學習,逐漸就熟練了。

7.5 第3步:新增GUIX庫所有相關檔案到ThreadX核心工程模板

瞭解了ThreadX核心框架和GUIX框架後,介紹下如何將GUIX移植到ThreadX核心工程模板裡面。我們這裡一步到位,直接把所有相關的檔案都加上,然後再介紹如何修改,方便大家移植到自己的板子上。

7.5.1 第3.1步,下載GUIX原始碼包

按照第2章2.3.1小節講解的方法下載GUIX軟體包guix-6.0.1_rel(如果軟體包升級了,數字6.0.1略有不同),下面是GUIX軟體包內容:

主要用到兩個資料夾:

common資料夾裡面是原始碼檔案。

ports資料夾裡面是移植檔案。

7.5.2 第3.2步,建立GUIX資料夾到工程模板

在工程模板建立GUIX資料夾

GUIX的M7核心port資料夾裡面只有一個GNU(對應路徑ports\cortex_m7),為了方便我們管理,再建立IAR,AC5和AC6三個資料夾,資料夾裡面的內容和GNU裡面的一樣,並且再新增一個src資料夾,整體效果就是下面這樣:

新建的src資料夾是用來存放GUIX底層驅動介面檔案用。

7.5.3 第3.3步,新增底層驅動介面檔案

新增驅動介面檔案到第3.2步建立的ac5->src和ac5-inc資料夾裡面(h檔案新增到inc資料夾,c檔案放到src資料夾)

為了移植方便,大家直接複製本週教程配套例子在此資料夾下的檔案即可。

7.5.4 第3.4步,新增Port檔案和原始碼檔案到工程

將原始碼檔案和ports檔案新增到MDK的工程專案中,新增後的效果如下:

推薦使用下面的方法新增,否則MDK會非常卡:

7.5.5 第3.5步,新增配置檔案gx_user.h

在User資料夾下新增檔案gx_user.h,直接從本章節教程配套例子的User資料夾複製即可。此檔案主要用於GUIX配置。

為了方便管理,我們這裡將路徑GUIX\ports\cortex_m7\ac5\inc裡面的gx_port.h檔案也新增進來了。

7.5.6 第3.5步,新增GUIX應用檔案

在User資料夾新增資料夾GUIX,直接從本章節教程配套例子的User資料夾複製即可,此資料夾主要是GUIX的應用部分,內容如下:

7.5.7 第3.6步,新增BSP驅動檔案

需要新增的BSP驅動檔案如下:

除了SDRAM,LTDC,EEPROM和觸控兩個的檔案以外,還要新增bsp_tim_pwm.c,這個是用於設定PWM背光用的。

另外注意一點,bsp_tft_lcd.c檔案還關聯了一些字型檔檔案,大家最好也將其新增到工程裡面。這些字型檔檔案位於本章配套例子的User資料夾下,大家直接複製到自己工程的工程裡面新增即可,新增後效果如下:

7.5.8 第3.7步,新增HAL庫檔案

相關BSP驅動關聯到的HAL庫檔案都添加了進來,簡單省事些,大家也可以把HAL庫所有檔案都新增進來:

7.5.9 第3.8步,新增預定義巨集

C/C++檔案中新增的預定義巨集如下:

  • USE_HAL_DRIVER
  • STM32H743xx
  • TX_ENABLE_FPU_SUPPORT 用於支援硬體FPU
  • TX_ENABLE_STACK_CHECKING 用於棧檢測
  • TX_INCLUDE_USER_DEFINE_FILE 用於包含tx_user.h
  • GX_INCLUDE_USER_DEFINE_FILE 用於包含gx_user.h

ASM彙編檔案裡面新增的巨集定義:

  • TX_ENABLE_FPU_SUPPORT

7.5.10 第3.9步,新增標頭檔案路徑

需要新增的路徑如下:

至此,我們需要的GUIX檔案都已經新增完畢。下面為大家介紹如何修改用於自己的板子。

7.6 第4步:SDRAM驅動實現(用於視訊記憶體,動態記憶體和畫布)

一定要保證SDRAM大批量讀寫資料時是正常的,SDRAM的測試可以自己專門做一個工程測試下。對於SDRAM的驅動實現,可以學習我們寫的BSP驅動教程第49章:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

不管你使用的是鎂光的,海力士的,三星的,ISSI的或者華邦的,基本實現方法都是一樣的。教程配套的板子使用的是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個位元組來表示。其它的顏色格式,依此類推。

  • 用作GUIX動態記憶體和canvas幕布

GUIX要做的炫酷,是比較消耗動態記憶體的,所以使用者可以將SDRAM除了用於視訊記憶體以外的所有記憶體全部用作GUIX動態記憶體和canvas幕布。

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

如果SDRAM的驅動測試已經沒有問題了,就可以將其新增到工程裡面了,開發板使用的SDRAM驅動檔案是bsp_fmc_sdram.c。

新增到工程裡面後要分配SDRAM的使用,教程配套開發板使用的是32MB,32位頻寬的SDRAM。

7.7 第5步:LTDC涉及到的引腳配置和時序配置

7.7.1 LTDC時序配置

使用者僅需配置LTDC涉及到的引腳和時序即可,配置函式封裝到下面兩個介面檔案的末尾。

  • gx_display_driver_stm32h7_565rgb.c 對應硬體RGB565介面。
  • gx_display_driver_stm32h7_24xrgb.c 對應硬體RGB888介面。

另外,由於開發板配套了4.3寸,5寸和7寸屏顯示屏,所以要對這幾種尺寸的顯示屏做自適應,每個屏的時序配置都是不一樣的,具體實現在gx_display_driver_stm32h7_565rgb.c和gx_display_driver_stm32h7_24xrgb.c介面檔案末尾的,即函式LCD_LL_Init。大家在給自己的顯示屏移植時主要修改這個函式即可,引腳配置需要在這個函式裡面實現。下面我們再結合函式LCD_LL_Init的實現,講解下配置時要注意的一些問題,具體程式碼如下:

1.    /*
2.    ******************************************************************************************************
3.    *    函 數 名: LCD_LL_Init
4.    *    功能說明: 配置LTDC
5.    *    形    參: 無
6.    *    返 回 值: 無
7.    *    筆    記:
8.    *       LCD_TFT 同步時序配置(整理自官方做的一個截圖,言簡意賅):
9.    *   ----------------------------------------------------------------------------
10.    *    
11.    *                                                 Total Width
12.    *                             <--------------------------------------------------->
13.    *                       Hsync width HBP             Active Width                HFP
14.    *                             <---><--><--------------------------------------><-->
15.    *                         ____    ____|_______________________________________|____ 
16.    *                             |___|   |                                       |    |
17.    *                                     |                                       |    |
18.    *                         __|         |                                       |    |
19.    *            /|\    /|\  |            |                                       |    |
20.    *             | VSYNC|   |            |                                       |    |
21.    *             |Width\|/  |__          |                                       |    |
22.    *             |     /|\     |         |                                       |    |
23.    *             |  VBP |      |         |                                       |    |
24.    *             |     \|/_____|_________|_______________________________________|    |
25.    *             |     /|\     |         | / / / / / / / / / / / / / / / / / / / |    |
26.    *             |      |      |         |/ / / / / / / / / / / / / / / / / / / /|    |
27.    *    Total    |      |      |         |/ / / / / / / / / / / / / / / / / / / /|    |
28.    *    Heigh    |      |      |         |/ / / / / / / / / / / / / / / / / / / /|    |
29.    *             |Active|      |         |/ / / / / / / / / / / / / / / / / / / /|    |
30.    *             |Heigh |      |         |/ / / / / / Active Display Area / / / /|    |
31.    *             |      |      |         |/ / / / / / / / / / / / / / / / / / / /|    |
32.    *             |      |      |         |/ / / / / / / / / / / / / / / / / / / /|    |
33.    *             |      |      |         |/ / / / / / / / / / / / / / / / / / / /|    |
34.    *             |      |      |         |/ / / / / / / / / / / / / / / / / / / /|    |
35.    *             |      |      |         |/ / / / / / / / / / / / / / / / / / / /|    |
36.    *             |     \|/_____|_________|_______________________________________|    |
37.    *             |     /|\     |                                                      |
38.    *             |  VFP |      |                                                      |
39.    *            \|/    \|/_____|______________________________________________________|
40.    *            
41.    *     
42.    *     每個LCD裝置都有自己的同步時序值:
43.    *     Horizontal Synchronization (Hsync) 
44.    *     Horizontal Back Porch (HBP)       
45.    *     Active Width                      
46.    *     Horizontal Front Porch (HFP)     
47.    *   
48.    *     Vertical Synchronization (Vsync)  
49.    *     Vertical Back Porch (VBP)         
50.    *     Active Heigh                       
51.    *     Vertical Front Porch (VFP)         
52.    *     
53.    *     LCD_TFT 視窗水平和垂直的起始以及結束位置 :
54.    *     ----------------------------------------------------------------
55.    *   
56.    *     HorizontalStart = (Offset_X + Hsync + HBP);
57.    *     HorizontalStop  = (Offset_X + Hsync + HBP + Window_Width - 1); 
58.    *     VarticalStart   = (Offset_Y + Vsync + VBP);
59.    *     VerticalStop    = (Offset_Y + Vsync + VBP + Window_Heigh - 1);
60.    *
61.    ******************************************************************************************************
62.    */
63.    static void LCD_LL_Init(void) 
64.    {
65.        /* 配置LCD相關的GPIO */
66.        {
67.            /* GPIOs Configuration */
68.            /*
69.            +------------------------+-----------------------+----------------------------+
70.            +                       LCD pins assignment                                   +
71.            +------------------------+-----------------------+----------------------------+
72.            |  LCDH7_TFT R0 <-> PI.15  |  LCDH7_TFT G0 <-> PJ.07 |  LCDH7_TFT B0 <-> PJ.12      |
73.            |  LCDH7_TFT R1 <-> PJ.00  |  LCDH7_TFT G1 <-> PJ.08 |  LCDH7_TFT B1 <-> PJ.13      |
74.            |  LCDH7_TFT R2 <-> PJ.01  |  LCDH7_TFT G2 <-> PJ.09 |  LCDH7_TFT B2 <-> PJ.14      |
75.            |  LCDH7_TFT R3 <-> PJ.02  |  LCDH7_TFT G3 <-> PJ.10 |  LCDH7_TFT B3 <-> PJ.15      |
76.            |  LCDH7_TFT R4 <-> PJ.03  |  LCDH7_TFT G4 <-> PJ.11 |  LCDH7_TFT B4 <-> PK.03      |
77.            |  LCDH7_TFT R5 <-> PJ.04  |  LCDH7_TFT G5 <-> PK.00 |  LCDH7_TFT B5 <-> PK.04      |
78.            |  LCDH7_TFT R6 <-> PJ.05  |  LCDH7_TFT G6 <-> PK.01 |  LCDH7_TFT B6 <-> PK.05      |
79.            |  LCDH7_TFT R7 <-> PJ.06  |  LCDH7_TFT G7 <-> PK.02 |  LCDH7_TFT B7 <-> PK.06      |
80.            -------------------------------------------------------------------------------
81.            |  LCDH7_TFT HSYNC <-> PI.12  | LCDTFT VSYNC <->  PI.13 |
82.            |  LCDH7_TFT CLK   <-> PI.14  | LCDH7_TFT DE   <->  PK.07 |
83.            -----------------------------------------------------
84.            */        
85.            GPIO_InitTypeDef GPIO_Init_Structure;
86.    
87.            /*##-1- Enable peripherals and GPIO Clocks #################################*/  
88.            /* 使能LTDC和DMA2D時鐘 */
89.            __HAL_RCC_LTDC_CLK_ENABLE();
90.            __HAL_RCC_DMA2D_CLK_ENABLE();  
91.            
92.            /* 使能GPIO時鐘 */
93.            __HAL_RCC_GPIOI_CLK_ENABLE();
94.            __HAL_RCC_GPIOJ_CLK_ENABLE();
95.            __HAL_RCC_GPIOK_CLK_ENABLE();
96.    
97.            /* GPIOI 配置 */
98.            GPIO_Init_Structure.Pin       = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; 
99.            GPIO_Init_Structure.Mode      = GPIO_MODE_AF_PP;
100.            GPIO_Init_Structure.Pull      = GPIO_NOPULL;
101.            GPIO_Init_Structure.Speed     = GPIO_SPEED_FREQ_HIGH;
102.            GPIO_Init_Structure.Alternate = GPIO_AF14_LTDC;  
103.            HAL_GPIO_Init(GPIOI, &GPIO_Init_Structure);
104.    
105.            /* GPIOJ 配置 */  
106.            GPIO_Init_Structure.Pin       = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | \
107.                                          GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | \
108.                                          GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | \
109.                                          GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; 
110.            GPIO_Init_Structure.Mode      = GPIO_MODE_AF_PP;
111.            GPIO_Init_Structure.Pull      = GPIO_NOPULL;
112.            GPIO_Init_Structure.Speed     = GPIO_SPEED_FREQ_HIGH;
113.            GPIO_Init_Structure.Alternate = GPIO_AF14_LTDC;  
114.            HAL_GPIO_Init(GPIOJ, &GPIO_Init_Structure);  
115.    
116.            /* GPIOK 配置 */  
117.            GPIO_Init_Structure.Pin       = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | \
118.                                            GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; 
119.            GPIO_Init_Structure.Mode      = GPIO_MODE_AF_PP;
120.            GPIO_Init_Structure.Pull      = GPIO_NOPULL;
121.            GPIO_Init_Structure.Speed     = GPIO_SPEED_FREQ_HIGH;
122.            GPIO_Init_Structure.Alternate = GPIO_AF14_LTDC;  
123.            HAL_GPIO_Init(GPIOK, &GPIO_Init_Structure);      
124.        }
125.        
126.        /*##-2- LTDC初始化 #############################################################*/  
127.        {    
128.            uint16_t Width, Height, HSYNC_W, HBP, HFP, VSYNC_W, VBP, VFP;
129.            RCC_PeriphCLKInitTypeDef  PeriphClkInitStruct = {0};
130.            LTDC_LayerCfgTypeDef     pLayerCfg = {0};
131.    
132.            /* 支援6種面板 */
133.            switch (g_LcdType)
134.            {
135.                case LCD_35_480X320:    /* 3.5寸 480 * 320 */    
136.                    Width = 480;
137.                    Height = 272;
138.                    HSYNC_W = 10;
139.                    HBP = 20;
140.                    HFP = 20;
141.                    VSYNC_W = 20;
142.                    VBP = 20;
143.                    VFP = 20;
144.                    break;
145.                
146.                case LCD_43_480X272:        /* 4.3寸 480 * 272 */            
147.                    Width = 480;
148.                    Height = 272;
149.    
150.                    HSYNC_W = 40;
151.                    HBP = 2;
152.                    HFP = 2;
153.                    VSYNC_W = 9;
154.                    VBP = 2;
155.                    VFP = 2;
156.            
157.                    /* LCD 時鐘配置 */
158.                    /* PLL3_VCO Input = HSE_VALUE/PLL3M = 25MHz/5 = 5MHz */
159.                    /* PLL3_VCO Output = PLL3_VCO Input * PLL3N = 5MHz * 48 = 240MHz */
160.                    /* PLLLCDCLK = PLL3_VCO Output/PLL3R = 240 / 10 = 24MHz */
161.                    /* LTDC clock frequency = PLLLCDCLK = 24MHz */
162.                    /*
163.                        重新整理率 = 24MHz /((Width + HSYNC_W  + HBP  + HFP)*(Height + VSYNC_W +  VBP  + VFP))
164.                                  = 24000000/((480 + 40  + 2  + 2)*(272 + 9 +  2  + 2)) 
165.                               = 24000000/(524*285)
166.                               = 160Hz    
167.    
168.                        當前這個配置方便使用者使用PLL3Q輸出的48MHz時鐘供USB使用。
169.                    */
170.                    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
171.                    PeriphClkInitStruct.PLL3.PLL3M = 5;
172.                    PeriphClkInitStruct.PLL3.PLL3N = 48;
173.                    PeriphClkInitStruct.PLL3.PLL3P = 2;
174.                    PeriphClkInitStruct.PLL3.PLL3Q = 5;
175.                    PeriphClkInitStruct.PLL3.PLL3R = 10;                
176.                    HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);                 
177.                    break;
178.                
179.                case LCD_50_480X272:        /* 5.0寸 480 * 272 */
180.                    Width = 480;
181.                    Height = 272;
182.                
183.                    HSYNC_W = 40;
184.                    HBP = 2;
185.                    HFP = 2;
186.                    VSYNC_W = 9;
187.                    VBP = 2;
188.                    VFP = 2;            
189.                    break;
190.                
191.                case LCD_50_800X480:        /* 5.0寸 800 * 480 */
192.                case LCD_70_800X480:        /* 7.0寸 800 * 480 */                    
193.                    Width = 800;
194.                    Height = 480;
195.    
196.                    HSYNC_W = 96;    /* =10時,顯示錯位,20時部分屏可以的,80時全部OK */
197.                    HBP = 10;
198.                    HFP = 10;
199.                    VSYNC_W = 2;
200.                    VBP = 10;
201.                    VFP = 10;            
202.    
203.                    /* LCD 時鐘配置 */
204.                    /* PLL3_VCO Input = HSE_VALUE/PLL3M = 25MHz/5 = 5MHz */
205.                    /* PLL3_VCO Output = PLL3_VCO Input * PLL3N = 5MHz * 48 = 240MHz */
206.                    /* PLLLCDCLK = PLL3_VCO Output/PLL3R = 240 / 10 = 24MHz */
207.                    /* LTDC clock frequency = PLLLCDCLK = 24MHz */
208.                    /*
209.                        重新整理率 = 24MHz /((Width + HSYNC_W  + HBP  + HFP)*(Height + VSYNC_W +  VBP  + VFP))
210.                                  = 24000000/((800 + 96  + 10  + 10)*(480 + 2 +  10  + 10)) 
211.                               = 24000000/(916*502)
212.                               = 52Hz    
213.                
214.                根據需要可以加大,100Hz重新整理率完全沒問題,設定PeriphClkInitStruct.PLL3.PLL3N = 100即可
215.                此時的LTDC時鐘是50MHz
216.                重新整理率 = 50MHz /((Width + HSYNC_W  + HBP  + HFP )*(Height + VSYNC_W +  VBP  +VFP  )) 
217.                               = 5000000/(916*502) 
218.                               = 108.7Hz
219.    
220.                        當前這個配置方便使用者使用PLL3Q輸出的48MHz時鐘供USB使用。
221.                    */ 
222.                    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
223.                    PeriphClkInitStruct.PLL3.PLL3M = 5;
224.                    PeriphClkInitStruct.PLL3.PLL3N = 48;
225.                    PeriphClkInitStruct.PLL3.PLL3P = 2;
226.                    PeriphClkInitStruct.PLL3.PLL3Q = 5;
227.                    PeriphClkInitStruct.PLL3.PLL3R = 10; 
228.                    HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);                 
229.                    break;
230.                
231.                case LCD_70_1024X600:        /* 7.0寸 1024 * 600 */
232.                    /* 實測畫素時鐘 = 53.7M */
233.                    Width = 1024;
234.                    Height = 600;
235.    
236.                    HSYNC_W = 2;    /* =10時,顯示錯位,20時部分屏可以的,80時全部OK */
237.                    HBP = 157;
238.                    HFP = 160;
239.                    VSYNC_W = 2;
240.                    VBP = 20;
241.                    VFP = 12;        
242.                
243.                    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
244.                    PeriphClkInitStruct.PLL3.PLL3M = 5;
245.                    PeriphClkInitStruct.PLL3.PLL3N = 48;
246.                    PeriphClkInitStruct.PLL3.PLL3P = 2;
247.                    PeriphClkInitStruct.PLL3.PLL3Q = 5;
248.                    PeriphClkInitStruct.PLL3.PLL3R = 10;
249.                    HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);             
250.                    break;
251.                
252.                default:
253.                    Width = 800;
254.                    Height = 480;
255.    
256.                    HSYNC_W = 80;    /* =10時,顯示錯位,20時部分屏可以的,80時全部OK */
257.                    HBP = 10;
258.                    HFP = 10;
259.                    VSYNC_W = 10;
260.                    VBP = 10;
261.                    VFP = 10;        
262.                
263.                    /* LCD 時鐘配置 */
264.                    /* PLL3_VCO Input = HSE_VALUE/PLL3M = 25MHz/5 = 5MHz */
265.                    /* PLL3_VCO Output = PLL3_VCO Input * PLL3N = 5MHz * 48 = 240MHz */
266.                    /* PLLLCDCLK = PLL3_VCO Output/PLL3R = 240 / 10 = 24MHz */
267.                    /* LTDC clock frequency = PLLLCDCLK = 24MHz */
268.                    /*
269.                        重新整理率 = 24MHz /((Width + HSYNC_W  + HBP  + HFP)*(Height + VSYNC_W +  VBP  + VFP))
270.                                  = 24000000/((800 + 96  + 10  + 10)*(480 + 2 +  10  + 10)) 
271.                               = 24000000/(916*502)
272.                               = 52Hz
273.    
274.                根據需要可以加大,100Hz重新整理率完全沒問題,設定PeriphClkInitStruct.PLL3.PLL3N = 100即可
275.                此時的LTDC時鐘是50MHz
276.                重新整理率 = 50MHz /((Width + HSYNC_W  + HBP  + HFP )*(Height + VSYNC_W +  VBP  +VFP  )) 
277.                               = 5000000/(916*502) 
278.                               = 108.7Hz
279.    
280.                        當前這個配置方便使用者使用PLL3Q輸出的48MHz時鐘供USB使用。
281.                    */ 
282.                    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
283.                    PeriphClkInitStruct.PLL3.PLL3M = 5;
284.                    PeriphClkInitStruct.PLL3.PLL3N = 48;
285.                    PeriphClkInitStruct.PLL3.PLL3P = 2;
286.                    PeriphClkInitStruct.PLL3.PLL3Q = 5;
287.                    PeriphClkInitStruct.PLL3.PLL3R = 10;  
288.                    HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);             
289.                    break;
290.            }        
291.    
292.            g_LcdHeight = Height;
293.            g_LcdWidth = Width;
294.            
295.            /* 配置訊號極性 */    
296.            hLTDC.Init.HSPolarity = LTDC_HSPOLARITY_AL;    /* HSYNC 低電平有效 */
297.            hLTDC.Init.VSPolarity = LTDC_VSPOLARITY_AL;     /* VSYNC 低電平有效 */
298.            hLTDC.Init.DEPolarity = LTDC_DEPOLARITY_AL;     /* DE 低電平有效 */
299.            hLTDC.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
300.    
301.            /* 時序配置 */    
302.            hLTDC.Init.HorizontalSync = (HSYNC_W - 1);
303.            hLTDC.Init.VerticalSync = (VSYNC_W - 1);
304.            hLTDC.Init.AccumulatedHBP = (HSYNC_W + HBP - 1);
305.            hLTDC.Init.AccumulatedVBP = (VSYNC_W + VBP - 1);  
306.            hLTDC.Init.AccumulatedActiveH = (Height + VSYNC_W + VBP - 1);
307.            hLTDC.Init.AccumulatedActiveW = (Width + HSYNC_W + HBP - 1);
308.            hLTDC.Init.TotalHeigh = (Height + VSYNC_W + VBP + VFP - 1);
309.            hLTDC.Init.TotalWidth = (Width + HSYNC_W + HBP + HFP - 1); 
310.    
311.            /* 配置背景層顏色 */
312.            hLTDC.Init.Backcolor.Blue = 0;
313.            hLTDC.Init.Backcolor.Green = 0;
314.            hLTDC.Init.Backcolor.Red = 0xff;
315.    
316.            hLTDC.Instance = LTDC;
317.            
318.    
319.            /* 開始配置圖層 ------------------------------------------------------*/
320.            /* 視窗顯示區設定 */ 
321.            pLayerCfg.WindowX0 = 0;
322.            pLayerCfg.WindowX1 = Width;
323.            pLayerCfg.WindowY0 = 0;
324.            pLayerCfg.WindowY1 = Height;
325.    
326.            /* 配置顏色格式 */ 
327.            pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
328.    
329.            /* 視訊記憶體地址 */
330.            pLayerCfg.FBStartAdress = FrameBufer;    
331.    
332.            /* Alpha常數 (255 表示完全不透明) */
333.            pLayerCfg.Alpha = 255;
334.    
335.            /* 無背景色 */
336.            pLayerCfg.Alpha0 = 0;     /* 完全透明 */
337.            pLayerCfg.Backcolor.Blue = 0;
338.            pLayerCfg.Backcolor.Green = 0;
339.            pLayerCfg.Backcolor.Red = 0;
340.    
341.            /* 配置圖層混合因數 */
342.            pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA;
343.            pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA;
344.    
345.            /* 配置行列大小 */
346.            pLayerCfg.ImageWidth  = Width;
347.            pLayerCfg.ImageHeight = Height;
348.    
349.            /* 配置LTDC  */  
350.            if (HAL_LTDC_Init(&hLTDC) != HAL_OK)
351.            {
352.                /* 初始化錯誤 */
353.                Error_Handler(__FILE__, __LINE__);
354.            }
355.    
356.            /* 配置圖層1 */
357.            if (HAL_LTDC_ConfigLayer(&hLTDC, &pLayerCfg, LTDC_LAYER_1) != HAL_OK)
358.            {
359.                /* 初始化錯誤 */
360.                Error_Handler(__FILE__, __LINE__);
361.            }  
362.        }  
363.    }
  • 第85到124行,主要是GPIO配置,注意DMA2D時鐘和LTDC時鐘別忘使能。
  • 第126到309行,這部分程式的實現在本教程第4章的4.4.4小節裡面有詳細說明。
    • 第133行,六種面板的識別是在bsp_touch.c檔案中實現的。大家自己配置時用不到這個,僅需提供一組時序引數和輸出時鐘即可,除非專案中需要切換不同顯示屏。
    • 第292到293行,全域性變數g_LcdWidth和g_LcdHeight在檔案bsp_tft_lcd.c檔案定義。如果大家自己移植時用不到檔案bsp_tft_lcd.c的話,需要自行定義這兩個全域性變數(另外,此檔案裡面的背光設定函式也要自行實現),因為這兩個變數要被檔案gx_display_driver_stm32h7_565rgb.c和gx_display_driver_stm32h7_24xrgb.c所呼叫
  • 第312到314行,STM32H7的圖層是由背景層,圖層1和圖層2組成,這裡配置的是背景層的顏色值,分別配置了R,G,B三原色的數值,範圍都是0-255。
  • 第321到347行,主要是配置圖層。
    • 第327行,如果使用RGG565顏色格式要配置為LTDC_PIXEL_FORMAT_RGB565。

如果使用ARGB8888顏色格式要配置為LTDC_PIXEL_FORMAT_ARGB8888。

7.7.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 = 0xFF;

如果背景層可以正常顯示紅色,說明引腳和時序配置都是沒有問題的。如果不成功要從以下幾個方面著手檢查:

  • 首先要清楚一點,當前的配置是否成功與SDRAM沒有任何關係,因為背景層還用不到SDRAM,圖層1和圖層2才需要SDRAM做視訊記憶體使用。
  • 從硬體著手檢查,保證STM32H7晶片焊接沒問題,TFT介面一定要牢固,防止接觸不良,特別是使用FPC軟排線的時候,測試階段,軟排線越短越好。有時候也可能是顯示屏有問題,最好可以備兩個顯示屏測試。
  • 從軟體配置著手檢查,檢視LTDC涉及到的所有引腳是否配置,引腳時鐘是否使能。有時候無法顯示也有可能是板子硬體設計不規範導致干擾較大造成的,此時,可以降低LTDC所涉及到GPIO的速度等級。

如果顯示了,但是顯示的位置不正確,可以重新調整時序引數即可。

7.8 第6步:電阻屏和電容屏觸控驅動的實現

本小節的實現基於本教程的第5章,當前驅動對電阻觸控晶片STMPE811和電容觸控晶片FT5X06、GT911和GT811的顯示屏都進行了支援。

實現比較簡單,因為GUIX的觸控分按下,鬆手和移動三個事件,正好這幾款觸控晶片的驅動也是分這三個事件,所以僅需修改下函式TOUCH_PutKey,所有顯示屏觸控就都可以完美融合了。

7.8.1 新增GUIX的按下,鬆手和移動三個事件

檔案bsp_ts_touch.c裡的函式TOUCH_PutKey修改如下:

/*
*********************************************************************************************************
*    函 數 名: TOUCH_PutKey
*    功能說明: 將1個觸控點座標值壓入觸控FIFO緩衝區。電阻觸控式螢幕形參是ADC值,電容觸控式螢幕形參是座標值
*    形    參: _usX, _usY 座標值
*    返 回 值: 無
*********************************************************************************************************
*/
#include   "gx_api.h"
void TOUCH_PutKey(uint8_t _ucEvent, uint16_t _usX, uint16_t _usY)
{
#if 1
    uint16_t xx, yy;
    GX_EVENT event;

    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:
            event.gx_event_type = GX_EVENT_PEN_DOWN;
            event.gx_event_payload.gx_event_pointdata.gx_point_x = xx;
            event.gx_event_payload.gx_event_pointdata.gx_point_y = yy;
            event.gx_event_sender = 0;
            event.gx_event_target = 0;
            event.gx_event_display_handle = 0xC0000000;
            gx_system_event_send(&event);
            break;

        case TOUCH_MOVE:
            event.gx_event_type = GX_EVENT_PEN_DRAG;
            event.gx_event_payload.gx_event_pointdata.gx_point_x = xx;
            event.gx_event_payload.gx_event_pointdata.gx_point_y = yy;
            event.gx_event_sender = 0;
            event.gx_event_target = 0;
            event.gx_event_display_handle = 0xC0000000;
            gx_system_event_fold(&event);
            break;

        case TOUCH_RELEASE:
            event.gx_event_type = GX_EVENT_PEN_UP;
            event.gx_event_payload.gx_event_pointdata.gx_point_x = xx;
            event.gx_event_payload.gx_event_pointdata.gx_point_y = yy;
            event.gx_event_sender = 0;
            event.gx_event_target = 0;
            event.gx_event_display_handle = 0xC0000000;
            gx_system_event_send(&event);
            break;

        default:
            break;
    }
#else
    省略未寫
#endif
}

特別注意0xC0000000,這個不是隨便寫的一個值,要與本章7.8.1小節設定STM32_SCREEN_HANDLE數值一致。

7.8.2 週期性呼叫觸控掃描函式

電阻觸控和電容觸控的掃描函式是TOUCH_Scan和TOUCH_CapScan,為了實現使用了ThreadX和裸機時的一樣的呼叫方式,專門在啟動任務裡面週期性的呼叫函式bsp_ProPer1ms(SysTick_ISR),而SysTick_ISR裡面呼叫了bsp_RunPer1ms,然後bsp_RunPer1ms裡面呼叫掃描函式,即如下的呼叫關係:

程式碼如下:

/*
*********************************************************************************************************
*    函 數 名: AppTaskStart
*    功能說明: 啟動任務。
*    形    參: thread_input 是在建立該任務時傳遞的形參
*    返 回 值: 無
    優 先 級: 2
*********************************************************************************************************
*/
static  void  AppTaskStart (ULONG thread_input)
{
    (void)thread_input;

    /* 先掛起定時器組 */
#ifndef TX_NO_TIMER
    tx_thread_suspend(&_tx_timer_thread);
#endif
    
    /* 優先執行任務統計 */
    OSStatInit();

    /* 恢復定時器組 */
#ifndef TX_NO_TIMER
    tx_thread_resume(&_tx_timer_thread);
#endif    

    /* 核心開啟後,恢復HAL裡的時間基準 */
    HAL_ResumeTick();
    
    /* 外設初始化 */
    bsp_Init();
    
    /* 建立任務 */
    AppTaskCreate(); 

    /* 建立任務間通訊機制 */
    AppObjCreate();    

    while (1)
    {  
        /* 需要週期性處理的程式,對應裸機工程呼叫的SysTick_ISR */
        bsp_ProPer1ms();
        tx_thread_sleep(1);
    }
}

7.8.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實現。
  • 如果大家不想用開發板實現的方案,想自己重新實現一個,也是沒問題的,注意跟GUIX關聯的方式。

7.9 第7步:GUIX底層介面函式和配置

GUI的底層介面函式和配置的實現在我們第1步中新增的底層驅動介面檔案中:

  • gx_display_driver_stm32h7_565rgb.c 對應硬體RGB565介面。
  • gx_display_driver_stm32h7_24xrgb.c 對應硬體RGB888介面。

這兩個檔案的實現套路是一樣的,我們這裡以gx_display_driver_stm32h7_565rgb檔案為例進行說明。

7.9.1 DMA2D使能巨集定義GX_CHROMEART_ENABLE

檔案開頭的巨集定義GX_CHROMEART_ENABLE用於使能DMA2D加速,測試階段可以將其關閉掉,關閉方法就是註釋掉巨集定義#define GX_CHROMEART_ENABLE即可。

7.9.2 視訊記憶體地址巨集定義FrameBufer

LCD的視訊記憶體地址設定是通過巨集定義配置:

#define FrameBufer SDRAM_LCD_BUF1

其中SDRAM_LCD_BUF1的定義是在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

巨集定義FrameBufer是在函式LCD_LL_Init裡面配置視訊記憶體地址的時候被呼叫。

7.9.3 介面函式stm32h7_graphics_driver_setup_565rgb

這個函式是GUIX連線底層最直接的函式,也是配置的關鍵。實現程式碼如下:

1.    /*
2.    ******************************************************************************************************
3.    *    函 數 名: stm32h7_graphics_driver_setup_565rgb
4.    *    功能說明: 驅動介面函式
5.    *    形    參: ---
6.    *    返 回 值: GX_SUCCESS
7.    ******************************************************************************************************
8.    */
9.    UINT stm32h7_graphics_driver_setup_565rgb(GX_DISPLAY *display)
10.    {
11.        LCD_LL_Init();
12.        
13.        _gx_display_driver_565rgb_setup(display, (VOID*)STM32_SCREEN_HANDLE, stm32h7_565rgb_buffer_toggle);
14.    
15.    #if defined(GX_CHROMEART_ENABLE)    
16.        display -> gx_display_driver_pixelmap_blend     = gx_chromeart_pixelmap_blend;
17.        display -> gx_display_driver_pixelmap_draw      = gx_chromeart_pixelmap_draw;
18.        display -> gx_display_driver_canvas_copy        = gx_chromeart_canvas_copy;
19.    
20.        display->gx_display_driver_horizontal_line_draw = gx_chromeart_horizontal_line_draw;
21.        display -> gx_display_driver_vertical_line_draw = gx_chromeart_vertical_line_draw;
22.        display -> gx_display_driver_8bit_glyph_draw    = gx_chromeart_glyph_8bit_draw;
23.    #endif
24.    
25.        return(GX_SUCCESS);
26.    }
  • 第11行,GPIO和LTDC初始化。
  • 第13行,為GUIX和LTDC建立關聯。特別注意引數STM32_SCREEN_HANDLE,巨集定義如下:

#define STM32_SCREEN_HANDLE 0xC0000000

本章7.8.1小節裡面的用到的0xC0000000就是從這裡來的,務必保證匹配。

  • 第16到22行,對一些底層函式做重定向,從而實現DMA2D加速。

7.9.4 通過DMA2D加速的幾個函式和Cache處理

通過DMA2D加速的幾個函式如下(這幾個函式,大家做移植的話,僅需注意變數g_LcdWidth和g_LcdHeight正確賦值了):

  • gx_chromeart_horizontal_line_draw
  • gx_chromeart_vertical_line_draw
  • gx_chromeart_canvas_copy
  • gx_chromeart_pixelmap_draw
  • gx_chromeart_pixelmap_blend
  • gx_chromeart_glyph_8bit_draw

這裡我們以函式gx_chromeart_horizontal_line_draw為例進行說明:

static VOID gx_chromeart_horizontal_line_draw(GX_DRAW_CONTEXT *context, INT xstart, INT xend, INT ypos, INT width, GX_COLOR color)
{
    uint32_t  put;
    int length;
    GX_CANVAS *canvas = context->gx_draw_context_canvas;

    put = (uint32_t) canvas->gx_canvas_memory;
    put += (canvas->gx_canvas_x_resolution * 2 * ypos) + (xstart * 2);

    length = xend - xstart + 1;


    DMA2D->CR = DMA2D_R2M;
    DMA2D->OCOLR = color;

    /* 輸出層 */
    DMA2D->OMAR = (uint32_t)put;
    DMA2D->OOR = canvas->gx_canvas_x_resolution - length;
    DMA2D->OPFCCR = LTDC_PIXEL_FORMAT_RGB565;

    DMA2D->NLR = (uint32_t)(length << 16) | (uint16_t)width;
    SCB_CleanInvalidateDCache();
    DMA2D->CR |= DMA2D_CR_START;
    while (DMA2D->CR & DMA2D_CR_START) {}
}

這裡主要實現了一個水平線的DMA2D加速,功能比較好理解。DMA2D的詳細介紹在本教程的第6章進行了非常詳細的說明,大家如下想了解每個配置語句的功能,可以深入學習第6章。

這裡特別注意函式SCB_CleanInvalidateDCache,因為開啟SDRAM空間的Cache情況下,DMA2D和CPU都會訪問到SDRAM空間,會有資料一致性問題。在DMA2D操作SDRAM前需要做Cache Clean操作,為了保險起見,我們這裡直接把無效化操作也做了,即直接呼叫函式SCB_CleanInvalidateDCache。

7.9.5 更新畫布canvas內容到LCD視訊記憶體

當前GUIX的顯示是採用的畫布機制,即GUIX先在畫布上把介面繪製好,然後將畫布中需要更新的區域繪製到LCD。這部分程式碼是移植成功與否的關鍵(如果大家是用於H7平臺,下面的程式碼無需任何修改可以直接使用):

/*
*********************************************************************************************************
*    函 數 名: stm32h7_565rgb_buffer_toggle
*    功能說明: 更新canvas內容到LCD視訊記憶體
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void stm32h7_565rgb_buffer_toggle(GX_CANVAS *canvas, GX_RECTANGLE *dirty)
{
    GX_RECTANGLE    Limit;
    GX_RECTANGLE    Copy;
    ULONG           offset;
    INT             copy_width;
    INT             copy_height;

#if !defined(GX_CHROMEART_ENABLE)
    INT             row;
    INT             src_stride_ulongs;
    INT             dest_stride_ulongs;
#endif

    ULONG *get;
    ULONG *put;

    gx_utility_rectangle_define(&Limit, 0, 0,
                        canvas->gx_canvas_x_resolution - 1,
                        canvas->gx_canvas_y_resolution - 1);

    if (gx_utility_rectangle_overlap_detect(&Limit, &canvas->gx_canvas_dirty_area, &Copy))
    {
        Copy.gx_rectangle_left &= 0xfffe;
        Copy.gx_rectangle_right |= 0x01;
        copy_width = Copy.gx_rectangle_right - Copy.gx_rectangle_left + 1;
        copy_height = Copy.gx_rectangle_bottom - Copy.gx_rectangle_top + 1;

        /* 從canvas讀取更新區 */
        offset = Copy.gx_rectangle_top * canvas->gx_canvas_x_resolution;
        offset += Copy.gx_rectangle_left;
        offset /= 2; 
        get = canvas ->gx_canvas_memory;
        get += offset;

        /* 從LCD視訊記憶體讀取要更新的區域,將canvas更新的資料複製進來 */
        put = (ULONG *) FrameBufer;
        offset = (canvas->gx_canvas_display_offset_y + Copy.gx_rectangle_top)* g_LcdWidth;
        offset += canvas->gx_canvas_display_offset_x + Copy.gx_rectangle_left;
        offset /= 2;
        put += offset;

#if !defined(GX_CHROMEART_ENABLE)    
        src_stride_ulongs = canvas ->gx_canvas_x_resolution / 2;
        dest_stride_ulongs = g_LcdWidth / 2;
        copy_width /= 2; 
        
        for(row = 0; row < copy_height; row++)
        {
            memcpy(put, get, copy_width * 4);
            put += dest_stride_ulongs;
            get += src_stride_ulongs;
        }
#else    
        DMA2D->CR = 0x00000000UL | (1 << 9);
        DMA2D->FGMAR = (uint32_t)get;
        DMA2D->OMAR = (uint32_t)put;
        DMA2D->FGOR = canvas->gx_canvas_x_resolution - copy_width;
        DMA2D->OOR = g_LcdWidth - copy_width;

        /* 前景層和輸出區域都採用RGB565顏色格式 */
        DMA2D->FGPFCCR = LTDC_PIXEL_FORMAT_RGB565;
        DMA2D->OPFCCR = LTDC_PIXEL_FORMAT_RGB565;
        DMA2D->NLR = (uint32_t)(copy_width << 16) | (uint16_t)copy_height;
        DMA2D->CR |= DMA2D_CR_START;
        while (DMA2D->CR & DMA2D_CR_START) {}
#endif
    }
}

7.10 第8步: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

7.11 第9步:新增GUIX應用進行測試

介紹完了前面幾步,剩下就是新增應用程式碼了。為了方便起見,大家直接使用本章教程配套例子裡面整理好的即可

7.11.1 新增應用檔案MainTask.C和MainTask.h

MainTask.h是GUIX相關的標頭檔案和函式宣告,MainTask.c檔案裡面主要是GUIX的初始化:

1.    /*
2.    ******************************************************************************************************
3.    *                                               變數
4.    ******************************************************************************************************
5.    */
6.    GX_WINDOW         *pScreen;
7.    GX_WINDOW_ROOT  *root;
8.    
9.    /*
10.    ******************************************************************************************************
11.    *    函 數 名: MainTask
12.    *    功能說明: GUI主函式
13.    *    形    參: 無
14.    *    返 回 值: 無
15.    ******************************************************************************************************
16.    */
17.    void MainTask(void) 
18.    {
19.        /* 避免上電後瞬間的撕裂感 */
20.        LCD_SetBackLight(0);
21.        
22.        /*
23.           觸控校準函式預設是註釋掉的,電阻屏需要校準,電容屏無需校準。如果使用者需要校準電阻屏的話,執行
24.           此函式即可,會將觸控校準引數儲存到EEPROM裡面,以後系統上電會自動從EEPROM裡面載入。
25.        */
26.    #if 0
27.        LCD_SetBackLight(255);
28.        LCD_InitHard();
29.        TOUCH_Calibration(2);
30.    #endif
31.        
32.        /*初始化配置 */
33.        gx_initconfig();
34.    
35.        /* 配置顯示屏 */
36.        gx_studio_display_configure(DISPLAY_1, stm32h7_graphics_driver_setup_565rgb,
37.            LANGUAGE_CHINESE, DISPLAY_1_THEME_1, &root);
38.        
39.        /* 建立視窗 */
40.        gx_studio_named_widget_create("window", (GX_WIDGET *)root, (GX_WIDGET **)&pScreen);
41.    
42.        /* 顯示根視窗 */
43.        gx_widget_show(root);
44.    
45.        /* 啟動GUIX */
46.        gx_system_start();
47.        
48.        tx_thread_sleep(100);
49.        LCD_SetBackLight(255);
50.        
51.        while(1)
52.        {
53.            tx_thread_sleep(20);
54.        }
55.    }
  • 第20行,為了避免上電時瞬間的撕裂感,這裡先關閉LCD背光,等首介面繪製完畢後再開啟。
  • 第26到30行,觸控校準函式預設是註釋掉的,電阻屏需要校準,電容屏無需校準。如果使用者需要校準電阻屏的話,執行此函式即可,會將觸控校準引數儲存到EEPROM裡面,以後系統上電會自動從EEPROM裡面載入。
  • 第36到第49行,主要是GUIX的初始化,初始化完畢後開啟背光。

7.11.2 新增動態記憶體配置和Canvas畫布配置檔案

動態記憶體和Canvas畫布配置都放在了檔案App_SysFunctions.c檔案裡面實現。

1.    /*
2.    ******************************************************************************************************
3.    *                                            動態記憶體分配
4.    ******************************************************************************************************
5.    */
6.    #define       GUI_NUMBYTES     1024*1024*24                          /* 設定動態記憶體大小 */
7.    #define       Canvas_Memory    0xC0400000                            /* 設定Canvas地址   */
8.    TX_BYTE_POOL  memory_pool;
9.    uint8_t       *MemoryBlock = (uint8_t *)(0xC0000000 + 1024*1024*8); /* 動態記憶體地址    */
10.    
11.    
12.    /*
13.    ******************************************************************************************************
14.    *                                            變數
15.    ******************************************************************************************************
16.    */
17.    extern GX_STUDIO_DISPLAY_INFO guiapp_display_table[1];
18.    
19.    
20.    /*
21.    ******************************************************************************************************
22.    *                                           動態記憶體函式
23.    ******************************************************************************************************
24.    */
25.    VOID *memory_allocate(ULONG size)
26.    {
27.        VOID *memptr;
28.    
29.        if (tx_byte_allocate(&memory_pool, &memptr, size, TX_NO_WAIT) == TX_SUCCESS)
30.        {
31.            return memptr;
32.        }
33.        return NULL;
34.    }
35.    
36.    void memory_free(VOID *mem)
37.    {
38.        tx_byte_release(mem);
39.    }
40.    
41.    /*
42.    ******************************************************************************************************
43.    *    函 數 名: gx_initconfig
44.    *    功能說明: GUIX
45.    *    形    參: 無       
46.    *    返 回 值: 無
47.    ******************************************************************************************************
48.    */
49.    void gx_initconfig(void) 
50.    {
51.         /* 初始化記憶體池 */
52.        tx_byte_pool_create(&memory_pool, "MemoryBlock",  MemoryBlock,  GUI_NUMBYTES);
53.        
54.        /* 初始化GUIX */
55.        gx_system_initialize();
56.    
57.        /* 註冊動態記憶體申請和釋放函式 */
58.        gx_system_memory_allocator_set(memory_allocate, memory_free);
59.        
60.        /* 自適應不同解析度顯示屏 */
61.        switch (g_LcdType)
62.        {
63.            
64.            case LCD_43_480X272:        /* 4.3寸 480 * 272 */    
65.            case LCD_50_480X272:        /* 5.0寸 480 * 272 */
66.                guiapp_display_table[0].x_resolution = 480;
67.                guiapp_display_table[0].y_resolution = 272;
68.                break;
69.            
70.            case LCD_50_800X480:        /* 5.0寸 800 * 480 */
71.            case LCD_70_800X480:        /* 7.0寸 800 * 480 */    
72.                guiapp_display_table[0].x_resolution = 800;
73.                guiapp_display_table[0].y_resolution = 480;
74.                break;
75.            
76.            default:    
77.                break;
78.        }
79.    
80.        guiapp_display_table[0].canvas_memory = (GX_COLOR *)Canvas_Memory;
81.    }
  • 第6到第9行,設定動態記憶體地址和畫布的地址。

教程配套的STM32H7板子有32MB的SDRAM空間。

    • 前4MB空間用於視訊記憶體,地址0xC0000000。
    • 中間4MB空間用於Canvas畫布,地址0xC0000000 + 1024*1024*4 = 0xC0400000。
    • 最後24MB空間用動態記憶體,地址0xC0000000 + 1024*1024*8 = 0xC0800000。
  • 第17行,這個是在GUIX Studio生成的xxxxx_resources.c檔案裡面。注意要匹配。
  • 第25到39行,動態記憶體的申請和釋放函式。然後通過gx_system_memory_allocator_set進行註冊。
  • 第61到78行,實現了個簡單的不同顯示屏自適應。
  • 第80行,設定Canvas畫布地址。

7.11.3 新增GUIX Studio生成的檔案

使用GUIX Studio生成了4個檔案:

  • guiapp_resources.h
  • guiapp_resources.c
  • guiapp_specifications.h
  • guiapp_specifications.c

後面章節再為大家介紹GUIX Studio生成的這幾個檔案。作為移植,大家進行將其加到移植工程裡面驗證是否正常即可。

7.11.4 BSP初始化並建立GUIX任務

BSP初始化主要涉及到SDRAM初始化,觸控初始化,LTDC初始化(放到了GUIX底層驅動介面檔案裡面了),背光開啟和EEPROM初始化(用於儲存電阻觸控式螢幕校準引數):

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    bsp_InitDWT();      /* 初始化DWT時鐘週期計數器 */       
    bsp_InitKey();         /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitUart();     /* 初始化串列埠 */
    bsp_InitExtIO();    /* 初始化FMC匯流排74HC574擴充套件IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();        /* 初始化LED */    
    bsp_InitTimer();      /* 初始化滴答定時器 */

    bsp_InitExtSDRAM(); /* 初始化SDRAM */

    bsp_InitI2C();     /* 初始化I2C匯流排,用於EEPROM */
    TOUCH_InitHard();

    /* 延遲200ms再點亮背光,避免瞬間高亮 */
    bsp_DelayMS(200); 
    LCD_SetBackLight(255);
}

GUIX任務的建立在main.c檔案裡面實現,配置如下:

#define  APP_CFG_TASK_GUI_PRIO                     5u
#define  APP_CFG_TASK_GUI_STK_SIZE                 4096u
static  TX_THREAD   AppTaskGUITCB;
static  uint64_t    AppTaskGUIStk[APP_CFG_TASK_GUI_STK_SIZE/8];


/**************建立GUIX任務*********************/
  tx_thread_create(&AppTaskGUITCB,               /* 任務控制塊地址 */    
                   "App Task GUI",              /* 任務名 */
                    AppTaskGUI,                  /* 啟動任務函式地址 */
                    0,                           /* 傳遞給任務的引數 */
                    &AppTaskGUIStk[0],            /* 堆疊基地址 */
                    APP_CFG_TASK_GUI_STK_SIZE,    /* 堆疊空間大小 */  
                    APP_CFG_TASK_GUI_PRIO,        /* 任務優先順序*/
                    APP_CFG_TASK_GUI_PRIO,        /* 任務搶佔閥值 */
                     TX_NO_TIME_SLICE,             /* 不開啟時間片 */
                     TX_AUTO_START);               /* 建立後立即啟動 */

/*
*********************************************************************************************************
*    函 數 名: AppTaskGUI
*    功能說明: GUI應用任務
*    形    參: thread_input 建立該任務時傳遞的形參
*    返 回 值: 無
    優 先 級: 5
*********************************************************************************************************
*/
static void AppTaskGUI(ULONG thread_input)
{    
    MainTask();
}

7.11.5 自動建立的GUIX系統任務。

初始化了GUIX後,會自動建立一個GUIX任務,對於這點,大家移植的使用要特別注意。此任務的優先順序和任務堆疊大小是在gx_port.h檔案裡面定義的。

7.12 顯示屏閃爍問題解決方法

如果大家除錯狀態下或者剛下載GUIX的程式到STM32H7/STM32F429裡面時,出現螢幕會閃爍,或者說抖動,這個是正常現象。詳見此貼的說明:

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

如果顯示屏長時間處於抖動狀態,說明LTDC的時鐘配置高了或者低了(高的概率居多),可以將LTDC時鐘降低一半或者提高一倍進行測試。配置方法看本教程第4章的4.4.3小節。

7.13 避免顯示屏上電瞬間高亮和撕裂感

大家使用顯示屏的時候,這兩個問題很容易遇到,這裡為大家提供個解決辦法,提升使用者體驗。

7.13.1 上電瞬間高亮解決辦法

這個問題並不是軟體配置造成的,通過條件PWM背光也是無法解決的。解決辦法是板子上後,先不要開啟PWM,延遲200ms後再開啟LCD的背光即可,注意時間不可以太短,太短沒效果,大家可以根據實際情況做條件。本章配套程式是放在bsp.c檔案的函式bsp_Init裡面做了處理。

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 省略未寫 */

    /* 延遲200ms再點亮背光,避免瞬間高亮 */
    bsp_DelayMS(200); 
    LCD_SetBackLight(255);
}

7.13.2 上電瞬間撕裂感解決辦法

有時候介面設計比較複雜時,開機後不能保證所有的控制元件同時加載出來,介面會有種撕裂的感覺,這個時候有個比較好的解決思路,GUIX初始化配置前關閉背光,初始化完畢並且首介面繪製完畢後再開啟背光,使用者體驗就會好很多。本章配套程式是放在MainTask.c檔案的函式MainTask裡面做了處理。

/*
*********************************************************************************************************
*    函 數 名: MainTask
*    功能說明: GUI主函式
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void MainTask(void) 
{
    /* 避免上電後瞬間的撕裂感 */
    LCD_SetBackLight(0); 0表示關閉LED
    
    /* 省略未寫 */
    
    tx_thread_sleep(100);
    LCD_SetBackLight(255); 255表示最亮
    
    while(1)
    {
        tx_thread_sleep(20);
    }
}

7.14 實驗例程

(注,如果是電阻屏,需要做觸控校準,校準方法看本教程附件章節A)

本章節配套了如下幾個例子供大家移植參考:

  • V7-2004_Threadx Kernel Template

ThreadX核心模板,用於大家移植GUIX的參考Demo,含有GCC,IAR,MDK AC5和AC6四個版本工程。

  • V7-2005_GUIX Template(RGB565)

GUIX模板例子,採用的硬體RGB565介面,含有GCC,IAR,MDK AC5和AC6四個版本工程。

  • V7-2006_GUIX Template(ARGB8888)

GUIX模板例子,採用的硬體ARGB8888介面,含有GCC,IAR,MDK AC5和AC6四個版本工程。

  • V7-2007_GUIX Studio Template

GUIX Studio工程模板,設計介面後,生成的檔案可直接新增到MDK,IAR和GCC軟體平臺使用。

顯示效果如下,800*480解析度:

IAR,MDK AC5和AC6工程可以串列埠列印任務執行情況:按開發板的按鍵K1可以列印,波特率 115200,資料位 8,奇偶校驗位無,停止位 1:

Embedded Studio(GCC)平臺的串列埠列印是通過其除錯元件SEGGER RTT做的串列埠列印,速度也非常快,列印效果如下:

展示裡面有亂碼是因為Embedded Studio不支援中文。

7.15 總結

本章節為大家講解的內容涉及到的知識較多,資訊量較大,部分知識點沒有弄明白是沒有關係的,但是一定要掌握ThreadX核心和ThreadX GUIX工程的框架設計,掌握後再分析細節,事半功倍。

如果可以的話,最好移植一個與教程配套開發板不一樣的顯示屏和觸控IC,這樣對於本章的認識將更加全面。