Проєкт моніторингу стану батареї з використанням JKBMS та TFT дисплея
Нещодавно отримав акумулятори LiFePo4 на 3.2V 304Ah та BMS плату від JiKongBMS на 200А (розряд).
Після складання та підключення по Bluetooth стало очевидним, що кожного разу заходити в додаток для перегляду інформації не дуже зручно. Спочатку я планував використати вольтметр або універсальний індикатор батареї, проте особливість LiFePo4 полягає в тому, що графік розряду на більшій частині ємності є майже прямою лінією. Це означає, що при напрузі, наприклад, 3,3 В на кожній комірці, досить складно визначити залишкову ємність акумулятора. А от використання інформації від BMS відкривало зовсім інші можливості: вона могла надавати точніші дані про стан батареї, такі як SOC (State of Charge) та інші ключові показники, які допомагають зрозуміти поточний рівень заряду.
Тому виникла ідея створити проєкт з моніторингу стану батареї на базі системи управління батареєю JKBMS та дисплея TFT ST7735. Метою було мати можливість в реальному часі відстежувати ключові параметри акумулятора, такі як напруга комірок, струм, SOC (State of Charge), температури та інші важливі показники. Хочу поділитися своїм досвідом, проблемами, з якими зіткнувся, та шляхами їх вирішення.
Так, так, я знаю, що можна було купити готовий дисплей для JKBMS. Проте прості варіанти відкинув одразу, дуже хотілось "попаяти" - не дарма за плечима диплом інженера. І з готовим дисплеєм не було б цієї статті. Тому найцікавіше далі:
Проблематика: основні виклики під час реалізації проєкту
Спочатку все здавалося простим: підключити дисплей до мікроконтролера, отримувати дані від JKBMS та виводити їх на екран. Але, як завжди буває в електроніці та програмуванні, реальність виявилася іншою (приблизно як у цій статті). Основні проблеми, з якими я зіткнувся:
- Підключення та сумісність пристроїв: JKBMS використовує UART для передачі даних, але потрібно було правильно налаштувати зв'язок з мікроконтролером.
- Обробка отриманих даних: Дані від BMS надходять у специфічному форматі, який потрібно правильно парсити.
- Відображення інформації на дисплеї: Потрібно було розробити інтерфейс, який би був зручним та інформативним.
- Робота з кнопкою для перемикання екранів: Реалізація стабільної роботи кнопки з антидребезгом була викликом, навіть коли все було підключено правильно, вона все одно відмовлялася працювати.
- Енергозбереження: Автоматичне вимкнення підсвічування дисплея після певного часу неактивності.
Підключення компонентів
Для початку потрібно було вибрати відповідний мікроконтролер. Після цілого дня роботи з ESP8266 CH340✨Wi-Fi платою NodeMCU, я зрозумів, що її недостатньо - одночасно працювати з різними портами їй складно (хоча не виключаю, що це може бути через мої руки). Тому я обрав STM32F103C8T6 (так званий "Blue Pill") через те, що він був під рукою 😊, а ще - він вміє одночасно працювати з кількома UART портами. Спочатку підключав до ПК через USB-UART програматор, вже пізніше здогадався використати ST-LINK V2.
Звертайте увагу на ПІДПИСАНІ ПІНИ на ваших пристроях, бо різні виробники можуть їх "чередувати", і те, що зображено, не завжди відповідає дійсності.
Дисплей TFT ST7735:
- CS (Chip Select) – до піна PB0 (налаштовується програмно)
- RST (Reset) – до PB1 (налаштовується програмно)
- DC (Data/Command) – до PB10 (налаштовується програмно)
- MOSI – до PA7 (за замовчуванням для SPI1)
- SCK – до PA5 (за замовчуванням для SPI1)
- Підсвічування – до PB4 (через резистор для керування)
JKBMS:
- TX (від BMS) – до PA3 (RX мікроконтролера)
- RX (до BMS) – до PA2 (TX мікроконтролера)
Кнопка:
- Один контакт – до PA0
- Інший контакт – до GND
- Пін PA0 налаштований з внутрішнім підтягувальним резистором (INPUT_PULLUP), хоча я для "вірочки" також через резистор підтягнув +3V.
Підключення дисплея не викликало проблем.
А от, щоб знайти в роз'ємі на JKBMS виводи RX/TX, довелося трохи погуглити. Виявилося, що в залежності від "ревізії" плати, "+" та "GND" можуть бути симетричними. За допомогою мультиметра визначив де "VCC" і відповідно біля нього мав бути TX.
Підключення кнопки теж було досить простим, але вона відмовлялася працювати, і тільки після години тестування мультиметром у різних місцях вона випадковим чином "ожила".
На практиці це виглядає так:
Ну і далі - "КОД". Нехай гуру "кодінга" сильно не критикують, можливо не оптимізовано, десь не найкращі рішення - проте все працює, і мене влаштовує. До речі, якщо будуть конкретні поради у коментарях - код буде підправлятись. (Повний скетч - у кінці статті)
Пройдусь трохи по блоках коду
Почнемо з підключення необхідних бібліотек та оголошення змінних:
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#include "FontsRus/TimesNRCyr10.h" // Шрифт для кирилиці
До речі, щодо кирилиці: українська буква "і" при виведенні на дисплей підміняється англійською "i". Це, звичайно, "костиль", але поки так піде.
Оголошуємо піни для дисплея та створюємо його об'єкт:
#define TFT_CS PB0
#define TFT_RST PB1
#define TFT_DC PB10
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
Налаштовуємо UART для зв'язку з JKBMS:
#define BMS_RX PA3
#define BMS_TX PA2
HardwareSerial &BMS = Serial2;
Оголошуємо пін для кнопки та керування підсвічуванням:
#define BUTTON_PIN PA0
#define TFT_BACKLIGHT_PIN PB4
Логіка роботи кнопки
Один з викликів був у тому, щоб кнопка працювала стабільно без помилкових спрацьовувань. Для цього реалізовано антидребезг. Основне призначення кнопки - перемикання екранів. Також налаштував автоматичне перемикання екранів кожні 10 секунд і засинання дисплея через 5 хвилин неактивності:
void loop() {
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == LOW) {
if (displaySleep) {
displaySleep = false;
lastInteractionTime = millis();
digitalWrite(TFT_BACKLIGHT_PIN, HIGH);
updateDisplay();
} else {
screenIndex = (screenIndex + 1) % 5;
lastInteractionTime = millis();
lastScreenChangeTime = millis();
updateDisplay();
}
}
}
}
lastButtonState = reading;
// Автоматична зміна екранів
if (millis() - lastScreenChangeTime >= 10000 && !displaySleep) {
screenIndex = (screenIndex + 1) % 5;
updateDisplay();
lastScreenChangeTime = millis();
}
// Вимкнення дисплея після 5 хвилин неактивності
if (millis() - lastInteractionTime >= 300000 && !displaySleep) {
displaySleep = true;
digitalWrite(TFT_BACKLIGHT_PIN, LOW);
tft.fillScreen(ST77XX_BLACK);
}
}
Отримання та обробка даних від JKBMS
Тут довелося трохи попрацювати. Згідно з інструкцією JKBMS, треба вельмишановно попросити відповідь через надсилання запиту "send→ 4E 57 00 13 00 00 00 00 06 03 00 ◇ 00 00 00 00 00 68 00 00 01 29".
Отриману відповідь заносимо в буфер, перевіряємо її, та парсимо вже в конкретних "екранах" на дисплеї. У кінці статті знайдете файл, з доклидим описом отриманих даних, та що кожен байт означає
Основна логіка отримання даних реалізована у функції updateDisplay()
:
void updateDisplay() {
uint8_t request[] = {
0x4E, 0x57, // ... решта байтів запиту
};
uint8_t responseBuffer[256] = {0};
while (BMS.available()) {
BMS.read();
}
BMS.write(request, sizeof(request));
unsigned long startMillis = millis();
int index = 0;
bool responseReceived = false;
// Читаємо дані протягом 50 мс
while (millis() - startMillis < 50) {
if (BMS.available()) {
if (index < sizeof(responseBuffer)) {
responseBuffer[index++] = BMS.read();
responseReceived = true;
} else {
BMS.read();
}
}
}
// Відображення даних або повідомлення про помилку
if (responseReceived) {
displayData(responseBuffer, index);
} else {
displayError();
}
}
Відображення даних на дисплеї
Парсинг відбувається за допомогою інструкції. В інтернеті є її опис, тому не буду зосереджувати на цьому увагу. В залежності від screenIndex
відображаються різні екрани. Наприклад, для відображення стану батареї:
void displayData(uint8_t* buffer, int length) {
tft.fillScreen(ST77XX_BLACK);
tft.setTextColor(ST77XX_WHITE);
switch (screenIndex) {
case 0:
displayBatteryStatus(buffer);
break;
// Інші випадки...
}
}
Додав кілька функцій для інтерактивності дисплея, наприклад колір в залежності від стану заряду. Ось функція для відображення стану батареї:
void displayBatteryStatus(uint8_t* buffer) {
int soc = buffer[SOC_INDEX];
tft.setTextColor(ST77XX_GREEN);
tft.setCursor(5, 15);
tft.println("Стан батареї");
tft.drawLine(0, 25, 160, 25, ST77XX_WHITE);
tft.setTextColor(ST77XX_WHITE);
tft.setCursor(5, 45);
tft.print("SOC: ");
tft.print(soc);
tft.println("%");
uint16_t socColor = ST77XX_GREEN;
if (soc < 20) {
socColor = ST77XX_RED;
} else if (soc < 50) {
socColor = ST77XX_ORANGE;
}
tft.drawRect(5, 60, 100, 15, ST77XX_WHITE);
tft.fillRect(6, 61, soc, 13, socColor);
uint16_t totalVoltage = (buffer[TOTAL_VOLTAGE_INDEX] << 8) | buffer[TOTAL_VOLTAGE_INDEX + 1];
tft.setCursor(5, 100);
tft.print("Напруга: ");
tft.setTextColor(ST77XX_RED);
tft.print(totalVoltage / 100.0);
tft.println("V");
}
Ось так виглядає код в роботі:
Проблеми та їх вирішення
- Затримка між натисканням кнопки та зміною екрана: Спочатку була значна затримка при перемиканні екранів. Це було через те, що в функції
updateDisplay()
ми чекали 200 мс на отримання даних від BMS. Зменшивши цей час до 50 мс, ми досягли швидшої реакції. ⚡ - Проблеми з піном для кнопки: Пін PB3 не працював належним чином для кнопки. Виявилося, що цей пін може бути зайнятий іншими функціями або не підтримує внутрішній підтягувальним резистор. Замінивши його на PA0, ми вирішили проблему. ✅
- Автоматичне засинання дисплея: Спочатку дисплей вимикався через 3 хвилини. Я вирішив збільшити цей час до 5 хвилин, змінивши перевірку неактивності. Можете виставити як вам зручно:
if (millis() - lastInteractionTime >= 300000 && !displaySleep) {
// ...
}
Тепер можна переходити до вмонтовування у корпус, щоб виглядало акуратно і було зручно користуватися. Кнопку логічно вивести біля дисплея. Для кріплення деталей можна використати двосторонній скотч, невеликі гвинтики або навіть гарячий клей (залежить від того, наскільки міцно хочете закріпити). Не забудьте про вентиляцію, щоб пристрій не перегрівався. Після цього лишається тільки перевірити, чи всі порти і кнопки працюють, і можна сміливо тестувати, підключати й користуватися!
Мій висновок
Цей проєкт виявився цікавим та корисним досвідом. Я навчився працювати з JKBMS, обробляти дані з UART та відображати їх на дисплеї. Хоча були проблеми, їх вирішення допомогло мені глибше зрозуміти роботу мікроконтролера та периферійних пристроїв.
Поради для тих, хто хоче повторити цей проєкт:
- Детально ознайомтеся зі специфікаціями вашого BMS та мікроконтролера. 📚
- Перевіряйте апаратне підключення за допомогою мультиметра. 🛠️
- Використовуйте серійний монітор для відладки та виведення діагностичної інформації. 🖥️
- Не бійтеся експериментувати та змінювати код під свої потреби. 💡
UPD1 (доповнення статі №1)
Окрім основних параметрів, на TFT-дисплеї можна відображати й інші дані, які передаються по протоколу RX-TX. На скрінах можна побачити, цю велику кількість інформаці. По суті Це дозволяє користувачу створити "аналог" додатка, на екрані та відслідковувати його параметри в реальному часі. Всі ці дані значно полегшують контроль за роботою батареї, забезпечуючи своєчасне виявлення потенційних проблем і оптимізацію процесу заряджання та розряджання.
Завантаження необхідного скетча
Для успішного виконання проекту вам потрібно завантажити файал:
Завантажити розшифровку отриманої відповіді від JKBMS
Успіхів з проєктом разом із myproject.com.ua.
© 2024 Мій Проект.Автор: Jazzzman. Використання матеріалів дозволено лише з посиланням на джерело.
Написати коментар