【LVGL】学习笔记--(1)Keil中嵌入式系统移植LVGL

news2025/7/17 17:08:19

一 LVGL简介

最近emwin用的比较烦躁,同时被LVGL酷炫的界面吸引到了,所以准备换用LVGL试试水。

LVGL(轻量级和通用图形库)是一个免费和开源的图形库,它提供了创建嵌入式GUI所需的一切,具有易于使用的图形元素,美丽的视觉效果和低内存占用。

【1】主要特性

  1. 丰富且强大的模块化图形组件:按钮 (buttons)、图表 (charts)、列表 (lists)、滑动条 (sliders)、图片 (images) 等

  1. 高级的图形引擎:动画、抗锯齿、透明度、平滑滚动、图层混合等效果

  1. 支持多种输入设备:触摸屏、 键盘、编码器、按键等

  1. 支持多显示设备

  1. 不依赖特定的硬件平台,可以在任何显示屏上运行

  1. 配置可裁剪(最低资源占用:64 kB Flash,16 kB RAM)

  1. 基于UTF-8的多语种支持,例如中文、日文、韩文、阿拉伯文等

  1. 可以通过类CSS的方式来设计、布局图形界面(例如:Flexbox、Grid)

  1. 支持操作系统、外置内存、以及硬件加速(LVGL已内建支持STM32 DMA2D、NXP PXP和VGLite)

  1. 即便仅有单缓冲区(frame buffer)的情况下,也可保证渲染如丝般顺滑

  1. 全部由C编写完成,并支持C++调用

  1. 支持Micropython编程,参见:LVGL API in Micropython

  1. 支持模拟器仿真,可以无硬件依托进行开发

  1. 丰富详实的例程

  1. 详尽的文档以及API参考手册,可线上查阅或可下载为PDF格式

  1. 在 MIT 许可下免费和开源

【2】配置要求

【3】与其他显示工具的对比

二 LVGL移植前准备

【1】参考资料

  1. LVGL官网:https://lvgl.io/

  1. LVGL源码:https://gitee.com/my_lvgl/lvgl

  1. 百问网LVGL中文开发手册:http://lvgl.100ask.net/8.2/index.html

  1. 一些关于移植的经验资料:

LITTLEVGL (LVGL)入门之移植到STM32芯片

LVGL学习笔记 | 02 - 移植LVGL 8.2到STM32F407开发板(MCU屏)

STM32移植LVGL(LittleVGL)

在STM32F4上移植LVGL8.2

【2】硬件资源

硬件资源包括:

  1. 嵌入式平台(我用的MCU是华大的HC32F460芯片)

  1. 实现了屏幕驱动的液晶屏(我用的是3.2寸320*240的8080并口屏,屏幕驱动可以移步【嵌入式】MCU(HC32F460)+并口LCD液晶屏ILI9341 移植emWin记录)

  1. 输入设备(非必要,包括触摸屏、鼠标、键盘、编码器、按键,我自己使用的是磁控旋钮编码器)

【3】其他

