#include #include #include #define BMS_RX 16 #define BMS_TX 17 #define BMS_DE 4 TFT_eSPI tft = TFT_eSPI(); struct BmsData { float packV = 0; float current = 0; int soc = 0; char status = 'S'; bool online = false; }; BmsData bats[4]; uint16_t getCRC(const uint8_t *buf, size_t len) { uint16_t crc = 0xFFFF; for (size_t pos = 0; pos < len; pos++) { crc ^= (uint16_t)buf[pos]; for (int i = 8; i != 0; i--) { if ((crc & 0x0001) != 0) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return crc; } void requestBmsData(uint8_t id) { uint8_t req[8] = {id, 0x03, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00}; uint16_t crc = getCRC(req, 6); req[6] = crc & 0xFF; req[7] = (crc >> 8) & 0xFF; digitalWrite(BMS_DE, HIGH); delayMicroseconds(500); Serial2.write(req, 8); Serial2.flush(); delayMicroseconds(500); digitalWrite(BMS_DE, LOW); unsigned long start = millis(); uint8_t res[64]; int idx = 0; while (millis() - start < 800) { if (Serial2.available()) res[idx++] = Serial2.read(); } if (idx >= 47 && res[0] == id) { bats[id-1].packV = ((res[3] << 8) | res[4]) / 100.0f; int16_t rawCurrent = (res[5] << 8) | res[6]; bats[id-1].current = rawCurrent / 100.0f; if (bats[id-1].current > 0.2) bats[id-1].status = 'C'; else if (bats[id-1].current < -0.2) bats[id-1].status = 'D'; else bats[id-1].status = 'S'; bats[id-1].soc = (res[45] << 8) | res[46]; bats[id-1].online = true; } else { bats[id-1].online = false; } } void updateDisplay() { tft.fillScreen(TFT_BLACK); float totalAmps = 0; int avgSoc = 0; int count = 0; for (int i = 0; i < 4; i++) { int y = 12 + (i * 48); if (bats[i].online) { // Номер (размер 2 для экономии места) tft.setTextSize(2); tft.setTextColor(TFT_WHITE); tft.setCursor(5, y + 8); tft.printf("B%d", i + 1); // Данные (размер 3) tft.setTextSize(3); // SOC tft.setTextColor(TFT_YELLOW); tft.setCursor(50, y); tft.printf("%d%%", bats[i].soc); // Ток tft.setTextColor(TFT_CYAN); tft.setCursor(135, y); tft.printf("%4.1fA", bats[i].current); // Статус без скобок if (bats[i].status == 'C') tft.setTextColor(TFT_GREEN); else if (bats[i].status == 'D') tft.setTextColor(TFT_RED); else tft.setTextColor(TFT_DARKGREY); tft.setCursor(275, y); tft.printf("%c", bats[i].status); totalAmps += bats[i].current; avgSoc += bats[i].soc; count++; } else { tft.setTextSize(2); tft.setTextColor(TFT_DARKGREY); tft.setCursor(5, y + 8); tft.printf("B%d: OFFLINE", i + 1); } } // --- НИЖНЯЯ ПАНЕЛЬ --- tft.drawFastHLine(0, 202, 320, TFT_RED); if (count > 0) { tft.setTextSize(3); tft.setTextColor(TFT_WHITE); tft.setCursor(5, 212); tft.printf("%d%%", avgSoc / count); tft.setCursor(140, 212); if (totalAmps > 0.2) tft.setTextColor(TFT_GREEN); else if (totalAmps < -0.2) tft.setTextColor(TFT_RED); else tft.setTextColor(TFT_WHITE); tft.printf("SUM:%.1fA", totalAmps); } } void setup() { Serial2.begin(9600, SERIAL_8N1, BMS_RX, BMS_TX); pinMode(BMS_DE, OUTPUT); tft.init(); tft.setRotation(1); tft.fillScreen(TFT_BLACK); } void loop() { for (int i = 1; i <= 4; i++) { requestBmsData(i); delay(50); } updateDisplay(); delay(5000); }