Skip to content

Watchdog Triggered Cheap Yellow Display #283

Open
@brickhockey3

Description

@brickhockey3

I am currently having an issue when trying to connect to SAE1850 devices but Ido not encounter the error with OBDII devices. I am trying to use LVGL on a cheap yellow display to make a simple multigauge.

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>
#include <BluetoothSerial.h>
#include <ELMduino.h>
#include <atomic>

#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
#define XPT2046_IRQ 36
#define XPT2046_MOSI 32
#define XPT2046_MISO 39
#define XPT2046_CLK 25
#define XPT2046_CS 33

#define LCD_BACK_LIGHT_PIN 21
#define LEDC_CHANNEL_0     0
#define LEDC_TIMER_12_BIT  12
#define LEDC_BASE_FREQ     5000

SPIClass touchscreenSPI(VSPI);
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);
TFT_eSPI tft = TFT_eSPI();
uint32_t draw_buf[SCREEN_WIDTH * SCREEN_HEIGHT / 10];

BluetoothSerial SerialBT;
ELM327 obd;
std::atomic<uint32_t> rpm{0};
std::atomic<uint32_t> coolant_temp{0};
std::atomic<uint32_t> voltage{0};
std::atomic<uint32_t> tpsval{0};
uint32_t last_displayed_rpm = -1;
uint32_t last_displayed_temp = -1;
uint32_t last_displayed_voltage = -1;
uint32_t last_displayed_tpsval = -1;

SemaphoreHandle_t lvgl_mutex;
TaskHandle_t lvgl_task_handle;
TaskHandle_t obd_task_handle;
TaskHandle_t bt_icon_task_handle;

static lv_obj_t *status_label;
static lv_obj_t *rpm_label;
static lv_obj_t *rpm_val;
static lv_obj_t *coolant_temp_label;
static lv_obj_t *coolant_temp_val;
static lv_obj_t *voltage_label;
static lv_obj_t *voltage_unit_label;
static lv_obj_t *voltage_val;
static lv_obj_t *TPS_label;
static lv_obj_t *TPS_unit_label;
static lv_obj_t *TPS_val;
static lv_obj_t *bt_icon_label;
static lv_obj_t *toggle_btn;
static lv_obj_t *settings_btn;
static lv_obj_t *settings_scr;
static lv_obj_t *main_scr;
static lv_obj_t *brightness_slider;

bool use_celsius = false;
uint8_t current_brightness = 128;

void lvgl_task(void *parameters);
void obd_task(void *parameters);
void bt_icon_task(void *parameters);
void show_main_screen();
void show_settings_screen();

void ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax = 255) {
  // calculate duty, 4095 from 2 ^ 12 - 1
  uint32_t duty = (4095 * min(value, valueMax)) / valueMax;

  // write duty to LEDC
  ledcWrite(channel, duty);
  Serial.printf("duty value: %d\n", duty);
  Serial.printf("brightness value: %d\n", current_brightness);
}

void brightness_slider_event_handler(lv_event_t *e) {
  lv_obj_t *slider = (lv_obj_t *)lv_event_get_target(e);  // Explicit cast added here
  int value = lv_slider_get_value(slider);
  current_brightness = value;  // Update the global brightness value
  Serial.printf("Brightness Slider Value: %d\n", value);  // Log to Serial Monitor
  ledcAnalogWrite(LEDC_CHANNEL_0, value);
  // ⚡️ Later: Add brightness control logic here
}

void toggle_temp_unit_event_handler(lv_event_t *e) {
  use_celsius = !use_celsius;
  lv_obj_t *btn = (lv_obj_t *)lv_event_get_current_target(e);
  lv_label_set_text(lv_obj_get_child(btn, 0), use_celsius ? "Units: Metric" : "Units: Standard");
  update_coolant_temp_val(coolant_temp.load());
  Serial.printf("Temperature unit changed to: %s\n", use_celsius ? "metric" : "standard");
}

void back_btn_event_handler(lv_event_t *e) {
  show_main_screen();
}

void settings_btn_event_handler(lv_event_t *e) {
  show_settings_screen();
}

