最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98429
本章节将为大家介绍ThreadX GUIX的GCC方式移植和设计框架,理论上不建议初学者直接学习学习,因为本章节涉及到的知识点很多,建议对GUIX的应用有一些了解后再来看,这样将事半功倍。但是本章的工程模板框架一定要学习。本章节提供的移植方法支持RGB565和ARGB8888两种颜色格式的实现。同时可以自适应我们生产的4.3寸,5寸和7寸的电阻屏和电容屏。
虽然本章节是以我们开发板为例进行移植的,但是教会大家如何移植到自己的板子上以及移植过程中的注意事项是本章节的重点。
目录
第10章 ThreadX GUIX移植到STM32F429(GCC)
10.1 初学者重要提示
10.2 移植前的准备工作以及移植GUIX的流程
10.3 第1步:了解ThreadX内核模板框架设计
10.3.1 准备一个ThreadX内核工程模板
10.3.2 内核框架整体把控(重要)
10.3.3 各种头文件汇总includes.h
10.3.4 TheadX配置文件tx_user.h
10.3.5 系统时钟节拍配置tx_initialize_low_level.s
10.3.6 TheadX任务管理main.c
10.3.7 TheadX启动任务
10.3.8 HAL库时间基准stm32f4xx_hal_timbase_tim.c
10.3.9 ThreadX使能硬件浮点
10.4 第2步:了解GUIX模板框架设计
10.4.1 GUIX工程模板
10.4.2 GUIX框架整体把控(重要)
10.4.3 GUIX配置文件gx_user.h
10.4.4 外设驱动初始化文件bsp.c
10.4.5 外设驱动头文件汇总bsp.h
10.4.6 LCD裸机驱动文件
10.4.7 电阻和电容触摸驱动文件
10.4.8 电阻触摸校准参数存储到EEPROM
10.4.9 GUIX底层驱动接口文件
10.4.10 GUIX源码文件
10.4.11 GUIX应用文件
10.5 第3步:添加GUIX库所有相关文件到ThreadX内核工程模板
10.5.1 第3.1步,下载GUIX源码包
10.5.2 第3.2步,创建GUIX文件夹到工程模板
10.5.3 第3.3步,添加底层驱动接口文件
10.5.4 第3.4步,添加Port文件和源码文件到工程
10.5.5 第3.5步,添加配置文件gx_user.h
10.5.6 第3.5步,添加GUIX应用文件
10.5.7 第3.6步,添加BSP驱动文件
10.5.8 第3.7步,添加HAL库文件
10.5.9 第3.8步,添加预定义宏
10.5.10 第3.9步,添加头文件路径
10.6 第4步:SDRAM驱动实现(用于显存,动态内存和画布)
10.7 第5步:LTDC涉及到的引脚配置和时序配置
10.7.1 LTDC时序配置
10.7.2 如何验证LTDC的时序配置是否正确
10.8 第6步:电阻屏和电容屏触摸驱动的实现
10.8.1 添加GUIX的按下,松手和移动三个事件
10.8.2 周期性调用触摸扫描函数
10.8.3 如何将触摸驱动移植到自己的板子
10.9 第7步:GUIX底层接口函数和配置
10.9.1 DMA2D使能宏定义GX_CHROMEART_ENABLE
10.9.2 显存地址宏定义FrameBufer
10.9.3 接口函数stm32f4_graphics_driver_setup_565rgb
10.9.4 通过DMA2D加速的几个函数和Cache处理
10.9.5 更新画布canvas内容到LCD显存
10.10 第8步:添加GUIX应用进行测试
10.10.1 添加应用文件MainTask.C和MainTask.h
10.10.2 添加动态内存配置和Canvas画布配置文件
10.10.3 添加GUIX Studio生成的文件
10.10.4 BSP初始化并创建GUIX任务
10.10.5 自动创建的GUIX系统任务。
10.11 显示屏闪烁问题解决方法
10.12 避免显示屏上电瞬间高亮和撕裂感
10.12.1 上电瞬间高亮解决办法
10.12.2 上电瞬间撕裂感解决办法
10.13 实验例程
10.14 总结
GUIX需要的底层接口函数已经全部集成在gx_display_driver_stm32f4_24xrgb.c文件和gx_display_driver_stm32f4_565rgb.c里面。对于这两个文件,用户仅需学会使用里面的两个宏配置以及LTDC涉及到的引脚和时序配置函数,这个是需要用户自己去实现的,配置方法已经在本章节的10.7小节进行讲解。
另外还有一个显示屏背光调节函数LCD_SetBackLight的调用,其它都不用做任何修改。这三个地方都设置了,GUIX的显示屏移植就完成了。
电容触摸的移植比较容易,因为电容触摸芯片可以自动触摸校准,所以仅需配置完触摸芯片后将触摸芯片返回的触摸坐标(电容触摸芯片返回的就是实际的坐标值),按下,松手和移动三种状态发送给GUIX即可。
电阻触摸的移植要稍麻烦些,由于电阻触摸板的线性度不是很好,如果不做触摸校准和滤波处理会有点击不准确和飞点问题。当前配套2点和4点触摸校准算法,大家可以根据需要选择,默认是用的2点触摸校准算法。其中触摸滤波方法是检测到触摸后先延迟30ms,消除抖动,然后采集10组坐标值做升序排列,去掉最大的几组坐标和最小的几组坐标,对中间的几组求平均作为最终的数值(电容触摸芯片返回的是ADC数值,不是实际坐标值)。然后将最终的数值代入通过触摸校准建立的线性公式来获得实际的坐标值,此时就可以将触摸坐标和触摸按下,松手和移动状态发送给GUIX。
移植前注意以下两个问题:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=97090 。
配套模板名称:V6-2004_ThreadX Kernel Template
GUIX的移植通过以下8步完成,下面各个小节详细讲解每一步:
移植GUIX前,我们优先了解下ThreadX内核模板程序的框图。
首先准备好一个简单的ThreadX工程模板,工程模板的制作在ThreadX内核教程里面有详细说明,这里的重点是教大家移植GUIX,对应的例子名称:V6-2004_ThreadX Kernel Template。准备好的工程模板如下图所示(特别注意,我们这个模板已经添加裸机LCD操作所需的文件)。
为了帮助大家更好的理解ThreadX内核例子模板,专门制作了一个框图,可以让大家整体把控模板设计:
下面把几个关键点逐一为大家做个说明。
这个文件主要实现工程中各种头文件的汇总,大家用到的都可以将其放到这个头文件里面。其它应用源文件有调用到的,直接调用这个头文件includes.h即可。
使用这个头文件主要是方便各种头文件的管理。
/* ********************************************************************************************************* * 标准库 ********************************************************************************************************* */ #include#include #include #include /* ********************************************************************************************************* * OS ********************************************************************************************************* */ #include "tx_api.h" #include "tx_timer.h" /* ********************************************************************************************************* * APP / BSP ********************************************************************************************************* */ #include /* ********************************************************************************************************* * 变量和函数 ********************************************************************************************************* */ /* 方便RTOS里面使用 */ extern void SysTick_ISR(void); #define bsp_ProPer1ms SysTick_ISR
此文件主要用于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
这个汇编文件里面有个重要参数需要大家配置,即芯片主频和系统时钟节拍。
SYSTEM_CLOCK EQU 168000000
SYSTICK_CYCLES EQU ((SYSTEM_CLOCK / 1000) -1)
168000000是系统时钟主频,1000对应的就是系统时钟节拍,这里1000就表示1000Hz。
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];
启动任务里面主要做了四个工作:
代码如下:
/* ********************************************************************************************************* * 函 数 名: 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); } }
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; }
这个是移植的坑王,大家移植后,可以测试下多任务的FPU计算是否有异常。比如两个任务运行相同的浮点运算和刷新速度,看看两个任务的输出是否同步变化,这个测试非常重要:
那么问题来了,正确的使能姿势是什么?务必保证使能了预定义宏:
以往我们做教程都是先介绍如何移植,然后看最后的移植效果。这次我们反过来,先看移植完成的效果,然后移植。
GUIX工程模板移植完成后,大体是下面这种效果:
为了帮助大家更好的理解GUIX内核例子模板,专门制作了一个框图,可以让大家整体把控模板设计:
下面把几个关键点逐一为大家做个说明。
此文件主要用于GUIX的配置,GUIX相关的宏定义配置非常多,当前先把这个文件预留出来,随着后面章节的进行,用到那些宏定义了再添加。
使用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); }
此文件主要用于添加了那些外设驱动文件后,使能相应头文件,特别工程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_429.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"
STM32F429的LCD控制器LTDC控制有两个相关的驱动文件,即bsp_tft_429.c和bsp_tft_lcd.c。移植GUIX时,这两个文件基本用不上了,已经把这两个文件实现的LTDC配置全部集成到了GUIX的底层接口驱动文件里面,方便大家移植。
电阻和电容触摸主要用的以下几个文件:
EEPROM的驱动主要涉及到两个文件:
配置EEPROM用到的两个引脚。
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(); } }
当前制作了两个底层驱动接口文件:
大家可以根据需要选择相应驱动。
GUIX的源码文件非常多,一个文件一个API,有1200个左右,大家移植的时候最好都加上。
几个应用文件的作用如下:
随着后面的章节的学习,逐渐就熟练了。
了解了ThreadX内核框架和GUIX框架后,介绍下如何将GUIX移植到ThreadX内核工程模板里面。我们这里一步到位,直接把所有相关的文件都加上,然后再介绍如何修改,方便大家移植到自己的板子上。
按照第2章2.3.1小节讲解的方法下载GUIX软件包guix-6.0.1_rel(如果软件包升级了,数字6.0.1略有不同),下面是GUIX软件包内容:
主要用到两个文件夹:
common文件夹里面是源码文件。
ports文件夹里面是移植文件。
在工程模板创建GUIX文件夹
GUIX的M4内核port文件夹里面只有一个GNU(对应路径ports\cortex_m4),为了方便我们管理,再创建IAR,AC5和AC6三个文件夹,文件夹里面的内容和GNU里面的一样,并且再添加一个src文件夹,整体效果就是下面这样:
新建的src文件夹是用来存放GUIX底层驱动接口文件用。
添加驱动接口文件到第3.2步创建的gnu->src和gnu-inc文件夹里面(h文件添加到inc文件夹,c文件放到src文件夹)
为了移植方便,大家直接复制本周教程配套例子在此文件夹下的文件即可。
将源码文件和ports文件添加到MDK的工程项目中,添加后的效果如下:
添加的时候最好400个文件为一组,如果全部1200个文件同时添加,Embedded Studio会没有反应。
在User文件夹下添加文件gx_user.h,直接从本章节教程配套例子的User文件夹复制即可。此文件主要用于GUIX配置。
为了方便管理,我们这里将路径GUIX\ports\cortex_m4\gnu\inc里面的gx_port.h文件也添加进来了。
在User文件夹添加文件夹GUIX,直接从本章节教程配套例子的User文件夹复制即可,此文件夹主要是GUIX的应用部分,内容如下:
需要添加的BSP驱动文件如下:
除了SDRAM,LTDC,EEPROM和触摸两个的文件以外,还要添加bsp_tim_pwm.c,这个是用于设置PWM背光用的。
另外注意一点,bsp_tft_lcd.c文件还关联了一些字库文件,大家最好也将其添加到工程里面。这些字库文件位于本章配套例子的User文件夹下,大家直接复制到自己工程的工程里面添加即可,添加后效果如下:
相关BSP驱动关联到的HAL库文件都添加了进来,简单省事些,大家也可以把HAL库所有文件都添加进来:
C/C++文件中添加的预定义宏如下:
需要添加的路径如下:
至此,我们需要的GUIX文件都已经添加完毕。下面为大家介绍如何修改用于自己的板子。
一定要保证SDRAM大批量读写数据时是正常的,SDRAM的测试可以自己专门做一个工程测试下。对于SDRAM的驱动实现,可以学习我们写的BSP驱动教程第39章:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255 。
不管你使用的是镁光的,海力士的,三星的,ISSI的或者华邦的,基本实现方法都是一样的。教程配套的板子使用的是ISSI的32位带宽的SDRAM,如果想最大限度发挥STM32F429驱动SDRAM的性能,强烈建议使用32位带宽的SDRAM,或者两个16位SDRAM组成32位带宽的SDRAM也是可以的。那SDRAM主要起到什么作用呢?作用有二:
STM32F429的LTDC外接RGB接口屏是没有显存的,所以需要SDRAM用作显存。如果用户选择STM32F429 LTDC的颜色格式是32位色ARGB8888,那么所需要显存大小(单位字节)是:显示屏宽 * 显示屏高 * (32/8), 其中32/8是表示这种颜色格式的一个像素点需要4个字节来表示。又比如配置颜色格式是16位色的RGB565,那么需要的显存大小是:显示屏宽 * 显示屏高 * (16/8),其中16/8是表示这种颜色格式的一个像素点需要2个字节来表示。其它的颜色格式,依此类推。
GUIX要做的炫酷,是比较消耗动态内存的,所以用户可以将SDRAM除了用于显存以外的所有内存全部用作GUIX动态内存和canvas幕布。
============================================================
如果SDRAM的驱动测试已经没有问题了,就可以将其添加到工程里面了,开发板使用的SDRAM驱动文件是bsp_fmc_sdram.c。
添加到工程里面后要分配SDRAM的使用,教程配套开发板使用的是16MB,32位带宽的SDRAM。
用户仅需配置LTDC涉及到的引脚和时序即可,配置函数封装到下面两个接口文件的末尾。
另外,由于开发板配套了4.3寸,5寸和7寸屏显示屏,所以要对这几种尺寸的显示屏做自适应,每个屏的时序配置都是不一样的,具体实现在gx_display_driver_stm32f4_565rgb.c和gx_display_driver_stm32f4_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. | LCD429_TFT R0 <-> PI.15 | LCD429_TFT G0 <-> PJ.07 | LCD429_TFT B0 <-> PJ.12 | 73. | LCD429_TFT R1 <-> PJ.00 | LCD429_TFT G1 <-> PJ.08 | LCD429_TFT B1 <-> PJ.13 | 74. | LCD429_TFT R2 <-> PJ.01 | LCD429_TFT G2 <-> PJ.09 | LCD429_TFT B2 <-> PJ.14 | 75. | LCD429_TFT R3 <-> PJ.02 | LCD429_TFT G3 <-> PJ.10 | LCD429_TFT B3 <-> PJ.15 | 76. | LCD429_TFT R4 <-> PJ.03 | LCD429_TFT G4 <-> PJ.11 | LCD429_TFT B4 <-> PK.03 | 77. | LCD429_TFT R5 <-> PJ.04 | LCD429_TFT G5 <-> PK.00 | LCD429_TFT B5 <-> PK.04 | 78. | LCD429_TFT R6 <-> PJ.05 | LCD429_TFT G6 <-> PK.01 | LCD429_TFT B6 <-> PK.05 | 79. | LCD429_TFT R7 <-> PJ.06 | LCD429_TFT G7 <-> PK.02 | LCD429_TFT B7 <-> PK.06 | 80. ------------------------------------------------------------------------------- 81. | LCD429_TFT HSYNC <-> PI.12 | LCDTFT VSYNC <-> PI.13 | 82. | LCD429_TFT CLK <-> PI.14 | LCD429_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_VERY_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_VERY_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_VERY_HIGH; 122. GPIO_Init_Structure.Alternate = GPIO_AF14_LTDC; 123. HAL_GPIO_Init(GPIOK, &GPIO_Init_Structure); 124. } 125. 126. /*##-2- LTDC初始化 #############################################################*/ 127. { 128. LTDC_LayerCfgTypeDef pLayerCfg = {0}; 129. uint16_t Width, Height, HSYNC_W, HBP, HFP, VSYNC_W, VBP, VFP; 130. RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {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. case LCD_50_480X272: /* 5.0寸 480 * 272 */ 148. Width = 480; 149. Height = 272; 150. 151. HSYNC_W = 40; 152. HBP = 2; 153. HFP = 2; 154. VSYNC_W = 9; 155. VBP = 2; 156. VFP = 2; 157. 158. /* LCD 时钟配置 */ 159. /* 160. PLLSAI_VCO Input = HSE_VALUE / PLL_M = 8M / 8 = 1MHz 161. PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAI_N = 1 * 420 = 420MHz 162. PLLLCDCLK = PLLSAI_VCO Output / PLLSAI_R = 420 / 7 = 60MHz 163. LTDC 时钟 = PLLLCDCLK / RCC_PLLSAIDivR = 60 / 4 = 15MHz 164. */ 165. /* 166. 刷新率 = 15MHz /((Width + HSYNC_W + HBP + HFP)*(Height + VSYNC_W + VBP + VFP)) 167. = 15000000/((480 + 40 + 2 + 2)*(272 + 9 + 2 + 2)) 168. = 15000000/(524*285) 169. = 100Hz 170. 171. */ 172. PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC; 173. PeriphClkInitStruct.PLLSAI.PLLSAIN = 420; 174. PeriphClkInitStruct.PLLSAI.PLLSAIR = 7; 175. PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_4; 176. if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) 177. { 178. Error_Handler(__FILE__, __LINE__); 179. } 180. break; 181. 182. case LCD_50_800X480: /* 5.0寸 800 * 480 */ 183. case LCD_70_800X480: /* 7.0寸 800 * 480 */ 184. Width = 800; 185. Height = 480; 186. 187. HSYNC_W = 96; 188. HBP = 10; 189. HFP = 10; 190. VSYNC_W = 2; 191. VBP = 10; 192. VFP = 10; 193. 194. /* LCD 时钟配置 */ 195. /* 196. PLLSAI_VCO Input = HSE_VALUE / PLL_M = 8M / 8 = 1MHz 197. PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAI_N = 1 * 420 = 420MHz 198. PLLLCDCLK = PLLSAI_VCO Output / PLLSAI_R = 420 / 7 = 60MHz 199. LTDC 时钟 = PLLLCDCLK / RCC_PLLSAIDivR = 60 / 4 = 15MHz 200. */ 201. /* 202. 刷新率 = 15MHz /((Width + HSYNC_W + HBP + HFP)*(Height + VSYNC_W + VBP + VFP)) 203. = 15000000/((800 + 96 + 10 + 10)*(480 + 2 + 10 + 10)) 204. = 15000000/(916*502) 205. = 32Hz 206. 207. 24位或者32位色选择LTDC输出15MHz,16位或者8位30MHz 208. */ 209. PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC; 210. PeriphClkInitStruct.PLLSAI.PLLSAIN = 420; 211. PeriphClkInitStruct.PLLSAI.PLLSAIR = 7; 212. PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_2; 213. if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) 214. { 215. Error_Handler(__FILE__, __LINE__); 216. } 217. break; 218. 219. case LCD_70_1024X600: /* 7.0寸 1024 * 600,未使用 */ 220. Width = 1024; 221. Height = 600; 222. 223. HSYNC_W = 2; 224. HBP = 157; 225. HFP = 160; 226. VSYNC_W = 2; 227. VBP = 20; 228. VFP = 12; 229. 230. break; 231. 232. default: /* 未使用 */ 233. Width = 800; 234. Height = 480; 235. 236. HSYNC_W = 80; 237. HBP = 10; 238. HFP = 10; 239. VSYNC_W = 10; 240. VBP = 10; 241. VFP = 10; 242. 243. break; 244. } 245. 246. g_LcdHeight = Height; 247. g_LcdWidth = Width; 248. 249. /* 配置信号极性 */ 250. hLTDC.Init.HSPolarity = LTDC_HSPOLARITY_AL; /* HSYNC 低电平有效 */ 251. hLTDC.Init.VSPolarity = LTDC_VSPOLARITY_AL; /* VSYNC 低电平有效 */ 252. hLTDC.Init.DEPolarity = LTDC_DEPOLARITY_AL; /* DE 低电平有效 */ 253. hLTDC.Init.PCPolarity = LTDC_PCPOLARITY_IPC; 254. 255. /* 时序配置 */ 256. hLTDC.Init.HorizontalSync = (HSYNC_W - 1); 257. hLTDC.Init.VerticalSync = (VSYNC_W - 1); 258. hLTDC.Init.AccumulatedHBP = (HSYNC_W + HBP - 1); 259. hLTDC.Init.AccumulatedVBP = (VSYNC_W + VBP - 1); 260. hLTDC.Init.AccumulatedActiveH = (Height + VSYNC_W + VBP - 1); 261. hLTDC.Init.AccumulatedActiveW = (Width + HSYNC_W + HBP - 1); 262. hLTDC.Init.TotalHeigh = (Height + VSYNC_W + VBP + VFP - 1); 263. hLTDC.Init.TotalWidth = (Width + HSYNC_W + HBP + HFP - 1); 264. 265. /* 配置背景层颜色 */ 266. hLTDC.Init.Backcolor.Blue = 0; 267. hLTDC.Init.Backcolor.Green = 0; 268. hLTDC.Init.Backcolor.Red = 0; 269. 270. hLTDC.Instance = LTDC; 271. 272. /* 开始配置图层 ------------------------------------------------------*/ 273. /* 窗口显示区设置 */ 274. pLayerCfg.WindowX0 = 0; 275. pLayerCfg.WindowX1 = Width; 276. pLayerCfg.WindowY0 = 0; 277. pLayerCfg.WindowY1 = Height; 278. 279. /* 配置颜色格式 */ 280. pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565; 281. 282. /* 显存地址 */ 283. pLayerCfg.FBStartAdress = FrameBufer; 284. 285. /* Alpha常数 (255 表示完全不透明) */ 286. pLayerCfg.Alpha = 255; 287. 288. /* 无背景色 */ 289. pLayerCfg.Alpha0 = 0; /* 完全透明 */ 290. pLayerCfg.Backcolor.Blue = 0; 291. pLayerCfg.Backcolor.Green = 0; 292. pLayerCfg.Backcolor.Red = 0; 293. 294. /* 配置图层混合因数 */ 295. pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA; 296. pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA; 297. 298. /* 配置行列大小 */ 299. pLayerCfg.ImageWidth = Width; 300. pLayerCfg.ImageHeight = Height; 301. 302. /* 配置LTDC */ 303. if (HAL_LTDC_Init(&hLTDC) != HAL_OK) 304. { 305. /* 初始化错误 */ 306. Error_Handler(__FILE__, __LINE__); 307. } 308. 309. /* 配置图层1 */ 310. if (HAL_LTDC_ConfigLayer(&hLTDC, &pLayerCfg, LTDC_LAYER_1) != HAL_OK) 311. { 312. /* 初始化错误 */ 313. Error_Handler(__FILE__, __LINE__); 314. } 315. } 316. 317. }
如果使用ARGB8888颜色格式要配置为LTDC_PIXEL_FORMAT_ARGB8888。
下面说一个最重要的问题,配置好时序了,怎么检查自己的配置是否成功了?用户仅需在函数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;
如果背景层可以正常显示红色,说明引脚和时序配置都是没有问题的。如果不成功要从以下几个方面着手检查:
本小节的实现基于本教程的第5章,当前驱动对电阻触摸芯片STMPE811和电容触摸芯片FT5X06、GT911和GT811的显示屏都进行了支持。
实现比较简单,因为GUIX的触摸分按下,松手和移动三个事件,正好这几款触摸芯片的驱动也是分这三个事件,所以仅需修改下函数TOUCH_PutKey,所有显示屏触摸就都可以完美融合了。
文件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数值一致。
电阻触摸和电容触摸的扫描函数是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); } }
通过前面的讲解,移植触摸驱动到自己的板子上,最简单的办法是将开发板与触摸相关的文件全部移植过来,然后在这些文件的基础上进行修改。下面分两种情况进行说明:
GUI的底层接口函数和配置的实现在我们第1步中添加的底层驱动接口文件中:
这两个文件的实现套路是一样的,我们这里以gx_display_driver_stm32f4_565rgb文件为例进行说明。
文件开头的宏定义GX_CHROMEART_ENABLE用于使能DMA2D加速,测试阶段可以将其关闭掉,关闭方法就是注释掉宏定义#define GX_CHROMEART_ENABLE即可。
LCD的显存地址设置是通过宏定义配置:
#define FrameBufer SDRAM_LCD_BUF1
其中SDRAM_LCD_BUF1的定义是在bsp_fmc_sdram.h,即:
#define EXT_SDRAM_ADDR ((uint32_t)0xC0000000) #define EXT_SDRAM_SIZE (16 * 1024 * 1024) /* LCD显存,第1页, 分配2M字节 */ #define SDRAM_LCD_BUF1 EXT_SDRAM_ADDR
宏定义FrameBufer是在函数LCD_LL_Init里面配置显存地址的时候被调用。
这个函数是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. }
#define STM32_SCREEN_HANDLE 0xC0000000
本章10.8.1小节里面的用到的0xC0000000就是从这里来的,务必保证匹配。
通过DMA2D加速的几个函数如下(这几个函数,大家做移植的话,仅需注意变量g_LcdWidth和g_LcdHeight正确赋值了):
这里我们以函数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。
当前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 } }
介绍完了前面几步,剩下就是添加应用代码了。为了方便起见,大家直接使用本章教程配套例子里面整理好的即可
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. }
动态内存和Canvas画布配置都放在了文件App_SysFunctions.c文件里面实现。
1. /* 2. ****************************************************************************************************** 3. * 动态内存分配 4. ****************************************************************************************************** 5. */ 6. #define GUI_NUMBYTES 1024*1024*8 /* 设置动态内存大小 */ 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. }
教程配套的STM32F429板子有16MB的SDRAM空间。
使用GUIX Studio生成了4个文件:
后面章节再为大家介绍GUIX Studio生成的这几个文件。作为移植,大家进行将其加到移植工程里面验证是否正常即可。
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(); }
初始化了GUIX后,会自动创建一个GUIX任务,对于这点,大家移植的使用要特别注意。此任务的优先级和任务堆栈大小是在gx_port.h文件里面定义的。
如果大家调试状态下或者刚下载GUIX的程序到STM32H7/STM32F429里面时,出现屏幕会闪烁,或者说抖动,这个是正常现象。详见此贴的说明:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=16892 。
如果显示屏长时间处于抖动状态,说明LTDC的时钟配置高了或者低了(高的概率居多),可以将LTDC时钟降低一半或者提高一倍进行测试。配置方法看本教程第4章的4.4.3小节。
大家使用显示屏的时候,这两个问题很容易遇到,这里为大家提供个解决办法,提升用户体验。
这个问题并不是软件配置造成的,通过条件PWM背光也是无法解决的。解决办法是板子上后,先不要开启PWM,延迟200ms后再打开LCD的背光即可,注意时间不可以太短,太短没效果,大家可以根据实际情况做条件。本章配套程序是放在bsp.c文件的函数bsp_Init里面做了处理。
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 省略未写 */ /* 延迟200ms再点亮背光,避免瞬间高亮 */ bsp_DelayMS(200); LCD_SetBackLight(255); }
有时候界面设计比较复杂时,开机后不能保证所有的控件同时加载出来,界面会有种撕裂的感觉,这个时候有个比较好的解决思路,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); } }
(注,如果是电阻屏,需要做触摸校准,校准方法看本教程附件章节A)
本章节配套了如下几个例子供大家移植参考:
ThreadX内核模板,用于大家移植GUIX的参考Demo,含有GCC,IAR,MDK AC5和AC6四个版本工程。
GUIX模板例子,采用的硬件RGB565接口,,含有GCC,IAR,MDK AC5和AC6四个版本工程。
GUIX模板例子,采用的硬件ARGB8888接口,含有GCC,IAR,MDK AC5和AC6四个版本工程。
GUIX Studio工程模板,设计界面后,生成的文件可直接添加到MDK,IAR和GCC软件平台使用。
显示效果如下,800*480分辨率:
IAR,MDK AC5和AC6工程可以串口打印任务执行情况:按开发板的按键K1可以打印,波特率 115200,数据位 8,奇偶校验位无,停止位 1:
Embedded Studio(GCC)平台的串口打印是通过其调试组件SEGGER RTT做的串口打印,速度也非常快,打印效果如下:
展示里面有乱码是因为Embedded Studio不支持中文。
本章节为大家讲解的内容涉及到的知识较多,信息量较大,部分知识点没有弄明白是没有关系的,但是一定要掌握ThreadX内核和ThreadX GUIX工程的框架设计,掌握后再分析细节,事半功倍。
如果可以的话,最好移植一个与教程配套开发板不一样的显示屏和触摸IC,这样对于本章的认识将更加全面。