新增页面、页面内成员(显示参数、点击反馈等)-- 手把手教程

本文档把「新增页面」和「页面内成员(显示参数、点击反馈等)」拆成最小步骤,按顺序做完即可得到一块可触摸、可跳转、可选带显示参数的新页。
假设新页名为 DemoPage(可替换成你的页面名)。


第一部分:新增页面(从零到可跳转)

步骤 1:准备两张图(背景 + 备份)

  • 背景图:本页正常显示用的 JPG(如 800×480),用于首次进入页面时全屏显示。
  • 备份图:与背景图同尺寸,但可点击区域画成“按下态”(如高亮、变色),用于点击时拷贝该矩形实现反馈。

两张图需通过工程现有方式导入 NOR Flash,并得到:宽度、高度、字节大小、在 NOR 中的起始地址
(若工程用 All_8889_Pic_NOR.h / BINARY_INFO,需保证顺序与下面枚举、_Pic[] 一致。)


步骤 2:在 lcdts_LCD_Pic.h 中增加图片枚举

打开 LCD_Menu_logic_architecture/lcdts_LCD_Pic.h,在 PICTURE_NAME_Pic 枚举里按与 NOR 中一致的顺序增加两项:

  // 在现有某项之后,例如 Parameter_settin_and_passing_backups 之后
  DemoPage,              /* 新页背景 */
  DemoPage_backups,      /* 新页备份(按键按下态) */
  Z_Number0,             /* 后面原有项保持不变 */
  // ...
  IMAGE_REFERENCE_ID_END

注意:不要改 IMAGE_REFERENCE_ID_END 的取值,只要保证新两项在它前面且顺序与图资表一致即可。


步骤 3:在 ui_page_main.c 中登记新页

打开 LCD_Menu_logic_architecture/ui_page_main.c

3.1 启用该页

page_worked[] 里,找到与配置表同一下标的位置(新页准备加在配置表第几条,就对应第几个下标),设为 1
若新页是第 8 条,则:

static char page_worked[] = {
    1,  /* Main_Page */
    // ...
    1,  /* 新页 DemoPage,下标 7 */
    0,  /* PAGE1 - 暂关闭 */
    // ...
};

3.2 在 ui_page_def_conf[] 中加一条

在结束标记 {0,0,0,0,0,NULL,NULL,0} 之前插入:

    {IMAGE_ID_DEMO_PAGE, PAGE_TYPE_INTERACT_SINGLE, 1, 0, 0,
     ui_tsj_DemoPage, ui_init_DemoPage, 3},
  • IMAGE_ID_DEMO_PAGE 下一步在 DemoPage.h 里定义为 DemoPage
  • 3 表示本页有 3 个响应区域(例如:返回上一级、返回主页、应用)。
  • 最后一行保持:{0, 0, 0, 0, 0, NULL, NULL, 0}

3.3 在 _Pic[] 中加两行

const INFO_Pic _Pic[IMAGE_REFERENCE_ID_END] 里,按与枚举完全相同的顺序增加两行(宽、高、大小、起始地址按实际图资填):

  { 800, 480, 你的背景图字节数, 背景图起始地址 },   /* DemoPage */
  { 800, 480, 你的备份图字节数, 备份图起始地址 },   /* DemoPage_backups */

步骤 4:新建 DemoPage.h

LCD_Menu_logic_architecture/ 下新建 DemoPage.h,内容如下(坐标按你 UI 图修改):

#ifndef __DEMO_PAGE_H__
#define __DEMO_PAGE_H__

#include "ui_page_base.h"
#include "lcdts_LCD_Pic.h"

#define IMAGE_ID_DEMO_PAGE  DemoPage

/* 本页可点击区域矩形 (x, y, w, h),与美工图一致 */
#define DEMO_BACK_PREV_X  0
#define DEMO_BACK_PREV_Y  0
#define DEMO_BACK_PREV_W  92
#define DEMO_BACK_PREV_H  72

#define DEMO_BACK_HOME_X  712
#define DEMO_BACK_HOME_Y  0
#define DEMO_BACK_HOME_W  88
#define DEMO_BACK_HOME_H  64

#define DEMO_APPLY_X  100
#define DEMO_APPLY_Y  340
#define DEMO_APPLY_W  600
#define DEMO_APPLY_H  88