void lv_create_settings_gui() {
  settings_scr = lv_obj_create(NULL);
  lv_obj_t *label = lv_label_create(settings_scr);
  lv_label_set_text(label, "Settings");
  lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 10);

  lv_obj_t *temp_unit_btn = lv_btn_create(settings_scr);
  lv_obj_add_flag(temp_unit_btn, LV_OBJ_FLAG_CHECKABLE);
  lv_obj_set_height(temp_unit_btn, LV_SIZE_CONTENT);
  lv_obj_align(temp_unit_btn, LV_ALIGN_CENTER, 0, 30);
  lv_obj_add_event_cb(temp_unit_btn, toggle_temp_unit_event_handler, LV_EVENT_CLICKED, NULL);
  lv_obj_t *btn_label = lv_label_create(temp_unit_btn);
  lv_label_set_text(btn_label, "Units: Standard");

  lv_obj_t *brightness_label = lv_label_create(settings_scr);
  lv_label_set_text(brightness_label, "Brightness");
  lv_obj_align(brightness_label, LV_ALIGN_CENTER, 0, -60);

  brightness_slider = lv_slider_create(settings_scr);
  lv_slider_set_range(brightness_slider, 0, 255);
  lv_slider_set_value(brightness_slider, current_brightness, LV_ANIM_OFF);
  lv_obj_set_size(brightness_slider, 180, 10);
  lv_obj_align(brightness_slider, LV_ALIGN_CENTER, 0, -40);
  lv_obj_add_event_cb(brightness_slider, brightness_slider_event_handler, LV_EVENT_VALUE_CHANGED, NULL);

  lv_obj_t *back_btn = lv_btn_create(settings_scr);
  lv_obj_set_height(back_btn, LV_SIZE_CONTENT);
  lv_obj_align(back_btn, LV_ALIGN_TOP_RIGHT, -10, 10);
  lv_obj_add_event_cb(back_btn, back_btn_event_handler, LV_EVENT_CLICKED, NULL);
  lv_obj_t *back_label = lv_label_create(back_btn);
  lv_label_set_text(back_label, "Back");
}

void lv_create_main_gui() {
  static lv_style_t style_large_text;
lv_style_init(&style_large_text);
lv_style_set_text_font(&style_large_text, &lv_font_montserrat_48); // Change to desired font
static lv_style_t style_medium_text;
lv_style_init(&style_medium_text);
lv_style_set_text_font(&style_medium_text, &lv_font_montserrat_32); // Change to desired font
static lv_style_t style_mm_text;
lv_style_init(&style_mm_text);
lv_style_set_text_font(&style_mm_text, &lv_font_montserrat_22); // Change to desired font
static lv_style_t style_small_text;
lv_style_init(&style_small_text);
lv_style_set_text_font(&style_small_text, &lv_font_montserrat_18); // Change to desired font

  main_scr = lv_scr_act();
  status_label = lv_label_create(main_scr);
  lv_label_set_text(status_label, "Status: Initializing...");
  lv_obj_add_style(status_label, &style_small_text, 0);
  lv_obj_align(status_label, LV_ALIGN_TOP_MID, 0, 10);

  rpm_val = lv_label_create(main_scr);
  lv_label_set_text(rpm_val, "----");
  lv_obj_add_style(rpm_val, &style_large_text, 0);
  lv_obj_align(rpm_val, LV_ALIGN_CENTER, 80, -20);

  rpm_label = lv_label_create(main_scr);
  lv_label_set_text(rpm_label, "RPM:");
  lv_obj_add_style(rpm_label, &style_medium_text, 0);
  lv_obj_align_to(rpm_label, rpm_val, LV_ALIGN_CENTER, 0, -30);

  voltage_val = lv_label_create(main_scr);
  lv_label_set_text(voltage_val, "--.-");
  lv_obj_add_style(voltage_val, &style_large_text, 0);
  lv_obj_align(voltage_val, LV_ALIGN_CENTER, 80, 70);

  voltage_label = lv_label_create(main_scr);
  lv_label_set_text(voltage_label, "Volts:");
  lv_obj_add_style(voltage_label, &style_medium_text, 0);
  lv_obj_align_to(voltage_label, voltage_val, LV_ALIGN_CENTER, 0, -40);
  voltage_unit_label = lv_label_create(main_scr);
  lv_label_set_text(voltage_unit_label, "V");
  lv_obj_add_style(voltage_unit_label, &style_large_text, 0);
  lv_obj_align_to(voltage_unit_label, voltage_val, LV_ALIGN_CENTER, 40, 0);

  TPS_val = lv_label_create(main_scr); 
  lv_label_set_text(TPS_val, "---");
  lv_obj_add_style(TPS_val, &style_large_text, 0);
  lv_obj_align(TPS_val, LV_ALIGN_CENTER, -80, -20);

  TPS_label = lv_label_create(main_scr); 
  lv_label_set_text(TPS_label, "Throttle Position:");
  lv_obj_add_style(TPS_label, &style_small_text, 0);
  lv_obj_align_to(TPS_label, TPS_val, LV_ALIGN_CENTER, 0, -30);
  TPS_unit_label = lv_label_create(main_scr); 
  lv_label_set_text(TPS_unit_label, "%");
  lv_obj_add_style(TPS_unit_label, &style_large_text, 0);
  lv_obj_align_to(TPS_unit_label, TPS_val, LV_ALIGN_CENTER, 60, 0);

  coolant_temp_val = lv_label_create(main_scr);
  update_coolant_temp_val(coolant_temp.load());
  lv_obj_add_style(coolant_temp_val, &style_large_text, 0);
  lv_obj_align(coolant_temp_val, LV_ALIGN_CENTER, -80, 70);

  coolant_temp_label = lv_label_create(main_scr);
  lv_label_set_text(coolant_temp_label, "Coolant Temp:");
  lv_obj_add_style(coolant_temp_label, &style_small_text, 0);
  lv_obj_align_to(coolant_temp_label, coolant_temp_val, LV_ALIGN_CENTER, 0, -40);

  bt_icon_label = lv_label_create(main_scr);
  lv_label_set_text(bt_icon_label, "");
  lv_obj_add_style(bt_icon_label, &style_small_text, 0);
  lv_obj_align(bt_icon_label, LV_ALIGN_TOP_LEFT, 10, 10);

  settings_btn = lv_btn_create(main_scr);
  lv_obj_set_height(settings_btn, LV_SIZE_CONTENT);
  lv_obj_align(settings_btn, LV_ALIGN_TOP_RIGHT, -5, 5);
  lv_obj_add_event_cb(settings_btn, settings_btn_event_handler, LV_EVENT_CLICKED, NULL);
  lv_obj_t *settings_label = lv_label_create(settings_btn);
  lv_label_set_text(settings_label, LV_SYMBOL_SETTINGS);
}

