RA6809 的 HMI(人机交互) 开发:菜单逻辑架构设计与实现详解(3)
新增页面、页面内成员(显示参数、点击反馈等)-- 手把手教程
本文档把「新增页面」和「页面内成员(显示参数、点击反馈等)」拆成最小步骤,按顺序做完即可得到一块可触摸、可跳转、可选带显示参数的新页。
假设新页名为 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 中:
- 增加头文件:
#include "LCD_Menu_logic_architecture/DemoPage.h" - 若使用 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变量保存,例如:
再提供对外的 set/get(可选):static unsigned long demo_voltage_01V = 2200; /* 0.1V 单位 */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。 - 在画完背景之后:
- 从 get 接口或本页 static 变量取出要显示的值。
- 拆成“整数部分 + 小数部分 + 小数位数”(若需要小数)。
- 调用绘制函数在指定矩形内画数字。
若工程里已有可变小数位绘制(如 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_decimals 或 draw_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 设为 NULL、config_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 后写回本页”:
- 进入键盘前:在本页该区域的
config_function里设置“当前编辑的是电压”(例如全局变量g_keyboard_edit_which = 0),并清空键盘缓冲(如keyboard_clear_buffer()),再让page_switch_function跳到键盘页。 - 键盘页 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 里:
- 可选:从备份图拷贝应用区矩形(变色)。
- 用本页的 get 取出数值,调用主控或主机接口写入,例如:
main_control_voltage_set(demo_voltage_get()); host_main_control_params_upload(demo_voltage_get(), ...); - 该区域的
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(人机交互) 开发:菜单逻辑架构设计
更多推荐



所有评论(0)