Open
Description
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