keil5编译stm32程序例程显示目标未创建 您所在的位置:网站首页 编译显示目标未创建 keil5编译stm32程序例程显示目标未创建

keil5编译stm32程序例程显示目标未创建

2023-08-26 14:44| 来源: 网络整理| 查看: 265

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 实验室设备网 版权所有