/* 区域 ID,与 ui_tsj 返回值一致 */
#define DEMO_AREA_ID_BACK_PREV  1
#define DEMO_AREA_ID_BACK_HOME  2
#define DEMO_AREA_ID_APPLY      3

unos_id_t ui_tsj_DemoPage(lcd_coordinate_t *pCoordinate);
int ui_init_DemoPage(ui_page_t *ui_page);

#endif

步骤 5:新建 DemoPage.c(最小可运行版)

LCD_Menu_logic_architecture/ 下新建 DemoPage.c,先实现仅背景 + 触摸判断 + 三个区域点击变色并跳转

#include "DemoPage.h"
#include "Main_Page.h"
#include "ui_page_main.h"
#include "lcdts_LCD_Pic.h"
#include "RA6809/RA8889_51.h"
#include "RA6809/RA8889_API_51.h"
#include "RA6809/UserDef.h"

extern const INFO_Pic _Pic[];

/* 点击反馈:从备份图拷贝矩形到显示层 */
static int demo_backup_rect_to_display(void *param)
{
    unsigned short rx, ry, rw, rh;
    if (param == NULL) return -1;
    rx = ((unsigned short *)param)[0];
    ry = ((unsigned short *)param)[1];
    rw = ((unsigned short *)param)[2];
    rh = ((unsigned short *)param)[3];
    Canvas_Image_Start_address(LAYER_ADDRESS(2));
    IDEC_Destination_Start_Address(LAYER_ADDRESS(2));
    ui_image_load(DemoPage_backups);
    BTE_Memory_Copy(LAYER_ADDRESS(2), canvas_image_width, rx, ry, 0, 0, 0, 0,
                    LAYER_ADDRESS(0), canvas_image_width, rx, ry, 12, rw, rh);
    Canvas_Image_Start_address(LAYER_ADDRESS(0));
    Main_Image_Start_Address(LAYER_ADDRESS(0));
    return UNOS_OK;
}

static const unsigned short rect_back_prev[4] = {
    DEMO_BACK_PREV_X, DEMO_BACK_PREV_Y, DEMO_BACK_PREV_W, DEMO_BACK_PREV_H
};
static const unsigned short rect_back_home[4] = {
    DEMO_BACK_HOME_X, DEMO_BACK_HOME_Y, DEMO_BACK_HOME_W, DEMO_BACK_HOME_H
};
static const unsigned short rect_apply[4] = {
    DEMO_APPLY_X, DEMO_APPLY_Y, DEMO_APPLY_W, DEMO_APPLY_H
};

/* 显示:只画背景 */
static int ui_display_DemoPage_page(ui_page_t *ui_page)
{
    if (ui_page == NULL) return -1;
    Canvas_Image_Start_address(LAYER_ADDRESS(1));
    IDEC_Destination_Start_Address(LAYER_ADDRESS(1));
    ui_image_load(ui_page->image_reference_id);
    BTE_Memory_Copy(LAYER_ADDRESS(1), canvas_image_width, 0, 0, 0, canvas_image_width, 0, 0,
                    LAYER_ADDRESS(0), canvas_image_width, 0, 0, 12, LCD_Width, LCD_Height);
    Canvas_Image_Start_address(LAYER_ADDRESS(0));
    Main_Image_Start_Address(LAYER_ADDRESS(0));
    return UNOS_OK;
}

/* 触摸判断:根据坐标返回区域 ID */
unos_id_t ui_tsj_DemoPage(lcd_coordinate_t *pCoordinate)
{
    unsigned int x, y;
    if (pCoordinate == NULL) return AREA_ID_NONE_SELECT;
    x = pCoordinate->x;
    y = pCoordinate->y;
    if (x >= DEMO_BACK_PREV_X && x < DEMO_BACK_PREV_X + DEMO_BACK_PREV_W &&
        y >= DEMO_BACK_PREV_Y && y < DEMO_BACK_PREV_Y + DEMO_BACK_PREV_H)
        return DEMO_AREA_ID_BACK_PREV;
    if (x >= DEMO_BACK_HOME_X && x < DEMO_BACK_HOME_X + DEMO_BACK_HOME_W &&
        y >= DEMO_BACK_HOME_Y && y < DEMO_BACK_HOME_Y + DEMO_BACK_HOME_H)
        return DEMO_AREA_ID_BACK_HOME;
    if (x >= DEMO_APPLY_X && x < DEMO_APPLY_X + DEMO_APPLY_W &&
        y >= DEMO_APPLY_Y && y < DEMO_APPLY_Y + DEMO_APPLY_H)
        return DEMO_AREA_ID_APPLY;
    return AREA_ID_NONE_SELECT;
}