推荐一款不错的LVGL设计工具GUI Guider,是由恩智浦开发的,通过NXP官网(https://www.nxp.com.cn/design/software/development-software/gui-guider:GUI-GUIDER)即可下载,具体使用后面有机会专门写一篇文章介绍。

准备好了以上的内容,接下来就可以进行移植了。

三 LVGL移植

【1】源码下载

首先下载LVGL源码,链接见上一章的参考资料。有很多版本,我这边没有选择最新的版本,而是用了V1.5.0-GA版本GUI Guider支持的V8.3.2版本:

下载完解压后是这样的:

【2】源码添加工程

  1. 将上面解压完成的lvgl-v8.3.2文件夹中的下列文件复制到自己keil工程自建文件夹GUI_LVGL中:

  1. 复制进来后,删除proting文件夹以及lv_conf_template文件中_template后缀:

  1. 在keil中建立管理目录:

其中:

  • GUI/LVGL用来存放src及其子目录中所有.c文件。这一步比较考验耐心和细致,每一个子目录下都有不少源文件。(要是不想自己keil工程中有许多看起来凌乱的源文件,可以参考STM32/keil把多个c文件编译为静态库lib中的方法,预先将这许许多多的.c文件编译为lib库,那么GUI/LVGL中放一个lib文件即可。但是这种方法要注意创建lib的工程和使用lib的工程芯片型号要一致才可以)。我是使用的lib库的形式。

  • GUI/LVGL/PORT用来存放porting下的三个源文件,分别是lv_port_disp.c(显示接口文件)、lv_port_indev(输入设备接口文件)、lv_port_fs(文件系统接口文件)

  • GUI/LVGL用来存放GUI Guider生成的图形界面文件,这边暂时还没有用到,可以不用管。

  1. 包含头文件路径:

  1. 最后还有两个重要的小操作,一个是要选用C99 Mode:

另一个是加大栈空间,方法是打开.s启动文件,修改其中的Stack_Size值,我这边配置的是8KB(根据上面LVGL运行配置要求的推荐值确定)

至此,可以编译一下,由于还没有使用显示接口等,所以这边应该是编译无误的。

【3】添加心跳接口

在timer.c中包含头文件"lvgl.h",并在1ms定时器中调用lv_tick_inc(1)启用心跳:

/**************************************************************************
* 函数名称: TimeraUnit2_IrqCallback
* 功能描述: 定时器A 单元2初始化
* 输入参数: 
* 输出参数: 
* 返 回 值: 
* 其它说明: 1ms定时器中断
**************************************************************************/
static void TimeraUnit2_IrqCallback(void)
{
    lv_tick_inc(1);
    TIMERA_ClearFlag(TIMERA_UNIT2, TimeraFlagOverflow);
}

当然还有其他方式让LVGL开始心跳,这个可以自行选用,控制心跳周期为1ms即可。

【4】显示接口对接屏幕驱动

  1. 修改lv_conf.h头文件中的#if 0为#if 1,同时根据适配的显示设备设置颜色格式:

补充,如果想屏幕刷新快一点,可以将FPS做如下调整(30ms-->10ms,即FPS由30-->100):

  1. 把lv_port_disp.c、lv_port_disp.h、lv_port_indev.c、lv_port_indev.h四个文件的#if 0 都改成#if 1 ,这四个文件包含的头文件名字还需根据编译报错信息修改。:

注意,若头文件#include "lvgl/lvgl.h"包含报错,可以添加宏定义LV_CONF_INCLUDE_SIMPLE

  1. 修改lv_port_disp.c中适配底层屏幕驱动分辨率(我用的就是320*240):

/*********************
 *      DEFINES
 *********************/
#ifndef MY_DISP_HOR_RES
    #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now.
    #define MY_DISP_HOR_RES    320
#endif

#ifndef MY_DISP_VER_RES
    #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen height, default value 240 is used for now.
    #define MY_DISP_VER_RES    240
#endif
  1. 修改lv_port_disp.c中lv_port_disp_init函数,该函数包含两个部分,一个是初始化disp_init(),一个是绘制缓冲区

其中初始化函数disp_init()中加入自己的屏幕初始化程序:

void LCD_AllInit(void)
{
    LCD_InitGPIO();  //初始化几个GPIO口,包括CS\RS\WR\RD\RES\DB0-15
    
    LCD_HardwareReset();  //LCD复位

    LCD_ConfigureREG();   //RGB配置初始化
    
    LCD_Clear(WHITE);  //清屏白色

    LCD_DisplayDir(data_inter.showDirect);  //默认为横屏
}

static void disp_init(void)
{
    /*You code here*/
    LCD_AllInit();
}
  1. 修改lv_port_disp.c中lv_port_disp_init函数绘制缓冲区的部分,删除不用的Example for 2/3,只留Example for 1即可:

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();

    /*-----------------------------
     * Create a buffer for drawing
     *----------------------------*/

    /**
     * LVGL requires a buffer where it internally draws the widgets.
     * Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.
     * The buffer has to be greater than 1 display row
     *
     * There are 3 buffering configurations:
     * 1. Create ONE buffer:
     *      LVGL will draw the display's content here and writes it to your display
     *
     * 2. Create TWO buffer:
     *      LVGL will draw the display's content to a buffer and writes it your display.
     *      You should use DMA to write the buffer's content to the display.
     *      It will enable LVGL to draw the next part of the screen to the other buffer while
     *      the data is being sent form the first buffer. It makes rendering and flushing parallel.
     *
     * 3. Double buffering
     *      Set 2 screens sized buffers and set disp_drv.full_refresh = 1.
     *      This way LVGL will always provide the whole rendered screen in `flush_cb`
     *      and you only need to change the frame buffer's address.
     */

    /* Example for 1) */
    static lv_disp_draw_buf_t draw_buf_dsc_1;
    static lv_color_t buf_1[MY_DISP_HOR_RES * 10];                          /*A buffer for 10 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/

    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/

    static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = MY_DISP_HOR_RES;
    disp_drv.ver_res = MY_DISP_VER_RES;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;

    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_1;

    /*Required for Example 3)*/
    //disp_drv.full_refresh = 1;

    /* Fill a memory array with a color if you have GPU.
     * Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL.
     * But if you have a different GPU you can use with this callback.*/
    //disp_drv.gpu_fill_cb = gpu_fill;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

补充,如果想屏幕刷新快一点,在SRAM足够的条件下,可以修改默认的buf_1[MY_DISP_HOR_RES * 10]为buf_1[MY_DISP_HOR_RES * MY_DISP_VER_RES / 4]:

static lv_disp_draw_buf_t draw_buf_dsc_1;
    static lv_color_t buf_1[MY_DISP_HOR_RES * MY_DISP_VER_RES / 4];                          /*A buffer for 10 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * MY_DISP_VER_RES / 4);   /*Initialize the display buffer*/

  1. 修改lv_port_disp.c中的刷屏函数disp_flush,默认的刷屏用打点的方式速度太慢,改用刷块的方式:

void LCD_Fill_LVGL(U16 sx,U16 sy,U16 ex,U16 ey,U16 *color)
{
    uint16_t height, width;
    uint16_t i, j;

    width = ex - sx + 1;            /* 得到填充的宽度 */
    height = ey - sy + 1;           /* 高度 */

    for (i = 0; i < height; i++)
    {
        LCD_SetCursor(sx, sy + i); /* 设置光标位置 */
        LCD_WriteRAM_Prepare();    /* 开始写入GRAM */

        for (j = 0; j < width; j++)
            LCD_WriteRAM(color[i * width + j]); /* 写入数据 */
    }
} 

/*Flush the content of the internal buffer the specific area on the display
 *You can use DMA or any hardware acceleration to do this operation in the background but
 *'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    LCD_Fill_LVGL(area->x1,area->y1,area->x2,area->y2,(U16 *)color_p);

    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

至此,显示接口对接屏幕驱动完成。

【5】输入设备接口对接编码器

如果没有使用到诸如触摸屏、鼠标、键盘、编码器、按键这几类的外部设备,只要显示界面不用操作的话,可以跳过这一节,直接到LVGL测试章节继续。

我这边用到的是磁控旋钮编码器外设

  1. 修改lv_port_indev.c中lv_port_indev_init函数,包含两个部分,一个是初始化encoder_init(),一个是注册编码器外部设备

其中初始化函数encoder_init()中加入自己的屏幕初始化程序:

/*Initialize your keypad*/
extern STRU_KNOB knob_old;
extern char knob_state_now;
static void encoder_init(void)
{
    /*Your code comes here*/
    MLX_InitGPIO();
    MLX_InitSPI();
    knob_old = knobPosInit();  //旋钮位置初始化
}
  1. 删除不用的外部设备,只留下编码器作为唯一外部输入:

void lv_port_indev_init(void)
{
    /**
     * Here you will find example implementation of input devices supported by LittelvGL:
     *  - Touchpad
     *  - Mouse (with cursor support)
     *  - Keypad (supports GUI usage only with key)
     *  - Encoder (supports GUI usage only with: left, right, push)
     *  - Button (external buttons to press points on the screen)
     *
     *  The `..._read()` function are only examples.
     *  You should shape them according to your hardware
     */

    static lv_indev_drv_t indev_drv;
    
    /*------------------
     * Encoder
     * -----------------*/

    /*Initialize your encoder if you have*/
    encoder_init();

    /*Register a encoder input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_ENCODER;
    indev_drv.read_cb = encoder_read;
    indev_encoder = lv_indev_drv_register(&indev_drv);

    /*Later you should create group(s) with `lv_group_t * group = lv_group_create()`,
     *add objects to the group with `lv_group_add_obj(group, obj)`
     *and assign this input device to group to navigate in it:
     *`lv_indev_set_group(indev_encoder, group);`*/
}
  1. 注册外部设备,修改encoder_read函数,encoder_handler可以不用管,encoder_diff的加减表示左旋和右旋,encoder_state赋值LV_INDEV_STATE_PR或者LV_INDEV_STATE_REL表示按下或松开

/*Will be called by the library to read the encoder*/
static void encoder_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    if(knob_state_now == 9 || knob_state_now == 15)  //按下动作和按下状态都算按下
        encoder_state = LV_INDEV_STATE_PR;   //按下
    else
        encoder_state = LV_INDEV_STATE_REL;  //松开
    
    if(knob_state_now == 19)
        encoder_diff ++;
    else if(knob_state_now == 17)
        encoder_diff --;

    data->enc_diff = encoder_diff;
    data->state = encoder_state;
    encoder_diff = 0;
}

