删除库进行重构,结构优化不好
This commit is contained in:
@ -1,869 +0,0 @@
|
||||
/**
|
||||
* @file menu_core.c
|
||||
* @brief 菜单核心逻辑:导航、栈管理、主循环(工业级:状态机驱动)
|
||||
*/
|
||||
#include "../api/menu.h"
|
||||
#include "menu_core.h"
|
||||
#include "menu_data.h"
|
||||
#include <stddef.h>
|
||||
|
||||
/************************** 内部函数声明 **************************/
|
||||
static MenuErrCode menu_core_stack_push(MenuGlobalCtx* global_ctx, MenuNodeId node_id);
|
||||
static MenuErrCode menu_core_stack_pop(MenuGlobalCtx* global_ctx, MenuNodeId* node_id);
|
||||
static MenuErrCode menu_core_navigate_enter(MenuGlobalCtx* global_ctx, MenuNodeId node_id);
|
||||
static MenuErrCode menu_core_navigate_back(MenuGlobalCtx* global_ctx);
|
||||
static MenuErrCode menu_core_navigate_up(MenuGlobalCtx* global_ctx);
|
||||
static MenuErrCode menu_core_navigate_down(MenuGlobalCtx* global_ctx);
|
||||
static bool menu_core_get_event(MenuGlobalCtx* global_ctx, MenuEvent* event);
|
||||
|
||||
/************************** 状态机相关函数声明 **************************/
|
||||
static MenuErrCode menu_state_action_normal_navigate(MenuGlobalCtx* global_ctx, MenuEventType event);
|
||||
static MenuErrCode menu_state_action_param_edit(MenuGlobalCtx* global_ctx, MenuEventType event);
|
||||
static MenuErrCode menu_state_action_confirm(MenuGlobalCtx* global_ctx, MenuEventType event);
|
||||
static MenuErrCode menu_state_action_error(MenuGlobalCtx* global_ctx, MenuEventType event);
|
||||
static MenuErrCode menu_state_transition(MenuGlobalCtx* global_ctx, MenuEventType event);
|
||||
|
||||
/************************** 状态转换表定义 **************************/
|
||||
static const MenuStateTransition menu_state_transitions[] = {
|
||||
// 当前状态 触发事件 下一个状态 转换动作
|
||||
{ MENU_STATE_INIT, MENU_EVENT_NONE, MENU_STATE_NORMAL, NULL },
|
||||
{ MENU_STATE_NORMAL, MENU_EVENT_KEY_UP, MENU_STATE_NORMAL, NULL },
|
||||
{ MENU_STATE_NORMAL, MENU_EVENT_KEY_DOWN, MENU_STATE_NORMAL, NULL },
|
||||
{ MENU_STATE_NORMAL, MENU_EVENT_KEY_ENTER, MENU_STATE_PARAM_EDIT, NULL },
|
||||
{ MENU_STATE_NORMAL, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL },
|
||||
{ MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_UP, MENU_STATE_PARAM_EDIT, NULL },
|
||||
{ MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_DOWN, MENU_STATE_PARAM_EDIT, NULL },
|
||||
{ MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL },
|
||||
{ MENU_STATE_PARAM_EDIT, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL },
|
||||
{ MENU_STATE_CONFIRM, MENU_EVENT_KEY_UP, MENU_STATE_CONFIRM, NULL },
|
||||
{ MENU_STATE_CONFIRM, MENU_EVENT_KEY_DOWN, MENU_STATE_CONFIRM, NULL },
|
||||
{ MENU_STATE_CONFIRM, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL },
|
||||
{ MENU_STATE_CONFIRM, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL },
|
||||
{ MENU_STATE_ERROR, MENU_EVENT_KEY_ENTER, MENU_STATE_NORMAL, NULL },
|
||||
{ MENU_STATE_ERROR, MENU_EVENT_KEY_BACK, MENU_STATE_NORMAL, NULL },
|
||||
};
|
||||
|
||||
static const uint8_t menu_state_transition_count = sizeof(menu_state_transitions) / sizeof(menu_state_transitions[0]);
|
||||
|
||||
/**
|
||||
* @brief 查找菜单节点(通过ID)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param node_id 节点ID
|
||||
* @return 节点指针(NULL表示未找到)
|
||||
*/
|
||||
MenuNode* menu_core_find_node(MenuGlobalCtx* global_ctx, MenuNodeId node_id)
|
||||
{
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++)
|
||||
{
|
||||
if (ctx->nodes[i].flags.is_registered && ctx->nodes[i].id == node_id)
|
||||
{
|
||||
return &ctx->nodes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单栈压入节点
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param node_id 节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_stack_push(MenuGlobalCtx* global_ctx, MenuNodeId node_id)
|
||||
{
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
MENU_ASSERT(ctx != NULL);
|
||||
|
||||
if (ctx->stack.top >= MENU_CONFIG_STACK_DEPTH)
|
||||
{
|
||||
MENU_DEBUG("Stack overflow: depth %d", MENU_CONFIG_STACK_DEPTH);
|
||||
return MENU_ERR_STACK_OVERFLOW;
|
||||
}
|
||||
|
||||
ctx->stack.nodes[ctx->stack.top++] = node_id;
|
||||
MENU_DEBUG("Stack push: node %d, top %d", node_id, ctx->stack.top);
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单栈弹出节点
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param node_id 输出参数,弹出的节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_stack_pop(MenuGlobalCtx* global_ctx, MenuNodeId* node_id)
|
||||
{
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
MENU_ASSERT(ctx != NULL && node_id != NULL);
|
||||
|
||||
if (ctx->stack.top == 0)
|
||||
{
|
||||
MENU_DEBUG("Stack underflow");
|
||||
return MENU_ERR_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
*node_id = ctx->stack.nodes[--ctx->stack.top];
|
||||
MENU_DEBUG("Stack pop: node %d, top %d", *node_id, ctx->stack.top);
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单导航到子节点(确认键)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param node_id 当前节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_enter(MenuGlobalCtx* global_ctx, MenuNodeId node_id)
|
||||
{
|
||||
MenuNode* node = menu_core_find_node(global_ctx, node_id);
|
||||
if (node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 执行进入回调
|
||||
if (node->enter_cb != NULL)
|
||||
{
|
||||
MenuErrCode err = node->enter_cb(node_id);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有子节点,压入栈并选中第一个子节点
|
||||
if (node->first_child != NULL)
|
||||
{
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
ctx->current_node_id = node->first_child->id;
|
||||
return menu_core_stack_push(global_ctx, node->first_child->id);
|
||||
}
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单导航返回父节点(返回键)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_back(MenuGlobalCtx* global_ctx)
|
||||
{
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
MenuNodeId current_id = ctx->current_node_id;
|
||||
MenuNode* current_node = menu_core_find_node(global_ctx, current_id);
|
||||
if (current_node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 执行退出回调
|
||||
if (current_node->exit_cb != NULL)
|
||||
{
|
||||
MenuErrCode err = current_node->exit_cb(current_id);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// 弹出栈,回到父节点
|
||||
MenuNodeId parent_id = current_node->parent_id;
|
||||
if (parent_id == 0) // 根节点
|
||||
{
|
||||
ctx->current_node_id = current_id;
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
MenuNodeId pop_id;
|
||||
MenuErrCode err = menu_core_stack_pop(global_ctx, &pop_id);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
ctx->current_node_id = parent_id;
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单导航上选(上键)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_up(MenuGlobalCtx* global_ctx)
|
||||
{
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id);
|
||||
if (current_node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 切换到上一个兄弟节点
|
||||
if (current_node->prev_sibling != NULL)
|
||||
{
|
||||
ctx->current_node_id = current_node->prev_sibling->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 循环到最后一个兄弟节点(工业级:友好的交互设计)
|
||||
MenuNode* last_node = current_node;
|
||||
while (last_node->next_sibling != NULL)
|
||||
{
|
||||
last_node = last_node->next_sibling;
|
||||
}
|
||||
ctx->current_node_id = last_node->id;
|
||||
}
|
||||
|
||||
MENU_DEBUG("Navigate up: current node %d", ctx->current_node_id);
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单导航下选(下键)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_core_navigate_down(MenuGlobalCtx* global_ctx)
|
||||
{
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id);
|
||||
if (current_node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 切换到下一个兄弟节点
|
||||
if (current_node->next_sibling != NULL)
|
||||
{
|
||||
ctx->current_node_id = current_node->next_sibling->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 循环到第一个兄弟节点
|
||||
MenuNode* parent_node = menu_core_find_node(global_ctx, current_node->parent_id);
|
||||
if (parent_node != NULL && parent_node->first_child != NULL)
|
||||
{
|
||||
ctx->current_node_id = parent_node->first_child->id;
|
||||
}
|
||||
}
|
||||
|
||||
MENU_DEBUG("Navigate down: current node %d", ctx->current_node_id);
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理单个菜单事件(使用状态机)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param event 事件指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_core_handle_event(MenuGlobalCtx* global_ctx, const MenuEvent* event)
|
||||
{
|
||||
MENU_ASSERT(event != NULL);
|
||||
|
||||
MenuErrCode err = MENU_OK;
|
||||
|
||||
// 使用状态机处理事件
|
||||
err = menu_state_transition(global_ctx, event->type);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 状态机转换处理函数
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param event 触发事件
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_state_transition(MenuGlobalCtx* global_ctx, MenuEventType event)
|
||||
{
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
MenuState current_state = ctx->current_state;
|
||||
MenuErrCode err = MENU_OK;
|
||||
|
||||
// 查找状态转换
|
||||
for (uint8_t i = 0; i < menu_state_transition_count; i++)
|
||||
{
|
||||
if (menu_state_transitions[i].current_state == current_state &&
|
||||
menu_state_transitions[i].event == event)
|
||||
{
|
||||
// 执行状态转换动作
|
||||
switch (current_state)
|
||||
{
|
||||
case MENU_STATE_NORMAL:
|
||||
err = menu_state_action_normal_navigate(global_ctx, event);
|
||||
break;
|
||||
case MENU_STATE_PARAM_EDIT:
|
||||
err = menu_state_action_param_edit(global_ctx, event);
|
||||
break;
|
||||
case MENU_STATE_CONFIRM:
|
||||
err = menu_state_action_confirm(global_ctx, event);
|
||||
break;
|
||||
case MENU_STATE_ERROR:
|
||||
err = menu_state_action_error(global_ctx, event);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
ctx->current_state = menu_state_transitions[i].next_state;
|
||||
MENU_DEBUG("State transition: %d -> %d, event: %d",
|
||||
current_state, ctx->current_state, event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 正常导航状态的动作处理
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param event 触发事件
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_state_action_normal_navigate(MenuGlobalCtx* global_ctx, MenuEventType event)
|
||||
{
|
||||
MenuErrCode err = MENU_OK;
|
||||
|
||||
switch (event)
|
||||
{
|
||||
case MENU_EVENT_KEY_UP:
|
||||
err = menu_core_navigate_up(global_ctx);
|
||||
break;
|
||||
case MENU_EVENT_KEY_DOWN:
|
||||
err = menu_core_navigate_down(global_ctx);
|
||||
break;
|
||||
case MENU_EVENT_KEY_ENTER:
|
||||
err = menu_core_navigate_enter(global_ctx, global_ctx->core.current_node_id);
|
||||
break;
|
||||
case MENU_EVENT_KEY_BACK:
|
||||
err = menu_core_navigate_back(global_ctx);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 参数编辑状态的动作处理
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param event 触发事件
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_state_action_param_edit(MenuGlobalCtx* global_ctx, MenuEventType event)
|
||||
{
|
||||
MenuErrCode err = MENU_OK;
|
||||
|
||||
// Suppress unused parameter warnings
|
||||
(void)global_ctx;
|
||||
(void)event;
|
||||
|
||||
// 这里可以添加参数编辑的具体逻辑
|
||||
// 例如:根据上下键调整参数值,确认键保存,返回键取消编辑
|
||||
MENU_DEBUG("Param edit state, event: %d", event);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 确认状态的动作处理
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param event 触发事件
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_state_action_confirm(MenuGlobalCtx* global_ctx, MenuEventType event)
|
||||
{
|
||||
MenuErrCode err = MENU_OK;
|
||||
|
||||
// Suppress unused parameter warnings
|
||||
(void)global_ctx;
|
||||
(void)event;
|
||||
|
||||
// 这里可以添加确认状态的具体逻辑
|
||||
// 例如:确认保存设置,确认退出等
|
||||
MENU_DEBUG("Confirm state, event: %d", event);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 错误状态的动作处理
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param event 触发事件
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_state_action_error(MenuGlobalCtx* global_ctx, MenuEventType event)
|
||||
{
|
||||
MenuErrCode err = MENU_OK;
|
||||
|
||||
// Suppress unused parameter warnings
|
||||
(void)global_ctx;
|
||||
(void)event;
|
||||
|
||||
// 这里可以添加错误状态的具体逻辑
|
||||
// 例如:显示错误信息,等待用户确认
|
||||
MENU_DEBUG("Error state, event: %d", event);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 刷新菜单显示(内部调用,对接port层显示接口)
|
||||
* @param global_ctx 全局上下文指针
|
||||
*/
|
||||
void menu_core_refresh_display(MenuGlobalCtx* global_ctx)
|
||||
{
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
MenuNode* current_node = menu_core_find_node(global_ctx, ctx->current_node_id);
|
||||
if (current_node == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 这里应该调用port层的显示接口
|
||||
// 例如:menu_port_display(current_node->name, current_node->id);
|
||||
MENU_DEBUG("Refresh display: current menu %s (ID: %d)", current_node->name, current_node->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从事件队列获取事件(非阻塞)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param event 输出参数,获取到的事件
|
||||
* @return 是否获取到事件
|
||||
*/
|
||||
/**
|
||||
* @brief 获取菜单组件上下文所需的内存大小
|
||||
* @return 上下文内存大小(字节)
|
||||
*/
|
||||
uint32_t menu_get_ctx_size(void)
|
||||
{
|
||||
return sizeof(MenuGlobalCtx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从事件队列获取事件(非阻塞,优先级队列:高优先级事件先处理)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param event 输出参数,获取到的事件
|
||||
* @return 是否获取到事件
|
||||
*/
|
||||
static bool menu_core_get_event(MenuGlobalCtx* global_ctx, MenuEvent* event)
|
||||
{
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
MENU_ASSERT(ctx != NULL && event != NULL);
|
||||
|
||||
if (ctx->event_queue.count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找优先级最高的事件
|
||||
uint8_t highest_priority_idx = ctx->event_queue.head;
|
||||
for (uint8_t i = 0; i < ctx->event_queue.count; i++)
|
||||
{
|
||||
uint8_t idx = (ctx->event_queue.head + i) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
if (ctx->event_queue.buffer[idx].priority > ctx->event_queue.buffer[highest_priority_idx].priority)
|
||||
{
|
||||
highest_priority_idx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
// 复制优先级最高的事件
|
||||
*event = ctx->event_queue.buffer[highest_priority_idx];
|
||||
|
||||
// 将最后一个事件移到被取出的位置
|
||||
uint8_t last_idx = (ctx->event_queue.head + ctx->event_queue.count - 1) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
if (highest_priority_idx != last_idx)
|
||||
{
|
||||
ctx->event_queue.buffer[highest_priority_idx] = ctx->event_queue.buffer[last_idx];
|
||||
}
|
||||
|
||||
// 更新队列计数
|
||||
ctx->event_queue.count--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单主循环(需在用户主循环中调用,处理事件和刷新显示)
|
||||
* @param global_ctx 全局上下文指针
|
||||
*/
|
||||
void menu_main_loop(MenuGlobalCtx* global_ctx)
|
||||
{
|
||||
MENU_ASSERT(global_ctx != NULL);
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
if (!global_ctx->is_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理事件队列
|
||||
MenuEvent event;
|
||||
while (menu_core_get_event(global_ctx, &event))
|
||||
{
|
||||
// 检查事件是否超时
|
||||
if (menu_utils_get_tick() - event.timestamp > MENU_CONFIG_EVENT_TIMEOUT)
|
||||
{
|
||||
MENU_DEBUG("Event timeout: type %d, priority %d", event.type, event.priority);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理事件
|
||||
MenuErrCode err = menu_core_handle_event(global_ctx, &event);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
MENU_DEBUG("Event handle error: %d", err);
|
||||
}
|
||||
}
|
||||
|
||||
// 定时刷新显示
|
||||
if (menu_utils_get_tick() - ctx->last_refresh_tick >= MENU_CONFIG_REFRESH_INTERVAL)
|
||||
{
|
||||
menu_core_refresh_display(global_ctx);
|
||||
ctx->last_refresh_tick = menu_utils_get_tick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单组件初始化(必须首先调用)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_init(MenuGlobalCtx* global_ctx)
|
||||
{
|
||||
if (global_ctx == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 初始化全局上下文
|
||||
MENU_MEM_SET_ZERO(global_ctx, sizeof(MenuGlobalCtx));
|
||||
|
||||
// 初始化核心上下文
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
MENU_MEM_SET_ZERO(ctx, sizeof(MenuCoreCtx));
|
||||
ctx->is_initialized = true;
|
||||
ctx->current_state = MENU_STATE_INIT; // 设置初始状态
|
||||
|
||||
// 初始化事件队列
|
||||
ctx->last_refresh_tick = menu_utils_get_tick();
|
||||
|
||||
// 初始化其他功能模块(如果启用)
|
||||
#if MENU_CONFIG_ENABLE_PARAM
|
||||
global_ctx->param.count = 0;
|
||||
#endif
|
||||
|
||||
#if MENU_CONFIG_ENABLE_LANG
|
||||
global_ctx->lang.count = 0;
|
||||
global_ctx->lang.current_lang_id = 0; // 默认语言ID
|
||||
#endif
|
||||
|
||||
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||
global_ctx->modbus.count = 0;
|
||||
#endif
|
||||
|
||||
global_ctx->is_initialized = true;
|
||||
|
||||
// 初始状态转换:从INIT到NORMAL
|
||||
MenuEvent init_event = {
|
||||
.type = MENU_EVENT_NONE,
|
||||
.param = 0,
|
||||
.timestamp = menu_utils_get_tick(),
|
||||
.priority = MENU_EVENT_PRIORITY_NORMAL
|
||||
};
|
||||
menu_state_transition(global_ctx, init_event.type);
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 菜单组件反初始化(释放资源)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_deinit(MenuGlobalCtx* global_ctx)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 反初始化各功能模块
|
||||
// 目前都是静态内存,无需释放,只需重置状态
|
||||
MENU_MEM_SET_ZERO(global_ctx, sizeof(MenuGlobalCtx));
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送事件到菜单事件队列(如按键事件、自定义事件)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param type 事件类型
|
||||
* @param param 事件附加参数(可选,如按键长按时间)
|
||||
* @param priority 事件优先级
|
||||
* @return 错误码
|
||||
*/
|
||||
// Undefine the macro to avoid conflict with the function definition
|
||||
#undef menu_post_event
|
||||
|
||||
MenuErrCode menu_post_event(MenuGlobalCtx* global_ctx, MenuEventType type, uint32_t param, MenuEventPriority priority)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized)
|
||||
{
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
// 检查事件队列是否已满
|
||||
if (ctx->event_queue.count >= MENU_CONFIG_EVENT_QUEUE_LEN)
|
||||
{
|
||||
return MENU_ERR_EVENT_QUEUE_FULL;
|
||||
}
|
||||
|
||||
// 入队事件
|
||||
uint8_t idx = (ctx->event_queue.head + ctx->event_queue.count) % MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
ctx->event_queue.buffer[idx].type = type;
|
||||
ctx->event_queue.buffer[idx].param = param;
|
||||
ctx->event_queue.buffer[idx].timestamp = menu_utils_get_tick();
|
||||
ctx->event_queue.buffer[idx].priority = priority;
|
||||
ctx->event_queue.count++;
|
||||
|
||||
MENU_DEBUG("Event posted: type %d, param %lu, priority %d, count %d",
|
||||
type, param, priority, ctx->event_queue.count);
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册菜单节点(构建菜单树)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param parent_id 父节点ID(根节点填0)
|
||||
* @param node_id 当前节点ID(唯一,不可重复)
|
||||
* @param name_str 菜单名称字符串(或多语言索引)
|
||||
* @param enter_cb 进入该菜单的回调函数(NULL表示无)
|
||||
* @param exit_cb 退出该菜单的回调函数(NULL表示无)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_register_node(MenuGlobalCtx* global_ctx, MenuNodeId parent_id, MenuNodeId node_id, const char* name_str, MenuCallback enter_cb, MenuCallback exit_cb)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized || node_id == 0)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
// 查找空闲节点
|
||||
MenuNode* new_node = NULL;
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++)
|
||||
{
|
||||
if (!ctx->nodes[i].flags.is_registered)
|
||||
{
|
||||
new_node = &ctx->nodes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_node == NULL)
|
||||
{
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// 检查节点ID是否已存在
|
||||
if (menu_core_find_node(global_ctx, node_id) != NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 初始化新节点
|
||||
MENU_MEM_SET_ZERO(new_node, sizeof(MenuNode));
|
||||
new_node->id = node_id;
|
||||
new_node->parent_id = parent_id;
|
||||
new_node->name = name_str;
|
||||
new_node->enter_cb = enter_cb;
|
||||
new_node->exit_cb = exit_cb;
|
||||
new_node->flags.is_registered = true;
|
||||
new_node->flags.is_selected = false;
|
||||
|
||||
// 如果是根节点(parent_id为0)
|
||||
if (parent_id == 0)
|
||||
{
|
||||
// 检查是否已有根节点
|
||||
if (ctx->current_node_id != 0)
|
||||
{
|
||||
// 已有根节点,将新节点作为兄弟节点添加到根节点之后
|
||||
MenuNode* root_node = menu_core_find_node(global_ctx, ctx->current_node_id);
|
||||
while (root_node->next_sibling != NULL)
|
||||
{
|
||||
root_node = root_node->next_sibling;
|
||||
}
|
||||
root_node->next_sibling = new_node;
|
||||
new_node->prev_sibling = root_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 第一个根节点,设置为当前节点
|
||||
ctx->current_node_id = node_id;
|
||||
menu_core_stack_push(global_ctx, node_id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 查找父节点
|
||||
MenuNode* parent_node = menu_core_find_node(global_ctx, parent_id);
|
||||
if (parent_node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 将新节点添加到父节点的子节点列表中
|
||||
if (parent_node->first_child == NULL)
|
||||
{
|
||||
// 父节点还没有子节点,作为第一个子节点
|
||||
parent_node->first_child = new_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已有子节点,添加到最后一个子节点之后
|
||||
MenuNode* last_child = parent_node->first_child;
|
||||
while (last_child->next_sibling != NULL)
|
||||
{
|
||||
last_child = last_child->next_sibling;
|
||||
}
|
||||
last_child->next_sibling = new_node;
|
||||
new_node->prev_sibling = last_child;
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前选中的菜单节点ID
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param node_id 输出参数,当前节点ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_get_current_node(MenuGlobalCtx* global_ctx, MenuNodeId* node_id)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized || node_id == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
*node_id = ctx->current_node_id;
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 批量注册菜单节点
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param configs 菜单节点配置数组
|
||||
* @param config_count 配置数量
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_register_nodes(MenuGlobalCtx* global_ctx, const MenuNodeConfig* configs, uint16_t config_count)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized || configs == NULL || config_count == 0)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
MenuErrCode err = MENU_OK;
|
||||
for (uint16_t i = 0; i < config_count; i++)
|
||||
{
|
||||
err = menu_register_node(global_ctx, configs[i].parent_id, configs[i].node_id,
|
||||
configs[i].name, configs[i].enter_cb, configs[i].exit_cb);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
MENU_ERROR("Failed to register menu node %d at index %d: %s",
|
||||
configs[i].node_id, i, menu_err_code_to_str(err));
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查菜单组件是否已初始化
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @return 是否已初始化
|
||||
*/
|
||||
bool menu_is_initialized(MenuGlobalCtx* global_ctx)
|
||||
{
|
||||
if (global_ctx == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return global_ctx->is_initialized;
|
||||
}
|
||||
|
||||
#if MENU_CONFIG_ENABLE_MEM_MONITOR
|
||||
/**
|
||||
* @brief 获取菜单内存使用统计信息
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param stats 输出参数,内存使用统计信息
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_get_mem_stats(MenuGlobalCtx* global_ctx, MenuMemStats* stats)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized || stats == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
MenuCoreCtx* ctx = &global_ctx->core;
|
||||
|
||||
// 初始化统计信息
|
||||
MENU_MEM_SET_ZERO(stats, sizeof(MenuMemStats));
|
||||
|
||||
// 统计菜单节点使用情况
|
||||
stats->total_nodes = MENU_CONFIG_MAX_NODES;
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES; i++)
|
||||
{
|
||||
if (ctx->nodes[i].flags.is_registered)
|
||||
{
|
||||
stats->used_nodes++;
|
||||
}
|
||||
}
|
||||
|
||||
// 统计参数使用情况
|
||||
stats->total_params = MENU_CONFIG_MAX_PARAMS;
|
||||
#if MENU_CONFIG_ENABLE_PARAM
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++)
|
||||
{
|
||||
if (global_ctx->param.params[i].is_registered)
|
||||
{
|
||||
stats->used_params++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// 统计Modbus映射使用情况
|
||||
stats->total_modbus_maps = MENU_CONFIG_MAX_MODBUS_MAPS;
|
||||
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||
stats->used_modbus_maps = global_ctx->modbus.count;
|
||||
#endif
|
||||
|
||||
// 统计事件队列使用情况
|
||||
stats->event_queue_len = MENU_CONFIG_EVENT_QUEUE_LEN;
|
||||
stats->event_queue_used = ctx->event_queue.count;
|
||||
|
||||
MENU_DEBUG("Memory stats: nodes %d/%d, params %d/%d, modbus %d/%d, events %d/%d",
|
||||
stats->used_nodes, stats->total_nodes,
|
||||
stats->used_params, stats->total_params,
|
||||
stats->used_modbus_maps, stats->total_modbus_maps,
|
||||
stats->event_queue_used, stats->event_queue_len);
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
#endif // MENU_CONFIG_ENABLE_MEM_MONITOR
|
||||
@ -1,636 +0,0 @@
|
||||
/**
|
||||
* @file menu_modbus.c
|
||||
* @brief 菜单参数-Modbus寄存器映射:注册、查询、数据双向转换(工业级:类型安全、字节序适配)
|
||||
*/
|
||||
#include "../api/menu.h"
|
||||
#include "menu_core.h"
|
||||
#include "menu_data.h"
|
||||
#include "menu_def.h"
|
||||
#include "menu_modbus.h"
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#if MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||
|
||||
/************************** 内部辅助函数 **************************/
|
||||
/**
|
||||
* @brief 转换Modbus协议地址到实际偏移地址(如40001→0,00001→0)
|
||||
* @param reg_type 寄存器类型
|
||||
* @param reg_addr 协议地址(如40001)
|
||||
* @param offset 输出参数,实际偏移地址
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_modbus_convert_addr(ModbusRegType reg_type, uint16_t reg_addr, uint16_t* offset)
|
||||
{
|
||||
if (offset == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 校验协议地址范围(符合Modbus标准)
|
||||
switch (reg_type)
|
||||
{
|
||||
case MODBUS_REG_TYPE_COIL:
|
||||
if (reg_addr < 1 || reg_addr > 9999)
|
||||
{
|
||||
MENU_DEBUG("Modbus coil addr out of range: %d", reg_addr);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
*offset = reg_addr - 1;
|
||||
break;
|
||||
case MODBUS_REG_TYPE_DISCRETE_INPUT:
|
||||
if (reg_addr < 10001 || reg_addr > 19999)
|
||||
{
|
||||
MENU_DEBUG("Modbus discrete input addr out of range: %d", reg_addr);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
*offset = reg_addr - 10001;
|
||||
break;
|
||||
case MODBUS_REG_TYPE_HOLDING_REG:
|
||||
if (reg_addr < 40001 || reg_addr > 49999)
|
||||
{
|
||||
MENU_DEBUG("Modbus holding reg addr out of range: %d", reg_addr);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
*offset = reg_addr - 40001;
|
||||
break;
|
||||
case MODBUS_REG_TYPE_INPUT_REG:
|
||||
if (reg_addr < 30001 || reg_addr > 39999)
|
||||
{
|
||||
MENU_DEBUG("Modbus input reg addr out of range: %d", reg_addr);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
*offset = reg_addr - 30001;
|
||||
break;
|
||||
default:
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 校验实际偏移地址不超过配置的最大值
|
||||
// 注意:MENU_CONFIG_MODBUS_MAX_ADDR 不应超过 uint16_t 的最大值 0xFFFF
|
||||
if (*offset > (uint16_t)MENU_CONFIG_MODBUS_MAX_ADDR)
|
||||
{
|
||||
MENU_DEBUG("Modbus reg offset out of range: %d, max allowed: %d", *offset, MENU_CONFIG_MODBUS_MAX_ADDR);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 校验寄存器类型与参数类型的匹配性(工业级:类型安全)
|
||||
* @param param_type 参数类型
|
||||
* @param reg_type 寄存器类型
|
||||
* @return 错误码
|
||||
*/
|
||||
static MenuErrCode menu_modbus_check_type_match(MenuParamType param_type, ModbusRegType reg_type)
|
||||
{
|
||||
// 1位寄存器(线圈、离散输入)只能匹配布尔型/8位整型参数
|
||||
if (reg_type == MODBUS_REG_TYPE_COIL || reg_type == MODBUS_REG_TYPE_DISCRETE_INPUT)
|
||||
{
|
||||
if (param_type != MENU_PARAM_TYPE_INT8 && param_type != MENU_PARAM_TYPE_UINT8)
|
||||
{
|
||||
MENU_DEBUG("Modbus 1-bit reg not match param type: %d", param_type);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
// 16位寄存器(保持、输入)可匹配所有类型(16/32/浮点需多寄存器)
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 字节序转换(适配不同的Modbus字节序,工业级:兼容不同从站)
|
||||
* @param data 数据缓冲区(16位为单位)
|
||||
* @param len 数据长度(16位的数量)
|
||||
* @param byte_order 字节序(0-小端,1-大端,2-Modbus标准(字小端,字节大端))
|
||||
* @param is_reverse 是否反向转换(用于读取/写入)
|
||||
*/
|
||||
static void menu_modbus_byte_order_convert(uint16_t* data, uint8_t len, uint8_t byte_order, bool is_reverse)
|
||||
{
|
||||
if (data == NULL || len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认使用配置的字节序
|
||||
if (byte_order > 2)
|
||||
{
|
||||
byte_order = MENU_CONFIG_MODBUS_BYTE_ORDER;
|
||||
}
|
||||
|
||||
switch (byte_order)
|
||||
{
|
||||
case 0: // 小端(低字节在前,高字节在后)
|
||||
if (is_reverse)
|
||||
{
|
||||
for (uint8_t i = 0; i < len; i++)
|
||||
{
|
||||
data[i] = (data[i] << 8) | (data[i] >> 8);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1: // 大端(高字节在前,低字节在后)
|
||||
// 无需转换,Modbus默认是大端
|
||||
break;
|
||||
case 2: // Modbus标准(字小端,字节大端:如32位值0x12345678→0x5678 0x1234)
|
||||
if (is_reverse)
|
||||
{
|
||||
// 读取时:将Modbus顺序转换为主机顺序
|
||||
for (uint8_t i = 0; i < len; i += 2)
|
||||
{
|
||||
if (i + 1 < len)
|
||||
{
|
||||
uint16_t temp = data[i];
|
||||
data[i] = data[i + 1];
|
||||
data[i + 1] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 写入时:将主机顺序转换为Modbus顺序
|
||||
for (uint8_t i = 0; i < len; i += 2)
|
||||
{
|
||||
if (i + 1 < len)
|
||||
{
|
||||
uint16_t temp = data[i];
|
||||
data[i] = data[i + 1];
|
||||
data[i + 1] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查找Modbus映射(通过参数ID)
|
||||
* @param ctx 菜单全局上下文
|
||||
* @param param_id 参数ID
|
||||
* @return 映射内部结构体指针(NULL表示未找到)
|
||||
*/
|
||||
static ModbusMapInternal* menu_modbus_find_by_param(MenuGlobalCtx* ctx, uint16_t param_id)
|
||||
{
|
||||
if (ctx == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MenuModbusCtx* modbus_ctx = &ctx->modbus;
|
||||
for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++)
|
||||
{
|
||||
if (modbus_ctx->maps[i].is_registered && modbus_ctx->maps[i].pub.param_id == param_id)
|
||||
{
|
||||
return &modbus_ctx->maps[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查找Modbus映射(通过寄存器类型和地址)
|
||||
* @param ctx 菜单全局上下文
|
||||
* @param reg_type 寄存器类型
|
||||
* @param reg_addr 寄存器地址(协议地址)
|
||||
* @return 映射内部结构体指针(NULL表示未找到)
|
||||
*/
|
||||
static ModbusMapInternal* menu_modbus_find_by_reg(MenuGlobalCtx* ctx, ModbusRegType reg_type, uint16_t reg_addr)
|
||||
{
|
||||
if (ctx == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MenuModbusCtx* modbus_ctx = &ctx->modbus;
|
||||
for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++)
|
||||
{
|
||||
if (modbus_ctx->maps[i].is_registered &&
|
||||
modbus_ctx->maps[i].pub.reg_type == reg_type &&
|
||||
modbus_ctx->maps[i].pub.reg_addr == reg_addr)
|
||||
{
|
||||
return &modbus_ctx->maps[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/************************** 对外接口实现 **************************/
|
||||
MenuErrCode menu_modbus_map_register(MenuGlobalCtx* ctx, uint16_t param_id, ModbusRegType reg_type, uint16_t reg_addr, uint8_t reg_count, ModbusPerm perm, uint8_t byte_order)
|
||||
{
|
||||
// 1. 校验参数是否已注册(通过获取参数值间接检查)
|
||||
float dummy_val;
|
||||
MenuErrCode err = menu_param_get_value(ctx, param_id, &dummy_val);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
MENU_DEBUG("Modbus map param %d not found", param_id);
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 2. 校验寄存器地址和转换为偏移
|
||||
uint16_t reg_offset;
|
||||
err = menu_modbus_convert_addr(reg_type, reg_addr, ®_offset);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
// 3. 校验寄存器数量(至少1个)
|
||||
if (reg_count == 0)
|
||||
{
|
||||
MENU_DEBUG("Modbus map reg count is zero");
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 4. 由于无法直接获取参数类型,这里跳过类型匹配校验
|
||||
// 实际项目中,可以考虑在参数模块中添加获取参数类型的接口
|
||||
// err = menu_modbus_check_type_match(param->type, reg_type);
|
||||
// if (err != MENU_OK)
|
||||
// {
|
||||
// return err;
|
||||
// }
|
||||
|
||||
// 5. 校验读写权限(与寄存器类型联动,如离散输入只能只读)
|
||||
#if MENU_CONFIG_MODBUS_PERMISSION
|
||||
if ((reg_type == MODBUS_REG_TYPE_DISCRETE_INPUT || reg_type == MODBUS_REG_TYPE_INPUT_REG) && perm != MODBUS_PERM_READ_ONLY)
|
||||
{
|
||||
MENU_DEBUG("Modbus input reg only support read");
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
if (reg_type == MODBUS_REG_TYPE_COIL && perm == MODBUS_PERM_READ_ONLY)
|
||||
{
|
||||
MENU_DEBUG("Modbus coil not support read only");
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 6. 检查映射是否已存在
|
||||
if (menu_modbus_find_by_param(ctx, param_id) != NULL || menu_modbus_find_by_reg(ctx, reg_type, reg_addr) != NULL)
|
||||
{
|
||||
MENU_DEBUG("Modbus map already exists: param %d, reg %d:%d", param_id, reg_type, reg_addr);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 7. 查找空闲的映射位置(从全局上下文获取)
|
||||
MenuModbusCtx* modbus_ctx = &ctx->modbus;
|
||||
ModbusMapInternal* map = NULL;
|
||||
for (uint8_t i = 0; i < MENU_CONFIG_MAX_MODBUS_MAPS; i++)
|
||||
{
|
||||
if (!modbus_ctx->maps[i].is_registered)
|
||||
{
|
||||
map = &modbus_ctx->maps[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (map == NULL)
|
||||
{
|
||||
MENU_DEBUG("Modbus map out of memory: max %d", MENU_CONFIG_MAX_MODBUS_MAPS);
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// 8. 获取参数类型
|
||||
MenuParamType param_type;
|
||||
err = menu_param_get_type(ctx, param_id, ¶m_type);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
// 9. 校验寄存器类型与参数类型匹配
|
||||
err = menu_modbus_check_type_match(param_type, reg_type);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
// 10. 初始化映射关系
|
||||
MENU_MEM_SET_ZERO(map, sizeof(ModbusMapInternal));
|
||||
map->pub.param_id = param_id;
|
||||
map->pub.reg_type = reg_type;
|
||||
map->pub.reg_addr = reg_addr;
|
||||
map->pub.reg_count = reg_count;
|
||||
map->pub.perm = perm;
|
||||
map->pub.byte_order = byte_order;
|
||||
map->param_type = param_type;
|
||||
map->reg_addr_offset = reg_offset;
|
||||
map->is_registered = true;
|
||||
|
||||
MENU_DEBUG("Modbus map registered: param %d → reg %d:%d (count %d, perm %d)",
|
||||
param_id, reg_type, reg_addr, reg_count, perm);
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_modbus_map_query_by_param(MenuGlobalCtx* ctx, uint16_t param_id, ModbusMap* map)
|
||||
{
|
||||
if (ctx == NULL || map == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
ModbusMapInternal* internal_map = menu_modbus_find_by_param(ctx, param_id);
|
||||
if (internal_map == NULL)
|
||||
{
|
||||
MENU_DEBUG("Modbus map param %d not found", param_id);
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 复制对外暴露的信息
|
||||
*map = internal_map->pub;
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_modbus_map_query_by_reg(MenuGlobalCtx* ctx, ModbusRegType reg_type, uint16_t reg_addr, ModbusMap* map)
|
||||
{
|
||||
if (ctx == NULL || map == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
ModbusMapInternal* internal_map = menu_modbus_find_by_reg(ctx, reg_type, reg_addr);
|
||||
if (internal_map == NULL)
|
||||
{
|
||||
MENU_DEBUG("Modbus map reg %d:%d not found", reg_type, reg_addr);
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 复制对外暴露的信息
|
||||
*map = internal_map->pub;
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_modbus_map_param_to_reg(MenuGlobalCtx* ctx, uint16_t param_id, uint8_t* reg_buf, uint8_t* buf_len)
|
||||
{
|
||||
if (ctx == NULL || reg_buf == NULL || buf_len == NULL || *buf_len == 0)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 1. 查找映射关系
|
||||
ModbusMapInternal* map = menu_modbus_find_by_param(ctx, param_id);
|
||||
if (map == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 2. 校验读写权限
|
||||
#if MENU_CONFIG_MODBUS_PERMISSION
|
||||
if (map->pub.perm == MODBUS_PERM_READ_ONLY)
|
||||
{
|
||||
MENU_DEBUG("Modbus map param %d is read only", param_id);
|
||||
return MENU_ERR_HW_PORT_ERROR; // 复用原有错误码
|
||||
}
|
||||
#endif
|
||||
|
||||
// 3. 获取参数值
|
||||
float param_val;
|
||||
MenuErrCode err = menu_param_get_value(ctx, param_id, ¶m_val);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
MENU_DEBUG("Failed to get param value: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
// 4. 转换参数值到寄存器数据(按类型和寄存器数量)
|
||||
uint16_t* reg_data = (uint16_t*)reg_buf;
|
||||
uint8_t req_len = map->pub.reg_count;
|
||||
|
||||
// 检查缓冲区长度是否足够
|
||||
if (*buf_len < req_len * 2) // 寄存器数据按16位计算,每个16位占2字节
|
||||
{
|
||||
MENU_DEBUG("Modbus reg buf too small: need %d bytes, got %d bytes", req_len * 2, *buf_len);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 清空缓冲区
|
||||
memset(reg_data, 0, req_len * 2);
|
||||
|
||||
// 根据参数类型转换
|
||||
// 使用之前保存的参数类型,不需要再次调用menu_param_find
|
||||
switch (map->pub.reg_type)
|
||||
{
|
||||
case MODBUS_REG_TYPE_COIL:
|
||||
case MODBUS_REG_TYPE_DISCRETE_INPUT:
|
||||
// 1位寄存器:取参数值的最低位
|
||||
if (req_len != 1)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for 1-bit reg: %d, expected 1", req_len);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
reg_data[0] = (uint16_t)(param_val > 0 ? 1 : 0);
|
||||
break;
|
||||
case MODBUS_REG_TYPE_HOLDING_REG:
|
||||
case MODBUS_REG_TYPE_INPUT_REG:
|
||||
// 16位寄存器:根据参数类型和数量转换
|
||||
if (map->param_type == MENU_PARAM_TYPE_INT16 || map->param_type == MENU_PARAM_TYPE_UINT16)
|
||||
{
|
||||
if (req_len != 1)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for 16-bit param: %d, expected 1", req_len);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
reg_data[0] = (uint16_t)param_val;
|
||||
}
|
||||
else if (map->param_type == MENU_PARAM_TYPE_INT32 || map->param_type == MENU_PARAM_TYPE_UINT32)
|
||||
{
|
||||
if (req_len != 2)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for 32-bit param: %d, expected 2", req_len);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
uint32_t val = (uint32_t)param_val;
|
||||
reg_data[0] = (uint16_t)(val & 0xFFFF);
|
||||
reg_data[1] = (uint16_t)((val >> 16) & 0xFFFF);
|
||||
}
|
||||
else if (map->param_type == MENU_PARAM_TYPE_FLOAT)
|
||||
{
|
||||
if (req_len != 2)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for float param: %d, expected 2", req_len);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
// 浮点型转换为32位二进制,占2个16位寄存器
|
||||
union {
|
||||
float f;
|
||||
uint32_t u32;
|
||||
} float_val;
|
||||
float_val.f = param_val;
|
||||
reg_data[0] = (uint16_t)(float_val.u32 & 0xFFFF);
|
||||
reg_data[1] = (uint16_t)((float_val.u32 >> 16) & 0xFFFF);
|
||||
}
|
||||
else if (map->param_type == MENU_PARAM_TYPE_INT8 || map->param_type == MENU_PARAM_TYPE_UINT8)
|
||||
{
|
||||
if (req_len != 1)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for 8-bit param: %d, expected 1", req_len);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
reg_data[0] = (uint16_t)param_val;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
MENU_DEBUG("Invalid reg type: %d", map->pub.reg_type);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 5. 字节序转换(写入寄存器前)
|
||||
menu_modbus_byte_order_convert(reg_data, req_len, map->pub.byte_order, false);
|
||||
|
||||
// 6. 设置实际写入长度
|
||||
*buf_len = req_len * 2;
|
||||
|
||||
MENU_DEBUG("Modbus param %d to reg: val %f → reg data len %d", param_id, param_val, *buf_len);
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
MenuErrCode menu_modbus_map_reg_to_param(MenuGlobalCtx* ctx, uint16_t param_id, const uint8_t* reg_buf, uint8_t buf_len)
|
||||
{
|
||||
if (ctx == NULL || reg_buf == NULL || buf_len == 0)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 1. 查找映射关系
|
||||
ModbusMapInternal* map = menu_modbus_find_by_param(ctx, param_id);
|
||||
if (map == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 2. 校验读写权限
|
||||
#if MENU_CONFIG_MODBUS_PERMISSION
|
||||
if (map->pub.perm == MODBUS_PERM_WRITE_ONLY)
|
||||
{
|
||||
MENU_DEBUG("Modbus map param %d is write only", param_id);
|
||||
return MENU_ERR_HW_PORT_ERROR; // 复用原有错误码
|
||||
}
|
||||
#endif
|
||||
|
||||
// 3. 校验缓冲区长度
|
||||
uint8_t req_len = map->pub.reg_count * 2;
|
||||
if (buf_len < req_len)
|
||||
{
|
||||
MENU_DEBUG("Modbus reg buf too small: need %d, got %d", req_len, buf_len);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 4. 复制寄存器数据并进行字节序转换(读取寄存器后)
|
||||
uint16_t reg_data[MENU_CONFIG_MAX_MODBUS_MAPS * 2] = {0}; // 足够大的临时缓冲区
|
||||
|
||||
// 复制数据到临时缓冲区
|
||||
memcpy(reg_data, reg_buf, req_len);
|
||||
|
||||
// 字节序转换
|
||||
menu_modbus_byte_order_convert(reg_data, map->pub.reg_count, map->pub.byte_order, true);
|
||||
|
||||
// 5. 转换寄存器数据到参数值
|
||||
float param_val = 0.0f;
|
||||
// 使用之前保存的参数类型,不需要再次调用menu_param_find
|
||||
switch (map->pub.reg_type)
|
||||
{
|
||||
case MODBUS_REG_TYPE_COIL:
|
||||
case MODBUS_REG_TYPE_DISCRETE_INPUT:
|
||||
// 1位寄存器:0→0,非0→1
|
||||
if (map->pub.reg_count != 1)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for 1-bit reg: %d, expected 1", map->pub.reg_count);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
param_val = (reg_data[0] & 0x01) ? 1.0f : 0.0f;
|
||||
break;
|
||||
case MODBUS_REG_TYPE_HOLDING_REG:
|
||||
case MODBUS_REG_TYPE_INPUT_REG:
|
||||
// 16位寄存器:根据参数类型转换
|
||||
if (map->param_type == MENU_PARAM_TYPE_INT8)
|
||||
{
|
||||
if (map->pub.reg_count != 1)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for 8-bit param: %d, expected 1", map->pub.reg_count);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
param_val = (int8_t)reg_data[0];
|
||||
}
|
||||
else if (map->param_type == MENU_PARAM_TYPE_UINT8)
|
||||
{
|
||||
if (map->pub.reg_count != 1)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for 8-bit param: %d, expected 1", map->pub.reg_count);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
param_val = (uint8_t)reg_data[0];
|
||||
}
|
||||
else if (map->param_type == MENU_PARAM_TYPE_INT16)
|
||||
{
|
||||
if (map->pub.reg_count != 1)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for 16-bit param: %d, expected 1", map->pub.reg_count);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
param_val = (int16_t)reg_data[0];
|
||||
}
|
||||
else if (map->param_type == MENU_PARAM_TYPE_UINT16)
|
||||
{
|
||||
if (map->pub.reg_count != 1)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for 16-bit param: %d, expected 1", map->pub.reg_count);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
param_val = (uint16_t)reg_data[0];
|
||||
}
|
||||
else if (map->param_type == MENU_PARAM_TYPE_INT32)
|
||||
{
|
||||
if (map->pub.reg_count != 2)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for 32-bit param: %d, expected 2", map->pub.reg_count);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
uint32_t val = ((uint32_t)reg_data[1] << 16) | reg_data[0];
|
||||
param_val = (int32_t)val;
|
||||
}
|
||||
else if (map->param_type == MENU_PARAM_TYPE_UINT32)
|
||||
{
|
||||
if (map->pub.reg_count != 2)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for 32-bit param: %d, expected 2", map->pub.reg_count);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
uint32_t val = ((uint32_t)reg_data[1] << 16) | reg_data[0];
|
||||
param_val = val;
|
||||
}
|
||||
else if (map->param_type == MENU_PARAM_TYPE_FLOAT)
|
||||
{
|
||||
if (map->pub.reg_count != 2)
|
||||
{
|
||||
MENU_DEBUG("Invalid reg count for float param: %d, expected 2", map->pub.reg_count);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
union {
|
||||
float f;
|
||||
uint32_t u32;
|
||||
} float_val;
|
||||
float_val.u32 = ((uint32_t)reg_data[1] << 16) | reg_data[0];
|
||||
param_val = float_val.f;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
MENU_DEBUG("Invalid reg type: %d", map->pub.reg_type);
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 6. 设置参数值(自动进行范围检查)
|
||||
MenuErrCode err = menu_param_set_value(ctx, param_id, param_val);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
MENU_DEBUG("Modbus reg to param %d: reg data len %d → val %f", param_id, buf_len, param_val);
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // MENU_CONFIG_ENABLE_MODBUS_MAP
|
||||
@ -1,207 +0,0 @@
|
||||
/**
|
||||
* @file menu_lang.c
|
||||
* @brief 菜单多语言支持模块(可裁剪功能)
|
||||
*/
|
||||
#include "menu_core.h"
|
||||
#include "menu_data.h"
|
||||
|
||||
#if MENU_CONFIG_ENABLE_LANG
|
||||
|
||||
/************************** 内部函数声明 **************************/
|
||||
static const char* menu_lang_find_str(MenuGlobalCtx* global_ctx, uint16_t str_id, uint8_t lang_id);
|
||||
|
||||
/**
|
||||
* @brief 查找语言字符串(通过字符串ID和语言ID)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param str_id 字符串ID
|
||||
* @param lang_id 语言ID
|
||||
* @return 语言字符串(NULL表示未找到)
|
||||
*/
|
||||
static const char* menu_lang_find_str(MenuGlobalCtx* global_ctx, uint16_t str_id, uint8_t lang_id)
|
||||
{
|
||||
MenuLangCtx* lang_ctx = &global_ctx->lang;
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++)
|
||||
{
|
||||
if (lang_ctx->strs[i].str_id == str_id && lang_ctx->strs[i].lang_id == lang_id)
|
||||
{
|
||||
return lang_ctx->strs[i].str;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册语言字符串
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param str_id 字符串ID
|
||||
* @param lang_id 语言ID
|
||||
* @param str 字符串内容
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_register_str(MenuGlobalCtx* global_ctx, uint16_t str_id, uint8_t lang_id, const char* str)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized)
|
||||
{
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (lang_id >= MENU_CONFIG_MAX_LANGS || str == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
MenuLangCtx* lang_ctx = &global_ctx->lang;
|
||||
// 查找空闲位置或已存在的字符串条目
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++)
|
||||
{
|
||||
if (!lang_ctx->strs[i].str || (lang_ctx->strs[i].str_id == str_id && lang_ctx->strs[i].lang_id == lang_id))
|
||||
{
|
||||
lang_ctx->strs[i].str_id = str_id;
|
||||
lang_ctx->strs[i].lang_id = lang_id;
|
||||
lang_ctx->strs[i].str = str;
|
||||
return MENU_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前语言的字符串
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param str_id 字符串ID
|
||||
* @return 字符串内容(NULL表示未找到)
|
||||
*/
|
||||
const char* menu_lang_get_str(MenuGlobalCtx* global_ctx, uint16_t str_id)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MenuLangCtx* lang_ctx = &global_ctx->lang;
|
||||
const char* str = menu_lang_find_str(global_ctx, str_id, lang_ctx->current_lang_id);
|
||||
if (str == NULL)
|
||||
{
|
||||
// 如果当前语言未找到,尝试使用默认语言(0)
|
||||
str = menu_lang_find_str(global_ctx, str_id, 0);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置当前语言
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param lang_id 语言ID(如0-中文,1-英文)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_set_current(MenuGlobalCtx* global_ctx, uint8_t lang_id)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized)
|
||||
{
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (lang_id >= MENU_CONFIG_MAX_LANGS)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
MenuLangCtx* lang_ctx = &global_ctx->lang;
|
||||
lang_ctx->current_lang_id = lang_id;
|
||||
MENU_DEBUG("Language changed to: %d", lang_id);
|
||||
|
||||
// 语言切换后,刷新菜单显示
|
||||
menu_core_refresh_display(global_ctx);
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前语言
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param lang_id 输出参数,当前语言ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_get_current(MenuGlobalCtx* global_ctx, uint8_t* lang_id)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized || lang_id == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
MenuLangCtx* lang_ctx = &global_ctx->lang;
|
||||
*lang_id = lang_ctx->current_lang_id;
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取支持的最大语言数量
|
||||
* @return 最大语言数量
|
||||
*/
|
||||
uint8_t menu_lang_get_max_langs(void)
|
||||
{
|
||||
return MENU_CONFIG_MAX_LANGS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 批量注册语言字符串
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param strs 语言字符串数组
|
||||
* @param count 字符串数量
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_register_strs(MenuGlobalCtx* global_ctx, const MenuLangStr* strs, uint16_t count)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized || strs == NULL || count == 0)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
MenuErrCode err = MENU_OK;
|
||||
for (uint16_t i = 0; i < count; i++)
|
||||
{
|
||||
err = menu_lang_register_str(global_ctx, strs[i].str_id, strs[i].lang_id, strs[i].str);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
MENU_ERROR("Failed to register lang str %d at index %d: %s",
|
||||
strs[i].str_id, i, menu_err_code_to_str(err));
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载语言包(动态加载语言字符串)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param lang_id 语言ID
|
||||
* @param strs 语言字符串数组
|
||||
* @param count 字符串数量
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_lang_load_pack(MenuGlobalCtx* global_ctx, uint8_t lang_id, const MenuLangStr* strs, uint16_t count)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized || strs == NULL || count == 0)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 先清除该语言的所有字符串
|
||||
MenuLangCtx* lang_ctx = &global_ctx->lang;
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_NODES * MENU_CONFIG_MAX_LANGS; i++)
|
||||
{
|
||||
if (lang_ctx->strs[i].lang_id == lang_id)
|
||||
{
|
||||
lang_ctx->strs[i].str_id = 0;
|
||||
lang_ctx->strs[i].lang_id = 0;
|
||||
lang_ctx->strs[i].str = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// 再注册新的字符串
|
||||
return menu_lang_register_strs(global_ctx, strs, count);
|
||||
}
|
||||
|
||||
#endif // MENU_CONFIG_ENABLE_LANG
|
||||
@ -1,399 +0,0 @@
|
||||
/**
|
||||
* @file menu_param.c
|
||||
* @brief 菜单参数管理模块(可裁剪功能)
|
||||
*/
|
||||
#include "menu_core.h"
|
||||
#include "menu_data.h"
|
||||
|
||||
#if MENU_CONFIG_ENABLE_PARAM
|
||||
|
||||
/************************** 内部函数声明 **************************/
|
||||
static MenuParam* menu_param_find(MenuGlobalCtx* global_ctx, uint16_t param_id);
|
||||
static void menu_param_float_to_internal(MenuParam* param, float value);
|
||||
static float menu_param_internal_to_float(const MenuParam* param);
|
||||
|
||||
/**
|
||||
* @brief 查找参数(通过参数ID,内部使用)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param param_id 参数ID
|
||||
* @return 参数指针(NULL表示未找到)
|
||||
*/
|
||||
static MenuParam* menu_param_find(MenuGlobalCtx* global_ctx, uint16_t param_id)
|
||||
{
|
||||
if (global_ctx == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MenuParamCtx* param_ctx = &global_ctx->param;
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++)
|
||||
{
|
||||
if (param_ctx->params[i].is_registered && param_ctx->params[i].id == param_id)
|
||||
{
|
||||
return ¶m_ctx->params[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将浮点值转换为参数内部值(根据类型)
|
||||
* @param param 参数指针
|
||||
* @param value 浮点值
|
||||
* @return 转换后的内部值(通过指针返回)
|
||||
*/
|
||||
static void menu_param_float_to_internal(MenuParam* param, float value)
|
||||
{
|
||||
MENU_ASSERT(param != NULL);
|
||||
|
||||
// 确保值在范围内
|
||||
if (value < param->min_val)
|
||||
{
|
||||
value = param->min_val;
|
||||
}
|
||||
else if (value > param->max_val)
|
||||
{
|
||||
value = param->max_val;
|
||||
}
|
||||
|
||||
// 应用缩放因子
|
||||
value /= param->scale;
|
||||
|
||||
// 根据参数类型转换
|
||||
switch (param->type)
|
||||
{
|
||||
case MENU_PARAM_TYPE_INT8:
|
||||
param->value.i8 = (int8_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT8:
|
||||
param->value.u8 = (uint8_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_INT16:
|
||||
param->value.i16 = (int16_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT16:
|
||||
param->value.u16 = (uint16_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_INT32:
|
||||
param->value.i32 = (int32_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT32:
|
||||
param->value.u32 = (uint32_t)value;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_FLOAT:
|
||||
param->value.f = value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将参数内部值转换为浮点值(根据类型)
|
||||
* @param param 参数指针
|
||||
* @return 浮点值
|
||||
*/
|
||||
static float menu_param_internal_to_float(const MenuParam* param)
|
||||
{
|
||||
MENU_ASSERT(param != NULL);
|
||||
|
||||
float value = 0.0f;
|
||||
|
||||
// 根据参数类型转换
|
||||
switch (param->type)
|
||||
{
|
||||
case MENU_PARAM_TYPE_INT8:
|
||||
value = (float)param->value.i8;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT8:
|
||||
value = (float)param->value.u8;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_INT16:
|
||||
value = (float)param->value.i16;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT16:
|
||||
value = (float)param->value.u16;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_INT32:
|
||||
value = (float)param->value.i32;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_UINT32:
|
||||
value = (float)param->value.u32;
|
||||
break;
|
||||
case MENU_PARAM_TYPE_FLOAT:
|
||||
value = param->value.f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 应用缩放因子
|
||||
return value * param->scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册参数到菜单节点(参数管理功能)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param node_id 菜单节点ID(参数与菜单绑定)
|
||||
* @param param_id 参数ID(唯一)
|
||||
* @param type 参数类型
|
||||
* @param min_val 最小值(浮点型,内部自动转换)
|
||||
* @param max_val 最大值(浮点型,内部自动转换)
|
||||
* @param default_val 默认值(浮点型,内部自动转换)
|
||||
* @param scale 缩放因子(如0.1表示保留1位小数)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_register(MenuGlobalCtx* global_ctx, MenuNodeId node_id, uint16_t param_id, MenuParamType type, float min_val, float max_val, float default_val, float scale)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized)
|
||||
{
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (scale <= 0.0f)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 查找菜单节点
|
||||
MenuNode* node = menu_core_find_node(global_ctx, node_id);
|
||||
if (node == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 检查参数ID是否已存在
|
||||
if (menu_param_find(global_ctx, param_id) != NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 查找空闲参数位置
|
||||
MenuParamCtx* param_ctx = &global_ctx->param;
|
||||
MenuParam* param = NULL;
|
||||
for (uint16_t i = 0; i < MENU_CONFIG_MAX_PARAMS; i++)
|
||||
{
|
||||
if (!param_ctx->params[i].is_registered)
|
||||
{
|
||||
param = ¶m_ctx->params[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (param == NULL)
|
||||
{
|
||||
return MENU_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// 初始化参数
|
||||
MENU_MEM_SET_ZERO(param, sizeof(MenuParam));
|
||||
param->id = param_id;
|
||||
param->type = type;
|
||||
param->min_val = min_val;
|
||||
param->max_val = max_val;
|
||||
param->scale = scale;
|
||||
param->is_registered = true;
|
||||
|
||||
// 设置默认值
|
||||
menu_param_float_to_internal(param, default_val);
|
||||
// 保存默认值
|
||||
menu_param_float_to_internal((MenuParam*)¶m->default_val, default_val);
|
||||
|
||||
// 将参数与菜单节点绑定
|
||||
node->param_id = param_id;
|
||||
|
||||
MENU_DEBUG("Param registered: ID=%d, Type=%d, Range=[%0.2f, %0.2f], Default=%0.2f, Scale=%0.2f",
|
||||
param_id, type, min_val, max_val, default_val, scale);
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置参数值
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param param_id 参数ID
|
||||
* @param value 新值(浮点型,内部自动转换)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_set_value(MenuGlobalCtx* global_ctx, uint16_t param_id, float value)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized)
|
||||
{
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// 查找参数
|
||||
MenuParam* param = menu_param_find(global_ctx, param_id);
|
||||
if (param == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 设置新值
|
||||
menu_param_float_to_internal(param, value);
|
||||
|
||||
MENU_DEBUG("Param set: ID=%d, Value=%0.2f (Internal: %0.2f)",
|
||||
param_id, value, menu_param_internal_to_float(param));
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取参数值
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param param_id 参数ID
|
||||
* @param value 输出参数,当前值(浮点型)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_get_value(MenuGlobalCtx* global_ctx, uint16_t param_id, float* value)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized || value == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
// 查找参数
|
||||
MenuParam* param = menu_param_find(global_ctx, param_id);
|
||||
if (param == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 获取当前值
|
||||
*value = menu_param_internal_to_float(param);
|
||||
|
||||
MENU_DEBUG("Param get: ID=%d, Value=%0.2f", param_id, *value);
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将参数恢复为默认值
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param param_id 参数ID
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_restore_default(MenuGlobalCtx* global_ctx, uint16_t param_id)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized)
|
||||
{
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// 查找参数
|
||||
MenuParam* param = menu_param_find(global_ctx, param_id);
|
||||
if (param == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 恢复默认值
|
||||
float default_val = menu_param_internal_to_float((const MenuParam*)¶m->default_val);
|
||||
return menu_param_set_value(global_ctx, param_id, default_val);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 增加参数值(用于菜单上下键调整)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param param_id 参数ID
|
||||
* @param step 步长(浮点型)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_increase(MenuGlobalCtx* global_ctx, uint16_t param_id, float step)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized)
|
||||
{
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
float current_val = 0.0f;
|
||||
MenuErrCode err = menu_param_get_value(global_ctx, param_id, ¤t_val);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
return menu_param_set_value(global_ctx, param_id, current_val + step);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 减少参数值(用于菜单上下键调整)
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param param_id 参数ID
|
||||
* @param step 步长(浮点型)
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_decrease(MenuGlobalCtx* global_ctx, uint16_t param_id, float step)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized)
|
||||
{
|
||||
return MENU_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
float current_val = 0.0f;
|
||||
MenuErrCode err = menu_param_get_value(global_ctx, param_id, ¤t_val);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
return menu_param_set_value(global_ctx, param_id, current_val - step);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取参数类型
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param param_id 参数ID
|
||||
* @param type 输出参数,参数类型
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_get_type(MenuGlobalCtx* global_ctx, uint16_t param_id, MenuParamType* type)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized || type == NULL)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
MenuParam* param = menu_param_find(global_ctx, param_id);
|
||||
if (param == NULL)
|
||||
{
|
||||
return MENU_ERR_NODE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// 返回参数类型
|
||||
*type = param->type;
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 批量注册参数到菜单节点
|
||||
* @param global_ctx 全局上下文指针
|
||||
* @param configs 参数配置数组
|
||||
* @param config_count 配置数量
|
||||
* @return 错误码
|
||||
*/
|
||||
MenuErrCode menu_param_register_batch(MenuGlobalCtx* global_ctx, const MenuParamConfig* configs, uint16_t config_count)
|
||||
{
|
||||
if (global_ctx == NULL || !global_ctx->is_initialized || configs == NULL || config_count == 0)
|
||||
{
|
||||
return MENU_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
MenuErrCode err = MENU_OK;
|
||||
for (uint16_t i = 0; i < config_count; i++)
|
||||
{
|
||||
err = menu_param_register(global_ctx, configs[i].node_id, configs[i].param_id,
|
||||
configs[i].type, configs[i].min_val, configs[i].max_val,
|
||||
configs[i].default_val, configs[i].scale);
|
||||
if (err != MENU_OK)
|
||||
{
|
||||
MENU_ERROR("Failed to register param %d at index %d: %s",
|
||||
configs[i].param_id, i, menu_err_code_to_str(err));
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return MENU_OK;
|
||||
}
|
||||
|
||||
#endif // MENU_CONFIG_ENABLE_PARAM
|
||||
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* @file menu_param.h
|
||||
* @brief 菜单参数管理内部类型定义(用户无需关心)
|
||||
*/
|
||||
#ifndef MENU_PARAM_H
|
||||
#define MENU_PARAM_H
|
||||
|
||||
#include "menu_core.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief 参数值共用体(支持多种数据类型)
|
||||
*/
|
||||
typedef union {
|
||||
int8_t i8; ///< 8位有符号整数
|
||||
uint8_t u8; ///< 8位无符号整数
|
||||
int16_t i16; ///< 16位有符号整数
|
||||
uint16_t u16; ///< 16位无符号整数
|
||||
int32_t i32; ///< 32位有符号整数
|
||||
uint32_t u32; ///< 32位无符号整数
|
||||
float f; ///< 浮点数
|
||||
} MenuParamValue;
|
||||
|
||||
/**
|
||||
* @brief 参数结构体(参数管理的核心单元)
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t id; ///< 参数ID(唯一)
|
||||
MenuParamType type; ///< 参数类型
|
||||
MenuParamValue value; ///< 当前值
|
||||
MenuParamValue default_val;///< 默认值
|
||||
float min_val; ///< 最小值(浮点表示)
|
||||
float max_val; ///< 最大值(浮点表示)
|
||||
float scale; ///< 缩放因子
|
||||
bool is_registered : 1; ///< 是否已注册
|
||||
} MenuParam;
|
||||
|
||||
#endif // MENU_PARAM_H
|
||||
@ -1,137 +0,0 @@
|
||||
/**
|
||||
* @file menu_utils.c
|
||||
* @brief 菜单工具模块:断言、打印、系统滴答等
|
||||
*/
|
||||
#include "menu_def.h"
|
||||
#include "menu_port.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* @brief 错误码字符串映射
|
||||
*/
|
||||
static const char* menu_err_str[] = {
|
||||
"OK", ///< MENU_OK
|
||||
"Invalid parameter", ///< MENU_ERR_INVALID_PARAM
|
||||
"Out of memory", ///< MENU_ERR_OUT_OF_MEMORY
|
||||
"Node not found", ///< MENU_ERR_NODE_NOT_FOUND
|
||||
"Stack overflow", ///< MENU_ERR_STACK_OVERFLOW
|
||||
"Stack underflow", ///< MENU_ERR_STACK_UNDERFLOW
|
||||
"Event queue full", ///< MENU_ERR_EVENT_QUEUE_FULL
|
||||
"Feature not supported", ///< MENU_ERR_NOT_SUPPORTED
|
||||
"Hardware port error", ///< MENU_ERR_HW_PORT_ERROR
|
||||
"Component not initialized", ///< MENU_ERR_NOT_INITIALIZED
|
||||
"Invalid context pointer" ///< MENU_ERR_INVALID_CONTEXT
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 断言失败处理函数
|
||||
* @param file 文件名
|
||||
* @param line 行号
|
||||
*/
|
||||
void menu_utils_assert_failed(const char* file, uint32_t line)
|
||||
{
|
||||
// 调用port层的打印接口输出断言信息
|
||||
menu_utils_log(MENU_LOG_LEVEL_CRITICAL, file, line, "Assertion failed");
|
||||
|
||||
// 可以在这里添加其他处理,如触发硬件异常、保存上下文等
|
||||
// 例如:menu_port_trigger_exception();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 通用日志函数(对接port层的硬件打印接口)
|
||||
* @param level 日志级别
|
||||
* @param file 文件名
|
||||
* @param line 行号
|
||||
* @param fmt 格式化字符串
|
||||
* @param ... 可变参数
|
||||
*/
|
||||
void menu_utils_log(MenuLogLevel level, const char* file, uint32_t line, const char* fmt, ...)
|
||||
{
|
||||
// Explicitly cast unused parameters to void to suppress warnings
|
||||
(void)level;
|
||||
(void)file;
|
||||
(void)line;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
// Get the driver and use its printf directly
|
||||
const MenuPortDriver* driver = menu_port_get_driver();
|
||||
if (driver != NULL && driver->printf != NULL)
|
||||
{
|
||||
// Output the log message
|
||||
driver->printf(fmt, args);
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 错误码转换为字符串
|
||||
* @param err_code 错误码
|
||||
* @return 错误信息字符串
|
||||
*/
|
||||
const char* menu_err_code_to_str(MenuErrCode err_code)
|
||||
{
|
||||
if (err_code < sizeof(menu_err_str) / sizeof(menu_err_str[0])) {
|
||||
return menu_err_str[err_code];
|
||||
} else {
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 调试打印函数(兼容旧接口,对接port层的硬件打印接口)
|
||||
* @param fmt 格式化字符串
|
||||
* @param ... 可变参数
|
||||
*/
|
||||
void menu_utils_printf(const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
// 调用port层的可变参数打印接口
|
||||
menu_port_printf(fmt, args);
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取系统滴答时间(ms,对接port层)
|
||||
* @return 当前滴答时间
|
||||
*/
|
||||
uint32_t menu_utils_get_tick(void)
|
||||
{
|
||||
// 调用port层的系统滴答获取接口
|
||||
return menu_port_get_tick();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算两个32位无符号整数的差值(处理溢出情况)
|
||||
* @param now 当前时间
|
||||
* @param last 上次时间
|
||||
* @return 差值(ms)
|
||||
*/
|
||||
uint32_t menu_utils_diff_tick(uint32_t now, uint32_t last)
|
||||
{
|
||||
if (now >= last)
|
||||
{
|
||||
return now - last;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理溢出情况
|
||||
return (UINT32_MAX - last) + now + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 延迟函数(毫秒级,对接port层)
|
||||
* @param ms 延迟时间(ms)
|
||||
*/
|
||||
void menu_utils_delay_ms(uint32_t ms)
|
||||
{
|
||||
// 调用port层的延迟接口
|
||||
menu_port_delay_ms(ms);
|
||||
}
|
||||
Reference in New Issue
Block a user