Files

16 KiB

name, description
name description
packer-ui 包装机界面(packer_ui)设计规范与开发指南。包括布局尺寸、颜色体系、字体使用和后续开发指导。适用于800x480 LCD上的LVGL界面。

Packer UI Design Guide

1. 布局总览

屏幕分辨率为 800x480,整体划分为左右两栏:


+--------------------------------------------------------------+

|  +--------------------------+  +--------------------------+  |

|  |    状态卡片 (左)          |  |   启动按钮 (右)           |  |

|  |    x=10,  y=10           |  |   x=580, y=10            |  |

|  |    w=560, h=330          |  |   w=210, h=330           |  |

|  |                          |  |                           |  |

|  | +----------+----------+  |  |    "启动"                 |  |

|  | | 当前重量  | 总包数   |  |  |                           |  |

|  | |          |          |  |  |                           |  |

|  | |  0.0     |    0     |  |  |                           |  |

|  | |          |          |  |  |                           |  |

|  | | 称重斗.. | 累计成品  |  |  |                           |  |

|  | +----------+----------+  |  |                           |  |

|  |   工序 进料      |  o 已停止     |  |                           |  |

|  +--------------------------+  +--------------------------+  |

|  +--------------------------+  +--------------------------+  |

|  |   目标重量卡片 (左下)     |  |   复位按钮 (右下)         |  |

|  |   x=10,  y=350           |  |   x=580, y=350           |  |

|  |   w=560, h=120           |  |   w=210, h=120           |  |

|  |                          |  |                           |  |

|  | 设计重量 190.0  克/包     |  |    复位                   |  |

|  |              o(-)  o(+)  |  |                           |  |

|  +--------------------------+  +--------------------------+  |

+--------------------------------------------------------------+

2. 容器与位置参数

2.1 顶层容器

| 控件 | 创建函数 | x | y | w | h | 圆角 | 背景色 |

|------|---------|---|---|---|---|------|--------|

| packer_scr | lv_obj_create | - | - | 800 | 480 | - | COLOR_BG |

| card_status | lv_obj_create | 10 | 10 | 560 | 330 | 20 | COLOR_CARD |

| card_target | lv_obj_create | 10 | 350 | 560 | 120 | 20 | COLOR_CARD_TARGET |

| btn_start | lv_btn_create | 580 | 10 | 210 | 330 | 20 | COLOR_BTN_START |

| btn_reset | lv_btn_create | 580 | 350 | 210 | 120 | 20 | COLOR_BTN_DARK |

| btn_minus | lv_btn_create | 331 | 30 | 60 | 60 | 30 | COLOR_BTN_ADJ |

| btn_plus | lv_btn_create | 463 | 30 | 60 | 60 | 30 | COLOR_BTN_ADJ |

2.2 状态卡片内部分布

card_status (560x330) 被垂直分割线分成左右两半:

左半区 -- 当前重量 (宽度 281px)

| 控件 | 文本 | 父级x偏移 | 父级y偏移 | 宽 | 高 | 字体 | 颜色 |

|------|------|----------|----------|-----|-----|------|------|

| w_title | "当前重量" | 0 | 14 | 281 | 28 | custom_cjk_20 | COLOR_GREEN |

| label_current_weight | "0.0" | 0 | 90 | 281 | 84 | custom_cjk_60 | COLOR_TEXT_MAIN |

| w_sub | "称重斗实时值" | 0 | 200 | 281 | 20 | custom_cjk_16 | 0x6f9181 |

右半区 -- 总包数 (宽度 278px)

| 控件 | 文本 | 父级x偏移 | 父级y偏移 | 宽 | 高 | 字体 | 颜色 |

|------|------|----------|----------|-----|-----|------|------|

| c_title | "总包数" | 282 | 14 | 278 | 30 | custom_cjk_20 | COLOR_GREEN |

| label_pack_count | "0" | 282 | 90 | 278 | 84 | custom_cjk_60 | COLOR_TEXT_MAIN |

| c_sub | "累计成品" | 282 | 200 | 278 | 20 | custom_cjk_16 | 0x6f9181 |

分割线

| 控件 | 位置(x,y) | 尺寸(w,h) | 颜色 |

|------|----------|----------|------|

| horizontal_line | (40, 240) | (480, 1) | COLOR_BORDER |

| vertical_line | (280, 20) | (1, 290) | COLOR_BORDER |

状态指示

| 控件 | 文本 | 父级x偏移 | 父级y偏移 | 宽 | 高 | 圆角 | 颜色 |

|------|------|----------|----------|-----|-----|------|------|

| status_dot | - | 330 | 270 | 34 | 35 | 17(圆形) | COLOR_GREEN |