knob_state_now表示当前旋钮的状态,其状态的获得在主循环中获取,将其映射到encoder_diff和encoder_state这两个变量

#define KNOB_ESCAPE         8   //旋钮抬起状态,xy不变
#define KNOB_UP             17  //z不变,正向旋转
#define KNOB_DOWN           19  //z不变,反向旋转
#define KNOB_ENTER_BEF      15  //按下动作

四 LVGL测试

至此便完成了LVGL的显示接口以及输入设备接口的移植。

在主任务中添加上面的初始化,这边简单做了一个小界面方便展示:

#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
static void lv_ex_label_1(void)
{
    lv_obj_t * label2 = lv_label_create(lv_scr_act());
    lv_label_set_recolor(label2, true);
    lv_label_set_long_mode(label2, LV_LABEL_LONG_SCROLL_CIRCULAR); /*Circular scroll*/
    lv_obj_set_width(label2, 120);
    // Hello world ! Trisuborn.
    lv_label_set_text(label2, "#ff0000 Hello# #00ff00 world ! Trisuborn.#");
    lv_obj_align(label2, LV_ALIGN_CENTER, 0, 0);
}

void GUI_Task(void)
{
    //LVGL初始化
    lv_init();
    //显示器初始化
    lv_port_disp_init();
    //外部输入初始化
    lv_port_indev_init();
    //界面生成
    lv_ex_label_1();

    while(1)
    {
        knob_now = knobPosNow();  //确定旋钮当前位置
        knob_state_now = Knob_State_Entry(&knob_old, &knob_now, knob_state_old);  //确定旋钮当前状态
        if(KNOB_ESCAPE == knob_state_now)
        {
            knob_state_old = knob_state_now;
        }
        else
        {
            knob_old = knob_now;
            knob_state_old = knob_state_now;
        }
        lv_task_handler();
        os_dly_wait(5);
    }
}

