Files
tronone-h7-scaffold/User_Code/module/software/prts/prts.c

1006 lines
32 KiB
C
Raw Normal View History

2026-07-20 21:59:16 +08:00
#include "prts.h"
2026-07-20 23:34:13 +08:00
#include "bsp_adc.h"
2026-07-20 21:59:16 +08:00
#include "lvgl.h"
2026-07-20 23:34:13 +08:00
#include "main.h"
2026-07-20 21:59:16 +08:00
#include "prts_lvgl_port.h"
2026-07-20 23:34:13 +08:00
#include "src/debugging/sysmon/lv_sysmon.h"
2026-07-20 21:59:16 +08:00
#include <limits.h>
#include <stddef.h>
2026-07-20 23:34:13 +08:00
#include <stdint.h>
2026-07-20 21:59:16 +08:00
#include <stdio.h>
2026-07-20 23:34:13 +08:00
#include <string.h>
#define PRTS_SCREEN_W 280
#define PRTS_SCREEN_H 240
#define PRTS_STATUS_H 30
#define PRTS_HINT_H 24
#define PRTS_CONTENT_H (PRTS_SCREEN_H - PRTS_STATUS_H - PRTS_HINT_H)
#define PRTS_CARD_W 130
#define PRTS_CARD_H 140
#define PRTS_CARD_GAP 15
#define PRTS_CARD_PAD_HOR 75
#define PRTS_DOT_SIZE_NORMAL 10
#define PRTS_DOT_SIZE_ACTIVE 10
2026-07-20 23:34:13 +08:00
#define PRTS_MENU_CARD_COUNT 3U
#define PRTS_STATUS_BRAND_X 18
#define PRTS_STATUS_BRAND_W 54
#define PRTS_STATUS_TITLE_W 130
2026-07-20 23:34:13 +08:00
#define PRTS_VALUE_PANEL_WIDTH 228
#define PRTS_VALUE_PANEL_HEIGHT 34
#define PRTS_VALUE_ACCENT_WIDTH 4
#define PRTS_VALUE_BG_COLOR 0x132530
#define PRTS_JOY_DEBOUNCE_MS 25U
#define PRTS_JOY_LONG_PRESS_MS 700U
#define PRTS_MONITOR_UPDATE_MS 150U
#define PRTS_SCREEN_ANIM_MS 0U
2026-07-20 23:34:13 +08:00
typedef enum
{
PRTS_PAGE_MAIN = 0,
PRTS_PAGE_STATUS,
PRTS_PAGE_MONITOR,
PRTS_PAGE_ASSETS
} PRTS_Page_t;
2026-07-20 21:59:16 +08:00
2026-07-20 23:34:13 +08:00
typedef enum
{
PRTS_JOY_NONE = 0,
PRTS_JOY_ENTER,
PRTS_JOY_UP,
PRTS_JOY_DOWN,
PRTS_JOY_LEFT,
PRTS_JOY_RIGHT
} PRTS_JoyKey_t;
typedef struct
{
const char *title;
const char *subtitle;
const char *hint;
uint32_t color;
PRTS_Page_t page;
} PRTS_MenuCard_t;
static const PRTS_MenuCard_t prts_menu_cards_data[PRTS_MENU_CARD_COUNT] = {
{"状态", "温度 / 遥控", "STATUS", 0x40C4FF, PRTS_PAGE_STATUS},
{"监视", "摇杆 / 性能", "MONITOR", 0xFFD666, PRTS_PAGE_MONITOR},
{"资源", "字体 / 存储", "ASSETS", 0x25D366, PRTS_PAGE_ASSETS},
};
2026-07-20 21:59:16 +08:00
static bool prts_initialized = false;
static bool prts_lvgl_initialized = false;
2026-07-20 23:34:13 +08:00
static lv_group_t *prts_key_group = NULL;
static lv_indev_t *prts_key_indev = NULL;
static lv_style_t prts_style_card_normal;
static lv_style_t prts_style_card_focused;
static lv_style_t prts_style_title_text;
static lv_style_transition_dsc_t prts_card_transition;
static bool prts_styles_ready = false;
static lv_obj_t *prts_main_screen = NULL;
static lv_obj_t *prts_status_screen = NULL;
static lv_obj_t *prts_monitor_screen = NULL;
static lv_obj_t *prts_assets_screen = NULL;
static lv_obj_t *prts_main_status_title = NULL;
static lv_obj_t *prts_menu_cards[PRTS_MENU_CARD_COUNT] = {NULL};
static lv_obj_t *prts_menu_dots[PRTS_MENU_CARD_COUNT] = {NULL};
static lv_obj_t *prts_menu_status_value = NULL;
static lv_obj_t *prts_menu_monitor_value = NULL;
2026-07-20 21:59:16 +08:00
static lv_obj_t *prts_temperature_panel = NULL;
static lv_obj_t *prts_temperature_label = NULL;
static lv_obj_t *prts_temperature_accent = NULL;
static lv_obj_t *prts_mode_panel = NULL;
static lv_obj_t *prts_mode_label = NULL;
static lv_obj_t *prts_mode_accent = NULL;
static lv_obj_t *prts_status_bar = NULL;
2026-07-20 23:34:13 +08:00
static lv_obj_t *prts_adc0_label = NULL;
static lv_obj_t *prts_adc1_label = NULL;
static lv_obj_t *prts_joy12_label = NULL;
static lv_obj_t *prts_joy_key_label = NULL;
static lv_obj_t *prts_joy_voltage_label = NULL;
static uint8_t prts_current_card = 0U;
static PRTS_Page_t prts_current_page = PRTS_PAGE_MAIN;
2026-07-20 21:59:16 +08:00
static int16_t prts_last_temperature_tenths = INT16_MIN;
static RobotMode_t prts_last_mode = (RobotMode_t) 0xFF;
2026-07-20 23:34:13 +08:00
static uint32_t prts_last_monitor_update_ms = 0U;
static const lv_font_t *prts_font(void)
{
return &prts_font_14;
}
static void prts_label_set_text_if_changed(lv_obj_t *label, const char *text)
{
const char *old_text;
if (label == NULL || text == NULL)
{
return;
}
old_text = lv_label_get_text(label);
if (old_text == NULL || strcmp(old_text, text) != 0)
{
lv_label_set_text(label, text);
}
}
2026-07-20 21:59:16 +08:00
static int16_t prts_temperature_to_tenths(float temperature_c)
{
return (int16_t) ((temperature_c * 10.0f) + (temperature_c >= 0.0f ? 0.5f : -0.5f));
}
static void prts_format_temperature(char *buf, size_t len, int16_t temperature_tenths)
{
uint16_t abs_temp;
uint16_t integer;
uint8_t decimal;
if (buf == NULL || len == 0U)
{
return;
}
if (temperature_tenths < 0)
{
abs_temp = (uint16_t) (-temperature_tenths);
integer = abs_temp / 10U;
decimal = (uint8_t) (abs_temp % 10U);
(void) snprintf(buf, len, "-%u.%u C", (unsigned int) integer, (unsigned int) decimal);
}
else
{
abs_temp = (uint16_t) temperature_tenths;
integer = abs_temp / 10U;
decimal = (uint8_t) (abs_temp % 10U);
(void) snprintf(buf, len, "%u.%u C", (unsigned int) integer, (unsigned int) decimal);
}
}
static const char *prts_robot_mode_text(RobotMode_t mode)
{
switch (mode)
{
case NORMAL_MODE:
return "NORMAL";
case SYS_ERROR_OCCURRED:
return "ERROR";
case REMOTE_NOT_CONNECTED:
return "REMOTE OFF";
case REMOTE_NOT_READY:
return "NOT READY";
case REMOTE_READY:
return "READY";
case REMOTE_PROTECT:
return "PROTECT";
2026-07-20 21:59:16 +08:00
case AUTO_SHOOTING_MODE:
return "AUTO SHOOT";
case IMU_CALIBERATION_MODE:
return "IMU CAL";
default:
return "UNKNOWN";
}
}
static lv_color_t prts_robot_mode_color(RobotMode_t mode)
{
switch (mode)
{
case NORMAL_MODE:
return lv_color_hex(0x25D366);
case SYS_ERROR_OCCURRED:
case REMOTE_NOT_CONNECTED:
return lv_color_hex(0xFF4D4F);
case REMOTE_NOT_READY:
return lv_color_hex(0xEDEDED);
case REMOTE_READY:
return lv_color_hex(0xC678FF);
case REMOTE_PROTECT:
2026-07-20 21:59:16 +08:00
return lv_color_hex(0xFFD666);
case AUTO_SHOOTING_MODE:
return lv_color_hex(0x40C4FF);
case IMU_CALIBERATION_MODE:
return lv_color_hex(0xC678FF);
default:
return lv_color_hex(0xD9D9D9);
}
}
static lv_color_t prts_robot_mode_text_color(RobotMode_t mode)
{
switch (mode)
{
case SYS_ERROR_OCCURRED:
case REMOTE_NOT_CONNECTED:
return lv_color_hex(0xFFE2E2);
case REMOTE_NOT_READY:
return lv_color_hex(0xFFFFFF);
case REMOTE_READY:
return lv_color_hex(0xF5E6FF);
case REMOTE_PROTECT:
2026-07-20 21:59:16 +08:00
return lv_color_hex(0xFFF3C4);
default:
return lv_color_hex(0xFFFFFF);
}
}
2026-07-20 23:34:13 +08:00
static const char *prts_joy_key_text(PRTS_JoyKey_t key)
{
switch (key)
{
case PRTS_JOY_ENTER:
return "ENTER";
case PRTS_JOY_UP:
return "UP";
case PRTS_JOY_DOWN:
return "DOWN";
case PRTS_JOY_LEFT:
return "LEFT";
case PRTS_JOY_RIGHT:
return "RIGHT";
case PRTS_JOY_NONE:
default:
return "NONE";
}
}
static PRTS_JoyKey_t prts_joy_from_adc12(uint16_t raw12)
{
if (raw12 > 3800U)
{
return PRTS_JOY_NONE;
}
if (raw12 < 200U)
{
return PRTS_JOY_ENTER;
}
if (raw12 > 600U && raw12 < 1050U)
{
return PRTS_JOY_DOWN;
}
if (raw12 > 1400U && raw12 < 1850U)
{
return PRTS_JOY_UP;
}
if (raw12 > 2200U && raw12 < 2700U)
{
return PRTS_JOY_LEFT;
}
if (raw12 > 3000U && raw12 < 3500U)
{
return PRTS_JOY_RIGHT;
}
return PRTS_JOY_NONE;
}
static void prts_keypad_read_cb(lv_indev_t *indev, lv_indev_data_t *data)
{
static PRTS_JoyKey_t candidate_key = PRTS_JOY_NONE;
static PRTS_JoyKey_t stable_key = PRTS_JOY_NONE;
static uint32_t candidate_since_ms = 0U;
static uint32_t stable_since_ms = 0U;
static uint32_t last_lv_key = 0U;
uint32_t now_ms = HAL_GetTick();
PRTS_JoyKey_t raw_key = prts_joy_from_adc12(BSP_ADC_GetJoystickRaw12());
(void) indev;
if (raw_key != candidate_key)
{
candidate_key = raw_key;
candidate_since_ms = now_ms;
}
if (stable_key != candidate_key && (now_ms - candidate_since_ms) >= PRTS_JOY_DEBOUNCE_MS)
{
stable_key = candidate_key;
stable_since_ms = now_ms;
}
if (stable_key == PRTS_JOY_NONE)
{
data->state = LV_INDEV_STATE_RELEASED;
data->key = last_lv_key;
return;
}
switch (stable_key)
{
case PRTS_JOY_ENTER:
last_lv_key = LV_KEY_ENTER;
break;
case PRTS_JOY_UP:
last_lv_key = LV_KEY_UP;
break;
case PRTS_JOY_DOWN:
last_lv_key = LV_KEY_DOWN;
break;
case PRTS_JOY_LEFT:
if (prts_current_page == PRTS_PAGE_MAIN)
{
last_lv_key = LV_KEY_PREV;
}
else
{
last_lv_key = ((now_ms - stable_since_ms) >= PRTS_JOY_LONG_PRESS_MS) ? LV_KEY_ESC : LV_KEY_LEFT;
}
break;
case PRTS_JOY_RIGHT:
last_lv_key = (prts_current_page == PRTS_PAGE_MAIN) ? LV_KEY_NEXT : LV_KEY_RIGHT;
break;
case PRTS_JOY_NONE:
default:
break;
}
data->state = LV_INDEV_STATE_PRESSED;
data->key = last_lv_key;
}
static void prts_init_styles(void)
{
static const lv_style_prop_t card_transition_props[] = {
LV_STYLE_BG_COLOR,
LV_STYLE_BORDER_COLOR,
LV_STYLE_BORDER_WIDTH,
0
};
if (prts_styles_ready)
{
return;
}
lv_style_transition_dsc_init(&prts_card_transition,
card_transition_props,
lv_anim_path_ease_out,
120,
0,
NULL);
lv_style_init(&prts_style_card_normal);
lv_style_set_bg_color(&prts_style_card_normal, lv_color_hex(0x2C2C2E));
lv_style_set_radius(&prts_style_card_normal, 8);
lv_style_set_border_width(&prts_style_card_normal, 0);
lv_style_set_pad_all(&prts_style_card_normal, 10);
lv_style_set_text_font(&prts_style_card_normal, prts_font());
lv_style_set_text_color(&prts_style_card_normal, lv_color_hex(0xEBEBF5));
lv_style_set_transform_width(&prts_style_card_normal, 0);
lv_style_set_transform_height(&prts_style_card_normal, 0);
lv_style_set_shadow_width(&prts_style_card_normal, 0);
lv_style_set_shadow_opa(&prts_style_card_normal, LV_OPA_0);
lv_style_set_transition(&prts_style_card_normal, &prts_card_transition);
lv_style_init(&prts_style_card_focused);
lv_style_set_bg_color(&prts_style_card_focused, lv_color_hex(0x3A3A3C));
lv_style_set_border_color(&prts_style_card_focused, lv_color_hex(0x0A84FF));
lv_style_set_border_width(&prts_style_card_focused, 2);
lv_style_set_transform_width(&prts_style_card_focused, 0);
lv_style_set_transform_height(&prts_style_card_focused, 0);
2026-07-20 23:34:13 +08:00
lv_style_set_shadow_width(&prts_style_card_focused, 0);
lv_style_set_shadow_color(&prts_style_card_focused, lv_color_hex(0x0A84FF));
lv_style_set_shadow_opa(&prts_style_card_focused, LV_OPA_0);
lv_style_set_transition(&prts_style_card_focused, &prts_card_transition);
lv_style_init(&prts_style_title_text);
lv_style_set_text_color(&prts_style_title_text, lv_color_hex(0xEBEBF5));
lv_style_set_text_font(&prts_style_title_text, prts_font());
prts_styles_ready = true;
}
static void prts_create_keypad(void)
{
if (prts_key_indev != NULL)
{
return;
}
prts_key_indev = lv_indev_create();
lv_indev_set_type(prts_key_indev, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_read_cb(prts_key_indev, prts_keypad_read_cb);
prts_key_group = lv_group_create();
lv_indev_set_group(prts_key_indev, prts_key_group);
lv_group_set_default(prts_key_group);
}
2026-07-20 21:59:16 +08:00
static void prts_style_label(lv_obj_t *label, lv_color_t color)
{
2026-07-20 23:34:13 +08:00
lv_obj_set_style_text_font(label, prts_font(), 0);
2026-07-20 21:59:16 +08:00
lv_obj_set_style_text_color(label, color, 0);
2026-07-20 23:34:13 +08:00
lv_obj_set_style_text_opa(label, LV_OPA_COVER, 0);
lv_obj_set_style_bg_opa(label, LV_OPA_TRANSP, 0);
}
static lv_obj_t *prts_create_bar(lv_obj_t *parent, const char *hint)
{
lv_obj_t *bar = lv_obj_create(parent);
lv_obj_t *label;
lv_obj_set_size(bar, PRTS_SCREEN_W, PRTS_HINT_H);
lv_obj_align(bar, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_set_style_bg_color(bar, lv_color_hex(0x111111), 0);
lv_obj_set_style_border_width(bar, 0, 0);
lv_obj_set_style_radius(bar, 0, 0);
lv_obj_set_style_pad_all(bar, 0, 0);
lv_obj_remove_flag(bar, LV_OBJ_FLAG_SCROLLABLE);
label = lv_label_create(bar);
lv_label_set_text(label, hint);
lv_obj_add_style(label, &prts_style_title_text, 0);
lv_obj_center(label);
return bar;
}
static lv_obj_t *prts_create_status_bar(lv_obj_t *parent, const char *title_text)
{
lv_obj_t *bar = lv_obj_create(parent);
lv_obj_t *brand;
lv_obj_t *title;
lv_obj_set_size(bar, PRTS_SCREEN_W, PRTS_STATUS_H);
lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_set_style_bg_color(bar, lv_color_hex(0x111111), 0);
lv_obj_set_style_border_width(bar, 0, 0);
lv_obj_set_style_radius(bar, 0, 0);
lv_obj_set_style_pad_all(bar, 0, 0);
lv_obj_remove_flag(bar, LV_OBJ_FLAG_SCROLLABLE);
brand = lv_label_create(bar);
lv_label_set_text(brand, "PRTS");
prts_style_label(brand, lv_color_hex(0x6EE7F9));
lv_obj_set_pos(brand, PRTS_STATUS_BRAND_X, 6);
lv_obj_set_size(brand, PRTS_STATUS_BRAND_W, 18);
lv_obj_set_style_text_align(brand, LV_TEXT_ALIGN_CENTER, 0);
2026-07-20 23:34:13 +08:00
title = lv_label_create(bar);
lv_label_set_text(title, title_text);
prts_style_label(title, lv_color_hex(0xEBEBF5));
lv_obj_set_pos(title, (PRTS_SCREEN_W - PRTS_STATUS_TITLE_W) / 2, 6);
lv_obj_set_size(title, PRTS_STATUS_TITLE_W, 18);
lv_obj_set_style_text_align(title, LV_TEXT_ALIGN_CENTER, 0);
2026-07-20 23:34:13 +08:00
if (parent == prts_main_screen)
{
prts_main_status_title = title;
}
return bar;
2026-07-20 21:59:16 +08:00
}
static lv_obj_t *prts_create_value_panel(lv_obj_t *parent, int32_t x, int32_t y)
{
lv_obj_t *panel = lv_obj_create(parent);
lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_pos(panel, x, y);
lv_obj_set_size(panel, PRTS_VALUE_PANEL_WIDTH, PRTS_VALUE_PANEL_HEIGHT);
lv_obj_set_style_radius(panel, 0, 0);
lv_obj_set_style_border_width(panel, 0, 0);
lv_obj_set_style_pad_all(panel, 0, 0);
lv_obj_set_style_bg_color(panel, lv_color_hex(PRTS_VALUE_BG_COLOR), 0);
lv_obj_set_style_bg_opa(panel, LV_OPA_COVER, 0);
return panel;
}
static lv_obj_t *prts_create_value_accent(lv_obj_t *parent, lv_color_t color)
{
lv_obj_t *accent = lv_obj_create(parent);
lv_obj_remove_flag(accent, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_pos(accent, 0, 0);
lv_obj_set_size(accent, PRTS_VALUE_ACCENT_WIDTH, PRTS_VALUE_PANEL_HEIGHT);
lv_obj_set_style_radius(accent, 0, 0);
lv_obj_set_style_border_width(accent, 0, 0);
lv_obj_set_style_pad_all(accent, 0, 0);
lv_obj_set_style_bg_color(accent, color, 0);
lv_obj_set_style_bg_opa(accent, LV_OPA_COVER, 0);
return accent;
}
2026-07-20 23:34:13 +08:00
static lv_obj_t *prts_create_card_text(lv_obj_t *parent, const char *text, int32_t y, lv_color_t color)
2026-07-20 21:59:16 +08:00
{
2026-07-20 23:34:13 +08:00
lv_obj_t *label = lv_label_create(parent);
lv_label_set_text(label, text);
2026-07-20 21:59:16 +08:00
lv_label_set_long_mode(label, LV_LABEL_LONG_MODE_CLIP);
2026-07-20 23:34:13 +08:00
prts_style_label(label, color);
lv_obj_set_pos(label, 14, y);
lv_obj_set_size(label, PRTS_CARD_W - 30, 20);
return label;
}
static void prts_update_dots(void)
{
for (uint8_t i = 0U; i < PRTS_MENU_CARD_COUNT; i++)
{
bool active = (i == prts_current_card);
lv_obj_set_size(prts_menu_dots[i],
active ? PRTS_DOT_SIZE_ACTIVE : PRTS_DOT_SIZE_NORMAL,
active ? PRTS_DOT_SIZE_ACTIVE : PRTS_DOT_SIZE_NORMAL);
lv_obj_set_style_bg_color(prts_menu_dots[i],
active ? lv_color_hex(0xFFFFFF) : lv_color_hex(0x888888),
0);
lv_obj_set_style_radius(prts_menu_dots[i], LV_RADIUS_CIRCLE, 0);
}
if (prts_main_status_title != NULL)
{
prts_label_set_text_if_changed(prts_main_status_title, prts_menu_cards_data[prts_current_card].title);
}
}
static void prts_focus_card(uint8_t index, lv_anim_enable_t anim)
{
if (index >= PRTS_MENU_CARD_COUNT)
{
return;
}
prts_current_card = index;
if (prts_key_group != NULL)
{
lv_group_focus_obj(prts_menu_cards[index]);
}
lv_obj_scroll_to_view(prts_menu_cards[index], anim);
prts_update_dots();
}
static void prts_show_page(PRTS_Page_t page, lv_screen_load_anim_t anim)
{
lv_obj_t *screen = prts_main_screen;
prts_current_page = page;
if (prts_key_group != NULL)
{
lv_group_remove_all_objs(prts_key_group);
}
switch (page)
{
case PRTS_PAGE_STATUS:
screen = prts_status_screen;
if (prts_key_group != NULL) lv_group_add_obj(prts_key_group, prts_status_screen);
break;
case PRTS_PAGE_MONITOR:
screen = prts_monitor_screen;
if (prts_key_group != NULL) lv_group_add_obj(prts_key_group, prts_monitor_screen);
break;
case PRTS_PAGE_ASSETS:
screen = prts_assets_screen;
if (prts_key_group != NULL) lv_group_add_obj(prts_key_group, prts_assets_screen);
break;
case PRTS_PAGE_MAIN:
default:
screen = prts_main_screen;
if (prts_key_group != NULL)
{
for (uint8_t i = 0U; i < PRTS_MENU_CARD_COUNT; i++)
{
lv_group_add_obj(prts_key_group, prts_menu_cards[i]);
}
}
break;
}
if (anim == LV_SCREEN_LOAD_ANIM_NONE || PRTS_SCREEN_ANIM_MS == 0U)
{
lv_screen_load(screen);
}
else
{
lv_screen_load_anim(screen, anim, PRTS_SCREEN_ANIM_MS, 0, false);
}
if (page == PRTS_PAGE_MAIN)
{
prts_focus_card(prts_current_card, LV_ANIM_ON);
}
else if (prts_key_group != NULL)
{
lv_group_focus_obj(screen);
}
}
static void prts_page_key_event_cb(lv_event_t *e)
{
if (lv_event_get_code(e) != LV_EVENT_KEY)
{
return;
}
if (lv_event_get_key(e) == LV_KEY_ESC)
{
prts_show_page(PRTS_PAGE_MAIN, LV_SCREEN_LOAD_ANIM_NONE);
2026-07-20 23:34:13 +08:00
}
}
static void prts_card_event_cb(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t *card = lv_event_get_target_obj(e);
uint32_t index = (uint32_t) lv_event_get_user_data(e);
if (index >= PRTS_MENU_CARD_COUNT)
{
return;
}
if (code == LV_EVENT_FOCUSED)
{
prts_current_card = (uint8_t) index;
lv_obj_scroll_to_view(card, LV_ANIM_ON);
prts_update_dots();
}
else if (code == LV_EVENT_CLICKED)
{
prts_current_card = (uint8_t) index;
prts_show_page(prts_menu_cards_data[index].page, LV_SCREEN_LOAD_ANIM_NONE);
2026-07-20 23:34:13 +08:00
}
}
static void prts_create_main_screen(void)
{
lv_obj_t *banner;
lv_obj_t *dot_row;
prts_main_screen = lv_obj_create(NULL);
lv_obj_set_style_bg_color(prts_main_screen, lv_color_hex(0x1A1A1A), 0);
lv_obj_remove_flag(prts_main_screen, LV_OBJ_FLAG_SCROLLABLE);
prts_create_status_bar(prts_main_screen, "主菜单");
prts_create_bar(prts_main_screen, "LEFT RIGHT ENTER");
banner = lv_obj_create(prts_main_screen);
lv_obj_set_size(banner, PRTS_SCREEN_W, PRTS_CONTENT_H);
lv_obj_align(banner, LV_ALIGN_TOP_MID, 0, PRTS_STATUS_H);
lv_obj_set_style_bg_opa(banner, LV_OPA_TRANSP, 0);
lv_obj_set_style_border_width(banner, 0, 0);
lv_obj_set_style_pad_hor(banner, PRTS_CARD_PAD_HOR, 0);
lv_obj_set_style_pad_column(banner, PRTS_CARD_GAP, 0);
lv_obj_set_scrollbar_mode(banner, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_scroll_dir(banner, LV_DIR_HOR);
lv_obj_set_scroll_snap_x(banner, LV_SCROLL_SNAP_CENTER);
lv_obj_set_flex_flow(banner, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(banner, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START);
for (uint8_t i = 0U; i < PRTS_MENU_CARD_COUNT; i++)
{
lv_obj_t *card = lv_button_create(banner);
lv_obj_t *accent;
lv_obj_set_size(card, PRTS_CARD_W, PRTS_CARD_H);
lv_obj_add_style(card, &prts_style_card_normal, 0);
lv_obj_add_style(card, &prts_style_card_focused, LV_STATE_FOCUSED);
lv_obj_remove_flag(card, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
lv_obj_add_event_cb(card, prts_card_event_cb, LV_EVENT_FOCUSED, (void *) (uintptr_t) i);
lv_obj_add_event_cb(card, prts_card_event_cb, LV_EVENT_CLICKED, (void *) (uintptr_t) i);
accent = lv_obj_create(card);
lv_obj_remove_flag(accent, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_pos(accent, 0, 0);
lv_obj_set_size(accent, 4, PRTS_CARD_H - 20);
lv_obj_set_style_radius(accent, 0, 0);
lv_obj_set_style_border_width(accent, 0, 0);
lv_obj_set_style_pad_all(accent, 0, 0);
lv_obj_set_style_bg_color(accent, lv_color_hex(prts_menu_cards_data[i].color), 0);
lv_obj_set_style_bg_opa(accent, LV_OPA_COVER, 0);
(void) prts_create_card_text(card, prts_menu_cards_data[i].hint, 8, lv_color_hex(prts_menu_cards_data[i].color));
(void) prts_create_card_text(card, prts_menu_cards_data[i].title, 42, lv_color_hex(0xFFFFFF));
(void) prts_create_card_text(card, prts_menu_cards_data[i].subtitle, 70, lv_color_hex(0xD1D1D6));
if (i == 0U)
{
prts_menu_status_value = prts_create_card_text(card, "UNKNOWN", 108, lv_color_hex(0xD9D9D9));
}
else if (i == 1U)
{
prts_menu_monitor_value = prts_create_card_text(card, "ADC --", 108, lv_color_hex(0xFFD666));
}
prts_menu_cards[i] = card;
}
dot_row = lv_obj_create(prts_main_screen);
lv_obj_remove_style_all(dot_row);
lv_obj_set_size(dot_row, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_set_flex_flow(dot_row, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(dot_row, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_align(dot_row, LV_ALIGN_BOTTOM_MID, 0, -PRTS_HINT_H - 2);
for (uint8_t i = 0U; i < PRTS_MENU_CARD_COUNT; i++)
{
prts_menu_dots[i] = lv_obj_create(dot_row);
lv_obj_remove_style_all(prts_menu_dots[i]);
lv_obj_set_size(prts_menu_dots[i], PRTS_DOT_SIZE_NORMAL, PRTS_DOT_SIZE_NORMAL);
lv_obj_set_style_radius(prts_menu_dots[i], LV_RADIUS_CIRCLE, 0);
lv_obj_set_style_bg_color(prts_menu_dots[i], lv_color_hex(0x888888), 0);
lv_obj_set_style_bg_opa(prts_menu_dots[i], LV_OPA_COVER, 0);
if (i > 0U)
{
lv_obj_set_style_margin_left(prts_menu_dots[i], 10, 0);
}
}
prts_update_dots();
2026-07-20 21:59:16 +08:00
}
static lv_obj_t *prts_create_card(lv_obj_t *parent, int32_t x, int32_t y, int32_t w, int32_t h)
{
lv_obj_t *card = lv_obj_create(parent);
lv_obj_remove_flag(card, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_pos(card, x, y);
lv_obj_set_size(card, w, h);
2026-07-20 23:34:13 +08:00
lv_obj_set_style_radius(card, 6, 0);
lv_obj_set_style_border_width(card, 0, 0);
lv_obj_set_style_bg_color(card, lv_color_hex(0x20262D), 0);
2026-07-20 21:59:16 +08:00
lv_obj_set_style_bg_opa(card, LV_OPA_COVER, 0);
lv_obj_set_style_pad_all(card, 10, 0);
return card;
}
static void prts_create_status_screen(void)
{
lv_obj_t *temp_card;
lv_obj_t *mode_card;
lv_obj_t *caption;
2026-07-20 23:34:13 +08:00
prts_status_screen = lv_obj_create(NULL);
lv_obj_set_style_bg_color(prts_status_screen, lv_color_hex(0x1A1A1A), 0);
lv_obj_remove_flag(prts_status_screen, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_event_cb(prts_status_screen, prts_page_key_event_cb, LV_EVENT_KEY, NULL);
2026-07-20 21:59:16 +08:00
2026-07-20 23:34:13 +08:00
prts_create_status_bar(prts_status_screen, "状态详情");
prts_create_bar(prts_status_screen, "HOLD LEFT 返回");
2026-07-20 21:59:16 +08:00
2026-07-20 23:34:13 +08:00
temp_card = prts_create_card(prts_status_screen, 16, 48, 248, 62);
2026-07-20 21:59:16 +08:00
caption = lv_label_create(temp_card);
lv_label_set_text(caption, "TEMPERATURE");
prts_style_label(caption, lv_color_hex(0xFFD666));
lv_obj_set_pos(caption, 0, 0);
2026-07-20 23:34:13 +08:00
prts_temperature_panel = prts_create_value_panel(temp_card, 0, 23);
2026-07-20 21:59:16 +08:00
prts_temperature_accent = prts_create_value_accent(prts_temperature_panel, lv_color_hex(0x40C4FF));
prts_temperature_label = lv_label_create(prts_temperature_panel);
lv_label_set_text(prts_temperature_label, "--.- C");
2026-07-20 23:34:13 +08:00
prts_style_label(prts_temperature_label, lv_color_hex(0xFFFFFF));
lv_obj_set_pos(prts_temperature_label, 12, 7);
lv_obj_set_size(prts_temperature_label, 210, 18);
2026-07-20 21:59:16 +08:00
2026-07-20 23:34:13 +08:00
mode_card = prts_create_card(prts_status_screen, 16, 124, 248, 62);
2026-07-20 21:59:16 +08:00
caption = lv_label_create(mode_card);
lv_label_set_text(caption, "CURRENT STATE");
prts_style_label(caption, lv_color_hex(0xFFD666));
lv_obj_set_pos(caption, 0, 0);
2026-07-20 23:34:13 +08:00
prts_mode_panel = prts_create_value_panel(mode_card, 0, 23);
2026-07-20 21:59:16 +08:00
prts_mode_accent = prts_create_value_accent(prts_mode_panel, lv_color_hex(0xD9D9D9));
prts_mode_label = lv_label_create(prts_mode_panel);
lv_label_set_text(prts_mode_label, "UNKNOWN");
2026-07-20 23:34:13 +08:00
prts_style_label(prts_mode_label, lv_color_hex(0xD9D9D9));
lv_obj_set_pos(prts_mode_label, 12, 7);
lv_obj_set_size(prts_mode_label, 210, 18);
prts_status_bar = lv_obj_create(prts_status_screen);
lv_obj_remove_flag(prts_status_bar, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_pos(prts_status_bar, 222, 11);
lv_obj_set_size(prts_status_bar, 46, 8);
lv_obj_set_style_radius(prts_status_bar, 2, 0);
lv_obj_set_style_border_width(prts_status_bar, 0, 0);
lv_obj_set_style_bg_color(prts_status_bar, lv_color_hex(0xD9D9D9), 0);
lv_obj_set_style_bg_opa(prts_status_bar, LV_OPA_COVER, 0);
}
static lv_obj_t *prts_create_metric_label(lv_obj_t *parent, const char *text, int32_t x, int32_t y)
{
lv_obj_t *label = lv_label_create(parent);
lv_label_set_text(label, text);
lv_label_set_long_mode(label, LV_LABEL_LONG_MODE_CLIP);
prts_style_label(label, lv_color_hex(0xEBEBF5));
lv_obj_set_pos(label, x, y);
lv_obj_set_size(label, 112, 18);
return label;
}
static void prts_create_monitor_screen(void)
{
lv_obj_t *card;
lv_obj_t *title;
prts_monitor_screen = lv_obj_create(NULL);
lv_obj_set_style_bg_color(prts_monitor_screen, lv_color_hex(0x1A1A1A), 0);
lv_obj_remove_flag(prts_monitor_screen, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_event_cb(prts_monitor_screen, prts_page_key_event_cb, LV_EVENT_KEY, NULL);
2026-07-20 21:59:16 +08:00
2026-07-20 23:34:13 +08:00
prts_create_status_bar(prts_monitor_screen, "监视器");
prts_create_bar(prts_monitor_screen, "HOLD LEFT 返回");
card = prts_create_card(prts_monitor_screen, 16, 46, 248, 142);
title = lv_label_create(card);
lv_label_set_text(title, "JOYSTICK ADC");
prts_style_label(title, lv_color_hex(0x6EE7F9));
lv_obj_set_pos(title, 0, 0);
prts_adc0_label = prts_create_metric_label(card, "PC4 0", 0, 32);
prts_adc1_label = prts_create_metric_label(card, "PA5 0", 118, 32);
prts_joy12_label = prts_create_metric_label(card, "12b 0", 0, 66);
prts_joy_key_label = prts_create_metric_label(card, "KEY NONE", 118, 66);
prts_joy_voltage_label = prts_create_metric_label(card, "JOY 0.000 V", 0, 100);
lv_obj_set_size(prts_joy_voltage_label, 224, 18);
}
static void prts_create_assets_screen(void)
{
lv_obj_t *card;
lv_obj_t *label;
prts_assets_screen = lv_obj_create(NULL);
lv_obj_set_style_bg_color(prts_assets_screen, lv_color_hex(0x1A1A1A), 0);
lv_obj_remove_flag(prts_assets_screen, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_add_event_cb(prts_assets_screen, prts_page_key_event_cb, LV_EVENT_KEY, NULL);
prts_create_status_bar(prts_assets_screen, "资源");
prts_create_bar(prts_assets_screen, "HOLD LEFT 返回");
card = prts_create_card(prts_assets_screen, 16, 46, 248, 142);
label = lv_label_create(card);
lv_label_set_text(label, "字体: PRTS 子集字库\n位置: 内部 Flash\n外置: W25Q64 接口准备\nOSPI: CubeMX 未生成");
prts_style_label(label, lv_color_hex(0xEBEBF5));
lv_obj_set_pos(label, 0, 0);
lv_obj_set_size(label, 228, 120);
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
}
static void prts_create_screens(void)
{
prts_create_main_screen();
prts_create_status_screen();
prts_create_monitor_screen();
prts_create_assets_screen();
prts_show_page(PRTS_PAGE_MAIN, LV_SCREEN_LOAD_ANIM_NONE);
2026-07-20 21:59:16 +08:00
}
bool PRTS_Init(void)
{
if (prts_initialized)
{
return true;
}
if (!prts_lvgl_initialized)
{
lv_init();
prts_lvgl_initialized = true;
}
if (!PRTS_LVGLPortInit())
{
return false;
}
2026-07-20 23:34:13 +08:00
BSP_ADC_Init();
prts_init_styles();
prts_create_keypad();
prts_create_screens();
lv_sysmon_show_performance(lv_display_get_default());
2026-07-20 21:59:16 +08:00
prts_initialized = true;
return true;
}
void PRTS_UpdateStatus(float temperature_c, RobotMode_t mode)
{
int16_t temperature_tenths;
lv_color_t mode_color;
char temperature_text[16];
if (!prts_initialized)
{
return;
}
temperature_tenths = prts_temperature_to_tenths(temperature_c);
if (temperature_tenths != prts_last_temperature_tenths)
{
bool temperature_warning = temperature_tenths >= 550;
prts_format_temperature(temperature_text, sizeof(temperature_text), temperature_tenths);
lv_label_set_text(prts_temperature_label, temperature_text);
lv_obj_set_style_bg_color(prts_temperature_accent,
temperature_warning ? lv_color_hex(0xFF4D4F) : lv_color_hex(0x40C4FF),
0);
lv_obj_set_style_text_color(prts_temperature_label,
temperature_warning ? lv_color_hex(0xFFE2E2) : lv_color_hex(0xFFFFFF),
0);
prts_last_temperature_tenths = temperature_tenths;
}
if (mode != prts_last_mode)
{
mode_color = prts_robot_mode_color(mode);
2026-07-20 23:34:13 +08:00
prts_label_set_text_if_changed(prts_mode_label, prts_robot_mode_text(mode));
2026-07-20 21:59:16 +08:00
lv_obj_set_style_text_color(prts_mode_label, prts_robot_mode_text_color(mode), 0);
lv_obj_set_style_bg_color(prts_mode_accent, mode_color, 0);
lv_obj_set_style_bg_color(prts_status_bar, mode_color, 0);
2026-07-20 23:34:13 +08:00
prts_label_set_text_if_changed(prts_menu_status_value, prts_robot_mode_text(mode));
lv_obj_set_style_text_color(prts_menu_status_value, mode_color, 0);
2026-07-20 21:59:16 +08:00
prts_last_mode = mode;
}
}
2026-07-20 23:34:13 +08:00
static void prts_update_monitor(uint32_t now_ms)
{
uint16_t adc0;
uint16_t adc1;
uint16_t raw12;
PRTS_JoyKey_t key;
char text[32];
if ((now_ms - prts_last_monitor_update_ms) < PRTS_MONITOR_UPDATE_MS)
{
return;
}
if (prts_current_page != PRTS_PAGE_MAIN && prts_current_page != PRTS_PAGE_MONITOR)
{
prts_last_monitor_update_ms = now_ms;
return;
}
prts_last_monitor_update_ms = now_ms;
adc0 = BSP_ADC_GetRaw(BSP_ADC_BOARD_VOLTAGE_INDEX);
adc1 = BSP_ADC_GetJoystickRaw();
raw12 = (uint16_t) (adc1 >> 4U);
key = prts_joy_from_adc12(raw12);
(void) snprintf(text, sizeof(text), "PC4 %5u", (unsigned int) adc0);
prts_label_set_text_if_changed(prts_adc0_label, text);
(void) snprintf(text, sizeof(text), "PA5 %5u", (unsigned int) adc1);
prts_label_set_text_if_changed(prts_adc1_label, text);
(void) snprintf(text, sizeof(text), "12b %4u", (unsigned int) raw12);
prts_label_set_text_if_changed(prts_joy12_label, text);
(void) snprintf(text, sizeof(text), "KEY %s", prts_joy_key_text(key));
prts_label_set_text_if_changed(prts_joy_key_label, text);
(void) snprintf(text,
sizeof(text),
"JOY %u.%03u V",
(unsigned int) ((((uint32_t) adc1) * 3300U / 65535U) / 1000U),
(unsigned int) ((((uint32_t) adc1) * 3300U / 65535U) % 1000U));
prts_label_set_text_if_changed(prts_joy_voltage_label, text);
(void) snprintf(text, sizeof(text), "PA5 %4u", (unsigned int) raw12);
prts_label_set_text_if_changed(prts_menu_monitor_value, text);
}
2026-07-20 21:59:16 +08:00
uint32_t PRTS_Task(void)
{
uint32_t next_ms;
if (!prts_initialized)
{
return 10U;
}
2026-07-20 23:34:13 +08:00
prts_update_monitor(HAL_GetTick());
2026-07-20 21:59:16 +08:00
next_ms = lv_timer_handler();
2026-07-20 23:34:13 +08:00
if (next_ms == 0U)
{
next_ms = 1U;
}
if (next_ms > 20U)
2026-07-20 21:59:16 +08:00
{
2026-07-20 23:34:13 +08:00
next_ms = 20U;
2026-07-20 21:59:16 +08:00
}
return next_ms;
}