1240 lines
31 KiB
C
1240 lines
31 KiB
C
<<<<<<< HEAD
|
||
#include <stdio.h>
|
||
#include <math.h>
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
|
||
#include "esp_lcd_panel_io.h"
|
||
#include "esp_lcd_panel_vendor.h"
|
||
#include "esp_lcd_panel_ops.h"
|
||
#include "driver/gpio.h"
|
||
#include "driver/spi_master.h"
|
||
#include "esp_err.h"
|
||
#include "esp_log.h"
|
||
#include "esp_timer.h"
|
||
#include "lvgl.h"
|
||
#include "esp_lvgl_port.h"
|
||
|
||
#include "gui.h"
|
||
#include "gpio.h"
|
||
#include "keypad.h"
|
||
#include "bubble.h"
|
||
#include "system.h"
|
||
|
||
#define DEVKIT
|
||
#undef DEVKIT
|
||
|
||
static const char *TAG = "gui";
|
||
|
||
#define UNLOCK() lvgl_port_unlock()
|
||
#define LOCK() lvgl_port_lock(0)
|
||
|
||
// LCD Pin Configuration
|
||
#define LCD_PIXEL_CLOCK_HZ (5 * 1000 * 1000)
|
||
#define LCD_SPI_HOST SPI2_HOST
|
||
|
||
#define PIN_NUM_nON 26
|
||
|
||
|
||
|
||
// ST7735S properties
|
||
#define LCD_H_RES 160
|
||
#define LCD_V_RES 80
|
||
#define LCD_CMD_BITS 8
|
||
#define LCD_PARAM_BITS 8
|
||
#define LCD_COLOR_SPACE ESP_LCD_COLOR_SPACE_RGB
|
||
#define LCD_BITS_PER_PIXEL 16
|
||
|
||
static esp_lcd_panel_handle_t panel_handle = NULL;
|
||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||
static lv_disp_t *disp = NULL;
|
||
static lv_obj_t *imu_label = NULL; // Label for IMU data
|
||
|
||
static lv_style_t style_mono8;
|
||
|
||
typedef struct
|
||
{
|
||
int selected;
|
||
int count;
|
||
lv_obj_t *obj;
|
||
} menu_context_t;
|
||
|
||
static void gui_task(void);
|
||
static void createBubble(lv_obj_t * scr);
|
||
static void build_scrollable_menu(void);
|
||
static void currentFocusIndex(menu_context_t *ctx);
|
||
|
||
#define MAX_ITEMS 10
|
||
#define VISIBLE_ITEMS 3
|
||
#define MENU_MAX_STRING_LENGTH 30
|
||
|
||
static const char *menu_items[MAX_ITEMS] = {
|
||
"V-Moda Crossfade", "Item 2", "Item 3", "Item 4",
|
||
"Item 5", "Item 6", "Item 7", "Item 8",
|
||
"Item 9", "Item 10"
|
||
};
|
||
|
||
static lv_obj_t *list;
|
||
static lv_obj_t *buttons[MAX_ITEMS];
|
||
static int selected_index = 0;
|
||
static lv_obj_t *_bubble = NULL;
|
||
static lv_obj_t * _menu = NULL;
|
||
static lv_obj_t *_currentPage = NULL;
|
||
|
||
static GuiMode_t _mode = GUI_BUBBLE;
|
||
|
||
/* 1. Prepare a default (unfocused) style */
|
||
static lv_style_t _styleUnfocusedBtn;
|
||
|
||
|
||
/* 2. Prepare a focus style */
|
||
static lv_style_t _styleFocusedBtn;
|
||
|
||
static menu_context_t _menuContext;
|
||
|
||
static bool notify_lvgl_flush_ready(void *user_ctx) {
|
||
if (disp) {
|
||
lv_display_flush_ready(disp);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
|
||
static void create_lvgl_demo(void)
|
||
{
|
||
|
||
lvgl_port_lock(0);
|
||
// Create a screen with black background
|
||
lv_obj_t *scr = lv_scr_act();
|
||
|
||
createBubble(scr);
|
||
//build_scrollable_menu();
|
||
|
||
lvgl_port_unlock();
|
||
|
||
|
||
}
|
||
|
||
static void lcd_init(void)
|
||
{
|
||
ESP_LOGI(TAG, "Initialize SPI bus");
|
||
spi_bus_config_t buscfg = {
|
||
.sclk_io_num = PIN_NUM_CLK,
|
||
.mosi_io_num = PIN_NUM_MOSI,
|
||
.miso_io_num = -1,
|
||
.quadwp_io_num = -1,
|
||
.quadhd_io_num = -1,
|
||
.max_transfer_sz = LCD_H_RES * LCD_V_RES * 2
|
||
};
|
||
|
||
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||
|
||
ESP_LOGI(TAG, "Install panel IO");
|
||
|
||
esp_lcd_panel_io_spi_config_t io_config = {
|
||
.dc_gpio_num = PIN_NUM_DC,
|
||
.cs_gpio_num = PIN_NUM_CS,
|
||
.pclk_hz = LCD_PIXEL_CLOCK_HZ,
|
||
.lcd_cmd_bits = LCD_CMD_BITS,
|
||
.lcd_param_bits = LCD_PARAM_BITS,
|
||
.spi_mode = 3,
|
||
.trans_queue_depth = 10,
|
||
.user_ctx = NULL,
|
||
};
|
||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_config, &io_handle));
|
||
|
||
esp_lcd_panel_dev_config_t panel_config = {
|
||
.reset_gpio_num = PIN_NUM_RST,
|
||
.rgb_endian = LCD_RGB_ENDIAN_BGR,
|
||
.bits_per_pixel = LCD_BITS_PER_PIXEL,
|
||
};
|
||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
|
||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
|
||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
|
||
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
|
||
|
||
|
||
|
||
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, 1, 26)); // ST7735S typically needs these offsets
|
||
|
||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
|
||
}
|
||
|
||
static void lvgl_init(void)
|
||
{
|
||
lv_init();
|
||
|
||
|
||
#if 1
|
||
const lvgl_port_cfg_t lvgl_cfg = {
|
||
.task_priority = 4, // LVGL task priority
|
||
.task_stack = 16384, // LVGL task stack size
|
||
.task_affinity = 0, // LVGL task can run on any core
|
||
.task_max_sleep_ms = 500, // Maximum sleep in LVGL task
|
||
.timer_period_ms = 5 // LVGL timer period
|
||
};
|
||
|
||
ESP_ERROR_CHECK(lvgl_port_init(&lvgl_cfg));
|
||
#endif
|
||
|
||
const lvgl_port_display_cfg_t disp_cfg = {
|
||
.io_handle = io_handle,
|
||
.panel_handle = panel_handle,
|
||
.buffer_size = LCD_H_RES * LCD_V_RES * 2,
|
||
.double_buffer = false,
|
||
.hres = LCD_H_RES,
|
||
.vres = LCD_V_RES,
|
||
.monochrome = false,
|
||
.rotation = {
|
||
.swap_xy = true,
|
||
.mirror_x = true,
|
||
.mirror_y = false,
|
||
},
|
||
.flags = {
|
||
//.buff_dma = true,
|
||
.swap_bytes = true,
|
||
}
|
||
};
|
||
|
||
disp = lvgl_port_add_disp(&disp_cfg);
|
||
//lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
|
||
|
||
|
||
}
|
||
|
||
static void createBubble(lv_obj_t * scr)
|
||
{
|
||
|
||
// 2) Create a bubble level of size 200×60, with range [−30°, +30°], initial 0°:
|
||
lv_obj_t * level = bubble_create(scr, 150, 40, -10.0f, +10.0f, 0.0f);
|
||
lv_obj_align(level, LV_ALIGN_CENTER, 0, 0);
|
||
|
||
// 3) … Later, when you read your accelerometer or keypad‐derived angle …
|
||
float new_angle = 10.0f;
|
||
bubble_setValue(level, new_angle);
|
||
|
||
// 4) You can call bubble_level_set_value(level, …) as often as you like.
|
||
// Each call invalidates the object and LVGL will call the draw callback
|
||
// (usually on the next tick, or immediately if LVGL is idle).
|
||
|
||
_bubble = level;
|
||
}
|
||
|
||
|
||
void gui_start(void)
|
||
{
|
||
|
||
// Initialize LCD
|
||
lcd_init();
|
||
|
||
// Initialize LVGL
|
||
lvgl_init();
|
||
|
||
// Create UI
|
||
create_lvgl_demo();
|
||
|
||
keypad_start();
|
||
|
||
gpio_set_level(PIN_NUM_BK_LIGHT, 1);
|
||
|
||
xTaskCreate(gui_task, "gui_task", 4096, NULL, 5, NULL);
|
||
|
||
}
|
||
|
||
static uint32_t waitForKeyPress(void)
|
||
{
|
||
QueueHandle_t q = keypad_getQueue();
|
||
uint32_t ev = 0;
|
||
|
||
if (xQueueReceive(q, &ev, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
return ev;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void handleMainMenu(void)
|
||
{
|
||
lvgl_port_lock(0);
|
||
// Create a screen with black background
|
||
lv_obj_t *scr = lv_scr_act();
|
||
|
||
//create_menu(scr);
|
||
|
||
lvgl_port_unlock();
|
||
}
|
||
|
||
|
||
// ───── MENU CONFIG ─────
|
||
#define ROW_H 20 // height of each row
|
||
static const char * items[] = { // your menu entries
|
||
"VMode Crossmade",
|
||
"Second Choice",
|
||
"Another Option",
|
||
"Yet Another",
|
||
"Yet Another",
|
||
"Last One"
|
||
};
|
||
#define ITEM_COUNT (sizeof(items)/sizeof(items[0]))
|
||
|
||
// ───── STATE & HELPERS ─────
|
||
static lv_obj_t * btn_array[ITEM_COUNT];
|
||
static int selected_idx = 0;
|
||
|
||
// Called whenever a button is “activated” (Enter/Click)
|
||
static void btn_click_cb(lv_event_t * e) {
|
||
lv_obj_t * btn = lv_event_get_target(e);
|
||
const char * txt = lv_label_get_text(lv_obj_get_child(btn, 0));
|
||
ESP_LOGI(TAG, "Activated: %s\n", txt);
|
||
}
|
||
|
||
// Repaint all rows so only btn_array[selected_idx] is highlighted
|
||
static void refresh_highlight(void) {
|
||
|
||
return;
|
||
lvgl_port_lock(0);
|
||
|
||
int index = 0;
|
||
|
||
lv_obj_t *page = _currentPage;
|
||
|
||
lv_obj_t * child = NULL;
|
||
lv_obj_t * next = lv_obj_get_child(page, index);
|
||
lv_obj_t * selected = NULL;
|
||
while(next) {
|
||
child = next;
|
||
|
||
ESP_LOGI(TAG, "Child: %p", child);
|
||
|
||
|
||
if (lv_obj_has_flag(child, LV_OBJ_FLAG_USER_1))
|
||
{
|
||
lv_obj_set_style_bg_color(child, lv_color_hex(0xFF8800), 0);
|
||
lv_obj_set_style_text_color(lv_obj_get_child(child,0),
|
||
lv_color_white(), 0);
|
||
|
||
selected = child;
|
||
ESP_LOGI(TAG, "Selected");
|
||
}
|
||
else
|
||
{
|
||
lv_obj_set_style_bg_color(child, lv_color_white(), 0);
|
||
lv_obj_set_style_text_color(lv_obj_get_child(child,0),
|
||
lv_color_black(), 0);
|
||
}
|
||
|
||
index++;
|
||
next = lv_obj_get_child(page, index);
|
||
}
|
||
|
||
if (selected)
|
||
{
|
||
lv_obj_scroll_to_view(selected, LV_ANIM_ON);
|
||
}
|
||
|
||
lvgl_port_unlock();
|
||
}
|
||
|
||
|
||
static void menuInc(int inc)
|
||
{
|
||
LOCK();
|
||
|
||
currentFocusIndex(&_menuContext);
|
||
|
||
ESP_LOGI(TAG, "Current Index: %d", _menuContext.selected);
|
||
|
||
lv_obj_t *next = NULL;
|
||
|
||
// check if we are at the first or last in the page
|
||
lv_obj_t *test = lv_obj_get_child(_currentPage, (inc > 0 ? -1 : 0));
|
||
|
||
if (_menuContext.obj != test)
|
||
{
|
||
next = lv_obj_get_child(_currentPage, _menuContext.selected + inc);
|
||
lv_obj_add_state(next, LV_STATE_FOCUSED);
|
||
lv_obj_clear_state(_menuContext.obj, LV_STATE_FOCUSED);
|
||
lv_obj_scroll_to_view(next, LV_ANIM_ON);
|
||
|
||
_menuContext.obj = next;
|
||
_menuContext.selected += inc;
|
||
}
|
||
|
||
UNLOCK();
|
||
}
|
||
|
||
static void menuNext(void)
|
||
{
|
||
menuInc(1);
|
||
}
|
||
|
||
|
||
static void menuPrevious(void)
|
||
{
|
||
menuInc(-1);
|
||
}
|
||
|
||
// Fire the “clicked” event on the selected row
|
||
static void activate_selected(void)
|
||
{
|
||
LOCK();
|
||
lv_obj_send_event(_menuContext.obj, LV_EVENT_CLICKED, NULL);
|
||
UNLOCK();
|
||
}
|
||
|
||
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text)
|
||
{
|
||
lv_obj_t * btn = lv_btn_create(page);
|
||
lv_obj_set_size(btn, LV_PCT(100), ROW_H);
|
||
|
||
|
||
lv_obj_add_style(btn, &_styleUnfocusedBtn, LV_PART_MAIN | LV_STATE_DEFAULT);
|
||
lv_obj_add_style(btn, &_styleFocusedBtn, LV_PART_MAIN | LV_STATE_FOCUSED);
|
||
|
||
lv_obj_add_state(btn, LV_STATE_DEFAULT);
|
||
|
||
|
||
// style it just like your old list
|
||
// lv_obj_set_style_bg_color(btn, lv_color_white(), 0);
|
||
lv_obj_set_style_radius(btn, 0, LV_PART_MAIN);
|
||
lv_obj_set_style_border_width(btn, 0, LV_PART_MAIN);
|
||
lv_obj_clear_flag(btn, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
// label & center
|
||
lv_obj_t * lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, text);
|
||
lv_obj_set_style_radius(lbl, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_style_border_width(lbl, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
|
||
// click callback
|
||
lv_obj_add_event_cb(btn, btn_click_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
return btn;
|
||
}
|
||
|
||
static int getSelectedIndex(lv_obj_t *page)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
|
||
|
||
static void currentFocusIndex(menu_context_t *ctx)
|
||
{
|
||
ctx->count = lv_obj_get_child_cnt(_currentPage);
|
||
ctx->obj = NULL;
|
||
ctx->selected = -1;
|
||
|
||
// return the index of the currently focused object
|
||
for(int i = 0; i < ctx->count; i++) {
|
||
lv_obj_t * child = lv_obj_get_child(_currentPage, i);
|
||
|
||
if (lv_obj_has_state(child, LV_STATE_FOCUSED))
|
||
{
|
||
ctx->obj = child;
|
||
ctx->selected = i;
|
||
|
||
return;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
}
|
||
// ───── BUILD THE MENU ─────
|
||
static void build_scrollable_menu(void) {
|
||
|
||
|
||
lv_style_init(&_styleFocusedBtn);
|
||
lv_style_init(&_styleUnfocusedBtn);
|
||
|
||
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0xff,0xff,0xff)); // gray bg
|
||
lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_hex(0xFF8800));
|
||
|
||
lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0x33,0x99,0xFF)); // blue bg
|
||
lv_style_set_text_color(&_styleUnfocusedBtn,lv_color_black());
|
||
|
||
|
||
// 2) Inside it, create the lv_menu and hide its sidebar/header
|
||
lv_obj_t *menu = lv_menu_create(lv_scr_act());
|
||
_menu = menu;
|
||
|
||
lv_obj_set_style_radius(menu, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_style_border_width(menu, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_size(menu, lv_pct(100), lv_pct(100));
|
||
lv_obj_center(menu);
|
||
|
||
lv_obj_set_scrollbar_mode(menu, LV_SCROLLBAR_MODE_AUTO);
|
||
|
||
|
||
lv_obj_t * main = lv_menu_page_create(menu, NULL);
|
||
lv_obj_set_style_radius(main, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_style_border_width(main, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_scrollbar_mode(main, LV_SCROLLBAR_MODE_AUTO);
|
||
lv_obj_set_size(main, lv_pct(100), lv_pct(100));
|
||
|
||
lv_menu_set_page(menu, main);
|
||
|
||
lv_obj_t * tmpObj;
|
||
|
||
|
||
|
||
lv_obj_t * calMenu = lv_menu_page_create(menu, NULL);
|
||
lv_obj_set_style_radius(calMenu, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_style_border_width(calMenu, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_scrollbar_mode(calMenu, LV_SCROLLBAR_MODE_AUTO);
|
||
|
||
tmpObj = addMenuItem(main, "Bluetooth");
|
||
lv_obj_add_state(tmpObj, LV_STATE_FOCUSED);
|
||
//lv_obj_add_flag(tmpObj, LV_OBJ_FLAG_USER_1);
|
||
|
||
tmpObj = addMenuItem(main, "Calibration");
|
||
lv_menu_set_load_page_event(menu, tmpObj, calMenu);
|
||
|
||
|
||
tmpObj = addMenuItem(main, "Volume");
|
||
|
||
addMenuItem(main, "About");
|
||
addMenuItem(main, "Exit");
|
||
|
||
|
||
addMenuItem(calMenu, "Calibrate Level");
|
||
addMenuItem(calMenu, "Reset Calibration");
|
||
addMenuItem(calMenu, "Exit");
|
||
|
||
_currentPage = main;
|
||
|
||
// 6) Initial highlight
|
||
selected_idx = 0;
|
||
refresh_highlight();
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
static void gui_task(void)
|
||
{
|
||
system_subscribe(xTaskGetCurrentTaskHandle());
|
||
|
||
// Grab queue handle
|
||
QueueHandle_t q = keypad_getQueue();
|
||
uint32_t ev = 0;
|
||
|
||
LOCK();
|
||
// _mode = GUI_MENU;
|
||
// lv_obj_remove_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||
lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||
//lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||
UNLOCK();
|
||
|
||
while (1)
|
||
{
|
||
|
||
|
||
if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE)
|
||
{
|
||
switch (ev) {
|
||
|
||
case (KEY_UP << KEY_LONG_PRESS):
|
||
{
|
||
system_setZeroAngle();
|
||
break;
|
||
}
|
||
|
||
case (KEY_DOWN << KEY_LONG_PRESS):
|
||
{
|
||
system_clearZeroAngle();
|
||
break;
|
||
}
|
||
#if 0
|
||
case (KEY0 << KEY_SHORT_PRESS):
|
||
|
||
if (_mode == GUI_MENU)
|
||
{
|
||
menuNext();
|
||
}
|
||
ESP_LOGI(TAG, "MAIN: Button 1 SHORT");
|
||
break;
|
||
case (KEY0 << KEY_LONG_PRESS):
|
||
{
|
||
|
||
|
||
if (_mode != GUI_MENU)
|
||
{
|
||
_mode = GUI_MENU;
|
||
lv_obj_remove_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
else
|
||
if (_mode == GUI_MENU)
|
||
{
|
||
activate_selected();
|
||
_mode = GUI_BUBBLE;
|
||
// lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||
// lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
break;
|
||
}
|
||
case (KEY1 << KEY_SHORT_PRESS):
|
||
{
|
||
if (_mode == GUI_MENU)
|
||
{
|
||
menuPrevious();
|
||
}
|
||
break;
|
||
}
|
||
case (KEY1 << KEY_LONG_PRESS):
|
||
ESP_LOGI(TAG, "MAIN: Button 2 LONG");
|
||
gpio_set_level(PIN_NUM_nON, 0);
|
||
break;
|
||
#endif
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
if (_mode == GUI_BUBBLE)
|
||
{
|
||
uint32_t notifiedBits = 0;
|
||
// clear on entry (first param), wait for any bit, block forever
|
||
xTaskNotifyWait(
|
||
0xFFFFFFFF, // clear any old bits on entry
|
||
0xFFFFFFFF, // clear bits on exit
|
||
¬ifiedBits,
|
||
pdMS_TO_TICKS(100));
|
||
|
||
if (notifiedBits & EM_EVENT_NEW_DATA)
|
||
{
|
||
ImuData_t d = system_getImuData();
|
||
bubble_setValue(_bubble, -d.angle);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
=======
|
||
#include <stdio.h>
|
||
#include <math.h>
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
|
||
#include "esp_lcd_panel_io.h"
|
||
#include "esp_lcd_panel_vendor.h"
|
||
#include "esp_lcd_panel_ops.h"
|
||
#include "driver/gpio.h"
|
||
#include "driver/spi_master.h"
|
||
#include "esp_err.h"
|
||
#include "esp_log.h"
|
||
#include "esp_timer.h"
|
||
#include "lvgl.h"
|
||
#include "esp_lvgl_port.h"
|
||
|
||
#include "gui.h"
|
||
#include "gpio.h"
|
||
#include "keypad.h"
|
||
#include "bubble.h"
|
||
#include "system.h"
|
||
|
||
#define DEVKIT
|
||
#undef DEVKIT
|
||
|
||
static const char *TAG = "gui";
|
||
|
||
#define UNLOCK() lvgl_port_unlock()
|
||
#define LOCK() lvgl_port_lock(0)
|
||
|
||
// LCD Pin Configuration
|
||
#define LCD_PIXEL_CLOCK_HZ (5 * 1000 * 1000)
|
||
#define LCD_SPI_HOST SPI2_HOST
|
||
|
||
#define PIN_NUM_nON 26
|
||
|
||
|
||
|
||
// ST7735S properties
|
||
#define LCD_H_RES 160
|
||
#define LCD_V_RES 80
|
||
#define LCD_CMD_BITS 8
|
||
#define LCD_PARAM_BITS 8
|
||
#define LCD_COLOR_SPACE ESP_LCD_COLOR_SPACE_RGB
|
||
#define LCD_BITS_PER_PIXEL 16
|
||
|
||
static esp_lcd_panel_handle_t panel_handle = NULL;
|
||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||
static lv_disp_t *disp = NULL;
|
||
static lv_obj_t *imu_label = NULL; // Label for IMU data
|
||
|
||
static lv_style_t style_mono8;
|
||
|
||
typedef struct
|
||
{
|
||
int selected;
|
||
int count;
|
||
lv_obj_t *obj;
|
||
} menu_context_t;
|
||
|
||
static void gui_task(void);
|
||
static void createBubble(lv_obj_t * scr);
|
||
static void build_scrollable_menu(void);
|
||
static void currentFocusIndex(menu_context_t *ctx);
|
||
|
||
#define MAX_ITEMS 10
|
||
#define VISIBLE_ITEMS 3
|
||
#define MENU_MAX_STRING_LENGTH 30
|
||
|
||
static const char *menu_items[MAX_ITEMS] = {
|
||
"V-Moda Crossfade", "Item 2", "Item 3", "Item 4",
|
||
"Item 5", "Item 6", "Item 7", "Item 8",
|
||
"Item 9", "Item 10"
|
||
};
|
||
|
||
static lv_obj_t *list;
|
||
static lv_obj_t *buttons[MAX_ITEMS];
|
||
static int selected_index = 0;
|
||
static lv_obj_t *_bubble = NULL;
|
||
static lv_obj_t * _menu = NULL;
|
||
static lv_obj_t *_currentPage = NULL;
|
||
|
||
static GuiMode_t _mode = GUI_BUBBLE;
|
||
|
||
/* 1. Prepare a default (unfocused) style */
|
||
static lv_style_t _styleUnfocusedBtn;
|
||
|
||
|
||
/* 2. Prepare a focus style */
|
||
static lv_style_t _styleFocusedBtn;
|
||
|
||
static menu_context_t _menuContext;
|
||
|
||
static bool notify_lvgl_flush_ready(void *user_ctx) {
|
||
if (disp) {
|
||
lv_display_flush_ready(disp);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
|
||
static void create_lvgl_demo(void)
|
||
{
|
||
|
||
lvgl_port_lock(0);
|
||
// Create a screen with black background
|
||
lv_obj_t *scr = lv_scr_act();
|
||
|
||
createBubble(scr);
|
||
//build_scrollable_menu();
|
||
|
||
lvgl_port_unlock();
|
||
|
||
|
||
}
|
||
|
||
static void lcd_init(void)
|
||
{
|
||
ESP_LOGI(TAG, "Initialize SPI bus");
|
||
spi_bus_config_t buscfg = {
|
||
.sclk_io_num = PIN_NUM_CLK,
|
||
.mosi_io_num = PIN_NUM_MOSI,
|
||
.miso_io_num = -1,
|
||
.quadwp_io_num = -1,
|
||
.quadhd_io_num = -1,
|
||
.max_transfer_sz = LCD_H_RES * LCD_V_RES * 2
|
||
};
|
||
|
||
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||
|
||
ESP_LOGI(TAG, "Install panel IO");
|
||
|
||
esp_lcd_panel_io_spi_config_t io_config = {
|
||
.dc_gpio_num = PIN_NUM_DC,
|
||
.cs_gpio_num = PIN_NUM_CS,
|
||
.pclk_hz = LCD_PIXEL_CLOCK_HZ,
|
||
.lcd_cmd_bits = LCD_CMD_BITS,
|
||
.lcd_param_bits = LCD_PARAM_BITS,
|
||
.spi_mode = 3,
|
||
.trans_queue_depth = 10,
|
||
.user_ctx = NULL,
|
||
};
|
||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_config, &io_handle));
|
||
|
||
esp_lcd_panel_dev_config_t panel_config = {
|
||
.reset_gpio_num = PIN_NUM_RST,
|
||
.rgb_endian = LCD_RGB_ENDIAN_BGR,
|
||
.bits_per_pixel = LCD_BITS_PER_PIXEL,
|
||
};
|
||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
|
||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
|
||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
|
||
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
|
||
|
||
|
||
|
||
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, 1, 26)); // ST7735S typically needs these offsets
|
||
|
||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
|
||
}
|
||
|
||
static void lvgl_init(void)
|
||
{
|
||
lv_init();
|
||
|
||
|
||
#if 1
|
||
const lvgl_port_cfg_t lvgl_cfg = {
|
||
.task_priority = 4, // LVGL task priority
|
||
.task_stack = 16384, // LVGL task stack size
|
||
.task_affinity = 0, // LVGL task can run on any core
|
||
.task_max_sleep_ms = 500, // Maximum sleep in LVGL task
|
||
.timer_period_ms = 5 // LVGL timer period
|
||
};
|
||
|
||
ESP_ERROR_CHECK(lvgl_port_init(&lvgl_cfg));
|
||
#endif
|
||
|
||
const lvgl_port_display_cfg_t disp_cfg = {
|
||
.io_handle = io_handle,
|
||
.panel_handle = panel_handle,
|
||
.buffer_size = LCD_H_RES * LCD_V_RES * 2,
|
||
.double_buffer = false,
|
||
.hres = LCD_H_RES,
|
||
.vres = LCD_V_RES,
|
||
.monochrome = false,
|
||
.rotation = {
|
||
.swap_xy = true,
|
||
.mirror_x = true,
|
||
.mirror_y = false,
|
||
},
|
||
.flags = {
|
||
//.buff_dma = true,
|
||
.swap_bytes = true,
|
||
}
|
||
};
|
||
|
||
disp = lvgl_port_add_disp(&disp_cfg);
|
||
//lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
|
||
|
||
|
||
}
|
||
|
||
static void createBubble(lv_obj_t * scr)
|
||
{
|
||
|
||
// 2) Create a bubble level of size 200×60, with range [−30°, +30°], initial 0°:
|
||
lv_obj_t * level = bubble_create(scr, 150, 40, -10.0f, +10.0f, 0.0f);
|
||
lv_obj_align(level, LV_ALIGN_CENTER, 0, 0);
|
||
|
||
// 3) … Later, when you read your accelerometer or keypad‐derived angle …
|
||
float new_angle = 10.0f;
|
||
bubble_setValue(level, new_angle);
|
||
|
||
// 4) You can call bubble_level_set_value(level, …) as often as you like.
|
||
// Each call invalidates the object and LVGL will call the draw callback
|
||
// (usually on the next tick, or immediately if LVGL is idle).
|
||
|
||
_bubble = level;
|
||
}
|
||
|
||
|
||
void gui_start(void)
|
||
{
|
||
|
||
// Initialize LCD
|
||
lcd_init();
|
||
|
||
// Initialize LVGL
|
||
lvgl_init();
|
||
|
||
// Create UI
|
||
create_lvgl_demo();
|
||
|
||
keypad_start();
|
||
|
||
gpio_set_level(PIN_NUM_BK_LIGHT, 1);
|
||
|
||
xTaskCreate(gui_task, "gui_task", 4096, NULL, 5, NULL);
|
||
|
||
}
|
||
|
||
static uint32_t waitForKeyPress(void)
|
||
{
|
||
QueueHandle_t q = keypad_getQueue();
|
||
uint32_t ev = 0;
|
||
|
||
if (xQueueReceive(q, &ev, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
return ev;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void handleMainMenu(void)
|
||
{
|
||
lvgl_port_lock(0);
|
||
// Create a screen with black background
|
||
lv_obj_t *scr = lv_scr_act();
|
||
|
||
//create_menu(scr);
|
||
|
||
lvgl_port_unlock();
|
||
}
|
||
|
||
|
||
// ───── MENU CONFIG ─────
|
||
#define ROW_H 20 // height of each row
|
||
static const char * items[] = { // your menu entries
|
||
"VMode Crossmade",
|
||
"Second Choice",
|
||
"Another Option",
|
||
"Yet Another",
|
||
"Yet Another",
|
||
"Last One"
|
||
};
|
||
#define ITEM_COUNT (sizeof(items)/sizeof(items[0]))
|
||
|
||
// ───── STATE & HELPERS ─────
|
||
static lv_obj_t * btn_array[ITEM_COUNT];
|
||
static int selected_idx = 0;
|
||
|
||
// Called whenever a button is “activated” (Enter/Click)
|
||
static void btn_click_cb(lv_event_t * e) {
|
||
lv_obj_t * btn = lv_event_get_target(e);
|
||
const char * txt = lv_label_get_text(lv_obj_get_child(btn, 0));
|
||
ESP_LOGI(TAG, "Activated: %s\n", txt);
|
||
}
|
||
|
||
// Repaint all rows so only btn_array[selected_idx] is highlighted
|
||
static void refresh_highlight(void) {
|
||
|
||
return;
|
||
lvgl_port_lock(0);
|
||
|
||
int index = 0;
|
||
|
||
lv_obj_t *page = _currentPage;
|
||
|
||
lv_obj_t * child = NULL;
|
||
lv_obj_t * next = lv_obj_get_child(page, index);
|
||
lv_obj_t * selected = NULL;
|
||
while(next) {
|
||
child = next;
|
||
|
||
ESP_LOGI(TAG, "Child: %p", child);
|
||
|
||
|
||
if (lv_obj_has_flag(child, LV_OBJ_FLAG_USER_1))
|
||
{
|
||
lv_obj_set_style_bg_color(child, lv_color_hex(0xFF8800), 0);
|
||
lv_obj_set_style_text_color(lv_obj_get_child(child,0),
|
||
lv_color_white(), 0);
|
||
|
||
selected = child;
|
||
ESP_LOGI(TAG, "Selected");
|
||
}
|
||
else
|
||
{
|
||
lv_obj_set_style_bg_color(child, lv_color_white(), 0);
|
||
lv_obj_set_style_text_color(lv_obj_get_child(child,0),
|
||
lv_color_black(), 0);
|
||
}
|
||
|
||
index++;
|
||
next = lv_obj_get_child(page, index);
|
||
}
|
||
|
||
if (selected)
|
||
{
|
||
lv_obj_scroll_to_view(selected, LV_ANIM_ON);
|
||
}
|
||
|
||
lvgl_port_unlock();
|
||
}
|
||
|
||
|
||
static void menuInc(int inc)
|
||
{
|
||
LOCK();
|
||
|
||
currentFocusIndex(&_menuContext);
|
||
|
||
ESP_LOGI(TAG, "Current Index: %d", _menuContext.selected);
|
||
|
||
lv_obj_t *next = NULL;
|
||
|
||
// check if we are at the first or last in the page
|
||
lv_obj_t *test = lv_obj_get_child(_currentPage, (inc > 0 ? -1 : 0));
|
||
|
||
if (_menuContext.obj != test)
|
||
{
|
||
next = lv_obj_get_child(_currentPage, _menuContext.selected + inc);
|
||
lv_obj_add_state(next, LV_STATE_FOCUSED);
|
||
lv_obj_clear_state(_menuContext.obj, LV_STATE_FOCUSED);
|
||
lv_obj_scroll_to_view(next, LV_ANIM_ON);
|
||
|
||
_menuContext.obj = next;
|
||
_menuContext.selected += inc;
|
||
}
|
||
|
||
UNLOCK();
|
||
}
|
||
|
||
static void menuNext(void)
|
||
{
|
||
menuInc(1);
|
||
}
|
||
|
||
|
||
static void menuPrevious(void)
|
||
{
|
||
menuInc(-1);
|
||
}
|
||
|
||
// Fire the “clicked” event on the selected row
|
||
static void activate_selected(void)
|
||
{
|
||
LOCK();
|
||
lv_obj_send_event(_menuContext.obj, LV_EVENT_CLICKED, NULL);
|
||
UNLOCK();
|
||
}
|
||
|
||
static lv_obj_t * addMenuItem(lv_obj_t *page, const char *text)
|
||
{
|
||
lv_obj_t * btn = lv_btn_create(page);
|
||
lv_obj_set_size(btn, LV_PCT(100), ROW_H);
|
||
|
||
|
||
lv_obj_add_style(btn, &_styleUnfocusedBtn, LV_PART_MAIN | LV_STATE_DEFAULT);
|
||
lv_obj_add_style(btn, &_styleFocusedBtn, LV_PART_MAIN | LV_STATE_FOCUSED);
|
||
|
||
lv_obj_add_state(btn, LV_STATE_DEFAULT);
|
||
|
||
|
||
// style it just like your old list
|
||
// lv_obj_set_style_bg_color(btn, lv_color_white(), 0);
|
||
lv_obj_set_style_radius(btn, 0, LV_PART_MAIN);
|
||
lv_obj_set_style_border_width(btn, 0, LV_PART_MAIN);
|
||
lv_obj_clear_flag(btn, LV_OBJ_FLAG_SCROLLABLE);
|
||
|
||
// label & center
|
||
lv_obj_t * lbl = lv_label_create(btn);
|
||
lv_label_set_text(lbl, text);
|
||
lv_obj_set_style_radius(lbl, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_style_border_width(lbl, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
|
||
// click callback
|
||
lv_obj_add_event_cb(btn, btn_click_cb, LV_EVENT_CLICKED, NULL);
|
||
|
||
return btn;
|
||
}
|
||
|
||
static int getSelectedIndex(lv_obj_t *page)
|
||
{
|
||
return -1;
|
||
}
|
||
|
||
|
||
|
||
static void currentFocusIndex(menu_context_t *ctx)
|
||
{
|
||
ctx->count = lv_obj_get_child_cnt(_currentPage);
|
||
ctx->obj = NULL;
|
||
ctx->selected = -1;
|
||
|
||
// return the index of the currently focused object
|
||
for(int i = 0; i < ctx->count; i++) {
|
||
lv_obj_t * child = lv_obj_get_child(_currentPage, i);
|
||
|
||
if (lv_obj_has_state(child, LV_STATE_FOCUSED))
|
||
{
|
||
ctx->obj = child;
|
||
ctx->selected = i;
|
||
|
||
return;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
}
|
||
// ───── BUILD THE MENU ─────
|
||
static void build_scrollable_menu(void) {
|
||
|
||
|
||
lv_style_init(&_styleFocusedBtn);
|
||
lv_style_init(&_styleUnfocusedBtn);
|
||
|
||
lv_style_set_bg_color(&_styleUnfocusedBtn, lv_color_make(0xff,0xff,0xff)); // gray bg
|
||
lv_style_set_text_color(&_styleUnfocusedBtn, lv_color_hex(0xFF8800));
|
||
|
||
lv_style_set_bg_color(&_styleFocusedBtn, lv_color_make(0x33,0x99,0xFF)); // blue bg
|
||
lv_style_set_text_color(&_styleUnfocusedBtn,lv_color_black());
|
||
|
||
|
||
// 2) Inside it, create the lv_menu and hide its sidebar/header
|
||
lv_obj_t *menu = lv_menu_create(lv_scr_act());
|
||
_menu = menu;
|
||
|
||
lv_obj_set_style_radius(menu, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_style_border_width(menu, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_size(menu, lv_pct(100), lv_pct(100));
|
||
lv_obj_center(menu);
|
||
|
||
lv_obj_set_scrollbar_mode(menu, LV_SCROLLBAR_MODE_AUTO);
|
||
|
||
|
||
lv_obj_t * main = lv_menu_page_create(menu, NULL);
|
||
lv_obj_set_style_radius(main, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_style_border_width(main, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_scrollbar_mode(main, LV_SCROLLBAR_MODE_AUTO);
|
||
lv_obj_set_size(main, lv_pct(100), lv_pct(100));
|
||
|
||
lv_menu_set_page(menu, main);
|
||
|
||
lv_obj_t * tmpObj;
|
||
|
||
|
||
|
||
lv_obj_t * calMenu = lv_menu_page_create(menu, NULL);
|
||
lv_obj_set_style_radius(calMenu, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_style_border_width(calMenu, 0, LV_PART_MAIN | LV_STATE_ANY);
|
||
lv_obj_set_scrollbar_mode(calMenu, LV_SCROLLBAR_MODE_AUTO);
|
||
|
||
tmpObj = addMenuItem(main, "Bluetooth");
|
||
lv_obj_add_state(tmpObj, LV_STATE_FOCUSED);
|
||
//lv_obj_add_flag(tmpObj, LV_OBJ_FLAG_USER_1);
|
||
|
||
tmpObj = addMenuItem(main, "Calibration");
|
||
lv_menu_set_load_page_event(menu, tmpObj, calMenu);
|
||
|
||
|
||
tmpObj = addMenuItem(main, "Volume");
|
||
|
||
addMenuItem(main, "About");
|
||
addMenuItem(main, "Exit");
|
||
|
||
|
||
addMenuItem(calMenu, "Calibrate Level");
|
||
addMenuItem(calMenu, "Reset Calibration");
|
||
addMenuItem(calMenu, "Exit");
|
||
|
||
_currentPage = main;
|
||
|
||
// 6) Initial highlight
|
||
selected_idx = 0;
|
||
refresh_highlight();
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
static void gui_task(void)
|
||
{
|
||
system_subscribe(xTaskGetCurrentTaskHandle());
|
||
|
||
// Grab queue handle
|
||
QueueHandle_t q = keypad_getQueue();
|
||
uint32_t ev = 0;
|
||
|
||
LOCK();
|
||
// _mode = GUI_MENU;
|
||
// lv_obj_remove_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||
lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||
//lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||
UNLOCK();
|
||
|
||
while (1)
|
||
{
|
||
|
||
|
||
if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE)
|
||
{
|
||
switch (ev) {
|
||
|
||
case (KEY_UP << KEY_LONG_PRESS):
|
||
{
|
||
system_setZeroAngle();
|
||
break;
|
||
}
|
||
|
||
case (KEY_DOWN << KEY_LONG_PRESS):
|
||
{
|
||
system_clearZeroAngle();
|
||
break;
|
||
}
|
||
#if 0
|
||
case (KEY0 << KEY_SHORT_PRESS):
|
||
|
||
if (_mode == GUI_MENU)
|
||
{
|
||
menuNext();
|
||
}
|
||
ESP_LOGI(TAG, "MAIN: Button 1 SHORT");
|
||
break;
|
||
case (KEY0 << KEY_LONG_PRESS):
|
||
{
|
||
|
||
|
||
if (_mode != GUI_MENU)
|
||
{
|
||
_mode = GUI_MENU;
|
||
lv_obj_remove_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||
lv_obj_add_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
else
|
||
if (_mode == GUI_MENU)
|
||
{
|
||
activate_selected();
|
||
_mode = GUI_BUBBLE;
|
||
// lv_obj_remove_flag(_bubble, LV_OBJ_FLAG_HIDDEN);
|
||
// lv_obj_add_flag(_menu, LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
break;
|
||
}
|
||
case (KEY1 << KEY_SHORT_PRESS):
|
||
{
|
||
if (_mode == GUI_MENU)
|
||
{
|
||
menuPrevious();
|
||
}
|
||
break;
|
||
}
|
||
case (KEY1 << KEY_LONG_PRESS):
|
||
ESP_LOGI(TAG, "MAIN: Button 2 LONG");
|
||
gpio_set_level(PIN_NUM_nON, 0);
|
||
break;
|
||
#endif
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
if (_mode == GUI_BUBBLE)
|
||
{
|
||
uint32_t notifiedBits = 0;
|
||
// clear on entry (first param), wait for any bit, block forever
|
||
xTaskNotifyWait(
|
||
0xFFFFFFFF, // clear any old bits on entry
|
||
0xFFFFFFFF, // clear bits on exit
|
||
¬ifiedBits,
|
||
pdMS_TO_TICKS(100));
|
||
|
||
if (notifiedBits & EM_EVENT_NEW_DATA)
|
||
{
|
||
ImuData_t d = system_getImuData();
|
||
bubble_setValue(_bubble, -d.angle);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
>>>>>>> 4feb4c0a98bddb1f2a172ea4b195ce31ba18d442
|
||
} |