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..c827f8b 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. @@ -124,10 +119,6 @@ */ //#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) @@ -137,35 +128,54 @@ # 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 +198,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 +208,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 +228,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 +248,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 +261,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 +274,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 +311,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 +337,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 +370,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 +412,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(); @@ -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 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..7ac0538 100644 --- a/JK-BMSToPylontechCAN/JK-BMS_LCD.hpp +++ b/JK-BMSToPylontechCAN/JK-BMS_LCD.hpp @@ -34,7 +34,7 @@ #define LCD_I2C_ADDRESS 0x27 // Default LCD address is 0x27 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 /* 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