Compare commits

...

13 Commits

Author SHA1 Message Date
maddiebaka 160ee1ce2f Finish numberstack code UI 2026-04-25 15:41:45 -04:00
maddiebaka 0a56a1ec1c Layout main UI screen 2026-03-12 12:28:06 -04:00
maddiebaka 4876d31648 Implement PID and telemetry 2026-03-12 11:12:38 -04:00
maddiebaka 9fe77e4e41 Adjust Kconfig values for new CYD variant 2026-03-12 11:10:57 -04:00
maddiebaka f02d138ffa Finish display impl, write UI widget, refactor 2025-12-31 11:56:54 -05:00
maddiebaka 7556496158 Remove managed_components, update .gitignore 2025-12-26 11:08:08 -05:00
maddiebaka afaaf0bb96 Adjust doxyfile syntax 2025-12-26 11:05:26 -05:00
maddiebaka 3256ab2e65 Add LVGL and set up with display/touch drivers 2025-12-26 11:04:16 -05:00
maddiebaka cb92f69ee3 Add doxyfile for doxygen 2025-12-20 16:22:28 -05:00
maddiebaka e1f4f46f23 Refactor out LCD init to display.c 2025-12-20 16:21:52 -05:00
maddiebaka 001d1dfeca Finish LCD init implementation 2025-12-20 14:53:22 -05:00
maddiebaka 1763303f3c Add first pass of SPI LCD logic 2025-12-05 23:35:15 -05:00
maddiebaka 33314d2a4a Remove gpio event queue line 2025-11-30 22:47:40 -05:00
13 changed files with 3895 additions and 55 deletions
+2
View File
@@ -1,3 +1,5 @@
build build
sdkconfig sdkconfig
sdkconfig.old sdkconfig.old
docs
managed_components
+2976
View File
File diff suppressed because it is too large Load Diff
+98
View File
@@ -0,0 +1,98 @@
dependencies:
atanisoft/esp_lcd_touch_xpt2046:
component_hash:
7e6381b67b6e487379118368b8e91624dc87036ef5734818de1db9eb35697998
dependencies:
- name: espressif/esp_lcd_touch
registry_url: https://components.espressif.com
require: private
version: '>=1.0.4'
- name: idf
require: private
version: '>=4.4'
source:
registry_url: https://components.espressif.com/
type: service
version: 1.0.6
espressif/cmake_utilities:
component_hash:
351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f
dependencies:
- name: idf
require: private
version: '>=4.1'
source:
registry_url: https://components.espressif.com
type: service
version: 0.5.3
espressif/esp_lcd_ili9341:
component_hash:
476fe49688f95201cba03a9391142d48c20b26f8e937de9ece44770a24943829
dependencies:
- name: espressif/cmake_utilities
registry_url: https://components.espressif.com
require: private
version: 0.*
- name: idf
require: private
version: '>=4.4'
source:
registry_url: https://components.espressif.com/
type: service
version: 2.0.1
espressif/esp_lcd_touch:
component_hash:
3f85a7d95af876f1a6ecca8eb90a81614890d0f03a038390804e5a77e2caf862
dependencies:
- name: idf
require: private
version: '>=4.4.2'
source:
registry_url: https://components.espressif.com
type: service
version: 1.2.1
espressif/esp_lvgl_port:
component_hash:
f872401524cb645ee6ff1c9242d44fb4ddcfd4d37d7be8b9ed3f4e85a404efcd
dependencies:
- name: idf
require: private
version: '>=5.1'
- name: lvgl/lvgl
registry_url: https://components.espressif.com
require: public
version: '>=8,<10'
source:
registry_url: https://components.espressif.com/
type: service
version: 2.7.0
hayschan/autopid_for_esp_idf:
component_hash:
abb01ee57353d22df70c27840ddec00b90544540c9a78ec18932737d7fbb7878
dependencies: []
source:
registry_url: https://components.espressif.com/
type: service
version: 1.0.2
idf:
source:
type: idf
version: 5.5.1
lvgl/lvgl:
component_hash:
17e68bfd21f0edf4c3ee838e2273da840bf3930e5dbc3bfa6c1190c3aed41f9f
dependencies: []
source:
registry_url: https://components.espressif.com/
type: service
version: 9.4.0
direct_dependencies:
- atanisoft/esp_lcd_touch_xpt2046
- espressif/esp_lcd_ili9341
- espressif/esp_lvgl_port
- hayschan/autopid_for_esp_idf
- idf
- lvgl/lvgl
manifest_hash: 5e3bb9d0c782ebaa9ecdc3b0310e8f13b698e0fd5071cab2a465725e2a33722b
target: esp32
version: 2.0.0
+3 -2
View File
@@ -1,3 +1,4 @@
idf_component_register(SRCS "spincoat-plater-firmware.c" "dshot_esc_encoder.c" idf_component_register(SRCS "spincoat-plater-firmware.cpp" "dshot_esc_encoder.c" "display.c" "motor.c" "ui.c"
PRIV_REQUIRES esp_driver_rmt esp_driver_uart 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 autopid_for_esp_idf
INCLUDE_DIRS ".") INCLUDE_DIRS ".")
+83 -2
View File
@@ -1,12 +1,93 @@
menu "Pin Mapping Configuration" menu "Pin Mapping Configuration"
config ESC_CTRL_PIN config ESC_CTRL_PIN
int "ESC control GPIO pin" int "ESC control GPIO pin"
default 22 default 18
help help
This is the pin used for sending DSHOT packets to the ESC. This is the pin used for sending DSHOT packets to the ESC.
config TELEMETRY_RX_PIN config TELEMETRY_RX_PIN
int "ESC telemetry uart rx pin" int "ESC telemetry uart rx pin"
default 27 default 19
help help
This is the pin used for receiving UART telemetry from the ESC. This is the pin used for receiving UART telemetry from the ESC.
config TFT_MISO
int "SPI LCD MISO pin"
default 12
help
This is the pin for MISO on the SPI LCD.
config TFT_MOSI
int "SPI LCD MOSI pin"
default 13
help
This is the pin for MOSI on the SPI LCD.
config TFT_SCKL
int "SPI LCD SCKL pin"
default 14
help
This is the pin for SCKL on the SPI LCD.
config TFT_CS
int "SPI LCD CS pin"
default 15
help
This is the pin for CS on the SPI LCD.
config TFT_DC
int "SPI LCD DC pin"
default 2
help
This is the pin for DC on the SPI LCD.
config TFT_BL
int "SPI LCD BL (backlight) pin"
default 21
help
This is the pin for backlight control on the SPI LCD.
config MOTOR_POLECOUNT
int "Pole count of motor driven by ESC"
default 6
help
This is the number of magnetic poles on your motor, which is used to calculate actual RPM
from electrical RPM.
config TOUCH_IRQ
int "SPI touchscreen IRQ pin"
default 36
help
This is the pin for IRQ on the SPI touchscreen.
config TOUCH_MOSI
int "SPI touchscreen MOSI pin"
default 32
help
This is the pin for MOSI on the SPI touchscreen.
config TOUCH_MISO
int "SPI touchscreen MISO pin"
default 39
help
This is the pin for MISO on the SPI touchscreen.
config TOUCH_CLK
int "SPI touchscreen CLK pin"
default 25
help
This is the pin for CLK on the SPI touchscreen.
config TOUCH_CS
int "SPI touchscreen CS pin"
default 33
help
This is the pin for CS on the SPI touchscreen.
config TFT_HRES
int "The horizontal resolution of the TFT display"
default 320
help
This is the horizontal resolution for the front-panel SPI LCD
config TFT_VRES
int "The vertical resolution of the TFT display"
default 240
help
This is the vertical resolution for the front-panel SPI LCD
config TFT_BPP
int "The bits per pixel of the TFT display"
default 16
help
This is the bits per pixel for the TFT display and influences
how much memory the display buffer takes up
endmenu endmenu
+253
View File
@@ -0,0 +1,253 @@
#include "display.h"
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "unity.h"
#include "unity_test_runner.h"
#include "esp_log.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 "esp_lcd_touch_xpt2046.h"
#include "lvgl.h"
#include "esp_lvgl_port.h"
SemaphoreHandle_t refresh_finish = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_touch_handle_t tp = NULL;
static const char * TAG = "spincoat-plater-firmware/display";
/**
* Callback for the TFT LCD, notifying when the screen is ready for another chunk of data and
* releasing the drawing semaphore.
*/
IRAM_ATTR static bool notify_refresh_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
BaseType_t need_yield = pdFALSE;
xSemaphoreGiveFromISR(refresh_finish, &need_yield);
return (need_yield == pdTRUE);
}
/**
* Callback for LVGL pointer input device, it reads from the esp_lcd_touch driver and updates
* the LVGL internal state for event handling.
*/
void touch_driver_read(lv_indev_t * indev, lv_indev_data_t * data) {
ESP_ERROR_CHECK(esp_lcd_touch_read_data(tp));
uint16_t x[1];
uint16_t y[1];
uint16_t strength[1];
uint8_t count = 0;
bool touchpad_pressed = esp_lcd_touch_get_coordinates(tp, x, y, strength, &count, 1);
if(touchpad_pressed == true) {
data->point.x = x[0];
data->point.y = y[0];
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
/**
* Callback for LVGL button events.
*/
static void btn_event_cb(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
ESP_LOGI(TAG, "LVGL button pressed.");
}
}
/**
* Draws a test UI
*/
void lv_example_btn_1(void)
{
lv_obj_t * label;
lv_obj_t * btn1 = lv_btn_create(lv_scr_act());
lv_obj_add_event_cb(btn1, btn_event_cb, LV_EVENT_ALL, NULL);
lv_obj_set_width(btn1, 120);
lv_obj_set_height(btn1, 100);
lv_obj_align(btn1, LV_ALIGN_CENTER, 0, -40);
label = lv_label_create(btn1);
lv_label_set_text(label, "Button");
lv_obj_center(label);
lv_obj_t * btn2 = lv_btn_create(lv_scr_act());
lv_obj_add_event_cb(btn2, btn_event_cb, LV_EVENT_ALL, NULL);
lv_obj_align(btn2, LV_ALIGN_CENTER, 0, 40);
lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE);
lv_obj_set_height(btn2, LV_SIZE_CONTENT);
label = lv_label_create(btn2);
lv_label_set_text(label, "Toggle");
lv_obj_center(label);
}
/**
* Initializes the SPI bus and starts communication with xpt2046 touch controller.
*/
void init_touchscreen_xpt2046(void) {
ESP_LOGI(TAG, "Initialize touchscreen SPI bus");
const spi_bus_config_t ts_bus_config = {
.mosi_io_num = CONFIG_TOUCH_MOSI,
.miso_io_num = CONFIG_TOUCH_MISO,
.sclk_io_num = CONFIG_TOUCH_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4096,
};
ESP_ERROR_CHECK(spi_bus_initialize(TOUCH_SPI_HOST, &ts_bus_config, SPI_DMA_CH_AUTO));
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
esp_lcd_panel_io_spi_config_t tp_io_config = ESP_LCD_TOUCH_IO_SPI_XPT2046_CONFIG(CONFIG_TOUCH_CS);
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_VRES,
.y_max = TFT_HRES,
.rst_gpio_num = -1,
.int_gpio_num = -1,
.flags = {
.swap_xy = 1,
.mirror_x = 0,
.mirror_y = 0,
},
};
ESP_LOGI(TAG, "Initialize touch controller XPT2046");
ESP_ERROR_CHECK(esp_lcd_touch_new_spi_xpt2046(tp_io_handle, &tp_cfg, &tp));
ESP_LOGI(TAG, "Finished initialization of XPT2046 touch driver");
}
/**
* Initializes the SPI LCD in preparation for writing graphics to it.
*/
void init_spi_lcd(void) {
ESP_LOGI(TAG, "Turn on backlight");
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << CONFIG_TFT_BL),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io_conf);
gpio_set_level(CONFIG_TFT_BL, 1);
ESP_LOGI(TAG, "Initialize SPI bus");
const spi_bus_config_t bus_config = ILI9341_PANEL_BUS_SPI_CONFIG(CONFIG_TFT_SCKL,
CONFIG_TFT_MOSI, TFT_HRES * 80 * TFT_BPP / 8);
TEST_ESP_OK(spi_bus_initialize(LCD_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO));
ESP_LOGI(TAG, "Install panel IO");
const esp_lcd_panel_io_spi_config_t io_config = ILI9341_PANEL_IO_SPI_CONFIG(CONFIG_TFT_CS, CONFIG_TFT_DC,
notify_refresh_ready, NULL);
TEST_ESP_OK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_config, &io_handle));
ESP_LOGI(TAG, "Install ili9341 panel driver");
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = -1, // Shared with Touch reset
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
.color_space = ESP_LCD_COLOR_SPACE_BGR,
#elif ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0)
.rgb_endian = LCD_RGB_ENDIAN_BGR,
#else
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
#endif
.bits_per_pixel = TFT_BPP,
};
TEST_ESP_OK(esp_lcd_new_panel_ili9341(io_handle, &panel_config, &panel_handle));
TEST_ESP_OK(esp_lcd_panel_reset(panel_handle));
TEST_ESP_OK(esp_lcd_panel_init(panel_handle));
TEST_ESP_OK(esp_lcd_panel_mirror(panel_handle, true, true));
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
TEST_ESP_OK(esp_lcd_panel_disp_off(panel_handle, false));
#else
TEST_ESP_OK(esp_lcd_panel_disp_on_off(panel_handle, true));
#endif
ESP_LOGI(TAG, "Finished init of spi LCD");
}
/**
* Initializes the LVGL display and sets everything up so that LVGL can draw to the TFT LCD.
*/
void init_lvgl_display(void) {
static lv_disp_t * disp_handle = NULL;
const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG();
TEST_ESP_OK(lvgl_port_init(&lvgl_cfg));
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = io_handle,
.panel_handle = panel_handle,
.buffer_size = LVGL_BUF_SIZE,
.double_buffer = true,
.hres = TFT_HRES,
.vres = TFT_VRES,
.monochrome = false,
.color_format = LV_COLOR_FORMAT_RGB565,
.rotation = {
.swap_xy = true,
.mirror_x = false,
.mirror_y = false,
},
.flags = {
.buff_dma = false,
.swap_bytes = true,
}
};
disp_handle = lvgl_port_add_disp(&disp_cfg);
}
/**
* Initializes the LVGL input driver as a pointer type so that LVGL can read input and dispatch
* UI events.
*/
void init_input(void) {
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, touch_driver_read);
}
/**
* Main function that completely initializes the TFT LCD, the touchscreen, the LVGL display,
* input device and draws the UI.
*/
void init_display(void) {
init_spi_lcd();
init_touchscreen_xpt2046();
init_lvgl_display();
init_input();
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "stdbool.h"
#include "esp_lcd_panel_io.h"
#define LCD_SPI_HOST SPI2_HOST
#define TOUCH_SPI_HOST SPI3_HOST
#define TFT_HRES CONFIG_TFT_HRES
#define TFT_VRES CONFIG_TFT_VRES
#define TFT_BPP CONFIG_TFT_BPP
#define LVGL_BUF_SIZE ((TFT_VRES * TFT_HRES) / 10) * (TFT_BPP / 8)
/**
* Initializes the display TFT and touchscreen.
*/
void init_display(void);
+21
View File
@@ -0,0 +1,21 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: '>=4.1.0'
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true
espressif/esp_lcd_ili9341: ^2.0.0
lvgl/lvgl: ^9.4.0
espressif/esp_lvgl_port: ^2.3.0
atanisoft/esp_lcd_touch_xpt2046: ^1.0.2
hayschan/autopid_for_esp_idf: ^1.0.2
@@ -1,32 +1,18 @@
/* #include "motor.h"
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <inttypes.h> #include "driver/spi_common.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/rmt_tx.h" #include "driver/rmt_tx.h"
#include "driver/uart.h" #include "driver/uart.h"
#include "esp_log.h"
#include "dshot_esc_encoder.h" #include "dshot_esc_encoder.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_CTRL CONFIG_ESC_CTRL_PIN
#define GPIO_ESC_RX CONFIG_TELEMETRY_RX_PIN #define GPIO_ESC_RX CONFIG_TELEMETRY_RX_PIN
#define UART_NUM UART_NUM_2
#define ESP_INTR_FLAG_DEFAULT 0 #define ESP_INTR_FLAG_DEFAULT 0
static const char *TAG = "spincoat-plater-firmware"; static const char *TAG = "spincoat-plater-firmware/motor";
//static QueueHandle_t gpio_evt_queue = NULL;
static QueueHandle_t uart_queue = NULL; static QueueHandle_t uart_queue = NULL;
const int uart_buffer_size = (1024 * 2); const int uart_buffer_size = (1024 * 2);
@@ -39,7 +25,7 @@ rmt_transmit_config_t tx_config = {
dshot_esc_throttle_t throttle = { dshot_esc_throttle_t throttle = {
.throttle = 0, .throttle = 0,
.telemetry_req = false, // telemetry is not supported in this example .telemetry_req = false,
}; };
/** /**
@@ -103,6 +89,9 @@ void init_rmt_esc_tx(void) {
initialize_esc_throttle(); 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) { void init_telemetry_uart_rx(void) {
uart_config_t uart_config = { uart_config_t uart_config = {
.baud_rate = 115200, .baud_rate = 115200,
@@ -113,11 +102,12 @@ void init_telemetry_uart_rx(void) {
.rx_flow_ctrl_thresh = 122, .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_driver_install(ESC_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_param_config(ESC_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)); 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 * Sends a DSHOT packet via the RMT. Make sure the RMT channel has been initialized
* by calling *init_rmt_esc_tx()* * by calling *init_rmt_esc_tx()*
@@ -155,54 +145,64 @@ uint8_t get_crc8(uint8_t *Buf, uint8_t BufLen){
* Parse the KISS telemetry frame and check the crc8 * Parse the KISS telemetry frame and check the crc8
* TODO: Do more with the data than print it * TODO: Do more with the data than print it
*/ */
void parse_telemetry(void) { bool parse_telemetry(esc_telemetry_t * telemetry) {
uint8_t frame_size = 10; uint8_t frame_size = 10;
uint8_t data[128]; uint8_t data[128];
// get data // get data
uint8_t length = uart_read_bytes(UART_NUM, data, frame_size, 100); uint8_t length = uart_read_bytes(ESC_UART_NUM, data, frame_size, 100);
uart_flush(UART_NUM); uart_flush(ESC_UART_NUM);
if(length < 10) return false;
if(length < 10) return;
// chop out just the payload // chop out just the payload
uint8_t payload[128]; uint8_t payload[128];
uint8_t payload_length = (frame_size - 1); uint8_t payload_length = (frame_size - 1);
for(uint8_t i = 0; i < payload_length; i++) { for(uint8_t i = 0; i < payload_length; i++) {
payload[i] = data[i]; payload[i] = data[i];
} }
// calculate the crc8 // calculate the crc8
uint8_t expected_crc8 = get_crc8(payload, payload_length); uint8_t expected_crc8 = get_crc8(payload, payload_length);
uint8_t received_crc8 = (uint8_t) data[frame_size - 1]; uint8_t received_crc8 = (uint8_t) data[frame_size - 1];
if(expected_crc8 != received_crc8) return; if(expected_crc8 != received_crc8) return false;
for(uint8_t i = 0; i < length; i++) {
printf("%d - %d\n", i, data[i]); telemetry->temperature = payload[0];
} telemetry->voltage = (payload[1] << 8) + payload[2];
printf("--------------------\n"); telemetry->current = (payload[3] << 8) + payload[4];
printf("expected: %d\n", expected_crc8); telemetry->consumption = (payload[5] << 8) + payload[6];
printf("received: %d\n", received_crc8); telemetry->rpm = (payload[7] << 8) + payload[8];
printf("======================\n");
return true;
} }
void app_main(void) { /**
* Helper function that sets up the RMT, creates the telemetry task, and
* initializes the telemetry UART.
*/
void init_motor(void) {
init_rmt_esc_tx(); init_rmt_esc_tx();
throttle.throttle = 300; throttle.throttle = 300;
xTaskCreate(&v_telemetry_packet_func, "v_telemetry_packet_func", 2048, NULL, 1, NULL); xTaskCreate(&v_telemetry_packet_func, "v_telemetry_packet_func", 2048, NULL, 1, NULL);
init_telemetry_uart_rx(); init_telemetry_uart_rx();
}
while(1) {
send_dshot_packet(); /**
* Returns the current throttle value
uint8_t length = 0; */
ESP_ERROR_CHECK(uart_get_buffered_data_len(UART_NUM, (size_t*)&length)); uint16_t get_throttle() {
return throttle.throttle;
if(length >= 10) { }
parse_telemetry();
} /**
} * Updates the throttle value.
*
* \arg \c throttle The new throttle value between 0-2048
*/
void update_throttle(uint16_t thr) {
throttle.throttle = thr;
} }
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include <inttypes.h>
#include <stdbool.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 ESC_UART_NUM UART_NUM_2
/**
* Struct for returning telemetry data
*/
typedef struct {
uint16_t temperature;
uint16_t voltage;
uint16_t current;
uint16_t consumption;
uint16_t rpm;
} esc_telemetry_t;
void init_rmt_esc_tx(void);
void send_dshot_packet(void);
bool parse_telemetry(esc_telemetry_t * telemetry);
void init_motor(void);
uint16_t get_throttle(void);
void update_throttle(uint16_t throttle);
+104
View File
@@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "driver/uart.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 "AutoPID-for-ESP-IDF.h"
extern "C" {
#include "dshot_esc_encoder.h"
#include "display.h"
#include "ui.h"
#include "motor.h"
}
static const char *TAG = "spincoat-plater-firmware";
static double throttle = 0;
static double zero_offset = 100; // Value to offset the throttle by, to skip the
// command values
static double OUTPUT_MIN = 0;
static double OUTPUT_MAX = 1948;
static double RPM_MAX = 250;
static double BANG_BANG_THRESHOLD = RPM_MAX + 200;
static double KP = 0.015;
static double KI = 0.8;
static double KD = 0.0;
static bool motor_running = false;
extern "C" void app_main(void) {
srand((unsigned int)esp_timer_get_time());
esc_telemetry_t telemetry;
double target = 100;
double real_rpm = 0;
AutoPID myPID(&real_rpm, &target, &throttle, OUTPUT_MIN, OUTPUT_MAX, KP, KI, KD);
myPID.setTimeStep(100);
myPID.setBangBang(400); // if you mess with this, you may melt your circuit
init_display();
vTaskDelay(pdMS_TO_TICKS(100));
build_ui();
init_motor();
update_throttle(throttle);
while(1) {
send_dshot_packet();
uint8_t length = 0;
ESP_ERROR_CHECK(uart_get_buffered_data_len(ESC_UART_NUM, (size_t*)&length));
if(length >= 10) {
if(parse_telemetry(&telemetry)) {
real_rpm = telemetry.rpm / (uint16_t) CONFIG_MOTOR_POLECOUNT;
myPID.run();
update_throttle(throttle + zero_offset);
update_rpm_readout(real_rpm);
ESP_LOGI(TAG, "eRPM: %d, RPM: %.2f, SetPoint: %.2f, Output: %.2f",
telemetry.rpm,
real_rpm,
target,
throttle + zero_offset); // Log the values
if(myPID.atSetPoint(10)) {
ESP_LOGI(TAG, "At setpoint.");
}
vTaskDelay(pdMS_TO_TICKS(100));
if(real_rpm >= 30) {
motor_running = true;
} else {
motor_running = false;
}
}
}
}
}
+217
View File
@@ -0,0 +1,217 @@
#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
#define MAX_RPM_SETTING 200
#define MAX_COAT_TIME_SETTING 30
#define MIN_RPM_SETTING 50
#define MIN_COAT_TIME_SETTING 5
#define RPM_STEPSIZE 25
#define COAT_TIME_STEPSIZE 5
static const char *TAG = "spincoat-plater-firmware/ui";
static lv_obj_t * rpm_label = NULL;
static lv_obj_t * coat_time_label = NULL;
static lv_obj_t * run_stop_label = NULL;
static uint16_t rpm_setting = 100; // rpm
static uint16_t coat_time_setting = 5; // seconds
static coat_state motor_state = STOPPED;
/**
* Callback for pressing the rpm increase button
*/
void rpm_increase_cb(lv_event_t * e) {
ESP_LOGI(TAG, "RPM increase callback called.");
if(rpm_setting < MAX_RPM_SETTING) {
rpm_setting += RPM_STEPSIZE;
lv_lock();
lv_label_set_text_fmt(rpm_label, "%d", rpm_setting);
lv_unlock();
}
}
/**
* Callback for pressing the rpm decrease button
*/
void rpm_decrease_cb(lv_event_t * e) {
ESP_LOGI(TAG, "RPM decrease callback called.");
if(rpm_setting > MIN_RPM_SETTING) {
rpm_setting -= RPM_STEPSIZE;
lv_lock();
lv_label_set_text_fmt(rpm_label, "%d", rpm_setting);
lv_unlock();
}
}
/**
* Callback for pressing the coat time increase button
*/
void coat_increase_cb(lv_event_t * e) {
ESP_LOGI(TAG, "Coat time increase callback called.");
if(coat_time_setting < MAX_COAT_TIME_SETTING) {
coat_time_setting += COAT_TIME_STEPSIZE;
lv_lock();
lv_label_set_text_fmt(coat_time_label, "%d", coat_time_setting);
lv_unlock();
}
}
/**
* Callback for pressing the coat time decrease button
*/
void coat_decrease_cb(lv_event_t * e) {
ESP_LOGI(TAG, "Coat time decrease callback called.");
if(coat_time_setting > MIN_COAT_TIME_SETTING) {
coat_time_setting -= COAT_TIME_STEPSIZE;
lv_lock();
lv_label_set_text_fmt(coat_time_label, "%d", coat_time_setting);
lv_unlock();
}
}
void run_stop_cb(lv_event_t * e) {
ESP_LOGI(TAG, "Run/stop callback called.");
if(run_stop_label == NULL) return;
lv_label_set_text(run_stop_label, "Stop");
}
/**
* Factory function that creates a "numberstack" widget and returns the label for it
*/
lv_obj_t * 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,
uint16_t start_value,
uint16_t stepsize) {
lv_obj_t * container = lv_obj_create(parent);
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, 0, 0);
/* -------- Top label -------- */
lv_obj_t * label_cont = lv_obj_create(container);
lv_obj_remove_flag(label_cont, LV_OBJ_FLAG_SCROLLABLE);
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_12, 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_fmt(lbl_top, "+%d", stepsize);
lv_obj_add_event_cb(btn_top, top_btn_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_remove_flag(label2_cont, LV_OBJ_FLAG_SCROLLABLE);
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_fmt(*label_value, "%d", start_value);
lv_obj_set_style_bg_opa(*label_value, LV_OPA_COVER, 0);
lv_obj_set_style_text_font(*label_value, &lv_font_montserrat_12, 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_fmt(lbl_bottom, "-%d", stepsize);
lv_obj_add_event_cb(btn_bottom, bottom_btn_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);
return container;
}
void update_rpm_readout(uint16_t rpm) {
if(rpm_label != NULL) {
lv_label_set_text_fmt(rpm_label, "RPM: %d", rpm);
}
};
void build_ui(void) {
lv_obj_t * container = lv_obj_create(lv_screen_active());
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_t * numberstacks_container = lv_obj_create(container);
lv_obj_set_size(numberstacks_container , lv_pct(100), lv_pct(70));
lv_obj_set_flex_flow(numberstacks_container, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(numberstacks_container,
LV_FLEX_ALIGN_SPACE_EVENLY,
LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER);
lv_obj_t * rpm_container = build_numberstack(numberstacks_container, "RPM", &rpm_label,
rpm_increase_cb, rpm_decrease_cb, rpm_setting, RPM_STEPSIZE);
lv_obj_t * coat_time_container = build_numberstack(numberstacks_container, "Coat Time",
&coat_time_label, coat_increase_cb, coat_decrease_cb, coat_time_setting, COAT_TIME_STEPSIZE);
lv_obj_set_size(rpm_container, lv_pct(48), lv_pct(96));
lv_obj_set_size(coat_time_container, lv_pct(48), lv_pct(96));
lv_obj_set_style_pad_all(container, 0, 0);
lv_obj_set_style_pad_all(numberstacks_container, 0, 0);
lv_obj_set_style_pad_all(rpm_container, 0, 0);
lv_obj_set_style_pad_all(coat_time_container, 0, 0);
/* -------- Run/Stop button -------- */
lv_obj_t * btn_run_stop = lv_button_create(container);
lv_obj_t * lbl_run_stop = lv_label_create(btn_run_stop);
run_stop_label = lbl_run_stop;
lv_label_set_text(lbl_run_stop, "Run");
lv_obj_center(lbl_run_stop);
lv_obj_add_event_cb(btn_run_stop, run_stop_cb, LV_EVENT_CLICKED, NULL);
lv_obj_set_size(btn_run_stop, lv_pct(96), lv_pct(18));
lv_obj_set_style_pad_all(btn_run_stop, 0, 0);
}
+33
View File
@@ -0,0 +1,33 @@
#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 */
typedef enum {
STOPPED,
SPINUP,
COATING,
SPINDOWN
} coat_state;
lv_obj_t * 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,
uint16_t start_value,
uint16_t stepsize);
void update_rpm_readout(uint16_t rpm);
void build_ui(void);