LVGL 模拟器

安装设置

Ubuntu24.04 VSCode

源码地址:源码地址

我们使用v8版本,拉取源码

git clone --recursive -b release/v8 --single-branch https://github.com/lvgl/lv_port_pc_vscode

安装编译环境和sdl

sudo apt-get update && sudo apt-get install -y build-essential libsdl2-dev

使用vscode开打这个目录, 注意不要直接打开目录,要打开workspace

源码配置

修改Makefile 将LV_DRIVER由原来的X11改成SDL2

#LV_DRIVER          := X11
LV_DRIVER          := SDL2

编译运行

按F5直接编译并运行,或者使用make来编译

修改模拟屏幕的分辨率

lv_drv_conf.h 98 和 99行的 SDL_HOR_RES SDL_VER_RES 修改分辨率

注意: 修改完后,需要make clean 彻底清除后再编译。

编写自己的项目代码

以简单计算器为例

首先,在项目目录下新建一个calc文件夹,用于存放我们的h和c文件。 在文件夹下建立lv_demo_calc.h和lv_demo_calc.c,内容如下

lv_demo_calc.h:

#ifndef LV_DEMO_CALC_H
#define LV_DEMO_CALC_H

#ifdef __cplusplus
extern "C" {
#endif

void lv_demo_calc(void);

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif // LV_DEMO_CALC_H

lv_demo_calc.c

#include "lvgl.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CALC_BTN_ROW 5
#define CALC_BTN_COL 4

static lv_obj_t *ta; // 显示
static char exp_buf[64];

static const char *btn_map[CALC_BTN_ROW][CALC_BTN_COL] = {
    {"7", "8", "9", "/"},
    {"4", "5", "6", "*"},
    {"1", "2", "3", "-"},
    {"0", ".", "=", "+"},
    {"C", NULL, NULL, NULL},
};

typedef struct {
    double last;
    double curr;
    char op;
    bool op_set;
    bool new_input;
} calc_ctx_t;

static calc_ctx_t ctx;

static void update_text_area() {
    lv_label_set_text(ta, exp_buf);
}

// 按钮事件回调
static void btn_event_cb(lv_event_t *e) {
    lv_obj_t *btn = lv_event_get_target(e);
    lv_obj_t *label = lv_obj_get_child(btn, 0);
    const char *txt = lv_label_get_text(label);

    if (strcmp(txt, "C") == 0) {
        memset(exp_buf, 0, sizeof(exp_buf));
        ctx.last = 0;
        ctx.curr = 0;
        ctx.op = 0;
        ctx.op_set = false;
        ctx.new_input = false;
        update_text_area();
        return;
    }
    if (strcmp(txt, "=") == 0) {
        if(ctx.op_set) {
            double res = 0;
            switch(ctx.op) {
                case '+': res = ctx.last + ctx.curr; break;
                case '-': res = ctx.last - ctx.curr; break;
                case '*': res = ctx.last * ctx.curr; break;
                case '/': res = (ctx.curr != 0) ? ctx.last / ctx.curr : 0; break;
                default: res = ctx.curr; break;
            }
            snprintf(exp_buf, sizeof(exp_buf), "%.10g", res);
            ctx.last = res;
            ctx.op = 0;
            ctx.op_set = false;
            ctx.new_input = true;
            update_text_area();
        }
        return;
    }
    // 运算符
    if(strcmp(txt, "+") == 0 || strcmp(txt, "-") == 0 || strcmp(txt, "*") == 0 || strcmp(txt, "/") == 0) {
        if(!ctx.op_set) {
            ctx.last = strtod(exp_buf, NULL);
            ctx.op = txt[0];
            ctx.op_set = true;
            ctx.new_input = true;
        } else {
            // 连续输入运算符
            double res = 0;
            switch(ctx.op) {
                case '+': res = ctx.last + ctx.curr; break;
                case '-': res = ctx.last - ctx.curr; break;
                case '*': res = ctx.last * ctx.curr; break;
                case '/': res = (ctx.curr != 0)? ctx.last / ctx.curr : 0; break;
            }
            ctx.last = res;
            ctx.op = txt[0];
            ctx.op_set = true;
            snprintf(exp_buf, sizeof(exp_buf), "%.10g", ctx.last);
            ctx.new_input = true;
            update_text_area();
        }
        return;
    }

    // 数字和点
    if(strlen(txt) == 1 && ((txt[0]>='0' && txt[0]<='9') || txt[0]=='.')) {
        if(ctx.new_input) {
            strncpy(exp_buf, txt, sizeof(exp_buf)-1);
            exp_buf[sizeof(exp_buf)-1] = '\0';
            ctx.new_input = false;
        } else {
            if(strlen(exp_buf) < sizeof(exp_buf)-1) {
                strncat(exp_buf, txt, 1);
                exp_buf[sizeof(exp_buf)-1] = '\0';
            }
        }
        ctx.curr = strtod(exp_buf, NULL);
        update_text_area();
        return;
    }
}

void lv_demo_calc(void)
{
    // 主容器
    lv_obj_t *cont = lv_obj_create(lv_scr_act());
    lv_obj_set_size(cont, 800, 480);
    lv_obj_center(cont);
    lv_obj_set_style_pad_all(cont, 0, 0);

    // 只读文本“区域”: 替换为label
    ta = lv_label_create(cont);
    lv_obj_set_size(ta, 770, 60);
    lv_obj_set_pos(ta, 15, 15);
    lv_label_set_text(ta, "");
    lv_obj_set_style_text_font(ta, &lv_font_montserrat_28, 0);
    lv_obj_set_style_text_align(ta, LV_TEXT_ALIGN_RIGHT, 0);

    // 按钮区,每个按钮宽170高62
    int start_x = 15, start_y = 90;
    int btn_w = 170, btn_h = 62, btn_space = 6;
    for(int r = 0; r < CALC_BTN_ROW; ++r) {
        for(int c = 0; c < CALC_BTN_COL; ++c) {
            const char *txt = btn_map[r][c];
            if(txt == NULL) continue;
            lv_obj_t *btn = lv_btn_create(cont);
            lv_obj_set_size(btn, btn_w, btn_h);
            lv_obj_set_pos(btn, start_x + c*(btn_w+btn_space), start_y + r*(btn_h+btn_space));
            lv_obj_t *label = lv_label_create(btn);
            lv_label_set_text(label, txt);
            lv_obj_center(label);
            lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
        }
    }

    memset(exp_buf, 0, sizeof(exp_buf));
    memset(&ctx, 0, sizeof(ctx));
    update_text_area();
}

在main.c中 #include "calc/lv_demo_calc.h"

main函数中,原来的lv_demo_widgets(); 注释掉,加上计算器的方法 lv_demo_calc();

lvglsim2

编译运行 运行结果