Compare commits

...

4 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
10 changed files with 310 additions and 103 deletions
+10 -1
View File
@@ -66,6 +66,14 @@ dependencies:
registry_url: https://components.espressif.com/ registry_url: https://components.espressif.com/
type: service type: service
version: 2.7.0 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: idf:
source: source:
type: idf type: idf
@@ -82,8 +90,9 @@ direct_dependencies:
- atanisoft/esp_lcd_touch_xpt2046 - atanisoft/esp_lcd_touch_xpt2046
- espressif/esp_lcd_ili9341 - espressif/esp_lcd_ili9341
- espressif/esp_lvgl_port - espressif/esp_lvgl_port
- hayschan/autopid_for_esp_idf
- idf - idf
- lvgl/lvgl - lvgl/lvgl
manifest_hash: d2b549f17124d3d38e4add4614fb10956ada1a576e4c5f6a623ea3756bcd3ba5 manifest_hash: 5e3bb9d0c782ebaa9ecdc3b0310e8f13b698e0fd5071cab2a465725e2a33722b
target: esp32 target: esp32
version: 2.0.0 version: 2.0.0
+2 -2
View File
@@ -1,4 +1,4 @@
idf_component_register(SRCS "spincoat-plater-firmware.c" "dshot_esc_encoder.c" "display.c" "motor.c" "ui.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_gpio esp_driver_uart esp_driver_spi 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 esp_lcd unity lvgl esp_lvgl_port esp_lcd_touch_xpt2046 autopid_for_esp_idf
INCLUDE_DIRS ".") INCLUDE_DIRS ".")
+8 -2
View File
@@ -1,12 +1,12 @@
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 config TFT_MISO
@@ -40,6 +40,12 @@ menu "Pin Mapping Configuration"
help help
This is the pin for backlight control on the SPI LCD. 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 config TOUCH_IRQ
int "SPI touchscreen IRQ pin" int "SPI touchscreen IRQ pin"
+1
View File
@@ -18,3 +18,4 @@ dependencies:
lvgl/lvgl: ^9.4.0 lvgl/lvgl: ^9.4.0
espressif/esp_lvgl_port: ^2.3.0 espressif/esp_lvgl_port: ^2.3.0
atanisoft/esp_lcd_touch_xpt2046: ^1.0.2 atanisoft/esp_lcd_touch_xpt2046: ^1.0.2
hayschan/autopid_for_esp_idf: ^1.0.2
+12 -10
View File
@@ -145,7 +145,7 @@ 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
@@ -153,7 +153,7 @@ void parse_telemetry(void) {
uart_flush(ESC_UART_NUM); uart_flush(ESC_UART_NUM);
if(length < 10) return; if(length < 10) return false;
// chop out just the payload // chop out just the payload
uint8_t payload[128]; uint8_t payload[128];
@@ -166,15 +166,17 @@ void parse_telemetry(void) {
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;
} }
/** /**
+13 -1
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <inttypes.h> #include <inttypes.h>
#include <stdbool.h>
#if CONFIG_IDF_TARGET_ESP32H2 #if CONFIG_IDF_TARGET_ESP32H2
#define DSHOT_ESC_RESOLUTION_HZ 32000000 // 32MHz resolution, DSHot protocol needs a relative high resolution #define DSHOT_ESC_RESOLUTION_HZ 32000000 // 32MHz resolution, DSHot protocol needs a relative high resolution
@@ -10,11 +11,22 @@
#define ESC_UART_NUM UART_NUM_2 #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 init_rmt_esc_tx(void);
void send_dshot_packet(void); void send_dshot_packet(void);
void parse_telemetry(void); bool parse_telemetry(esc_telemetry_t * telemetry);
void init_motor(void); void init_motor(void);
-58
View File
@@ -1,58 +0,0 @@
/*
* 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 "dshot_esc_encoder.h"
#include "display.h"
#include "ui.h"
#include "motor.h"
static const char *TAG = "spincoat-plater-firmware";
static const uint16_t throttle = 200;
void app_main(void) {
init_display();
build_ui();
init_motor();
//init_rmt_esc_tx();
//throttle.throttle = 300;
update_throttle(throttle);
//xTaskCreate(&v_telemetry_packet_func, "v_telemetry_packet_func", 2048, NULL, 1, NULL);
//init_telemetry_uart_rx();
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) {
parse_telemetry();
}
}
}
+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;
}
}
}
}
}
+143 -24
View File
@@ -9,43 +9,112 @@
#define BTN_INCREMENT 100 #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 const char *TAG = "spincoat-plater-firmware/ui";
static lv_obj_t * rpm_label = NULL; static lv_obj_t * rpm_label = NULL;
static lv_obj_t * coat_time_label = NULL;
static lv_obj_t * run_stop_label = NULL;
void top_cb(lv_event_t * e) { static uint16_t rpm_setting = 100; // rpm
update_throttle(get_throttle() + BTN_INCREMENT); static uint16_t coat_time_setting = 5; // seconds
lv_label_set_text_fmt(rpm_label, "RPM: %d", get_throttle());
}
void bottom_cb(lv_event_t * e) { static coat_state motor_state = STOPPED;
uint16_t throttle = get_throttle();
if(throttle >= BTN_INCREMENT) { /**
update_throttle(get_throttle() - BTN_INCREMENT); * Callback for pressing the rpm increase button
lv_label_set_text_fmt(rpm_label, "RPM: %d", get_throttle()); */
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();
} }
} }
void build_numberstack(lv_obj_t * parent, /**
* 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, const char * label_text,
lv_obj_t ** label_value, lv_obj_t ** label_value,
ns_btn_cb_t top_btn_cb, ns_btn_cb_t top_btn_cb,
ns_btn_cb_t bottom_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_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_flow(container, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(container, lv_obj_set_flex_align(container,
LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER); LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_all(container, 10, 0); lv_obj_set_style_pad_all(container, 0, 0);
lv_obj_set_style_pad_gap(container, 8, 0);
/* -------- Top label -------- */ /* -------- Top label -------- */
lv_obj_t * label_cont = lv_obj_create(container); 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_flex_grow(label_cont, 1);
lv_obj_set_width(label_cont, lv_pct(100)); lv_obj_set_width(label_cont, lv_pct(100));
@@ -54,14 +123,14 @@ void build_numberstack(lv_obj_t * parent,
lv_obj_set_style_bg_opa(label, LV_OPA_COVER, 0); 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_set_style_text_font(label, &lv_font_montserrat_12, 0);
lv_obj_center(label); lv_obj_center(label);
/* -------- Top button -------- */ /* -------- Top button -------- */
lv_obj_t * btn_top = lv_button_create(container); lv_obj_t * btn_top = lv_button_create(container);
lv_obj_t * lbl_top = lv_label_create(btn_top); lv_obj_t * lbl_top = lv_label_create(btn_top);
lv_label_set_text(lbl_top, "+100"); lv_label_set_text_fmt(lbl_top, "+%d", stepsize);
lv_obj_add_event_cb(btn_top, top_cb, LV_EVENT_CLICKED, (void *) 1); 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_flex_grow(btn_top, 1);
lv_obj_set_width(btn_top, lv_pct(100)); lv_obj_set_width(btn_top, lv_pct(100));
lv_obj_center(lbl_top); lv_obj_center(lbl_top);
@@ -69,30 +138,80 @@ void build_numberstack(lv_obj_t * parent,
/* -------- Center label -------- */ /* -------- Center label -------- */
lv_obj_t * label2_cont = lv_obj_create(container); 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_flex_grow(label2_cont, 1);
lv_obj_set_width(label2_cont, lv_pct(100)); lv_obj_set_width(label2_cont, lv_pct(100));
* label_value = lv_label_create(label2_cont); * label_value = lv_label_create(label2_cont);
lv_label_set_text(*label_value, ""); 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_bg_opa(*label_value, LV_OPA_COVER, 0);
lv_obj_set_style_text_font(*label_value, &lv_font_montserrat_14, 0); lv_obj_set_style_text_font(*label_value, &lv_font_montserrat_12, 0);
lv_obj_center(*label_value); lv_obj_center(*label_value);
/* -------- Bottom button -------- */ /* -------- Bottom button -------- */
lv_obj_t * btn_bottom = lv_button_create(container); lv_obj_t * btn_bottom = lv_button_create(container);
lv_obj_t * lbl_bottom = lv_label_create(btn_bottom); lv_obj_t * lbl_bottom = lv_label_create(btn_bottom);
lv_label_set_text(lbl_bottom, "-100"); lv_label_set_text_fmt(lbl_bottom, "-%d", stepsize);
lv_obj_add_event_cb(btn_bottom, bottom_cb, LV_EVENT_CLICKED, (void *) 2); 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_flex_grow(btn_bottom, 1);
lv_obj_set_width(btn_bottom, lv_pct(100)); lv_obj_set_width(btn_bottom, lv_pct(100));
lv_obj_center(lbl_bottom); 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) { void build_ui(void) {
rpm_label = NULL; lv_obj_t * container = lv_obj_create(lv_screen_active());
build_numberstack(lv_screen_active(), "RPM", &rpm_label, top_cb, bottom_cb); lv_obj_set_size(container, lv_pct(100), lv_pct(100));
lv_label_set_text_fmt(rpm_label, "RPM: %d", get_throttle()); 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);
} }
+14 -2
View File
@@ -12,10 +12,22 @@ typedef struct {
ns_btn_cb_t bottom_cb; ns_btn_cb_t bottom_cb;
} ns_widget_ctx_t; /** Numberstack widget context type */ } ns_widget_ctx_t; /** Numberstack widget context type */
void build_numberstack(lv_obj_t * parent, typedef enum {
STOPPED,
SPINUP,
COATING,
SPINDOWN
} coat_state;
lv_obj_t * build_numberstack(lv_obj_t * parent,
const char * label_text, const char * label_text,
lv_obj_t ** label_value, lv_obj_t ** label_value,
ns_btn_cb_t top_btn_cb, ns_btn_cb_t top_btn_cb,
ns_btn_cb_t bottom_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); void build_ui(void);