Move render to separate core

This commit is contained in:
2026-05-03 22:48:52 +02:00
parent d3db5fcec1
commit 6e7451e07a
+62 -67
View File
@@ -25,12 +25,14 @@
#define R_NEEDLE_TIP 160
#define R_HUB 22
// -- Engine Load Position (Top Middle) --
// -- Engine Load Position --
#define LOAD_W 100
#define LOAD_X (CX - (LOAD_W / 2))
#define LOAD_Y (CY - 70)
// ── State & OBD Variables ─────────────────────────────────────────
// ── Shared State & Concurrency ────────────────────────────────────
SemaphoreHandle_t xDataMutex;
static float boost_target = 0.0f;
static float boost_current = -1.0f;
static float peak_boost = -1.0f;
@@ -40,22 +42,19 @@ static float clt_temp = 0.0f;
static float iat_temp = 0.0f;
static float engine_load = 0.0f;
// Buffers for strings (moved out of draw calls for speed)
static char pbuf[16] = "0.00 bar";
static char clt_buf[24] = "COOLANT: 0 °C";
static char iat_buf[24] = "INTAKE: 0 °C";
// Animation State
static bool startup_anim = true;
static int anim_stage = 0;
static float anim_speed = 0.35f;
// BLE Status
static int ble_status = 1;
static lv_obj_t *face_obj;
static lv_obj_t *needle_obj;
// OBD Hardware Objects
// OBD Hardware
NimBLEClient* pClient = nullptr;
ELM327 myELM327;
@@ -69,7 +68,7 @@ static float bar_to_rad(float bar) {
static lv_coord_t px(float r, float rad) { return (lv_coord_t)(CX + cosf(rad) * r); }
static lv_coord_t py(float r, float rad) { return (lv_coord_t)(CY + sinf(rad) * r); }
// ── Drawing Functions ─────────────────────────────────────────────
// ── Drawing Functions (Run on Core 1) ─────────────────────────────
static void draw_gauge_face(lv_event_t *e) {
lv_draw_ctx_t *draw_ctx = lv_event_get_draw_ctx(e);
lv_point_t center = {CX, CY};
@@ -134,7 +133,7 @@ static void draw_gauge_face(lv_event_t *e) {
lv_area_t load_txt_area = {LOAD_X, LOAD_Y + 8, LOAD_X + LOAD_W, LOAD_Y + 20};
lv_draw_label(draw_ctx, &load_lbl_dsc, &load_txt_area, "ENGINE LOAD", NULL);
// 5. FUTURISTIC DATA HUB
// 5. DATA HUB
lv_draw_rect_dsc_t box_dsc;
lv_draw_rect_dsc_init(&box_dsc);
box_dsc.bg_color = lv_color_make(0x1A, 0x1A, 0x1A);
@@ -197,6 +196,7 @@ static void draw_needle(lv_event_t *e) {
lv_draw_rect(draw_ctx, &hub_dsc, &hub_area);
}
// ── OBD Communication (Core 0 Task) ───────────────────────────────
void connect_to_vlink() {
NimBLEScan* pScan = NimBLEDevice::getScan();
pScan->setActiveScan(true);
@@ -216,19 +216,54 @@ void connect_to_vlink() {
}
}
void DriverTask(void *parameter) {
while (1) {
if (ble_status != 2) {
connect_to_vlink();
vTaskDelay(pdMS_TO_TICKS(1000));
} else {
// Poll hardware
float t_clt = myELM327.engineCoolantTemp();
float t_iat = myELM327.intakeAirTemp();
float t_load = myELM327.engineLoad();
float map_kpa = myELM327.manifoldPressure();
// Safely write to shared variables
if (xSemaphoreTake(xDataMutex, pdMS_TO_TICKS(20))) {
if (myELM327.nb_rx_state == ELM_SUCCESS) {
clt_temp = t_clt;
iat_temp = t_iat;
engine_load = t_load;
boost_target = (map_kpa / 100.0f) - 1.01f;
if (boost_target > peak_boost) {
peak_boost = boost_target;
peak_time = millis();
}
sprintf(pbuf, "%.2f bar", peak_boost < 0 ? 0.00 : peak_boost);
sprintf(clt_buf, "COOLANT: %.0f °C", clt_temp);
sprintf(iat_buf, "INTAKE: %.0f °C", iat_temp);
}
xSemaphoreGive(xDataMutex);
}
vTaskDelay(pdMS_TO_TICKS(20)); // High polling frequency
}
}
}
// ── Initialization ────────────────────────────────────────────────
void gauge_create(void) {
lv_obj_t *scr = lv_scr_act();
lv_obj_clean(scr);
lv_obj_set_style_bg_color(scr, lv_color_black(), 0);
// Background Layer (Labels, Ticks)
face_obj = lv_obj_create(scr);
lv_obj_set_size(face_obj, SCREEN_W, SCREEN_H);
lv_obj_set_style_bg_opa(face_obj, 0, 0);
lv_obj_set_style_border_width(face_obj, 0, 0);
lv_obj_add_event_cb(face_obj, draw_gauge_face, LV_EVENT_DRAW_MAIN, NULL);
// Foreground Layer (Needle)
needle_obj = lv_obj_create(scr);
lv_obj_set_size(needle_obj, SCREEN_W, SCREEN_H);
lv_obj_set_style_bg_opa(needle_obj, 0, 0);
@@ -236,93 +271,53 @@ void gauge_create(void) {
lv_obj_add_event_cb(needle_obj, draw_needle, LV_EVENT_DRAW_MAIN, NULL);
}
void DriverTask(void *parameter) {
while (1) {
if (!startup_anim) {
if (ble_status != 2) {
connect_to_vlink();
vTaskDelay(pdMS_TO_TICKS(1000));
} else {
float t_clt = myELM327.engineCoolantTemp();
if (myELM327.nb_rx_state == ELM_SUCCESS) clt_temp = t_clt;
float t_iat = myELM327.intakeAirTemp();
if (myELM327.nb_rx_state == ELM_SUCCESS) iat_temp = t_iat;
float t_load = myELM327.engineLoad();
if (myELM327.nb_rx_state == ELM_SUCCESS) engine_load = t_load;
float map_kpa = myELM327.manifoldPressure();
if (myELM327.nb_rx_state == ELM_SUCCESS) {
boost_target = (map_kpa / 100.0f) - 1.01f;
}
if (boost_target > peak_boost) {
peak_boost = boost_target;
peak_time = millis();
}
// Update text buffers outside of drawing thread
sprintf(pbuf, "%.2f bar", peak_boost < 0 ? 0.00 : peak_boost);
sprintf(clt_buf, "COOLANT: %.0f °C", clt_temp);
sprintf(iat_buf, "INTAKE: %.0f °C", iat_temp);
vTaskDelay(pdMS_TO_TICKS(50));
}
} else {
vTaskDelay(pdMS_TO_TICKS(100));
}
}
}
void setup() {
Serial.begin(115200);
Serial1.begin(38400);
xDataMutex = xSemaphoreCreateMutex();
TCA9554PWR_Init(0x00);
Backlight_Init(); LCD_Init(); Lvgl_Init();
NimBLEDevice::init("ESP32_Gauge");
gauge_create();
xTaskCreatePinnedToCore(DriverTask, "DriverTask", 8192, NULL, 2, NULL, 1);
// Start Worker on Core 0
xTaskCreatePinnedToCore(DriverTask, "DriverTask", 8192, NULL, 1, NULL, 0);
}
void loop() {
static uint32_t last_face_update = 0;
// 1. Logic & Smoothing (Core 1)
if (startup_anim) {
if (anim_stage == 0) {
boost_current += anim_speed;
if (boost_current >= GAUGE_MAX) {
boost_current = GAUGE_MAX;
anim_stage = 1;
}
if (boost_current >= GAUGE_MAX) anim_stage = 1;
} else if (anim_stage == 1) {
float diff = boost_current - 0.0f;
float step = diff * 0.07f;
if (step < 0.01f) step = 0.01f;
boost_current -= step;
boost_current -= (boost_current * 0.07f);
if (boost_current <= 0.01f) {
boost_current = 0.0f;
boost_target = 0.0f;
startup_anim = false;
anim_stage = 2;
}
}
vTaskDelay(pdMS_TO_TICKS(10));
} else {
boost_current += (boost_target - boost_current) * 0.15f;
if (millis() - peak_time > 10000) {
peak_boost = boost_current;
peak_time = millis();
// Apply smoothing to target provided by Core 0
if (xSemaphoreTake(xDataMutex, 0)) {
boost_current += (boost_target - boost_current) * 0.15f;
if (millis() - peak_time > 10000) peak_boost = boost_current;
xSemaphoreGive(xDataMutex);
}
}
// CRITICAL OPTIMIZATION:
// Always update the needle (fast)
// 2. UI Refresh
lv_obj_invalidate(needle_obj);
// Update the face (labels/load) only every 100ms (10Hz) to save FPS
if (millis() - last_face_update > 100) {
lv_obj_invalidate(face_obj);
last_face_update = millis();
}
Lvgl_Loop();
vTaskDelay(pdMS_TO_TICKS(16)); // Target ~60 FPS
delay(16); // Target 60fps
}