From f02d138ffa790dd6a567076adb747262ed71ef44 Mon Sep 17 00:00:00 2001 From: maddiebaka Date: Wed, 31 Dec 2025 11:56:54 -0500 Subject: [PATCH] Finish display impl, write UI widget, refactor --- main/CMakeLists.txt | 2 +- main/display.c | 10 +- main/display.h | 7 +- main/motor.c | 206 ++++++++++++++++++++++++++++++++ main/motor.h | 23 ++++ main/spincoat-plater-firmware.c | 200 +++---------------------------- main/ui.c | 98 +++++++++++++++ main/ui.h | 21 ++++ 8 files changed, 368 insertions(+), 199 deletions(-) create mode 100644 main/motor.c create mode 100644 main/motor.h create mode 100644 main/ui.c create mode 100644 main/ui.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 6bebada..e37bfaa 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "spincoat-plater-firmware.c" "dshot_esc_encoder.c" "display.c" +idf_component_register(SRCS "spincoat-plater-firmware.c" "dshot_esc_encoder.c" "display.c" "motor.c" "ui.c" PRIV_REQUIRES esp_driver_rmt esp_driver_gpio esp_driver_uart esp_driver_spi esp_lcd unity lvgl esp_lvgl_port esp_lcd_touch_xpt2046 INCLUDE_DIRS ".") diff --git a/main/display.c b/main/display.c index 9bfd549..04f304e 100644 --- a/main/display.c +++ b/main/display.c @@ -58,7 +58,6 @@ void touch_driver_read(lv_indev_t * indev, lv_indev_data_t * data) { bool touchpad_pressed = esp_lcd_touch_get_coordinates(tp, x, y, strength, &count, 1); if(touchpad_pressed == true) { - //ESP_LOGI(TAG, "Touchpad pressed from LVGL..\n"); data->point.x = x[0]; data->point.y = y[0]; data->state = LV_INDEV_STATE_PRESSED; @@ -73,8 +72,6 @@ void touch_driver_read(lv_indev_t * indev, lv_indev_data_t * data) { static void btn_event_cb(lv_event_t * e) { lv_event_code_t code = lv_event_get_code(e); - //ESP_LOGI(TAG, "Button callback fired."); - if(code == LV_EVENT_CLICKED) { ESP_LOGI(TAG, "LVGL button pressed."); } @@ -132,8 +129,8 @@ void init_touchscreen_xpt2046(void) { ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(TOUCH_SPI_HOST, &tp_io_config, &tp_io_handle)); esp_lcd_touch_config_t tp_cfg = { - .x_max = TFT_HRES, - .y_max = TFT_VRES, + .x_max = TFT_VRES, + .y_max = TFT_HRES, .rst_gpio_num = -1, .int_gpio_num = -1, .flags = { @@ -227,7 +224,7 @@ void init_lvgl_display(void) { }, .flags = { .buff_dma = false, - .swap_bytes = false, + .swap_bytes = true, } }; disp_handle = lvgl_port_add_disp(&disp_cfg); @@ -253,5 +250,4 @@ void init_display(void) { init_touchscreen_xpt2046(); init_lvgl_display(); init_input(); - lv_example_btn_1(); } diff --git a/main/display.h b/main/display.h index 66f57ce..e0b7676 100644 --- a/main/display.h +++ b/main/display.h @@ -11,12 +11,7 @@ #define TFT_VRES CONFIG_TFT_VRES #define TFT_BPP CONFIG_TFT_BPP -#define LVGL_BUF_SIZE TFT_VRES * TFT_HRES / 10 * (TFT_BPP / 8) - -/** - * Draws a test bitmap of stripes of colors to the LCD. - */ -void test_draw_bitmap(esp_lcd_panel_handle_t panel_handle); +#define LVGL_BUF_SIZE ((TFT_VRES * TFT_HRES) / 10) * (TFT_BPP / 8) /** * Initializes the display TFT and touchscreen. diff --git a/main/motor.c b/main/motor.c new file mode 100644 index 0000000..a48ce56 --- /dev/null +++ b/main/motor.c @@ -0,0 +1,206 @@ +#include "motor.h" + +#include "driver/spi_common.h" +#include "driver/rmt_tx.h" +#include "driver/uart.h" +#include "esp_log.h" + +#include "dshot_esc_encoder.h" + +#define GPIO_ESC_CTRL CONFIG_ESC_CTRL_PIN +#define GPIO_ESC_RX CONFIG_TELEMETRY_RX_PIN +#define ESP_INTR_FLAG_DEFAULT 0 + +static const char *TAG = "spincoat-plater-firmware/motor"; + +static QueueHandle_t uart_queue = NULL; +const int uart_buffer_size = (1024 * 2); + +rmt_encoder_handle_t dshot_encoder = NULL; +rmt_channel_handle_t esc_chan = NULL; + +rmt_transmit_config_t tx_config = { + .loop_count = 0, +}; + +dshot_esc_throttle_t throttle = { + .throttle = 0, + .telemetry_req = false, +}; + +/** + * Sends a telemetry packet at a set, constant interval + */ +void v_telemetry_packet_func(void *pvParameters) { + TickType_t frequency = 10 / portTICK_PERIOD_MS; + TickType_t last_wake_time = xTaskGetTickCount(); + while(1) { + throttle.telemetry_req = true; + vTaskDelayUntil(&last_wake_time, frequency); + } +} + +/** + * Sends zero throttle to arm ESC for control. Stop/delete this task once the ESC has armed. + */ +void v_initialize_esc_throttle_func(void *pvParameters) { + while(1) { + ESP_ERROR_CHECK(rmt_transmit(esc_chan, dshot_encoder, &throttle, sizeof(throttle), &tx_config)); + } +} + +/** + * Starts task *v_initialize_esc_throttle_func()* for a few seconds and then destroys it. + * This function takes care of the arming stage of ESC control. + */ +void initialize_esc_throttle(void) { + TaskHandle_t v_init_throttle_handle = NULL; + xTaskCreate(&v_initialize_esc_throttle_func, "v_init_throttle_func", 2048, NULL, 5, &v_init_throttle_handle); + vTaskDelay(pdMS_TO_TICKS(5000)); + vTaskDelete(v_init_throttle_handle); +} + +/** + * Initialize the RMT system in preparation for sending DSHOT packets to the connected ESC. + */ +void init_rmt_esc_tx(void) { + ESP_LOGI(TAG, "Create RMT TX channel"); + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, // select a clock that can provide needed resolution + .gpio_num = GPIO_ESC_CTRL, + .mem_block_symbols = 64, + .resolution_hz = DSHOT_ESC_RESOLUTION_HZ, + .trans_queue_depth = 10, // set the number of transactions that can be pending in the background + }; + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &esc_chan)); + + ESP_LOGI(TAG, "Install Dshot ESC encoder"); + dshot_esc_encoder_config_t encoder_config = { + .resolution = DSHOT_ESC_RESOLUTION_HZ, + .baud_rate = 300000, // DSHOT300 protocol + .post_delay_us = 50, // extra delay between each frame + }; + ESP_ERROR_CHECK(rmt_new_dshot_esc_encoder(&encoder_config, &dshot_encoder)); + + ESP_LOGI(TAG, "Enable RMT TX channel"); + ESP_ERROR_CHECK(rmt_enable(esc_chan)); + + ESP_LOGI(TAG, "Start ESC by sending zero throttle for a while..."); + initialize_esc_throttle(); +} + +/** + * Initialize the UART receive pin so that we can receive telemetry data from the connected ESC. + */ +void init_telemetry_uart_rx(void) { + uart_config_t uart_config = { + .baud_rate = 115200, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS, + .rx_flow_ctrl_thresh = 122, + }; + + ESP_ERROR_CHECK(uart_driver_install(ESC_UART_NUM, uart_buffer_size, uart_buffer_size, 10, &uart_queue, 0)); + ESP_ERROR_CHECK(uart_param_config(ESC_UART_NUM, &uart_config)); + ESP_ERROR_CHECK(uart_set_pin(ESC_UART_NUM, UART_PIN_NO_CHANGE, GPIO_ESC_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); +} + + +/** + * Sends a DSHOT packet via the RMT. Make sure the RMT channel has been initialized + * by calling *init_rmt_esc_tx()* + */ +void send_dshot_packet(void) { + ESP_ERROR_CHECK(rmt_transmit(esc_chan, dshot_encoder, &throttle, sizeof(throttle), &tx_config)); + + if(throttle.telemetry_req == true) { + throttle.telemetry_req = false; + } +} + +/** + * Calculate one step of the crc8 and return it + */ +uint8_t update_crc8(uint8_t crc, uint8_t crc_seed){ + uint8_t crc_u, i; + crc_u = crc; + crc_u ^= crc_seed; + for ( i=0; i<8; i++) crc_u = ( crc_u & 0x80 ) ? 0x7 ^ ( crc_u << 1 ) : ( crc_u << 1 ); + return (crc_u); +} + +/** + * Calculate the entire crc8 for a KISS frame and return it for validation against the + * transmitted crc8 + */ +uint8_t get_crc8(uint8_t *Buf, uint8_t BufLen){ + uint8_t crc = 0, i; + for( i=0; i + +#if CONFIG_IDF_TARGET_ESP32H2 +#define DSHOT_ESC_RESOLUTION_HZ 32000000 // 32MHz resolution, DSHot protocol needs a relative high resolution +#else +#define DSHOT_ESC_RESOLUTION_HZ 40000000 // 40MHz resolution, DSHot protocol needs a relative high resolution +#endif + +#define ESC_UART_NUM UART_NUM_2 + +void init_rmt_esc_tx(void); + +void send_dshot_packet(void); + +void parse_telemetry(void); + +void init_motor(void); + +uint16_t get_throttle(void); + +void update_throttle(uint16_t throttle); diff --git a/main/spincoat-plater-firmware.c b/main/spincoat-plater-firmware.c index 8156ae4..5c27cb9 100644 --- a/main/spincoat-plater-firmware.c +++ b/main/spincoat-plater-firmware.c @@ -10,216 +10,46 @@ #include "freertos/task.h" #include "freertos/semphr.h" -#include "unity.h" -#include "unity_test_runner.h" - #include "esp_log.h" -#include "driver/rmt_tx.h" #include "driver/uart.h" -#include "driver/gpio.h" -#include "hal/spi_types.h" #include "esp_lcd_panel_ops.h" #include "driver/spi_common.h" #include "esp_lcd_panel_io.h" #include "esp_lcd_panel_commands.h" #include "esp_lcd_ili9341.h" +#include "lvgl.h" #include "dshot_esc_encoder.h" #include "display.h" +#include "ui.h" +#include "motor.h" -#if CONFIG_IDF_TARGET_ESP32H2 -#define DSHOT_ESC_RESOLUTION_HZ 32000000 // 32MHz resolution, DSHot protocol needs a relative high resolution -#else -#define DSHOT_ESC_RESOLUTION_HZ 40000000 // 40MHz resolution, DSHot protocol needs a relative high resolution -#endif - -#define GPIO_ESC_CTRL CONFIG_ESC_CTRL_PIN -#define GPIO_ESC_RX CONFIG_TELEMETRY_RX_PIN -#define UART_NUM UART_NUM_2 -#define ESP_INTR_FLAG_DEFAULT 0 static const char *TAG = "spincoat-plater-firmware"; -static QueueHandle_t uart_queue = NULL; -const int uart_buffer_size = (1024 * 2); +static const uint16_t throttle = 200; -rmt_encoder_handle_t dshot_encoder = NULL; -rmt_channel_handle_t esc_chan = NULL; - -rmt_transmit_config_t tx_config = { - .loop_count = 0, -}; - -dshot_esc_throttle_t throttle = { - .throttle = 0, - .telemetry_req = false, // telemetry is not supported in this example -}; - -/** - * Sends a telemetry packet at a set, constant interval - */ -void v_telemetry_packet_func(void *pvParameters) { - TickType_t frequency = 10 / portTICK_PERIOD_MS; - TickType_t last_wake_time = xTaskGetTickCount(); - while(1) { - throttle.telemetry_req = true; - vTaskDelayUntil(&last_wake_time, frequency); - } -} - -/** - * Sends zero throttle to arm ESC for control. Stop/delete this task once the ESC has armed. - */ -void v_initialize_esc_throttle_func(void *pvParameters) { - while(1) { - ESP_ERROR_CHECK(rmt_transmit(esc_chan, dshot_encoder, &throttle, sizeof(throttle), &tx_config)); - } -} - -/** - * Starts task *v_initialize_esc_throttle_func()* for a few seconds and then destroys it. - * This function takes care of the arming stage of ESC control. - */ -void initialize_esc_throttle(void) { - TaskHandle_t v_init_throttle_handle = NULL; - xTaskCreate(&v_initialize_esc_throttle_func, "v_init_throttle_func", 2048, NULL, 5, &v_init_throttle_handle); - vTaskDelay(pdMS_TO_TICKS(5000)); - vTaskDelete(v_init_throttle_handle); -} - -/** - * Initialize the RMT system in preparation for sending DSHOT packets to the connected ESC. - */ -void init_rmt_esc_tx(void) { - ESP_LOGI(TAG, "Create RMT TX channel"); - rmt_tx_channel_config_t tx_chan_config = { - .clk_src = RMT_CLK_SRC_DEFAULT, // select a clock that can provide needed resolution - .gpio_num = GPIO_ESC_CTRL, - .mem_block_symbols = 64, - .resolution_hz = DSHOT_ESC_RESOLUTION_HZ, - .trans_queue_depth = 10, // set the number of transactions that can be pending in the background - }; - ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &esc_chan)); - - ESP_LOGI(TAG, "Install Dshot ESC encoder"); - dshot_esc_encoder_config_t encoder_config = { - .resolution = DSHOT_ESC_RESOLUTION_HZ, - .baud_rate = 300000, // DSHOT300 protocol - .post_delay_us = 50, // extra delay between each frame - }; - ESP_ERROR_CHECK(rmt_new_dshot_esc_encoder(&encoder_config, &dshot_encoder)); - - ESP_LOGI(TAG, "Enable RMT TX channel"); - ESP_ERROR_CHECK(rmt_enable(esc_chan)); - - ESP_LOGI(TAG, "Start ESC by sending zero throttle for a while..."); - initialize_esc_throttle(); -} - -/** - * Initialize the UART receive pin so that we can receive telemetry data from the connected ESC. - */ -void init_telemetry_uart_rx(void) { - uart_config_t uart_config = { - .baud_rate = 115200, - .data_bits = UART_DATA_8_BITS, - .parity = UART_PARITY_DISABLE, - .stop_bits = UART_STOP_BITS_1, - .flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS, - .rx_flow_ctrl_thresh = 122, - }; - - ESP_ERROR_CHECK(uart_driver_install(UART_NUM, uart_buffer_size, uart_buffer_size, 10, &uart_queue, 0)); - ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config)); - ESP_ERROR_CHECK(uart_set_pin(UART_NUM, UART_PIN_NO_CHANGE, GPIO_ESC_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); -} - - -/** - * Sends a DSHOT packet via the RMT. Make sure the RMT channel has been initialized - * by calling *init_rmt_esc_tx()* - */ -void send_dshot_packet(void) { - ESP_ERROR_CHECK(rmt_transmit(esc_chan, dshot_encoder, &throttle, sizeof(throttle), &tx_config)); - - if(throttle.telemetry_req == true) { - throttle.telemetry_req = false; - } -} - -/** - * Calculate one step of the crc8 and return it - */ -uint8_t update_crc8(uint8_t crc, uint8_t crc_seed){ - uint8_t crc_u, i; - crc_u = crc; - crc_u ^= crc_seed; - for ( i=0; i<8; i++) crc_u = ( crc_u & 0x80 ) ? 0x7 ^ ( crc_u << 1 ) : ( crc_u << 1 ); - return (crc_u); -} - -/** - * Calculate the entire crc8 for a KISS frame and return it for validation against the - * transmitted crc8 - */ -uint8_t get_crc8(uint8_t *Buf, uint8_t BufLen){ - uint8_t crc = 0, i; - for( i=0; i= 10) { parse_telemetry(); diff --git a/main/ui.c b/main/ui.c new file mode 100644 index 0000000..89b09e0 --- /dev/null +++ b/main/ui.c @@ -0,0 +1,98 @@ +#include "ui.h" + +// #include "lv_font.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" + +#include "motor.h" + +#define BTN_INCREMENT 100 + +static const char *TAG = "spincoat-plater-firmware/ui"; + +static lv_obj_t * rpm_label = NULL; + +void top_cb(lv_event_t * e) { + update_throttle(get_throttle() + BTN_INCREMENT); + lv_label_set_text_fmt(rpm_label, "RPM: %d", get_throttle()); +} + +void bottom_cb(lv_event_t * e) { + uint16_t throttle = get_throttle(); + if(throttle >= BTN_INCREMENT) { + update_throttle(get_throttle() - BTN_INCREMENT); + lv_label_set_text_fmt(rpm_label, "RPM: %d", get_throttle()); + } +} + +void build_numberstack(lv_obj_t * parent, + const char * label_text, + lv_obj_t ** label_value, + ns_btn_cb_t top_btn_cb, + ns_btn_cb_t bottom_btn_cb) { + + lv_obj_t * container = lv_obj_create(parent); + lv_obj_set_size(container , lv_pct(100), lv_pct(100)); + lv_obj_set_flex_flow(container, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(container, + LV_FLEX_ALIGN_START, + LV_FLEX_ALIGN_CENTER, + LV_FLEX_ALIGN_CENTER); + + lv_obj_set_style_pad_all(container, 10, 0); + lv_obj_set_style_pad_gap(container, 8, 0); + + /* -------- Top label -------- */ + lv_obj_t * label_cont = lv_obj_create(container); + + lv_obj_set_flex_grow(label_cont, 1); + lv_obj_set_width(label_cont, lv_pct(100)); + + lv_obj_t * label = lv_label_create(label_cont); + lv_label_set_text(label, label_text); + + lv_obj_set_style_bg_opa(label, LV_OPA_COVER, 0); + + lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0); + lv_obj_center(label); + + /* -------- Top button -------- */ + lv_obj_t * btn_top = lv_button_create(container); + lv_obj_t * lbl_top = lv_label_create(btn_top); + lv_label_set_text(lbl_top, "+100"); + lv_obj_add_event_cb(btn_top, top_cb, LV_EVENT_CLICKED, (void *) 1); + lv_obj_set_flex_grow(btn_top, 1); + lv_obj_set_width(btn_top, lv_pct(100)); + lv_obj_center(lbl_top); + + /* -------- Center label -------- */ + lv_obj_t * label2_cont = lv_obj_create(container); + + lv_obj_set_flex_grow(label2_cont, 1); + lv_obj_set_width(label2_cont, lv_pct(100)); + + * label_value = lv_label_create(label2_cont); + lv_label_set_text(*label_value, ""); + + lv_obj_set_style_bg_opa(*label_value, LV_OPA_COVER, 0); + + lv_obj_set_style_text_font(*label_value, &lv_font_montserrat_14, 0); + lv_obj_center(*label_value); + + /* -------- Bottom button -------- */ + lv_obj_t * btn_bottom = lv_button_create(container); + lv_obj_t * lbl_bottom = lv_label_create(btn_bottom); + lv_label_set_text(lbl_bottom, "-100"); + lv_obj_add_event_cb(btn_bottom, bottom_cb, LV_EVENT_CLICKED, (void *) 2); + lv_obj_set_flex_grow(btn_bottom, 1); + lv_obj_set_width(btn_bottom, lv_pct(100)); + lv_obj_center(lbl_bottom); +} + +void build_ui(void) { + rpm_label = NULL; + build_numberstack(lv_screen_active(), "RPM", &rpm_label, top_cb, bottom_cb); + lv_label_set_text_fmt(rpm_label, "RPM: %d", get_throttle()); +} + diff --git a/main/ui.h b/main/ui.h new file mode 100644 index 0000000..2394d41 --- /dev/null +++ b/main/ui.h @@ -0,0 +1,21 @@ +#pragma once + +#include "lvgl.h" + +#include "motor.h" + +/** Numberstack button callback type */ +typedef void (*ns_btn_cb_t)(lv_event_t *); + +typedef struct { + ns_btn_cb_t top_cb; + ns_btn_cb_t bottom_cb; +} ns_widget_ctx_t; /** Numberstack widget context type */ + +void build_numberstack(lv_obj_t * parent, + const char * label_text, + lv_obj_t ** label_value, + ns_btn_cb_t top_btn_cb, + ns_btn_cb_t bottom_btn_cb); + +void build_ui(void);