int ui_init_DemoPage(ui_page_t *ui_page)
{
    ui_response_area_t *r;
    unos_id_2_t main_page_id;
    if (ui_page == NULL) return -1;
    ui_page->page_display = ui_display_DemoPage_page;
    if (ui_page->page_response_area == NULL) return -1;

    main_page_id = UI_PAGE_ID_BY_IMAGE_PARAM(IMAGE_ID_MAIN_PAGE, 0);

    r = &ui_page->page_response_area[0];
    r->response_area_id = DEMO_AREA_ID_BACK_PREV;
    r->config_function = demo_backup_rect_to_display;
    r->config_param = (void *)rect_back_prev;
    r->page_switch_function = ui_page_switch_to;
    r->page_id = main_page_id;

    r = &ui_page->page_response_area[1];
    r->response_area_id = DEMO_AREA_ID_BACK_HOME;
    r->config_function = demo_backup_rect_to_display;
    r->config_param = (void *)rect_back_home;
    r->page_switch_function = ui_page_switch_to;
    r->page_id = main_page_id;

    r = &ui_page->page_response_area[2];
    r->response_area_id = DEMO_AREA_ID_APPLY;
    r->config_function = demo_backup_rect_to_display;
    r->config_param = (void *)rect_apply;
    r->page_switch_function = ui_page_switch_to;
    r->page_id = main_page_id;

    return UNOS_OK;
}

说明:

  • demo_backup_rect_to_display:通用“从备份图拷矩形”的回调,param(x,y,w,h) 四元组指针。
  • ui_tsj_DemoPage:只做矩形判断,返回 DEMO_AREA_ID_*AREA_ID_NONE_SELECT
  • ui_init_DemoPage:三个区域都“先变色再跳转主页”;若“返回上一级”要跳到别的页,把对应 page_id 改成 UI_PAGE_ID_BY_IMAGE_PARAM(目标页IMAGE_ID, 0) 即可。

步骤 6:在 main.c 中参与编译并防止被优化掉

main.c 中:

  1. 增加头文件:
    #include "LCD_Menu_logic_architecture/DemoPage.h"
    
  2. 若使用 Keil 且担心 init/tsj 未被引用而被剔除,可加(与现有页面一致):
    static void *_demo_page_init_ref = (void *)ui_init_DemoPage;
    static void *_demo_page_tsj_ref  = (void *)ui_tsj_DemoPage;
    

DemoPage.c 加入工程编译。下面步骤 7 说明如何从主页某一块点进 DemoPage。


步骤 7:从主页某块点进 DemoPage

主页 Main_Page 为例:在主页上增加第三个可点击区域(例如一块“进入 Demo 页”的按钮),点击后先变色再跳转到 DemoPage。

7.1 修改 Main_Page.h

打开 LCD_Menu_logic_architecture/Main_Page.h

  • 增加第三块区域的矩形宏(坐标与美工图一致,不要和现有两块重叠),例如:
    /* 现有:参数显示与设定、控件设定与传递 */
    /* 新增:进入 Demo 页,例如右下角一块 */
    #define MAIN_PAGE_DEMO_ENTRY_X    410
    #define MAIN_PAGE_DEMO_ENTRY_Y    250
    #define MAIN_PAGE_DEMO_ENTRY_W    200
    #define MAIN_PAGE_DEMO_ENTRY_H    80
    
  • 增加区域 ID(与 ui_tsj 返回值一致):
    #define MAIN_PAGE_AREA_ID_PARAM_DISPLAY      1
    #define MAIN_PAGE_AREA_ID_EQUIPMENT_SETTINGS 2
    #define MAIN_PAGE_AREA_ID_DEMO_ENTRY         3   /* 进入 DemoPage */
    

7.2 修改 Main_Page.c