void show_main_screen() {
  lv_scr_load(main_scr);
}

void show_settings_screen() {
  lv_scr_load(settings_scr);
}

void setup() {
  Serial.begin(115200);
  lv_init();
  lvgl_mutex = xSemaphoreCreateMutex();

  touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
  touchscreen.begin(touchscreenSPI);
  touchscreen.setRotation(2);
  tft.init();
  tft.begin();
  
  // Initialize LEDC for backlight control
  ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
  ledcAttachPin(LCD_BACK_LIGHT_PIN, LEDC_CHANNEL_0);
  ledcAnalogWrite(LEDC_CHANNEL_0, current_brightness); // Set initial brightness
  delay(1000);
    Serial.println("Setting initial brightness to 25...");
    ledcAnalogWrite(LEDC_CHANNEL_0, 25); // Set to mid-high brightness
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);

  lv_display_t *disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
  lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);

  lv_indev_t *indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
  lv_indev_set_read_cb(indev, touchscreen_read);

  lv_create_main_gui();
  lv_create_settings_gui();

  xTaskCreatePinnedToCore(lvgl_task, "LVGL Task", 12000, NULL, 3, &lvgl_task_handle, 1);
  xTaskCreatePinnedToCore(obd_task, "OBD Task", 10000, NULL, 2, &obd_task_handle, 0);
  xTaskCreatePinnedToCore(bt_icon_task, "BT Icon Task", 2048, NULL, 1, &bt_icon_task_handle, 1);
}

void loop() {
  vTaskDelete(NULL);
}

void touchscreen_read(lv_indev_t *indev, lv_indev_data_t *data) {
  if (touchscreen.tirqTouched() && touchscreen.touched()) {
    TS_Point p = touchscreen.getPoint();
    data->point.x = map(p.x, 200, 3700, 1, SCREEN_WIDTH);
    data->point.y = map(p.y, 240, 3800, 1, SCREEN_HEIGHT);
    data->state = LV_INDEV_STATE_PRESSED;
  } else {
    data->state = LV_INDEV_STATE_RELEASED;
  }
}

void lv_safe_call(std::function<void()> func) {
  if (xSemaphoreTake(lvgl_mutex, pdMS_TO_TICKS(50))) {
    func();
    xSemaphoreGive(lvgl_mutex);
  }
}