| label_status | "已停止" | 420 | 280 | - | - | - | COLOR_TEXT_MAIN, font 20px |

工序状态 (左下, 分割线下方)

标题 "工序" 位于 x=50, y=270, 使用 font 20px, COLOR_GREEN。

当前工序名称紧跟在标题右侧, 使用 font 20px, COLOR_TEXT_MAIN:

| 控件 | 文本(初始) | x | y | 字体 | 颜色 |

|-----|-----------|----|----|------|------|

| proc_title | "工序" | 50 | 270 | custom_cjk_20 | COLOR_GREEN |

| label_step | "进料" | 120 | 270 | custom_cjk_20 | COLOR_TEXT_MAIN |

通过 current_step (0~5) 索引 step_name[] 数组动态切换显示的工序:


static const char *step_name[] = {"进料", "称重", "压缩", "打包", "封包", "出料"};

if (current_step < 6)

    lv_label_set_text(label_step, step_name[current_step]);

2.3 目标重量卡内部布局

card_target (560x120) 从左到右:

| 控件 | 文本 | 父级x偏移 | 父级y偏移 | 宽/尺寸 | 高 | 字体 | 颜色 |

|------|------|----------|----------|---------|-----|------|------|

| t_title | "设计重量" | 0 | 50 | 90 | 120 | custom_cjk_20 | COLOR_GREEN |

| label_target_val | "190.0" | 100 | 30 | 150 | 120 | custom_cjk_60 | COLOR_TEXT_MAIN |

| t_unit | "克/包" | 240 | 50 | - | - | custom_cjk_20 | 0x6f9181 |

| btn_minus | "-" | 331 | 30 | 60x60 | - | custom_cjk_60 | COLOR_TEXT_MAIN |

| btn_plus | "+" | 463 | 30 | 60x60 | - | custom_cjk_40 | COLOR_TEXT_MAIN |

btn_minus 字符"-"用 font 60px, btn_plus 字符"+"用 font 40px, 视觉上调整到大小接近。

3. 颜色体系

3.1 颜色常量定义


COLOR_BG         = 0x161b22    // 深色背景(深蓝黑)

COLOR_CARD       = 0x000000    // 卡片背景(纯黑)

COLOR_CARD_TARGET = 0x080a0e   // 目标重量卡片(近乎纯黑)

COLOR_BORDER     = 0x30363d    // 分割线颜色(暗灰)

COLOR_TEXT_MAIN  = 0xffffff    // 主文字(纯白)

COLOR_TEXT_SEC   = 0x9ab3a5    // 次要文字(薄荷灰)

COLOR_BTN_START  = 0x06990c    // 启动按钮背景(绿色)

COLOR_BTN_DARK   = 0x000000    // 复位按钮背景(纯黑)

COLOR_BTN_ADJ    = 0x1e3c28    // 加减按钮背景(深绿)

COLOR_GREEN      = 0x06990c    // 状态灯/绿色指示(绿色)

COLOR_RED        = 0x990B06    // 红色(深红)

COLOR_ADJ_ICON   = 0xc4c4c4    // 调节图标辅助色(浅灰)

3.2 辅助颜色(直接硬编码)

| 用途 | 颜色值 | 说明 |

|------|--------|------|

| w_sub / c_sub 文字 | 0x6f9181 | 副标题, 茶绿色 |

| t_unit "克/包" | 0x6f9181 | 单位标签, 茶绿色 |

3.3 状态灯逻辑

状态灯(status_dot)是一个 34x35 的圆形, 通过背景色切换运行/停止:

| 状态 | 圆点颜色 | 圆点值 | 文字颜色 |

|------|---------|--------|---------|

| 运行中 | COLOR_GREEN | 0x06990c | COLOR_TEXT_MAIN |

| 已停止 | COLOR_RED | 0x990B06 | COLOR_TEXT_MAIN |

状态灯右侧显示文字 label_status, 内容对应切换: "运行中" / "已停止", 文字始终为白色 (COLOR_TEXT_MAIN)。

4. 字体系统

4.1 字体规格

使用 SimHei(黑体)生成的 LVGL 位图字体, 4bpp 抗锯齿:

| 大小 | 字体变量 | 适用场景 |

|:----:|----------|----------|

| 16px | custom_cjk_16 | 副标题, 辅助说明文字 |

| 20px | custom_cjk_20 | 区域标题, 标签, 单位 |

| 40px | custom_cjk_40 | 按钮文字, 部分符号(+) |

| 60px | custom_cjk_60 | 数值显示, 大字符号(-) |

4.2 字体声明


LV_FONT_DECLARE(custom_cjk_16);

LV_FONT_DECLARE(custom_cjk_20);

LV_FONT_DECLARE(custom_cjk_40);

LV_FONT_DECLARE(custom_cjk_60);

5. 按钮样式规则

所有按钮统一移除阴影, 轮廓, 边框:


// 每个按钮创建后必须执行的样式

lv_obj_set_style_shadow_width(btn, 0, 0);

lv_obj_set_style_shadow_opa(btn, LV_OPA_0, 0);

lv_obj_set_style_outline_width(btn, 0, 0);

lv_obj_set_style_outline_opa(btn, LV_OPA_0, 0);

lv_obj_set_style_border_width(btn, 0, 0);

lv_obj_set_style_border_side(btn, LV_BORDER_SIDE_NONE, 0);

按钮样式差异(仅背景色和圆角不同):

| 按钮 | 圆角 | 背景色 | 文字颜色 | 字体 |

|------|------|--------|---------|------|

| btn_start | 20 | COLOR_BTN_START (绿) | COLOR_TEXT_MAIN (白) | custom_cjk_40 |

| btn_reset | 20 | COLOR_BTN_DARK (黑) | COLOR_TEXT_SEC (灰绿) | custom_cjk_40 |

| btn_minus | 30 | COLOR_BTN_ADJ (深绿) | COLOR_TEXT_MAIN (白) | custom_cjk_60 |

| btn_plus | 30 | COLOR_BTN_ADJ (深绿) | COLOR_TEXT_MAIN (白) | custom_cjk_40 |

6. 卡片通用样式

所有卡片(create_card)默认属性:


lv_obj_set_style_border_width(card, 0, 0);   // 无边框

lv_obj_clear_flag(card, LV_OBJ_FLAG_SCROLLABLE);  // 不可滚动

// 圆角在 create_card 的参数中传入

// 背景色在 create_card 的参数中传入

状态卡片和目标重量卡片额外设置:


lv_obj_set_style_pad_hor(card, 0, LV_PART_MAIN);

lv_obj_set_style_pad_ver(card, 0, LV_PART_MAIN);

lv_obj_set_style_outline_width(card, 0, LV_PART_MAIN);

lv_obj_set_style_border_width(card, 0, LV_PART_MAIN);

7. 回调函数

| 回调 | 绑定的控件 | 触发事件 | 行为 |

|------|-----------|---------|------|

| start_cb | btn_start | LV_EVENT_CLICKED | 设置 is_running=true, 清零 current_weight, 更新显示 |

| reset_cb | btn_reset | LV_EVENT_CLICKED | 设置 is_running=false, 清零 weight 和 pack_count, 更新显示 |

| minus_cb | btn_minus | LV_EVENT_CLICKED | 运行中忽略, 否则 target_weight -= 10 (下限 10), 更新显示 |

| plus_cb | btn_plus | LV_EVENT_CLICKED | 运行中忽略, 否则 target_weight += 10 (上限 2000), 更新显示 |

8. 核心数据结构


// 控件指针(文件内 static)

static lv_obj_t *label_target_val;      // 目标重量值标签

static lv_obj_t *label_current_weight;  // 当前重量值标签

static lv_obj_t *label_pack_count;      // 总包数值标签

static lv_obj_t *label_status;          // 状态文字标签

static lv_obj_t *status_dot;            // 状态指示圆点

static lv_obj_t *label_step;            // 当前工序标签(进料/称重/压缩/打包/封包/出料)



// 状态变量(volatile, 可能被中断/任务修改)

static volatile bool is_running = false;    // 运行状态

static volatile uint8_t current_step = 0;   // 当前工序索引(0~5)

static volatile uint32_t target_weight = 190;  // 目标重量 (单位: 0.1g)

static volatile uint32_t current_weight = 230; // 当前重量 (单位: 0.1g)

static volatile uint32_t pack_count = 2340;    // 总包数

9. update_display() 刷新规则

每次状态变更后调用 update_display():


static void update_display(void)

{

    char tmp[32];

    // 1. 刷新当前重量(保留一位小数)

    snprintf(tmp, sizeof(tmp), "%lu.%lu",

             (unsigned long)(current_weight / 10),

             (unsigned long)(current_weight % 10));

    lv_label_set_text(label_current_weight, tmp);



    // 2. 刷新总包数(整数)

    snprintf(tmp, sizeof(tmp), "%lu", (unsigned long)pack_count);

    lv_label_set_text(label_pack_count, tmp);



    // 3. 刷新状态灯颜色

    lv_obj_set_style_bg_color(status_dot,

        is_running ? COLOR_GREEN : COLOR_RED, 0);



    // 4. 刷新状态文字

    lv_label_set_text(label_status, is_running ? "运行中" : "已停止");

}

