最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98429
第10章 ThreadX GUIX移植到STM32H7(GCC)
本章节将为大家介绍ThreadX GUIX的GCC方式移植和设计框架,理论上不建议初学者直接学习学习,因为本章节涉及到的知识点很多,建议对GUIX的应用有一些了解后再来看,这样将事半功倍。但是本章的工程模板框架一定要学习。本章节提供的移植方法支持RGB565和ARGB8888两种颜色格式的实现。同时可以自适应我们生产的4.3寸,5寸和7寸的电阻屏和电容屏。
虽然本章节是以我们开发板为例进行移植的,但是教会大家如何移植到自己的板子上以及移植过程中的注意事项是本章节的重点。
10.1初学者重要提示
10.2移植前的准备工作以及移植GUIX的流程
10.3第1步:ThreadX内核模板框架设计
10.4第2步:GUIX模板框架设计(重要)
10.5第3步:下载GUIX库并添加到ThreadX内核工程模板
10.6第4步:SDRAM驱动的实现
10.7第5步:LTDC涉及到的引脚配置和时序配置
10.8第6步:电阻屏和电容屏触摸驱动的实现
10.9第7步:GUIX底层接口函数和配置
10.10第8步:SDRAM的MPU Cache配置
10.11第9步:添加GUI应用进行测试。
10.12显示屏闪烁文件解决方法
10.13避免显示屏上电瞬间高亮和撕裂感
10.14实验例程
10.15 总结
10.1 初学者重要提示
- 学习本章节前,务必保证已经学习了本教程的第4章,第5章和第6章,这三章是移植前的必备知识。
- 为了方便大家移植,推荐直接添加我们的工程文件到自己的工程或者直接使用我们的工程模板,按照本章的修改说明移植即可。
- 提供了ARGB8888和RGB565两种颜色格式的移植工程,移植方法是一样的,仅添加的接口文件不同。
- 本章节是以移植到ThreadX上为例进行说明的,移植到其它小型RTOS方法,后面章节再为大家介绍。
- 由于开发板要自适应4.3寸,5寸和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。
10.2 移植前的准备工作以及移植GUIX的流程
移植前注意以下两个问题:
- 本章节的IDE开发环境务必是Embedded Studio5.10及其以上版本,镜像下载地址:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=97090 。
- 准备一个简单的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应用进行测试。
10.3 第1步:了解ThreadX内核模板框架设计
移植GUIX前,我们优先了解下ThreadX内核模板程序的框图。
10.3.1 准备一个ThreadX内核工程模板
首先准备好一个简单的ThreadX工程模板,工程模板的制作在ThreadX内核教程里面有详细说明,这里的重点是教大家移植GUIX,对应的例子名称:V7-2004_ThreadX Kernel Template。准备好的工程模板如下图所示(特别注意,我们这个模板已经添加裸机LCD操作所需的文件)。
10.3.2 内核框架整体把控(重要)
为了帮助大家更好的理解ThreadX内核例子模板,专门制作了一个框图,可以让大家整体把控模板设计:
下面把几个关键点逐一为大家做个说明。
10.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
10.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
10.3.5 系统时钟节拍配置tx_initialize_low_level.s
这个汇编文件里面有个重要参数需要大家配置,即芯片主频和系统时钟节拍。
SYSTEM_CLOCK EQU 400000000
SYSTICK_CYCLES EQU ((SYSTEM_CLOCK / 1000) -1)
400000000是系统时钟主频,1000对应的就是系统时钟节拍,这里1000就表示1000Hz。
10.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];
10.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); } }
10.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; }
10.3.9 ThreadX使能硬件浮点
这个是移植的坑王,大家移植后,可以测试下多任务的FPU计算是否有异常。比如两个任务运行相同的浮点运算和刷新速度,看看两个任务的输出是否同步变化,这个测试非常重要:
那么问题来了,正确的使能姿势是什么?务必保证使能了预定义宏:
10.4 第2步:了解GUIX模板框架设计
以往我们做教程都是先介绍如何移植,然后看最后的移植效果。这次我们反过来,先看移植完成的效果,然后移植。
10.4.1 GUIX工程模板
GUIX工程模板移植完成后,大体是下面这种效果:
10.4.2 GUIX框架整体把控(重要)
为了帮助大家更好的理解GUIX内核例子模板,专门制作了一个框图,可以让大家整体把控模板设计:
下面把几个关键点逐一为大家做个说明。
10.4.3 GUIX配置文件gx_user.h
此文件主要用于GUIX的配置,GUIX相关的宏定义配置非常多,当前先把这个文件预留出来,随着后面章节的进行,用到那些宏定义了再添加。
10.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); }
10.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"
10.4.6 LCD裸机驱动文件
STM32H7的LCD控制器LTDC控制有两个相关的驱动文件,即bsp_tft_h7.c和bsp_tft_lcd.c。移植GUIX时,这两个文件基本用不上了,已经把这两个文件实现的LTDC配置全部集成到了GUIX的底层接口驱动文件里面,方便大家移植。
10.4.7 电阻和电容触摸驱动文件
电阻和电容触摸主要用的以下几个文件:
- 总触摸文件,用于识别各种触摸:bsp_ts_touch.c
- 电容触摸文件bsp_ts_ft5x06.c
- 电容触摸文件bsp_ts_gt811.c
- 电容触摸文件bsp_ts_gt911.c
- 电阻触摸文件bsp_ts_stmpe811
10.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(); } }
10.4.9 GUIX底层驱动接口文件
当前制作了两个底层驱动接口文件:
- gx_display_driver_stm32h7_565rgb.c 对应硬件RGB565接口。
- gx_display_driver_stm32h7_24xrgb.c 对应硬件RGB888接口。
大家可以根据需要选择相应驱动。
10.4.10 GUIX源码文件
GUIX的源码文件非常多,一个文件一个API,有1200个左右,大家移植的时候最好都加上。
10.4.11 GUIX应用文件
几个应用文件的作用如下:
随着后面的章节的学习,逐渐就熟练了。
10.5 第3步:添加GUIX库所有相关文件到ThreadX内核工程模板
了解了ThreadX内核框架和GUIX框架后,介绍下如何将GUIX移植到ThreadX内核工程模板里面。我们这里一步到位,直接把所有相关的文件都加上,然后再介绍如何修改,方便大家移植到自己的板子上。
10.5.1 第3.1步,下载GUIX源码包
按照第2章2.3.1小节讲解的方法下载GUIX软件包guix-6.0.1_rel(如果软件包升级了,数字6.0.1略有不同),下面是GUIX软件包内容:
主要用到两个文件夹:
common文件夹里面是源码文件。
ports文件夹里面是移植文件。
10.5.2 第3.2步,创建GUIX文件夹到工程模板
在工程模板创建GUIX文件夹
GUIX的M7内核port文件夹里面只有一个GNU(对应路径portscortex_m7),为了方便我们管理,再创建IAR,AC5和AC6三个文件夹,文件夹里面的内容和GNU里面的一样,并且再添加一个src文件夹,整体效果就是下面这样:
新建的src文件夹是用来存放GUIX底层驱动接口文件用。
10.5.3 第3.3步,添加底层驱动接口文件
添加驱动接口文件到第3.2步创建的gnu->src和gnu-inc文件夹里面(h文件添加到inc文件夹,c文件放到src文件夹)
为了移植方便,大家直接复制本周教程配套例子在此文件夹下的文件即可。
10.5.4 第3.4步,添加Port文件和源码文件到工程
将源码文件和ports文件添加到MDK的工程项目中,添加后的效果如下:
添加的时候最好400个文件为一组,如果全部1200个文件同时添加,Embedded Studio会没有反应。
10.5.5 第3.5步,添加配置文件gx_user.h
在User文件夹下添加文件gx_user.h,直接从本章节教程配套例子的User文件夹复制即可。此文件主要用于GUIX配置。
为了方便管理,我们这里将路径GUIXportscortex_m7gnuinc里面的gx_port.h文件也添加进来了。
10.5.6 第3.5步,添加GUIX应用文件
在User文件夹添加文件夹GUIX,直接从本章节教程配套例子的User文件夹复制即可,此文件夹主要是GUIX的应用部分,内容如下:
10.5.7 第3.6步,添加BSP驱动文件
需要添加的BSP驱动文件如下:
除了SDRAM,LTDC,EEPROM和触摸两个的文件以外,还要添加bsp_tim_pwm.c,这个是用于设置PWM背光用的。
另外注意一点,bsp_tft_lcd.c文件还关联了一些字库文件,大家最好也将其添加到工程里面。这些字库文件位于本章配套例子的User文件夹下,大家直接复制到自己工程的工程里面添加即可,添加后效果如下:
10.5.8 第3.7步,添加HAL库文件
相关BSP驱动关联到的HAL库文件都添加了进来,简单省事些,大家也可以把HAL库所有文件都添加进来:
10.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
10.5.10 第3.9步,添加头文件路径
需要添加的路径如下:
至此,我们需要的GUIX文件都已经添加完毕。下面为大家介绍如何修改用于自己的板子。
10.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。
10.7 第5步:LTDC涉及到的引脚配置和时序配置
10.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。
10.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的速度等级。
如果显示了,但是显示的位置不正确,可以重新调整时序参数即可。
10.8 第6步:电阻屏和电容屏触摸驱动的实现
本小节的实现基于本教程的第5章,当前驱动对电阻触摸芯片STMPE811和电容触摸芯片FT5X06、GT911和GT811的显示屏都进行了支持。
实现比较简单,因为GUIX的触摸分按下,松手和移动三个事件,正好这几款触摸芯片的驱动也是分这三个事件,所以仅需修改下函数TOUCH_PutKey,所有显示屏触摸就都可以完美融合了。
10.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,这个不是随便写的一个值,要与本章10.8.1小节设置STM32_SCREEN_HANDLE数值一致。
10.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); } }
10.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关联的方式。
10.9 第7步:GUIX底层接口函数和配置
GUI的底层接口函数和配置的实现在我们第1步中添加的底层驱动接口文件中:
- gx_display_driver_stm32h7_565rgb.c 对应硬件RGB565接口。
- gx_display_driver_stm32h7_24xrgb.c 对应硬件RGB888接口。
这两个文件的实现套路是一样的,我们这里以gx_display_driver_stm32h7_565rgb文件为例进行说明。
10.9.1 DMA2D使能宏定义GX_CHROMEART_ENABLE
文件开头的宏定义GX_CHROMEART_ENABLE用于使能DMA2D加速,测试阶段可以将其关闭掉,关闭方法就是注释掉宏定义#define GX_CHROMEART_ENABLE即可。
10.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里面配置显存地址的时候被调用。
10.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
本章10.8.1小节里面的用到的0xC0000000就是从这里来的,务必保证匹配。
- 第16到22行,对一些底层函数做重定向,从而实现DMA2D加速。
10.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。
10.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 } }
10.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
10.11 第9步:添加GUIX应用进行测试
介绍完了前面几步,剩下就是添加应用代码了。为了方便起见,大家直接使用本章教程配套例子里面整理好的即可
10.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的初始化,初始化完毕后打开背光。
10.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画布地址。
10.11.3 添加GUIX Studio生成的文件
使用GUIX Studio生成了4个文件:
- guiapp_resources.h
- guiapp_resources.c
- guiapp_specifications.h
- guiapp_specifications.c
后面章节再为大家介绍GUIX Studio生成的这几个文件。作为移植,大家进行将其加到移植工程里面验证是否正常即可。
10.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(); }
10.11.5 自动创建的GUIX系统任务。
初始化了GUIX后,会自动创建一个GUIX任务,对于这点,大家移植的使用要特别注意。此任务的优先级和任务堆栈大小是在gx_port.h文件里面定义的。
10.12 显示屏闪烁问题解决方法
如果大家调试状态下或者刚下载GUIX的程序到STM32H7/STM32F429里面时,出现屏幕会闪烁,或者说抖动,这个是正常现象。详见此贴的说明:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=16892 。
如果显示屏长时间处于抖动状态,说明LTDC的时钟配置高了或者低了(高的概率居多),可以将LTDC时钟降低一半或者提高一倍进行测试。配置方法看本教程第4章的4.4.3小节。
10.13 避免显示屏上电瞬间高亮和撕裂感
大家使用显示屏的时候,这两个问题很容易遇到,这里为大家提供个解决办法,提升用户体验。
10.13.1 上电瞬间高亮解决办法
这个问题并不是软件配置造成的,通过条件PWM背光也是无法解决的。解决办法是板子上后,先不要开启PWM,延迟200ms后再打开LCD的背光即可,注意时间不可以太短,太短没效果,大家可以根据实际情况做条件。本章配套程序是放在bsp.c文件的函数bsp_Init里面做了处理。
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 省略未写 */ /* 延迟200ms再点亮背光,避免瞬间高亮 */ bsp_DelayMS(200); LCD_SetBackLight(255); }
10.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); } }
10.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不支持中文。
10.15 总结
本章节为大家讲解的内容涉及到的知识较多,信息量较大,部分知识点没有弄明白是没有关系的,但是一定要掌握ThreadX内核和ThreadX GUIX工程的框架设计,掌握后再分析细节,事半功倍。
如果可以的话,最好移植一个与教程配套开发板不一样的显示屏和触摸IC,这样对于本章的认识将更加全面。