void update_status_label(const char *text) {
  lv_safe_call([&]() { lv_label_set_text(status_label, text); });
}

void update_rpm_val(uint32_t current_rpm) {
  if (current_rpm > 0 && current_rpm != last_displayed_rpm) {
    lv_safe_call([&]() {
      char buf[16];
      snprintf(buf, sizeof(buf), "%d", current_rpm);
      lv_label_set_text(rpm_val, buf);
    });
    last_displayed_rpm = current_rpm;
  }
}

void update_coolant_temp_val(uint32_t temp) {
  lv_safe_call([&]() {
    char buf[16];  // Adjust buffer size since we removed extra text
    if (temp == 0 && last_displayed_temp == -1) {
      snprintf(buf, sizeof(buf), "--%s", use_celsius ? "°C" : "°F");
    } else {
      uint32_t display_temp = use_celsius ? temp : static_cast<uint32_t>((temp * 9.0 / 5.0) + 32);
      snprintf(buf, sizeof(buf), "%d%s", display_temp, use_celsius ? "°C" : "°F");
    }
    lv_label_set_text(coolant_temp_val, buf);
    last_displayed_temp = temp;
  });
}

void update_voltage_val(uint32_t voltage) {
  if (voltage != last_displayed_voltage) {
    lv_safe_call([&]() {
      char buf[32];  // Increase buffer size to accommodate decimal points
      snprintf(buf, sizeof(buf), "%.1f", static_cast<float>(voltage)); // Use float directly
      lv_label_set_text(voltage_val, buf);
    });
    last_displayed_voltage = voltage;
  }
}

void update_TPS_val(uint32_t tpsval) {
  if (tpsval != last_displayed_tpsval) {
    lv_safe_call([&]() {
      char buf[32];  // Increase buffer size to accommodate decimal points
      snprintf(buf, sizeof(buf), "%d", tpsval);
      lv_label_set_text(TPS_val, buf);
    });
    last_displayed_tpsval = tpsval;
  }
}

void connect_to_obd() {
  update_status_label("Connecting to OBD...");
  SerialBT.begin("ESP32_OBD", true);
  //SerialBT.setTimeout(50);

  if (!SerialBT.connect("OBDII")) {
    update_status_label("Retrying OBD connection...");
    //vTaskDelay(pdMS_TO_TICKS(1000));
  }

  SerialBT.println("AT Z");     // Reset
  vTaskDelay(pdMS_TO_TICKS(1000));

  if (!obd.begin(SerialBT, true, 2000)) {
    update_status_label("Reinitializing ELM327...");
   // vTaskDelay(pdMS_TO_TICKS(200));
  }
  update_status_label("Connected to ELM327");
}

void lvgl_task(void *parameters) {
  while (true) {
    if (xSemaphoreTake(lvgl_mutex, portMAX_DELAY)) {
      lv_task_handler();
      lv_tick_inc(5);
      xSemaphoreGive(lvgl_mutex);
    }
    vTaskDelay(pdMS_TO_TICKS(2));
  }
}

typedef enum {
  ENG_RPM,
  COOLANT_TEMP,
  VOLTAGE,
  TPS
} obd_pid_states;