打开 LCD_Menu_logic_architecture/Main_Page.c

  • 在文件顶部增加对 DemoPage 的包含:
    #include "DemoPage.h"
    
  • 在“区域矩形”处增加第三块的四元组(与 6.5 中 rect 用法一致):
    static const unsigned short main_page_demo_entry_rect[4] = {
        MAIN_PAGE_DEMO_ENTRY_X, MAIN_PAGE_DEMO_ENTRY_Y,
        MAIN_PAGE_DEMO_ENTRY_W, MAIN_PAGE_DEMO_ENTRY_H
    };
    
  • ui_tsj_Main_Page 里增加对第三块的判断(放在 return AREA_ID_NONE_SELECT 之前):
    if (x >= MAIN_PAGE_DEMO_ENTRY_X && x < MAIN_PAGE_DEMO_ENTRY_X + MAIN_PAGE_DEMO_ENTRY_W &&
        y >= MAIN_PAGE_DEMO_ENTRY_Y && y < MAIN_PAGE_DEMO_ENTRY_Y + MAIN_PAGE_DEMO_ENTRY_H)
        return MAIN_PAGE_AREA_ID_DEMO_ENTRY;
    return AREA_ID_NONE_SELECT;
    
  • ui_init_Main_Page 里增加第三个响应区域(在现有两个 r = &ui_page->page_response_area[1]; 之后):
    unos_id_2_t demo_page_id = UI_PAGE_ID_BY_IMAGE_PARAM(IMAGE_ID_DEMO_PAGE, 0);
    
    // ... 原有 response_area[0]、[1] 不变 ...
    
    r = &ui_page->page_response_area[2];
    r->response_area_id = MAIN_PAGE_AREA_ID_DEMO_ENTRY;
    r->config_function = main_page_backup_rect_to_display;
    r->config_param = (void *)main_page_demo_entry_rect;
    r->page_switch_function = ui_page_switch_to;
    r->page_id = demo_page_id;
    

7.3 修改 ui_page_main.c 中主页的响应区域数量

打开 LCD_Menu_logic_architecture/ui_page_main.c,找到 Main_Page 的配置行,把最后一个数字从 2 改为 3

    {IMAGE_ID_MAIN_PAGE, PAGE_TYPE_INTERACT_SINGLE, 1, 0, 0,
     ui_tsj_Main_Page, ui_init_Main_Page, 3},   /* 原来是 2,改为 3 */

7.4 备份图

若希望点击“进入 Demo”这块时也有按下态,需要在 Main_Page_backups 这张图里,在 (MAIN_PAGE_DEMO_ENTRY_X, MAIN_PAGE_DEMO_ENTRY_Y) 位置画上同样大小的高亮块;否则仅跳转也可,视觉上可能无变化。

完成后:在主页上点击你划定的“进入 Demo”区域,会先执行 main_page_backup_rect_to_display(若备份图该块有按下态则会变色),再跳转到 DemoPage;在 DemoPage 点“返回上一级/返回主页/应用”会回到主页。

从其他页进入 DemoPage 的通用做法:在任意页的 .c 里为该页的某个响应区域设置
r->page_id = UI_PAGE_ID_BY_IMAGE_PARAM(IMAGE_ID_DEMO_PAGE, 0);
并在该页的 ui_tsj 中有一块矩形返回对应的 response_area_id,在 ui_init 里该 response_area_id 的 config(可选)与 page_switch 填好即可。


第二部分:页面内成员 — 显示参数

“显示参数”指页面上随数据变化的数字或文字(如电压、电流、功率),不是静态图。

2.1 数据放在哪里

  • 仅本页使用:在本页 .c 里用 static 变量保存,例如:
    static unsigned long demo_voltage_01V = 2200;  /* 0.1V 单位 */
    
    再提供对外的 set/get(可选):
    void demo_voltage_set(unsigned long v) { demo_voltage_01V = v; }
    unsigned long demo_voltage_get(void) { return demo_voltage_01V; }
    
  • 多页共用:在“主控/参数”等模块里集中存,本页通过其 get 接口取数(如 main_control_voltage_get())。

