Files
soundshot/main/gui.c
Brent Perteet f59eb660cf Initial commit
IMU and bubble level working
2025-06-14 16:38:53 -05:00

400 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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";
// 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;
static void gui_task(void);
static void createBubble(lv_obj_t * scr);
#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;
// Update highlight based on selected_index
void update_highlight() {
lvgl_port_lock(0);
for (int i = 0; i < MAX_ITEMS; i++) {
if (buttons[i]) {
lv_obj_remove_state(buttons[i], LV_STATE_FOCUSED);
}
}
//if (buttons[selected_index])
{
lv_obj_add_state(buttons[selected_index], LV_STATE_FOCUSED);
lv_obj_scroll_to_view(buttons[selected_index], LV_ANIM_ON);
}
lvgl_port_unlock();
}
// Called when the GPIO button is pressed
void scroll_down() {
if (selected_index < MAX_ITEMS - 1) {
selected_index++;
update_highlight();
}
}
void scroll_up() {
if (selected_index > 0) {
selected_index--;
update_highlight();
}
}
static void create_menu(lv_obj_t *parent)
{
lvgl_port_lock(0);
list = lv_list_create(parent);
lv_obj_set_size(list, 160, 80);
lv_obj_center(list);
lv_obj_set_style_pad_row(list, 2, 0);
lv_obj_set_scroll_dir(list, LV_DIR_VER);
lv_obj_set_scrollbar_mode(list, LV_SCROLLBAR_MODE_AUTO);
for (int i = 0; i < MAX_ITEMS; i++) {
buttons[i] = lv_list_add_btn(list, NULL, menu_items[i]);
}
// Apply style to show selection highlight
static lv_style_t style_focus;
lv_style_init(&style_focus);
lv_style_set_bg_color(&style_focus, lv_palette_main(LV_PALETTE_RED));
lv_style_set_bg_opa(&style_focus, LV_OPA_COVER);
lv_style_set_text_color(&style_focus, lv_color_white());
for (int i = 0; i < MAX_ITEMS; i++) {
lv_obj_add_style(buttons[i], &style_focus, LV_STATE_FOCUSED);
}
update_highlight();
lvgl_port_unlock();
}
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();
//lv_obj_set_style_bg_color(scr, lv_color_black(), LV_PART_MAIN);
//lv_obj_set_style_bg_color(scr, lv_palette_main(LV_PALETTE_RED), LV_PART_MAIN);
//lv_style_init(&style_mono8);
//lv_style_set_text_font(&style_mono8, &lv_font_unscii_8);
//create_menu(scr);
createBubble(scr);
lvgl_port_unlock();
#if 0
// Create a label for IMU data
imu_label = lv_label_create(scr);
lv_obj_set_style_text_font(imu_label, &lv_font_montserrat_20, LV_PART_MAIN);
lv_obj_set_style_text_color(imu_label, lv_color_white(), LV_PART_MAIN);
lv_obj_align(imu_label, LV_ALIGN_CENTER, 0, 0);
lv_label_set_text(imu_label, "SOUNDSHOT!");
// // Create a button
// lv_obj_t *btn = lv_btn_create(scr);
// lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_size(btn, 70, 30);
// // Add label to button
// label = lv_label_create(btn);
// lv_label_set_text(label, "Button");
// lv_obj_center(label);
// // Create a slider
// lv_obj_t *slider = lv_slider_create(scr);
// lv_obj_align(slider, LV_ALIGN_BOTTOM_MID, 0, -20);
// lv_obj_set_width(slider, 70);
#endif
}
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,
//.on_color_trans_done = notify_lvgl_flush_ready,
.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 = 32768, // LVGL task stack size
.task_affinity = -1, // 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 = true,
.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 keypadderived 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 ui_test()
{
lv_obj_t *label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "Hello, LVGL 9.2!");
lv_obj_center(label);
}
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);
system_notifyAll(EM_EVENT_NEW_DATA);
}
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();
}
static void gui_task(void)
{
system_subscribe(xTaskGetCurrentTaskHandle());
// Grab queue handle
QueueHandle_t q = keypad_getQueue();
uint32_t ev = 0;
while (1)
{
if (xQueueReceive(q, &ev, pdMS_TO_TICKS(10)) == pdTRUE)
{
switch (ev) {
case (KEY0 << KEY_SHORT_PRESS):
scroll_down();
ESP_LOGI(TAG, "MAIN: Button 1 SHORT");
break;
case (KEY0 << KEY_LONG_PRESS):
ESP_LOGI(TAG, "MAIN: Button 1 LONG");
break;
case (KEY1 << KEY_SHORT_PRESS):
scroll_up();
ESP_LOGI(TAG, "MAIN: Button 2 SHORT");
break;
case (KEY1 << KEY_LONG_PRESS):
ESP_LOGI(TAG, "MAIN: Button 2 LONG");
gpio_set_level(PIN_NUM_nON, 0);
break;
default:
break;
}
}
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
&notifiedBits,
portMAX_DELAY);
if (notifiedBits & EM_EVENT_NEW_DATA)
{
ImuData_t d = system_getImuData();
//ESP_LOGI(TAG, "Angle: %.2f", a);
bubble_setValue(_bubble, -d.filtered[ANGLE_XY]);
}
}
}