void obd_task(void *parameters) {
  connect_to_obd();  // Initial connection
  obd_pid_states obd_state = ENG_RPM;

  while (true) {
    if (!SerialBT.hasClient()) { 
      // If Bluetooth is disconnected, attempt to reconnect
      update_status_label("OBD Disconnected! Reconnecting...");

      while (!SerialBT.hasClient()) {  // Keep trying until connected
        Serial.println("Attempting to reconnect...");
        SerialBT.end();  // Reset Bluetooth
        vTaskDelay(pdMS_TO_TICKS(500)); // Short delay
        SerialBT.begin("ESP32_OBD", true);
        if (SerialBT.connect("OBDII")) {
          connect_to_obd();  // Reinitialize OBD connection
          update_status_label("Reconnected to OBD");
        }
        vTaskDelay(pdMS_TO_TICKS(2000)); // Wait before retrying
      }
    }

    // Continue polling OBD if connected
    switch (obd_state) {
      case ENG_RPM: {
        float tempRPM = obd.rpm();
        if (obd.nb_rx_state == ELM_SUCCESS) {
          rpm = static_cast<uint32_t>(tempRPM);
          update_rpm_val(rpm.load());
          obd_state = COOLANT_TEMP;
        } else if (obd.nb_rx_state != ELM_GETTING_MSG) {
          obd.printError();
          obd_state = COOLANT_TEMP;
        }
        break;
      }

      case COOLANT_TEMP: {
        float tempC = obd.engineCoolantTemp();
        if (obd.nb_rx_state == ELM_SUCCESS) {
          coolant_temp = static_cast<uint32_t>(tempC);
          update_coolant_temp_val(coolant_temp.load());
          obd_state = VOLTAGE;
        } else if (obd.nb_rx_state != ELM_GETTING_MSG) {
          obd.printError();
          obd_state = VOLTAGE;
        }
        break;
      }

      case VOLTAGE: {
        float voltageC = obd.ctrlModVoltage();
        if (obd.nb_rx_state == ELM_SUCCESS) {
          voltage = static_cast<uint32_t>(voltageC);
          update_voltage_val(voltage.load());
          obd_state = TPS;
        } else if (obd.nb_rx_state != ELM_GETTING_MSG) {
          obd.printError();
          obd_state = TPS;
        }
        break;
      }

      case TPS: {
        float throttle = obd.throttle();
        if (obd.nb_rx_state == ELM_SUCCESS) {
          tpsval = static_cast<uint32_t>(throttle);
          update_TPS_val(tpsval.load());
          obd_state = ENG_RPM;
        } else if (obd.nb_rx_state != ELM_GETTING_MSG) {
          obd.printError();
          obd_state = ENG_RPM;
        }
        break;
      }
    }

    vTaskDelay(pdMS_TO_TICKS(12));  // Small delay between readings
  }
}

void bt_icon_task(void *parameters) {
  bool visible = true;
  while (true) {
    lv_safe_call([&]() {
      lv_label_set_text(bt_icon_label, SerialBT.hasClient() ? LV_SYMBOL_BLUETOOTH : (visible ? LV_SYMBOL_BLUETOOTH : ""));
    // Update TPS label when Bluetooth is disconnected
      if (!SerialBT.hasClient()) {
        lv_label_set_text(rpm_val, "----");
        lv_label_set_text(coolant_temp_val, "---");
        lv_label_set_text(voltage_val, "--");
        lv_label_set_text(TPS_val, "---");
        update_status_label("Retrying connection...");
      }
    });

    visible = !visible;
    vTaskDelay(pdMS_TO_TICKS(SerialBT.hasClient() ? 500 : 250));
  }
}

Serial monitor:

Delimiter found.
All chars received: OK
Clearing input serial buffer
Sending the following command/query: 0100
	Received char: S
	Received char: E
	Received char: A
	Received char: R
	Received char: C
	Received char: H
	Received char: I
	Received char: N
	Received char: G
	Received char: .
	Received char: .
	Received char: .
	Received char: \r
E (47170) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (47170) task_wdt:  - IDLE (CPU 0)
E (47170) task_wdt: Tasks currently running:
E (47170) task_wdt: CPU 0: OBD Task
E (47170) task_wdt: CPU 1: IDLE
E (47170) task_wdt: Aborting.

When I use the Bluetooth_multiple_pids example my serial monitor returns:

All chars received: OK
Clearing input serial buffer
Sending the following command/query: 0100
   Received char: S
   Received char: E
   Received char: A
   Received char: R
   Received char: C
   Received char: H
   Received char: I
   Received char: N
   Received char: G
   Received char: .
   Received char: .
   Received char: .
   Received char: \r
   Received char: U
   Received char: N
   Received char: A
   Received char: B
   Received char: L
   Received char: E
   Received char: _
   Received char: T
   Received char: O
   Received char: _
   Received char: C
   Received char: O
Received char: N
   Received char: N
   Received char: E
   Received char: C
   Received char: T
   Received char: \r
   Received char: \r
   Received char: >
Delimiter found.
All chars received: SEARCHING...UNABLETOCONNECT
ELM responded with error "UNABLE TO CONNECT"
Setting protocol via AT TP A%c did not work - trying via AT SP %c
Clearing input serial buffer
Sending the following command/query: AT SP 0
   Received char: O
   Received char: K
   Received char: \r
   Received char: \r
   Received char: >
Delimiter found.
All chars received: OK

Metadata

Metadata

Assignees

Labels

questionFurther information is requested

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions