From 27cb0b772c5994cd4efe98ad1a2df5a74e07dab1 Mon Sep 17 00:00:00 2001 From: Armin Date: Sun, 23 Jun 2024 15:35:27 +0200 Subject: [PATCH] Bumped version to 3.2.0 - Keep accumulated analytics data at reset. --- .github/workflows/TestCompile.yml | 2 +- JK-BMSToPylontechCAN/JK-BMS.h | 4 +- JK-BMSToPylontechCAN/JK-BMS.hpp | 25 +- JK-BMSToPylontechCAN/JK-BMSToPylontechCAN.ino | 141 ++++++----- JK-BMSToPylontechCAN/JK-BMS_Analytics.h | 17 +- JK-BMSToPylontechCAN/JK-BMS_Analytics.hpp | 98 ++++++-- JK-BMSToPylontechCAN/JK-BMS_LCD.hpp | 211 +++++----------- JK-BMSToPylontechCAN/LCDPrintUtils.hpp | 232 ++++++++++++++++++ JK-BMSToPylontechCAN/LiquidCrystal_I2C.hpp | 4 + JK-BMSToPylontechCAN/Pylontech_CAN.h | 4 +- README.md | 27 +- 11 files changed, 497 insertions(+), 268 deletions(-) create mode 100644 JK-BMSToPylontechCAN/LCDPrintUtils.hpp diff --git a/.github/workflows/TestCompile.yml b/.github/workflows/TestCompile.yml index e96067c..151cb4a 100644 --- a/.github/workflows/TestCompile.yml +++ b/.github/workflows/TestCompile.yml @@ -53,7 +53,7 @@ jobs: - arduino-boards-fqbn: MightyCore:avr:644 platform-url: https://mcudude.github.io/MightyCore/package_MCUdude_MightyCore_index.json build-properties: - All: -DANDRES_644_BOARD + All: -DUSE_LAYOUT_FOR_644_BOARD steps: - name: Checkout diff --git a/JK-BMSToPylontechCAN/JK-BMS.h b/JK-BMSToPylontechCAN/JK-BMS.h index d44a459..e88d5d8 100644 --- a/JK-BMSToPylontechCAN/JK-BMS.h +++ b/JK-BMSToPylontechCAN/JK-BMS.h @@ -99,7 +99,7 @@ void computeUpTimeString(); void printJKStaticInfo(); void printJKDynamicInfo(); void detectAndPrintAlarmInfo(); -#if !defined(DISABLE_MONITORING) +#if defined(ENABLE_MONITORING) void printCSVLine(char aLeadingChar = '\0'); #endif @@ -190,7 +190,7 @@ struct JKComputedDataStruct { int16_t Battery10MilliAmpere; // Charging is positive discharging is negative float BatteryLoadCurrentFloat; // Ampere int16_t BatteryLoadPower; // Watt Computed value, Charging is positive discharging is negative - int32_t BatteryCapacityAccumulator10MilliAmpere; // 500 Ah = 180,000,000 10MilliAmpereSeconds + int32_t BatteryCapacityAsAccumulator10MilliAmpere; // 500 Ah = 180,000,000 10MilliAmpereSeconds. Pre-computed capacity to compare with accumulator value. bool BMSIsStarting; // True if SOC and Cycles are both 0, for around 16 seconds during JK-BMS startup. }; extern struct JKComputedDataStruct JKComputedData; // All derived converted and computed data useful for display diff --git a/JK-BMSToPylontechCAN/JK-BMS.hpp b/JK-BMSToPylontechCAN/JK-BMS.hpp index c259824..ab1feb3 100644 --- a/JK-BMSToPylontechCAN/JK-BMS.hpp +++ b/JK-BMSToPylontechCAN/JK-BMS.hpp @@ -552,7 +552,7 @@ void printJKCellStatisticsInfo() { void initializeComputedData() { // Initialize capacity accumulator with sensible value - JKComputedData.BatteryCapacityAccumulator10MilliAmpere = (AMPERE_HOUR_AS_ACCUMULATOR_10_MILLIAMPERE / 100) + JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere = (AMPERE_HOUR_AS_ACCUMULATOR_10_MILLIAMPERE / 100) * sJKFAllReplyPointer->SOCPercent * JKComputedData.TotalCapacityAmpereHour; } @@ -592,12 +592,12 @@ void fillJKComputedData() { JKComputedData.Battery10MilliAmpere = getCurrent(sJKFAllReplyPointer->Battery10MilliAmpere); JKComputedData.BatteryLoadCurrentFloat = JKComputedData.Battery10MilliAmpere / 100.0; - JKComputedData.BatteryCapacityAccumulator10MilliAmpere += JKComputedData.Battery10MilliAmpere; + JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere += JKComputedData.Battery10MilliAmpere; if (lastJKReply.SOCPercent == 0 && sJKFAllReplyPointer->SOCPercent == 1) { JK_INFO_PRINTLN(F("Reset capacity to 1%")); // Reset capacity at transition from 0 to 1 - JKComputedData.BatteryCapacityAccumulator10MilliAmpere = getOnePercentCapacityAsAccumulator10Milliampere(); - JKLastPrintedData.BatteryCapacityAccumulator10MilliAmpere = JKComputedData.BatteryCapacityAccumulator10MilliAmpere; + JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere = getOnePercentCapacityAsAccumulator10Milliampere(); + JKLastPrintedData.BatteryCapacityAccumulator10MilliAmpere = JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere; } // Serial.print("Battery10MilliAmpere=0x"); @@ -871,6 +871,9 @@ void printActiveState(bool aIsActive) { Serial.print(F(" active")); } +/* + * Called exclusively once by processJK_BMSStatusFrame() + */ void printJKStaticInfo() { Serial.println(F("*** BMS INFO ***")); @@ -918,7 +921,7 @@ extern const char sCSVCaption[] PROGMEM; void printJKDynamicInfo() { JKReplyStruct *tJKFAllReplyPointer = sJKFAllReplyPointer; -#if !defined(DISABLE_MONITORING) +#if defined(ENABLE_MONITORING) # if defined(MONOTORING_PERIOD_FAST) // Print every dataset, every 2 seconds, and caption every minute printCSVLine(); @@ -942,8 +945,8 @@ void printJKDynamicInfo() { // Print +CSV line every percent of nominal battery capacity (TotalCapacityAmpereHour) for capacity to voltage graph if (abs( JKLastPrintedData.BatteryCapacityAccumulator10MilliAmpere - - JKComputedData.BatteryCapacityAccumulator10MilliAmpere) > getOnePercentCapacityAsAccumulator10Milliampere()) { - JKLastPrintedData.BatteryCapacityAccumulator10MilliAmpere = JKComputedData.BatteryCapacityAccumulator10MilliAmpere; + - JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere) > getOnePercentCapacityAsAccumulator10Milliampere()) { + JKLastPrintedData.BatteryCapacityAccumulator10MilliAmpere = JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere; printCSVLine('+'); } @@ -1003,7 +1006,7 @@ void printJKDynamicInfo() { Serial.println(F("There is less than 10% capacity below 3.0V and 20% capacity below 3.2V.")); } #endif -#if !defined(DISABLE_MONITORING) && !defined(MONOTORING_PERIOD_FAST) +#if defined(ENABLE_MONITORING) && !defined(MONOTORING_PERIOD_FAST) /* * Print CSV caption every 10 minute */ @@ -1097,7 +1100,7 @@ void printJKDynamicInfo() { } } -#if !defined(DISABLE_MONITORING) +#if defined(ENABLE_MONITORING) const char sCSVCaption[] PROGMEM = "Uptime[min];Cell_1;Cell_2;Cell_3;Cell_4;Cell_5;Cell_6;Cell_7;Cell_8;Cell_9;Cell_10;Cell_11;Cell_12;Cell_13;Cell_14;Cell_15;Cell_16;Voltage[mV];Current[A];Capacity[100mAh];SOC[%]"; @@ -1140,7 +1143,7 @@ void setCSVString() { dtostrf(JKComputedData.BatteryLoadCurrentFloat, 4, 2, &tCurrentAsFloatString[0]); sprintf_P(&sStringBuffer[tBufferIndex], PSTR("%u;%s;%ld;%d"), JKComputedData.BatteryVoltage10Millivolt * 10, tCurrentAsFloatString, - JKComputedData.BatteryCapacityAccumulator10MilliAmpere / (AMPERE_HOUR_AS_ACCUMULATOR_10_MILLIAMPERE / 10), /* 100mAh units*/ + JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere / (AMPERE_HOUR_AS_ACCUMULATOR_10_MILLIAMPERE / 10), /* 100mAh units*/ sJKFAllReplyPointer->SOCPercent); } } @@ -1153,7 +1156,7 @@ void printCSVLine(char aLeadingChar) { setCSVString(); Serial.println(sStringBuffer); } -#endif // !defined(DISABLE_MONITORING) +#endif // defined(ENABLE_MONITORING) #include "LocalDebugLevelEnd.h" #endif // _JK_BMS_HPP diff --git a/JK-BMSToPylontechCAN/JK-BMSToPylontechCAN.ino b/JK-BMSToPylontechCAN/JK-BMSToPylontechCAN.ino index 9ecff54..5126178 100644 --- a/JK-BMSToPylontechCAN/JK-BMSToPylontechCAN.ino +++ b/JK-BMSToPylontechCAN/JK-BMSToPylontechCAN.ino @@ -97,15 +97,10 @@ * --------------- GND */ -/* - * Ideas: - * Balancing time per day / week / month etc. - */ #include -#define VERSION_EXAMPLE "3.2.0" // For full revision history see https://github.com/ArminJo/JK-BMSToPylontechCAN?tab=readme-ov-file#revision-history -#define MILLIS_IN_ONE_SECOND 1000L +#define VERSION_EXAMPLE "3.2.0" /* * If battery SOC is below this value, the inverter is forced to charge the battery from any available power source regardless of inverter settings. @@ -117,55 +112,74 @@ * Macros for CAN data modifications */ //#define CAN_DATA_MODIFICATION // Currently enables the function to reduce max current at high SOC level -//#define USE_CCCV_MODIFY_FUNCTION // Changes modification to CCCV method my Ngoc: https://github.com/ArminJo/JK-BMSToPylontechCAN/discussions/31 +//#define USE_CCCV_MODIFY_FUNCTION // Changes modification to CCCV method by Ngoc: https://github.com/ArminJo/JK-BMSToPylontechCAN/discussions/31 //#define USE_OWN_MODIFY_FUNCTION // Use (currently empty) function which must be filled in at bottom of Pylontech_CAN.hpp /* * Values for standard CAN data modification */ //#define MAX_CURRENT_MODIFICATION_LOWER_SOC_THRESHOLD_PERCENT 80 // Start SOC for linear reducing maximum current. Default 80 //#define MAX_CURRENT_MODIFICATION_MIN_CURRENT_TENTHS_OF_AMPERE 50 // Value of current at 100 % SOC. Units are 100 mA! Default 50 -#if !defined(DO_NOT_SHOW_SHORT_CELL_VOLTAGES) -#define SHOW_SHORT_CELL_VOLTAGES // Print 3 digits cell voltage (value - 3.0 V) on Cell Info page. Enables display of up to 20 voltages or additional information. -#endif - //#define DEBUG // This enables debug output for all files - only for development //#define STANDALONE_TEST // If activated, fixed BMS data is sent to CAN bus and displayed on LCD. #if defined(STANDALONE_TEST) +//#define LCD_PAGES_TEST // Additional automatic tests +# if defined(LCD_PAGES_TEST) +//#define BIG_NUMBER_TEST // Additional automatic tests, especially for rendering of JK_BMS_PAGE_BIG_INFO +# endif //#define ENABLE_MONITORING # if defined(USE_NO_LCD) #undef USE_NO_LCD // LCD is activated for standalone test # endif #endif +#if defined(__AVR_ATmega644P__) +#define USE_LAYOUT_FOR_644_BOARD +#endif + /* - * Options to reduce program size + * Options to reduce program size / add optional features */ -//#define ENABLE_MONITORING // Requires additional 846 bytes program space -#if defined(ENABLE_MONITORING) -# if !defined(MONOTORING_PERIOD_SECONDS) +#if FLASHEND > 0x7FFF // for more than 32k +#define ENABLE_MONITORING // Requires additional 858 bytes program space +#define SERIAL_INFO_PRINT // Requires additional 1684 bytes program space +#endif +#define KEEP_ANALYTICS_ACCUMULATED_DATA_AT_RESET // Requires additional 80 bytes program space + +//#define DO_NOT_SHOW_SHORT_CELL_VOLTAGES // Saves 470 bytes program space +#if !defined(DO_NOT_SHOW_SHORT_CELL_VOLTAGES) +#define SHOW_SHORT_CELL_VOLTAGES // Print 3 digits cell voltage (value - 3.0 V) on Cell Info page. Enables display of up to 20 voltages or additional information. +#endif + +#if defined(ENABLE_MONITORING) && !defined(MONOTORING_PERIOD_SECONDS) //#define MONOTORING_PERIOD_FAST // If active, then print CSV line every 2 seconds, else every minute #define MONOTORING_PERIOD_SLOW // If active, then print CSV line every hour, and CSV line every 10 minutes -# endif -#elif FLASHEND <= 0x7FFF // for 32k or less -#define DISABLE_MONITORING // Disables writing cell and current values CSV data to serial output. Saves 846 bytes program space. - currently activated to save program space. #endif #if !defined(SERIAL_INFO_PRINT) && !defined(STANDALONE_TEST) && FLASHEND <= 0x7FFF -#define NO_SERIAL_INFO_PRINT // Disables writing some info to serial output. Saves 974 bytes program space. - currently activated to save program space. +#define NO_SERIAL_INFO_PRINT // Disables writing some info to serial output. Saves 974 bytes program space. #endif -#if !defined(ENABLE_LIFEPO4_PLAUSI_WARNING) -#define SUPPRESS_LIFEPO4_PLAUSI_WARNING // Disables warning on Serial out about using LiFePO4 beyond 3.0 v to 3.45 V. +#if defined(NO_SERIAL_INFO_PRINT) +#define JK_INFO_PRINT(...) void(); +#define JK_INFO_PRINTLN(...) void(); +#else +#define JK_INFO_PRINT(...) Serial.print(__VA_ARGS__); +#define JK_INFO_PRINTLN(...) Serial.println(__VA_ARGS__); #endif -//#define NO_CAPACITY_35F_EXTENSIONS // Disables generating of frame 0x35F for total capacity. This additional frame is no problem for Deye inverters. -//#define NO_CAPACITY_379_EXTENSIONS // Disables generating of frame 0x379 for total capacity. This additional frame is no problem for Deye inverters. -//#define NO_BYD_LIMITS_373_EXTENSIONS // Disables generating of frame 0x373 for cell limits as sent by BYD battery. See https://github.com/dfch/BydCanProtocol/tree/main. This additional frame is no problem for Deye inverters. + +//#define NO_CAPACITY_35F_EXTENSIONS // Disables generating of frame 0x35F for total capacity. This additional frame is no problem for Deye inverters. Saves 56 bytes program space. +//#define NO_CAPACITY_379_EXTENSIONS // Disables generating of frame 0x379 for total capacity. This additional frame is no problem for Deye inverters. Saves 24 bytes program space. +//#define NO_BYD_LIMITS_373_EXTENSIONS // Disables generating of frame 0x373 for cell limits as sent by BYD battery. See https://github.com/dfch/BydCanProtocol/tree/main. This additional frame is no problem for Deye inverters. Saves 200 bytes program space. //#define NO_CELL_STATISTICS // Disables generating and display of cell balancing statistics. Saves 1628 bytes program space. -//#define NO_ANALYTICS // Disables generating, storing and display of SOC graph for Arduino Serial Plotter. Saves 3882 bytes program space. +//#define NO_ANALYTICS // Disables generating, storing and display of SOC graph for Arduino Serial Plotter. Saves 3856 bytes program space. //#define USE_NO_LCD // Disables the code for the LCD display. Saves 25% program space on a Nano. //#define USE_NO_COMMUNICATION_STATUS_LEDS // The code for the BMS and CAN communication status LED is deactivated and the pins are not switched to output +#if !defined(ENABLE_LIFEPO4_PLAUSI_WARNING) +#define SUPPRESS_LIFEPO4_PLAUSI_WARNING // Disables warning on Serial out about using LiFePO4 beyond 3.0 v to 3.45 V. +#endif + #if !defined(USE_NO_LCD) #define USE_SERIAL_2004_LCD // Parallel or 1604 LCD not yet supported #define LCD_MESSAGE_PERSIST_TIME_MILLIS 2000 @@ -188,8 +202,8 @@ # endif #endif -// sStringBuffer is defined in JK-BMS_LCD.hpp if DISABLE_MONITORING and NO_ANALYTICS are defined -#if !defined(DISABLE_MONITORING) +// sStringBuffer is defined in JK-BMS_LCD.hpp if not ENABLE_MONITORING and NO_ANALYTICS are defined +#if defined(ENABLE_MONITORING) char sStringBuffer[100]; // For cvs lines, "Store computed capacity" line and LCD rows #elif !defined(NO_ANALYTICS) char sStringBuffer[40]; // for "Store computed capacity" line, printComputedCapacity() and LCD rows @@ -198,13 +212,12 @@ char sStringBuffer[40]; // for "Store computed capacity" line, p /* * Pin layout, may be adapted to your requirements */ -//#define ANDRES_644_BOARD #define BUZZER_PIN A2 // To signal errors #define PAGE_SWITCH_DEBUG_BUTTON_PIN_FOR_INFO 2 // Button at INT0 / D2 for switching LCD pages - definition is not used in program, only for documentation. // The standard RX of the Arduino is used for the JK_BMS connection. #define JK_BMS_RX_PIN_FOR_INFO 0 // We use the Serial RX pin - definition is not used in program, only for documentation. #if !defined(JK_BMS_TX_PIN) // Allow override by global symbol -# if defined(ANDRES_644_BOARD) +# if defined(USE_LAYOUT_FOR_644_BOARD) #define JK_BMS_TX_PIN 12 # else #define JK_BMS_TX_PIN 4 @@ -219,7 +232,7 @@ char sStringBuffer[40]; // for "Store computed capacity" line, p #else // BMS and CAN communication status LEDs # if !defined(BMS_COMMUNICATION_STATUS_LED_PIN) -# if defined(ANDRES_644_BOARD) +# if defined(USE_LAYOUT_FOR_644_BOARD) #define BMS_COMMUNICATION_STATUS_LED_PIN 14 #define CAN_COMMUNICATION_STATUS_LED_PIN 15 # else @@ -239,7 +252,7 @@ char sStringBuffer[40]; // for "Store computed capacity" line, p //#define TIMING_TEST #if defined(TIMING_TEST) -# if defined(ANDRES_644_BOARD) +# if defined(USE_LAYOUT_FOR_644_BOARD) #define TIMING_TEST_PIN 13 # else #define TIMING_TEST_PIN 10 // is SS pin for SPI and must be used as OUTPUT (set by SPI.init())! @@ -252,7 +265,7 @@ char sStringBuffer[40]; // for "Store computed capacity" line, p * SPI: MOSI - 11, MISO - 12, SCK - 13. CS cannot be replaced by constant ground. * I2C: SDA - A4, SCL - A5. */ -#if defined(ANDRES_644_BOARD) +#if defined(USE_LAYOUT_FOR_644_BOARD) #define SPI_CS_PIN 4 // !SS Must be specified before #include "MCP2515_TX.hpp" #define SPI_MOSI_PIN_FOR_INFO 5 // Definition is not used in program, only for documentation. #define SPI_MISO_PIN_FOR_INFO 6 // Definition is not used in program, only for documentation. @@ -265,7 +278,7 @@ char sStringBuffer[40]; // for "Store computed capacity" line, p #define SPI_MISO_PIN_FOR_INFO 12 // Definition is not used in program, only for documentation. #define SPI_SCK_PIN_FOR_INFO 13 // Definition is not used in program, only for documentation. # endif -#endif // defined(ANDRES_644_BOARD) +#endif // defined(USE_LAYOUT_FOR_644_BOARD) /* * Program timing, may be adapted to your requirements @@ -302,6 +315,8 @@ uint8_t sBeepTimeoutCounter = 0; #error NO_BEEP_ON_ERROR and ONE_BEEP_ON_ERROR are both defined, which makes no sense! #endif +#define MILLIS_IN_ONE_SECOND 1000L + /* * Page button stuff * @@ -326,14 +341,6 @@ bool readJK_BMSStatusFrame(); void processJK_BMSStatusFrame(); void handleFrameReceiveTimeout(); -#if defined(NO_SERIAL_INFO_PRINT) -#define JK_INFO_PRINT(...) void(); -#define JK_INFO_PRINTLN(...) void(); -#else -#define JK_INFO_PRINT(...) Serial.print(__VA_ARGS__); -#define JK_INFO_PRINTLN(...) Serial.println(__VA_ARGS__); -#endif - #include "HexDump.hpp" #include "digitalWriteFast.h" @@ -367,13 +374,13 @@ bool sTimeoutJustdetected = false; // Is set to true at first detection * CAN stuff */ #if !defined(NO_CAPACITY_35F_EXTENSIONS) // SMA Sunny Island inverters -//#define CAPACITY_35F_EXTENSIONS // Add frame 0x35F for total capacity for SMA Sunny Island inverters, which is no problem for Deye inverters. +#define CAPACITY_35F_EXTENSIONS // Add frame 0x35F for total capacity for SMA Sunny Island inverters, which is no problem for Deye inverters. #endif #if !defined(NO_CAPACITY_379_EXTENSIONS) // Luxpower SNA inverters -//#define CAPACITY_379_EXTENSIONS // Add frame 0x379 for total capacity for Luxpower SNA inverters, which is no problem for Deye inverters. +#define CAPACITY_379_EXTENSIONS // Add frame 0x379 for total capacity for Luxpower SNA inverters, which is no problem for Deye inverters. #endif #if !defined(NO_BYD_LIMITS_373_EXTENSIONS) // BYD -//#define BYD_LIMITS_373_EXTENSIONS // Add frame 0x373 for cell limits as sent by BYD battery, which is no problem for Deye inverters. +#define BYD_LIMITS_373_EXTENSIONS // Add frame 0x373 for cell limits as sent by BYD battery, which is no problem for Deye inverters. #endif #include "Pylontech_CAN.hpp" // Must be before #include "MCP2515_TX.hpp" #define CAN_BAUDRATE 500000 // 500 kB @@ -409,7 +416,7 @@ bool sBMSFrameProcessingComplete = false; // True if one status frame was receiv #if TIMEOUT_MILLIS_FOR_FRAME_REPLY > MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS #error "TIMEOUT_MILLIS_FOR_FRAME_REPLY must be smaller than MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS to detect timeouts" #endif -bool sStaticInfoWasSent = false; // Flag to send static info only once after reset. +bool sInitialActionsPerformed = false; // Flag to send static info only once after reset. void processReceivedData(); void printReceivedData(); @@ -423,10 +430,6 @@ void handleOvervoltage(); #include "LocalDebugLevelStart.h" // no include "LocalDebugLevelEnd.h" required :-) #if defined(STANDALONE_TEST) -//#define LCD_PAGES_TEST -# if defined(LCD_PAGES_TEST) -//#define BIG_NUMBER_TEST -# endif const uint8_t TestJKReplyStatusFrame[] PROGMEM = { /* Header*/0x4E, 0x57, 0x01, 0x2D, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, /*Length of Cell voltages*/ 0x79, 0x30, @@ -492,14 +495,14 @@ delay(4000); // To be able to connect Serial monitor after reset or power up and // Just to know which program is running on my Arduino Serial.println(F("START " __FILE__ "\r\nVersion " VERSION_EXAMPLE " from " __DATE__)); -#if defined(ANDRES_644_BOARD) +#if defined(USE_LAYOUT_FOR_644_BOARD) JK_INFO_PRINTLN(F("Settings are for Andres 644 board")); #endif -#if defined(DISABLE_MONITORING) - JK_INFO_PRINTLN(F("Monitoring disabled")); -#else +#if defined(ENABLE_MONITORING) JK_INFO_PRINTLN(F("Monitoring enabled")); +#else + JK_INFO_PRINTLN(F("Monitoring disabled")); #endif #if defined(NO_CELL_STATISTICS) @@ -517,7 +520,7 @@ delay(4000); // To be able to connect Serial monitor after reset or power up and JK_INFO_PRINTLN(sBatteryESRMilliohm); findFirstSOCDataPointIndex(); -#if defined(ANDRES_644_BOARD) +#if defined(USE_LAYOUT_FOR_644_BOARD) JK_INFO_PRINT(F("EEPROM SOC data start index=")); JK_INFO_PRINT(SOCDataPointsInfo.ArrayStartIndex); JK_INFO_PRINT(F(" length=")); @@ -738,6 +741,7 @@ void loop() { sResponseFrameBytesAreExpected = false; // No response! sBMSFrameProcessingComplete = true; // for LCD timeout etc. processReceivedData(); // for statistics + writeSOCData(); // for analytics tests printBMSDataOnLCD(); // for switching between MAX and MIN display delay(MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS); // do it simple :-) @@ -912,6 +916,21 @@ void processJK_BMSStatusFrame() { JK_INFO_PRINTLN(F("Successfully receiving first BMS status frame after BMS communication timeout")); } processReceivedData(); + + if (!sInitialActionsPerformed) { + /* + * Do initialization once here + */ + sInitialActionsPerformed = true; + initializeComputedData(); + Serial.println(); + printJKStaticInfo(); +#if !defined(NO_ANALYTICS) + initializeAnalytics(); + JK_INFO_PRINTLN(); +#endif + } + printReceivedData(); #if !defined(NO_ANALYTICS) writeSOCData(); @@ -1013,16 +1032,10 @@ void processReceivedData() { sCANDataIsInitialized = true; // One time flag } +/* + * Called exclusively by processJK_BMSStatusFrame() + */ void printReceivedData() { - if (!sStaticInfoWasSent) { - // Send static info only once after reset - sStaticInfoWasSent = true; - initializeComputedData(); -#if !defined(NO_ANALYTICS) - initializeAnalytics(); -#endif - printJKStaticInfo(); - } #if defined(USE_SERIAL_2004_LCD) if (sLCDDisplayPageNumber != JK_BMS_PAGE_CAPACITY_INFO) { // Do not interfere with plotter output @@ -1070,6 +1083,8 @@ void doStandaloneTest() { if (sSerialLCDAvailable) { testLCDPages(); delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); + testPrintFloatValueRightAlignedOnLCD(); + delay(2*LCD_MESSAGE_PERSIST_TIME_MILLIS); # if defined(BIG_NUMBER_TEST) testBigNumbers(); # endif diff --git a/JK-BMSToPylontechCAN/JK-BMS_Analytics.h b/JK-BMSToPylontechCAN/JK-BMS_Analytics.h index 8fad725..ba1e931 100644 --- a/JK-BMSToPylontechCAN/JK-BMS_Analytics.h +++ b/JK-BMSToPylontechCAN/JK-BMS_Analytics.h @@ -50,14 +50,17 @@ struct SOCDataPointsInfoStruct { * Index of next value to be written is ArrayStartIndex + ArrayLength % NUMBER_OF_SOC_DATA_POINTS * => if array is full i.e. ArrayLength == NUMBER_OF_SOC_DATA_POINTS, index of next value to be written is ArrayStartIndex. */ - uint16_t ArrayStartIndex; // Index of first data entry in cyclic SOCDataPointsEEPROMArray. Index of next value to be written if ArrayLength == NUMBER_OF_SOC_DATA_POINTS. - uint16_t ArrayLength; // Length of valid data in Array. Required if not fully written. Maximum is NUMBER_OF_SOC_DATA_POINTS + uint16_t ArrayStartIndex; // Index of first data entry in cyclic SOCDataPointsEEPROMArray. Index of next value to be written if ArrayLength == NUMBER_OF_SOC_DATA_POINTS. + uint16_t ArrayLength; // Length of valid data in Array. Required if not fully written. Maximum is NUMBER_OF_SOC_DATA_POINTS bool currentlyWritingOnAnEvenPage; // If true SOC_EVEN_EEPROM_PAGE_INDICATION_BIT is set in SOCPercent. - uint16_t NumberOfSamples = 0; // For one sample each 2 seconds, we can store up to 36.4 hours here. - long AverageAccumulatorVoltageDifferenceToEmpty10Millivolt = 0; // Serves as accumulator to enable a more smooth graph. - long AverageAccumulator10Milliampere = 0; // Serves as accumulator for AverageAmpere - long DeltaAccumulator10Milliampere = 0; // Serves as accumulator to avoid rounding errors for consecutive data points of Delta100MilliampereHour. 1 Ah is 180,000 => Can hold values of +/-11930 Ah. We can have a residual of up to 18,000 (100 mAh) after write. - long lastWrittenBatteryCapacityAccumulator10Milliampere = 0; + + uint16_t NumberOfSamples; // For one sample each 2 seconds, we can store up to 36.4 hours here. + long AverageAccumulatorVoltageDifferenceToEmpty10Millivolt; // Serves as accumulator to enable a more smooth graph. + long AverageAccumulator10Milliampere; // Serves as accumulator for AverageAmpere + long DeltaAccumulator10Milliampere; // Serves as accumulator to avoid rounding errors for consecutive data points of Delta100MilliampereHour. 1 Ah is 180,000 => Can hold values of +/-11930 Ah. We can have a residual of up to 18,000 (100 mAh) after write. + long lastWrittenBatteryCapacityAsAccumulator10Milliampere; + + uint16_t checksumForReboot; // Checksum of NumberOfSamples up to lastWrittenBatteryCapacityAsAccumulator10Milliampere to decide if we can keep this data at reboot }; extern SOCDataPointsInfoStruct SOCDataPointsInfo; diff --git a/JK-BMSToPylontechCAN/JK-BMS_Analytics.hpp b/JK-BMSToPylontechCAN/JK-BMS_Analytics.hpp index c4decf8..b26a996 100644 --- a/JK-BMSToPylontechCAN/JK-BMS_Analytics.hpp +++ b/JK-BMSToPylontechCAN/JK-BMS_Analytics.hpp @@ -53,10 +53,57 @@ volatile EEMEM uint8_t sFiller3_EEPROM; * Cyclic buffer start is SOCDataPointsInfo.ArrayStartIndex and length (required if not fully written) SOCDataPointsInfo.ArrayLength */ EEMEM SOCDataPointDeltaStruct SOCDataPointsEEPROMArray[NUMBER_OF_SOC_DATA_POINTS]; // 255 for 1 kB EEPROM -SOCDataPointsInfoStruct SOCDataPointsInfo; +SOCDataPointsInfoStruct SOCDataPointsInfo +#ifdef KEEP_ANALYTICS_ACCUMULATED_DATA_AT_RESET +__attribute__((section(".noinit"))) +#endif +; + +/* + * Compute clipped 16 bit sum of 7 16 bit values from SOCDataPointsInfo.NumberOfSamples up to lastWrittenBatteryCapacityAsAccumulator10Milliampere + */ +uint16_t computeSOCDataPointsInfoChecksum() { + uint16_t *tPointer = (uint16_t*) &SOCDataPointsInfo.NumberOfSamples; + uint16_t tChecksum = 0; + do { + tChecksum += *tPointer++; + } while (tPointer < &SOCDataPointsInfo.checksumForReboot); + DEBUG_PRINT(F("CS=")); + DEBUG_PRINTLN(SOCDataPointsInfo.checksumForReboot); + return tChecksum; +} +/* + * Is called only once in loop after receiving of first response + */ void initializeAnalytics() { - SOCDataPointsInfo.lastWrittenBatteryCapacityAccumulator10Milliampere = JKComputedData.BatteryCapacityAccumulator10MilliAmpere; +#ifdef KEEP_ANALYTICS_ACCUMULATED_DATA_AT_RESET + /* + * If computed checksum is equal actual checksum, we assume that we had an reset + * e.g. for displaying the Arduino Plotter graph and can reuse the contents of the accumulators, + * especially NumberOfSamples and DeltaAccumulator10Milliampere + */ + auto tComputedChecksum= computeSOCDataPointsInfoChecksum(); + if (tComputedChecksum != SOCDataPointsInfo.checksumForReboot) { + // Clear only members from NumberOfSamples up to DeltaAccumulator10Milliampere + memset(&SOCDataPointsInfo.NumberOfSamples, 0, 14); + // Reset to computed value based on SOCPercent and TotalCapacityAmpereHour + SOCDataPointsInfo.lastWrittenBatteryCapacityAsAccumulator10Milliampere = + JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere; + JK_INFO_PRINT(tComputedChecksum); + JK_INFO_PRINT(F(" != ")); + JK_INFO_PRINT(SOCDataPointsInfo.checksumForReboot); + JK_INFO_PRINT(F(" -> clear")); + } else { + JK_INFO_PRINT(F("Reboot detected: keep")); + } + JK_INFO_PRINTLN(F(" SOCDataPointsInfo")); + +#else + // Reset to computed value based on SOCPercent and TotalCapacityAmpereHour + SOCDataPointsInfo.lastWrittenBatteryCapacityAsAccumulator10Milliampere = + JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere; +#endif } /* * Just clear the complete EEPROM @@ -106,7 +153,7 @@ void findFirstSOCDataPointIndex() { SOCDataPointsInfo.currentlyWritingOnAnEvenPage = tPageIsEvenFlag; } else if (tStartPageIsEvenFlag ^ tPageIsEvenFlag) { // Here data page changes from even to odd or vice versa -#if defined(ANDRES_644_BOARD) +#if defined(USE_LAYOUT_FOR_644_BOARD) JK_INFO_PRINT(F("Found even/odd toggling before index=")); JK_INFO_PRINTLN(i); #else @@ -123,16 +170,16 @@ void findFirstSOCDataPointIndex() { void setESRMilliohmAndPrintDeltas(uint16_t aVoltToEmptyAccumulatedDeltasESR, uint16_t aVoltToEmptyAccumulatedDeltasNewESR, uint8_t aNewESRMilliohm) { -#if !defined(NO_SERIAL_INFO_PRINT) - Serial.print(F("Delta of ")); - Serial.print(sBatteryESRMilliohm); - Serial.print(F(" mOhm=")); - Serial.print(aVoltToEmptyAccumulatedDeltasESR); - Serial.print(F(", Delta of new ")); - Serial.print(aNewESRMilliohm); - Serial.print(F(" mOhm=")); - Serial.println(aVoltToEmptyAccumulatedDeltasNewESR); -#else + + JK_INFO_PRINT(F("Delta of ")); + JK_INFO_PRINT(sBatteryESRMilliohm); + JK_INFO_PRINT(F(" mOhm=")); + JK_INFO_PRINT(aVoltToEmptyAccumulatedDeltasESR); + JK_INFO_PRINT(F(", Delta of new ")); + JK_INFO_PRINT(aNewESRMilliohm); + JK_INFO_PRINT(F(" mOhm=")); + JK_INFO_PRINTLN(aVoltToEmptyAccumulatedDeltasNewESR); +#if defined(NO_SERIAL_INFO_PRINT) (void) aVoltToEmptyAccumulatedDeltasESR; (void) aVoltToEmptyAccumulatedDeltasNewESR; #endif @@ -464,13 +511,13 @@ void writeSOCData() { */ if (lastJKReply.SOCPercent == 0 && tCurrentSOCPercent == 1) { // Serial.println(F("SOC 0 -> 1 -> reset residual capacity")); - SOCDataPointsInfo.DeltaAccumulator10Milliampere = 0; // reset residual capacity - SOCDataPointsInfo.lastWrittenBatteryCapacityAccumulator10Milliampere = - JKComputedData.BatteryCapacityAccumulator10MilliAmpere; + SOCDataPointsInfo.DeltaAccumulator10Milliampere = 0; // Reset residual capacity + SOCDataPointsInfo.lastWrittenBatteryCapacityAsAccumulator10Milliampere = + JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere; // Reset to computed value based on SOCPercent and TotalCapacityAmpereHour } - uint16_t tSOCDataPointsArrayNextWriteIndex = (SOCDataPointsInfo.ArrayStartIndex - + SOCDataPointsInfo.ArrayLength) % NUMBER_OF_SOC_DATA_POINTS; + uint16_t tSOCDataPointsArrayNextWriteIndex = (SOCDataPointsInfo.ArrayStartIndex + SOCDataPointsInfo.ArrayLength) + % NUMBER_OF_SOC_DATA_POINTS; uint16_t tSOCDataPointsArrayLastWriteIndex; if (tSOCDataPointsArrayNextWriteIndex == 0) { tSOCDataPointsArrayLastWriteIndex = NUMBER_OF_SOC_DATA_POINTS - 1; @@ -489,8 +536,8 @@ void writeSOCData() { bool tExtraCapacityChangedMoreThan1Percent = (tLastWrittenSOCPercent == 100 || tLastWrittenSOCPercent == 0 || (tLastWrittenSOCPercent == 1 && tCurrentSOCPercent != 1)) && abs( - SOCDataPointsInfo.lastWrittenBatteryCapacityAccumulator10Milliampere - - JKComputedData.BatteryCapacityAccumulator10MilliAmpere) + SOCDataPointsInfo.lastWrittenBatteryCapacityAsAccumulator10Milliampere + - JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere) > getOnePercentCapacityAsAccumulator10Milliampere(); if (tExtraCapacityChangedMoreThan1Percent) { @@ -604,14 +651,19 @@ void writeSOCData() { SOCDataPointsInfo.AverageAccumulator10Milliampere = 0; SOCDataPointsInfo.AverageAccumulatorVoltageDifferenceToEmpty10Millivolt = 0; SOCDataPointsInfo.NumberOfSamples = 0; - SOCDataPointsInfo.lastWrittenBatteryCapacityAccumulator10Milliampere = - JKComputedData.BatteryCapacityAccumulator10MilliAmpere; -#if !defined(DISABLE_MONITORING) + SOCDataPointsInfo.lastWrittenBatteryCapacityAsAccumulator10Milliampere = + JKComputedData.BatteryCapacityAsAccumulator10MilliAmpere; +#if defined(ENABLE_MONITORING) // Special monitoring output to generate capacity to cell voltage graphs e.g. with excel printCSVLine('#'); Serial.println(); #endif } + +#ifdef KEEP_ANALYTICS_ACCUMULATED_DATA_AT_RESET + // Update checksum + SOCDataPointsInfo.checksumForReboot = computeSOCDataPointsInfoChecksum(); +#endif } #include "LocalDebugLevelEnd.h" diff --git a/JK-BMSToPylontechCAN/JK-BMS_LCD.hpp b/JK-BMSToPylontechCAN/JK-BMS_LCD.hpp index 2400e71..30d46c8 100644 --- a/JK-BMSToPylontechCAN/JK-BMS_LCD.hpp +++ b/JK-BMSToPylontechCAN/JK-BMS_LCD.hpp @@ -26,15 +26,17 @@ #ifndef _JK_BMS_LCD_HPP #define _JK_BMS_LCD_HPP +#include "LCDPrintUtils.hpp" + /* * LCD hardware stuff */ #define LCD_COLUMNS 20 #define LCD_ROWS 4 -#define LCD_I2C_ADDRESS 0x27 // Default LCD address is 0x27 for a 20 chars and 4 line / 2004 display +#define LCD_I2C_ADDRESS LCD_I2C_DEFAULT_ADDRESS // 0x27 Default LCD address for a 20 chars and 4 line / 2004 display bool sSerialLCDAvailable; -#if defined(DISABLE_MONITORING) && defined(NO_ANALYTICS) +#if !defined(ENABLE_MONITORING) && defined(NO_ANALYTICS) char sStringBuffer[LCD_COLUMNS + 1]; // Only for rendering a LCD row with sprintf_P() #endif /* @@ -58,7 +60,6 @@ bool sSerialLCDIsSwitchedOff = false; uint16_t sFrameCounterForLCDTAutoOff = 0; # endif -#include "LiquidCrystal_I2C.hpp" // This defines USE_SOFT_I2C_MASTER, if SoftI2CMasterConfig.h is available. Use only the modified version delivered with this program! LiquidCrystal_I2C myLCD(LCD_I2C_ADDRESS, LCD_COLUMNS, LCD_ROWS); /* @@ -168,7 +169,6 @@ void printDebugInfoOnLCD() { } # if !defined(DISPLAY_ALWAYS_ON) - /* * Called on button press, BMS communication timeout and new error * Always reset timeout counter! @@ -188,6 +188,7 @@ bool checkAndTurnLCDOn() { } return false; } + /* * Display backlight handling */ @@ -207,18 +208,6 @@ void doLCDBacklightTimeoutHandling() { } # endif -void LCDPrintSpaces(uint8_t aNumberOfSpacesToPrint) { - for (uint_fast8_t i = 0; i < aNumberOfSpacesToPrint; ++i) { - myLCD.print(' '); - } -} - -void LCDClearLine(uint8_t aLineNumber) { - myLCD.setCursor(0, aLineNumber); - LCDPrintSpaces(20); - myLCD.setCursor(0, aLineNumber); -} - void printShortEnableFlagsOnLCD() { if (sJKFAllReplyPointer->ChargeIsEnabled) { myLCD.print('C'); @@ -307,70 +296,6 @@ void printAlarmHexOrStateOnLCD() { } } -/* - * Print current right aligned as 5 character including sign and trailing "A " - * -9.99 - * -99.9 - * -999 - */ -void printCurrent5CharacterRightAlignedOnLCD() { - int16_t tBattery10MilliAmpere = JKComputedData.Battery10MilliAmpere; - if (tBattery10MilliAmpere >= 0) { - myLCD.print(' '); // handle not printed + sign - } - tBattery10MilliAmpere = abs(tBattery10MilliAmpere); // remove sign for length computation - uint8_t tNumberOfDecimalPlaces; - if (tBattery10MilliAmpere < 1000) { - // less than 10 A (1000 * 10mA) - tNumberOfDecimalPlaces = 2; // -9.99 - } else if (tBattery10MilliAmpere < 10000) { - // less than 100 A (10000 * 10mA) - tNumberOfDecimalPlaces = 1; // -99.9 - } else { - myLCD.print(' '); // Handle not printed decimal point . for -999 - Maximum current is 327 A - tNumberOfDecimalPlaces = 0; // -999 - } - myLCD.print(JKComputedData.BatteryLoadCurrentFloat, tNumberOfDecimalPlaces); - myLCD.print(F("A ")); -} - -/* - * Print 3 characters 2.4 or .45 - */ -void printVoltageDifference3CharactersOnLCD() { - int16_t tBatteryToEmptyDifference10Millivolt = JKComputedData.BatteryVoltageDifferenceToEmpty10Millivolt; - if (tBatteryToEmptyDifference10Millivolt < 100) { - // Print small values as ".43" instead of "0.4" - sprintf_P(sStringBuffer, PSTR(".%02d"), tBatteryToEmptyDifference10Millivolt); - myLCD.print(sStringBuffer); - } else { - myLCD.print(tBatteryToEmptyDifference10Millivolt / 100.0, 1); - } -} - -/* - * Print current right aligned as 4 character including sign - * -9.9 - * -99 - * -999 - */ -void printCurrent4CharacterRightAlignedOnLCD() { - int16_t tBattery10MilliAmpere = JKComputedData.Battery10MilliAmpere; - if (tBattery10MilliAmpere >= 0) { - myLCD.print(' '); // handle not printed + sign - } - tBattery10MilliAmpere = abs(tBattery10MilliAmpere); // remove sign for length computation - uint8_t tNumberOfDecimalPlaces = 0; - if (tBattery10MilliAmpere < 1000) { - // less than 10 A (1000 * 10mA) - tNumberOfDecimalPlaces = 1; // -9.9 - } else if (tBattery10MilliAmpere < 10000) { - // less than 100 A (10000 * 10mA) - myLCD.print(' '); // handle not printed decimal point . for -99 - } - myLCD.print(JKComputedData.BatteryLoadCurrentFloat, tNumberOfDecimalPlaces); -} - /* * We can display only up to 16 cell values on the LCD :-( * Print Info in last 3 columns @@ -406,7 +331,7 @@ void printCellInfoOnLCD() { myLCD.print(sStringBuffer); } else if (i == 8) { // Print current in the last 4 characters - printCurrent4CharacterRightAlignedOnLCD(); + printFloatValueRightAlignedOnLCD(JKComputedData.BatteryLoadCurrentFloat, 4); } else if (i == 12) { // print " A " or " A B" myLCD.print(F(" A ")); @@ -436,7 +361,7 @@ void printCellInfoOnLCD() { if (tNumberOfCellInfoEntries > 0 && tNumberOfCellInfoEntries <= 16) { // print voltage difference myLCD.setCursor(17, 3); - printVoltageDifference3CharactersOnLCD(); + printFloatValueRightAlignedOnLCD(JKComputedData.BatteryVoltageDifferenceToEmpty10Millivolt / 100.0, 3, true); // true = no leading space } #else if (tNumberOfCellInfoEntries > 12) { @@ -572,11 +497,11 @@ void printBigInfoOnLCD() { } myLCD.setCursor(tColumn, UNITS_ROW_FOR_BIG_INFO); // 3, 6 or 8 - myLCD.print('%'); + myLCD.print('%'); // print "small" percent sign /* - * Here we can start the power string at column 4, 7 or 9 + * Here we can start the big number power string at column 4, 7 or 9 */ - uint8_t tAvailableColumns = (LCD_COLUMNS - 2) - tColumn; + uint8_t tAvailableColumns = (LCD_COLUMNS - 2) - tColumn; // 14, 11, 9. -2 for the trailing W or KW char tKiloWattChar = ' '; int16_t tBatteryLoadPower = JKComputedData.BatteryLoadPower; /* @@ -589,6 +514,7 @@ void printBigInfoOnLCD() { } else { sprintf_P(sStringBuffer, PSTR("%d"), JKComputedData.BatteryLoadPower); } + /* * Then compute maximum possible string length */ @@ -643,13 +569,13 @@ void printBigInfoOnLCD() { */ myLCD.setCursor(0, 3); myLCD.print(JKComputedData.TemperatureMaximum); - myLCD.print(F("\xDF ")); + myLCD.print(F(DEGREE_SIGN_STRING " ")); myLCD.setCursor(4, 3); - printCurrent5CharacterRightAlignedOnLCD(); + printFloatValueRightAlignedOnLCD(JKComputedData.BatteryLoadCurrentFloat, 5); + myLCD.print('A'); - myLCD.setCursor(11, 3); - myLCD.print(JKComputedData.BatteryVoltageDifferenceToEmpty10Millivolt / 100.0, 2); + printFloatValueRightAlignedOnLCD(JKComputedData.BatteryVoltageDifferenceToEmpty10Millivolt / 100.0, 5); myLCD.print('V'); myLCD.setCursor(17, 3); // Last 3 characters are the actual states @@ -701,57 +627,45 @@ void printCANInfoOnLCD() { * Voltage, current and maximum temperature in row 3 */ myLCD.setCursor(0, 2); - // Voltage - myLCD.print(PylontechCANCurrentValuesFrame356.FrameData.Voltage10Millivolt / 100); - myLCD.print('.'); - myLCD.print(PylontechCANCurrentValuesFrame356.FrameData.Voltage10Millivolt % 100); +// Voltage + printFloatValueRightAlignedOnLCD(PylontechCANCurrentValuesFrame356.FrameData.Voltage10Millivolt / 100.0, 5, true); myLCD.print(F("V ")); - // Current - int16_t tCurrent100Milliampere = PylontechCANCurrentValuesFrame356.FrameData.Current100Milliampere; - myLCD.print(tCurrent100Milliampere / 10); - if (tCurrent100Milliampere < 0) { - // avoid negative numbers after decimal point - tCurrent100Milliampere = -tCurrent100Milliampere; - } - if (tCurrent100Milliampere < 100) { - // Print fraction if value < 100 - myLCD.print('.'); - myLCD.print(tCurrent100Milliampere % 10); - } + +// Current + printFloatValueRightAlignedOnLCD(PylontechCANCurrentValuesFrame356.FrameData.Current100Milliampere / 10.0, 5); myLCD.print(F("A ")); - // Temperature - myLCD.setCursor(14, 2); + +// Temperature, we have only a 1 degree resolution here myLCD.print(PylontechCANCurrentValuesFrame356.FrameData.Temperature100Millicelsius / 10); - if (PylontechCANCurrentValuesFrame356.FrameData.Temperature100Millicelsius < 100) { - // Print fraction if value < 100 - myLCD.print('.'); - myLCD.print(PylontechCANCurrentValuesFrame356.FrameData.Temperature100Millicelsius % 10); - } - myLCD.print(F("\xDF" "C ")); + myLCD.print(F(DEGREE_SIGN_STRING "C ")); /* * Request flags in row 4 */ myLCD.setCursor(0, 3); - // Charge enable +// Charge enable if (PylontechCANBatteryRequestFrame35C.FrameData.ChargeEnable) { myLCD.print(F("CH ")); } else { myLCD.print(F(" ")); } if (PylontechCANBatteryRequestFrame35C.FrameData.DischargeEnable) { - myLCD.print(F("DC")); + myLCD.print(F("DC ")); + } else { + myLCD.print(F(" ")); } - myLCD.setCursor(6, 3); if (PylontechCANBatteryRequestFrame35C.FrameData.ForceChargeRequestI) { - myLCD.print(F("FORCEI")); + myLCD.print(F("FORCEI ")); + } else { + myLCD.print(F(" ")); } - myLCD.setCursor(13, 3); if (PylontechCANBatteryRequestFrame35C.FrameData.ForceChargeRequestII) { myLCD.print(F("FORCEII")); + } else { + myLCD.print(F(" ")); } - // Currently constant 0 +// Currently constant 0 // myLCD.setCursor(10, 3); // if (PylontechCANBatteryRequestFrame35C.FrameData.FullChargeRequest) { // myLCD.print(F("FULL")); @@ -765,20 +679,17 @@ void printCANInfoOnLCD() { void printVoltageCurrentAndPowerOnLCD() { myLCD.setCursor(0, 2); // Voltage - myLCD.print(JKComputedData.BatteryVoltageFloat, 2); +// myLCD.print(JKComputedData.BatteryVoltageFloat, 2); // currently requires more programming space + printFloatValueRightAlignedOnLCD(JKComputedData.BatteryVoltageFloat, 5, true); // true -> do not print leading space myLCD.print(F("V ")); + // Current - printCurrent5CharacterRightAlignedOnLCD(); + printFloatValueRightAlignedOnLCD(JKComputedData.BatteryLoadCurrentFloat, 5); + myLCD.print('A'); + // Power - if (JKComputedData.BatteryLoadPower < -10000) { - // over 10 kW - myLCD.setCursor(13, 2); - myLCD.print(JKComputedData.BatteryLoadPower); // requires 6 columns - } else { - myLCD.setCursor(14, 2); - sprintf_P(sStringBuffer, PSTR("%5d"), JKComputedData.BatteryLoadPower); // force use of 5 columns - myLCD.print(sStringBuffer); - } + sprintf_P(sStringBuffer, PSTR("%6d"), JKComputedData.BatteryLoadPower); // force use of 6 columns + myLCD.print(sStringBuffer); myLCD.print('W'); } @@ -797,21 +708,21 @@ void printVoltageDifferenceAndTemperature() { && abs(JKComputedData.TemperatureSensor1 - JKComputedData.TemperatureSensor2) < (min(JKComputedData.TemperatureSensor1, JKComputedData.TemperatureSensor1) / 4)) { tShowBothExternalTemperatures = false; - myLCD.print(JKComputedData.BatteryVoltageDifferenceToEmpty10Millivolt / 100.0, 2); + printFloatValueRightAlignedOnLCD(JKComputedData.BatteryVoltageDifferenceToEmpty10Millivolt / 100.0, 5, true); // true = no leading space myLCD.print(F("V ")); } myLCD.print(JKComputedData.TemperaturePowerMosFet); - myLCD.print(F("\xDF" "C ")); + myLCD.print(F(DEGREE_SIGN_STRING "C ")); if (tShowBothExternalTemperatures) { - // show both external sensors +// show both external sensors myLCD.print(JKComputedData.TemperatureSensor1); - myLCD.print(F("\xDF" "C ")); + myLCD.print(F(DEGREE_SIGN_STRING "C ")); myLCD.print(JKComputedData.TemperatureSensor2); - myLCD.print(F("\xDF" "C ")); + myLCD.print(F(DEGREE_SIGN_STRING "C ")); } else { - // show maximum of the 2 external sensors +// show maximum of the 2 external sensors myLCD.print(max(JKComputedData.TemperatureSensor1, JKComputedData.TemperatureSensor2)); - myLCD.print(F("\xDF" "C ")); + myLCD.print(F(DEGREE_SIGN_STRING "C ")); } } @@ -861,12 +772,12 @@ void printAlarmInfoOnLCD() { tCellIndexToPrint = JKConvertedCellInfo.IndexOfMaximumCellMillivolt; tCellMillivoltToPrint = JKConvertedCellInfo.MaximumCellMillivolt; } - // print millivolt with fix format 3 character value +// print millivolt with fix format 3 character value sprintf_P(sStringBuffer, PSTR("%2d %3dmV "), tCellIndexToPrint + 1, tCellMillivoltToPrint - 3000); myLCD.print(sStringBuffer); } else if (strlen_P(tLastAlarmString) > LCD_COLUMNS) { - // remainder of alarm string +// remainder of alarm string strncpy_P(sStringBuffer, &tLastAlarmString[LCD_COLUMNS], LCD_COLUMNS - LENGTH_OF_UPTIME_STRING); // 11 = Length of uptime string sStringBuffer[LCD_COLUMNS - LENGTH_OF_UPTIME_STRING] = '\0'; myLCD.print(sStringBuffer); @@ -907,11 +818,9 @@ void printOverwiewOrAlarmInfoOnLCD() { myLCD.print(sJKFAllReplyPointer->SOCPercent); myLCD.print(F("% ")); // Remaining capacity - myLCD.print(JKComputedData.RemainingCapacityAmpereHour); + sprintf_P(sStringBuffer, PSTR("%3d"), JKComputedData.RemainingCapacityAmpereHour); + myLCD.print(sStringBuffer); myLCD.print(F("Ah ")); - if (JKComputedData.RemainingCapacityAmpereHour < 100) { - myLCD.print(' '); - } // Last 3 characters are the enable states myLCD.setCursor(14, 1); myLCD.print(F("En:")); @@ -955,7 +864,7 @@ void printBMSDataOnLCD() { # endif } - // do not clear alarm info, which is only printed once +// do not clear alarm info, which is only printed once if (!sShowAlarmInsteadOfOverview) { myLCD.clear(); } @@ -1160,7 +1069,7 @@ void setLCDDisplayPage(uint8_t aLCDDisplayPageNumber, bool aDoNotPrint) { void testLCDPages() { sLCDDisplayPageNumber = JK_BMS_PAGE_OVERVIEW; printBMSDataOnLCD(); - delay (LCD_MESSAGE_PERSIST_TIME_MILLIS); + delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); sLCDDisplayPageNumber = JK_BMS_PAGE_CELL_INFO; // Create symbols character for maximum and minimum @@ -1173,6 +1082,7 @@ void testLCDPages() { printBMSDataOnLCD(); delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); + Serial.println(F("Test alarms")); /* * Test alarms */ @@ -1203,7 +1113,9 @@ void testLCDPages() { // reset alarm sAlarmIndexToShowOnLCD = INDEX_NO_ALARM; sAlarmJustGetsActive = false; + sShowAlarmInsteadOfOverview = false; + Serial.println(F("Test maximum values")); /* * Check display of maximum values */ @@ -1217,9 +1129,10 @@ void testLCDPages() { delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); /* - * maximum values at overview + * maximum values at Big Info */ sLCDDisplayPageNumber = JK_BMS_PAGE_BIG_INFO; + myLCD.clear(); bigNumberLCD.begin(); printBMSDataOnLCD(); delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); @@ -1236,6 +1149,7 @@ void testLCDPages() { delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); sLCDDisplayPageNumber = JK_BMS_PAGE_BIG_INFO; + myLCD.clear(); printBMSDataOnLCD(); delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); @@ -1247,8 +1161,9 @@ void testLCDPages() { } void testBigNumbers() { + Serial.println(F("Test BigNumbers")); + sLCDDisplayPageNumber = JK_BMS_PAGE_BIG_INFO; - myLCD.clear(); for (int j = 0; j < 3; ++j) { // Test with 100 % and 42 % @@ -1261,6 +1176,7 @@ void testBigNumbers() { for (int i = 0; i < 5; ++i) { delay(2 * LCD_MESSAGE_PERSIST_TIME_MILLIS); + myLCD.clear(); printBMSDataOnLCD(); JKComputedData.BatteryLoadPower /= 10; // 1234 -> 12 JKComputedData.BatteryLoadCurrentFloat = JKComputedData.BatteryLoadPower / JKComputedData.BatteryVoltageFloat; @@ -1273,6 +1189,7 @@ void testBigNumbers() { for (int i = 0; i < 5; ++i) { delay(2 * LCD_MESSAGE_PERSIST_TIME_MILLIS); + myLCD.clear(); printBMSDataOnLCD(); JKComputedData.BatteryLoadPower /= 10; // 1234 -> 12 JKComputedData.BatteryLoadCurrentFloat = JKComputedData.BatteryLoadPower / JKComputedData.BatteryVoltageFloat; diff --git a/JK-BMSToPylontechCAN/LCDPrintUtils.hpp b/JK-BMSToPylontechCAN/LCDPrintUtils.hpp new file mode 100644 index 0000000..64257e4 --- /dev/null +++ b/JK-BMSToPylontechCAN/LCDPrintUtils.hpp @@ -0,0 +1,232 @@ +/* + * LCDPrintUtils.hpp + * + * Contains LCD related variables and functions + * + * Copyright (C) 2024 Armin Joachimsmeyer + * Email: armin.joachimsmeyer@gmail.com + * + * This file is part of ArduinoUtils https://github.com/ArminJo/JK-BMSToPylontechCAN. + * + * JK-BMSToPylontechCAN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef _LCD_PRINT_UTILS_HPP +#define _LCD_PRINT_UTILS_HPP + +//#define LOCAL_DEBUG // This enables debug output only for this file - only for development + +/* + * Helper macro for getting a macro definition as string + */ +#if !defined(STR_HELPER) +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) +#endif + +#define LCD_I2C_DEFAULT_ADDRESS 0x27 // Default LCD address is 0x27 for a I2C adaptor with PCF8574 + +#define DEGREE_SIGN_STRING "\xDF" + +#if !defined(LCD_COLUMNS) +#define LCD_COLUMNS 20 +#endif +extern char sStringBuffer[]; // For rendering a LCD row with sprintf_P() + +#include "LiquidCrystal_I2C.hpp" // This defines USE_SOFT_I2C_MASTER, if SoftI2CMasterConfig.h is available. Use only the modified version delivered with this program! +extern LiquidCrystal_I2C myLCD; + +void LCDPrintSpaces(uint8_t aNumberOfSpacesToPrint); +void LCDClearLine(uint8_t aLineNumber); +uint8_t getNumberOfDecimalsFor16BitValues(uint16_t a16BitValue); +void printFloatValueRightAlignedOnLCD(float aFloatValue, uint8_t aNumberOfCharactersToPrint, bool aNoLeadingSpaceForPositiveValues = + false); +void LCDPrintSpaces(uint8_t aNumberOfSpacesToPrint) { + for (uint_fast8_t i = 0; i < aNumberOfSpacesToPrint; ++i) { + myLCD.print(' '); + } +} + +void LCDClearLine(uint8_t aLineNumber) { + myLCD.setCursor(0, aLineNumber); + LCDPrintSpaces(20); + myLCD.setCursor(0, aLineNumber); +} + +/* + * !!! We internally use uint16_t, for bigger values we have an overflow. + */ +uint8_t getNumberOfDecimalsFor16BitValues(uint16_t a16BitValue) { + uint16_t tCompareValue = 1; + /* + * Check for 10, 100, 1000 + */ + for (uint_fast8_t tNumberOfDecimals = 0; tNumberOfDecimals < 5; ++tNumberOfDecimals) { + if (a16BitValue < tCompareValue) { + return tNumberOfDecimals; + } + tCompareValue *= 10; + } + // here we have values >= 10000 + return 5; +} + +/* + * !!! we internally use uint32_t, for bigger values we have an overflow. + * Requires 26 bytes more program space than getNumberOfDecimalsFor16BitValues() + */ +uint8_t getNumberOfDecimalsFor32BitValues(uint32_t a32BitValue) { + uint_fast8_t tNumberOfDecimals = 1; + uint32_t tCompareValue = 10; + /* + * Check for 10, 100, 1000 up to 100,000,000 + */ + for (; tNumberOfDecimals < 10; ++tNumberOfDecimals) { + if (a32BitValue < tCompareValue) { + return tNumberOfDecimals; + } + tCompareValue *= 10; + } + // here we have values >= 1,000,000 + return 10; +} +/* + * !!! We internally use uint16_t, for bigger values (> 65,536 or < -65,536) we have an overflow. + * + * @param aNumberOfCharactersToPrint - The characters to be used for the most negative value to show. + * I.e. 4 => max negative value is "-999" max positive value is " 999". + * Values below 10 and -10 are displayed as floats with decimal point " 9.9" and "-9.9". + * Values below 1 and -1 are displayed as floats without 0 before decimal point " .9" and "-.9". + * If positive, there is a leading space, which improves readability if directly concatenated to another value. + * e.g. "71V1.189A" is not readable, "71V 1.18A" is as well as "71V-1.18A". + * + * Saves programming space if used more than 1 times for printing floats if used instead of myLCD.print(JKComputedData.BatteryVoltageFloat, 2); + */ +void printFloatValueRightAlignedOnLCD(float aFloatValue, uint8_t aNumberOfCharactersToPrint, + bool aNoLeadingSpaceForPositiveValues) { + + uint16_t tAbsValue = abs(aFloatValue); // remove sign for length computation + uint8_t tNumberOfDecimals = getNumberOfDecimalsFor16BitValues(tAbsValue); + int8_t tNumberOfDecimalPlaces = (aNumberOfCharactersToPrint - 2) - tNumberOfDecimals; + if (aNoLeadingSpaceForPositiveValues && aFloatValue >= 0) { + tNumberOfDecimalPlaces++; // Use this increased value internally, since we do not eventually print the '-' + } +#if defined(LOCAL_DEBUG) + Serial.print(F("NumberOfDecimalPlaces(")); + Serial.print(tAbsValue); + Serial.print(F(", ")); + Serial.print(aNumberOfCharactersToPrint); + Serial.print(F(")=")); + Serial.print(tNumberOfDecimalPlaces); + Serial.print(F(" NumberOfDecimals=")); + Serial.println(tNumberOfDecimals); +#endif + if (tNumberOfDecimalPlaces < 0) { + tNumberOfDecimalPlaces = 0; + } + + char *tStartOfString = sStringBuffer; + if (tNumberOfDecimals == 0 && tNumberOfDecimalPlaces > 0) { + if (aFloatValue >= 0) { + if (!aNoLeadingSpaceForPositiveValues) { + myLCD.print(' '); + } + tStartOfString = &sStringBuffer[1]; + } else { + myLCD.print('-'); + tStartOfString = &sStringBuffer[2]; + } + } + dtostrf(aFloatValue, aNumberOfCharactersToPrint, tNumberOfDecimalPlaces, sStringBuffer); + myLCD.print(tStartOfString); +} + +void testPrintFloatValueRightAlignedOnLCD() { + myLCD.clear(); + float tTestValue = 123.45; + printFloatValueRightAlignedOnLCD(tTestValue, 6); + printFloatValueRightAlignedOnLCD(tTestValue, 5); + printFloatValueRightAlignedOnLCD(tTestValue, 4); // no leading space here + printFloatValueRightAlignedOnLCD(tTestValue, 3); // no leading space here + // Result=" 123.4 123 123123" + + myLCD.setCursor(0, 1); + printFloatValueRightAlignedOnLCD(-tTestValue, 6); + printFloatValueRightAlignedOnLCD(-tTestValue, 5); + printFloatValueRightAlignedOnLCD(-tTestValue, 4); + printFloatValueRightAlignedOnLCD(-tTestValue, 3); // requires also 5 character + // Result="-123.4 -123-123-123" + + myLCD.setCursor(0, 2); + tTestValue = -1.234; + printFloatValueRightAlignedOnLCD(tTestValue, 6); + printFloatValueRightAlignedOnLCD(tTestValue, 5); + printFloatValueRightAlignedOnLCD(tTestValue, 4); + printFloatValueRightAlignedOnLCD(tTestValue, 3); + printFloatValueRightAlignedOnLCD(tTestValue, 2); + // Result="-1.234-1.23-1.2 -1-1" + + myLCD.setCursor(0, 3); + tTestValue = -0.1234; + printFloatValueRightAlignedOnLCD(tTestValue, 6); + printFloatValueRightAlignedOnLCD(tTestValue, 5); + printFloatValueRightAlignedOnLCD(tTestValue, 4); + printFloatValueRightAlignedOnLCD(tTestValue, 3); + printFloatValueRightAlignedOnLCD(tTestValue, 2); + // Result="-.1234-.123-.12-.1-0" + + delay(4000); + + myLCD.clear(); + tTestValue = 123.45; + // no leading space here + printFloatValueRightAlignedOnLCD(tTestValue, 6, true); + printFloatValueRightAlignedOnLCD(tTestValue, 5, true); + printFloatValueRightAlignedOnLCD(tTestValue, 4, true); + printFloatValueRightAlignedOnLCD(tTestValue, 3, true); + // Result="123.45123.4 123123" + + myLCD.setCursor(0, 1); + printFloatValueRightAlignedOnLCD(-tTestValue, 6, true); + printFloatValueRightAlignedOnLCD(-tTestValue, 5, true); + printFloatValueRightAlignedOnLCD(-tTestValue, 4, true); + printFloatValueRightAlignedOnLCD(-tTestValue, 3, true); + // Result="-123.4 -123-123-123" + + tTestValue = 1.2344; // .12345 leads to rounding up for .1235 + myLCD.setCursor(0, 2); + printFloatValueRightAlignedOnLCD(tTestValue, 6, true); + printFloatValueRightAlignedOnLCD(tTestValue, 5, true); + printFloatValueRightAlignedOnLCD(tTestValue, 4, true); + printFloatValueRightAlignedOnLCD(tTestValue, 3, true); + printFloatValueRightAlignedOnLCD(tTestValue, 2, true); + // Result="1.23441.2341.231.2 1" + + tTestValue = 0.12344; // .12345 leads to rounding up for .1235 + myLCD.setCursor(0, 3); + printFloatValueRightAlignedOnLCD(tTestValue, 6, true); + printFloatValueRightAlignedOnLCD(tTestValue, 5, true); + printFloatValueRightAlignedOnLCD(tTestValue, 4, true); + printFloatValueRightAlignedOnLCD(tTestValue, 3, true); + printFloatValueRightAlignedOnLCD(tTestValue, 2, true); + // Result=".12344.1234.123.12.1" + + delay(4000); +} + +#if defined(LOCAL_DEBUG) +#undef LOCAL_DEBUG +#endif +#endif // _LCD_PRINT_UTILS_HPP diff --git a/JK-BMSToPylontechCAN/LiquidCrystal_I2C.hpp b/JK-BMSToPylontechCAN/LiquidCrystal_I2C.hpp index 3369306..7ac4cea 100644 --- a/JK-BMSToPylontechCAN/LiquidCrystal_I2C.hpp +++ b/JK-BMSToPylontechCAN/LiquidCrystal_I2C.hpp @@ -1,3 +1,4 @@ +// LiquidCrystal_I2C.hpp // Based on the work by DFRobot /* * Extensions made by AJ 2023 @@ -7,6 +8,8 @@ * Added createChar() with PROGMEM input * Added fast timing */ +#ifndef _LIQUID_CRYSTAL_I2C_HPP +#define _LIQUID_CRYSTAL_I2C_HPP #include "Arduino.h" @@ -369,3 +372,4 @@ void LiquidCrystal_I2C::setContrast(uint8_t new_val) { } #pragma GCC diagnostic pop +#endif // _LIQUID_CRYSTAL_I2C_HPP diff --git a/JK-BMSToPylontechCAN/Pylontech_CAN.h b/JK-BMSToPylontechCAN/Pylontech_CAN.h index 9e5620d..80c6219 100644 --- a/JK-BMSToPylontechCAN/Pylontech_CAN.h +++ b/JK-BMSToPylontechCAN/Pylontech_CAN.h @@ -126,7 +126,7 @@ struct PylontechCANSohSocFrame355Struct { uint16_t SOCPercent; uint16_t SOHPercent = 100; // fixed 100 #if defined(CAPACITY_35F_EXTENSIONS) -// uint16_t SOCHighDefinition100PPM = 10000; // for SMA Sunny Island inverters + uint16_t SOCHighDefinition100PPM = 10000; // for SMA Sunny Island inverters #endif } FrameData; void fillFrame(struct JKReplyStruct *aJKFAllReply) { @@ -278,7 +278,7 @@ struct PylontechCANBatteryRequesFrame35CStruct { if (aJKFAllReply->SOCPercent < SOC_THRESHOLD_FOR_FORCE_CHARGE_REQUEST_I) { // ForceChargeRequestI forces the inverter to charge the battery from any available power source regardless of inverter settings FrameData.ForceChargeRequestI = 1; - } else + } else { FrameData.ForceChargeRequestI = 0; } #else diff --git a/README.md b/README.md index d803f21..48efa75 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ The JK-BMS RS485 data (e.g. at connector GPS) are provided as RS232 TTL with 115 - The voltage in the SOC graph is corrected by the automatically computed ESR to get a smoother voltage curve. **If the Aduino IDE complains about more than 100% of program storage space, burn the Uno Bootloader on your Nano, if not already done, and select the Uno as board. The Arduino Nano board definition has a [wrong "upload.maximum_size" value](https://github.com/arduino/ArduinoCore-avr/pull/546).**
-Enabling the macro `NO_SERIAL_INFO_PRINT` saves program space. +Look [here](https://github.com/ArminJo/JK-BMSToPylontechCAN/blob/main/JK-BMSToPylontechCAN/JK-BMSToPylontechCAN.ino#L139) for options to reduce program size / add optional features. **On a MCP2515 / TJA1050 kit for Arduino you must [replace the assembled 8 MHz crystal with a 16 MHz one](https://www.mittns.de/thread/1340-mcp2515-8mhz-auf-16mhz-upgrade/).** @@ -253,17 +253,15 @@ Modify them by enabling / disabling them, or change the values if applicable. | `DISPLAY_ON_TIME_SECONDS` | 300 | 300 s / 5 min after the last button press, the backlight of the LCD display is switched off. | | `DISPLAY_ON_TIME_SECONDS_IF_TIMEOUT` | 180 | 180 s / 3 min after the first timeout / BMS shutdown, the backlight of the LCD display is switched off. | | `USE_NO_COMMUNICATION_STATUS_LEDS` | disabled | If activated, the code for the BMS and CAN communication status LED is deactivated and the pins are not switched to output. | -| `NO_SERIAL_INFO_PRINT` | enabled | Disables writing some info to serial output. Saves 974 bytes program space. | -| `DISABLE_MONITORING` | enabled | Disables writing cell and current values CSV data to serial output. Saves 534 bytes program space. | -| `NO_CELL_STATISTICS` | disabled | Disables generating and display of cell balancing statistics. Saves 16558 bytes program space. | +| `SERIAL_INFO_PRINT` | disabled | Enables writing some info to serial output. Requires additional 1684 bytes program space. | +| `ENABLE_MONITORING` | enabled for more than 32k FLASH | Enables writing cell and current values CSV data to serial output. Requires additional 858 bytes program space. | +| `NO_CELL_STATISTICS` | disabled | Disables generating and display of cell balancing statistics. Saves 1628 bytes program space. | | `NO_ANALYTICS` | disabled | Disables generating, storing and display of SOC graph for Arduino Serial Plotter. Saves 3882 bytes program space. | | `STANDALONE_TEST` | disabled | If activated, fixed BMS data is sent to CAN bus and displayed on LCD. | -| `NO_CAPACITY_35F_EXTENSIONS` | disabled | If activated, supress sending of frame 0x35F for total capacity for SMA Sunny Island inverters over CAN. | -| `NO_CAPACITY_379_EXTENSIONS` | disabled | If activated, supress sending of frame 0x379 for total capacity for Luxpower SNA inverters over CAN. | -| `NO_BYD_LIMITS_373_EXTENSIONS` | disabled | If activated, supress sending of frame 0x373 for cell limits as sent by BYD battery over CAN. | -| `DO_NOT_SHOW_SHORT_CELL_VOLTAGES` | disabled | If activated, do not print 3 digits cell voltage (value - 3.0 V) on Cell Info page. Disables display of up to 20 voltages or display of additional information on this page. | -| `DISABLE_MONITORING` | disabled | If activated, no cell and current values CSV data are written to serial output -. | +| `NO_CAPACITY_35F_EXTENSIONS` | disabled | If activated, supress sending of frame 0x35F for total capacity for SMA Sunny Island inverters over CAN. Saves 56 bytes program space. | +| `NO_CAPACITY_379_EXTENSIONS` | disabled | If activated, supress sending of frame 0x379 for total capacity for Luxpower SNA inverters over CAN. Saves 24 bytes program space. | +| `NO_BYD_LIMITS_373_EXTENSIONS` | disabled | If activated, supress sending of frame 0x373 for cell limits as sent by BYD battery over CAN. Saves 200 bytes program space. | +| `DO_NOT_SHOW_SHORT_CELL_VOLTAGES` | disabled | If activated, do not print 3 digits cell voltage (value - 3.0 V) on Cell Info page. Disables display of up to 20 voltages or display of additional information on this page. Saves 470 bytes program space. | | | | | | `CAN_DATA_MODIFICATION` | disabled | If activated, it currently enables the function to reduce max current at high SOC level. | | `MAX_CURRENT_MODIFICATION_LOWER_SOC_`
`THRESHOLD_PERCENT` | 80 | Start SOC for linear reducing maximum current. | @@ -322,6 +320,7 @@ This program uses the following libraries, which are already included in this re ### Version 3.2.0 - Adaption for ATmega644. - Automatic ESR computation only for more than 100 data points. +- Keep accumulated analytics data at reset. ### Version 3.1.0 - Analytics bug fixing. @@ -385,11 +384,13 @@ See also [here](https://github.com/ArminJo/JK-BMSToPylontechCAN/tree/main/extras ``` START ../src/JK-BMSToPylontechCAN.cpp Version 2.0 from Sep 8 2023 -Serial to JK-BMS started with 115200 bit/s! +Serial to JK-BMS started with 115.200 kbit/s! CAN started with 500 kbit/s! -If you connect debug pin 3 to ground, additional debug data is printed +Page switching button is at pin PAGE_BUTTON_PIN +At long press, CAN Info page is entered and additional debug data is printed as long as button is pressed 2000 ms between 2 BMS requests 2000 ms between 2 CAN transmissions +No LCD Backlight timeout *** BMS INFO *** Protocol Version Number=1 @@ -434,6 +435,8 @@ Dedicated Charge Switch Active=0 Start Current Calibration State=0 Battery Actual Capacity[Ah]=115 +Reboot detected: keep SOCDataPointsInfo + Total Runtime Minutes=49260 -> 34D05H00M *** CELL INFO *** 16 Cells, Minimum=3397 mV, Maximum=3408mV, Delta=11 mV, Average=3403 mV