Initial commit

IMU and bubble level working
This commit is contained in:
Brent Perteet
2025-06-14 16:38:53 -05:00
commit f59eb660cf
21 changed files with 6704 additions and 0 deletions

400
main/gui.c Normal file
View File

@@ -0,0 +1,400 @@
#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]);
}
}
}