2.2 在 page_display 里画出来

  • 进入本页或刷新时,会调用 ui_page->page_display(ui_page),即你的 ui_display_DemoPage_page
  • 画完背景之后:
    1. 从 get 接口或本页 static 变量取出要显示的值。
    2. 拆成“整数部分 + 小数部分 + 小数位数”(若需要小数)。
    3. 调用绘制函数在指定矩形内画数字。

若工程里已有可变小数位绘制(如 draw_value_png_variable_decimals),可直接复用:

#include "Parameter_control_and_display.h"

static int ui_display_DemoPage_page(ui_page_t *ui_page)
{
    unsigned long v01 = demo_voltage_get();  /* 或 main_control_voltage_get() */
    unsigned int disp_int = (unsigned int)(v01 / 10UL);
    unsigned int disp_frac = (unsigned int)(v01 % 10UL);
    unsigned char disp_digits = 2;

    // 先画背景(同上)
    // ...

    SPI_NOR_initial_DMA(3, 1, 1, 1, 0);
    draw_value_png_variable_decimals(
        (unsigned long)disp_int, disp_frac, disp_digits,
        DEMO_VOLTAGE_BOX_X, DEMO_VOLTAGE_BOX_Y,
        DEMO_VOLTAGE_BOX_W, DEMO_VOLTAGE_BOX_H);

    BTE_Memory_Copy(LAYER_ADDRESS(2), ...);  // 合成层拷到显示层(若需要)
    return UNOS_OK;
}

在头文件中定义显示框矩形:DEMO_VOLTAGE_BOX_X/Y/W/H,与美工图一致。
若没有现成绘制函数,可参考 Parameter_control_and_display.c 里的 draw_value_png_variable_decimalsdraw_value_png_centered_2decimals 的实现(图层、BTE、PNG 数字资源 Z_Number0~9)。

2.3 何时刷新显示

  • 从别页切到本页:框架会调一次 page_display,无需额外操作。
  • 在本页点了某个键希望数字更新:把该键的 page_id 设成本页UI_PAGE_ID_BY_IMAGE_PARAM(IMAGE_ID_DEMO_PAGE, 0)),这样点击后会再次执行本页的 page_display,数字会按最新数据重画。

第三部分:页面内成员 — 点击反馈

“点击反馈”指用户按下某区域时,立刻看到的视觉变化(如按键高亮),通常用“备份图矩形拷贝”实现。

3.1 定义区域矩形

DemoPage.h 里为每个可点击区域定义 (x, y, w, h),与 UI 图完全一致:

#define DEMO_BACK_PREV_X  0
#define DEMO_BACK_PREV_Y  0
#define DEMO_BACK_PREV_W  92
#define DEMO_BACK_PREV_H  72
// ... 其他区域

3.2 写一个通用“备份图矩形拷贝”回调

DemoPage.c 里(上面步骤 5 已给出):

static int demo_backup_rect_to_display(void *param)
{
    unsigned short rx, ry, rw, rh;
    if (param == NULL) return -1;
    rx = ((unsigned short *)param)[0];
    ry = ((unsigned short *)param)[1];
    rw = ((unsigned short *)param)[2];
    rh = ((unsigned short *)param)[3];
    Canvas_Image_Start_address(LAYER_ADDRESS(2));
    IDEC_Destination_Start_Address(LAYER_ADDRESS(2));
    ui_image_load(DemoPage_backups);   /* 加载备份图到层2 作为源 */
    BTE_Memory_Copy(LAYER_ADDRESS(2), canvas_image_width, rx, ry, 0, 0, 0, 0,
                    LAYER_ADDRESS(0), canvas_image_width, rx, ry, 12, rw, rh);
    Canvas_Image_Start_address(LAYER_ADDRESS(0));
    Main_Image_Start_Address(LAYER_ADDRESS(0));
    return UNOS_OK;
}

含义:从备份图的 (rx,ry) 起拷一块 (rw,rh) 到显示层的 (rx,ry),所以备份图里该矩形应画成“按下态”。

3.3 为每个区域准备 (x,y,w,h) 并绑定到 config_function

DemoPage.c 里为每个区域定义一个四元组,并填到 ui_init_DemoPage 的对应项:

static const unsigned short rect_back_prev[4] = {
    DEMO_BACK_PREV_X, DEMO_BACK_PREV_Y, DEMO_BACK_PREV_W, DEMO_BACK_PREV_H
};
// ... 其他 rect_xxx

