OBD II бортовой компьютер на ESP32 с дисплеем своими руками
Собираем отдельный экран для автомобиля на ESP32 и Bluetooth ELM327. CYD, оригинальная T-Display, M5Stack Basic и M5StickC Plus2: полный код, настройки дисплеев, переподключение и проверка показаний.
Температура двигателя спрятана за условными делениями, тахометра вообще нет, а телефон с диагностическим приложением каждый раз приходится доставать и закреплять. Отдельный экран с нужными показаниями решает эту задачу: включили зажигание, дождались подключения и видите данные перед собой.
Соберём OBD II бортовой компьютер на ESP32 с готовым дисплеем. Данные получаем через Bluetooth ELM327, без самостоятельного подключения микроконтроллера к CAN-шине. Рассмотрим CYD, оригинальную T-Display, M5Stack Basic и M5StickC Plus2. Ниже — полный исходник, настройки для каждой платы и порядок проверки. Это базовый монитор оборотов, скорости и температуры охлаждающей жидкости; расход топлива и фирменные параметры автомобиля потребуют отдельной доработки.
Как ESP32 получает данные из автомобиля
Цепочка простая: ЭБУ автомобиля → диагностический разъём OBD → адаптер ELM327 → Bluetooth Classic SPP → ESP32 → экран. ELM327 общается с автомобилем по поддерживаемому диагностическому протоколу, а ESP32 отправляет адаптеру текстовые команды. Через Bluetooth для программы получается последовательный поток, похожий на обычный Serial.
Здесь важно не перепутать два разных выбора протокола. Bluetooth SPP — связь между экраном и адаптером. CAN, ISO 9141-2, KWP и другие варианты — связь адаптера с автомобилем. В скетче ELM327 настраивается на автоматический выбор автомобильного протокола. Наличие разъёма подходящей формы само по себе ещё не гарантирует поддержку стандартных OBD-II PID.
В первом варианте опрашиваем три параметра режима 01:
| Запрос | Параметр | Расчёт из байтов ответа |
|---|---|---|
| 01 0C | Обороты двигателя | ((A × 256) + B) / 4, об/мин |
| 01 0D | Скорость автомобиля | A, км/ч |
| 01 05 | Температура охлаждающей жидкости | A − 40, °C |
Декодирование берёт на себя ELMduino. Например, ответ 41 0C 1A F8 означает 1726 об/мин: (26 × 256 + 248) / 4. Это пример расчёта, а не результат измерения на машине. Скорость приходит от ЭБУ, а не от GPS; показания могут отличаться от приборной панели. Поддержку конкретного PID проверяют на конкретном автомобиле.
Главное при покупке: нужен Bluetooth Classic
Для большинства недорогих Bluetooth ELM327 нужен классический ESP32 с поддержкой SPP. ESP32-S3, ESP32-C3 и ESP32-S2 для этого скетча не подходят: у S3 и C3 нет Bluetooth Classic, а у S2 нет Bluetooth. Надпись «Bluetooth» в карточке товара может означать только BLE. Это другой транспорт.
Адаптер ELM327 тоже выбирайте по транспорту: нужен Bluetooth Classic SPP, не Wi-Fi и не BLE-only. У BLE-адаптера могут быть собственные UUID сервиса и характеристик, правила отправки команд и уведомлений. Переключить библиотеку одним флагом недостаточно. Надписи «v1.5» или «v2.1» на корпусе клона не подтверждают качество реализации команд.
На сайте есть отдельная инструкция по LILYGO T-Display S3. Она пригодится для проектов с этой платой, но её распиновку и скетчи нельзя переносить в описанный здесь SPP-проект. Оригинальная TTGO T-Display и T-Display S3 — разные устройства.
Какие платы с экраном подойдут
| Плата | Экран | Настройка проекта | Для чего удобна |
|---|---|---|---|
| CYD ESP32-2432S028R | 2,8″, 240 × 320, ILI9341 | cyd, TFT_eSPI | Крупные показания, доступная плата для своего корпуса |
| Оригинальная TTGO T-Display на ESP32 | 1,14″, 135 × 240, ST7789 | tdisplay, TFT_eSPI | Компактный экран, две кнопки, узкое место установки |
| M5Stack Basic на классическом ESP32 | 2″, 320 × 240 | m5basic, M5Unified | Устройство в готовом корпусе с кнопками |
| M5StickC Plus2, ESP32-PICO-V3-02 | 1,14″, 135 × 240, ST7789V2 | m5stickcplus2, M5Unified | Миниатюрный индикатор в корпусе |
Для первой сборки я бы выбрал CYD, если важна читаемость, или M5Stack Basic, если не хочется начинать с изготовления корпуса. Маленькие T-Display и StickC удобнее разместить, но на них разумно выводить два-три параметра, а не приборную панель с десятком шкал.
CYD — семейство похожих плат, а не единая распиновка. Профиль ниже рассчитан на ESP32-2432S028R с ILI9341. Платы с другими экранами, ST7789, диагональю 3,5″ или ESP32-S3 проверяйте отдельно. Сенсорный слой CYD в этом проекте не используется. Для M5Stack намеренно применяем M5Unified: библиотека определяет поддерживаемое устройство и настраивает экран и питание. Это удобнее, чем переносить старый TFT-профиль Basic на другую ревизию дисплея.
Что понадобится и как подать питание
- Одна из перечисленных плат с классическим ESP32.
- ELM327 с Bluetooth Classic SPP и известным PIN, если он требуется.
- USB-кабель с передачей данных для прошивки.
- Автомобильный USB-адаптер 12 → 5 В, подходящий кабель и крепление.
- Компьютер с PlatformIO либо Arduino IDE.
ELM327 вставляется в диагностический разъём автомобиля. Экран получает 5 В через USB. Провода CAN между платой и автомобилем не нужны. Первую проверку удобнее делать с питанием экрана от компьютера или пауэрбанка, а после успешного подключения переносить устройство в машину.
Не подавайте бортовые 12 В на USB, вывод 5V или 3V3 платы. Для постоянной установки нужен автомобильный источник питания, рассчитанный на реальные условия бортовой сети. OBD-разъём во многих автомобилях остаётся запитанным после выключения зажигания: оставленный адаптер может расходовать аккумулятор. USB-розетка тоже не всегда отключается вместе с зажиганием — это стоит проверить заранее.
Закрепите экран так, чтобы он не закрывал обзор, приборы и зоны раскрытия подушек безопасности. Открытая плата не должна касаться металла. Если будете печатать корпус, можно начать с моделирования корпуса в браузерном 3Ditor, оставив доступ к USB и место под крепление. Литиевый аккумулятор внутри готовой платы не делает её подходящей для хранения на раскалённой панели летом.
Проверяем ELM327 до прошивки экрана
Сначала проверьте адаптер диагностическим приложением на телефоне: сопряжение, соединение с ЭБУ и получение оборотов или температуры. Затем полностью отключите приложение от ELM327. Такие адаптеры обычно обслуживают одно SPP-подключение; телефон, удерживающий соединение, будет мешать ESP32.
Нужен именно Bluetooth MAC адаптера. В коде адрес задаётся шестью байтами: адрес AA:BB:CC:11:22:33 превращается в {0xAA, 0xBB, 0xCC, 0x11, 0x22, 0x33}. Это пример-заглушка: замените все шесть байтов. Имя «OBDII» бывает одинаковым у нескольких устройств, поэтому адрес надёжнее имени.
Если MAC неизвестен, временно загрузите этот отдельный скетч на классический ESP32 и откройте монитор порта 115200. Он выполняет поиск Bluetooth Classic, а не BLE. Включите адаптер и убедитесь, что телефон от него отключён. После поиска прошейте основной проект; сканер одновременно с ним не запускают.
#include <Arduino.h>
#include <BluetoothSerial.h>
BluetoothSerial bt;
void setup() {
Serial.begin(115200);
if (!bt.begin("ESP32-OBD-Scan", true)) {
Serial.println("Bluetooth start failed");
return;
}
if (!bt.discoverAsync([](BTAdvertisedDevice *device) {
Serial.println(device->toString().c_str());
})) {
Serial.println("Discovery start failed");
return;
}
delay(10000);
bt.discoverAsyncStop();
Serial.println("Discovery finished");
}
void loop() { delay(1000); }
PIN берите из инструкции к вашему ELM327; часто встречаются 1234 и 0000, но это не универсальное правило. Ниже применяется API Arduino-ESP32 2.0.17: bt.setPin(elmPin). В более новых версиях ядра API сопряжения меняется, поэтому для воспроизводимой сборки версии зафиксированы.
Готовый проект для четырёх плат
Скачать исходники OBD-компьютера для ESP32. В архиве — PlatformIO-проект, общий скетч для Arduino IDE и отдельный Bluetooth-сканер. Секретов и универсальной прошивки с чужим MAC внутри нет: адрес адаптера нужно изменить перед сборкой.
Основной путь ниже — PlatformIO в VS Code. Создайте папку проекта, положите platformio.ini в корень, а main.cpp — в папку src. Зафиксированы платформа espressif32 6.9.0 с Arduino-ESP32 2.0.17, ELMduino 3.4.1, TFT_eSPI 2.5.43, M5Unified 0.2.7 и M5GFX 0.2.32. Это проверяемая конфигурация примера, а не требование всегда пользоваться старым ядром.
Общая логика остаётся одной. Профили выбирают дисплейную библиотеку и распиновку. Для CYD используется HSPI с MOSI 13, SCLK 14, CS 15 и DC 2; подсветка — GPIO21. Для оригинальной T-Display: MOSI 19, SCLK 18, CS 5, DC 16, RST 23, подсветка GPIO4. Эти определения передаются и в исходник, и в саму TFT_eSPI, поэтому не нужно редактировать её установленный User_Setup.h.
platformio.ini: настройки экранов и библиотек
[env]
platform = espressif32@6.9.0
framework = arduino
monitor_speed = 115200
board_build.partitions = huge_app.csv
lib_deps =
PowerBroker2/ELMDuino@3.4.1
bodmer/TFT_eSPI@2.5.43
build_flags =
-D USER_SETUP_LOADED=1
-D LOAD_GLCD=1
-D LOAD_FONT2=1
[env:cyd]
board = esp32dev
build_flags =
${env.build_flags}
-D ILI9341_2_DRIVER=1
-D TFT_WIDTH=240
-D TFT_HEIGHT=320
-D TFT_MISO=12
-D TFT_MOSI=13
-D TFT_SCLK=14
-D TFT_CS=15
-D TFT_DC=2
-D TFT_RST=-1
-D TFT_BL=21
-D USE_HSPI_PORT=1
-D SPI_FREQUENCY=27000000
-D SPI_READ_FREQUENCY=20000000
[env:tdisplay]
board = lilygo-t-display
build_flags =
${env.build_flags}
-D ST7789_DRIVER=1
-D TFT_WIDTH=135
-D TFT_HEIGHT=240
-D CGRAM_OFFSET=1
-D TFT_SDA_READ=1
-D TFT_MOSI=19
-D TFT_SCLK=18
-D TFT_CS=5
-D TFT_DC=16
-D TFT_RST=23
-D TFT_BL=4
-D SPI_FREQUENCY=40000000
-D SPI_READ_FREQUENCY=6000000
[env:m5basic]
board = m5stack-core-esp32
lib_deps =
PowerBroker2/ELMDuino@3.4.1
m5stack/M5Unified@0.2.7
m5stack/M5GFX@0.2.32
build_flags = -D DISPLAY_M5=1
[env:m5stickcplus2]
board = esp32dev
lib_deps =
${env:m5basic.lib_deps}
build_flags = -D DISPLAY_M5=1
huge_app.csv выделяет место под приложение с Bluetooth; в этой конфигурации нет двух OTA-разделов. Для первого запуска прошиваем по USB. Профиль StickC Plus2 использует базовую цель esp32dev без PSRAM: памяти для данного монитора достаточно, а определение дисплея и питания выполняет M5Unified. Это не профиль для ESP32-S3 или другой модели Stick.
main.cpp: подключение к ELM327 и вывод показаний
В начале файла замените MAC и PIN. Надписи на экране оставлены латиницей: стандартный шрифт TFT_eSPI не содержит русскую кириллицу. Значение -- означает отсутствие свежего успешного ответа. «SPP connected» подтверждает транспортное соединение и инициализацию адаптера, но не подтверждает поддержку всех параметров ЭБУ.
#include <Arduino.h>
#include <BluetoothSerial.h>
#include <ELMduino.h>
#include <memory>
#include <cmath>
#if defined(DISPLAY_M5)
#include <M5Unified.h>
#define screen M5.Display
#else
#include <TFT_eSPI.h>
TFT_eSPI screen;
#endif
#if !defined(CONFIG_BT_SPP_ENABLED)
#error This sketch needs a classic ESP32 with Bluetooth SPP
#endif
// Replace these six bytes with your ELM327 Bluetooth MAC address.
uint8_t elmAddress[6] = {0xAA, 0xBB, 0xCC, 0x11, 0x22, 0x33};
const char *elmPin = "1234"; // Use the PIN supplied with your adapter.
BluetoothSerial bt;
std::unique_ptr<ELM327> elm;
struct Reading { float value = 0; uint32_t updated = 0; bool valid = false; };
Reading data[3];
uint8_t pid = 0;
uint32_t lastAttempt = 0, lastDraw = 0, completedAt = 0;
bool attempted = false, ready = false, paused = false;
const uint32_t STALE_MS = 10000;
void invalidate() {
for (auto &r : data) r.valid = false;
}
void drawDashboard(const char *status) {
const uint16_t ink = 0xFFFF, green = 0x07E0, bg = 0x0000;
const int w = screen.width(), h = screen.height();
screen.setTextFont(2);
screen.setTextColor(green, bg);
screen.fillRect(0, 0, w, 22, bg);
screen.drawString(status, 4, 2);
const char *labels[] = {"RPM", "km/h", "Coolant C"};
const int rowH = (h - 24) / 3;
for (int i = 0; i < 3; ++i) {
const int y = 24 + i * rowH;
screen.fillRect(0, y, w, rowH, bg);
screen.setTextColor(ink, bg);
screen.drawString(labels[i], 4, y + 2);
char value[24];
if (ready && data[i].valid && uint32_t(millis() - data[i].updated) < STALE_MS)
snprintf(value, sizeof(value), "%.0f", data[i].value);
else snprintf(value, sizeof(value), "--");
screen.setTextDatum(2); // Top-right alignment in both display libraries.
screen.drawString(value, w - 6, y + 2);
screen.setTextDatum(0); // Restore top-left alignment for labels.
}
}
bool connectAdapter() {
drawDashboard("Connecting...");
Serial.println("Connecting to ELM327 via SPP");
if (!bt.connect(elmAddress)) return false;
// Recreate ELM327 to free its previous buffer and reset its query state.
elm.reset(new ELM327());
if (!elm->begin(bt, true, 3000, '0')) {
elm.reset();
bt.disconnect();
return false;
}
pid = 0;
paused = false;
invalidate();
return true;
}
void setup() {
Serial.begin(115200);
#if defined(DISPLAY_M5)
auto cfg = M5.config();
M5.begin(cfg);
screen.setBrightness(128);
#else
screen.init();
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, HIGH);
#endif
screen.setRotation(1);
screen.fillScreen(0x0000);
drawDashboard("Starting Bluetooth");
if (!bt.begin("ESP32-OBD-Dashboard", true)) {
drawDashboard("BT start failed");
while (true) delay(1000);
}
bt.setPin(elmPin);
}
void loop() {
#if defined(DISPLAY_M5)
M5.update();
#endif
if (ready && !bt.connected()) {
ready = false;
elm.reset();
invalidate();
paused = false;
attempted = false;
Serial.println("Bluetooth disconnected");
}
if (!ready) {
if (!attempted || uint32_t(millis() - lastAttempt) >= 5000) {
attempted = true;
ready = connectAdapter();
lastAttempt = millis(); // Retry five seconds after an attempt finishes.
drawDashboard(ready ? "SPP connected" : "ELM not connected");
}
delay(10);
return;
}
if (uint32_t(millis() - lastDraw) >= 250) {
lastDraw = millis();
drawDashboard("SPP connected");
}
if (paused && uint32_t(millis() - completedAt) < 100) return;
paused = false;
float value;
switch (pid) {
case 0: value = elm->rpm(); break;
case 1: value = elm->kph(); break;
default: value = elm->engineCoolantTemp(); break;
}
if (elm->nb_rx_state == ELM_GETTING_MSG) return;
if (elm->nb_rx_state == ELM_SUCCESS && std::isfinite(value)) {
data[pid].value = value;
data[pid].updated = millis();
data[pid].valid = true;
} else {
data[pid].valid = false;
Serial.printf("PID slot %u: ", pid);
elm->printError();
}
pid = (pid + 1) % 3;
completedAt = millis();
paused = true;
}
Как устроен опрос и почему нельзя отправлять всё сразу
ELMduino работает с PID неблокирующим способом. Вызов rpm() запускает запрос или продолжает чтение ответа. Пока nb_rx_state == ELM_GETTING_MSG, повторяем тот же вызов. Только после успеха или ошибки переходим к следующему параметру. Если вызвать RPM, скорость и температуру подряд без ожидания, ответы будут смешиваться с незавершённым запросом.
В скетче pid по очереди выбирает обороты, скорость и температуру. Между завершёнными запросами есть пауза 100 мс. Это не обещание десяти измерений в секунду: фактическое время зависит от автомобильного протокола, ЭБУ и адаптера. Рисование раз в 250 мс тоже не означает, что данные приходят с такой частотой.
При ошибке соответствующее поле сразу становится недействительным. Даже успешное значение перестаёт показываться через 10 секунд без обновления. При потере Bluetooth аннулируются все поля. Поэтому вчерашние 90 °C не останутся на дисплее как текущее измерение. Нулевые обороты и нулевая скорость, полученные успешным ответом, остаются нормальными данными.
После потери связи программа пробует подключиться снова, выдерживая пять секунд после завершения неудачной попытки. Объект ELM327 пересоздаётся: так освобождается его буфер и сбрасывается состояние незавершённого запроса. Вызовы соединения Bluetooth и первоначальной настройки ELM327 могут блокировать выполнение на несколько секунд; для первой версии это допустимо, но для полностью отзывчивого интерфейса их стоит вынести в отдельную задачу.
Скетч не очищает ошибки и не выполняет исполнительные тесты. Он читает выбранные стандартные параметры и настраивает адаптер. Поддержку дополнительных PID разумно проверить отдельно запросами битовых масок 01 00, 01 20 и далее, прежде чем добавлять их в постоянный опрос.
Собираем и загружаем прошивку
В терминале PlatformIO из корня проекта выполните команду для вашей платы. Одновременно загружать четыре профиля не нужно:
pio run -e cyd
pio run -e cyd -t upload
pio device monitor -b 115200
Для остальных устройств замените cyd на tdisplay, m5basic или m5stickcplus2. Первое выполнение скачает зависимости. Если подключено несколько COM-портов, укажите нужный через --upload-port COM5 и pio device monitor --port COM5 -b 115200, заменив COM5 своим портом.
Если привычнее Arduino IDE
В архиве есть OBD_Dashboard/OBD_Dashboard.ino с тем же кодом. Установите пакет плат ESP32 версии 2.0.17 и ELMduino 3.4.1. Для CYD и оригинальной T-Display установите TFT_eSPI 2.5.43 и выберите соответствующий профиль дисплея: для CYD точный User_Setup.h проекта ESP32-Cheap-Yellow-Display, для T-Display — Setup25_TTGO_T_Display.h. Активным должен быть один профиль, не несколько одновременно. После обновления библиотеки проверяйте настройки заново.
Для M5Stack Basic и StickC Plus2 установите M5Unified 0.2.7 и M5GFX 0.2.32 и добавьте #define DISPLAY_M5 1 первой строкой .ino, до всех include. Для CYD и T-Display это определение не добавляйте. Выберите цель на классическом ESP32 и раздел приложения, в который помещается полученная прошивка; для ESP32 Dev Module подойдёт Huge APP. MAC и PIN меняются одинаково в обеих средах. Если устройство новое для вас, пригодится разбор структуры скетча и цикла loop().
Первое подключение в автомобиле
- На стоящем автомобиле вставьте ELM327 и включите зажигание. Для оборотов двигатель нужно запустить в подходящих условиях.
- Отключите телефон от адаптера, подайте питание экрану и откройте монитор Serial.
- Дождитесь подключения. Если «Connecting...» задерживается, смотрите журнал, MAC и PIN, а не только экран.
- Проверьте RPM на холостом ходу. При заглушённом двигателе допустим успешный ответ 0.
- Посмотрите температуру: на холодном двигателе она должна быть правдоподобной для условий, при прогреве — меняться. Универсальный порог перегрева не задаём: он зависит от двигателя.
- На стоящей машине ожидайте скорость 0. Динамическую проверку проводите с помощником или записью журнала, не работая с устройством за рулём.
- Выключите питание адаптера: показания должны смениться на «--». Верните питание и проверьте повторное подключение.
Предел проверки примеров: все четыре основных профиля собраны компилятором с указанными зависимостями. Проверка на конкретной плате, автомобиле и вашем ELM327 не выполнялась. Успешная сборка подтверждает синтаксис, доступность API и размер приложения; она не подтверждает качество Bluetooth-связи, правильность аппаратной ревизии экрана или поддержку PID автомобилем.
Если экран или OBD-показания не работают
| Симптом | Что проверить |
|---|---|
| BluetoothSerial / SPP не собирается | Выбрана классическая ESP32, а не S3/C3/S2; установлено ядро 2.0.17, как в примере |
| Прошивка не помещается | Таблица разделов и размер приложения; используйте Huge APP для данного проекта |
| Чёрный экран при работающем Serial | Точная ревизия платы, драйвер дисплея, вывод подсветки и выбранный профиль |
| Экран смещён или обрезан | Для T-Display нужны размеры 135 × 240 и CGRAM_OFFSET; профиль CYD не подходит |
| ELM not connected | Питание адаптера, MAC, PIN, SPP вместо BLE и отсутствие подключения телефона |
| SPP connected, но поля «--» | Зажигание, доступность ЭБУ, сообщения ELMduino в Serial и поддержку стандартных PID |
| NO DATA только для одного поля | Поддержку этого PID; не подставляйте 0 вместо отсутствующего ответа |
| Связь рвётся при запуске двигателя | Просадку питания USB/адаптера и поведение Bluetooth после восстановления питания |
Скорость 115200 в примере относится к журналу USB Serial. Для BluetoothSerial она не является настраиваемой скоростью UART внутри ELM327. Совет «поменяйте Bluetooth с 115200 на 38400» здесь не исправляет ошибки транспорта: у нас Bluetooth.begin получает имя устройства и режим master.
Если поиск ничего не обнаруживает, убедитесь, что адаптер допускает обнаружение и не занят телефоном. При проблемах со старыми сохранёнными сопряжениями воспользуйтесь официальным примером удаления paired devices из Arduino-ESP32 и выполните сопряжение заново. Если адаптер отвечает на AT-команды, но не общается с ЭБУ, экран и Bluetooth уже не главные подозреваемые: проверяйте автомобильный протокол и сам ELM327.
Что добавить после первого рабочего запуска
Начните с переключения страниц кнопками, изменения яркости и более крупного отображения одного параметра. Затем можно добавить температуру воздуха на впуске, нагрузку двигателя или другие поддерживаемые PID. Каждый новый параметр занимает время в очереди; большой список замедлит обновление важных показаний.
Напряжение через AT RV — измерение питания адаптера, а не гарантированно точный вольтметр аккумулятора. В основном скетче его нет. Если добавляете AT-команды вручную, отправляйте их только после завершения текущего запроса и отдельно разбирайте ответ. Нельзя одновременно читать один поток Serial через ELMduino и свой независимый парсер.
Расход топлива тоже не появляется автоматически из скорости и RPM. Для оценки нужны поддерживаемые параметры, например MAF, корректные допущения для топлива и режима работы, а затем сравнение с реальной заправкой. Температуру масла, давление и состояние коробки стандартные PID предоставляют не для каждой машины; могут понадобиться расширенные запросы производителя.
Для первого прибора достаточно устойчиво получить три показания, пережить отключение адаптера и не показывать старые данные как свежие. После этого уже имеет смысл заниматься корпусом, графиками и красивыми шкалами.
Документация и исходники
- Espressif: BluetoothSerial и ограничения SPP.
- ELMduino: API и последовательный неблокирующий опрос.
- ELM327: документация команд и протоколов.
- CYD ESP32-2432S028R: схема и настройки дисплея.
- Оригинальная TTGO T-Display: репозиторий производителя.
- M5Stack Basic: спецификация и распиновка; StickC Plus2: экран и управление питанием.
- M5Unified: общая библиотека устройств M5Stack.
Понравилась статья?
Поставьте лайк и сохраните полезный материал в закладки.