update_display() 由外部任务循环调用, 也由 start_cb/reset_cb 等回调触发。

10. 后续开发指南

10.1 新增控件原则

  • 所有坐标都以 800x480 为基准, 不要超出范围

  • 使用 create_card() 辅助函数创建新的卡片容器, 统一无边框无滚动

  • 新按钮必须执行 "无边框三部曲": shadow=0, outline=0, border=0

  • 新文字标签必须指定字体, 不要依赖默认字体(默认字体不含中文)

10.2 颜色使用

  • 标题一律用 COLOR_GREEN, 内容/数值一律用 COLOR_TEXT_MAIN (白色)

  • 优先使用已定义的 COLOR_xxx 常量, 不要随意硬编码新颜色

  • 如需新增颜色: 在 set_colors() 中添加新常量, 不要在代码中直接写 lv_color_hex()

  • 副标题文字绿色调使用 0x6f9181, 与 COLOR_TEXT_SEC (0x9ab3a5) 区分

  • COLOR_TEXT_SEC 目前仅用于复位按钮文字(白色改为后已不再使用, 保留以备后续)

10.3 字体使用

  • 标题/说明: custom_cjk_20

  • 数值: custom_cjk_60

  • 辅助文字: custom_cjk_16

  • 按钮: custom_cjk_40

  • 如需新增汉字: 使用 gen_font 技能重新生成字体

10.4 添加新页面

如需添加新页面(如设置页, 历史记录页), 建议步骤:

  1. 新建 xxx_ui.cpp / xxx_ui.h(参考 packer_ui 结构)

  2. 遵循相同的卡片布局风格: 深色背景, 圆角卡片, 绿黑配色

  3. 在新文件的 xxx_ui_create() 中完成创建

  4. lv_scr_load() 切换页面时, 先用 lv_obj_del() 删除旧页面控件(可选)

  5. main.cpp 或任务流程中调用新页面的 create 函数

10.5 动画与过渡

当前界面为静态布局, 如需添加过渡效果:


// 示例: 按钮按下缩放动画

lv_anim_t a;

lv_anim_init(&a);

lv_anim_set_var(&a, btn);

lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_scale);

lv_anim_set_values(&a, 256, 280);  // LV_IMG_ZOOM_NONE=256

lv_anim_set_time(&a, 100);

lv_anim_start(&a);

10.6 国际化和单位

  • 所有 UI 文字为中文硬编码

  • 重量显示格式: "190.0"(一位小数), 重量内部以 0.1g 为单位存储

  • 如需支持多语言, 建议将所有字符串抽取到常量表中

10.7 触摸事件

当前按钮使用 LV_EVENT_CLICKED 事件, 如需区分按下/释放:


lv_obj_add_event_cb(btn, my_cb, LV_EVENT_PRESSED, NULL);

lv_obj_add_event_cb(btn, my_cb, LV_EVENT_RELEASED, NULL);



static void my_cb(lv_event_t *e)

{

    lv_event_code_t code = lv_event_get_code(e);

    if (code == LV_EVENT_PRESSED) {

        // 按下时改变按钮样式

    } else if (code == LV_EVENT_RELEASED) {

        // 释放时恢复

    }

}

10.8 键盘/物理按键支持

LVGL 默认支持键盘焦点导航。焦点指示器为按钮的 outline 样式。如需自定义焦点样式:


// 自定义焦点时的边框颜色

lv_obj_set_style_outline_color(btn, lv_color_hex(0x58a6ff), LV_STATE_FOCUSED);

lv_obj_set_style_outline_width(btn, 2, LV_STATE_FOCUSED);

11. 文件位置

| 文件 | 路径 |

|------|------|

| UI 源码 | app/tasks/packer_ui.cpp |

| UI 头文件 | app/tasks/packer_ui.h |

| 调试日志 | app/tasks/debug_log.h |

| 字体生成脚本 | regen_font_new.py |

12. 界面逻辑

  1. 当前重量数值跟随着称称重变化

  2. 总包数数值跟包装好的包数变化

  3. 状态灯颜色跟运行状态变化

  4. 状态文字跟运行状态变化

  5. 当前工序标签跟当前工序变化

  6. 增加按键点击时,设计重量会增加1g,最大值90g

  7. 减小按键点击时,设计重量会减少1g,最小值1g

  8. 复位按键点击时,发出机器复位的指令,所有工序停止,并且各种结构归零复位。

  9. 运行按键点击时,开始运行,并开始进行包装。并且启动按键变成红色,文字变成停止,状态灯变绿,文字变为运行中,再次点击时,按键变为绿色言,文字变为启动,状态灯变红,文字变为已停止。