// 在 ui_init_DemoPage 中:
r->config_function = demo_backup_rect_to_display;
r->config_param = (void *)rect_back_prev;

这样该区域被点击时,会先执行 demo_backup_rect_to_display(rect_back_prev),再执行 page_switch_function

3.4 不需要变色的区域

若某区域只跳转、不需视觉反馈,把该区的 config_function 设为 NULLconfig_param 设为 NULL 即可,例如:

r->config_function = NULL;
r->config_param = NULL;
r->page_switch_function = ui_page_switch_to;
r->page_id = main_page_id;

第四部分:页面内成员 — 设置参数(可选)

“设置参数”指用户在本页或子页(如键盘页)修改某个数值,改完后要写回本页或主控。

4.1 本页持有数值并提供 set/get

在本页 .c 里用 static 存,并对外提供 set:

static unsigned long demo_voltage_01V = 2200;

void demo_voltage_set(unsigned long v) { demo_voltage_01V = v; }
unsigned long demo_voltage_get(void) { return demo_voltage_01V; }

在 .h 里声明,供键盘页或应用回调使用。

4.2 点击某区域打开键盘并写回

若希望“点击电压框 → 进键盘页 → OK 后写回本页”:

  1. 进入键盘前:在本页该区域的 config_function 里设置“当前编辑的是电压”(例如全局变量 g_keyboard_edit_which = 0),并清空键盘缓冲(如 keyboard_clear_buffer()),再让 page_switch_function 跳到键盘页。
  2. 键盘页 OK:在键盘页 OK 的 config_function 里解析输入,调用本页的 set(如 demo_voltage_set(...)),然后 page_switch_function 跳回本页;框架会再执行一次本页的 page_display,显示的就是新值。

参考:Parameter_control_and_display 的“电压/电流”区域用 parameter_control_set_keyboard_edit_voltage/Current 做 config,跳转到 Keyboard_input;键盘 OK 时调用 parameter_control_voltage_set_with_display 等写回并跳回参数页。

4.3 “应用”按钮:把本页参数写回主控或主机

在“应用”区域的 config_function 里:

  1. 可选:从备份图拷贝应用区矩形(变色)。
  2. 用本页的 get 取出数值,调用主控或主机接口写入,例如:
    main_control_voltage_set(demo_voltage_get());
    host_main_control_params_upload(demo_voltage_get(), ...);
    
  3. 该区域的 page_switch_function 跳转到主控页或主页。

这样“显示参数”和“设置参数”就通过 set/get 与 page_display 联动起来了。


第五部分:自检清单

  • lcdts_LCD_Pic.h 中枚举与 NOR 图资顺序一致,且 _Pic[] 中两行与枚举一致。
  • ui_page_def_conf 里新页的 response_area_num 与实际区域数一致。
  • page_worked 对应下标为 1。
  • 每个可点击区域在 ui_tsj 中有矩形判断,且返回值与 response_area_id 一致。
  • 每个响应区域的 response_area_id 在 init 里与 ui_tsj 的返回值一一对应。
  • 需要点击变色的区域:备份图该矩形为按下态,且 config_function 使用“备份图矩形拷贝”并传入正确 (x,y,w,h)。
  • 有显示参数时:在 page_display 中根据 get 或本页变量调用绘制函数,且显示框坐标在头文件中定义。
  • main.c 已包含新页头文件,且 init/tsj 已通过引用或强制指针避免被链接器剔除。
  • 从某页点进 DemoPage:入口页(如 Main_Page)已增加新区域矩形与区域 ID、ui_tsj 中新增判断、ui_init 中新增 response_area 且 page_id = UI_PAGE_ID_BY_IMAGE_PARAM(IMAGE_ID_DEMO_PAGE, 0),且该页在 ui_page_def_conf 里的 response_area_num 已加 1(如主页从 2 改为 3)。

按上述步骤做完,即可得到一块带“显示参数 + 点击反馈 + 可选设置参数”的新页面;若某页不需要显示参数或设置参数,可只做对应部分。

如需更细的操作步骤说明或工程代码(可与深圳市瑞福科技联系)。

RA6809 的 HMI(人机交互) 开发:菜单逻辑架构设计

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