效果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/369113.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

极光笔记 | 埋点体系建设与实施方法论

PART 01 前 言随着网络技术的发展&#xff0c;从粗犷型到精细化运营型&#xff0c;再到现在的数字化运营&#xff0c;数据变得越来越细分和重要&#xff0c;不仅可以进行策略调整&#xff0c;还可以实现自动化的精细化运营。而数据价值的起点就是埋点&#xff0c;只有合理地埋点…

[计算机网络(第八版)]第一章 概述(学习笔记)

1.1 计算机网络在信息时代中的作用 21世纪是以网络为核心的信息时代&#xff0c;21世纪的重要重要特征&#xff1a;数字化、网络化与信息化。 三大类网络 电信网络&#xff1a;向用户提供电话、电报、传真等服务&#xff1b;有线电视网络&#xff1a;向用户传送各种电视节目&am…

zabbix4.0-使用zabbix监控别的主机-使用模板来创建图形

目录 1、 配置zabbix的yum源 2、下载zabbix-agent 3、配置zabbix-agent的配置文件 4、关闭防火墙&#xff0c;selinux 5、重启zabbix-agent 6、连通性测试&#xff0c;在zabbix-server服务器上面使用zabbix_get获取zabbix-agent服务器上的数据 7、在zabbix web端配置zab…

