keil5编译stm32程序例程显示目标未创建 | 您所在的位置:网站首页 › 编译显示目标未创建 › keil5编译stm32程序例程显示目标未创建 |
LittlevGL是一个开源的GUI软件,它是 Gábor Kiss-Vámosi开发和维护的。 LittlevGL 系统概述 LittlevGL 系统包括应用程序,LVGL图形库以及Driver特定驱动程序,其中应用程序创建GUI,并处理特定任务的应用程序。 应用程序可以与图形库进行通信并创建GUI,它包含HAL硬件抽象层接口,用于注册显示和输入设备驱动程序。 Driver 除了应用中用到的特定驱动程序,它包含用于驱动显示的功能,另外可选的是包含读取触摸板或者按键输入驱动程序。 根据MCU的不同,有两种典型的显示硬件设置。一个内置LCD/TFT驱动,另一个没有内置驱动。在这两种情况下,都需要一个帧缓冲区来存储屏幕的当前图像。 如果MCU有TFT/LCD驱动程序,则可以通过RGB接口直接连接MCU液晶显示器。在这种情况下,帧缓冲器可以在内部RAM中(如果MCU有足够的RAM)或者在外部RAM中(如果MCU具有存储器扩展接口)。I.MXRT1050/1060就是这种形式。 另外是采用外部显示控制器,这时MCU没有TFT/LCD驱动接口,则必须使用外部显示控制器(例如SSD1963、SSD1306、ILI9341)。在这种情况下,MCU可以通过并行口、SPI或I2C与显示控制器通信,帧缓冲区通常位于显示控制器中,为MCU节省了大量的RAM。在I.MXRT1010系列中可以采用Flexio接口实现8080总线来连接外部的LCD。 LittlevGL版本说明 目前LittlevGL网站发布的最新版本是v7.4.0。 https://github.com/lvgl/lvgl/blob/master/lvgl.h #define LVGL_VERSION_MAJOR 7 #define LVGL_VERSION_MINOR 4 #define LVGL_VERSION_PATCH 0 在NXP官方最新的SDK包里面已经包含了LittlevGL,下载后在如下目录中可以找到 SDK_2.8.2_EVKB-IMXRT1050\boards\evkbimxrt1050\littlevgl_examples,里面有集成FreeRTOS和裸机的参考例程。 其支持的版本是为v7.0.0。 #define LVGL_VERSION_MAJOR 7 #define LVGL_VERSION_MINOR 0 #define LVGL_VERSION_PATCH 0 移植要点 基本上大部分的MCU都可以运行LittlevGL, 其对资源的需求如下: 名称 最小资源要求 推荐的资源要求 架构 16,32或者64位的处理器 时钟 > 16 MHz > 48 MHz Flash/ROM > 64 kB > 180 kB RAM > 2 kB > 4 kB 栈 > 2 kB > 8 kB 堆 > 2 kB > 8 kB 编译器 C99 或者更新的版本 1. 适配不同的屏幕 a. 修改屏幕分辨率 如果需要根据实际屏幕支持不同大小的分辨率,例如需要支持1024x600的LVDS的液晶屏,需要修改参数为如下设置。在lv_conf_internal.h中修改分辨率大小。 #define LV_HOR_RES_MAX 1024 // (480) #define LV_VER_RES_MAX 600 // (320) 在fsl_elcdif.c中修改LCD屏的配置函数参数如下。 void ELCDIF_RgbModeGetDefaultConfig(elcdif_rgb_mode_config_t *config) { assert(config); memset(config, 0, sizeof(*config)); config->panelWidth = 1024U; config->panelHeight = 600U; config->hsw = 41; config->hfp = 160; //4; config->hbp = 160; //8; config->vsw = 10; config->vfp = 12;//4; config->vbp = 23;//2; config->polarityFlags = kELCDIF_VsyncActiveLow | kELCDIF_HsyncActiveLow | kELCDIF_DataEnableActiveLow |kELCDIF_DriveDataOnFallingClkEdge; config->bufferAddr = 0U; config->pixelFormat = kELCDIF_PixelFormatRGB888; config->dataBus = kELCDIF_DataBus24Bit; } 可以参考LVDS屏幕的时序参数表进行修改,如下图是1024x600的时序表参数。
b.修改显存大小 在单芯片MCU的设计中,不采用外扩RAM的情况下,如何修改I.MXRT的内存大小来适配更大的图像显存。如下为了配置448K TCM的RAM空间,需要在SDK代码里面修改如下部分,在MCUXpresso配置里面设置内存分配大小,OCRAM需要保留最小64K空间,这是由于Boot ROM的要求。
第一步:startup文件修改 不需要配置GPR14寄存器,它和FlexRAM配置无关,只需要添加如下代码到启动文件中。 LDR R0, =0x400ac044 LDR R1, =0xaaaaaaa5 //FlexRAM 配置设置 STR R1,[R0] LDR R0,=0x400ac040 LDR R1,=0x04 LDR R3,[R0] ORR R2,R1,R3 STR R2,[R0] 需要注意的是,在使用前修改FlexRAM的配置,因此推荐将上述代码放在reset handler, NXP也提供了FlexRAM相关的使用应用笔记如下。 https://www.nxp.com/docs/en/application-note/AN12077.pdf 第二步:MPU文件修改 ARM只指定普通内存支持非对齐访问,而默认的SDK中只将芯片出厂默认内存配置为普通内存,因此需要将BOARD_ConfigMPU里面的配置做如下修改。 /* Region 5 setting: Memory with Normal type, not shareable, outer/inner write back */ MPU->RBAR = ARM_MPU_RBAR(4, 0x20000000U); MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 1, 0, ARM_MPU_REGION_SIZE_256KB); /* Region 6 setting: Memory with Normal type, not shareable, outer/inner write back */ MPU->RBAR = ARM_MPU_RBAR(5, 0x20200000U); MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 1, 0, ARM_MPU_REGION_SIZE_512KB);/* Region 5 setting: Memory with Normal type, not shareable, outer/inner write back */ MPU->RBAR = ARM_MPU_RBAR(6, 0x20040000U); MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 1, 0, ARM_MPU_REGION_SIZE_128KB); /* Region 5 setting: Memory with Normal type, not shareable, outer/inner write back */ MPU->RBAR = ARM_MPU_RBAR(7, 0x20060000U); MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 1, 0, ARM_MPU_REGION_SIZE_64KB); /* Region 7 setting: Memory with Normal type, not shareable, outer/inner write back */ MPU->RBAR = ARM_MPU_RBAR(8, 0x20280000U); MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 1, 0, ARM_MPU_REGION_SIZE_64KB); //Flexram 第三步:OCRAM使用的注意事项 如果数据全部放在OCRAM里面,需要注意清Cache,防止在LCD显示的时候,下一帧的数据将下一帧的数据重叠,导致出现花屏或者是屏幕抖动。一般来说,OCRAM用于存储局部变量、堆和栈。I/DTCM(FlexRAM组配置为TCM)可以直接通过CPU访问,绕过一级L1 cache,因此,建议将关键代码和需要快速处理的数据放在TCM,例如向量表等。 2.创建项目 获取LVGL 库文件,LVGL 的图形库可以在 GitHub: https://github.com/lvgl/lvgl下载到。可以克隆它或从GitHub下载该库的最新版本。包含lvgl的图形库目录需要复制到项目中的。 a. 配置文件LVGL有一个配置头文件lv_conf.h,它设置了库的基本参数,禁用未使用的模块和特性、在编译时调整内存缓存区的大小等等。 复制lvgl目录旁边的lvgl/lv_conf_template.h,并将其重命名为lv_conf.h。打开文件并将开头的“if 0”更改为“#if 1”以启用其内容。当然lv_conf.h 也可以复制到其他地方,但是应该将LV_CONF_INCLUDE_SIMPLE定义到编译器选项中(例如,针对gcc编译器 -DLV_CONF_INCLUDE_SIMPLE ),并手动设置include的路径。 在配置文件中,至少检查这三个配置选项,并根据硬件进行修改。LV_HOR_RES_MAX 最大水平分辨率;LV_VER_RES_MAX 最大垂直分辨率;LV_COLOR_DEPTH 颜色深度,8 适用于RG332,16适用于RGB565或者32 用于RGB888 和ARGB8888。 b.初始化为了使用图形库,必须要进行初始化,初始化步骤如下:调用 lv_init(),初始化驱动, 在LVGL中注册显示和输入设备驱动程序, 在中断中每隔x毫秒调用lv_tick_inc(x),以告知所用时间。每隔几毫秒定期调用lv_task_handler()来处理与LVGL相关的任务。更详细的信息可以参考https://github.com/lvgl/lvgl网站链接。 c. 显示接口为了设置显示,lv_disp_buf_t 和lv_disp_drv_t 变量可以被初始化。 lv_disp_buf_t 包含内部图形缓冲区;lv_disp_drv_t 包含回调函数,用于与显示交互并操作与图形相关的内容。 显存lv_disp_buf_t可以初始化为如下: static lv_disp_buf_t disp_buf; static lv_color_t buf_1[MY_DISP_HOR_RES * 10]; static lv_color_t buf_2[MY_DISP_HOR_RES * 10]; lv_disp_buf_init(&disp_buf, buf_1, buf_2, MY_DISP_HOR_RES*10); 关于缓存区的大小,有3个可能的配置。 一个缓冲区:LVGL将屏幕的内容绘制到一个缓冲区中并将其发送到显示器。缓冲区可以比屏幕小。在这种情况下,较大的区域将被重新绘制为多个部分。如果只有小区域发生变化(例如按下按钮),则只有这些区域将被刷新。 两个非屏幕大小的缓冲区(具有两个缓冲区)可以进入一个缓冲区,而另一个缓冲区的内容被发送到后台显示。DMA或其他硬件应将数据传输到LCD上,同时让CPU绘图。这样,显示的渲染和刷新变得并行,可以有效的解决LCD显示断层的问题。与一个缓冲区类似,如果缓冲区小于要刷新的区域,LVGL将分块绘制显示内容。 两个屏幕大小的缓冲区。与两个非屏幕大小的缓冲区相比,LVGL将始终提供整个屏幕的内容,而不仅仅是块。这样,驱动程序可以简单地将帧缓冲区的地址更改为从LVGL接收的缓冲区。因此,当MCU有LCD/TFT接口并且帧缓冲区只是RAM中的一部分位置时,这种方法效果最好。 一旦缓冲区初始化就绪后,需要初始化显示驱动程序,在最简单的情况下,只需要设置以下两个lv_disp_drv_t字段:一个是指向初始化的lv_disp_buf_t变量的缓冲区指针,以及回调函数flush_cb,用于将缓冲区的内容复制到显示器的特定区域。 hor_res 显示器的水平分辨率。(LV_HOR_RES_MAX默认来自LV_conf.h)。 ver_res垂直分辨率显示。(LV_VER_RES_MAX默认来自LV_conf.h)。 color_chroma_key在色度图像上绘制为透明的颜色。默认情况下,LV_COLOR_TRANSP来自LV_conf.h)。 user_data 用户数据,驱动程序的自定义用户数据。它的类型可以在lv\u conf.h中修改。 anti-aliasing使用抗混叠(边缘平滑)。默认情况下,从LV\u conf.h获取。 rotated 如果设置为1,则交换水平面和垂直面。LVGL在两种情况下绘制的方向相同(从上到下的直线),因此还需要重新配置驱动程序以更改显示的填充方向。 screen_transp如果设置为1,屏幕可以有透明或不透明样式。LV_COLOR_SCREEN_TRANSP需要在LV_conf.h中启用。 要使用GPU,可以使用以下回调:gpu_fill_cb用颜色填充内存中的一个区域;gpu_blend_cb使用不透明度混合两个内存缓冲区。请注意,这些函数需要直接进入内存(RAM),而不是直接显示。 其他一些可选的回调可使单色、灰度或其他非标准RGB显示更轻松、更优化。 rounder_cb圆化要重新绘制的区域的坐标。例如,一个2x2像素可以转换成2x8。如果显示控制器只能刷新具有特定高度或宽度的区域(通常为8倍像素高,单色显示),则可以使用此选项。 set_px_cb设置一个自定义函数来写入显示缓冲区。如果显示器有特殊的颜色格式,它可以用来更紧凑地存储像素。(例如,1位单色、2位灰度等)这样,在lv_disp_buf_t中使用的缓冲器可以更小,以仅保存给定区域大小所需的位数。set_px_cb无法使用两个屏幕大小的缓冲区显示缓冲区配置。 monitor_cb回调函数告诉在多长时间内刷新了多少像素。 要设置lv_disp_drv_t变量的字段,需要用lv_disp_drv_init(&disp_drv)初始化。最后,要为LVGL注册一个显示,需要调用lv_disp_drv_register(&disp_drv)。 总的来说是这样的, lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); /*基本的初始化*/ disp_drv.buffer = &disp_buf; /*设置初始化缓存*/ disp_drv.flush_cb = my_flush_cb; /*设置刷新回调以绘制到显示屏上*/ lv_disp_t * disp; disp = lv_disp_drv_register(&disp_drv); /*注册驱动,保存创建显示对象*/ 如下是简单的回调例子, void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p){ /*将所有像素逐个显示在屏幕上*/ int32_t x, y; for(y = area->y1; y y2; y++) { for(x = area->x1; x x2; x++) { put_px(x, y, *color_p) color_p++; } } lv_disp_flush_ready(disp);} /* 告知图形库,可以准备刷新了*/ void my_gpu_fill_cb(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, const lv_area_t * dest_area, const lv_area_t * fill_area, lv_color_t color);{ uint32_t x, y; dest_buf += dest_width * fill_area->y1; /*跳到第一行*/ for(y = fill_area->y1; y y2; y++) { for(x = fill_area->x1; x x2; x++) { dest_buf[x] = color; } dest_buf+=dest_width; /*跳到下一行*/ }} void my_gpu_blend_cb(lv_disp_drv_t * disp_drv, lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa){ uint32_t i; for(i = 0; i /* 更新所需的区域 */ area->y1 = area->y1 & 0x07; area->y2 = (area->y2 & 0x07) + 8;} void my_set_px_cb(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa){ /* 根据需求,写入到缓存区用于显示*/ buf += buf_w * (y >> 3) + x; if(lv_color_brightness(color) > 128) (*buf) |= (1 data->point.x = touchpad_x; data->point.y = touchpad_y; data->state = LV_INDEV_STATE_PR or LV_INDEV_STATE_REL; return false; /*没有缓存了,因此没有更多的数据可被读取*/ } 触摸板驱动程序必须返回上次的X/Y坐标,即使状态是LV_INDEV_STATE_REL,要设置鼠标光标,则使用 lv_indev_set_cursor(my_indev, &img_cursor),(my_indev 是lv_indev_drv_register的返回值)。 为了使用按键或者键盘,注册 read_cb 函数为LV_INDEV_TYPE_KEYPAD 类型,在 lv_conf.h 中使能LV_USE_GROUP ,必须创建一个对象组: lv_group_t * g = lv_group_create(),对象必须使用 lv_group_add_obj(g, obj)进行添加。必须将创建的组分配给输入设备lv_indev_set_group(my_indev, g) (my_indev 是lv_indev_drv_register的返回值), 使用LV_KEY_... 在组中的对象之间作为引导,在 lv_core/lv_group.h 中查看可用的按键。 indev_drv.type = LV_INDEV_TYPE_KEYPAD; indev_drv.read_cb = keyboard_read; bool keyboard_read(lv_indev_drv_t * drv, lv_indev_data_t*data){ data->key = last_key(); /*获取上一次按下或者释放的按键*/ if(key_pressed()) data->state = LV_INDEV_STATE_PR; else data->state = LV_INDEV_STATE_REL; return false; /*没有缓存了,因此没有更多的数据可被读取*/ } e. Tick接口LVGL需要一个系统时钟来知道动画和其他任务的运行时间。需要定期调用lv_tick_inc(tick_period)函数,并以毫秒为单位。例如,每毫秒呼叫一次。lv_tick_inc应该在比lv_task_handler()优先级更高的程序中调用(例如在中断中),以精确地知道所用的毫秒数,即使lv_task_handler的执行需要更长的时间。使用FreeRTOS,可以在vApplicationTickHook中调用lv_tick_inc。目前官方SDK2.8的SDK里面采用的是Systick作为系统嘀嗒,也可以采用PIT等其他定时器来实现。 void * tick_thread (void *args){ while(1) { usleep(5*1000); lv_tick_inc(5); }} uint32_t lv_tick_get(void) 获取启动后所用的毫秒数。 uint32_t lv_tick_elaps(uint32_t prev_tick) 获取上一次时间戳以后所经过的毫秒数。在基于Linux或者uclinux的操作系统上,可以在以下线程中调用lv_tick_inc: void * tick_thread (void *args){ while(1) { usleep(5*1000); lv_tick_inc(5); }} f. 任务处理 为了处理 LVGL任务,需要在如下其中一个函数中周期性的调用lv_task_handler()。在while(1) 的main() 函数中;在周期性的定时中断中;在OS任务中。为了保持系统的响应,建议在5ms左右调度一次。例如: while(1) { lv_task_handler(); my_delay_ms(5); } g. 睡眠管理当没有外部输入事件发生的时候,MCU可以进入睡眠,可以参考如下例程: while(1) { if(lv_disp_get_inactive_time(NULL) < 1000) { lv_task_handler(); } else { timer_stop(); /*停止定时器,调用lv_tick_inc()*/ sleep();/*MCU睡眠*/ } my_delay_ms(5);} 需要在读输入设备函数中添加如下代码,以处理按键,触摸事件唤醒。 lv_tick_inc(LV_DISP_DEF_REFR_PERIOD); /*唤醒后强制任务执行*/ timer_start(); /*重启定时器,调用lv_tick_inc()*/ lv_task_handler(); /*调用lv_task_handler() 手动处理唤醒事件*/ 除了lv_disp_get_inactive_time()外,还可检查lv_anim_count_running()来查看是否每个动画是否都已完成。 3.实现旋转功能 在SDK库中,包含了几个关于PXP模块的演示,其中rotate演示介绍了如何使用PXP rotate函数,请参考。此外,图形用户界面库通常提供绘图功能,集成旋转选项,可以显示旋转图像。 发现在实现显示旋转90度或270度时,I.MXRT自带的PXP旋转特性并不完美,因为目标会被分解成一个由子块组成的网格进行旋转,子块为8x8或16x16。如果目标的大小不能被所选的块大小整除,旋转的目标看起来会移动。建议移植LittlevGL的v6.1.1及以上的版本,它支持旋转特性。 欢迎关注公众号: |
CopyRight 2018-2019 实验室设备网 版权所有 |