diff --git a/JK-BMSToPylontechCAN/JK-BMSToPylontechCAN.ino b/JK-BMSToPylontechCAN/JK-BMSToPylontechCAN.ino index db4a82a..358950e 100644 --- a/JK-BMSToPylontechCAN/JK-BMSToPylontechCAN.ino +++ b/JK-BMSToPylontechCAN/JK-BMSToPylontechCAN.ino @@ -104,7 +104,7 @@ //#define SOC_THRESHOLD_FOR_FORCE_CHARGE_REQUEST_I 0 // This disables the setting if the force charge request, even if battery SOC is 0. const uint8_t sSOCThresholdForForceCharge = SOC_THRESHOLD_FOR_FORCE_CHARGE_REQUEST_I; -#define VERSION_EXAMPLE "2.2" +#define VERSION_EXAMPLE "2.2.2" /* * Pin layout, may be adapted to your requirements @@ -132,18 +132,15 @@ const uint8_t sSOCThresholdForForceCharge = SOC_THRESHOLD_FOR_FORCE_CHARGE_REQUE //#define TIMING_TEST #define TIMING_TEST_PIN 7 -#if !defined(DEBUG) -//#define DEBUG -#endif /* * Program timing, may be adapted to your requirements */ -#if defined(DEBUG) -#define MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS 5000 -#define MILLISECONDS_BETWEEN_CAN_FRAME_SEND 5000 -#define SECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS "5" // Only for display on LCD -#define SECONDS_BETWEEN_CAN_FRAME_SEND "5" // Only for display on LCD +#if defined(STANDALONE_TEST) +#define MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS 1000 +#define MILLISECONDS_BETWEEN_CAN_FRAME_SEND 1000 +#define SECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS "1" // Only for display on LCD +#define SECONDS_BETWEEN_CAN_FRAME_SEND "1" // Only for display on LCD #else #define MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS 2000 #define MILLISECONDS_BETWEEN_CAN_FRAME_SEND 2000 @@ -180,20 +177,20 @@ uint16_t sTimeoutFrameCounter = 0; // Counts BMS frame timeouts, (every 2 s /* * Display timeouts, may be adapted to your requirements */ -# if defined(DEBUG) -#define DISPLAY_ON_TIME_STRING "30s " +# if defined(STANDALONE_TEST) +#define DISPLAY_ON_TIME_STRING "30 s" #define DISPLAY_ON_TIME_SECONDS 30L // L to avoid overflow at macro processing -#define DISPLAY_ON_TIME_SECONDS_IF_TIMEOUT 20L //#define NO_MULTIPLE_BEEPS_ON_TIMEOUT // Activate it if you do not want multiple beeps #define BEEP_ON_TIME_SECONDS_IF_TIMEOUT 10L // 10 s # else #define DISPLAY_ON_TIME_STRING "5 min" // Only for display on LCD #define DISPLAY_ON_TIME_SECONDS 300L // 5 minutes. L to avoid overflow at macro processing -#define DISPLAY_ON_TIME_SECONDS_IF_TIMEOUT 180L // 3 minutes # endif // DEBUG //#define DISPLAY_ALWAYS_ON // Activate this, if you want the display to be always on. # if !defined(DISPLAY_ALWAYS_ON) +void doLCDBacklightTimeoutHandling(); +bool checkAndTurnLCDOn(); bool sSerialLCDIsSwitchedOff = false; uint16_t sFrameCounterForLCDTAutoOff = 0; # endif @@ -474,6 +471,9 @@ delay(4000); // To be able to connect Serial monitor after reset or power up and #endif Serial.println(F(STR(MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS) " ms between 2 BMS requests")); Serial.println(F(STR(MILLISECONDS_BETWEEN_CAN_FRAME_SEND) " ms between 2 CAN transmissions")); +#if defined(USE_LCD) && !defined(DISPLAY_ALWAYS_ON) + Serial.println(F("LCD Backlight timeout is " DISPLAY_ON_TIME_STRING)); +#endif Serial.println(); #if defined(USE_LCD) @@ -485,7 +485,11 @@ delay(4000); // To be able to connect Serial monitor after reset or power up and myLCD.setCursor(0, 1); myLCD.print(F("Long press = debug")); myLCD.setCursor(0, 2); +#if defined(STANDALONE_TEST) + myLCD.print(F("Test -fixed BMS data")); +#else myLCD.print(F("Get BMS every " SECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS " s ")); +#endif myLCD.setCursor(0, 3); myLCD.print(F("Send CAN every " SECONDS_BETWEEN_CAN_FRAME_SEND " s ")); delay(2000); // To see the messages @@ -546,8 +550,10 @@ void loop() { } #if defined(STANDALONE_TEST) + sBMSFrameProcessingComplete = true; // for LCD timeout etc. processReceivedData(); // for statistics - delay(500); + printBMSDataOnLCD(); // for switching between MAX and MIN display + delay(MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS); // do it simple :-) #else /* * Get reply from BMS and check timeout @@ -616,32 +622,18 @@ void loop() { sErrorStatusJustChanged = false; #if defined(USE_LCD) setDisplayPage(JK_BMS_PAGE_OVERVIEW); +# if !defined(DISPLAY_ALWAYS_ON) + if (checkAndTurnLCDOn()) { + Serial.println(F("error status changing")); // Switch on LCD display, triggered by error status changing + } +# endif #endif } } -#if defined(USE_LCD) +#if defined(USE_LCD) && !defined(DISPLAY_ALWAYS_ON) if (sSerialLCDAvailable) { -# if !defined(DISPLAY_ALWAYS_ON) - /* - * Display backlight handling - */ - sFrameCounterForLCDTAutoOff++; - if (sFrameCounterForLCDTAutoOff == 0) { - sFrameCounterForLCDTAutoOff--; // To avoid overflow, we have an unsigned integer here - } - if (sFrameCounterForLCDTAutoOff == (DISPLAY_ON_TIME_SECONDS * 1000U) / MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS) { - myLCD.noBacklight(); // switch off backlight after 5 minutes - sSerialLCDIsSwitchedOff = true; - Serial.println(F("Switch off LCD display, triggered by LCD \"ON\" timeout reached.")); - } - if (!sDoErrorBeep && sSerialLCDIsSwitchedOff) { - myLCD.backlight(); // Switch backlight LED on after timeout - sSerialLCDIsSwitchedOff = false; - sFrameCounterForLCDTAutoOff = 0; // start again to enable switch off after 5 minutes - Serial.println(F("Switch on LCD display, triggered by successfully receiving a BMS status frame")); - } -# endif + doLCDBacklightTimeoutHandling(); } #endif @@ -712,10 +704,18 @@ void processJK_BMSStatusFrame() { Serial.println(); } - sFrameIsRequested = false; // do not try to receive more + sFrameIsRequested = false; // Everything OK, do not try to receive more sBMSFrameProcessingComplete = true; sJKBMSFrameHasTimeout = false; - sTimeoutFrameCounter = 0; + if (sTimeoutFrameCounter > 0) { + // First frame after timeout + sTimeoutFrameCounter = 0; +#if defined(USE_LCD) && !defined(DISPLAY_ALWAYS_ON) + if (checkAndTurnLCDOn()) { + Serial.println(F("successfully receiving first BMS status frame after BMS communication timeout")); // Switch on LCD display, triggered by successfully receiving first BMS status frame + } +#endif + } processReceivedData(); printReceivedData(); /* @@ -781,32 +781,33 @@ void handleFrameReceiveTimeout() { myLCD.setCursor(0, 0); printCANInfoOnLCD(); } +# if !defined(DISPLAY_ALWAYS_ON) + if (checkAndTurnLCDOn()) { + Serial.println(F("BMS communication timeout")); // Switch on LCD display, triggered by BMS communication timeout + } +# endif #endif } sTimeoutFrameCounter++; if (sTimeoutFrameCounter == 0) { sTimeoutFrameCounter--; // To avoid overflow, we have an unsigned integer here } + #if defined(USE_LCD) - if (sSerialLCDAvailable) { - if (sLCDDisplayPageNumber != JK_BMS_PAGE_CAN_INFO) { - myLCD.clear(); - myLCD.setCursor(0, 0); - myLCD.print(F("Receive timeout ")); - myLCD.print(sTimeoutFrameCounter); - myLCD.setCursor(0, 1); - myLCD.print(F("Is BMS switched off?")); - } + /* + * Global timeout message + */ + if (sSerialLCDAvailable # if !defined(DISPLAY_ALWAYS_ON) - /* - * Check for display timeout here - */ - if (sTimeoutFrameCounter == ((DISPLAY_ON_TIME_SECONDS_IF_TIMEOUT * 1000) / MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS)) { - myLCD.noBacklight(); // switch off backlight after 3 minutes - sSerialLCDIsSwitchedOff = true; - Serial.println(F("Switch off LCD display now, triggered by receive timeouts.")); - } + && !sSerialLCDIsSwitchedOff # endif + && sLCDDisplayPageNumber != JK_BMS_PAGE_CAN_INFO) { + myLCD.clear(); + myLCD.setCursor(0, 0); + myLCD.print(F("Receive timeout ")); + myLCD.print(sTimeoutFrameCounter); + myLCD.setCursor(0, 1); + myLCD.print(F("Is BMS switched off?")); } #endif } @@ -847,6 +848,44 @@ void printReceivedData() { } #if defined(USE_LCD) +# if !defined(DISPLAY_ALWAYS_ON) + +/* + * Called on button press, BMS communication timeout and new error + * Always reset timeout counter! + * @return true if LCD was switched off before + */ +bool checkAndTurnLCDOn() { + sFrameCounterForLCDTAutoOff = 0; // Always start again to enable backlight switch off after 5 minutes + + if (sSerialLCDIsSwitchedOff) { + /* + * If backlight LED off, switch it on, but do not select next page, except if debug button was pressed. + */ + myLCD.backlight(); + sSerialLCDIsSwitchedOff = false; + Serial.print(F("Switch on LCD display, triggered by ")); // to be continued by caller + return true; + } + return false; +} +/* + * Display backlight handling + */ +void doLCDBacklightTimeoutHandling() { + sFrameCounterForLCDTAutoOff++; + if (sFrameCounterForLCDTAutoOff == 0) { + sFrameCounterForLCDTAutoOff--; // To avoid overflow, we have an unsigned integer here + } + + if (sFrameCounterForLCDTAutoOff == (DISPLAY_ON_TIME_SECONDS * 1000U) / MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS) { + myLCD.noBacklight(); // switch off backlight after 5 minutes + sSerialLCDIsSwitchedOff = true; + Serial.println(F("Switch off LCD display, triggered by LCD \"ON\" timeout reached.")); + } +} +# endif + void LCDPrintSpaces(uint8_t aNumberOfSpacesToPrint) { for (uint_fast8_t i = 0; i < aNumberOfSpacesToPrint; ++i) { myLCD.print(' '); @@ -1014,7 +1053,7 @@ void printCellStatisticsOnLCD() { myLCD.print(F("% ")); if (i % 4 == 3) { /* - * Use the last 4 characters for information + * Use the last 4 characters of a LCD row for information * about min or max and the total balancing time */ if (i == 3) { @@ -1368,50 +1407,44 @@ void handlePageButtonPress(bool aButtonToggleState __attribute__((unused))) { void checkButtonPress() { #if defined(USE_LCD) if (sSerialLCDAvailable) { -# if !defined(DISPLAY_ALWAYS_ON) - if ((sPageButtonJustPressed) && sSerialLCDIsSwitchedOff) { - /* - * If backlight LED off, switch it on, but do not select next page, except if debug button was pressed. - */ - myLCD.backlight(); - sSerialLCDIsSwitchedOff = false; - Serial.println(F("Switch on LCD display, triggered by button press")); - sPageButtonJustPressed = false; // avoid switching pages if page button was pressed. - - sFrameCounterForLCDTAutoOff = 0; // start again to enable backlight switch off after 5 minutes - if (sTimeoutFrameCounter > 0) { - sTimeoutFrameCounter = 1; // start again to enable switch off after 3 minutes, but save "timeout flag" - } - } -# endif - /* * Handle Page button */ uint8_t tDisplayPageNumber = sLCDDisplayPageNumber; if (sPageButtonJustPressed) { sPageButtonJustPressed = false; +# if !defined(DISPLAY_ALWAYS_ON) /* - * Switch display pages to next page + * If backlight LED off, switch it on, but do not select next page */ - tDisplayPageNumber++; + if (checkAndTurnLCDOn()) { + Serial.println(F("button press")); // Switch on LCD display, triggered by button press + sPageButtonJustPressed = false; // avoid switching pages if page button was pressed. + } else +# endif + { + /* + * Switch display pages to next page + */ + tDisplayPageNumber++; - if (sJKBMSFrameHasTimeout || tDisplayPageNumber > JK_BMS_PAGE_MAX) { - // Receive timeout or wrap around here - tDisplayPageNumber = JK_BMS_PAGE_OVERVIEW; + if (sJKBMSFrameHasTimeout || tDisplayPageNumber > JK_BMS_PAGE_MAX) { + // Receive timeout or wrap around here + tDisplayPageNumber = JK_BMS_PAGE_OVERVIEW; - } else if (tDisplayPageNumber == JK_BMS_PAGE_CELL_INFO) { - // Create symbols character for maximum and minimum - bigNumberLCD._createChar(1, bigNumbersTopBlock); - bigNumberLCD._createChar(2, bigNumbersBottomBlock); + } else if (tDisplayPageNumber == JK_BMS_PAGE_CELL_INFO) { + // Create symbols character for maximum and minimum + bigNumberLCD._createChar(1, bigNumbersTopBlock); + bigNumberLCD._createChar(2, bigNumbersBottomBlock); - // Prepare for statistics page here display max first - sCellStatisticsDisplayCounter = 0; + // Prepare for statistics page here display max first but for half the regular time + sCellStatisticsDisplayCounter = (CELL_STATISTICS_COUNTER_MASK >> 1) - 1; - } else if (tDisplayPageNumber == JK_BMS_PAGE_BIG_INFO) { - bigNumberLCD.begin(); // Creates custom character used for generating big numbers + } else if (tDisplayPageNumber == JK_BMS_PAGE_BIG_INFO) { + bigNumberLCD.begin(); // Creates custom character used for generating big numbers + } + setDisplayPage(tDisplayPageNumber); } - setDisplayPage(tDisplayPageNumber); } else if (PageSwitchButtonAtPin2.readDebouncedButtonState()) { if (tDisplayPageNumber == JK_BMS_PAGE_CAN_INFO) { // Button is still pressed @@ -1426,9 +1459,6 @@ void checkButtonPress() { Serial.println(); Serial.println(F("Long press detected -> switch to CAN page and activate one time debug print")); setDisplayPage(JK_BMS_PAGE_CAN_INFO); -# if !defined(DISPLAY_ALWAYS_ON) - sFrameCounterForLCDTAutoOff = 0; // start again to enable backlight switch off after 5 minutes -# endif } } } // PageSwitchButtonAtPin2.ButtonStateHasJustChanged diff --git a/JK-BMSToPylontechCAN/Pylontech_CAN.hpp b/JK-BMSToPylontechCAN/Pylontech_CAN.hpp index c2b84d9..42691ab 100644 --- a/JK-BMSToPylontechCAN/Pylontech_CAN.hpp +++ b/JK-BMSToPylontechCAN/Pylontech_CAN.hpp @@ -63,6 +63,9 @@ void sendPylontechCANFrame(struct PylontechCANFrameStruct *aPylontechCANFrame) { aPylontechCANFrame->FrameData.UBytes); } +/* + * Called in case of BMS communication timeout + */ void modifyAllCanDataToInactive() { PylontechCANCurrentValuesFrame.FrameData.Current100Milliampere = 0; // Clear all requests in case of timeout / BMS switched off, before sending diff --git a/README.md b/README.md index 28736c7..7a9bbbb 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The JK-BMS RS485 data (e.g. at connector GPS) are provided as RS232 TTL with 105 - Protocol converter from the JK-BMS status frame to Pylontech CAN frames. - Display of BMS information, Cell voltages and alarms on a locally attached serial 2004 LCD. - Page button for switching 4 LCD display pages. -- Statistics of minimum and maximum cells during balancing to identify conspicious cells. +- Statistics of minimum and maximum cells during balancing to identify conspicuous cells. - Realtime monitoring of some CAN data sent by long button press. - Switch off LCD backlight after timeout (can be disabled). - Beep on alarm and connection timeouts with selectable timeout. @@ -41,7 +41,8 @@ The JK-BMS RS485 data (e.g. at connector GPS) are provided as RS232 TTL with 105 **By default, the program sends a request to force charge the battery if SOC is below 5 %. This can be adapted by changing the line `#define SOC_THRESHOLD_FOR_FORCE_CHARGE_REQUEST_I 5`.** -At around 100% SOC, the JK-BMS seems to send strange current information of more than +/- 1 ampere. +At around 100% SOC, the JK-BMS seems to send strange current information of more than +/- 1 ampere.
+If CAN communications breaks, the inverter may use different values for controlling the battery (e.g. "Use Batt V"), which may lead to additional discharging / charging.
@@ -71,7 +72,7 @@ At around 100% SOC, the JK-BMS seems to send strange current information of more | :-: | :-: | | ![My installation](https://github.com/ArminJo/JK-BMSToPylontechCAN/blob/main/pictures/CompleteInstallation.jpg) | ![Automatic brightness](https://github.com/ArminJo/JK-BMSToPylontechCAN/blob/main/pictures/AutomaticBrightness.jpg) | | Breadboard detail | | -| ![Breadbaoard detail](https://github.com/ArminJo/JK-BMSToPylontechCAN/blob/main/pictures/BreadbaoardDetail.jpg) | | +| ![Breadboard detail](https://github.com/ArminJo/JK-BMSToPylontechCAN/blob/main/pictures/BreadboardDetail.jpg) | | | No-breadboard version overview | No-breadboard version overview | | ![Overview no breadboard](https://github.com/ArminJo/JK-BMSToPylontechCAN/blob/main/pictures/NoBreadboardOverview1.jpg) | ![Overview no breadboard](https://github.com/ArminJo/JK-BMSToPylontechCAN/blob/main/pictures/NoBreadboardOverview2.jpg) | | Nano top view | Nano bottom view | @@ -86,7 +87,7 @@ Also usable as connection schematic. - [Wokwi JK-BMSToPylontechCAN example](https://wokwi.com/projects/371657348012321793).
- + # Connection schematic The standard **RX** of the Arduino is used for the JK_BMS connection.
A **schottky diode** is inserted into the RX line to allow programming the AVR with the JK-BMS still connected and switched on. @@ -100,7 +101,7 @@ On the Deye, connect cable before setting `Battery Mode` to `Lithium`, to avoid ``` 78L05 Schottky diode - From Uno / Nano 5 V - ___ to enable powering CAN + ___ to enable powering CAN External 6.6 V from Battery #2 >--o--------|___|-------o--|<|-< Uno 5V module by Nano USB, | | | if battery is not attached __________ Schottky diode ____|____ --- ____|____ _________ @@ -122,8 +123,8 @@ On the Deye, connect cable before setting `Battery Mode` to `Lithium`, to avoid | | --|<|-- RX of Uno / Nano | ----------- D4 (or other pin, if specified) --------------- GND - - + + # Automatic brightness control for 2004 LCD 5V O------o------o | | diff --git a/pictures/BreadbaoardDetail.jpg b/pictures/BreadboardDetail.jpg similarity index 100% rename from pictures/BreadbaoardDetail.jpg rename to pictures/BreadboardDetail.jpg