Windows 11 22H2 中文版、英文版 (x64、ARM64) 下载 (updated Feb 2023)

Windows 11, version 22H2&#xff0c;2023 年 2 月 更新 请访问原文链接&#xff1a;https://sysin.org/blog/windows-11/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;www.sysin.org 全新推出 Windows 11 全新 Windows 体验&#x…

考PMP的用处有哪些?备考攻略+资料分享

说到底&#xff0c;考PMP就是为了给工作提供便利&#xff0c;你考了之后会用它&#xff0c;将学习的东西运用到工作中&#xff0c;比如提高项目完成率&#xff0c;能升职加薪啊等等&#xff0c;那自然很是有用的。 不用&#xff0c;那就只是一张当摆设的纸&#xff0c;当然&am…

项目经理如何度量项目?及项目度量指标实例【静说】

度量项目是项目经理的一个重要职责&#xff0c;通过度量项目&#xff0c;项目经理可以了解项目的进展情况&#xff0c;及时发现问题并采取相应的措施&#xff0c;以确保项目能够按时、按质、按预算完成。 分享给大家一些常见的项目度量指标&#xff1a; 1. 项目进度&#xff…

docker-compose 简单配置php和nginx及注意事项

docker-compose.yml内容&#xff1a; /docker/web/config/nginx/conf/default.conf内容&#xff1a; server { listen 80; server_name localhost; root /usr/share/nginx/html; error_log /var/log/nginx/localhost.log; location / { try_files $…

ESP32-FPV-Camera介绍和使用

ESP32-FPV-Camera介绍和使用1. 编译目标2. 编译步骤Step 1 软件配置环境准备Step 2 获取开源代码Step 3 2.4G WiFi频段选择Step 4 要确保2.4G WiFi网卡处于Monitor状态Step 5 修改频点相关代码Step 6 修改WiFi网卡相关代码Step 7 OpenGL 版本问题Step 8 构建天空端Step 9 构建地…

从0开始写Vue项目-Vue实现用户数据批量上传和数据导出

从0开始写Vue项目-环境和项目搭建_慕言要努力的博客-CSDN博客从0开始写Vue项目-Vue2集成Element-ui和后台主体框架搭建_慕言要努力的博客-CSDN博客从0开始写Vue项目-Vue页面主体布局和登录、注册页面_慕言要努力的博客-CSDN博客从0开始写Vue项目-SpringBoot整合Mybatis-plus实现…

04--WXML

1、什么是WXML什么是Wxml呢&#xff1f;我们首先要介绍一下Html&#xff0c;Html的全称为HyperTextMarkup Language&#xff0c;翻译过来就是超文本标记语言&#xff0c;这种语言目前已经普遍用于前端开发&#xff0c;而wxml正是从html演变而来&#xff0c;它基于微信这个平台&…

4EVERLAND:ERC-721 Token的存储选择

4EVERLAND&#xff1a;一个 Web3 基础设施&#xff0c;可促进项目更轻松、更快速地托管前端、存储数据/NFT/文件&#xff0c;并在 IPFS、Arweave 和 Dfinity 之上访问它们。 NFT , 数字所有权 使用以太坊标准的 NFT 创新ERC-721解决了互联网内容的主要问题之一&#xff1a;所…

计算机网络(2)从十六进制的ip数据报中得到详细字段信息

本博文介绍如何将十六进制的ip报文拆分出具体的字段信息。社会计算机网络和网络协议分析的初学者参考&#xff08;今天看了网络协议分析期末复习重点的最后一个大题&#xff0c;竟然一头雾水&#xff0c;然后快马加鞭翻阅各种资料&#xff0c;然后差不多学会 了&#xff09;wir…

RK3568平台开发系列讲解(驱动基础篇)中断子系统框架

🚀返回专栏总目录 文章目录 一、中断硬件的组成二、软件框架三、中断常见概念沉淀、分享、成长,让自己和他人都能有所收获!😄 📢中断是指 CPU 正常运行期间,由于内外部事件或程序预先安排的事件,引起的 CPU 暂时停止正在运行的程序, 转而为该内部或外部预先安排的事…

零基础入门反序列化及常用trick

前言 反序列化是一个尤为重要的知识点&#xff0c;最近对反序列化进行了简单学习&#xff0c;并总结如下&#xff0c;希望能对正在学习的师傅有所帮助。 前提 了解序列化和反序列化 学习反序列化之前&#xff0c;首先要知道序列化&#xff0c;简单的举个栗子来说 <?ph…

C语言学习及复习笔记-【10】数组

10. 字符串 定义&#xff1a;在 C 语言中&#xff0c;字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。因此&#xff0c;一个以 null 结尾的字符串&#xff0c;包含了组成字符串的字符。 以下是 C/C 中定义的字符串的内存表示&#xff1a; 10.1 C 中有大量操作字符串…

Linux 脚本(sh)之 定时清理悬空、指定镜像,自动增长版本号

定时任务(images_clean)&#xff1a; 位置&#xff1a;/mydata/hostmachine_jenkins/images_clean.sh 作用&#xff1a;Jenkins发布之后&#xff0c;遗留下来的老版镜像以及悬空镜像进行定时清理 注意&#xff1a;如果你需要发布新的服务&#xff0c;那么你需要进入当前目录…

Git导出增量包的操作步骤

前言在项目开发部署中&#xff0c;通常是将一个Git项目全量打包发布&#xff0c;但有的场景只需要导出有变更的那部分文件&#xff0c;增量发布&#xff0c;此时就需要使用Git导出增量包了。一、查看提交记录拿到提交ID码①例如使用的gitlab使用方法参考下图(一目了然) 【推荐】…

利用机器学习(mediapipe),进行人手的21个3D手关节坐标检测

感知手的形状和动作的能力可能是在各种技术领域和平台上改善用户体验的重要组成部分。例如,它可以构成手语理解和手势控制的基础,并且还可以在增强现实中将数字内容和信息覆盖在物理世界之上。虽然自然而然地出现在人们手中,但是强大的实时手感知力无疑是一项具有挑战性的计…

【基础算法】字符串哈希

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

逆风翻盘拿下感知实习offer,机会总是留给有准备的人

个人背景211本&#xff0c;985硕&#xff0c;本科是计算机科学与技术专业&#xff0c;研究生是自学计算机视觉方向&#xff0c;本科主要做C和python程序设计开发&#xff0c;java安卓开发&#xff0c;研究生主要做目标检测&#xff0c;现在在入门目标跟踪和3d目标检测。无论文&…