├── .github └── workflows │ └── TestCompile.yml ├── JK-BMSToPylontechCAN ├── ADCUtils.h ├── AVRUtils.cpp ├── AVRUtils.h ├── EasyButtonAtInt01.h ├── EasyButtonAtInt01.hpp ├── HexDump.hpp ├── JK-BMS.h ├── JK-BMS.hpp ├── JK-BMSToPylontechCAN.ino ├── JK-BMS_Analytics.h ├── JK-BMS_Analytics.hpp ├── JK-BMS_LCD.h ├── JK-BMS_LCD.hpp ├── LCDBigNumbers.hpp ├── LCDPrintUtils.hpp ├── LiquidCrystal_I2C.h ├── LiquidCrystal_I2C.hpp ├── LocalDebugLevelCheck.h ├── LocalDebugLevelEnd.h ├── LocalDebugLevelStart.h ├── LongUnion.h ├── MCP2515_TX.h ├── MCP2515_TX.hpp ├── Pylontech_CAN.h ├── Pylontech_CAN.hpp ├── SPI_.cpp ├── SPI_.h ├── SoftI2CMaster.h ├── SoftI2CMasterConfig.h ├── SoftwareSerialTX.cpp ├── SoftwareSerialTX.h ├── digitalWriteFast.h └── mcp2515_can_dfs.h ├── LICENSE.txt ├── README.md ├── extras ├── CellUndervoltage.log ├── JK-BMS.log ├── SOCDataESR.png ├── SOCDataNew.log └── SOCDataNoESR.png └── pictures ├── 644Board.jpg ├── AlarmPage_MosFetOvertemperature.png ├── AlarmPage_Undervoltage.png ├── AutomaticBrightness.jpg ├── BMS-CAN_PCB_top_v0.1.png ├── BigInfoPage.png ├── BreadboardAndOverviewPage.jpg ├── BreadboardDetail.jpg ├── BreadboardSimple.jpg ├── CANInfoPage.png ├── CapacityStatisticsPercentagePage.png ├── CapacityStatisticsVoltagePage.png ├── CellInfoPage.png ├── CellInfoPage_shortVoltages.png ├── CompleteInstallation.jpg ├── EasyEda_shematics_by_Ngoc_Dang_Dinh.png ├── NanoBottom.jpg ├── NanoTop.jpg ├── NoBreadboardOverview1.jpg ├── NoBreadboardOverview2.jpg ├── OverviewPage.png ├── StandaloneTest.jpg ├── StatisticsMaxPage.png └── StatisticsMinPage.png /.github/workflows/TestCompile.yml: -------------------------------------------------------------------------------- 1 | # TestCompile.yml 2 | # Github workflow script to test compile all examples of an Arduino library repository. 3 | # 4 | # Copyright (C) 2020-2022 Armin Joachimsmeyer 5 | # https://github.com/ArminJo/Github-Actions 6 | # 7 | 8 | # This is the name of the workflow, visible on GitHub UI. 9 | name: TestCompile 10 | on: 11 | workflow_dispatch: # To run it manually 12 | description: 'manual build check' 13 | push: 14 | paths: 15 | - '**.ino' 16 | - '**.cpp' 17 | - '**.hpp' 18 | - '**.h' 19 | - '**TestCompile.yml' 20 | jobs: 21 | build: 22 | name: Test compiling examples for Nano / Uno 23 | runs-on: ubuntu-latest 24 | 25 | strategy: 26 | matrix: 27 | arduino-boards-fqbn: 28 | - arduino:avr:nano 29 | - arduino:avr:uno 30 | - arduino:avr:uno|STANDALONE_TEST 31 | - arduino:avr:uno|DEBUG 32 | - arduino:avr:uno|DISPLAY_ALWAYS_ON 33 | - arduino:avr:uno|USE_NO_LCD 34 | - MightyCore:avr:644 35 | 36 | include: 37 | - arduino-boards-fqbn: arduino:avr:uno|STANDALONE_TEST 38 | build-properties: 39 | All: -DSTANDALONE_TEST 40 | 41 | - arduino-boards-fqbn: arduino:avr:uno|DEBUG 42 | build-properties: 43 | All: -DDEBUG 44 | 45 | - arduino-boards-fqbn: arduino:avr:uno|DISPLAY_ALWAYS_ON 46 | build-properties: 47 | All: -DDISPLAY_ALWAYS_ON 48 | 49 | - arduino-boards-fqbn: arduino:avr:uno|USE_NO_LCD 50 | build-properties: 51 | All: -DUSE_NO_LCD 52 | 53 | - arduino-boards-fqbn: MightyCore:avr:644 54 | platform-url: https://mcudude.github.io/MightyCore/package_MCUdude_MightyCore_index.json 55 | build-properties: 56 | All: -DUSE_LAYOUT_FOR_644_BOARD 57 | 58 | steps: 59 | - name: Checkout 60 | uses: actions/checkout@master 61 | 62 | - name: Compile all examples 63 | uses: ArminJo/arduino-test-compile@master 64 | with: 65 | # required-libraries: EasyButtonAtInt01,SoftI2CMaster 66 | arduino-board-fqbn: ${{ matrix.arduino-boards-fqbn }} 67 | platform-url: ${{ matrix.platform-url }} 68 | build-properties: ${{ toJson(matrix.build-properties) }} -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/ADCUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ADCUtils.h 3 | * 4 | * Copyright (C) 2016-2022 Armin Joachimsmeyer 5 | * Email: armin.joachimsmeyer@gmail.com 6 | * 7 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 8 | * 9 | * ArduinoUtils is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | * See the GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | * 22 | */ 23 | 24 | #ifndef _ADC_UTILS_H 25 | #define _ADC_UTILS_H 26 | 27 | #include 28 | 29 | #if defined(__AVR__) && defined(ADCSRA) && defined(ADATE) && (!defined(__AVR_ATmega4809__)) 30 | #define ADC_UTILS_ARE_AVAILABLE 31 | 32 | // External Reference Current is 150 uA for 5 V and 100 uA for 3.5 V 33 | #define READING_FOR_AREF 1024L // Datasheet 24.2: The minimum value represents GND and the maximum value represents the voltage on the AREF pin minus 1 LSB 34 | 35 | // PRESCALE4 => 13 * 4 = 52 microseconds per ADC conversion at 1 MHz Clock => 19,2 kHz 36 | #define ADC_PRESCALE2 1 // 26 microseconds per ADC conversion at 1 MHz 37 | #define ADC_PRESCALE4 2 // 52 microseconds per ADC conversion at 1 MHz 38 | // PRESCALE8 => 13 * 8 = 104 microseconds per ADC sample at 1 MHz Clock => 9,6 kHz 39 | #define ADC_PRESCALE8 3 // 104 microseconds per ADC conversion at 1 MHz 40 | #define ADC_PRESCALE16 4 // 13/208 microseconds per ADC conversion at 16/1 MHz - degradations in linearity at 16 MHz 41 | #define ADC_PRESCALE32 5 // 26/416 microseconds per ADC conversion at 16/1 MHz - very good linearity at 16 MHz 42 | #define ADC_PRESCALE64 6 // 52 microseconds per ADC conversion at 16 MHz 43 | #define ADC_PRESCALE128 7 // 104 microseconds per ADC conversion at 16 MHz --- Arduino default 44 | 45 | // definitions for 0.1 ms conversion time 46 | #if (F_CPU == 1000000) 47 | #define ADC_PRESCALE ADC_PRESCALE8 48 | #elif (F_CPU == 8000000) 49 | #define ADC_PRESCALE ADC_PRESCALE64 50 | #elif (F_CPU == 16000000) 51 | #define ADC_PRESCALE ADC_PRESCALE128 52 | #endif 53 | 54 | /* 55 | * Reference shift values are complicated for ATtinyX5 since we have the extra register bit REFS2 56 | * in ATTinyCore, this bit is handled programmatical and therefore the defines are different. 57 | * To keep my library small, I use the changed defines. 58 | * After including this file you can not call the ATTinyCore readAnalog functions reliable, if you specify references other than default! 59 | */ 60 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 61 | // defines are for ADCUtils.cpp, they can be used WITHOUT bit reordering 62 | #undef DEFAULT 63 | #undef EXTERNAL 64 | #undef INTERNAL1V1 65 | #undef INTERNAL 66 | #undef INTERNAL2V56 67 | #undef INTERNAL2V56_EXTCAP 68 | 69 | #define DEFAULT 0 70 | #define EXTERNAL 4 71 | #define INTERNAL1V1 8 72 | #define INTERNAL INTERNAL1V1 73 | #define INTERNAL2V56 9 74 | #define INTERNAL2V56_EXTCAP 13 75 | 76 | #define SHIFT_VALUE_FOR_REFERENCE REFS2 77 | #define MASK_FOR_ADC_REFERENCE (_BV(REFS0) | _BV(REFS1) | _BV(REFS2)) 78 | #define MASK_FOR_ADC_CHANNELS (_BV(MUX0) | _BV(MUX1) | _BV(MUX2) | _BV(MUX3)) 79 | #else // AVR_ATtiny85 80 | 81 | #define SHIFT_VALUE_FOR_REFERENCE REFS0 82 | #define MASK_FOR_ADC_REFERENCE (_BV(REFS0) | _BV(REFS1)) 83 | #define MASK_FOR_ADC_CHANNELS (_BV(MUX0) | _BV(MUX1) | _BV(MUX2) | _BV(MUX3)) 84 | #endif 85 | 86 | // Temperature channel definitions - 1 LSB / 1 degree Celsius 87 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 88 | #define ADC_TEMPERATURE_CHANNEL_MUX 15 89 | #define ADC_1_1_VOLT_CHANNEL_MUX 12 90 | #define ADC_GND_CHANNEL_MUX 13 91 | #define ADC_CHANNEL_MUX_MASK 0x0F 92 | 93 | #elif defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 94 | #define ADC_ISCR_CHANNEL_MUX 3 95 | #define ADC_TEMPERATURE_CHANNEL_MUX 11 96 | #define ADC_1_1_VOLT_CHANNEL_MUX 12 97 | #define ADC_GND_CHANNEL_MUX 14 98 | #define ADC_VCC_4TH_CHANNEL_MUX 13 99 | #define ADC_CHANNEL_MUX_MASK 0x1F 100 | 101 | #elif defined(__AVR_ATmega328P__) 102 | #define ADC_TEMPERATURE_CHANNEL_MUX 8 103 | #define ADC_1_1_VOLT_CHANNEL_MUX 14 104 | #define ADC_GND_CHANNEL_MUX 15 105 | #define ADC_CHANNEL_MUX_MASK 0x0F 106 | 107 | #elif defined(__AVR_ATmega644P__) 108 | #define ADC_TEMPERATURE_CHANNEL_MUX // not existent 109 | #define ADC_1_1_VOLT_CHANNEL_MUX 0x1E 110 | #define ADC_GND_CHANNEL_MUX 0x1F 111 | #define ADC_CHANNEL_MUX_MASK 0x0F 112 | 113 | #elif defined(__AVR_ATmega32U4__) 114 | #define ADC_TEMPERATURE_CHANNEL_MUX 0x27 115 | #define ADC_1_1_VOLT_CHANNEL_MUX 0x1E 116 | #define ADC_GND_CHANNEL_MUX 0x1F 117 | #define ADC_CHANNEL_MUX_MASK 0x3F 118 | 119 | #elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) 120 | #define ADC_1_1_VOLT_CHANNEL_MUX 0x1E 121 | #define ADC_GND_CHANNEL_MUX 0x1F 122 | #define ADC_CHANNEL_MUX_MASK 0x1F 123 | 124 | #define INTERNAL INTERNAL1V1 125 | 126 | #else 127 | #error "No temperature channel definitions specified for this AVR CPU" 128 | #endif 129 | 130 | /* 131 | * Thresholds for OVER and UNDER voltage and detection of kind of power supply (USB or Li-ion) 132 | * 133 | * Default values are suitable for Li-ion batteries. 134 | * We normally have voltage drop at the connectors, so the battery voltage is assumed slightly higher, than the Arduino VCC. 135 | * But keep in mind that the ultrasonic distance module HC-SR04 may not work reliable below 3.7 volt. 136 | */ 137 | #if !defined(LI_ION_VCC_UNDERVOLTAGE_THRESHOLD_MILLIVOLT) 138 | #define LI_ION_VCC_UNDERVOLTAGE_THRESHOLD_MILLIVOLT 3400 // Do not stress your battery and we require some power for standby 139 | #endif 140 | #if !defined(LI_ION_VCC_EMERGENCY_UNDERVOLTAGE_THRESHOLD_MILLIVOLT) 141 | #define LI_ION_VCC_EMERGENCY_UNDERVOLTAGE_THRESHOLD_MILLIVOLT 3000 // Many Li-ions are specified down to 3.0 volt 142 | #endif 143 | 144 | #if !defined(VCC_UNDERVOLTAGE_THRESHOLD_MILLIVOLT) 145 | #define VCC_UNDERVOLTAGE_THRESHOLD_MILLIVOLT LI_ION_VCC_UNDERVOLTAGE_THRESHOLD_MILLIVOLT 146 | #endif 147 | #if !defined(VCC_EMERGENCY_UNDERVOLTAGE_THRESHOLD_MILLIVOLT) 148 | #define VCC_EMERGENCY_UNDERVOLTAGE_THRESHOLD_MILLIVOLT LI_ION_VCC_EMERGENCY_UNDERVOLTAGE_THRESHOLD_MILLIVOLT 149 | #endif 150 | #if !defined(VCC_OVERVOLTAGE_THRESHOLD_MILLIVOLT) 151 | #define VCC_OVERVOLTAGE_THRESHOLD_MILLIVOLT 5250 // + 5 % operation voltage 152 | #endif 153 | #if !defined(VCC_EMERGENCY_OVERVOLTAGE_THRESHOLD_MILLIVOLT) 154 | #define VCC_EMERGENCY_OVERVOLTAGE_THRESHOLD_MILLIVOLT 5500 // +10 %. Max recommended operation voltage 155 | #endif 156 | #if !defined(VCC_CHECK_PERIOD_MILLIS) 157 | #define VCC_CHECK_PERIOD_MILLIS 10000L // 10 seconds period of VCC checks 158 | #endif 159 | #if !defined(VCC_UNDERVOLTAGE_CHECKS_BEFORE_STOP) 160 | #define VCC_UNDERVOLTAGE_CHECKS_BEFORE_STOP 6 // Shutdown after 6 times (60 seconds) VCC below VCC_UNDERVOLTAGE_THRESHOLD_MILLIVOLT or 1 time below VCC_EMERGENCY_UNDERVOLTAGE_THRESHOLD_MILLIVOLT 161 | #endif 162 | 163 | #if !defined(VOLTAGE_USB_POWERED_LOWER_THRESHOLD_MILLIVOLT) 164 | #define VOLTAGE_USB_POWERED_LOWER_THRESHOLD_MILLIVOLT 4300 // Assume USB powered above this voltage 165 | #endif 166 | 167 | #if !defined(VOLTAGE_USB_POWERED_UPPER_THRESHOLD_MILLIVOLT) 168 | #define VOLTAGE_USB_POWERED_UPPER_THRESHOLD_MILLIVOLT 4950 // Assume USB powered below this voltage, because of the loss in USB cable. If we have > 4950, we assume to be powered by VIN. 169 | // In contrast to e.g. powered by VIN, which results in almost perfect 5 volt supply 170 | #endif 171 | 172 | extern long sLastVCCCheckMillis; 173 | extern uint8_t sVCCTooLowCounter; 174 | 175 | uint16_t readADCChannel(uint8_t aADCChannelNumber); 176 | uint16_t readADCChannelWithReference(uint8_t aADCChannelNumber, uint8_t aReference); 177 | uint16_t waitAndReadADCChannelWithReference(uint8_t aADCChannelNumber, uint8_t aReference); 178 | uint16_t waitAndReadADCChannelWithReferenceAndRestoreADMUXAndReference(uint8_t aADCChannelNumber, uint8_t aReference); 179 | uint16_t readADCChannelWithOversample(uint8_t aADCChannelNumber, uint8_t aOversampleExponent); 180 | void setADCChannelAndReferenceForNextConversion(uint8_t aADCChannelNumber, uint8_t aReference); 181 | uint16_t readADCChannelWithReferenceOversampleFast(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aOversampleExponent); 182 | uint32_t readADCChannelMultiSamples(uint8_t aPrescale, uint16_t aNumberOfSamples); 183 | uint16_t readADCChannelMultiSamplesWithReference(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aNumberOfSamples); 184 | uint32_t readADCChannelMultiSamplesWithReferenceAndPrescaler(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aPrescale, 185 | uint16_t aNumberOfSamples); 186 | uint16_t readADCChannelWithReferenceMax(uint8_t aADCChannelNumber, uint8_t aReference, uint16_t aNumberOfSamples); 187 | uint16_t readADCChannelWithReferenceMaxMicros(uint8_t aADCChannelNumber, uint8_t aReference, uint16_t aMicrosecondsToAquire); 188 | uint16_t readUntil4ConsecutiveValuesAreEqual(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aDelay, 189 | uint8_t aAllowedDifference, uint8_t aMaxRetries); 190 | 191 | uint8_t checkAndWaitForReferenceAndChannelToSwitch(uint8_t aADCChannelNumber, uint8_t aReference); 192 | 193 | /* 194 | * readVCC*() functions store the result in sVCCVoltageMillivolt or sVCCVoltage 195 | */ 196 | float getVCCVoltageSimple(void); 197 | void readVCCVoltageSimple(void); 198 | void readVCCVoltageMillivoltSimple(void); 199 | void readVCCVoltage(void); 200 | uint16_t getVCCVoltageMillivolt(void); 201 | void readVCCVoltageMillivolt(void); 202 | uint16_t getVCCVoltageReadingFor1_1VoltReference(void); 203 | uint16_t printVCCVoltageMillivolt(Print *aSerial); 204 | void readAndPrintVCCVoltageMillivolt(Print *aSerial); 205 | 206 | uint16_t getVoltageMillivolt(uint16_t aVCCVoltageMillivolt, uint8_t aADCChannelForVoltageMeasurement); 207 | uint16_t getVoltageMillivolt(uint8_t aADCChannelForVoltageMeasurement); 208 | uint16_t getVoltageMillivoltWith_1_1VoltReference(uint8_t aADCChannelForVoltageMeasurement); 209 | float getCPUTemperatureSimple(void); 210 | float getCPUTemperature(void); 211 | float getTemperature(void) __attribute__ ((deprecated ("Renamed to getCPUTemperature()"))); // deprecated 212 | 213 | bool isVCCUSBPowered(); 214 | bool isVCCUSBPowered(Print *aSerial); 215 | bool isVCCUndervoltageMultipleTimes(); 216 | void resetCounterForVCCUndervoltageMultipleTimes(); 217 | bool isVCCUndervoltage(); 218 | bool isVCCEmergencyUndervoltage(); 219 | bool isVCCOvervoltage(); 220 | bool isVCCOvervoltageSimple(); // Version using readVCCVoltageMillivoltSimple() 221 | bool isVCCTooHighSimple(); // Version not using readVCCVoltageMillivoltSimple() 222 | 223 | #endif // defined(__AVR__) ... 224 | 225 | /* 226 | * Variables and functions defined as dummies to allow for seamless compiling on non AVR platforms 227 | */ 228 | extern float sVCCVoltage; 229 | extern uint16_t sVCCVoltageMillivolt; 230 | 231 | uint16_t readADCChannelWithReferenceOversample(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aOversampleExponent); 232 | 233 | uint16_t getVCCVoltageMillivoltSimple(void); 234 | float getVCCVoltage(void); 235 | float getCPUTemperature(void); 236 | 237 | #endif // _ADC_UTILS_H 238 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/AVRUtils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AVRUtils.cpp 3 | * 4 | * Stack, Ram and Heap utilities. 5 | * Sleep utilities. 6 | * 7 | * Copyright (C) 2016-2024 Armin Joachimsmeyer 8 | * Email: armin.joachimsmeyer@gmail.com 9 | * 10 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 11 | * 12 | * Arduino-Utils is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 20 | * See the GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | */ 26 | 27 | #include "Arduino.h" 28 | 29 | #if defined(__AVR__) && defined (SPMCSR) && !(defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__) || defined(__AVR_ATtiny3217__)) 30 | #include "AVRUtils.h" 31 | #include 32 | #include 33 | #include 34 | #include // for __malloc_margin 35 | 36 | /* 37 | * Returns actual start of available / free heap 38 | * Usage for print: 39 | Serial.print(F("AvailableHeapStart=0x")); 40 | Serial.println((uint16_t) getAvailableHeapStart(), HEX); 41 | */ 42 | uint8_t* getAvailableHeapStart(void) { 43 | if (__brkval == 0) { 44 | // __brkval is 0 if no malloc() has happened before 45 | // __brkval = __malloc_heap_start; 46 | __brkval = &__heap_start; 47 | } 48 | return (uint8_t*) __brkval; 49 | } 50 | void printAvailableHeapStart(Print *aSerial) { 51 | aSerial->print(F("Heap start=")); 52 | aSerial->println((uint16_t) getAvailableHeapStart()); 53 | } 54 | 55 | /* 56 | * Initialize RAM between current stack and actual heap start (__brkval) with pattern 0x5A 57 | */ 58 | void initStackFreeMeasurement() { 59 | uint8_t *tHeapPtr = getAvailableHeapStart(); // This sets __brkval 60 | 61 | // Fill / paint stack 62 | do { 63 | *tHeapPtr++ = HEAP_STACK_UNTOUCHED_VALUE; 64 | } while (tHeapPtr < (uint8_t*) SP); 65 | } 66 | 67 | /* 68 | * @param aStackUnusedSizePointer points to variable which is written with amount of stack/heap not used/touched. 69 | * @return The amount of stack/heap touched since the last call to initStackFreeMeasurement() 70 | * -1 if stack was completely used 71 | * A downward search fails, because it finds an allocated variable / array on stack, which was unused! 72 | * An upward search may be wrong, and claiming too much stack used, because malloc does not initialize the memory 73 | * and the search fails with multiple mallocs and partial writing of allocated regions. 74 | * In this case you should initialize stack free measurement after releasing last heap block. 75 | */ 76 | //#include // for Serial 77 | int16_t getStackMaxUsedAndUnusedSizes(uint16_t *aStackUnusedSizePointer) { 78 | /* 79 | * Search for first touched value from end of current heap. 80 | */ 81 | uint16_t tStackUnused = 0; 82 | uint8_t *tHeapPtr = getAvailableHeapStart(); // __brkval 83 | while (*tHeapPtr == HEAP_STACK_UNTOUCHED_VALUE && tHeapPtr <= (uint8_t*) SP) { 84 | tHeapPtr++; 85 | tStackUnused++; 86 | } 87 | 88 | int16_t tStackMaxUsedSize = (RAMEND + 1) - (uint16_t) tHeapPtr; 89 | 90 | *aStackUnusedSizePointer = tStackUnused; 91 | if (tStackUnused == 0) { 92 | return -1; 93 | } 94 | return tStackMaxUsedSize; 95 | } 96 | 97 | /* 98 | * Prints the amount of stack NOT used/touched and used/touched since the last call to initStackFreeMeasurement() 99 | * Example: "Stack unused=0, used=16" if stack runs into data 100 | */ 101 | void printStackMaxUsedAndUnusedSizes(Print *aSerial) { 102 | uint16_t tStackUnusedBytes; 103 | aSerial->print(F("Stack used=")); 104 | aSerial->print(RAMEND - SP); 105 | aSerial->print(F(", max used=")); 106 | aSerial->print(getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes)); 107 | aSerial->print(F(", unused=")); 108 | aSerial->print(tStackUnusedBytes); 109 | aSerial->print(F(" of current total ")); 110 | aSerial->println((RAMEND + 1) - (uint16_t) getAvailableHeapStart()); 111 | } 112 | 113 | /* 114 | * Search upwards the first two HEAP_STACK_UNTOUCHED_VALUE values after current begin of heap 115 | */ 116 | uint16_t getHeapMaxUsedSize() { 117 | uint8_t *tHeapPtr = getAvailableHeapStart(); 118 | while (*tHeapPtr != HEAP_STACK_UNTOUCHED_VALUE && *(tHeapPtr + 1) != HEAP_STACK_UNTOUCHED_VALUE && tHeapPtr <= (uint8_t*) SP) { 119 | tHeapPtr++; 120 | } 121 | // tHeapPtr points now to lowest untouched stack position or to lowest current stack byte 122 | return tHeapPtr - (uint8_t*) __malloc_heap_start; 123 | } 124 | 125 | /* 126 | * Prints the amount of stack NOT used/touched and used/touched since the last call to initStackFreeMeasurement() 127 | * Print only if value changed. 128 | * @return true, if values changed 129 | */ 130 | bool printStackMaxUsedAndUnusedSizesIfChanged(Print *aSerial) { 131 | static int16_t tOldStackUsedBytes = 0; 132 | 133 | uint16_t tStackUnusedBytes; 134 | int16_t tStackMaxUsedBytes = getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes); 135 | if (tOldStackUsedBytes != tStackMaxUsedBytes) { 136 | tOldStackUsedBytes = tStackMaxUsedBytes; 137 | aSerial->print(F("Stack used=")); 138 | aSerial->print(RAMEND - SP); 139 | aSerial->print(F(", max used=")); 140 | aSerial->print(tStackMaxUsedBytes); 141 | aSerial->print(F(", unused=")); 142 | aSerial->println(tStackUnusedBytes); 143 | return true; 144 | } 145 | return false; 146 | } 147 | 148 | /* 149 | * Get amount of free Stack = current stackpointer - heap end 150 | */ 151 | uint16_t getCurrentAvailableStackSize(void) { 152 | uint16_t tAvailableHeapStart = (uint16_t) getAvailableHeapStart(); // __brkval 153 | if (tAvailableHeapStart >= SP) { 154 | return 0; 155 | } 156 | return (SP - tAvailableHeapStart); 157 | } 158 | void printCurrentAvailableStackSize(Print *aSerial) { 159 | aSerial->print(F("Currently available Stack[bytes]=")); 160 | aSerial->println(getCurrentAvailableStackSize()); 161 | } 162 | 163 | /* 164 | * Get amount of maximum available memory for malloc() 165 | * FreeRam - __malloc_margin (128 for ATmega328) 166 | */ 167 | uint16_t getCurrentAvailableHeapSize(void) { 168 | if (getCurrentAvailableStackSize() <= (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) { 169 | return 0; 170 | } 171 | // SP - __brkval - (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN) 172 | return getCurrentAvailableStackSize() - (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN); // (128) 173 | } 174 | 175 | void printCurrentAvailableHeapSize(Print *aSerial) { 176 | aSerial->print(F("Currently available Heap[bytes]=")); 177 | aSerial->println(getCurrentAvailableHeapSize()); 178 | } 179 | 180 | /* 181 | * Simple and short implementation, does not work before initStackFreeMeasurement() or first malloc() 182 | * The STACK required for this function is 4 bytes, so available numbers are 4 less than for caller. 183 | */ 184 | void printCurrentAvailableHeapSizeSimple(Print *aSerial) { 185 | aSerial->print(F("available=")); 186 | aSerial->println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)); 187 | } 188 | 189 | // This define is in AVRUtils.h 190 | // #define PRINT_AVAILABLE_HEAP Serial.print(F("available="));Serial.println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) 191 | 192 | void printBaseRAMData(Print *aSerial) { 193 | aSerial->print(F("__malloc_heap_start=")); 194 | aSerial->print((uint16_t) __malloc_heap_start); // = __bss_end, __heap_start in lst file 195 | aSerial->print(F("|0x")); 196 | aSerial->print((uint16_t) __malloc_heap_start, HEX); 197 | 198 | aSerial->print(F(", &__heap_start=")); 199 | aSerial->print((uint16_t) &__heap_start); 200 | aSerial->print(F("|0x")); 201 | aSerial->print((uint16_t) &__heap_start, HEX); 202 | 203 | aSerial->print(F(", __brkval=")); 204 | aSerial->print((uint16_t) __brkval); // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() 205 | aSerial->print(F("|0x")); 206 | aSerial->print((uint16_t) __brkval, HEX); 207 | 208 | aSerial->print(F(", __malloc_margin=")); 209 | aSerial->print((uint16_t) __malloc_margin); // =128 210 | 211 | aSerial->print(F(", SP=")); 212 | aSerial->print((uint16_t) SP); 213 | aSerial->print(F("|0x")); 214 | aSerial->print((uint16_t) SP, HEX); 215 | 216 | /* 217 | * The next 2 entries seems to be always 0 218 | */ 219 | aSerial->print(F(", __malloc_heap_end=")); 220 | aSerial->print((uint16_t) __malloc_heap_end); 221 | 222 | aSerial->print(F(", __flp=")); 223 | aSerial->print((uint16_t) __flp); // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() 224 | aSerial->println(); 225 | } 226 | 227 | /* 228 | * RAM starts with Data, i.e. variables initialized with values != 0, 229 | * followed by BSS, i.e. uninitalized variables (which are initialized with 0) 230 | * and variables not initialized by using attribute "__attribute__((section(".noinit")))". 231 | * It ends with the heap and the stack. 232 | * 233 | * The STACK required for this function is 8 bytes, so available numbers are 8 less than for caller. 234 | * 235 | * Sample output: 236 | * Data+BSS=445. Heap: used=770, max used=1096, available=663. Stack: available=791, used=42, max used=319, unused=188 of current total 833 237 | * Formulas: 238 | * Stack available + used = current total 239 | * Heap available + __malloc_margin (128) = Stack available 240 | * Data+BSS + Heap max used + Stack unused + Stack max used = RAMSIZE 241 | */ 242 | void printRAMInfo(Print *aSerial) { 243 | 244 | aSerial->print(F("Data+BSS=")); 245 | aSerial->print((uint16_t) &__heap_start - RAMSTART); 246 | 247 | aSerial->print(F(". Heap: used=")); 248 | aSerial->print((uint16_t) getAvailableHeapStart() - (uint16_t) &__heap_start); 249 | aSerial->print(F(", max written=")); // if Stack uses total heap, we see the stack size here :-( 250 | aSerial->print(getHeapMaxUsedSize()); 251 | aSerial->print(F(", available=")); 252 | uint16_t tStackAvailable = SP - (uint16_t) getAvailableHeapStart() + 1; 253 | aSerial->print(tStackAvailable - (uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN); 254 | 255 | aSerial->print(F(". Stack: available=")); 256 | aSerial->print(tStackAvailable); 257 | aSerial->print(F(", used=")); 258 | aSerial->print(RAMEND - SP); 259 | uint16_t tStackUnusedBytes; 260 | aSerial->print(F(", max used=")); 261 | aSerial->print(getStackMaxUsedAndUnusedSizes(&tStackUnusedBytes)); 262 | aSerial->print(F(", unused=")); 263 | aSerial->print(tStackUnusedBytes); 264 | aSerial->print(F(" of current total ")); 265 | aSerial->print((RAMEND + 1) - (uint16_t) getAvailableHeapStart()); // getAvailableHeapStart() 266 | 267 | aSerial->println(); 268 | } 269 | 270 | void set__malloc_margin(uint8_t aNewMallocMargin) { 271 | __malloc_margin = aNewMallocMargin; 272 | } 273 | 274 | void reset__malloc_margin() { 275 | __malloc_margin = 128; 276 | } 277 | 278 | bool isAddressInRAM(void *aAddressToCheck) { 279 | return (aAddressToCheck <= (void*) RAMEND); 280 | } 281 | 282 | bool isAddressBelowAvailableHeapStart(void *aAddressToCheck) { 283 | return (aAddressToCheck < getAvailableHeapStart()); 284 | } 285 | 286 | /* 287 | * Test available heap by callocing 128 bytes chunks, 288 | * If no memory available, try with 64, 32 etc up to 2, 1 byte chunks 289 | */ 290 | void testCallocSizesAndPrint(Print *aSerial) { 291 | uint8_t *tLastMallocPtr; 292 | uint16_t tMallocSize = 128; 293 | while (true) { 294 | aSerial->print(F("SP=0x")); 295 | aSerial->print(SP, HEX); 296 | aSerial->print(F(" available=")); 297 | aSerial->print(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)); 298 | uint8_t *tMallocPtr = (uint8_t*) calloc(tMallocSize, 1); 299 | 300 | aSerial->print(F(" -> calloc(")); 301 | aSerial->print(tMallocSize); 302 | aSerial->print(F(",1)")); 303 | 304 | if (tMallocPtr == nullptr) { 305 | aSerial->print(F("failed ->")); 306 | tMallocSize = tMallocSize >> 1; 307 | if (tMallocSize < 1) { 308 | aSerial->println(); 309 | break; 310 | } 311 | } else { 312 | tLastMallocPtr = tMallocPtr; 313 | aSerial->print(F("=0x")); 314 | aSerial->print((uint16_t) tLastMallocPtr, HEX); 315 | aSerial->print(F(" ->")); 316 | 317 | *tLastMallocPtr = HEAP_STACK_UNTOUCHED_VALUE; // For testing detection using 2 consecutive HEAP_STACK_UNTOUCHED_VALUE 318 | *(tLastMallocPtr + tMallocSize - 1) = 0x11; 319 | } 320 | printCurrentAvailableHeapSizeSimple(aSerial); 321 | } 322 | } 323 | /******************************************** 324 | * SLEEP AND WATCHDOG STUFF 325 | ********************************************/ 326 | 327 | #ifndef _MILLIS_UTILS_H 328 | // copied from MillisUtils.h 329 | /* 330 | * storage for millis value to enable compensation for interrupt disable at signal acquisition etc. 331 | */ 332 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 333 | #define timer0_millis millis_timer_millis // The ATTinyCore libraries use other variable name in wiring.c 334 | #endif 335 | 336 | extern volatile unsigned long timer0_millis; 337 | #endif // MILLIS_UTILS_H_ 338 | 339 | /* 340 | * For sleep modes see sleep.h 341 | * SLEEP_MODE_IDLE 342 | * SLEEP_MODE_ADC 343 | * SLEEP_MODE_PWR_DOWN 344 | * SLEEP_MODE_PWR_SAVE 345 | * SLEEP_MODE_STANDBY 346 | * SLEEP_MODE_EXT_STANDBY 347 | */ 348 | // required only once 349 | void initSleep(uint8_t tSleepMode) { 350 | sleep_enable(); 351 | set_sleep_mode(tSleepMode); 352 | } 353 | 354 | /* 355 | * Watchdog wakes CPU periodically and all we have to do is call sleep_cpu(); 356 | * aWatchdogPrescaler (see wdt.h) can be one of 357 | * WDTO_15MS, 30, 60, 120, 250, WDTO_500MS 358 | * WDTO_1S to WDTO_8S 359 | */ 360 | void initPeriodicSleepWithWatchdog(uint8_t tSleepMode, uint8_t aWatchdogPrescaler) { 361 | sleep_enable() 362 | ; 363 | set_sleep_mode(tSleepMode); 364 | MCUSR = ~_BV(WDRF); // Clear WDRF in MCUSR 365 | 366 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) \ 367 | || defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) \ 368 | || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 369 | #define WDTCSR WDTCR 370 | #endif 371 | // Watchdog interrupt enable + reset interrupt flag -> needs ISR(WDT_vect) 372 | uint8_t tWDTCSR = _BV(WDIE) | _BV(WDIF) | (aWatchdogPrescaler & 0x08 ? _WD_PS3_MASK : 0x00) | (aWatchdogPrescaler & 0x07); // handles that the WDP3 bit is in bit 5 of the WDTCSR register, 373 | WDTCSR = _BV(WDCE) | _BV(WDE); // clear lock bit for 4 cycles by writing 1 to WDCE AND WDE 374 | WDTCSR = tWDTCSR; // set final Value 375 | } 376 | 377 | /* 378 | * @param aWatchdogPrescaler (see wdt.h) can be one of WDTO_15MS, 30, 60, 120, 250, WDTO_500MS, WDTO_1S to WDTO_8S 379 | * 0 (15 ms) to 3(120 ms), 4 (250 ms) up to 9 (8000 ms) 380 | */ 381 | uint16_t computeSleepMillis(uint8_t aWatchdogPrescaler) { 382 | uint16_t tResultMillis = 8000; 383 | for (uint8_t i = 0; i < (9 - aWatchdogPrescaler); ++i) { 384 | tResultMillis = tResultMillis / 2; 385 | } 386 | return tResultMillis + DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN; // + for the (default) startup time. !!! But this depends from Clock Source and sleep mode !!! 387 | } 388 | 389 | /* 390 | * @param aWatchdogPrescaler (see wdt.h) can be one of WDTO_15MS, 30, 60, 120, 250, WDTO_500MS, WDTO_1S to WDTO_8S 391 | * 0 (15 ms) to 3(120 ms), 4 (250 ms) up to 9 (8000 ms) 392 | * ! I have see + 30 % deviation from nominal WDT clock! 393 | * @param aAdjustMillis if true, adjust the Arduino internal millis counter the get quite correct millis() 394 | * results even after sleep, since the periodic 1 ms timer interrupt is disabled while sleeping. 395 | * Interrupts are enabled before sleep! 396 | * !!! Do not forget to call e.g. noTone() or Serial.flush(); to wait for the last character to be sent, and/or disable interrupt sources before !!! 397 | */ 398 | void sleepWithWatchdog(uint8_t aWatchdogPrescaler, bool aAdjustMillis) { 399 | MCUSR = 0; // Clear MCUSR to enable a correct interpretation of MCUSR after reset 400 | ADCSRA &= ~ADEN; // disable ADC just before sleep -> saves 200 uA 401 | 402 | // use wdt_enable() since it handles that the WDP3 bit is in bit 5 of the WDTCSR register 403 | wdt_enable(aWatchdogPrescaler); 404 | 405 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 406 | # if !defined(timer0_millis) 407 | #define timer0_millis millis_timer_millis // The ATTinyCore + Digispark libraries use millis_timer_millis in wiring.c 408 | # endif 409 | #define WDTCSR WDTCR 410 | #endif 411 | WDTCSR |= _BV(WDIE) | _BV(WDIF); // Watchdog interrupt enable + reset interrupt flag -> requires ISR(WDT_vect) 412 | sei(); // Enable interrupts, to get the watchdog interrupt, which will wake us up 413 | sleep_cpu(); // The watchdog interrupt will wake us up from sleep 414 | 415 | // We wake up here :-) 416 | wdt_disable(); // Because next interrupt will otherwise lead to a reset, since wdt_enable() sets WDE / Watchdog System Reset Enable 417 | ADCSRA |= ADEN; 418 | 419 | /* 420 | * Since timer clock may be disabled adjust millis only if not slept in IDLE mode (SM2...0 bits are 000) 421 | */ 422 | #if defined(SM2) 423 | if (aAdjustMillis && (SMCR & ((_BV(SM2) | _BV(SM1) | _BV(SM0)))) != 0) { 424 | #elif ! defined(SMCR) 425 | if (aAdjustMillis && (MCUCR & ((_BV(SM1) | _BV(SM0)))) != 0) { 426 | #else 427 | if (aAdjustMillis && (SMCR & ((_BV(SM1) | _BV(SM0)))) != 0) { 428 | #endif 429 | timer0_millis += computeSleepMillis(aWatchdogPrescaler); 430 | } 431 | } 432 | 433 | /* 434 | * 0 -> %1 435 | * _BV(CLKPS0) -> %2 436 | * _BV(CLKPS1) -> %4 437 | * _BV(CLKPS1) | _BV(CLKPS0) -> 8 etc. up to 256 438 | */ 439 | void setclockDivisionFactor(uint8_t aDivisionBits) { 440 | CLKPR = _BV(CLKPCE); 441 | CLKPR = aDivisionBits; 442 | } 443 | 444 | #endif // defined(__AVR__) 445 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/AVRUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AVRUtils.h 3 | * 4 | * Copyright (C) 2016-2024 Armin Joachimsmeyer 5 | * Email: armin.joachimsmeyer@gmail.com 6 | * 7 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 8 | * 9 | * Arduino-Utils is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | * See the GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | * 22 | */ 23 | 24 | #include "Arduino.h" 25 | 26 | #if defined(__AVR__) && defined (SPMCSR) && !(defined(__AVR_ATtiny1616__) || defined(__AVR_ATtiny3216__) || defined(__AVR_ATtiny3217__)) 27 | #ifndef _AVR_UTILS_H 28 | #define _AVR_UTILS_H 29 | 30 | #include 31 | #include 32 | #include 33 | #include "avr/boot.h" 34 | 35 | /* 36 | * The largest address just not allocated so far 37 | * Under Unix, the "break value" was the end of the data 38 | * segment as dynamically requested from the operating system. 39 | * Since we don't have an operating system, just make sure 40 | * that we don't collide with the stack. 41 | */ 42 | extern void *__brkval; // The largest address just not allocated so far / start of available / free heap, initialized at first malloc() 43 | extern void *__flp; // 44 | extern char __heap_start; // = __bss_end, the linker address of heap start 45 | #define HEURISTIC_ADDITIONAL_MALLOC_MARGIN 14 // No malloc() possible if size is lower than (__malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN) 46 | 47 | /* 48 | * storage for millis value to enable compensation for interrupt disable at signal acquisition etc. 49 | */ 50 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 51 | # if !defined(_MILLIS_UTILS_H) 52 | #define timer0_millis millis_timer_millis // The ATTinyCore libraries use other variable name in wiring.c - copied from MillisUtils.h 53 | # endif 54 | # if !defined(DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN) 55 | #define DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN 65 56 | # endif 57 | #else 58 | # if !defined(DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN) 59 | #define DEFAULT_MILLIS_FOR_WAKEUP_AFTER_POWER_DOWN 0 // default for Uno / Nano etc. 60 | # endif 61 | #endif 62 | 63 | extern volatile unsigned long timer0_millis; 64 | 65 | void initSleep(uint8_t tSleepMode); 66 | void initPeriodicSleepWithWatchdog(uint8_t tSleepMode, uint8_t aWatchdogPrescaler); 67 | uint16_t computeSleepMillis(uint8_t aWatchdogPrescaler); 68 | void sleepWithWatchdog(uint8_t aWatchdogPrescaler, bool aAdjustMillis = false); 69 | 70 | #include 71 | 72 | uint8_t* getAvailableHeapStart(); 73 | void printAvailableHeapStart(Print *aSerial); 74 | uint16_t getCurrentAvailableStackSize(void); 75 | void printCurrentAvailableStackSize(Print *aSerial); 76 | uint16_t getCurrentAvailableHeapSize(void); 77 | void printCurrentAvailableHeapSize(Print *aSerial); 78 | void printCurrentAvailableHeapSizeSimple(Print *aSerial); 79 | #define PRINT_AVAILABLE_HEAP Serial.print(F("available="));Serial.println(SP - (uint16_t) __brkval + 1 - ((uint16_t) __malloc_margin + HEURISTIC_ADDITIONAL_MALLOC_MARGIN)) 80 | 81 | #define HEAP_STACK_UNTOUCHED_VALUE 0x5A 82 | void initStackFreeMeasurement(); 83 | 84 | int16_t getStackMaxUsedAndUnusedSizes(uint16_t *aStackUnusedSizePointer); 85 | void printStackMaxUsedAndUnusedSizes(Print *aSerial); 86 | bool printStackMaxUsedAndUnusedSizesIfChanged(Print *aSerial); 87 | 88 | void printBaseRAMData(Print *aSerial); 89 | void printRAMInfo(Print *aSerial); 90 | 91 | bool isAddressInRAM(void *aAddressToCheck); 92 | bool isAddressBelowAvailableHeapStart(void *aAddressToCheck); 93 | 94 | void set__malloc_margin(uint8_t aNewMallocMargin); 95 | void reset__malloc_margin(); 96 | 97 | void testCallocSizesAndPrint(Print *aSerial); 98 | 99 | #endif // _AVR_UTILS_H 100 | #endif // defined(__AVR__) 101 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/EasyButtonAtInt01.h: -------------------------------------------------------------------------------- 1 | /* 2 | * EasyButtonAtInt01.h 3 | * 4 | * Arduino library for handling push buttons connected between ground and INT0 and / or INT1 pin. 5 | * INT0 and INT1 are connected to Pin 2 / 3 on most Arduinos (ATmega328), to PB6 / PA3 on ATtiny167 and on ATtinyX5 we have only INT0 at PB2. 6 | * The library is totally based on interrupt. 7 | * Debouncing is implemented in a not blocking way! It is merely done by ignoring a button change within the debouncing time. 8 | * So button state is instantly available without debouncing delay! 9 | * 10 | * Usage: 11 | * #define USE_BUTTON_0 12 | * #include "EasyButtonAtInt01.h" 13 | * EasyButton Button0AtPin2(true); 14 | * The macros INT0_PIN and INT1_PIN are set after the include. 15 | * 16 | * Copyright (C) 2018-2024 Armin Joachimsmeyer 17 | * armin.joachimsmeyer@gmail.com 18 | * 19 | * This file is part of EasyButtonAtInt01 https://github.com/ArminJo/EasyButtonAtInt01. 20 | * 21 | * EasyButtonAtInt01 is free software: you can redistribute it and/or modify 22 | * it under the terms of the GNU General Public License as published by 23 | * the Free Software Foundation, either version 3 of the License, or 24 | * (at your option) any later version. 25 | * 26 | * This program is distributed in the hope that it will be useful, 27 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 28 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 29 | * See the GNU General Public License for more details. 30 | * 31 | * You should have received a copy of the GNU General Public License 32 | * along with this program. If not, see . 33 | */ 34 | 35 | #ifndef _EASY_BUTTON_AT_INT01_H 36 | #define _EASY_BUTTON_AT_INT01_H 37 | 38 | #define VERSION_EASY_BUTTON "3.4.0" 39 | #define VERSION_EASY_BUTTON_MAJOR 3 40 | #define VERSION_EASY_BUTTON_MINOR 4 41 | #define VERSION_EASY_BUTTON_PATCH 0 42 | // The change log is at the bottom of the file 43 | 44 | /* 45 | * Macro to convert 3 version parts into an integer 46 | * To be used in preprocessor comparisons, such as #if VERSION_EASY_BUTTON_HEX >= VERSION_HEX_VALUE(3, 0, 0) 47 | */ 48 | #define VERSION_HEX_VALUE(major, minor, patch) ((major << 16) | (minor << 8) | (patch)) 49 | #define VERSION_EASY_BUTTON_HEX VERSION_HEX_VALUE(VERSION_EASY_BUTTON_MAJOR, VERSION_EASY_BUTTON_MINOR, VERSION_EASY_BUTTON_PATCH) 50 | #if defined(__AVR__) 51 | #include 52 | 53 | /* 54 | * Usage: 55 | * #define USE_BUTTON_0 // Enable code for button at INT0 56 | * #define USE_BUTTON_1 // Enable code for button at INT1 (PCINT0 for ATtinyX5) 57 | * #include "EasyButtonAtInt01.h" 58 | * EasyButton Button0AtPin2(true); // true -> Button is connected to INT0 59 | * EasyButton Button0AtPin3(false, &Button3CallbackHandler); // false -> button is not connected to INT0 => connected to INT1 60 | * ... 61 | * digitalWrite(LED_BUILTIN, Button0AtPin2.ButtonToggleState); 62 | * ... 63 | * 64 | */ 65 | 66 | // Return values for button state 67 | #define BUTTON_IS_ACTIVE true 68 | #define BUTTON_IS_INACTIVE false 69 | 70 | /* 71 | * Enable this if you buttons are active high. 72 | */ 73 | //#define BUTTON_IS_ACTIVE_HIGH 74 | /* 75 | * Define USE_ATTACH_INTERRUPT to force use of the arduino function attachInterrupt(). 76 | * It is required if you get the error " multiple definition of `__vector_1'" (or `__vector_2'), because another library uses the attachInterrupt() function. 77 | * For one button it needs additional 160 bytes program memory, for 2 buttons it needs additional 88 bytes. 78 | */ 79 | //#define USE_ATTACH_INTERRUPT 80 | // 81 | /* 82 | * You can define your own value if you have buttons which are worse or better than the one I have. 83 | * Since debouncing is not done with blocking wait, reducing this value makes not much sense, except you expect regular short button presses, 84 | * which durations are shorter than BUTTON_DEBOUNCING_MILLIS. 85 | * Press duration below 50 ms are almost impossible to generate by normal button pressing, but they can generated by just hitting the button. 86 | * 87 | * Test your own new value with the DebounceTest example 88 | * 89 | * Analyze the button actual debounce value with defining ANALYZE_MAX_BOUNCING_PERIOD and looking at MaxBouncingPeriodMillis. 90 | * Defining ANALYZE_MAX_BOUNCING_PERIOD computes the maximum bouncing period. 91 | * this is the time between first level change and last bouncing level change during BUTTON_DEBOUNCING_MILLIS 92 | */ 93 | //#define ANALYZE_MAX_BOUNCING_PERIOD 94 | #if !defined(BUTTON_DEBOUNCING_MILLIS) 95 | #define BUTTON_DEBOUNCING_MILLIS 50 // 35 millis measured for my button :-). 96 | #endif 97 | 98 | /* 99 | * Activating this enables save 2 bytes RAM and 64 bytes program memory 100 | */ 101 | //#define NO_BUTTON_RELEASE_CALLBACK 102 | // 103 | /* 104 | * Return values for checkForLongPress() 105 | */ 106 | #define EASY_BUTTON_LONG_PRESS_STILL_POSSIBLE 0 107 | #define EASY_BUTTON_LONG_PRESS_ABORT 1 // button was released, no long press detection possible 108 | #define EASY_BUTTON_LONG_PRESS_DETECTED 2 109 | 110 | #define EASY_BUTTON_LONG_PRESS_DEFAULT_MILLIS 400 111 | #define EASY_BUTTON_DOUBLE_PRESS_DEFAULT_MILLIS 400 112 | 113 | /* 114 | * This activates LED_BUILTIN as long as button is pressed 115 | */ 116 | //#define BUTTON_LED_FEEDBACK 117 | #if defined(BUTTON_LED_FEEDBACK) 118 | # if !defined(BUTTON_LED_FEEDBACK_PIN) 119 | # if defined(LED_BUILTIN) 120 | # define BUTTON_LED_FEEDBACK_PIN LED_BUILTIN // if not specified, use built in led - pin 13 on Uno board 121 | # else 122 | # error "BUTTON_LED_FEEDBACK is defined but neither BUTTON_LED_FEEDBACK_PIN nor LED_BUILTIN is defined" 123 | # endif 124 | # endif 125 | #endif 126 | 127 | // For external measurement of code timing 128 | //#define MEASURE_EASY_BUTTON_INTERRUPT_TIMING 129 | 130 | #if defined(MEASURE_EASY_BUTTON_INTERRUPT_TIMING) 131 | # if !defined(INTERRUPT_TIMING_OUTPUT_PIN) 132 | #define INTERRUPT_TIMING_OUTPUT_PIN 6 // use pin 6 133 | //#define INTERRUPT_TIMING_OUTPUT_PIN 12 // use pin 12 134 | # endif 135 | #endif 136 | 137 | //#define TRACE 138 | #if defined(TRACE) 139 | #warning If using TRACE, the timing of the interrupt service routine changes, e.g. you will see more spikes, than expected! 140 | #endif 141 | 142 | /* 143 | * These defines are here to enable saving of 150 bytes program memory if only one button is needed 144 | */ 145 | //#define USE_BUTTON_0 146 | //#define USE_BUTTON_1 147 | #if ! (defined(USE_BUTTON_0) || defined(USE_BUTTON_1)) 148 | #error USE_BUTTON_0 and USE_BUTTON_1 are not defined, please define them or remove the #include "EasyButtonAtInt01.h" 149 | #endif 150 | // Can be be used as parameter 151 | #define BUTTON_AT_INT0 ((bool)true) 152 | #define BUTTON_AT_INT1_OR_PCINT ((bool)false) 153 | /* 154 | * Pin and port definitions for Arduinos 155 | */ 156 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 157 | #define INT0_PIN 2 158 | #define INT0_DDR_PORT (DDRB) 159 | #define INT0_IN_PORT (PINB) 160 | #define INT0_OUT_PORT (PORTB) 161 | 162 | # if defined(USE_BUTTON_1) 163 | # if !defined(INT1_PIN) 164 | #define INT1_PIN 3 165 | # elif (INT1_PIN != 2) && (INT1_PIN > 5) 166 | #error INT1_PIN (for PCINT0 interrupt) can only be 0,1,3,4,5 167 | # endif 168 | #define INT1_DDR_PORT (DDRB) 169 | #define INT1_IN_PORT (PINB) 170 | #define INT1_OUT_PORT (PORTB) 171 | # endif // defined(USE_BUTTON_1) 172 | 173 | #elif defined(USE_INT2_FOR_BUTTON_0) // Hack for ATmega 644 174 | # if defined(USE_BUTTON_1) 175 | #error If USE_INT2_FOR_BUTTON_0 is defined, only USE_BUTTON_0 is allowed, USE_BUTTON_1 must be disabled! 176 | # endif 177 | // dirty hack, but INT0 and INT1 are occupied by second USART 178 | #define INT0_PIN 2 // PB2 / INT2 179 | #define INT0_DDR_PORT (DDRB) 180 | #define INT0_IN_PORT (PINB) 181 | #define INT0_OUT_PORT (PORTB) 182 | 183 | #elif defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__) 184 | // from here we use only ATtinyCore / PAx / PBx numbers, since on Digispark board and core library there is used a strange enumeration of pins 185 | #define INT0_PIN 14 // PB6 / INT0 is connected to USB+ on DigisparkPro boards and labeled with 3 (D3) 186 | #define INT0_DDR_PORT (DDRB) 187 | #define INT0_IN_PORT (PINB) 188 | #define INT0_OUT_PORT (PORTB) 189 | 190 | 191 | # if defined(USE_BUTTON_1) 192 | # if !defined(INT1_PIN) 193 | #define INT1_PIN 3 // PA3 labeled 9 on DigisparkPro boards 194 | # endif // !defined(INT1_PIN) 195 | 196 | // Check for pin range and map digispark to PA pin number 197 | # if defined(ARDUINO_AVR_DIGISPARKPRO) 198 | # if INT1_PIN == 5 199 | #undef INT1_PIN 200 | #define INT1_PIN 7 // PA7 201 | # elif INT1_PIN == 6 202 | #undef INT1_PIN 203 | #define INT1_PIN 0 // PA0 204 | # elif INT1_PIN == 7 205 | #undef INT1_PIN 206 | #define INT1_PIN 1 // PA1 207 | # elif INT1_PIN == 8 208 | #undef INT1_PIN 209 | #define INT1_PIN 2 // PA2 210 | # elif INT1_PIN == 9 211 | #undef INT1_PIN 212 | #define INT1_PIN 3 // PA3 213 | # elif INT1_PIN == 10 214 | #undef INT1_PIN 215 | #define INT1_PIN 4 // PA4 216 | # elif INT1_PIN == 11 217 | #undef INT1_PIN 218 | #define INT1_PIN 5 // PA5 219 | # elif INT1_PIN == 12 220 | #undef INT1_PIN 221 | #define INT1_PIN 6 // PA6 222 | # else 223 | #error INT1_PIN (for PCINT0 interrupt) can only be 5 to 12 224 | # endif 225 | # else // defined(ARDUINO_AVR_DIGISPARKPRO) 226 | # if (INT1_PIN > 7) 227 | #error INT1_PIN (for PCINT0 interrupt) can only be 0 to 7 228 | # endif 229 | # endif // defined(ARDUINO_AVR_DIGISPARKPRO) 230 | #define INT1_DDR_PORT (DDRA) 231 | #define INT1_IN_PORT (PINA) 232 | #define INT1_OUT_PORT (PORTA) 233 | # endif // defined(USE_BUTTON_1) 234 | #else 235 | 236 | // other AVR MCUs 237 | #define INT0_PIN 2 238 | #define INT0_DDR_PORT (DDRD) 239 | #define INT0_IN_PORT (PIND) 240 | #define INT0_OUT_PORT (PORTD) 241 | 242 | # if defined(USE_BUTTON_1) 243 | # if !defined(INT1_PIN) 244 | #define INT1_PIN 3 245 | # elif (INT1_PIN > 7) 246 | #error INT1_PIN (for PCINT2 interrupt) can only be Arduino pins 0 to 7 (PD0 to PD7) 247 | # endif 248 | #define INT1_DDR_PORT (DDRD) 249 | #define INT1_IN_PORT (PIND) 250 | #define INT1_OUT_PORT (PORTD) 251 | # endif // defined(USE_BUTTON_1) 252 | #endif // defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 253 | 254 | #if defined(USE_BUTTON_1) 255 | #define INT1_BIT INT1_PIN 256 | #endif 257 | 258 | #if defined(USE_BUTTON_1) && ((!defined(ISC10)) || ((defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)) && INT1_PIN != 3)) \ 259 | && !defined(INTENTIONALLY_USE_PCI0_FOR_BUTTON1) && !(defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)) 260 | #warning Using PCINT0 interrupt for button 1 261 | #endif 262 | 263 | #if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) 264 | #define EICRA MCUCR 265 | #define EIFR GIFR 266 | #define EIMSK GIMSK 267 | #endif 268 | 269 | #if (INT0_PIN >= 8) 270 | #define INT0_BIT (INT0_PIN - 8) 271 | #else 272 | #define INT0_BIT INT0_PIN 273 | #endif 274 | 275 | class EasyButton { 276 | 277 | public: 278 | 279 | /* 280 | * These constructors are deterministic if only one button is enabled 281 | * If two buttons are enabled they can be taken for the 1. button at INT0 282 | */ 283 | EasyButton(); 284 | EasyButton(void (*aButtonPressCallback)(bool aButtonToggleState)); 285 | #if !defined(NO_BUTTON_RELEASE_CALLBACK) 286 | EasyButton(void (*aButtonPressCallback)(bool aButtonToggleState), 287 | void (*aButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis)); 288 | #endif 289 | /* 290 | * These constructors use the first (bool) parameter to decide which button to take. 291 | */ 292 | EasyButton(bool aIsButtonAtINT0); 293 | EasyButton(bool aIsButtonAtINT0, void (*aButtonPressCallback)(bool aButtonToggleState)); 294 | #if !defined(NO_BUTTON_RELEASE_CALLBACK) 295 | EasyButton(bool aIsButtonAtINT0, void (*aButtonPressCallback)(bool aButtonToggleState), 296 | void (*aButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis)); 297 | #endif 298 | 299 | void init(bool aIsButtonAtINT0); 300 | bool enablePCIInterrupt(); 301 | 302 | /* 303 | * !!! checkForDoublePress() works only reliable if called in button press callback function !!! 304 | */ 305 | bool checkForDoublePress(uint16_t aDoublePressDelayMillis = EASY_BUTTON_DOUBLE_PRESS_DEFAULT_MILLIS); 306 | 307 | bool readButtonState(); 308 | bool getButtonStateIsActive(); // get private member 309 | bool readDebouncedButtonState(); 310 | bool updateButtonState(); 311 | uint16_t updateButtonPressDuration(); // Updates the ButtonPressDurationMillis by polling, since this cannot be done by interrupt. 312 | 313 | bool checkForForButtonNotPressedTime(uint16_t aTimeoutMillis); 314 | 315 | //!!! Consider to use button release callback handler and check the ButtonPressDurationMillis 316 | uint8_t checkForLongPress(uint16_t aLongPressThresholdMillis = EASY_BUTTON_LONG_PRESS_DEFAULT_MILLIS); 317 | bool checkForLongPressBlocking(uint16_t aLongPressThresholdMillis = EASY_BUTTON_LONG_PRESS_DEFAULT_MILLIS); 318 | 319 | void handleINT01Interrupts(); // internal use only 320 | 321 | bool LastBounceWasChangeToInactive; // Internal state, reflects actual reading with spikes and bouncing. Negative logic: true / active means button pin is LOW 322 | volatile bool ButtonToggleState; // Toggle is on press, not on release - initial value is false 323 | 324 | /* 325 | * Flag to enable action only once. Only set to true by library. 326 | * Can be checked and set to false by main program to enable only one action per button press. 327 | */ 328 | volatile bool ButtonStateHasJustChanged; 329 | 330 | /* 331 | * Duration of active state. Is is set at button release. Can in theory not be less than BUTTON_DEBOUNCING_MILLIS. 332 | * By definition, shorter presses are recognized as bouncing. 333 | * To cover this case you can call updateButtonState() from an outside loop which updates the button state in this case. 334 | */ 335 | volatile uint16_t ButtonPressDurationMillis; 336 | 337 | /* 338 | * Milliseconds of last button change, going active or inactive. 339 | * If bouncing occurs the time is not updated with the time of the bouncing. 340 | * So ButtonPressDurationMillis is the complete time and not the time after the last bounce. 341 | */ 342 | volatile unsigned long ButtonLastChangeMillis; 343 | 344 | /* 345 | * If last button change was going inactive, ButtonReleaseMillis contains the same value as ButtonLastChangeMillis 346 | * It is required for double press recognition, which is done when button is active and ButtonLastChangeMillis has just changed. 347 | * Be aware, that the first press after booting may be detected as double press! 348 | * This is because ButtonReleaseMillis is initialized with 0 milliseconds, which is interpreted as the first press happened at the beginning of boot. 349 | */ 350 | volatile unsigned long ButtonReleaseMillis; 351 | 352 | #if defined(ANALYZE_MAX_BOUNCING_PERIOD) 353 | volatile unsigned int MaxBouncingPeriodMillis = 0; // Maximum bouncing period. Time between first level change and last bouncing level change during BUTTON_DEBOUNCING_MILLIS 354 | #endif 355 | 356 | volatile bool isButtonAtINT0; 357 | void (*ButtonPressCallback)(bool aButtonToggleState) = nullptr; // If not null, is called on every button press with ButtonToggleState as parameter 358 | #if !defined(NO_BUTTON_RELEASE_CALLBACK) 359 | void (*ButtonReleaseCallback)(bool aButtonToggleState, uint16_t aButtonPressDurationMillis) = nullptr; // If not null, is called on every button release with ButtonPressDurationMillis as parameter 360 | #endif 361 | 362 | #if defined(USE_BUTTON_0) 363 | static EasyButton *sPointerToButton0ForISR; 364 | #endif 365 | #if defined(USE_BUTTON_1) 366 | static EasyButton *sPointerToButton1ForISR; 367 | #endif 368 | 369 | private: 370 | /* 371 | * If last press duration < BUTTON_DEBOUNCING_MILLIS it holds wrong value (true instead of false), therefore it is private. 372 | * To get current state, use readButtonState(). 373 | */ 374 | volatile bool ButtonStateIsActive; // State at last change. Negative logic: true / active means button pin is LOW. 375 | }; 376 | // end of class definition 377 | 378 | void handleINT0Interrupt(); 379 | void handleINT1Interrupt(); 380 | 381 | /* 382 | * This functions are weak and can be replaced by your own code 383 | */ 384 | #if defined(USE_BUTTON_0) 385 | void __attribute__ ((weak)) handleINT0Interrupt(); 386 | #endif 387 | 388 | #if defined(USE_BUTTON_1) 389 | void __attribute__ ((weak)) handleINT1Interrupt(); 390 | #endif 391 | 392 | #endif // defined(__AVR__) 393 | 394 | /* Version 3.4.1 - 12/2023 395 | * - Avoid wrong double press detection if calling checkForDoublePress() after release of button. 396 | * - Hack for ATmega 644. 397 | * 398 | * Version 3.4.0 - 10/2023 399 | * - Added NO_INITIALIZE_IN_CONSTRUCTOR macro to enable late initializing. 400 | * - ButtonStateIsActive is now private, since it is not reliable after bouncing. Use readButtonState() or readDebouncedButtonState() instead. 401 | * 402 | * Version 3.3.1 - 2/2022 403 | * - Avoid mistakenly double press detection after boot. 404 | * 405 | * Version 3.3.0 - 9/2021 406 | * - Renamed EasyButtonAtInt01.cpp.h to EasyButtonAtInt01.hpp. 407 | * 408 | * Version 3.2.0 - 1/2021 409 | * - Allow button1 on pin 8 to 13 and A0 to A5 for ATmega328. 410 | * 411 | * Version 3.1.0 - 6/2020 412 | * - 2 sets of constructors, one for only one button used and one for the second button if two buttons used. 413 | * - Map pin numbers for Digispark pro boards, for use with with digispark library. 414 | * 415 | * Version 3.0.0 - 5/2020 416 | * - Added button release handler and adapted examples. 417 | * - Revoke change for "only one true result per press for checkForLongPressBlocking()". It is superseded by button release handler. 418 | * - Support buttons which are active high by defining BUTTON_IS_ACTIVE_HIGH. 419 | * - Improved detection of maximum bouncing period used in DebounceTest. 420 | * 421 | * Version 2.1.0 - 5/2020 422 | * - Avoid 1 ms delay for checkForLongPressBlocking() if button is not pressed. 423 | * - Only one true result per press for checkForLongPressBlocking(). 424 | * 425 | * Version 2.0.0 - 1/2020 426 | * - Ported to ATtinyX5 and ATiny167. 427 | * - Support also PinChangeInterrupt for button 1 on Pin PA0 to PA7 for ATtiniy87/167. 428 | * - Long press detection support. 429 | * - Double press detection support. 430 | * - Renamed to EasyButtonAtInt01.hpp 431 | */ 432 | 433 | #endif // _EASY_BUTTON_AT_INT01_H 434 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/HexDump.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * HexDump.hpp 3 | * Hex memory dump utility functions for Arduino. 4 | * 0x00 and 0xFF are printed as spaces, values above 0x80 are printed as '.'. 5 | * 6 | * Sample output: 7 | * 0x0000: 0xF1 0x81 0x82 0x00 0x08 0x02 0x00 0x27 0xFF 0xFF 0x0E 0xB3 0x81 0xFC 0x9B 0x47 ... .. ' .....G 8 | * 0x0020: 0x00 0x00 0x00 0x00 0x20 0x65 0x00 0x0F 0xBE 0xEB 0x9B 0x98 0x2C 0xF1 0x08 0x2C e .....,.., 9 | * 10 | * Copyright (C) 2022-2024 Armin Joachimsmeyer 11 | * Email: armin.joachimsmeyer@gmail.com 12 | * 13 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 14 | * 15 | * Arduino-Utils is free software: you can redistribute it and/or modify 16 | * it under the terms of the GNU General Public License as published by 17 | * the Free Software Foundation, either version 3 of the License, or 18 | * (at your option) any later version. 19 | * 20 | * This program is distributed in the hope that it will be useful, 21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 23 | * See the GNU General Public License for more details. 24 | * 25 | * You should have received a copy of the GNU General Public License 26 | * along with this program. If not, see . 27 | */ 28 | 29 | #ifndef _HEX_DUMP_HPP 30 | #define _HEX_DUMP_HPP 31 | 32 | #include 33 | 34 | // Next lines are a copy of the HexDump.h content 35 | #define _16_BYTES_PER_LINE 16 36 | 37 | #define HEX_DUMP_FORMAT_16_BIT_ABSOLUTE_ADDRESS 0x00 // Print 16 bit absolute address 38 | #define HEX_DUMP_FORMAT_NO_ADDRESS_AT_ALL 0x01 // Bit 0: else print 39 | #define HEX_DUMP_FORMAT_RELATIVE_ADDRESS 0x02 // Bit 1: else absolute address 40 | #define HEX_DUMP_FORMAT_8_BIT_ADDRESS 0x04 // Bit 2: else 16 bit Address 41 | #define HEX_DUMP_FORMAT_ASCII_VALUES 0x08 // default 42 | 43 | void printBufferHex(uint8_t *aBufferAddress, uint16_t aNumberOfBytesToPrint); // Prints no address and hex bytes without ASCII representation. 44 | void printBufferHexDump(uint8_t *aBufferAddress, uint16_t aNumberOfBytesToPrint); // Prints short relative address and hex bytes without ASCII representation. 45 | void printBufferHexAndASCIIDump(uint8_t *aBufferAddress, uint16_t aNumberOfBytesToPrint); // Prints short relative address and hex bytes without ASCII representation. 46 | void printMemoryHexNoASCIIDump(uint8_t *aMemoryAddress, uint16_t aNumberOfBytesToPrint); // Prints 16 bit address and hex bytes with ASCII representation. 47 | void printMemoryHexAndASCIIDump(uint8_t *aMemoryAddress, uint16_t aNumberOfBytesToPrint); // Prints 16 bit address and hex bytes with ASCII representation. 48 | void printStackMemory(uint16_t aNumberOfBytesToPrint); // Prints 16 bit address and hex bytes ending at top of stack / RAM end. 49 | void printStackDump(); // Prints 16 bit address and hex bytes starting at current stackpointer and ending at ending at top of stack / RAM end. 50 | void printMemoryHexDump(uint8_t *aMemory, uint16_t aSizeOfMemoryToPrint, uint8_t aBytesPerLine = _16_BYTES_PER_LINE, 51 | uint8_t aFormatFlags = HEX_DUMP_FORMAT_ASCII_VALUES); 52 | void printBytePaddedHex(uint8_t aHexValueToPrint); 53 | void printWordPaddedHex(uint16_t aHexValueToPrint); 54 | // End of the copy of the HexDump.h content 55 | 56 | /* 57 | * Prints no address and hex bytes without ASCII representation. 58 | */ 59 | void printBufferHex(uint8_t *aBufferAddress, uint16_t aNumberOfBytesToPrint) { 60 | printMemoryHexDump(aBufferAddress, aNumberOfBytesToPrint, _16_BYTES_PER_LINE, HEX_DUMP_FORMAT_NO_ADDRESS_AT_ALL); 61 | } 62 | /* 63 | * Prints short relative address and hex bytes without ASCII representation. 64 | */ 65 | void printBufferHexDump(uint8_t *aBufferAddress, uint16_t aNumberOfBytesToPrint) { 66 | printMemoryHexDump(aBufferAddress, aNumberOfBytesToPrint, _16_BYTES_PER_LINE, 67 | HEX_DUMP_FORMAT_8_BIT_ADDRESS | HEX_DUMP_FORMAT_RELATIVE_ADDRESS); 68 | } 69 | /* 70 | * Prints short relative address and hex bytes without ASCII representation. 71 | */ 72 | void printBufferHexAndASCIIDump(uint8_t *aBufferAddress, uint16_t aNumberOfBytesToPrint) { 73 | printMemoryHexDump(aBufferAddress, aNumberOfBytesToPrint, _16_BYTES_PER_LINE, 74 | HEX_DUMP_FORMAT_8_BIT_ADDRESS | HEX_DUMP_FORMAT_RELATIVE_ADDRESS | HEX_DUMP_FORMAT_ASCII_VALUES); 75 | } 76 | 77 | /* 78 | * Prints 16 bit address and hex bytes with ASCII representation. 79 | */ 80 | void printMemoryHexNoASCIIDump(uint8_t *aMemoryAddress, uint16_t aNumberOfBytesToPrint) { 81 | printMemoryHexDump(aMemoryAddress, aNumberOfBytesToPrint, _16_BYTES_PER_LINE, HEX_DUMP_FORMAT_16_BIT_ABSOLUTE_ADDRESS); 82 | } 83 | 84 | /* 85 | * Prints 16 bit address and hex bytes starting at current stackpointer and ending at ending at top of stack / RAM end. 86 | */ 87 | void printStackDump() { 88 | Serial.print(F("Caller address=0x")); 89 | uint16_t tCallerAddress = (uint16_t) __builtin_return_address(0); 90 | Serial.print(tCallerAddress, HEX); 91 | Serial.print(F(" | ")); 92 | Serial.println(tCallerAddress << 1, HEX); 93 | printMemoryHexDump((uint8_t*) SP, RAMEND - SP, _16_BYTES_PER_LINE, HEX_DUMP_FORMAT_16_BIT_ABSOLUTE_ADDRESS); 94 | } 95 | 96 | /* 97 | * Prints 16 bit address and hex bytes ending at top of stack / RAM end. 98 | */ 99 | void printStackMemory(uint16_t aNumberOfBytesToPrint) { 100 | Serial.print(F("SP=0x")); 101 | Serial.print((uint16_t) SP, HEX); 102 | Serial.print(F(", caller address=0x")); 103 | uint16_t tCallerAddress = (uint16_t) __builtin_return_address(0); 104 | Serial.print(tCallerAddress, HEX); 105 | Serial.print(F(" | ")); 106 | Serial.println(tCallerAddress << 1, HEX); 107 | 108 | uint8_t *tMemoryAddress = (uint8_t*) ((RAMEND + 1) - aNumberOfBytesToPrint); 109 | printMemoryHexDump(tMemoryAddress, aNumberOfBytesToPrint, _16_BYTES_PER_LINE, HEX_DUMP_FORMAT_16_BIT_ABSOLUTE_ADDRESS); 110 | } 111 | 112 | /* 113 | * Prints 16 bit address and hex bytes with ASCII representation. 114 | * like printMemoryHexDump(aMemoryAddress, aNumberOfBytesToPrint), because of default parameter 115 | */ 116 | void printMemoryHexAndASCIIDump(uint8_t *aMemoryAddress, uint16_t aNumberOfBytesToPrint) { 117 | printMemoryHexDump(aMemoryAddress, aNumberOfBytesToPrint, _16_BYTES_PER_LINE, HEX_DUMP_FORMAT_ASCII_VALUES); 118 | } 119 | 120 | /** 121 | * Prints lines of memory content 122 | * 0x0000: 0xF1 0x81 0x82 0x00 0x08 0x02 0x00 0x27 0xFF 0xFF 0x0E 0xB3 0x81 0xFC 0x9B 0x47 ... .. ' .....G 123 | * @param aNumberOfBytesToPrint Number of lines to print are: (aSizeOfMemoryToPrint/BYTES_PER_LINE) + 1 124 | * @param aBytesPerLine Number of bytes in one line 125 | * @param aFormatFlags See definitions above 126 | */ 127 | void printMemoryHexDump(uint8_t *aMemory, uint16_t aNumberOfBytesToPrint, uint8_t aBytesPerLine, uint8_t aFormatFlags) { 128 | uint16_t tIndex = 0; 129 | Serial.println(); 130 | while (true) { 131 | if (aBytesPerLine > aNumberOfBytesToPrint) { 132 | // last line 133 | aBytesPerLine = aNumberOfBytesToPrint; 134 | } 135 | 136 | if (aBytesPerLine == 0) { 137 | break; 138 | } else { 139 | aNumberOfBytesToPrint -= aBytesPerLine; 140 | if ((aFormatFlags & HEX_DUMP_FORMAT_NO_ADDRESS_AT_ALL) == 0) { 141 | /* 142 | * Print address in different formats 143 | */ 144 | uint16_t tAddress = tIndex; 145 | if ((aFormatFlags & HEX_DUMP_FORMAT_RELATIVE_ADDRESS) == 0) { 146 | tAddress += (uint16_t) aMemory; 147 | } 148 | if ((aFormatFlags & HEX_DUMP_FORMAT_8_BIT_ADDRESS) != 0) { 149 | printBytePaddedHex(tAddress); 150 | } else { 151 | printWordPaddedHex(tAddress); 152 | } 153 | Serial.print(F(": ")); 154 | } 155 | 156 | /* 157 | * print hex bytes 158 | */ 159 | for (uint_fast8_t i = 0; i < aBytesPerLine; i++) { 160 | printBytePaddedHex(aMemory[tIndex + i]); 161 | } 162 | 163 | if ((aFormatFlags & HEX_DUMP_FORMAT_ASCII_VALUES) != 0) { 164 | /* 165 | * print bytes ASCII representation 166 | */ 167 | Serial.print(F(" ")); 168 | for (uint_fast8_t i = 0; i < aBytesPerLine; i++) { 169 | uint8_t tCharacterToPrint = aMemory[tIndex + i]; 170 | // if(isalnum(tIndex+i)){ // requires 40 bytes more program space 171 | if (' ' <= tCharacterToPrint && tCharacterToPrint <= '~') { 172 | Serial.print((char) tCharacterToPrint); 173 | } else if (tCharacterToPrint != 0x00 && tCharacterToPrint != 0xFF) { 174 | // for non printable characters except 0 and FF 175 | Serial.print('.'); 176 | } else { 177 | Serial.print(' '); 178 | } 179 | } 180 | } 181 | Serial.println(); 182 | tIndex += aBytesPerLine; 183 | } 184 | } 185 | } 186 | 187 | /* 188 | * Print with leading space and padded with 0 189 | */ 190 | void printBytePaddedHex(uint8_t aHexValueToPrint) { 191 | Serial.print(F(" 0x")); 192 | if (aHexValueToPrint < 0x10) { 193 | Serial.print('0'); 194 | } 195 | Serial.print(aHexValueToPrint, HEX); 196 | } 197 | 198 | void printWordPaddedHex(uint16_t aHexValueToPrint) { 199 | Serial.print(F("0x")); 200 | if (aHexValueToPrint < 0x1000) { 201 | Serial.print('0'); 202 | } 203 | if (aHexValueToPrint < 0x100) { 204 | Serial.print('0'); 205 | } 206 | if (aHexValueToPrint < 0x10) { 207 | Serial.print('0'); 208 | } 209 | Serial.print(aHexValueToPrint, HEX); 210 | 211 | } 212 | #endif // _HEX_DUMP_HPP 213 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/JK-BMS_Analytics.h: -------------------------------------------------------------------------------- 1 | /* 2 | * JK-BMS_Analytics.h 3 | * 4 | * Definitions of the data structures used by JK-BMS_Analytics 5 | * 6 | * Copyright (C) 2024 Armin Joachimsmeyer 7 | * Email: armin.joachimsmeyer@gmail.com 8 | * 9 | * This file is part of ArduinoUtils https://github.com/ArminJo/JK-BMSToPylontechCAN. 10 | * 11 | * JK-BMSToPylontechCAN is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 19 | * See the GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program. If not, see . 23 | * 24 | */ 25 | #ifndef _JK_BMS_ANALYTICS_H 26 | #define _JK_BMS_ANALYTICS_H 27 | 28 | /* 29 | * The value of AverageAccumulator10Milliampere for 1 Ah is: 30 | * (60 * 60 * 1000L / MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS) is number of samples in 1 hour -> 1800 at 1 sample / 2 seconds 31 | * 100 is factor for 10 mA to 1 A 32 | */ 33 | #define CAPACITY_10_mA_ACCUMULATOR_1_AMPERE_HOUR (100L * 60L * 60L * 1000L / MILLISECONDS_BETWEEN_JK_DATA_FRAME_REQUESTS) // 180,000 34 | 35 | /* 36 | * This structure is stored to EEPROM 37 | */ 38 | #define SOC_EVEN_EEPROM_PAGE_INDICATION_BIT 0x80 // Set in SOCPercent if we currently write on an even page. Required to find the end of current data in cyclic buffer. 39 | struct SOCDataPointDeltaStruct { 40 | uint8_t SOCPercent; 41 | uint8_t VoltageDifferenceToEmpty50Millivolt; // 1 = 50 mV, 255 = 12.75 V. Values > 240 to 255 / 12 V to 12.7 V are taken as negative ones, just in case it happens. 42 | int8_t AverageAmpere; 43 | int8_t Delta100MilliampereHour; // at a capacity of 320 Ah we have 3.2 Ah per 1% SOC 44 | }; 45 | #define NUMBER_OF_SOC_DATA_POINTS (((E2END + 1) - sizeof(SOCDataPointDeltaStruct)) / sizeof(SOCDataPointDeltaStruct)) // 0xFE for 1k EEPROM, 0x1FE for 2kEEPROM 46 | 47 | /* 48 | * This structure is initially filled while reading data from EEPROM and updated on writing 49 | */ 50 | struct SOCDataPointsInfoStruct { 51 | /* 52 | * Index of next value to be written is ArrayStartIndex + ArrayLength % NUMBER_OF_SOC_DATA_POINTS 53 | * => if array is full i.e. ArrayLength == NUMBER_OF_SOC_DATA_POINTS, index of next value to be written is ArrayStartIndex. 54 | */ 55 | 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. 56 | uint16_t ArrayLength; // Length of valid data in Array. Required if not fully written. Maximum is NUMBER_OF_SOC_DATA_POINTS 57 | bool currentlyWritingOnAnEvenPage; // If true SOC_EVEN_EEPROM_PAGE_INDICATION_BIT is set in SOCPercent. 58 | 59 | uint16_t NumberOfSamples; // For one sample each 2 seconds, we can store up to 36.4 hours here. 60 | long AverageAccumulatorVoltageDifferenceToEmpty10Millivolt; // Serves as accumulator to enable a more smooth graph. 61 | long AverageAccumulator10Milliampere; // Serves as accumulator for AverageAmpere 62 | 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. 63 | long lastWrittenBatteryCapacityAsAccumulator10Milliampere; 64 | 65 | uint16_t checksumForReboot; // Checksum of values from NumberOfSamples up to lastWrittenBatteryCapacityAsAccumulator10Milliampere to decide if we can keep this data at reboot 66 | }; 67 | extern SOCDataPointsInfoStruct SOCDataPointsInfo; 68 | 69 | struct SOCDataPointMinMaxStruct { 70 | uint8_t SOCPercent; 71 | int16_t VoltageDifferenceToEmpty50Millivolt; 72 | int16_t CapacityAmpereHour; 73 | int8_t AverageAmpere; 74 | }; 75 | 76 | void initializeAnalytics(); 77 | void updateCompleteEEPROMTo_FF(); 78 | void writeSOCData(); 79 | void findFirstSOCDataPointIndex(); 80 | void readBatteryESRfromEEPROM(); 81 | void readAndPrintSOCData(); 82 | 83 | #endif // _JK_BMS_ANALYTICS_H 84 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/JK-BMS_LCD.h: -------------------------------------------------------------------------------- 1 | /* 2 | * JK-BMS_LCD.h 3 | * 4 | * Contains declarations for LCD related variables and functions 5 | * 6 | * Copyright (C) 2023-2024 Armin Joachimsmeyer 7 | * Email: armin.joachimsmeyer@gmail.com 8 | * 9 | * This file is part of ArduinoUtils https://github.com/ArminJo/JK-BMSToPylontechCAN. 10 | * 11 | * JK-BMSToPylontechCAN is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 19 | * See the GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program. If not, see . 23 | * 24 | */ 25 | 26 | #ifndef _JK_BMS_LCD_H 27 | #define _JK_BMS_LCD_H 28 | 29 | /* 30 | * LCD hardware stuff 31 | */ 32 | #define LCD_I2C_DEFAULT_ADDRESS 0x27 // Default LCD address is 0x27 for a I2C adaptor with PCF8574 33 | #define LCD_COLUMNS 20 34 | #define LCD_ROWS 4 35 | #define LCD_I2C_ADDRESS LCD_I2C_DEFAULT_ADDRESS // 0x27 Default LCD address for a 20 chars and 4 line / 2004 display 36 | extern bool sSerialLCDAvailable; 37 | 38 | #if !defined(ENABLE_MONITORING) && defined(NO_ANALYTICS) 39 | extern char sStringBuffer[LCD_COLUMNS + 1]; // Only for rendering a LCD row with snprintf_P() 40 | #endif 41 | /* 42 | * Display timeouts, may be adapted to your requirements 43 | */ 44 | # if defined(STANDALONE_TEST) 45 | #define DISPLAY_ON_TIME_STRING "30 s" 46 | #define DISPLAY_ON_TIME_SECONDS 30L // L to avoid overflow at macro processing 47 | //#define NO_MULTIPLE_BEEPS_ON_TIMEOUT // Activate it if you do not want multiple beeps 48 | #define BEEP_ON_TIME_SECONDS_IF_TIMEOUT 10L // 10 s 49 | # else 50 | # if !defined(DISPLAY_ON_TIME_SECONDS) 51 | #define DISPLAY_ON_TIME_SECONDS 300L // 5 minutes. L to avoid overflow at macro processing 52 | #define DISPLAY_ON_TIME_STRING "5 min" // Only for display on LCD 53 | # endif 54 | # endif 55 | 56 | //#define DISPLAY_ALWAYS_ON // Activate this, if you want the display to be always on. 57 | # if !defined(DISPLAY_ALWAYS_ON) 58 | void doLCDBacklightTimeoutHandling(); 59 | bool checkAndTurnLCDOn(); 60 | extern bool sSerialLCDIsSwitchedOff; 61 | extern uint16_t sFrameCounterForLCDTAutoOff; 62 | # endif 63 | 64 | /* 65 | * LCD display pages 66 | */ 67 | #define JK_BMS_PAGE_OVERVIEW 0 // is selected in case of BMS alarm message 68 | #define JK_BMS_PAGE_CELL_INFO 1 69 | #if defined(NO_CELL_STATISTICS) 70 | #define JK_BMS_PAGE_BIG_INFO 2 71 | #define JK_BMS_PAGE_CAN_INFO 3 // Enter on long press 72 | #define JK_BMS_PAGE_CAPACITY_INFO 4 73 | #else 74 | #define JK_BMS_PAGE_CELL_STATISTICS 2 75 | #define CELL_STATISTICS_COUNTER_MASK 0x04 // must be a multiple of 2 and determines how often one page (min or max) is displayed. 76 | #define JK_BMS_PAGE_BIG_INFO 3 77 | #define JK_BMS_PAGE_CAN_INFO 4 // Enter on long press 78 | #define JK_BMS_PAGE_CAPACITY_INFO 5 79 | #endif 80 | 81 | // Switch to start page after 10 seconds of JK_BMS_PAGE_CAPACITY_INFO. 10 seconds to allow EEROM clearing by long press. 82 | #define JK_BMS_PAGE_CAPACITY_INFO_PAGE_TIMEOUT_MILLIS 10000 83 | #define CELL_CAPACITY_COUNTER_VOLTAGE 0x06 // If counter "anded" with mask is true show delta voltages instead of percents. 84 | 85 | #define JK_BMS_PAGE_MAX JK_BMS_PAGE_BIG_INFO 86 | #define JK_BMS_DEBUG_PAGE_MAX JK_BMS_PAGE_CAPACITY_INFO 87 | #define JK_BMS_START_PAGE JK_BMS_PAGE_BIG_INFO 88 | //uint8_t sLCDDisplayPageNumber = JK_BMS_PAGE_OVERVIEW; // Start with Overview page 89 | extern uint8_t sLCDDisplayPageNumber; // Start with Big Info page 90 | 91 | void setLCDDisplayPage(uint8_t aLCDDisplayPageNumber, bool aDoNotPrint = false); 92 | 93 | void printBMSDataOnLCD(); 94 | void printCANInfoOnLCD(); 95 | void LCDPrintSpaces(uint8_t aNumberOfSpacesToPrint); 96 | void LCDClearLine(uint8_t aLineNumber); 97 | 98 | void setupLCD(); 99 | void printDebugInfoOnLCD(); 100 | 101 | # if !defined(DISPLAY_ALWAYS_ON) 102 | bool checkAndTurnLCDOn(); 103 | void doLCDBacklightTimeoutHandling(); 104 | # endif 105 | 106 | void printShortEnableFlagsOnLCD(); 107 | void printShortStateOnLCD(); 108 | void printLongStateOnLCD(); 109 | void printAlarmHexOrStateOnLCD(); 110 | void printCellInfoOnLCD(); 111 | 112 | #if !defined(NO_CELL_STATISTICS) 113 | void printCellStatisticsOnLCD(); 114 | #endif // !defined(NO_CELL_STATISTICS) 115 | 116 | #if !defined(NO_ANALYTICS) 117 | void printCapacityInfoOnLCD(); 118 | #endif 119 | 120 | void printBigInfoOnLCD(); 121 | void printCANInfoOnLCD(); 122 | void printVoltageCurrentAndPowerOnLCD(); 123 | void printVoltageDifferenceAndTemperature(); 124 | void printTimeoutMessageOnLCD(); 125 | void printAlarmInfoOnLCD(); 126 | void printOverwiewOrAlarmInfoOnLCD(); 127 | void printBMSDataOnLCD(); 128 | void checkButtonPressForLCD(); 129 | void setLCDDisplayPage(uint8_t aLCDDisplayPageNumber, bool aDoNotPrint); 130 | 131 | #if defined(STANDALONE_TEST) 132 | void testLCDPages(); 133 | void testBigNumbers(); 134 | #endif // STANDALONE_TEST 135 | 136 | #endif // _JK_BMS_LCD_H 137 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/LCDPrintUtils.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * LCDPrintUtils.hpp 3 | * 4 | * Contains LCD related variables and functions 5 | * 6 | * Copyright (C) 2024 Armin Joachimsmeyer 7 | * Email: armin.joachimsmeyer@gmail.com 8 | * 9 | * This file is part of ArduinoUtils https://github.com/ArminJo/JK-BMSToPylontechCAN. 10 | * 11 | * JK-BMSToPylontechCAN is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 19 | * See the GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program. If not, see . 23 | * 24 | */ 25 | 26 | #ifndef _LCD_PRINT_UTILS_HPP 27 | #define _LCD_PRINT_UTILS_HPP 28 | 29 | //#define LOCAL_DEBUG // This enables debug output only for this file - only for development 30 | 31 | /* 32 | * Helper macro for getting a macro definition as string 33 | */ 34 | #if !defined(STR_HELPER) 35 | #define STR_HELPER(x) #x 36 | #define STR(x) STR_HELPER(x) 37 | #endif 38 | 39 | #define DEGREE_SIGN_STRING "\xDF" 40 | 41 | #if !defined(LCD_COLUMNS) 42 | #define LCD_COLUMNS 20 43 | #endif 44 | extern char sStringBuffer[]; // For rendering a LCD row with snprintf_P() 45 | 46 | #include "LiquidCrystal_I2C.hpp" // This defines USE_SOFT_I2C_MASTER, if SoftI2CMasterConfig.h is available. Use only the modified version delivered with this program! 47 | extern LiquidCrystal_I2C myLCD; 48 | 49 | void LCDPrintSpaces(uint8_t aNumberOfSpacesToPrint); 50 | void LCDClearLine(uint8_t aLineNumber); 51 | uint8_t getNumberOfDecimalsFor16BitValues(uint16_t a16BitValue); 52 | void printFloatValueRightAlignedOnLCD(float aFloatValue, uint8_t aNumberOfCharactersToPrint, bool aNoLeadingSpaceForPositiveValues = 53 | false); 54 | void LCDPrintSpaces(uint8_t aNumberOfSpacesToPrint) { 55 | for (uint_fast8_t i = 0; i < aNumberOfSpacesToPrint; ++i) { 56 | myLCD.print(' '); 57 | } 58 | } 59 | 60 | void LCDClearLine(uint8_t aLineNumber) { 61 | myLCD.setCursor(0, aLineNumber); 62 | LCDPrintSpaces(20); 63 | myLCD.setCursor(0, aLineNumber); 64 | } 65 | 66 | /* 67 | * !!! We internally use uint16_t, for bigger values we have an overflow. 68 | */ 69 | uint8_t getNumberOfDecimalsFor16BitValues(uint16_t a16BitValue) { 70 | uint16_t tCompareValue = 1; 71 | /* 72 | * Check for 10, 100, 1000 73 | */ 74 | for (uint_fast8_t tNumberOfDecimals = 0; tNumberOfDecimals < 5; ++tNumberOfDecimals) { 75 | if (a16BitValue < tCompareValue) { 76 | return tNumberOfDecimals; 77 | } 78 | tCompareValue *= 10; 79 | } 80 | // here we have values >= 10000 81 | return 5; 82 | } 83 | 84 | /* 85 | * !!! we internally use uint32_t, for bigger values we have an overflow. 86 | * Requires 26 bytes more program space than getNumberOfDecimalsFor16BitValues() 87 | */ 88 | uint8_t getNumberOfDecimalsFor32BitValues(uint32_t a32BitValue) { 89 | uint_fast8_t tNumberOfDecimals = 1; 90 | uint32_t tCompareValue = 10; 91 | /* 92 | * Check for 10, 100, 1000 up to 100,000,000 93 | */ 94 | for (; tNumberOfDecimals < 10; ++tNumberOfDecimals) { 95 | if (a32BitValue < tCompareValue) { 96 | return tNumberOfDecimals; 97 | } 98 | tCompareValue *= 10; 99 | } 100 | // here we have values >= 1,000,000 101 | return 10; 102 | } 103 | /* 104 | * !!! We internally use uint16_t, for bigger values (> 65,536 or < -65,536) we have an overflow. 105 | * @param aNoLeadingSpaceForPositiveValues - Normally each positive value has a leading space as placeholder for the minus sign. 106 | * If we know, that values are always positive, this space can be omitted by aNoLeadingSpaceForPositiveValues == true. 107 | * @param aNumberOfCharactersToPrint - The characters to be used for the most negative value to show. 108 | * I.e. 4 => max negative value is "-999" max positive value is " 999". 109 | * Values below 10 and -10 are displayed as floats with decimal point " 9.9" and "-9.9". 110 | * Values below 1 and -1 are displayed as floats without 0 before decimal point " .9" and "-.9". 111 | * If positive, there is a leading space, which improves readability if directly concatenated to another value. 112 | * e.g. "71V1.189A" is not readable, "71V 1.18A" is as well as "71V-1.18A". 113 | * 114 | * Saves programming space if used more than 1 times for printing floats if used instead of myLCD.print(JKComputedData.BatteryVoltageFloat, 2); 115 | */ 116 | void printFloatValueRightAlignedOnLCD(float aFloatValue, uint8_t aNumberOfCharactersToPrint, 117 | bool aNoLeadingSpaceForPositiveValues) { 118 | 119 | uint16_t tAbsValue = abs(aFloatValue); // remove sign for length computation 120 | uint8_t tNumberOfDecimals = getNumberOfDecimalsFor16BitValues(tAbsValue); 121 | int8_t tNumberOfDecimalPlaces = (aNumberOfCharactersToPrint - 2) - tNumberOfDecimals; 122 | if (aNoLeadingSpaceForPositiveValues && aFloatValue >= 0) { 123 | tNumberOfDecimalPlaces++; // Use this increased value internally, since we do not eventually print the '-' 124 | } 125 | #if defined(LOCAL_DEBUG) 126 | Serial.print(F("NumberOfDecimalPlaces(")); 127 | Serial.print(tAbsValue); 128 | Serial.print(F(", ")); 129 | Serial.print(aNumberOfCharactersToPrint); 130 | Serial.print(F(")=")); 131 | Serial.print(tNumberOfDecimalPlaces); 132 | Serial.print(F(" NumberOfDecimals=")); 133 | Serial.println(tNumberOfDecimals); 134 | #endif 135 | if (tNumberOfDecimalPlaces < 0) { 136 | tNumberOfDecimalPlaces = 0; 137 | } 138 | 139 | char *tStartOfString = sStringBuffer; 140 | if (tNumberOfDecimals == 0 && tNumberOfDecimalPlaces > 0) { 141 | if (aFloatValue >= 0) { 142 | if (!aNoLeadingSpaceForPositiveValues) { 143 | myLCD.print(' '); 144 | } 145 | tStartOfString = &sStringBuffer[1]; 146 | } else { 147 | myLCD.print('-'); 148 | tStartOfString = &sStringBuffer[2]; 149 | } 150 | } 151 | dtostrf(aFloatValue, aNumberOfCharactersToPrint, tNumberOfDecimalPlaces, sStringBuffer); 152 | myLCD.print(tStartOfString); 153 | } 154 | 155 | void testPrintFloatValueRightAlignedOnLCD() { 156 | Serial.println(F("testPrintFloatValueRightAlignedOnLCD: 1.")); 157 | 158 | myLCD.clear(); 159 | float tTestValue = 123.45; 160 | printFloatValueRightAlignedOnLCD(tTestValue, 6); 161 | printFloatValueRightAlignedOnLCD(tTestValue, 5); 162 | printFloatValueRightAlignedOnLCD(tTestValue, 4); // no leading space here 163 | printFloatValueRightAlignedOnLCD(tTestValue, 3); // no leading space here 164 | // Result=" 123.4 123 123123" 165 | 166 | myLCD.setCursor(0, 1); 167 | printFloatValueRightAlignedOnLCD(-tTestValue, 6); 168 | printFloatValueRightAlignedOnLCD(-tTestValue, 5); 169 | printFloatValueRightAlignedOnLCD(-tTestValue, 4); 170 | printFloatValueRightAlignedOnLCD(-tTestValue, 3); // requires also 5 character 171 | // Result="-123.4 -123-123-123" 172 | 173 | myLCD.setCursor(0, 2); 174 | tTestValue = -1.234; 175 | printFloatValueRightAlignedOnLCD(tTestValue, 6); 176 | printFloatValueRightAlignedOnLCD(tTestValue, 5); 177 | printFloatValueRightAlignedOnLCD(tTestValue, 4); 178 | printFloatValueRightAlignedOnLCD(tTestValue, 3); 179 | printFloatValueRightAlignedOnLCD(tTestValue, 2); 180 | // Result="-1.234-1.23-1.2 -1-1" 181 | 182 | myLCD.setCursor(0, 3); 183 | tTestValue = -0.1234; 184 | printFloatValueRightAlignedOnLCD(tTestValue, 6); 185 | printFloatValueRightAlignedOnLCD(tTestValue, 5); 186 | printFloatValueRightAlignedOnLCD(tTestValue, 4); 187 | printFloatValueRightAlignedOnLCD(tTestValue, 3); 188 | printFloatValueRightAlignedOnLCD(tTestValue, 2); 189 | // Result="-.1234-.123-.12-.1-0" 190 | 191 | delay(4000); 192 | Serial.println(F("testPrintFloatValueRightAlignedOnLCD: 2.")); 193 | 194 | myLCD.clear(); 195 | tTestValue = 123.45; 196 | // no leading space here 197 | printFloatValueRightAlignedOnLCD(tTestValue, 6, true); 198 | printFloatValueRightAlignedOnLCD(tTestValue, 5, true); 199 | printFloatValueRightAlignedOnLCD(tTestValue, 4, true); 200 | printFloatValueRightAlignedOnLCD(tTestValue, 3, true); 201 | // Result="123.45123.4 123123" 202 | 203 | myLCD.setCursor(0, 1); 204 | printFloatValueRightAlignedOnLCD(-tTestValue, 6, true); 205 | printFloatValueRightAlignedOnLCD(-tTestValue, 5, true); 206 | printFloatValueRightAlignedOnLCD(-tTestValue, 4, true); 207 | printFloatValueRightAlignedOnLCD(-tTestValue, 3, true); 208 | // Result="-123.4 -123-123-123" 209 | 210 | tTestValue = 1.2344; // .12345 leads to rounding up for .1235 211 | myLCD.setCursor(0, 2); 212 | printFloatValueRightAlignedOnLCD(tTestValue, 6, true); 213 | printFloatValueRightAlignedOnLCD(tTestValue, 5, true); 214 | printFloatValueRightAlignedOnLCD(tTestValue, 4, true); 215 | printFloatValueRightAlignedOnLCD(tTestValue, 3, true); 216 | printFloatValueRightAlignedOnLCD(tTestValue, 2, true); 217 | // Result="1.23441.2341.231.2 1" 218 | 219 | tTestValue = 0.12344; // .12345 leads to rounding up for .1235 220 | myLCD.setCursor(0, 3); 221 | printFloatValueRightAlignedOnLCD(tTestValue, 6, true); 222 | printFloatValueRightAlignedOnLCD(tTestValue, 5, true); 223 | printFloatValueRightAlignedOnLCD(tTestValue, 4, true); 224 | printFloatValueRightAlignedOnLCD(tTestValue, 3, true); 225 | printFloatValueRightAlignedOnLCD(tTestValue, 2, true); 226 | // Result=".12344.1234.123.12.1" 227 | 228 | delay(4000); 229 | Serial.println(F("testPrintFloatValueRightAlignedOnLCD: End")); 230 | 231 | } 232 | 233 | #if defined(LOCAL_DEBUG) 234 | #undef LOCAL_DEBUG 235 | #endif 236 | #endif // _LCD_PRINT_UTILS_HPP 237 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/LiquidCrystal_I2C.h: -------------------------------------------------------------------------------- 1 | //YWROBOT 2 | #ifndef LiquidCrystal_I2C_h 3 | #define LiquidCrystal_I2C_h 4 | 5 | #include 6 | #include "Print.h" 7 | #if !defined(USE_SOFT_I2C_MASTER) && !defined(USE_SOFT_WIRE) 8 | #include 9 | #endif 10 | 11 | // commands 12 | #define LCD_CLEARDISPLAY 0x01 13 | #define LCD_RETURNHOME 0x02 14 | #define LCD_ENTRYMODESET 0x04 15 | #define LCD_DISPLAYCONTROL 0x08 16 | #define LCD_CURSORSHIFT 0x10 17 | #define LCD_FUNCTIONSET 0x20 18 | #define LCD_SETCGRAMADDR 0x40 19 | #define LCD_SETDDRAMADDR 0x80 20 | 21 | // flags for display entry mode 22 | #define LCD_ENTRYRIGHT 0x00 23 | #define LCD_ENTRYLEFT 0x02 24 | #define LCD_ENTRYSHIFTINCREMENT 0x01 25 | #define LCD_ENTRYSHIFTDECREMENT 0x00 26 | 27 | // flags for display on/off control 28 | #define LCD_DISPLAYON 0x04 29 | #define LCD_DISPLAYOFF 0x00 30 | #define LCD_CURSORON 0x02 31 | #define LCD_CURSOROFF 0x00 32 | #define LCD_BLINKON 0x01 33 | #define LCD_BLINKOFF 0x00 34 | 35 | // flags for display/cursor shift 36 | #define LCD_DISPLAYMOVE 0x08 37 | #define LCD_CURSORMOVE 0x00 38 | #define LCD_MOVERIGHT 0x04 39 | #define LCD_MOVELEFT 0x00 40 | 41 | // flags for function set 42 | #define LCD_8BITMODE 0x10 43 | #define LCD_4BITMODE 0x00 44 | #define LCD_2LINE 0x08 45 | #define LCD_1LINE 0x00 46 | #define LCD_5x10DOTS 0x04 47 | #define LCD_5x8DOTS 0x00 48 | 49 | // flags for backlight control 50 | #define LCD_BACKLIGHT 0x08 51 | #define LCD_NOBACKLIGHT 0x00 52 | 53 | #define En 0b00000100 // Enable bit 54 | #define Rw 0b00000010 // Read/Write bit 55 | #define Rs 0b00000001 // Register select bit 56 | 57 | class LiquidCrystal_I2C : public Print { 58 | public: 59 | LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); 60 | void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS ); 61 | void clear(); 62 | void home(); 63 | void noDisplay(); 64 | void display(); 65 | void noBlink(); 66 | void blink(); 67 | void noCursor(); 68 | void cursor(); 69 | void scrollDisplayLeft(); 70 | void scrollDisplayRight(); 71 | void printLeft(); 72 | void printRight(); 73 | void leftToRight(); 74 | void rightToLeft(); 75 | void shiftIncrement(); 76 | void shiftDecrement(); 77 | void noBacklight(); 78 | void backlight(); 79 | void autoscroll(); 80 | void noAutoscroll(); 81 | void createChar(uint8_t, uint8_t[]); 82 | void createChar(uint8_t location, const char *charmap); 83 | // Example: const char bell[8] PROGMEM = {B00100,B01110,B01110,B01110,B11111,B00000,B00100,B00000}; 84 | 85 | void setCursor(uint8_t, uint8_t); 86 | size_t write(uint8_t); 87 | void command(uint8_t); 88 | void init(); 89 | void oled_init(); 90 | 91 | ////compatibility API function aliases 92 | void blink_on(); // alias for blink() 93 | void blink_off(); // alias for noBlink() 94 | void cursor_on(); // alias for cursor() 95 | void cursor_off(); // alias for noCursor() 96 | void setBacklight(uint8_t new_val); // alias for backlight() and nobacklight() 97 | void load_custom_character(uint8_t char_num, uint8_t *rows); // alias for createChar() 98 | void printstr(const char[]); 99 | 100 | ////Unsupported API functions (not implemented in this library) 101 | uint8_t status(); 102 | void setContrast(uint8_t new_val); 103 | uint8_t keypad(); 104 | void setDelay(int,int); 105 | void on(); 106 | void off(); 107 | uint8_t init_bargraph(uint8_t graphtype); 108 | void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end); 109 | void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end); 110 | 111 | 112 | private: 113 | void init_priv(); 114 | void send(uint8_t, uint8_t); 115 | void write4bits(uint8_t); 116 | void expanderWrite(uint8_t); 117 | void pulseEnable(uint8_t); 118 | uint8_t _Addr; 119 | uint8_t _displayfunction; 120 | uint8_t _displaycontrol; 121 | uint8_t _displaymode; 122 | uint8_t _numlines; 123 | bool _oled; 124 | uint8_t _cols; 125 | uint8_t _rows; 126 | uint8_t _backlightval; 127 | }; 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/LiquidCrystal_I2C.hpp: -------------------------------------------------------------------------------- 1 | // LiquidCrystal_I2C.hpp 2 | // Based on the work by DFRobot 3 | /* 4 | * Extensions made by AJ 2023 5 | * Removed Arduino 0.x support 6 | * Added SoftI2CMaste support, which drastically reduces program size. 7 | * Added OLED stuff 8 | * Added createChar() with PROGMEM input 9 | * Added fast timing 10 | */ 11 | #ifndef _LIQUID_CRYSTAL_I2C_HPP 12 | #define _LIQUID_CRYSTAL_I2C_HPP 13 | 14 | #include "Arduino.h" 15 | 16 | #if defined(__AVR__) && !defined(USE_SOFT_I2C_MASTER) && __has_include("SoftI2CMasterConfig.h") 17 | #define USE_SOFT_I2C_MASTER // must be before #include "LiquidCrystal_I2C.h" 18 | #endif 19 | 20 | #include "LiquidCrystal_I2C.h" 21 | #include 22 | 23 | inline size_t LiquidCrystal_I2C::write(uint8_t value) { 24 | send(value, Rs); 25 | return 1; 26 | } 27 | 28 | #if defined(USE_SOFT_I2C_MASTER) 29 | //#define USE_SOFT_I2C_MASTER_H_AS_PLAIN_INCLUDE 30 | #include "SoftI2CMasterConfig.h" // Include configuration for sources 31 | #include "SoftI2CMaster.h" // include sources 32 | #elif defined(USE_SOFT_WIRE) 33 | #define USE_SOFTWIRE_H_AS_PLAIN_INCLUDE 34 | #include "SoftWire.h" 35 | #endif 36 | 37 | #if defined(__AVR__) 38 | /* 39 | * The datasheet says: a command need > 37us to settle. Enable pulse must be > 450ns. 40 | * Use no delay for enable pulse after each command, 41 | * because the overhead of this library seems to be using the 37 us and 450 ns. 42 | * At least it works perfectly for all my LCD's connected to Uno, Nano etc. 43 | * and it saves a lot of time in realtime applications using LCD as display, 44 | * like https://github.com/ArminJo/Arduino-DTSU666H_PowerMeter 45 | */ 46 | #define USE_FAST_TIMING 47 | #endif 48 | 49 | // When the display powers up, it is configured as follows: 50 | // 51 | // 1. Display clear 52 | // 2. Function set: 53 | // DL = 1; 8-bit interface data 54 | // N = 0; 1-line display 55 | // F = 0; 5x8 dot character font 56 | // 3. Display on/off control: 57 | // D = 0; Display off 58 | // C = 0; Cursor off 59 | // B = 0; Blinking off 60 | // 4. Entry mode set: 61 | // I/D = 1; Increment by 1 62 | // S = 0; No shift 63 | // 64 | // Note, however, that resetting the Arduino doesn't reset the LCD, so we 65 | // can't assume that its in that state when a sketch starts (and the 66 | // LiquidCrystal constructor is called). 67 | 68 | LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t lcd_cols, uint8_t lcd_rows) { 69 | _Addr = lcd_Addr; 70 | _cols = lcd_cols; 71 | _rows = lcd_rows; 72 | _backlightval = LCD_NOBACKLIGHT; 73 | _oled = false; 74 | } 75 | 76 | void LiquidCrystal_I2C::oled_init() { 77 | _oled = true; 78 | init_priv(); 79 | } 80 | 81 | void LiquidCrystal_I2C::init() { 82 | init_priv(); 83 | } 84 | 85 | void LiquidCrystal_I2C::init_priv() { 86 | #if defined(USE_SOFT_I2C_MASTER) 87 | i2c_init(); 88 | #else 89 | Wire.begin(); 90 | #endif 91 | _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; 92 | begin(_cols, _rows); 93 | } 94 | 95 | void LiquidCrystal_I2C::begin(uint8_t cols __attribute__((unused)), uint8_t lines, uint8_t dotsize) { 96 | if (lines > 1) { 97 | _displayfunction |= LCD_2LINE; 98 | } 99 | _numlines = lines; 100 | 101 | // for some 1 line displays you can select a 10 pixel high font 102 | if ((dotsize != 0) && (lines == 1)) { 103 | _displayfunction |= LCD_5x10DOTS; 104 | } 105 | 106 | // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! 107 | // according to datasheet, we need at least 40ms after power rises above 2.7V 108 | // before sending commands. Arduino can turn on way before 4.5V so we'll wait 50 109 | delay(50); 110 | 111 | // Now we pull both RS and R/W low to begin commands 112 | expanderWrite(_backlightval); // reset expander and turn backlight off (Bit 8 =1) 113 | delay(1000); 114 | 115 | //put the LCD into 4 bit mode 116 | // this is according to the hitachi HD44780 datasheet 117 | // figure 24, pg 46 118 | 119 | // we start in 8bit mode, try to set 4 bit mode 120 | write4bits(0x03 << 4); 121 | delayMicroseconds(4500); // wait min 4.1ms 122 | 123 | // second try 124 | write4bits(0x03 << 4); 125 | delayMicroseconds(4500); // wait min 4.1ms 126 | 127 | // third go! 128 | write4bits(0x03 << 4); 129 | delayMicroseconds(150); 130 | 131 | // finally, set to 4-bit interface 132 | write4bits(0x02 << 4); 133 | 134 | // set # lines, font size, etc. 135 | command(LCD_FUNCTIONSET | _displayfunction); 136 | 137 | // turn the display on with no cursor or blinking default 138 | _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; 139 | display(); 140 | 141 | // clear it off 142 | clear(); 143 | 144 | // Initialize to default text direction (for roman languages) 145 | _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; 146 | 147 | // set the entry mode 148 | command(LCD_ENTRYMODESET | _displaymode); 149 | 150 | home(); 151 | 152 | } 153 | 154 | /********** high level commands, for the user! */ 155 | void LiquidCrystal_I2C::clear() { 156 | command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero 157 | #if defined(USE_FAST_TIMING) 158 | delayMicroseconds(1500); // this command takes a long time! // AJ 20.9.23 1200 is too short for my 2004 LCD's, 1400 is OK 159 | #else 160 | delayMicroseconds(2000); // this command takes a long time! 161 | #endif 162 | if (_oled) 163 | setCursor(0, 0); 164 | } 165 | 166 | void LiquidCrystal_I2C::home() { 167 | command(LCD_RETURNHOME); // set cursor position to zero 168 | delayMicroseconds(2000); // this command takes a long time! 169 | } 170 | 171 | void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row) { 172 | int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 }; 173 | if (row > _numlines) { 174 | row = _numlines - 1; // we count rows starting w/0 175 | } 176 | command(LCD_SETDDRAMADDR | (col + row_offsets[row])); 177 | } 178 | 179 | // Turn the display on/off (quickly) 180 | void LiquidCrystal_I2C::noDisplay() { 181 | _displaycontrol &= ~LCD_DISPLAYON; 182 | command(LCD_DISPLAYCONTROL | _displaycontrol); 183 | } 184 | void LiquidCrystal_I2C::display() { 185 | _displaycontrol |= LCD_DISPLAYON; 186 | command(LCD_DISPLAYCONTROL | _displaycontrol); 187 | } 188 | 189 | // Turns the underline cursor on/off 190 | void LiquidCrystal_I2C::noCursor() { 191 | _displaycontrol &= ~LCD_CURSORON; 192 | command(LCD_DISPLAYCONTROL | _displaycontrol); 193 | } 194 | void LiquidCrystal_I2C::cursor() { 195 | _displaycontrol |= LCD_CURSORON; 196 | command(LCD_DISPLAYCONTROL | _displaycontrol); 197 | } 198 | 199 | // Turn on and off the blinking cursor 200 | void LiquidCrystal_I2C::noBlink() { 201 | _displaycontrol &= ~LCD_BLINKON; 202 | command(LCD_DISPLAYCONTROL | _displaycontrol); 203 | } 204 | void LiquidCrystal_I2C::blink() { 205 | _displaycontrol |= LCD_BLINKON; 206 | command(LCD_DISPLAYCONTROL | _displaycontrol); 207 | } 208 | 209 | // These commands scroll the display without changing the RAM 210 | void LiquidCrystal_I2C::scrollDisplayLeft(void) { 211 | command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT); 212 | } 213 | void LiquidCrystal_I2C::scrollDisplayRight(void) { 214 | command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT); 215 | } 216 | 217 | // This is for text that flows Left to Right 218 | void LiquidCrystal_I2C::leftToRight(void) { 219 | _displaymode |= LCD_ENTRYLEFT; 220 | command(LCD_ENTRYMODESET | _displaymode); 221 | } 222 | 223 | // This is for text that flows Right to Left 224 | void LiquidCrystal_I2C::rightToLeft(void) { 225 | _displaymode &= ~LCD_ENTRYLEFT; 226 | command(LCD_ENTRYMODESET | _displaymode); 227 | } 228 | 229 | // This will 'right justify' text from the cursor 230 | void LiquidCrystal_I2C::autoscroll(void) { 231 | _displaymode |= LCD_ENTRYSHIFTINCREMENT; 232 | command(LCD_ENTRYMODESET | _displaymode); 233 | } 234 | 235 | // This will 'left justify' text from the cursor 236 | void LiquidCrystal_I2C::noAutoscroll(void) { 237 | _displaymode &= ~LCD_ENTRYSHIFTINCREMENT; 238 | command(LCD_ENTRYMODESET | _displaymode); 239 | } 240 | 241 | // Allows us to fill the first 8 CGRAM locations 242 | // with custom characters 243 | void LiquidCrystal_I2C::createChar(uint8_t location, uint8_t charmap[]) { 244 | location &= 0x7; // we only have 8 locations 0-7 245 | command(LCD_SETCGRAMADDR | (location << 3)); 246 | for (int i = 0; i < 8; i++) { 247 | write(charmap[i]); 248 | } 249 | } 250 | 251 | //createChar with PROGMEM input 252 | void LiquidCrystal_I2C::createChar(uint8_t location, const char *charmap) { 253 | location &= 0x7; // we only have 8 locations 0-7 254 | command(LCD_SETCGRAMADDR | (location << 3)); 255 | for (int i = 0; i < 8; i++) { 256 | write(pgm_read_byte_near(charmap++)); 257 | } 258 | } 259 | 260 | // Turn the (optional) backlight off/on 261 | void LiquidCrystal_I2C::noBacklight(void) { 262 | _backlightval = LCD_NOBACKLIGHT; 263 | expanderWrite(0); 264 | } 265 | 266 | void LiquidCrystal_I2C::backlight(void) { 267 | _backlightval = LCD_BACKLIGHT; 268 | expanderWrite(0); 269 | } 270 | 271 | /*********** mid level commands, for sending data/cmds */ 272 | 273 | inline void LiquidCrystal_I2C::command(uint8_t value) { 274 | send(value, 0); 275 | } 276 | 277 | /************ low level data pushing commands **********/ 278 | 279 | // write either command or data 280 | void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) { 281 | uint8_t highnib = value & 0xf0; 282 | uint8_t lownib = (value << 4) & 0xf0; 283 | write4bits((highnib) | mode); 284 | write4bits((lownib) | mode); 285 | } 286 | 287 | void LiquidCrystal_I2C::write4bits(uint8_t value) { 288 | expanderWrite(value); 289 | pulseEnable(value); 290 | } 291 | 292 | void LiquidCrystal_I2C::expanderWrite(uint8_t _data) { 293 | #if defined(USE_SOFT_I2C_MASTER) 294 | i2c_write_byte(_Addr << 1, _data | _backlightval); 295 | #else 296 | Wire.beginTransmission(_Addr); 297 | Wire.write((int )(_data) | _backlightval); 298 | Wire.endTransmission(); 299 | #endif 300 | } 301 | 302 | void LiquidCrystal_I2C::pulseEnable(uint8_t _data) { 303 | expanderWrite(_data | En); // En high 304 | #if !defined(USE_FAST_TIMING) 305 | delayMicroseconds(1); // enable pulse must be > 450ns // AJ 20.9.23 not required for my LCD's 306 | #endif 307 | expanderWrite(_data & ~En); // En low 308 | #if !defined(USE_FAST_TIMING) 309 | delayMicroseconds(50); // commands need > 37us to settle // AJ 20.9.23 not required for my LCD's 310 | #endif 311 | } 312 | 313 | // Alias functions 314 | 315 | void LiquidCrystal_I2C::cursor_on() { 316 | cursor(); 317 | } 318 | 319 | void LiquidCrystal_I2C::cursor_off() { 320 | noCursor(); 321 | } 322 | 323 | void LiquidCrystal_I2C::blink_on() { 324 | blink(); 325 | } 326 | 327 | void LiquidCrystal_I2C::blink_off() { 328 | noBlink(); 329 | } 330 | 331 | void LiquidCrystal_I2C::load_custom_character(uint8_t char_num, uint8_t *rows) { 332 | createChar(char_num, rows); 333 | } 334 | 335 | void LiquidCrystal_I2C::setBacklight(uint8_t new_val) { 336 | if (new_val) { 337 | backlight(); // turn backlight on 338 | } else { 339 | noBacklight(); // turn backlight off 340 | } 341 | } 342 | 343 | void LiquidCrystal_I2C::printstr(const char c[]) { 344 | //This function is not identical to the function used for "real" I2C displays 345 | //it's here so the user sketch doesn't have to be changed 346 | print(c); 347 | } 348 | 349 | // unsupported API functions 350 | #pragma GCC diagnostic push 351 | #pragma GCC diagnostic ignored "-Wunused-parameter" 352 | void LiquidCrystal_I2C::off() { 353 | } 354 | void LiquidCrystal_I2C::on() { 355 | } 356 | void LiquidCrystal_I2C::setDelay(int cmdDelay, int charDelay) { 357 | } 358 | uint8_t LiquidCrystal_I2C::status() { 359 | return 0; 360 | } 361 | uint8_t LiquidCrystal_I2C::keypad() { 362 | return 0; 363 | } 364 | uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype) { 365 | return 0; 366 | } 367 | void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end) { 368 | } 369 | void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_row_end) { 370 | } 371 | void LiquidCrystal_I2C::setContrast(uint8_t new_val) { 372 | } 373 | #pragma GCC diagnostic pop 374 | 375 | #endif // _LIQUID_CRYSTAL_I2C_HPP 376 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/LocalDebugLevelCheck.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LocalDebugLevelCheck.h 3 | * Throw error if LOCAL_TRACE, LOCAL_DEBUG or LOCAL_INFO is defined, which should not be at the start of any hpp file. 4 | * Each LOCAL_* definition must be undefined at the end of the file which defined it using #include "LocalDebugLevelEnd.h". 5 | * 6 | * LOCAL_TRACE // Information you need to understand details of a function or if you hunt a bug. 7 | * LOCAL_DEBUG // Information need to understand the operating of your program. E.g. function calls and values of control variables. 8 | * LOCAL_INFO // Information you want to see in regular operation to see what the program is doing. E.g. "Now playing Muppets melody". 9 | * LOCAL_WARN // Information that the program may encounter problems, like small Heap/Stack area. 10 | * LOCAL_ERROR // Informations to explain why the program will not run. E.g. not enough Ram for all created objects. 11 | * 12 | * Copyright (C) 2025 Armin Joachimsmeyer 13 | * Email: armin.joachimsmeyer@gmail.com 14 | * 15 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 16 | * 17 | * Arduino-Utils is free software: you can redistribute it and/or modify 18 | * it under the terms of the GNU General Public License as published by 19 | * the Free Software Foundation, either version 3 of the License, or 20 | * (at your option) any later version. 21 | * 22 | * This program is distributed in the hope that it will be useful, 23 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 24 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 25 | * See the GNU General Public INFOse for more details. 26 | * 27 | * You should have received a copy of the GNU General Public License 28 | * along with this program. If not, see . 29 | * 30 | */ 31 | 32 | /* 33 | * Check LOCAL_* macros 34 | */ 35 | #if defined(LOCAL_TRACE) 36 | #error "LOCAL_TRACE is enabled at top of included file. Maybe because of missing include of LocalDebugLevelEnd.h at end of previous included file." 37 | #endif 38 | 39 | #if defined(LOCAL_DEBUG) 40 | #error "LOCAL_DEBUG is enabled at top of included file. Maybe because of missing include of LocalDebugLevelEnd.h at end of previous included file." 41 | #endif 42 | 43 | #if defined(LOCAL_INFO) 44 | #error "LOCAL_INFO is enabled at top of included file. Maybe because of missing include of LocalDebugLevelEnd.h at end of previous included file." 45 | #endif 46 | 47 | /* 48 | * Check *_PRINT macros 49 | */ 50 | #if defined(TRACE_PRINT) 51 | #error "TRACE_PRINT is enabled at top of included file. Maybe because of missing include of LocalDebugLevelEnd.h at end of previous included file." 52 | #endif 53 | 54 | #if defined(DEBUG_PRINT) 55 | #error "DEBUG_PRINT is enabled at top of included file. Maybe because of missing include of LocalDebugLevelEnd.h at end of previous included file." 56 | #endif 57 | 58 | #if defined(INFO_PRINT) 59 | #error "INFO_PRINT is enabled at top of included file. Maybe because of missing include of LocalDebugLevelEnd.h at end of previous included file." 60 | #endif 61 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/LocalDebugLevelEnd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LocalDebugLevelEnd.h 3 | * Undefine local macros at the end of an included (.hpp) file 4 | * 5 | * Copyright (C) 2024 Armin Joachimsmeyer 6 | * Email: armin.joachimsmeyer@gmail.com 7 | * 8 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 9 | * 10 | * Arduino-Utils is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 18 | * See the GNU General Public INFOse for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | * 23 | */ 24 | 25 | /* 26 | * Undefine local macros at the end of an included (.hpp) file 27 | */ 28 | #if defined(LOCAL_TRACE) 29 | #undef LOCAL_TRACE 30 | #endif 31 | #undef TRACE_PRINT 32 | #undef TRACE_PRINTLN 33 | #if defined(LOCAL_DEBUG) 34 | #undef LOCAL_DEBUG 35 | #endif 36 | #undef DEBUG_PRINT 37 | #undef DEBUG_PRINTLN 38 | #if defined(LOCAL_INFO) 39 | #undef LOCAL_INFO 40 | #endif 41 | #undef INFO_PRINT 42 | #undef INFO_PRINTLN 43 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/LocalDebugLevelStart.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LocalDebugLevelStart.h 3 | * Include to propagate global debug levels to file local ones and to define appropriate print macros. 4 | * To propagate debug levels to each other, use #include "DebugLevel.h". 5 | * !!! If used in included (.hpp) files, #include "LocalDebugLevelEnd.h" must be used at end of file to undefine local macros. 6 | * 7 | * LOCAL_TRACE // Information you need to understand details of a function or if you hunt a bug. 8 | * LOCAL_DEBUG // Information need to understand the operating of your program. E.g. function calls and values of control variables. 9 | * LOCAL_INFO // Information you want to see in regular operation to see what the program is doing. E.g. "Now playing Muppets melody". 10 | * LOCAL_WARN // Information that the program may encounter problems, like small Heap/Stack area. 11 | * LOCAL_ERROR // Informations to explain why the program will not run. E.g. not enough Ram for all created objects. 12 | * 13 | * Copyright (C) 2024-2025 Armin Joachimsmeyer 14 | * Email: armin.joachimsmeyer@gmail.com 15 | * 16 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 17 | * 18 | * Arduino-Utils is free software: you can redistribute it and/or modify 19 | * it under the terms of the GNU General Public License as published by 20 | * the Free Software Foundation, either version 3 of the License, or 21 | * (at your option) any later version. 22 | * 23 | * This program is distributed in the hope that it will be useful, 24 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 26 | * See the GNU General Public INFOse for more details. 27 | * 28 | * You should have received a copy of the GNU General Public License 29 | * along with this program. If not, see . 30 | * 31 | */ 32 | 33 | /* 34 | * Propagate debug level to local ones but not to each other, i.e. Enabling TRACE does not enable DEBUG and INFO 35 | */ 36 | #if defined(TRACE) 37 | #define LOCAL_TRACE 38 | #endif 39 | 40 | #if defined(DEBUG) 41 | #define LOCAL_DEBUG 42 | #endif 43 | 44 | #if defined(INFO) 45 | #define LOCAL_INFO 46 | #endif 47 | 48 | /* 49 | * Define appropriate print macros 50 | */ 51 | #if defined(LOCAL_TRACE) 52 | #define TRACE_PRINT(...) Serial.print(__VA_ARGS__) 53 | #define TRACE_PRINTLN(...) Serial.println(__VA_ARGS__) 54 | #else 55 | #define TRACE_PRINT(...) void() 56 | #define TRACE_PRINTLN(...) void() 57 | #endif 58 | 59 | #if defined(LOCAL_DEBUG) 60 | #define DEBUG_PRINT(...) Serial.print(__VA_ARGS__) 61 | #define DEBUG_PRINTLN(...) Serial.println(__VA_ARGS__) 62 | #else 63 | #define DEBUG_PRINT(...) void() 64 | #define DEBUG_PRINTLN(...) void() 65 | #endif 66 | 67 | #if defined(LOCAL_INFO) 68 | #define INFO_PRINT(...) Serial.print(__VA_ARGS__) 69 | #define INFO_PRINTLN(...) Serial.println(__VA_ARGS__) 70 | #else 71 | #define INFO_PRINT(...) void() 72 | #define INFO_PRINTLN(...) void() 73 | #endif 74 | 75 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/LongUnion.h: -------------------------------------------------------------------------------- 1 | /* 2 | * LongUnion.h 3 | * 4 | * Copyright (C) 2020-2022 Armin Joachimsmeyer 5 | * Email: armin.joachimsmeyer@gmail.com 6 | * 7 | * This file is part of Arduino-Utils https://github.com/ArminJo/Arduino-Utils. 8 | * 9 | * Arduino-Utils is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | * See the GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | * 22 | */ 23 | 24 | #if !defined(_WORD_UNION_H) || !defined(_LONG_UNION_H) || !defined(_LONG_LONG_UNION_H) 25 | 26 | #include 27 | 28 | #ifndef _WORD_UNION_H 29 | #define _WORD_UNION_H 30 | /** 31 | * Union to specify parts / manifestations of a 16 bit Word without casts and shifts. 32 | * It also supports the compiler generating small code. 33 | * Usage: WordUnion tWord; 34 | * tWord.UByte.HighByte = 0x12; 35 | */ 36 | union WordUnion { 37 | struct { 38 | uint8_t LowByte; 39 | uint8_t HighByte; 40 | } UByte; 41 | struct { 42 | int8_t LowByte; 43 | int8_t HighByte; 44 | } Byte; 45 | uint8_t UBytes[2]; // UBytes[0] is LowByte 46 | int8_t Bytes[2]; 47 | uint16_t UWord; 48 | int16_t Word; 49 | uint8_t *BytePointer; 50 | }; 51 | #endif // _WORD_UNION_H 52 | 53 | #ifndef _LONG_UNION_H 54 | #define _LONG_UNION_H 55 | /** 56 | * Union to specify parts / manifestations of a 32 bit Long without casts and shifts. 57 | * It also supports the compiler generating small code. 58 | */ 59 | union LongUnion { 60 | struct { 61 | uint8_t LowByte; 62 | uint8_t MidLowByte; 63 | uint8_t MidHighByte; 64 | uint8_t HighByte; 65 | } UByte; 66 | struct { 67 | int8_t LowByte; 68 | int8_t MidLowByte; 69 | int8_t MidHighByte; 70 | int8_t HighByte; 71 | } Byte; 72 | /* Does not work for STM32 73 | struct { 74 | uint8_t LowByte; 75 | uint16_t MidWord; 76 | uint8_t HighByte; 77 | } UByteWord; 78 | */ 79 | struct { 80 | uint16_t LowWord; 81 | uint16_t HighWord; 82 | } UWord; 83 | struct { 84 | int16_t LowWord; 85 | int16_t HighWord; 86 | } Word; 87 | struct { 88 | WordUnion LowWord; 89 | WordUnion HighWord; 90 | } TwoWordUnions; 91 | uint8_t UBytes[4]; // seems to have the same code size as using struct UByte 92 | int8_t Bytes[4]; // Bytes[0] is LowByte 93 | uint16_t UWords[2]; 94 | int16_t Words[2]; 95 | uint32_t ULong; 96 | int32_t Long; 97 | float Float; 98 | }; 99 | #endif // _LONG_UNION_H 100 | 101 | #ifndef _LONG_LONG_UNION_H 102 | #define _LONG_LONG_UNION_H 103 | /** 104 | * Union to specify parts / manifestations of a 64 bit LongLong without casts and shifts. 105 | * It also supports the compiler generating small code. 106 | */ 107 | union LongLongUnion { 108 | struct { 109 | uint16_t LowWord; 110 | uint16_t MidLowWord; 111 | uint16_t MidHighWord; 112 | uint16_t HighWord; 113 | } UWord; 114 | struct { 115 | int16_t LowWord; 116 | int16_t MidLowWord; 117 | int16_t MidHighWord; 118 | int16_t HighWord; 119 | } Word; 120 | struct { 121 | WordUnion LowWord; 122 | WordUnion MidLowWord; 123 | WordUnion MidHighWord; 124 | WordUnion HighWord; 125 | } FourWordUnions; 126 | struct { 127 | uint32_t LowLong; 128 | uint32_t HighLong; 129 | } ULong; 130 | struct { 131 | int32_t LowLong; 132 | int32_t HighLong; 133 | } Long; 134 | struct { 135 | LongUnion LowLong; 136 | LongUnion HighLong; 137 | } TwoLongUnions; 138 | uint8_t UBytes[8]; // seems to have the same code size as using struct UByte 139 | int8_t Bytes[8]; 140 | uint16_t UWords[4]; 141 | int16_t Words[4]; 142 | uint64_t ULongLong; 143 | int64_t LongLong; 144 | #if __DBL_MANT_DIG__== 24 145 | float Floats[2]; // 32 bit double, as for AVR 146 | #else 147 | // 64 bit double 148 | double Double; 149 | #endif 150 | }; 151 | #endif // _LONG_LONG_UNION_H 152 | 153 | #endif // !defined(_WORD_UNION_H) || !defined(_LONG_UNION_H) || !defined(_LONG_LONG_UNION_H) 154 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/MCP2515_TX.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2515_TX.h 3 | * 4 | * 5 | * Copyright (C) 2023 Armin Joachimsmeyer 6 | * Email: armin.joachimsmeyer@gmail.com 7 | * 8 | * This file is part of ArduinoUtils https://github.com/ArminJo/JK-BMSToPylontechCAN. 9 | * 10 | * JK-BMSToPylontechCAN is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 18 | * See the GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | * 23 | */ 24 | 25 | #ifndef _MCP2515_TX_H 26 | #define _MCP2515_TX_H 27 | 28 | #include 29 | 30 | bool initializeCAN(uint32_t aBaudrate, uint8_t aCrystalMHz, Print *aSerial); // Return true if error happens 31 | bool sendCANMessage(uint16_t aCANId, uint8_t aLengthOfBuffer, const uint8_t *aSendDataBufferPointer); // Return true if error happens 32 | #endif // _MCP2515_TX_H 33 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/MCP2515_TX.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * MCP2515_TX.hpp 3 | * 4 | * Functions to control send only functions for MCP2515 CAN controller 5 | * 6 | * 7 | * Copyright (C) 2023-2024 Armin Joachimsmeyer 8 | * Email: armin.joachimsmeyer@gmail.com 9 | * 10 | * This file is part of ArduinoUtils https://github.com/ArminJo/JK-BMSToPylontechCAN. 11 | * 12 | * JK-BMSToPylontechCAN is free software: you can redistribute it and/or modify 13 | * it under the terms of the GNU General Public License as published by 14 | * the Free Software Foundation, either version 3 of the License, or 15 | * (at your option) any later version. 16 | * 17 | * This program is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 20 | * See the GNU General Public License for more details. 21 | * 22 | * You should have received a copy of the GNU General Public License 23 | * along with this program. If not, see . 24 | * 25 | */ 26 | 27 | #ifndef _MCP2515_TX_HPP 28 | #define _MCP2515_TX_HPP 29 | 30 | #include "SPI_.h" 31 | #include "mcp2515_can_dfs.h" 32 | #include "digitalWriteFast.h" 33 | 34 | SPISettings sSPISettings(4000000, MSBFIRST, SPI_MODE0); 35 | #if !defined SPI_CS_PIN 36 | #define SPI_CS_PIN 9 // Pin 9 seems to be the default pin for the Arduino CAN bus shield. Alternately you can use pin 10 on this shield 37 | #endif 38 | 39 | #define MCP2515_RETURN_OK false 40 | 41 | #define MCP2515_CAN_CONTROL_REGISTER_CONTENT MODE_NORMAL // default mode 42 | //#define MCP2515_CAN_CONTROL_REGISTER_CONTENT MODE_ONESHOT | CLKOUT_ENABLE; // Alternative mode with no resending and clock output at pin 3 43 | 44 | void resetMCP2515(void) { 45 | SPI.beginTransaction(sSPISettings); 46 | digitalWriteFast(SPI_CS_PIN, LOW); 47 | SPI.transfer(0xc0); 48 | digitalWriteFast(SPI_CS_PIN, HIGH); 49 | SPI.endTransaction(); 50 | delayMicroseconds(10); 51 | } 52 | 53 | uint8_t readMCP2515Register(uint8_t address) { 54 | uint8_t value; 55 | 56 | SPI.beginTransaction(sSPISettings); 57 | digitalWriteFast(SPI_CS_PIN, LOW); 58 | SPI.transfer(0x03); 59 | SPI.transfer(address); 60 | value = SPI.transfer(0x00); 61 | digitalWriteFast(SPI_CS_PIN, HIGH); 62 | SPI.endTransaction(); 63 | 64 | return value; 65 | } 66 | 67 | void modifyMCP2515Register(uint8_t address, uint8_t mask, uint8_t value) { 68 | SPI.beginTransaction(sSPISettings); 69 | digitalWriteFast(SPI_CS_PIN, LOW); 70 | SPI.transfer(0x05); 71 | SPI.transfer(address); 72 | SPI.transfer(mask); 73 | SPI.transfer(value); 74 | digitalWriteFast(SPI_CS_PIN, HIGH); 75 | SPI.endTransaction(); 76 | } 77 | 78 | void writeMCP2515Register(uint8_t address, uint8_t value) { 79 | SPI.beginTransaction(sSPISettings); 80 | digitalWriteFast(SPI_CS_PIN, LOW); 81 | SPI.transfer(0x02); 82 | SPI.transfer(address); 83 | SPI.transfer(value); 84 | digitalWriteFast(SPI_CS_PIN, HIGH); 85 | SPI.endTransaction(); 86 | } 87 | 88 | const char StringAccessFailed[] PROGMEM = " access to MCP2515 config mode register failed"; 89 | 90 | /* 91 | * return true if error happens 92 | */ 93 | bool initializeCAN(uint32_t aBaudrate, uint8_t aCrystalMHz, Print *aSerial) { // Using Print class saves 95 bytes flash 94 | pinModeFast(SPI_CS_PIN, OUTPUT); 95 | 96 | SPI.begin(); // start SPI 97 | 98 | resetMCP2515(); // Reset MCP2515 99 | 100 | // Set Configuration mode 101 | writeMCP2515Register(MCP_CANCTRL, MODE_CONFIG); 102 | if (readMCP2515Register(MCP_CANCTRL) != MODE_CONFIG) { 103 | if(aSerial != nullptr) { 104 | aSerial->print(F("First")); 105 | aSerial->println(reinterpret_cast(StringAccessFailed)); 106 | } 107 | return true; 108 | } 109 | 110 | /* 111 | * Set timing for 16 MHz crystal and 500 kBit/s 112 | */ 113 | if (aBaudrate == 500000) { 114 | /* 115 | * Set timing for 16 MHz crystal and 500 kBit/s 116 | */ 117 | if (aCrystalMHz == 20) { 118 | writeMCP2515Register(MCP_CNF1, MCP_20MHz_500kBPS_CFG1); // Baud Rate Prescaler 119 | writeMCP2515Register(MCP_CNF2, MCP_20MHz_500kBPS_CFG2); // 0x80 is BTLMODE and always set 120 | writeMCP2515Register(MCP_CNF3, MCP_20MHz_500kBPS_CFG3); 121 | } else if (aCrystalMHz == 16) { 122 | writeMCP2515Register(MCP_CNF1, MCP_16MHz_500kBPS_CFG1); // Baud Rate Prescaler 123 | writeMCP2515Register(MCP_CNF2, MCP_16MHz_500kBPS_CFG2); // 0x80 is BTLMODE and always set 124 | writeMCP2515Register(MCP_CNF3, MCP_16MHz_500kBPS_CFG3); 125 | } else { 126 | if(aSerial != nullptr) { 127 | aSerial->println(F("500 kB is not working stable with 8 MHz crystal")); 128 | } 129 | return true; 130 | } 131 | } else if (aBaudrate == 250000) { 132 | if (aCrystalMHz == 16) { 133 | writeMCP2515Register(MCP_CNF1, MCP_16MHz_250kBPS_CFG1); // Baud Rate Prescaler 134 | writeMCP2515Register(MCP_CNF2, MCP_16MHz_250kBPS_CFG2); // 0x80 is BTLMODE and always set 135 | writeMCP2515Register(MCP_CNF3, MCP_16MHz_250kBPS_CFG3); 136 | } else { 137 | writeMCP2515Register(MCP_CNF1, MCP_8MHz_250kBPS_CFG1); // Baud Rate Prescaler 138 | writeMCP2515Register(MCP_CNF2, MCP_8MHz_250kBPS_CFG2); // 0x80 is BTLMODE and always set 139 | writeMCP2515Register(MCP_CNF3, MCP_8MHz_250kBPS_CFG3); 140 | } 141 | /* 142 | * Other baud rates and crystal combinations can be added like above 143 | * The compiler optimizer only uses the active code :-) 144 | */ 145 | } 146 | 147 | // Reset Configuration mode 148 | writeMCP2515Register(MCP_CANCTRL, MCP2515_CAN_CONTROL_REGISTER_CONTENT); 149 | if (readMCP2515Register(MCP_CANCTRL) != MCP2515_CAN_CONTROL_REGISTER_CONTENT) { 150 | if(aSerial != nullptr) { 151 | aSerial->println(F("Last")); 152 | aSerial->println(reinterpret_cast(StringAccessFailed)); 153 | } 154 | return true; 155 | } 156 | 157 | return false; 158 | } 159 | 160 | /* 161 | * return true if error happens 162 | */ 163 | bool sendCANMessage(uint16_t aCANId, uint8_t aLengthOfBuffer, const uint8_t *aSendDataBufferPointer) { 164 | 165 | /* 166 | * We use transmit buffer 0 167 | */ 168 | writeMCP2515Register(MCP_TXB0SIDH, aCANId >> 3); // write bit 3:10 of ID 169 | writeMCP2515Register(MCP_TXB0SIDL, aCANId << 5); // write bit 0:2 and flag "no extended" 170 | writeMCP2515Register(MCP_TXB0DLC, aLengthOfBuffer); 171 | 172 | // Fill buffer 173 | for (uint_fast8_t i = 0; i < aLengthOfBuffer; i++) { 174 | writeMCP2515Register(MCP_TXB0D0 + i, aSendDataBufferPointer[i]); 175 | } 176 | 177 | writeMCP2515Register(MCP_TXB0CTRL, MCP_TXB_TXREQ_M); 178 | 179 | /* 180 | * Check for end of transmission, and if an error happened 181 | */ 182 | while (readMCP2515Register(MCP_TXB0CTRL) & MCP_TXB_TXREQ_M) { 183 | if (readMCP2515Register(MCP_TXB0CTRL) & (MCP_TXB_TXERR_M | MCP_TXB_MLOA_M | MCP_TXB_ABTF_M)) { 184 | /* 185 | * Error happened here, abort transfer. First retransmit is still pending! 186 | */ 187 | writeMCP2515Register(MCP_CANCTRL, ABORT_TX | MCP2515_CAN_CONTROL_REGISTER_CONTENT); // Set "Abort All Pending Transmissions" bit 188 | delayMicroseconds(10); 189 | writeMCP2515Register(MCP_CANCTRL, MCP2515_CAN_CONTROL_REGISTER_CONTENT); // Reset "Abort All Pending Transmissions" bit 190 | return true; // Error 191 | } 192 | } 193 | 194 | return false; 195 | } 196 | #endif // _MCP2515_TX_HPP 197 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/Pylontech_CAN.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Pylontech_CAN.hpp 3 | * 4 | * Functions to fill and send CAN data defined in Pylontech_CAN.h 5 | * 6 | * Useful links: 7 | * https://www.setfirelabs.com/green-energy/pylontech-can-reading-can-replication 8 | * https://www.skpang.co.uk/products/teensy-4-1-triple-can-board-with-240x240-ips-lcd 9 | * 10 | * Copyright (C) 2023-2024 Armin Joachimsmeyer 11 | * Email: armin.joachimsmeyer@gmail.com 12 | * 13 | * This file is part of ArduinoUtils https://github.com/ArminJo/JK-BMSToPylontechCAN. 14 | * 15 | * JK-BMSToPylontechCAN is free software: you can redistribute it and/or modify 16 | * it under the terms of the GNU General Public License as published by 17 | * the Free Software Foundation, either version 3 of the License, or 18 | * (at your option) any later version. 19 | * 20 | * This program is distributed in the hope that it will be useful, 21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 23 | * See the GNU General Public License for more details. 24 | * 25 | * You should have received a copy of the GNU General Public License 26 | * along with this program. If not, see . 27 | * 28 | */ 29 | 30 | #ifndef _PYLONTECH_CAN_HPP 31 | #define _PYLONTECH_CAN_HPP 32 | 33 | #include 34 | 35 | #include "MCP2515_TX.h" // my reduced driver 36 | #include "Pylontech_CAN.h" 37 | 38 | struct PylontechCANBatteryLimitsFrame351Struct PylontechCANBatteryLimitsFrame351; 39 | struct PylontechCANSohSocFrame355Struct PylontechCANSohSocFrame355; 40 | struct PylontechCANCurrentValuesFrame356Struct PylontechCANCurrentValuesFrame356; 41 | struct PylontechCANBatteryRequesFrame35CStruct PylontechCANBatteryRequestFrame35C; 42 | struct PylontechCANErrorsWarningsFrame359Struct PylontechCANErrorsWarningsFrame359; 43 | // Extensions to the standard Pylontech protocol 44 | struct PylontechCANSMACapacityFrame35FStruct PylontechCANSMACapacityFrame35F; 45 | struct PylontechCANLuxpowerCapacityFrame379Struct PylontechCANLuxpowerCapacityFrame379; 46 | struct BYDCANCellLimitsFrame373Struct BYDCANCellLimitsFrame373; 47 | 48 | // Frames with fixed data 49 | struct PylontechCANManufacturerFrameStruct35E PylontechCANManufacturerFrame35E; 50 | struct PylontechCANAliveFrame305Struct PylontechCANAliveFrame305; 51 | 52 | void modifyCANData(); // user function, which currently enables the function to reduce max current at high SOC level 53 | 54 | void fillAllCANData(JK_BMS *aJK_BMS_Ptr) { 55 | PylontechCANBatteryLimitsFrame351.fillFrame(aJK_BMS_Ptr); 56 | PylontechCANSohSocFrame355.fillFrame(aJK_BMS_Ptr); 57 | PylontechCANBatteryRequestFrame35C.fillFrame(aJK_BMS_Ptr); 58 | PylontechCANErrorsWarningsFrame359.fillFrame(aJK_BMS_Ptr); 59 | PylontechCANCurrentValuesFrame356.fillFrame(aJK_BMS_Ptr); 60 | #if defined(CAPACITY_35F_EXTENSIONS) 61 | PylontechCANSMACapacityFrame35F.fillFrame(aJK_BMS_Ptr); 62 | #endif 63 | #if defined(CAPACITY_379_EXTENSIONS) 64 | PylontechCANLuxpowerCapacityFrame379.fillFrame(aJK_BMS_Ptr); 65 | #endif 66 | #if defined(BYD_LIMITS_373_EXTENSIONS) 67 | BYDCANCellLimitsFrame373.fillFrame(aJK_BMS_Ptr); 68 | #endif 69 | #if defined(CAN_DATA_MODIFICATION) 70 | modifyCANData(); 71 | #endif 72 | } 73 | 74 | void sendCANFrame(struct CANFrameStruct *aPylontechCANFrame) { 75 | sendCANMessage(aPylontechCANFrame->CANFrameInfo.CANId, aPylontechCANFrame->CANFrameInfo.FrameLength, 76 | aPylontechCANFrame->FrameData.UBytes); 77 | } 78 | 79 | /* 80 | * Called in case of BMS communication timeout 81 | */ 82 | void modifyAllCanDataToInactive() { 83 | PylontechCANCurrentValuesFrame356.FrameData.Current100Milliampere = 0; 84 | // Clear all requests in case of timeout / BMS switched off, before sending 85 | reinterpret_cast(&PylontechCANBatteryRequestFrame35C)->FrameData.UWords[0] = 0; 86 | reinterpret_cast(&PylontechCANErrorsWarningsFrame359)->FrameData.ULong.LowLong = 0; 87 | } 88 | 89 | void printCANFrame(struct CANFrameStruct *aPylontechCANFrame) { 90 | Serial.print(F("CANId=0x")); 91 | Serial.print(aPylontechCANFrame->CANFrameInfo.CANId, HEX); 92 | Serial.print(F(", FrameLength=")); 93 | Serial.print(aPylontechCANFrame->CANFrameInfo.FrameLength); 94 | Serial.print(F(", Data=0x")); 95 | for (uint_fast8_t i = 0; i < aPylontechCANFrame->CANFrameInfo.FrameLength; ++i) { 96 | if (i != 0) { 97 | Serial.print(F(", 0x")); 98 | } 99 | Serial.print(aPylontechCANFrame->FrameData.UBytes[i], HEX); 100 | } 101 | Serial.println(); 102 | } 103 | 104 | /* 105 | * Inverter reply every second: 0x305: 00-00-00-00-00-00-00-00 106 | * If no CAN receiver is attached, every frame is retransmitted once, because of the NACK error. 107 | * Or use CAN.writeRegister(REG_CANCTRL, 0x08); // One Shot Mode 108 | */ 109 | void sendAllCANFrames(bool aDebugModeActive) { 110 | if (aDebugModeActive) { 111 | printCANFrame(reinterpret_cast(&PylontechCANBatteryLimitsFrame351)); 112 | printCANFrame(reinterpret_cast(&PylontechCANSohSocFrame355)); 113 | printCANFrame(reinterpret_cast(&PylontechCANCurrentValuesFrame356)); 114 | printCANFrame(reinterpret_cast(&PylontechCANManufacturerFrame35E)); 115 | printCANFrame(reinterpret_cast(&PylontechCANBatteryRequestFrame35C)); 116 | printCANFrame(reinterpret_cast(&PylontechCANAliveFrame305)); 117 | printCANFrame(reinterpret_cast(&PylontechCANErrorsWarningsFrame359)); 118 | #if defined(CAPACITY_35F_EXTENSIONS) 119 | printCANFrame(reinterpret_cast(&PylontechCANSMACapacityFrame35F)); 120 | #endif 121 | #if defined(CAPACITY_379_EXTENSIONS) 122 | printCANFrame(reinterpret_cast(&PylontechCANLuxpowerCapacityFrame379)); 123 | #endif 124 | #if defined(BYD_LIMITS_373_EXTENSIONS) 125 | printCANFrame(reinterpret_cast(&BYDCANCellLimitsFrame373)); 126 | #endif 127 | 128 | } 129 | sendCANFrame(reinterpret_cast(&PylontechCANBatteryLimitsFrame351)); 130 | sendCANFrame(reinterpret_cast(&PylontechCANSohSocFrame355)); 131 | sendCANFrame(reinterpret_cast(&PylontechCANCurrentValuesFrame356)); 132 | sendCANFrame(reinterpret_cast(&PylontechCANManufacturerFrame35E)); 133 | sendCANFrame(reinterpret_cast(&PylontechCANBatteryRequestFrame35C)); 134 | sendCANFrame(reinterpret_cast(&PylontechCANAliveFrame305)); 135 | sendCANFrame(reinterpret_cast(&PylontechCANErrorsWarningsFrame359)); 136 | #if defined(CAPACITY_35F_EXTENSIONS) 137 | sendCANFrame(reinterpret_cast(&PylontechCANSMACapacityFrame35F)); 138 | #endif 139 | #if defined(CAPACITY_379_EXTENSIONS) 140 | sendCANFrame(reinterpret_cast(&PylontechCANLuxpowerCapacityFrame379)); 141 | #endif 142 | #if defined(BYD_LIMITS_373_EXTENSIONS) 143 | sendCANFrame(reinterpret_cast(&BYDCANCellLimitsFrame373)); 144 | #endif 145 | } 146 | 147 | /* 148 | * User defined function to modify CAN data sent to inverter. 149 | * Currently implemented is a function to reduce max current at high SOC level 150 | */ 151 | #if !defined(MAX_CURRENT_MODIFICATION_LOWER_SOC_THRESHOLD_PERCENT) 152 | #define MAX_CURRENT_MODIFICATION_LOWER_SOC_THRESHOLD_PERCENT 80 // Start SOC for linear reducing maximum current. Default 80 153 | #endif 154 | #if !defined(MAX_CURRENT_MODIFICATION_MIN_CURRENT_TENTHS_OF_AMPERE) 155 | #define MAX_CURRENT_MODIFICATION_MIN_CURRENT_TENTHS_OF_AMPERE 50 // Value of current at 100 % SOC. Units are 100 mA! Default 50 156 | #endif 157 | 158 | #if !defined(USE_OWN_MODIFY_FUNCTION) && !defined(USE_CCCV_MODIFY_FUNCTION) 159 | void modifyCANData() { 160 | if (JK_BMS_1.JKAllReplyPointer->SOCPercent >= MAX_CURRENT_MODIFICATION_LOWER_SOC_THRESHOLD_PERCENT) { 161 | /* 162 | * Reduce max current linear from 100% at MAX_CURRENT_MODIFICATION_LOWER_SOC_THRESHOLD (80%) SOC 163 | * to MAX_CURRENT_MODIFICATION_MIN_CURRENT_TENTHS_OF_AMPERE (1A) at 100% SOC 164 | */ 165 | PylontechCANBatteryLimitsFrame351.FrameData.BatteryChargeCurrentLimit100Milliampere = map( 166 | JK_BMS_1.JKAllReplyPointer->SOCPercent, 167 | MAX_CURRENT_MODIFICATION_LOWER_SOC_THRESHOLD_PERCENT, 100, 168 | PylontechCANBatteryLimitsFrame351.FrameData.BatteryChargeCurrentLimit100Milliampere, 169 | MAX_CURRENT_MODIFICATION_MIN_CURRENT_TENTHS_OF_AMPERE); 170 | } 171 | } 172 | #elif defined(USE_CCCV_MODIFY_FUNCTION) 173 | /* 174 | * Example from https://github.com/paulsteigel/JK-BMSToPylontechCAN/blob/b96af8e2e39850211ca847f347a632ed57e5ce4f/JK-BMSToPylontechCAN/Pylontech_CAN.hpp#L152 175 | */ 176 | uint8_t ReachChargeLimit(); 177 | void resetCharge(); 178 | // For controlling charge scheme 179 | const uint8_t CHARGE_PHASE_1 = 45; // 45 minutes warming up, charging current will go up in linear mode 180 | const uint8_t CHARGE_PHASE_3 = 45; // 45 minutes warming up, charging current will go down gradually in linear mode 181 | //#define SOC_END_CONSTANT_CURRENT 80 // milestone for changing charging scheme 182 | #define CHARGING_CURRENT_PER_CAPACITY 3 // to be three tenth of capacity 183 | #define MAX_SOC_BULK_CHARGE_THRESHOLD_PERCENT 95 // SOC Level to move to absorption mode 184 | 185 | const uint8_t CHARGE_RATIO = 60L; 186 | const uint32_t MOMENTARY_CHARGE_DURATION = (2L * CHARGE_RATIO * 1000L); // if charge is continuing in this duration, the main charging sequense started 187 | const uint32_t CHARGE_STATUS_REFRESH_INTERVAL = (1L * CHARGE_RATIO * 1000L); // interval for refreshing charge status 188 | 189 | //bool IsCharging = false; // Keep state of charging one getcurrent return positive 190 | //uint16_t Max_Charge_Current_100_milliAmp; // keep peak charging current during warm up for stabilising 191 | 192 | uint16_t Computed_Current_limits_100mA; // 193 | uint16_t Charge_Current_100_milliAmp; 194 | uint32_t StartChargeTime = 0; // Store starting time for charge 195 | uint32_t LastCheckTime = 0; // Record time count for each charging check 196 | uint8_t ChargePhase = 0; // Charging phase (1CC,2CC,3CV) 197 | uint8_t MinuteCount = 0; // counting minutes for charge 198 | uint8_t ChargeTryEffort = 0; // Count of Charge try when battery is full for stopping the charge action from Inverter 199 | 200 | void modifyCANData() { 201 | uint8_t ChargeStatusRef = 0; 202 | uint16_t Local_Charge_Current_100_milliAmp; 203 | 204 | if (JKComputedData.BatteryLoadCurrentFloat > 0) { 205 | ChargeStatusRef = ReachChargeLimit(); 206 | if (StartChargeTime == 0) { 207 | if (ChargeStatusRef >= 1 && ChargeTryEffort > 2) { 208 | // Too much for now, request charging off 209 | ChargeTryEffort++; 210 | PylontechCANBatteryRequestFrame35C.FrameData.ChargeEnable = 0; 211 | Serial.println(F("Setting charge to OFF:")); 212 | return; 213 | } else { 214 | PylontechCANBatteryRequestFrame35C.FrameData.ChargeEnable = 215 | JK_BMS_1.JKAllReplyPointer->BMSStatus.StatusBits.DischargeMosFetActive; 216 | } 217 | StartChargeTime = millis(); // Store starting time for charge 218 | // Get the proper charging current: either BMS limit or using 0.3C 219 | Computed_Current_limits_100mA = min(swap(JK_BMS_1.JKAllReplyPointer->ChargeOvercurrentProtectionAmpere) * 10, 220 | JKComputedData.TotalCapacityAmpereHour * CHARGING_CURRENT_PER_CAPACITY); 221 | Serial.print(F("Charging check: >Selected Current:")); 222 | Serial.println(Computed_Current_limits_100mA); 223 | } 224 | } else { 225 | resetCharge(); 226 | return; 227 | } 228 | 229 | // Get into current charging phase 230 | if (StartChargeTime == 0) return; // No charging detected 231 | // Allow this to be triggered once 232 | if (ChargePhase == 0) { 233 | if ((millis() - StartChargeTime) < MOMENTARY_CHARGE_DURATION) return; 234 | // Charge started in more than MOMENTARY_CHARGE_DURATION, charging started, get into phase 1 235 | //MinuteCount = MOMENTARY_CHARGE_DURATION / (CHARGE_RATIO * 1000L); // Marking the counter for warming up charge 236 | MinuteCount = map(JKComputedData.BatteryLoadCurrentFloat * 10, 1, Computed_Current_limits_100mA, 0, CHARGE_PHASE_1) + 1; 237 | ChargePhase = 1; 238 | Serial.println(F("Enter phase 1:")); 239 | Serial.print(MinuteCount); 240 | } 241 | 242 | // Check if end of phase 1, move to phase 2 or next 243 | if (ChargePhase == 1) { 244 | if ((millis() - StartChargeTime) >= ((CHARGE_PHASE_1 + 1) * CHARGE_RATIO * 1000L)) { 245 | ChargePhase = 2; 246 | MinuteCount = 0; //reset the minute counter to enter phase 2 247 | Serial.print(F("Enter phase 2:")); 248 | Serial.println(MinuteCount); 249 | } 250 | } else if (ChargePhase == 2) { 251 | //ChargeStatusRef = ReachChargeLimit(); 252 | if (ChargeStatusRef == 1) { 253 | ChargePhase = 3; 254 | MinuteCount = 0; //reset the minute counter during phase 2 255 | } else if (ChargeStatusRef == 2) { 256 | // reducing current by 10% 257 | Charge_Current_100_milliAmp = Charge_Current_100_milliAmp * 0.98; 258 | } 259 | } 260 | 261 | if ((millis() - LastCheckTime) < CHARGE_STATUS_REFRESH_INTERVAL) return; 262 | LastCheckTime = millis(); //reset the counter for resuming the check 263 | switch (ChargePhase) { 264 | case 1: 265 | // Linear mapping of charging current until reaching 0.3C 266 | Local_Charge_Current_100_milliAmp = map(MinuteCount, 0, CHARGE_PHASE_1, 1, Computed_Current_limits_100mA); 267 | Charge_Current_100_milliAmp = 268 | (Local_Charge_Current_100_milliAmp > Computed_Current_limits_100mA) ? 269 | Computed_Current_limits_100mA : Local_Charge_Current_100_milliAmp; 270 | PylontechCANBatteryLimitsFrame351.FrameData.BatteryChargeCurrentLimit100Milliampere = Charge_Current_100_milliAmp; 271 | MinuteCount++; 272 | Serial.print(F("Charging phase 1: minute count::")); 273 | Serial.println(MinuteCount); 274 | Serial.print(F("Applied charged current::")); 275 | Serial.print(PylontechCANBatteryLimitsFrame351.FrameData.BatteryChargeCurrentLimit100Milliampere); 276 | Serial.print(F("/")); 277 | Serial.println(Computed_Current_limits_100mA); 278 | 279 | break; 280 | case 2: 281 | // in this phase, do nothing to current at all 282 | Serial.print(F("Charging phase 2: Minute count::")); 283 | Serial.println(MinuteCount); 284 | Serial.print(F("Applied charged current::")); 285 | Serial.print(Charge_Current_100_milliAmp); 286 | Serial.print(F("/")); 287 | Serial.println(Computed_Current_limits_100mA); 288 | MinuteCount++; 289 | break; 290 | case 3: 291 | //Keep charging till full or when the Inverter stop charging, need a new routine for this charging 292 | Serial.print(F("Charging phase 3: Minute count::")); 293 | Serial.println(MinuteCount); 294 | MinuteCount++; 295 | if (ChargeStatusRef != 2) 296 | Charge_Current_100_milliAmp = map(MinuteCount, 1, CHARGE_PHASE_3, Computed_Current_limits_100mA, 0); 297 | PylontechCANBatteryLimitsFrame351.FrameData.BatteryChargeCurrentLimit100Milliampere = Charge_Current_100_milliAmp; 298 | Serial.print(F("Applied charged current::")); 299 | Serial.print(PylontechCANBatteryLimitsFrame351.FrameData.BatteryChargeCurrentLimit100Milliampere); 300 | Serial.print(F("/")); 301 | Serial.println(Computed_Current_limits_100mA); 302 | 303 | if (Charge_Current_100_milliAmp == 0) { 304 | // tell the inverter to stop charging or keep it to maintain the charge 305 | Serial.print(F("End of charge")); 306 | resetCharge(); 307 | } 308 | break; 309 | default: 310 | // statements 311 | break; 312 | } 313 | } 314 | 315 | void resetCharge() { 316 | Serial.println(F("Reset charging parameters")); 317 | StartChargeTime = 0; // reset charge starting time 318 | LastCheckTime = 0; 319 | MinuteCount = 0; 320 | ChargePhase = 0; 321 | ChargeTryEffort = 0; 322 | // recover the charging limits 323 | if (PylontechCANBatteryLimitsFrame351.FrameData.BatteryChargeCurrentLimit100Milliampere 324 | != swap(JK_BMS_1.JKAllReplyPointer->ChargeOvercurrentProtectionAmpere) * 10) { 325 | PylontechCANBatteryLimitsFrame351.FrameData.BatteryChargeCurrentLimit100Milliampere = swap( 326 | JK_BMS_1.JKAllReplyPointer->ChargeOvercurrentProtectionAmpere) * 10; 327 | } 328 | } 329 | 330 | uint8_t ReachChargeLimit() { 331 | /* Status return: 332 | * 0: nothing; 333 | * 1: stop phase; 334 | * 2: reducing current 10%; 335 | */ 336 | //will do this check every 5 minutes 337 | uint16_t Charge_MilliVolt_limit = 0; 338 | if ((millis() - LastCheckTime) < CHARGE_STATUS_REFRESH_INTERVAL) return 0; 339 | // first check over voltage 340 | if (JK_BMS_1.JKAllReplyPointer->BatteryType == 0) { //LFP battery 341 | Charge_MilliVolt_limit = 3450; 342 | } else if (JK_BMS_1.JKAllReplyPointer->BatteryType == 1) { //Lithium ion 343 | Charge_MilliVolt_limit = 4200; 344 | } 345 | // check SOC First 346 | return (JK_BMS_1.JKAllReplyPointer->SOCPercent >= MAX_SOC_BULK_CHARGE_THRESHOLD_PERCENT) ? 1 : 0; 347 | Serial.print(F("Battery type:")); 348 | Serial.println(JK_BMS_1.JKAllReplyPointer->BatteryType); 349 | if ((JKConvertedCellInfo.MaximumCellMillivolt * 1.02) > Charge_MilliVolt_limit) { 350 | Serial.print(F("Status check::")); 351 | Serial.println((JKConvertedCellInfo.MaximumCellMillivolt * 1.02) - Charge_MilliVolt_limit); 352 | return 2; 353 | } 354 | } 355 | #else 356 | /* 357 | * Put your own modification code here :-) 358 | */ 359 | void modifyCANData() { 360 | 361 | } 362 | #endif // !defined(USE_OWN_MODIFY_FUNCTION) && !defined(USE_CCCV_MODIFY_FUNCTION) 363 | 364 | #endif // _PYLONTECH_CAN_HPP 365 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/SPI_.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 by Cristian Maglie 3 | * Copyright (c) 2014 by Paul Stoffregen (Transaction API) 4 | * Copyright (c) 2014 by Matthijs Kooijman (SPISettings AVR) 5 | * Copyright (c) 2014 by Andrew J. Kroll (atomicity fixes) 6 | * SPI Master library for arduino. 7 | * 8 | * This file is free software; you can redistribute it and/or modify 9 | * it under the terms of either the GNU General Public License version 2 10 | * or the GNU Lesser General Public License version 2.1, both as 11 | * published by the Free Software Foundation. 12 | */ 13 | 14 | #include "SPI_.h" 15 | #include "digitalWriteFast.h" 16 | 17 | SPIClass SPI; 18 | 19 | uint8_t SPIClass::initialized = 0; 20 | uint8_t SPIClass::interruptMode = 0; 21 | uint8_t SPIClass::interruptMask = 0; 22 | uint8_t SPIClass::interruptSave = 0; 23 | #if defined(SPI_TRANSACTION_MISMATCH_LED) 24 | uint8_t SPIClass::inTransactionFlag = 0; 25 | #endif 26 | 27 | void SPIClass::begin() 28 | { 29 | uint8_t sreg = SREG; 30 | noInterrupts(); // Protect from a scheduler and prevent transactionBegin 31 | if (!initialized) { 32 | // Set SS to high so a connected chip will be "deselected" by default 33 | uint8_t port = digitalPinToPort(SS); 34 | uint8_t bit = digitalPinToBitMask(SS); 35 | volatile uint8_t *reg = portModeRegister(port); 36 | 37 | // if the SS pin is not already configured as an output 38 | // then set it high (to enable the internal pull-up resistor) 39 | if(!(*reg & bit)){ 40 | digitalWriteFast(SS, HIGH); 41 | } 42 | 43 | // When the SS pin is set as OUTPUT, it can be used as 44 | // a general purpose output port (it doesn't influence 45 | // SPI operations). 46 | pinModeFast(SS, OUTPUT); 47 | 48 | // Warning: if the SS pin ever becomes a LOW INPUT then SPI 49 | // automatically switches to Slave, so the data direction of 50 | // the SS pin MUST be kept as OUTPUT. 51 | SPCR |= _BV(MSTR); 52 | SPCR |= _BV(SPE); 53 | 54 | // Set direction register for SCK and MOSI pin. 55 | // MISO pin automatically overrides to INPUT. 56 | // By doing this AFTER enabling SPI, we avoid accidentally 57 | // clocking in a single bit since the lines go directly 58 | // from "input" to SPI control. 59 | // http://code.google.com/p/arduino/issues/detail?id=888 60 | pinModeFast(SCK, OUTPUT); 61 | pinModeFast(MOSI, OUTPUT); 62 | } 63 | initialized++; // reference count 64 | SREG = sreg; 65 | } 66 | 67 | void SPIClass::end() { 68 | uint8_t sreg = SREG; 69 | noInterrupts(); // Protect from a scheduler and prevent transactionBegin 70 | // Decrease the reference counter 71 | if (initialized) 72 | initialized--; 73 | // If there are no more references disable SPI 74 | if (!initialized) { 75 | SPCR &= ~_BV(SPE); 76 | interruptMode = 0; 77 | #if defined(SPI_TRANSACTION_MISMATCH_LED) 78 | inTransactionFlag = 0; 79 | #endif 80 | } 81 | SREG = sreg; 82 | } 83 | 84 | // mapping of interrupt numbers to bits within SPI_AVR_EIMSK 85 | #if defined(__AVR_ATmega32U4__) 86 | #define SPI_INT0_MASK (1< 3 | * Copyright (c) 2014 by Paul Stoffregen (Transaction API) 4 | * Copyright (c) 2014 by Matthijs Kooijman (SPISettings AVR) 5 | * Copyright (c) 2014 by Andrew J. Kroll (atomicity fixes) 6 | * Copyright (c) 2024 by Armin Joachimsmeyer (digitalWriteFast) 7 | * SPI Master library for arduino. 8 | * 9 | * This file is free software; you can redistribute it and/or modify 10 | * it under the terms of either the GNU General Public License version 2 11 | * or the GNU Lesser General Public License version 2.1, both as 12 | * published by the Free Software Foundation. 13 | */ 14 | 15 | #ifndef _SPI_H_INCLUDED 16 | #define _SPI_H_INCLUDED 17 | 18 | #include 19 | 20 | // SPI_HAS_TRANSACTION means SPI has beginTransaction(), endTransaction(), 21 | // usingInterrupt(), and SPISetting(clock, bitOrder, dataMode) 22 | #define SPI_HAS_TRANSACTION 1 23 | 24 | // SPI_HAS_NOTUSINGINTERRUPT means that SPI has notUsingInterrupt() method 25 | #define SPI_HAS_NOTUSINGINTERRUPT 1 26 | 27 | // SPI_ATOMIC_VERSION means that SPI has atomicity fixes and what version. 28 | // This way when there is a bug fix you can check this define to alert users 29 | // of your code if it uses better version of this library. 30 | // This also implies everything that SPI_HAS_TRANSACTION as documented above is 31 | // available too. 32 | #define SPI_ATOMIC_VERSION 1 33 | 34 | // Uncomment this line to add detection of mismatched begin/end transactions. 35 | // A mismatch occurs if other libraries fail to use SPI.endTransaction() for 36 | // each SPI.beginTransaction(). Connect an LED to this pin. The LED will turn 37 | // on if any mismatch is ever detected. 38 | //#define SPI_TRANSACTION_MISMATCH_LED 5 39 | 40 | #if !defined(LSBFIRST) 41 | #define LSBFIRST 0 42 | #endif 43 | #if !defined(MSBFIRST) 44 | #define MSBFIRST 1 45 | #endif 46 | 47 | #define SPI_CLOCK_DIV4 0x00 48 | #define SPI_CLOCK_DIV16 0x01 49 | #define SPI_CLOCK_DIV64 0x02 50 | #define SPI_CLOCK_DIV128 0x03 51 | #define SPI_CLOCK_DIV2 0x04 52 | #define SPI_CLOCK_DIV8 0x05 53 | #define SPI_CLOCK_DIV32 0x06 54 | 55 | #define SPI_MODE0 0x00 56 | #define SPI_MODE1 0x04 57 | #define SPI_MODE2 0x08 58 | #define SPI_MODE3 0x0C 59 | 60 | #define SPI_MODE_MASK 0x0C // CPOL = bit 3, CPHA = bit 2 on SPCR 61 | #define SPI_CLOCK_MASK 0x03 // SPR1 = bit 1, SPR0 = bit 0 on SPCR 62 | #define SPI_2XCLOCK_MASK 0x01 // SPI2X = bit 0 on SPSR 63 | 64 | // define SPI_AVR_EIMSK for AVR boards with external interrupt pins 65 | #if defined(EIMSK) 66 | #define SPI_AVR_EIMSK EIMSK 67 | #elif defined(GICR) 68 | #define SPI_AVR_EIMSK GICR 69 | #elif defined(GIMSK) 70 | #define SPI_AVR_EIMSK GIMSK 71 | #endif 72 | 73 | class SPISettings { 74 | public: 75 | SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { 76 | if (__builtin_constant_p(clock)) { 77 | init_AlwaysInline(clock, bitOrder, dataMode); 78 | } else { 79 | init_MightInline(clock, bitOrder, dataMode); 80 | } 81 | } 82 | SPISettings() { 83 | init_AlwaysInline(4000000, MSBFIRST, SPI_MODE0); 84 | } 85 | private: 86 | void init_MightInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) { 87 | init_AlwaysInline(clock, bitOrder, dataMode); 88 | } 89 | void init_AlwaysInline(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) 90 | __attribute__((__always_inline__)) { 91 | // Clock settings are defined as follows. Note that this shows SPI2X 92 | // inverted, so the bits form increasing numbers. Also note that 93 | // fosc/64 appears twice 94 | // SPR1 SPR0 ~SPI2X Freq 95 | // 0 0 0 fosc/2 96 | // 0 0 1 fosc/4 97 | // 0 1 0 fosc/8 98 | // 0 1 1 fosc/16 99 | // 1 0 0 fosc/32 100 | // 1 0 1 fosc/64 101 | // 1 1 0 fosc/64 102 | // 1 1 1 fosc/128 103 | 104 | // We find the fastest clock that is less than or equal to the 105 | // given clock rate. The clock divider that results in clock_setting 106 | // is 2 ^^ (clock_div + 1). If nothing is slow enough, we'll use the 107 | // slowest (128 == 2 ^^ 7, so clock_div = 6). 108 | uint8_t clockDiv; 109 | 110 | // When the clock is known at compiletime, use this if-then-else 111 | // cascade, which the compiler knows how to completely optimize 112 | // away. When clock is not known, use a loop instead, which generates 113 | // shorter code. 114 | if (__builtin_constant_p(clock)) { 115 | if (clock >= F_CPU / 2) { 116 | clockDiv = 0; 117 | } else if (clock >= F_CPU / 4) { 118 | clockDiv = 1; 119 | } else if (clock >= F_CPU / 8) { 120 | clockDiv = 2; 121 | } else if (clock >= F_CPU / 16) { 122 | clockDiv = 3; 123 | } else if (clock >= F_CPU / 32) { 124 | clockDiv = 4; 125 | } else if (clock >= F_CPU / 64) { 126 | clockDiv = 5; 127 | } else { 128 | clockDiv = 6; 129 | } 130 | } else { 131 | uint32_t clockSetting = F_CPU / 2; 132 | clockDiv = 0; 133 | while (clockDiv < 6 && clock < clockSetting) { 134 | clockSetting /= 2; 135 | clockDiv++; 136 | } 137 | } 138 | 139 | // Compensate for the duplicate fosc/64 140 | if (clockDiv == 6) 141 | clockDiv = 7; 142 | 143 | // Invert the SPI2X bit 144 | clockDiv ^= 0x1; 145 | 146 | // Pack into the SPISettings class 147 | spcr = _BV(SPE) | _BV(MSTR) | ((bitOrder == LSBFIRST) ? _BV(DORD) : 0) | 148 | (dataMode & SPI_MODE_MASK) | ((clockDiv >> 1) & SPI_CLOCK_MASK); 149 | spsr = clockDiv & SPI_2XCLOCK_MASK; 150 | } 151 | uint8_t spcr; 152 | uint8_t spsr; 153 | friend class SPIClass; 154 | }; 155 | 156 | 157 | class SPIClass { 158 | public: 159 | // Initialize the SPI library 160 | static void begin(); 161 | 162 | // If SPI is used from within an interrupt, this function registers 163 | // that interrupt with the SPI library, so beginTransaction() can 164 | // prevent conflicts. The input interruptNumber is the number used 165 | // with attachInterrupt. If SPI is used from a different interrupt 166 | // (eg, a timer), interruptNumber should be 255. 167 | static void usingInterrupt(uint8_t interruptNumber); 168 | // And this does the opposite. 169 | static void notUsingInterrupt(uint8_t interruptNumber); 170 | // Note: the usingInterrupt and notUsingInterrupt functions should 171 | // not to be called from ISR context or inside a transaction. 172 | // For details see: 173 | // https://github.com/arduino/Arduino/pull/2381 174 | // https://github.com/arduino/Arduino/pull/2449 175 | 176 | // Before using SPI.transfer() or asserting chip select pins, 177 | // this function is used to gain exclusive access to the SPI bus 178 | // and configure the correct settings. 179 | inline static void beginTransaction(SPISettings settings) { 180 | if (interruptMode > 0) { 181 | uint8_t sreg = SREG; 182 | noInterrupts(); 183 | 184 | #if defined(SPI_AVR_EIMSK) 185 | if (interruptMode == 1) { 186 | interruptSave = SPI_AVR_EIMSK; 187 | SPI_AVR_EIMSK &= ~interruptMask; 188 | SREG = sreg; 189 | } else 190 | #endif 191 | { 192 | interruptSave = sreg; 193 | } 194 | } 195 | 196 | #if defined(SPI_TRANSACTION_MISMATCH_LED) 197 | if (inTransactionFlag) { 198 | pinModeFast(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); 199 | digitalWriteFast(SPI_TRANSACTION_MISMATCH_LED, HIGH); 200 | } 201 | inTransactionFlag = 1; 202 | #endif 203 | 204 | SPCR = settings.spcr; 205 | SPSR = settings.spsr; 206 | } 207 | 208 | // Write to the SPI bus (MOSI pin) and also receive (MISO pin) 209 | inline static uint8_t transfer(uint8_t data) { 210 | SPDR = data; 211 | /* 212 | * The following NOP introduces a small delay that can prevent the wait 213 | * loop form iterating when running at the maximum speed. This gives 214 | * about 10% more speed, even if it seems counter-intuitive. At lower 215 | * speeds it is unnoticed. 216 | */ 217 | asm volatile("nop"); 218 | while (!(SPSR & _BV(SPIF))) ; // wait 219 | return SPDR; 220 | } 221 | inline static uint16_t transfer16(uint16_t data) { 222 | union { uint16_t val; struct { uint8_t lsb; uint8_t msb; }; } in, out; 223 | in.val = data; 224 | if (!(SPCR & _BV(DORD))) { 225 | SPDR = in.msb; 226 | asm volatile("nop"); // See transfer(uint8_t) function 227 | while (!(SPSR & _BV(SPIF))) ; 228 | out.msb = SPDR; 229 | SPDR = in.lsb; 230 | asm volatile("nop"); 231 | while (!(SPSR & _BV(SPIF))) ; 232 | out.lsb = SPDR; 233 | } else { 234 | SPDR = in.lsb; 235 | asm volatile("nop"); 236 | while (!(SPSR & _BV(SPIF))) ; 237 | out.lsb = SPDR; 238 | SPDR = in.msb; 239 | asm volatile("nop"); 240 | while (!(SPSR & _BV(SPIF))) ; 241 | out.msb = SPDR; 242 | } 243 | return out.val; 244 | } 245 | inline static void transfer(void *buf, size_t count) { 246 | if (count == 0) return; 247 | uint8_t *p = (uint8_t *)buf; 248 | SPDR = *p; 249 | while (--count > 0) { 250 | uint8_t out = *(p + 1); 251 | while (!(SPSR & _BV(SPIF))) ; 252 | uint8_t in = SPDR; 253 | SPDR = out; 254 | *p++ = in; 255 | } 256 | while (!(SPSR & _BV(SPIF))) ; 257 | *p = SPDR; 258 | } 259 | // After performing a group of transfers and releasing the chip select 260 | // signal, this function allows others to access the SPI bus 261 | inline static void endTransaction(void) { 262 | #if defined(SPI_TRANSACTION_MISMATCH_LED) 263 | if (!inTransactionFlag) { 264 | pinModeFast(SPI_TRANSACTION_MISMATCH_LED, OUTPUT); 265 | digitalWrite(SPI_TRANSACTION_MISMATCH_LED, HIGH); 266 | } 267 | inTransactionFlag = 0; 268 | #endif 269 | 270 | if (interruptMode > 0) { 271 | #if defined(SPI_AVR_EIMSK) 272 | uint8_t sreg = SREG; 273 | #endif 274 | noInterrupts(); 275 | #if defined(SPI_AVR_EIMSK) 276 | if (interruptMode == 1) { 277 | SPI_AVR_EIMSK = interruptSave; 278 | SREG = sreg; 279 | } else 280 | #endif 281 | { 282 | SREG = interruptSave; 283 | } 284 | } 285 | } 286 | 287 | // Disable the SPI bus 288 | static void end(); 289 | 290 | // This function is deprecated. New applications should use 291 | // beginTransaction() to configure SPI settings. 292 | inline static void setBitOrder(uint8_t bitOrder) { 293 | if (bitOrder == LSBFIRST) SPCR |= _BV(DORD); 294 | else SPCR &= ~(_BV(DORD)); 295 | } 296 | // This function is deprecated. New applications should use 297 | // beginTransaction() to configure SPI settings. 298 | inline static void setDataMode(uint8_t dataMode) { 299 | SPCR = (SPCR & ~SPI_MODE_MASK) | dataMode; 300 | } 301 | // This function is deprecated. New applications should use 302 | // beginTransaction() to configure SPI settings. 303 | inline static void setClockDivider(uint8_t clockDiv) { 304 | SPCR = (SPCR & ~SPI_CLOCK_MASK) | (clockDiv & SPI_CLOCK_MASK); 305 | SPSR = (SPSR & ~SPI_2XCLOCK_MASK) | ((clockDiv >> 2) & SPI_2XCLOCK_MASK); 306 | } 307 | // These undocumented functions should not be used. SPI.transfer() 308 | // polls the hardware flag which is automatically cleared as the 309 | // AVR responds to SPI's interrupt 310 | inline static void attachInterrupt() { SPCR |= _BV(SPIE); } 311 | inline static void detachInterrupt() { SPCR &= ~_BV(SPIE); } 312 | 313 | private: 314 | static uint8_t initialized; 315 | static uint8_t interruptMode; // 0=none, 1=mask, 2=global 316 | static uint8_t interruptMask; // which interrupts to mask 317 | static uint8_t interruptSave; // temp storage, to restore state 318 | #if defined(SPI_TRANSACTION_MISMATCH_LED) 319 | static uint8_t inTransactionFlag; 320 | #endif 321 | }; 322 | 323 | extern SPIClass SPI; 324 | 325 | #endif 326 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/SoftI2CMasterConfig.h: -------------------------------------------------------------------------------- 1 | /* Arduino SoftI2C library. 2 | * 3 | * SoftI2CMasterConfig.h 4 | * 5 | * This contains a sample configuration setting for SoftI2CMaster.h 6 | * The existence of this file can trigger the use of SoftI2CMaster by usage of "#if __has_include("SoftI2CMasterConfig.h")", 7 | * which saves 2110 bytes program memory and 200 bytes RAM compared with Arduino Wire. 8 | * 9 | * Copyright (C) 2022, Armin Joachimsmeyer 10 | * 11 | * This file is part of SoftI2CMaster https://github.com/felias-fogg/SoftI2CMaster. 12 | * 13 | * This Library is free software: you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation, either version 3 of the License, or 16 | * (at your option) any later version. 17 | * 18 | * This Library is distributed in the hope that it will be useful, 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 21 | * GNU General Public License for more details. 22 | * 23 | * You should have received a copy of the GNU General Public License 24 | * along with the Arduino I2cMaster Library. If not, see 25 | * . 26 | */ 27 | 28 | /* In order to use the library, you need to define SDA_PIN, SCL_PIN, 29 | * SDA_PORT and SCL_PORT before including this file. Have a look at 30 | * http://www.arduino.cc/en/Reference/PortManipulation for finding out 31 | * which values to use. For example, if you use digital pin 3 (corresponding 32 | * to PD3) for SDA and digital pin 13 (corresponding to PB5) 33 | * for SCL on a standard Arduino, 34 | * you have to use the following definitions: 35 | * #define SDA_PIN 3 36 | * #define SDA_PORT PORTD 37 | * #define SCL_PIN 5 38 | * #define SCL_PORT PORTB 39 | * 40 | * Alternatively, you can define the compile time constant I2C_HARDWARE, 41 | * in which case the TWI hardware is used. In this case you have to use 42 | * the standard SDA/SCL pins (and, of course, the chip needs to support 43 | * this). 44 | * 45 | * You can also define the following constants (see also below): 46 | ' - I2C_PULLUP = 1 meaning that internal pullups should be used 47 | * - I2C_CPUFREQ, when changing CPU clock frequency dynamically 48 | * - I2C_FASTMODE = 1 meaning that the I2C bus allows speeds up to 400 kHz 49 | * - I2C_SLOWMODE = 1 meaning that the I2C bus will allow only up to 25 kHz 50 | * - I2C_NOINTERRUPT = 1 in order to prohibit interrupts while 51 | * communicating (see below). This can be useful if you use the library 52 | * for communicating with SMbus devices, which have timeouts. 53 | * Note, however, that interrupts are disabled from issuing a start condition 54 | * until issuing a stop condition. So use this option with care! 55 | * - I2C_TIMEOUT = 0...10000 msec in order to return from the I2C functions 56 | * in case of a I2C bus lockup (i.e., SCL constantly low). 0 means no timeout. 57 | * - I2C_MAXWAIT = 0...32767 number of retries in i2c_start_wait. 0 means never stop. 58 | */ 59 | #ifndef _SOFT_I2C_MASTER_CONFIG_H 60 | #define _SOFT_I2C_MASTER_CONFIG_H 61 | 62 | //#define SCL_PIN 5 63 | //#define SCL_PORT PORTC 64 | //#define SDA_PIN 4 65 | //#define SDA_PORT PORTC 66 | #define I2C_HARDWARE 1 // use I2C Hardware 67 | #define I2C_PULLUP 1 68 | //#define I2C_TIMEOUT 5000 // costs 350 bytes 69 | #define I2C_FASTMODE 1 70 | 71 | #endif // _SOFT_I2C_MASTER_CONFIG_H 72 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/SoftwareSerialTX.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SoftwareSerialTX.h (from SoftSerial.h) - 3 | Multi-instance software serial library for Arduino/Wiring 4 | -- Transmit-only imoplementation 5 | -- reduce footprint in code memory and RAM compared to SoftwareSerial 6 | ~ 668 byte code 7 | ~ 68 byte RAM 8 | 9 | This library is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | 14 | This library is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 17 | See the GNU Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public 20 | License along with this library; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | 23 | The latest version of this library can always be found at 24 | http://arduiniana.org. 25 | 26 | */ 27 | 28 | #if defined(__AVR_ATmega168__) ||defined(__AVR_ATmega168P__) ||defined(__AVR_ATmega328P__) ||defined(__AVR_ATmega644P__) 29 | // 30 | // Includes 31 | // 32 | #include 33 | #include 34 | #include "SoftwareSerialTX.h" 35 | 36 | // 37 | // Constructor 38 | // 39 | SoftwareSerialTX::SoftwareSerialTX() { 40 | } 41 | SoftwareSerialTX::SoftwareSerialTX(uint8_t transmitPin) { 42 | setTX(transmitPin); 43 | } 44 | 45 | size_t SoftwareSerialTX::write(uint8_t b) { 46 | // By declaring these as local variables, the compiler will put them 47 | // in registers _before_ disabling interrupts and entering the 48 | // critical timing sections below, which makes it a lot easier to 49 | // verify the cycle timings 50 | volatile uint8_t *reg = _transmitPortRegister; 51 | uint8_t reg_mask = _transmitBitMask; 52 | uint8_t inv_mask = ~_transmitBitMask; 53 | uint8_t oldSREG = SREG; 54 | uint16_t delay = _tx_delay; 55 | 56 | cli(); // turn off interrupts for a clean txmit 57 | 58 | // Write the start bit 59 | *reg &= inv_mask; 60 | 61 | _delay_loop_2(delay); 62 | 63 | // Write each of the 8 bits 64 | for (uint8_t i = 8; i > 0; --i) { 65 | if (b & 1) // choose bit 66 | *reg |= reg_mask; // send 1 67 | else 68 | *reg &= inv_mask; // send 0 69 | 70 | _delay_loop_2(delay); 71 | b >>= 1; 72 | } 73 | 74 | // restore pin to natural state 75 | *reg |= reg_mask; 76 | 77 | SREG = oldSREG; // turn interrupts back on 78 | _delay_loop_2(delay); 79 | 80 | return 1; 81 | } 82 | 83 | void SoftwareSerialTX::begin(long speed) { 84 | uint16_t bit_delay = (F_CPU / speed) / 4; 85 | //_rx_delay_centering = _rx_delay_intrabit = _rx_delay_stopbit = _tx_delay = 0; 86 | // 12 (gcc 4.8.2) or 13 (gcc 4.3.2) cycles from start bit to first bit, 87 | // 15 (gcc 4.8.2) or 16 (gcc 4.3.2) cycles between bits, 88 | // 12 (gcc 4.8.2) or 14 (gcc 4.3.2) cycles from last bit to stop bit 89 | // These are all close enough to just use 15 cycles, since the inter-bit 90 | // timings are the most critical (deviations stack 8 times) 91 | _tx_delay = subtract_cap(bit_delay, 15 / 4); 92 | } 93 | 94 | uint16_t SoftwareSerialTX::subtract_cap(uint16_t num, uint16_t sub) { 95 | if (num > sub) 96 | return num - sub; 97 | else 98 | return 1; 99 | } 100 | #include "digitalWriteFast.h" 101 | 102 | void SoftwareSerialTX::setTX(uint8_t tx) { 103 | // First write, then set output. If we do this the other way around, 104 | // the pin would be output low for a short while before switching to 105 | // output high. Now, it is input with pullup for a short while, which 106 | // is fine. With inverse logic, either order is fine. 107 | digitalWriteFast(tx, HIGH); 108 | pinModeFast(tx, OUTPUT); 109 | _transmitBitMask = digitalPinToBitMask(tx); 110 | _transmitPortRegister = portOutputRegister(digitalPinToPort(tx)); 111 | } 112 | 113 | size_t SoftwareSerialTX::write(const uint8_t *buffer, size_t size) { 114 | size_t n = 0; 115 | while (size--) { 116 | if (write(*buffer++)) 117 | n++; 118 | else 119 | break; 120 | } 121 | return n; 122 | } 123 | #else 124 | #error unsupported platform 125 | #endif 126 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/SoftwareSerialTX.h: -------------------------------------------------------------------------------- 1 | /* 2 | SoftwareSerialTX.h (from SoftSerial.h) - 3 | Multi-instance software serial library for Arduino/Wiring 4 | -- Transmit-only imoplementation 5 | -- reduce footprint in code memory and RAM compared to SoftwareSerial 6 | ~ 686 byte code 7 | ~ 68 byte RAM 8 | 9 | This library is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU Lesser General Public 11 | License as published by the Free Software Foundation; either 12 | version 2.1 of the License, or (at your option) any later version. 13 | 14 | This library is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | Lesser General Public License for more details. 18 | 19 | You should have received a copy of the GNU Lesser General Public 20 | License along with this library; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | 23 | The latest version of this library can always be found at 24 | http://arduiniana.org. 25 | */ 26 | 27 | #ifndef SoftwareSerialTX_h 28 | #define SoftwareSerialTX_h 29 | 30 | #include 31 | 32 | class SoftwareSerialTX 33 | { 34 | public: 35 | // per object data 36 | uint8_t _transmitBitMask; 37 | volatile uint8_t *_transmitPortRegister; 38 | uint16_t _tx_delay; 39 | 40 | // private methods 41 | void setTX(uint8_t transmitPin); 42 | 43 | #ifndef ARDUINO_RASPBERRY_PI_PICO 44 | // Return num - sub, or 1 if the result would be < 1 45 | static uint16_t subtract_cap(uint16_t num, uint16_t sub); 46 | #endif 47 | 48 | // public methods 49 | SoftwareSerialTX(); 50 | SoftwareSerialTX(uint8_t transmitPin); 51 | void begin(long speed); 52 | size_t write(uint8_t byte); 53 | size_t write(const uint8_t *buffer, size_t size); 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /JK-BMSToPylontechCAN/mcp2515_can_dfs.h: -------------------------------------------------------------------------------- 1 | /* 2 | mcp_can_dfs.h 3 | 2012 Copyright (c) Seeed Technology Inc. All right reserved. 4 | 5 | Author:Loovee (loovee@seeed.cc) 6 | 2014-1-16 7 | 8 | Contributor: 9 | 10 | Cory J. Fowler 11 | Latonita 12 | Woodward1 13 | Mehtajaghvi 14 | BykeBlast 15 | TheRo0T 16 | Tsipizic 17 | ralfEdmund 18 | Nathancheek 19 | BlueAndi 20 | Adlerweb 21 | Btetz 22 | Hurvajs 23 | xboxpro1 24 | ttlappalainen 25 | 26 | The MIT License (MIT) 27 | 28 | Copyright (c) 2013 Seeed Technology Inc. 29 | 30 | Permission is hereby granted, free of charge, to any person obtaining a copy 31 | of this software and associated documentation files (the "Software"), to deal 32 | in the Software without restriction, including without limitation the rights 33 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 34 | copies of the Software, and to permit persons to whom the Software is 35 | furnished to do so, subject to the following conditions: 36 | 37 | The above copyright notice and this permission notice shall be included in 38 | all copies or substantial portions of the Software. 39 | 40 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 41 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 42 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 43 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 44 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 45 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 46 | THE SOFTWARE. 47 | */ 48 | #ifndef _MCP2515DFS_H_ 49 | #define _MCP2515DFS_H_ 50 | 51 | #include 52 | #include 53 | 54 | 55 | // if print debug information 56 | #ifndef DEBUG_EN 57 | #define DEBUG_EN 1 58 | #endif 59 | 60 | // Begin mt 61 | 62 | #define TIMEOUTVALUE 50 63 | #define MCP_SIDH 0 64 | #define MCP_SIDL 1 65 | #define MCP_EID8 2 66 | #define MCP_EID0 3 67 | 68 | #define MCP_TXB_EXIDE_M 0x08 // In TXBnSIDL 69 | #define MCP_DLC_MASK 0x0F // 4 LSBits 70 | #define MCP_RTR_MASK 0x40 // (1<<6) Bit 6 71 | 72 | #define MCP_RXB_RX_ANY 0x60 73 | #define MCP_RXB_RX_EXT 0x40 74 | #define MCP_RXB_RX_STD 0x20 75 | #define MCP_RXB_RX_STDEXT 0x00 76 | #define MCP_RXB_RX_MASK 0x60 77 | #define MCP_RXB_BUKT_MASK (1<<2) 78 | 79 | 80 | // Bits in the TXBnCTRL registers. 81 | 82 | #define MCP_TXB_TXBUFE_M 0x80 83 | #define MCP_TXB_ABTF_M 0x40 84 | #define MCP_TXB_MLOA_M 0x20 85 | #define MCP_TXB_TXERR_M 0x10 86 | #define MCP_TXB_TXREQ_M 0x08 87 | #define MCP_TXB_TXIE_M 0x04 88 | #define MCP_TXB_TXP10_M 0x03 89 | 90 | #define MCP_TXB_RTR_M 0x40 // In TXBnDLC 91 | #define MCP_RXB_IDE_M 0x08 // In RXBnSIDL 92 | #define MCP_RXB_RTR_M 0x40 // In RXBnDLC 93 | 94 | #define MCP_STAT_TX_PENDING_MASK (0x54) 95 | #define MCP_STAT_TX0_PENDING (0x04) 96 | #define MCP_STAT_TX1_PENDING (0x10) 97 | #define MCP_STAT_TX2_PENDING (0x40) 98 | #define MCP_STAT_TXIF_MASK (0xA8) 99 | #define MCP_STAT_TX0IF (0x08) 100 | #define MCP_STAT_TX1IF (0x20) 101 | #define MCP_STAT_TX2IF (0x80) 102 | #define MCP_STAT_RXIF_MASK (0x03) 103 | #define MCP_STAT_RX0IF (1<<0) 104 | #define MCP_STAT_RX1IF (1<<1) 105 | 106 | #define MCP_EFLG_RX1OVR (1<<7) 107 | #define MCP_EFLG_RX0OVR (1<<6) 108 | #define MCP_EFLG_TXBO (1<<5) 109 | #define MCP_EFLG_TXEP (1<<4) 110 | #define MCP_EFLG_RXEP (1<<3) 111 | #define MCP_EFLG_TXWAR (1<<2) 112 | #define MCP_EFLG_RXWAR (1<<1) 113 | #define MCP_EFLG_EWARN (1<<0) 114 | #define MCP_EFLG_ERRORMASK (0xF8) // 5 MS-Bits 115 | 116 | // Define MCP2515 register addresses 117 | 118 | #define MCP_RXF0SIDH 0x00 119 | #define MCP_RXF0SIDL 0x01 120 | #define MCP_RXF0EID8 0x02 121 | #define MCP_RXF0EID0 0x03 122 | #define MCP_RXF1SIDH 0x04 123 | #define MCP_RXF1SIDL 0x05 124 | #define MCP_RXF1EID8 0x06 125 | #define MCP_RXF1EID0 0x07 126 | #define MCP_RXF2SIDH 0x08 127 | #define MCP_RXF2SIDL 0x09 128 | #define MCP_RXF2EID8 0x0A 129 | #define MCP_RXF2EID0 0x0B 130 | #define MCP_BFPCTRL 0x0C 131 | #define MCP_TXRTSCTRL 0x0D 132 | #define MCP_CANSTAT 0x0E 133 | #define MCP_CANCTRL 0x0F 134 | #define MCP_RXF3SIDH 0x10 135 | #define MCP_RXF3SIDL 0x11 136 | #define MCP_RXF3EID8 0x12 137 | #define MCP_RXF3EID0 0x13 138 | #define MCP_RXF4SIDH 0x14 139 | #define MCP_RXF4SIDL 0x15 140 | #define MCP_RXF4EID8 0x16 141 | #define MCP_RXF4EID0 0x17 142 | #define MCP_RXF5SIDH 0x18 143 | #define MCP_RXF5SIDL 0x19 144 | #define MCP_RXF5EID8 0x1A 145 | #define MCP_RXF5EID0 0x1B 146 | #define MCP_TEC 0x1C 147 | #define MCP_REC 0x1D 148 | #define MCP_RXM0SIDH 0x20 149 | #define MCP_RXM0SIDL 0x21 150 | #define MCP_RXM0EID8 0x22 151 | #define MCP_RXM0EID0 0x23 152 | #define MCP_RXM1SIDH 0x24 153 | #define MCP_RXM1SIDL 0x25 154 | #define MCP_RXM1EID8 0x26 155 | #define MCP_RXM1EID0 0x27 156 | #define MCP_CNF3 0x28 157 | #define MCP_CNF2 0x29 158 | #define MCP_CNF1 0x2A 159 | #define MCP_CANINTE 0x2B 160 | #define MCP_CANINTF 0x2C 161 | #define MCP_EFLG 0x2D 162 | #define MCP_TXB0CTRL 0x30 163 | #define MCP_TXB0SIDH 0x31 164 | #define MCP_TXB0SIDL 0x32 165 | #define MCP_TXB0DLC 0x35 166 | #define MCP_TXB0D0 0x36 167 | #define MCP_TXB1CTRL 0x40 168 | #define MCP_TXB1SIDH 0x41 169 | #define MCP_TXB2CTRL 0x50 170 | #define MCP_TXB2SIDH 0x51 171 | #define MCP_RXB0CTRL 0x60 172 | #define MCP_RXB0SIDH 0x61 173 | #define MCP_RXB1CTRL 0x70 174 | #define MCP_RXB1SIDH 0x71 175 | 176 | #define MCP_TX_INT 0x1C // Enable all transmit interrup ts 177 | #define MCP_TX01_INT 0x0C // Enable TXB0 and TXB1 interru pts 178 | #define MCP_RX_INT 0x03 // Enable receive interrupts 179 | #define MCP_NO_INT 0x00 // Disable all interrupts 180 | 181 | #define MCP_TX01_MASK 0x14 182 | #define MCP_TX_MASK 0x54 183 | 184 | 185 | // Define SPI Instruction Set 186 | 187 | #define MCP_WRITE 0x02 188 | #define MCP_READ 0x03 189 | #define MCP_BITMOD 0x05 190 | #define MCP_LOAD_TX0 0x40 191 | #define MCP_LOAD_TX1 0x42 192 | #define MCP_LOAD_TX2 0x44 193 | 194 | #define MCP_RTS_TX0 0x81 195 | #define MCP_RTS_TX1 0x82 196 | #define MCP_RTS_TX2 0x84 197 | #define MCP_RTS_ALL 0x87 198 | #define MCP_READ_RX0 0x90 199 | #define MCP_READ_RX1 0x94 200 | #define MCP_READ_STATUS 0xA0 201 | #define MCP_RX_STATUS 0xB0 202 | #define MCP_RESET 0xC0 203 | 204 | 205 | // CANCTRL Register Values 206 | 207 | #define MODE_NORMAL 0x00 208 | #define MODE_SLEEP 0x20 209 | #define MODE_LOOPBACK 0x40 210 | #define MODE_LISTENONLY 0x60 211 | #define MODE_CONFIG 0x80 212 | #define MODE_POWERUP 0xE0 213 | #define MODE_MASK 0xE0 214 | #define ABORT_TX 0x10 215 | #define MODE_ONESHOT 0x08 216 | #define CLKOUT_ENABLE 0x04 217 | #define CLKOUT_DISABLE 0x00 218 | #define CLKOUT_PS1 0x00 219 | #define CLKOUT_PS2 0x01 220 | #define CLKOUT_PS4 0x02 221 | #define CLKOUT_PS8 0x03 222 | 223 | 224 | // CNF1 Register Values 225 | 226 | #define SJW1 0x00 227 | #define SJW2 0x40 228 | #define SJW3 0x80 229 | #define SJW4 0xC0 230 | 231 | 232 | // CNF2 Register Values 233 | 234 | #define BTLMODE 0x80 235 | #define SAMPLE_1X 0x00 236 | #define SAMPLE_3X 0x40 237 | 238 | 239 | // CNF3 Register Values 240 | 241 | #define SOF_ENABLE 0x80 242 | #define SOF_DISABLE 0x00 243 | #define WAKFIL_ENABLE 0x40 244 | #define WAKFIL_DISABLE 0x00 245 | 246 | 247 | // CANINTF Register Bits 248 | 249 | #define MCP_RX0IF 0x01 250 | #define MCP_RX1IF 0x02 251 | #define MCP_TX0IF 0x04 252 | #define MCP_TX1IF 0x08 253 | #define MCP_TX2IF 0x10 254 | #define MCP_ERRIF 0x20 255 | #define MCP_WAKIF 0x40 256 | #define MCP_MERRF 0x80 257 | 258 | // BFPCTRL Register Bits 259 | 260 | #define B1BFS 0x20 261 | #define B0BFS 0x10 262 | #define B1BFE 0x08 263 | #define B0BFE 0x04 264 | #define B1BFM 0x02 265 | #define B0BFM 0x01 266 | 267 | // TXRTCTRL Register Bits 268 | 269 | #define B2RTS 0x20 270 | #define B1RTS 0x10 271 | #define B0RTS 0x08 272 | #define B2RTSM 0x04 273 | #define B1RTSM 0x02 274 | #define B0RTSM 0x01 275 | 276 | // speed 20 MHz - values from https://www.kvaser.com/support/calculators/bit-timing-calculator/ 277 | 278 | #define MCP_20MHz_500kBPS_CFG1 (0x00) 279 | #define MCP_20MHz_500kBPS_CFG2 (0xAC) 280 | #define MCP_20MHz_500kBPS_CFG3 (0x07) 281 | 282 | // speed 16M 283 | 284 | #define MCP_16MHz_1000kBPS_CFG1 (0x00) 285 | #define MCP_16MHz_1000kBPS_CFG2 (0xD0) 286 | #define MCP_16MHz_1000kBPS_CFG3 (0x82) 287 | 288 | #define MCP_16MHz_800kBPS_CFG1 (0x40) 289 | #define MCP_16MHz_800kBPS_CFG2 (0x92) 290 | #define MCP_16MHz_800kBPS_CFG3 (0x02) 291 | 292 | #define MCP_16MHz_666kBPS_CFG1 (0x00) 293 | #define MCP_16MHz_666kBPS_CFG2 (0xA0) 294 | #define MCP_16MHz_666kBPS_CFG3 (0x04) 295 | 296 | #define MCP_16MHz_500kBPS_CFG1 (0x00) 297 | #define MCP_16MHz_500kBPS_CFG2 (0xF0) 298 | #define MCP_16MHz_500kBPS_CFG3 (0x86) 299 | 300 | #define MCP_16MHz_250kBPS_CFG1 (0x41) 301 | #define MCP_16MHz_250kBPS_CFG2 (0xF1) 302 | #define MCP_16MHz_250kBPS_CFG3 (0x85) 303 | 304 | #define MCP_16MHz_200kBPS_CFG1 (0x01) 305 | #define MCP_16MHz_200kBPS_CFG2 (0xFA) 306 | #define MCP_16MHz_200kBPS_CFG3 (0x87) 307 | 308 | #define MCP_16MHz_125kBPS_CFG1 (0x03) 309 | #define MCP_16MHz_125kBPS_CFG2 (0xF0) 310 | #define MCP_16MHz_125kBPS_CFG3 (0x86) 311 | 312 | #define MCP_16MHz_100kBPS_CFG1 (0x03) 313 | #define MCP_16MHz_100kBPS_CFG2 (0xFA) 314 | #define MCP_16MHz_100kBPS_CFG3 (0x87) 315 | 316 | #define MCP_16MHz_95kBPS_CFG1 (0x03) 317 | #define MCP_16MHz_95kBPS_CFG2 (0xAD) 318 | #define MCP_16MHz_95kBPS_CFG3 (0x07) 319 | 320 | #define MCP_16MHz_83k3BPS_CFG1 (0x03) 321 | #define MCP_16MHz_83k3BPS_CFG2 (0xBE) 322 | #define MCP_16MHz_83k3BPS_CFG3 (0x07) 323 | 324 | #define MCP_16MHz_80kBPS_CFG1 (0x03) 325 | #define MCP_16MHz_80kBPS_CFG2 (0xFF) 326 | #define MCP_16MHz_80kBPS_CFG3 (0x87) 327 | 328 | #define MCP_16MHz_50kBPS_CFG1 (0x07) 329 | #define MCP_16MHz_50kBPS_CFG2 (0xFA) 330 | #define MCP_16MHz_50kBPS_CFG3 (0x87) 331 | 332 | #define MCP_16MHz_40kBPS_CFG1 (0x07) 333 | #define MCP_16MHz_40kBPS_CFG2 (0xFF) 334 | #define MCP_16MHz_40kBPS_CFG3 (0x87) 335 | 336 | #define MCP_16MHz_33kBPS_CFG1 (0x09) 337 | #define MCP_16MHz_33kBPS_CFG2 (0xBE) 338 | #define MCP_16MHz_33kBPS_CFG3 (0x07) 339 | 340 | #define MCP_16MHz_31k25BPS_CFG1 (0x0F) 341 | #define MCP_16MHz_31k25BPS_CFG2 (0xF1) 342 | #define MCP_16MHz_31k25BPS_CFG3 (0x85) 343 | 344 | #define MCP_16MHz_25kBPS_CFG1 (0X0F) 345 | #define MCP_16MHz_25kBPS_CFG2 (0XBA) 346 | #define MCP_16MHz_25kBPS_CFG3 (0X07) 347 | 348 | #define MCP_16MHz_20kBPS_CFG1 (0x0F) 349 | #define MCP_16MHz_20kBPS_CFG2 (0xFF) 350 | #define MCP_16MHz_20kBPS_CFG3 (0x87) 351 | 352 | #define MCP_16MHz_10kBPS_CFG1 (0x1F) 353 | #define MCP_16MHz_10kBPS_CFG2 (0xFF) 354 | #define MCP_16MHz_10kBPS_CFG3 (0x87) 355 | 356 | #define MCP_16MHz_5kBPS_CFG1 (0x3F) 357 | #define MCP_16MHz_5kBPS_CFG2 (0xFF) 358 | #define MCP_16MHz_5kBPS_CFG3 (0x87) 359 | 360 | 361 | // speed 12M 362 | 363 | #define MCP_12MHz_1000kBPS_CFG1 (0x00)// 364 | #define MCP_12MHz_1000kBPS_CFG2 (0x88)// 365 | #define MCP_12MHz_1000kBPS_CFG3 (0x01)// 366 | 367 | #define MCP_12MHz_666kBPS_CFG1 (0x00)// 368 | #define MCP_12MHz_666kBPS_CFG2 (0x92)// 369 | #define MCP_12MHz_666kBPS_CFG3 (0x01)// 370 | 371 | #define MCP_12MHz_500kBPS_CFG1 (0x00) 372 | #define MCP_12MHz_500kBPS_CFG2 (0x9B) 373 | #define MCP_12MHz_500kBPS_CFG3 (0x02) 374 | 375 | #define MCP_12MHz_250kBPS_CFG1 (0x00)// 376 | #define MCP_12MHz_250kBPS_CFG2 (0xBF)// 377 | #define MCP_12MHz_250kBPS_CFG3 (0x06)/// 378 | 379 | #define MCP_12MHz_200kBPS_CFG1 (0x01)// 380 | #define MCP_12MHz_200kBPS_CFG2 (0xA4)// 381 | #define MCP_12MHz_200kBPS_CFG3 (0x03)// 382 | 383 | #define MCP_12MHz_125kBPS_CFG1 (0x01)// 384 | #define MCP_12MHz_125kBPS_CFG2 (0xBF)// 385 | #define MCP_12MHz_125kBPS_CFG3 (0x06)// 386 | 387 | #define MCP_12MHz_100kBPS_CFG1 (0x02)// 388 | #define MCP_12MHz_100kBPS_CFG2 (0xB6)// 389 | #define MCP_12MHz_100kBPS_CFG3 (0x04)// 390 | 391 | #define MCP_12MHz_95kBPS_CFG1 (0x02)// 392 | #define MCP_12MHz_95kBPS_CFG2 (0xBE)// 393 | #define MCP_12MHz_95kBPS_CFG3 (0x04)// 394 | 395 | #define MCP_12MHz_83k3BPS_CFG1 (0x03)// 396 | #define MCP_12MHz_83k3BPS_CFG2 (0xB5)// 397 | #define MCP_12MHz_83k3BPS_CFG3 (0x03)// 398 | 399 | #define MCP_12MHz_80kBPS_CFG1 (0x04)// 400 | #define MCP_12MHz_80kBPS_CFG2 (0xA4)// 401 | #define MCP_12MHz_80kBPS_CFG3 (0x03)// 402 | 403 | #define MCP_12MHz_50kBPS_CFG1 (0x05)// 404 | #define MCP_12MHz_50kBPS_CFG2 (0xB6)// 405 | #define MCP_12MHz_50kBPS_CFG3 (0x04)// 406 | 407 | #define MCP_12MHz_40kBPS_CFG1 (0x09)// 408 | #define MCP_12MHz_40kBPS_CFG2 (0xA4)// 409 | #define MCP_12MHz_40kBPS_CFG3 (0x03)// 410 | 411 | #define MCP_12MHz_33kBPS_CFG1 (0x0C)// 412 | #define MCP_12MHz_33kBPS_CFG2 (0xA4)// 413 | #define MCP_12MHz_33kBPS_CFG3 (0x02)// 414 | 415 | #define MCP_12MHz_31k25BPS_CFG1 (0x0B)// 416 | #define MCP_12MHz_31k25BPS_CFG2 (0xAC)// 417 | #define MCP_12MHz_31k25BPS_CFG3 (0x03)// 418 | 419 | #define MCP_12MHz_25kBPS_CFG1 (0X0B)// 420 | #define MCP_12MHz_25kBPS_CFG2 (0XB6)// 421 | #define MCP_12MHz_25kBPS_CFG3 (0X04)// 422 | 423 | #define MCP_12MHz_20kBPS_CFG1 (0x0C)// 424 | #define MCP_12MHz_20kBPS_CFG2 (0xBF)// 425 | #define MCP_12MHz_20kBPS_CFG3 (0x05)// 426 | 427 | 428 | 429 | // speed 8M 430 | 431 | #define MCP_8MHz_1000kBPS_CFG1 (0x00) 432 | #define MCP_8MHz_1000kBPS_CFG2 (0x80) 433 | #define MCP_8MHz_1000kBPS_CFG3 (0x00) 434 | 435 | #define MCP_8MHz_800kBPS_CFG1 (0x00) 436 | #define MCP_8MHz_800kBPS_CFG2 (0x80) 437 | #define MCP_8MHz_800kBPS_CFG3 (0x01) 438 | 439 | #define MCP_8MHz_500kBPS_CFG1 (0x00) 440 | #define MCP_8MHz_500kBPS_CFG2 (0x90) 441 | #define MCP_8MHz_500kBPS_CFG3 (0x02) 442 | 443 | #define MCP_8MHz_250kBPS_CFG1 (0x00) 444 | #define MCP_8MHz_250kBPS_CFG2 (0xb1) 445 | #define MCP_8MHz_250kBPS_CFG3 (0x05) 446 | 447 | #define MCP_8MHz_200kBPS_CFG1 (0x00) 448 | #define MCP_8MHz_200kBPS_CFG2 (0xb4) 449 | #define MCP_8MHz_200kBPS_CFG3 (0x06) 450 | 451 | #define MCP_8MHz_125kBPS_CFG1 (0x01) 452 | #define MCP_8MHz_125kBPS_CFG2 (0xb1) 453 | #define MCP_8MHz_125kBPS_CFG3 (0x05) 454 | 455 | #define MCP_8MHz_100kBPS_CFG1 (0x01) 456 | #define MCP_8MHz_100kBPS_CFG2 (0xb4) 457 | #define MCP_8MHz_100kBPS_CFG3 (0x06) 458 | 459 | #define MCP_8MHz_80kBPS_CFG1 (0x01) 460 | #define MCP_8MHz_80kBPS_CFG2 (0xbf) 461 | #define MCP_8MHz_80kBPS_CFG3 (0x07) 462 | 463 | #define MCP_8MHz_50kBPS_CFG1 (0x03) 464 | #define MCP_8MHz_50kBPS_CFG2 (0xb4) 465 | #define MCP_8MHz_50kBPS_CFG3 (0x06) 466 | 467 | #define MCP_8MHz_40kBPS_CFG1 (0x03) 468 | #define MCP_8MHz_40kBPS_CFG2 (0xbf) 469 | #define MCP_8MHz_40kBPS_CFG3 (0x07) 470 | 471 | #define MCP_8MHz_31k25BPS_CFG1 (0x07) 472 | #define MCP_8MHz_31k25BPS_CFG2 (0xa4) 473 | #define MCP_8MHz_31k25BPS_CFG3 (0x04) 474 | 475 | #define MCP_8MHz_20kBPS_CFG1 (0x07) 476 | #define MCP_8MHz_20kBPS_CFG2 (0xbf) 477 | #define MCP_8MHz_20kBPS_CFG3 (0x07) 478 | 479 | #define MCP_8MHz_10kBPS_CFG1 (0x0f) 480 | #define MCP_8MHz_10kBPS_CFG2 (0xbf) 481 | #define MCP_8MHz_10kBPS_CFG3 (0x07) 482 | 483 | #define MCP_8MHz_5kBPS_CFG1 (0x1f) 484 | #define MCP_8MHz_5kBPS_CFG2 (0xbf) 485 | #define MCP_8MHz_5kBPS_CFG3 (0x07) 486 | 487 | #define MCPDEBUG (0) 488 | #define MCPDEBUG_TXBUF (0) 489 | #define MCP_N_TXBUFFERS (3) 490 | 491 | #define MCP_RXBUF_0 (MCP_RXB0SIDH) 492 | #define MCP_RXBUF_1 (MCP_RXB1SIDH) 493 | 494 | #define MCP2515_SELECT() digitalWriteFast(SPICS, LOW) 495 | #define MCP2515_UNSELECT() digitalWriteFast(SPICS, HIGH) 496 | 497 | #define MCP2515_OK (0) 498 | #define MCP2515_FAIL (1) 499 | #define MCP_ALLTXBUSY (2) 500 | 501 | #define CANDEBUG 1 502 | 503 | #define CANUSELOOP 0 504 | 505 | #define CANSENDTIMEOUT (200) // milliseconds 506 | 507 | #define MCP_PIN_HIZ (0) 508 | #define MCP_PIN_INT (1) 509 | #define MCP_PIN_OUT (2) 510 | #define MCP_PIN_IN (3) 511 | 512 | #define MCP_RX0BF (0) 513 | #define MCP_RX1BF (1) 514 | #define MCP_TX0RTS (2) 515 | #define MCP_TX1RTS (3) 516 | #define MCP_TX2RTS (4) 517 | 518 | 519 | // initial value of gCANAutoProcess 520 | 521 | #define CANAUTOPROCESS (1) 522 | #define CANAUTOON (1) 523 | #define CANAUTOOFF (0) 524 | #define CAN_STDID (0) 525 | #define CAN_EXTID (1) 526 | #define CANDEFAULTIDENT (0x55CC) 527 | #define CANDEFAULTIDENTEXT (CAN_EXTID) 528 | 529 | 530 | 531 | 532 | #define CAN_MAX_CHAR_IN_MESSAGE (8) 533 | 534 | #endif 535 | /********************************************************************************************************* 536 | END FILE 537 | *********************************************************************************************************/ 538 | -------------------------------------------------------------------------------- /extras/CellUndervoltage.log: -------------------------------------------------------------------------------- 1 | CSV: 1;0;11;18;18;18;18;13;15;18;18;18;20;20;15;18;4824;0;20;0 2 | CSV: 1;0;10;18;18;18;18;13;15;18;18;20;18;18;13;18;4823;0;20;0 3 | CSV: 2;0;10;18;18;18;18;13;13;18;18;18;20;20;15;18;4823;0;20;0 4 | CSV: 0;0;10;18;18;18;18;15;15;18;18;20;18;18;13;18;4823;0;20;0 5 | CSV: 1;0;10;18;18;18;18;13;15;18;18;18;20;18;13;18;4823;0;20;0 6 | CSV: 1;0;10;18;18;18;17;14;13;18;18;18;18;18;13;18;4823;-20;20;0 7 | CSV: 1;0;11;18;17;18;18;14;15;18;18;18;20;18;15;18;4824;-20;20;0 8 | CSV: 1;0;10;17;17;18;18;14;13;18;18;18;18;20;13;18;4823;0;20;0 9 | CSV: 1;0;10;17;18;18;18;13;15;18;18;18;18;18;15;18;4823;0;20;0 10 | CSV: 1;0;10;17;18;18;17;14;13;18;18;20;20;20;15;18;4823;-20;20;0 11 | CSV: 1;0;10;17;18;18;18;14;13;20;18;18;20;18;13;18;4823;-20;20;0 12 | CSV: 1;0;10;17;18;18;18;14;15;18;20;20;18;18;13;18;4823;-20;20;0 13 | CSV: 1;0;10;17;17;18;17;12;13;20;18;20;20;18;13;18;4823;0;20;0 14 | CSV: 1;0;10;18;18;18;17;13;15;20;18;18;18;18;13;18;4823;0;20;0 15 | CSV: 0;0;10;17;18;18;18;14;15;18;18;18;20;20;13;18;4823;-20;20;0 16 | CSV: 1;0;10;17;17;18;17;13;15;18;18;20;20;18;13;18;4823;0;20;0 17 | CSV: 1;0;10;17;17;18;17;13;13;18;18;18;18;18;13;17;4823;0;20;0 18 | CSV: 1;-1;11;18;17;18;17;14;13;18;18;18;18;18;13;18;4823;-20;20;0 19 | CSV: 1;0;10;18;18;18;18;13;15;18;18;18;18;20;13;18;4824;0;20;0 20 | CSV: 0;0;10;18;18;18;18;13;13;18;18;18;18;18;13;18;4823;0;20;0 21 | CSV: 1;0;10;18;17;17;18;14;13;18;18;18;18;18;13;18;4823;-20;20;0 22 | CSV: 1;0;10;18;18;18;18;13;13;18;18;18;18;18;13;18;4823;0;20;0 23 | CSV: 1;0;10;17;17;18;17;13;13;18;18;18;18;18;12;18;4823;0;20;0 24 | CSV: 0;-1;10;18;18;18;17;13;15;18;17;18;18;18;13;18;4823;0;20;0 25 | CSV: 1;0;10;18;17;18;18;14;13;18;18;18;18;18;13;18;4823;-20;20;0 26 | CSV: 0;0;10;18;18;18;18;13;13;18;18;20;18;18;13;18;4823;0;20;0 27 | CSV: 1;-1;10;17;18;18;17;13;15;18;18;20;18;18;13;18;4823;0;20;0 28 | CSV: 1;0;10;17;17;18;18;14;13;18;18;18;18;18;13;18;4823;-20;20;0 29 | CSV: 0;0;10;18;18;18;18;13;13;18;18;18;18;18;13;18;4823;-20;20;0 30 | CSV: 0;-1;10;17;17;18;17;13;15;18;18;18;18;18;13;18;4823;0;20;0 31 | CSV: 1;-1;10;18;18;18;17;13;15;18;18;18;18;20;13;18;4823;0;20;0 32 | CSV: 1;-1;10;17;17;18;17;13;15;18;18;18;18;18;13;18;4823;0;20;0 33 | CSV: 0;0;10;17;17;18;18;13;15;18;18;20;20;18;13;18;4823;0;20;0 34 | CSV: 0;-1;10;17;18;18;17;13;15;18;18;18;18;18;13;18;4823;0;20;0 35 | CSV: 1;0;10;17;17;18;18;13;15;18;18;20;18;18;13;18;4823;0;20;0 36 | CSV: 1;-1;10;17;18;18;17;13;13;18;18;18;20;18;13;17;4823;0;20;0 37 | CSV: 1;0;10;17;17;18;17;13;13;18;18;18;18;18;13;18;4822;0;20;0 38 | CSV: 1;0;10;17;17;18;17;13;15;18;18;18;18;18;12;18;4823;0;20;0 39 | CSV: 0;-1;10;17;17;17;18;14;15;18;18;18;18;18;13;17;4822;-20;20;0 40 | CSV: 0;0;10;18;18;18;18;14;13;18;18;18;18;18;13;18;4823;-20;20;0 41 | CSV: 0;0;10;17;18;18;17;12;15;18;18;18;18;18;13;18;4823;20;20;0 42 | CSV: 0;-1;10;17;17;18;17;13;13;18;18;18;18;18;13;18;4822;0;20;0 43 | CSV: 1;0;10;18;17;18;17;13;13;18;18;18;18;18;13;18;4823;0;20;0 44 | CSV: 0;-1;10;18;18;18;17;13;13;18;18;18;18;18;13;18;4823;0;20;0 45 | CSV: 1;-1;10;17;17;18;17;14;15;18;18;18;18;18;13;17;4822;-20;20;0 46 | CSV: 0;0;10;17;17;17;18;13;13;18;18;18;18;18;13;18;4823;0;20;0 47 | CSV: 1;-1;10;18;17;18;17;13;13;18;17;18;18;18;13;17;4822;0;20;0 48 | CSV: 0;0;10;17;18;18;17;13;13;18;18;18;18;18;13;18;4823;0;20;0 49 | CSV: 0;-1;10;17;17;18;18;13;13;18;17;18;18;18;13;18;4823;0;20;0 50 | CSV: 0;0;10;18;18;18;17;13;15;20;18;18;18;18;13;18;4823;0;20;0 51 | CSV: 1;-1;10;17;17;17;17;13;15;18;18;18;18;18;13;18;4823;0;20;0 52 | CSV: 0;0;8;17;17;18;17;13;15;18;18;18;18;18;13;18;4822;0;20;0 53 | CSV: 1;-1;10;17;17;18;18;13;13;18;17;18;18;18;13;18;4822;0;20;0 54 | CSV: 0;-1;10;18;18;18;18;13;15;18;20;18;18;18;13;18;4823;0;20;0 55 | CSV: 0;-1;10;17;17;18;17;13;15;18;18;20;18;18;13;18;4823;0;20;0 56 | CSV: 1;0;10;17;17;18;18;13;15;18;18;18;18;18;13;17;4823;0;20;0 57 | CSV: 0;-1;8;17;17;18;17;13;15;18;18;18;18;18;13;18;4822;0;20;0 58 | CSV: 1;0;10;17;18;18;17;13;13;18;18;18;18;18;13;18;4823;0;20;0 59 | CSV: 0;0;10;17;17;18;17;12;13;18;18;18;18;18;13;18;4822;0;20;0 60 | CSV: 0;-1;10;17;17;18;17;13;15;18;18;18;18;18;13;17;4822;-20;20;0 61 | CSV: 0;-1;10;17;17;18;17;13;13;18;18;18;18;18;13;18;4822;0;20;0 62 | CSV: 0;-1;10;17;17;18;18;12;13;18;18;18;18;18;13;18;4823;0;20;0 63 | CSV: 0;0;10;17;17;18;17;13;15;18;18;18;18;18;13;17;4822;0;20;0 64 | CSV: 0;0;10;18;17;18;17;12;13;17;18;18;18;18;13;18;4822;20;20;0 65 | CSV: 1;-1;8;17;17;18;17;14;15;18;17;18;18;18;13;18;4822;-20;20;0 66 | CSV: 0;-1;10;17;18;18;17;13;13;18;18;18;18;18;13;18;4822;0;20;0 67 | *** ALARM FLAGS *** 68 | Alarm bits=0x8 | 0b1000 69 | Alarm bit=0b1000 -> Discharging undervoltage 70 | 71 | CSV: 0;-1;10;17;18;18;17;13;13;18;18;18;18;18;13;18;4822;0;0;0 72 | SOC[%]=0 -> Remaining Capacity[Ah]=0 73 | Set LCD display page to: 0 74 | CSV: 1;0;11;17;18;18;18;13;15;18;18;18;18;18;13;17;4823;0;0;0 75 | Charging MosFet enabled, active | Discharging MosFet enabled, not active | Balancing enabled, not active 76 | CSV: 1;-1;10;18;17;18;18;13;15;18;18;18;20;21;15;18;4823;0;0;0 77 | CSV: 1;0;11;17;17;20;20;15;15;18;20;18;20;18;13;18;4823;0;0;0 78 | CSV: 1;0;10;17;18;18;18;13;15;18;18;20;20;18;13;17;4823;0;0;0 79 | CSV: 4;7;17;25;24;22;22;15;28;23;24;26;26;27;24;29;4830;125;0;0 80 | Battery Voltage[V]=48.30, Current[A]=1.25, Power[W]=60, Difference to full[V]=6.9 81 | Charging MosFet enabled, active | Discharging MosFet enabled, active | Balancing enabled, not active 82 | CSV: 23;21;34;31;21;-5;-5;-37;49;-5;3;16;16;31;38;45;4862;1236;0;0 83 | Battery Voltage[V]=48.62, Current[A]=12.36, Power[W]=600, Difference to full[V]=6.6 84 | CSV: 28;26;37;35;25;-1;-1;-32;55;-57;-34;-8;-8;23;43;47;4870;2766;0;1 85 | Battery Voltage[V]=48.70, Current[A]=27.66, Power[W]=1347, Difference to full[V]=6.5 86 | Charging MosFet enabled, active | Discharging MosFet enabled, active | Balancing enabled, active 87 | CSV: 34;33;44;24;65532;65475;65475;65414;50;65477;65503;-5;-5;24;44;48;4872;2787;0;1 88 | Battery Voltage[V]=48.72, Current[A]=27.87, Power[W]=1357, Difference to full[V]=6.5 89 | CSV: 38;37;50;28;0;-57;65478;65420;61;65483;65507;65532;65532;26;48;53;4881;2787;0;1 90 | Battery Voltage[V]=48.81, Current[A]=27.87, Power[W]=1360, Difference to full[V]=6.4 91 | CSV: 44;40;51;31;0;65481;65481;65424;55;65483;65509;1;1;31;51;55;4884;2766;0;1 92 | Battery Voltage[V]=48.84, Current[A]=27.66, Power[W]=1350, Difference to full[V]=6.4 93 | CSV: 44;40;51;31;0;65481;65481;65426;58;65485;65512;3;5;34;54;58;4887;2787;0;1 94 | Battery Voltage[V]=48.87, Current[A]=27.87, Power[W]=1362, Difference to full[V]=6.3 95 | CSV: 47;44;54;33;4;65483;65483;65428;61;65487;65513;4;7;37;56;60;4892;2745;0;1 96 | Battery Voltage[V]=48.92, Current[A]=27.45, Power[W]=1342, Difference to full[V]=6.3 97 | CSV: 49;47;56;35;6;65487;65487;65433;64;65492;65518;8;12;39;59;65;4896;2787;0;1 98 | Battery Voltage[V]=48.96, Current[A]=27.87, Power[W]=1364, Difference to full[V]=6.2 99 | CSV: 51;49;58;38;7;65489;65487;65433;66;65492;65519;9;12;42;62;67;4899;2766;0;1 100 | Battery Voltage[V]=48.99, Current[A]=27.66, Power[W]=1355, Difference to full[V]=6.2 101 | CSV: 53;50;59;39;10;65491;65491;65436;69;65493;65519;9;13;43;65;69;4902;2808;0;1 102 | Battery Voltage[V]=49.02, Current[A]=28.08, Power[W]=1376, Difference to full[V]=6.2 103 | CSV: 55;53;61;39;10;65491;65491;65439;73;65497;65522;10;14;42;65;69;4902;2766;0;1 104 | Battery Voltage[V]=49.02, Current[A]=27.66, Power[W]=1355, Difference to full[V]=6.2 105 | CSV: 58;54;62;42;14;65496;65495;65436;71;65496;65522;11;16;45;66;71;4905;2766;0;1 106 | Battery Voltage[V]=49.05, Current[A]=27.66, Power[W]=1356, Difference to full[V]=6.2 107 | CSV: 59;55;65;43;15;65496;65497;65437;72;65498;65523;12;16;46;69;72;4908;2766;0;1 108 | Battery Voltage[V]=49.08, Current[A]=27.66, Power[W]=1357, Difference to full[V]=6.1 109 | CSV: 58;55;67;48;21;-34;-37;65442;75;-37;65524;15;18;49;70;75;4911;2787;0;1 110 | Battery Voltage[V]=49.11, Current[A]=27.87, Power[W]=1368, Difference to full[V]=6.1 111 | CSV: 62;59;70;50;21;65500;65498;65442;82;65503;65526;15;17;48;70;76;4914;2787;0;1 112 | Battery Voltage[V]=49.14, Current[A]=27.87, Power[W]=1369, Difference to full[V]=6.1 113 | CSV: 62;59;70;50;21;65500;65498;65445;76;65503;65530;20;23;51;73;75;4917;2787;0;1 114 | Battery Voltage[V]=49.17, Current[A]=27.87, Power[W]=1370, Difference to full[V]=6.0 115 | CSV: 65;61;71;50;20;65501;65500;65445;81;65505;65529;17;23;53;75;78;4919;2787;0;1 116 | Battery Voltage[V]=49.19, Current[A]=27.87, Power[W]=1370, Difference to full[V]=6.0 117 | CSV: 67;64;73;54;23;65503;-34;65444;88;65511;-1;22;25;51;75;78;4919;2787;0;1 118 | CSV: 67;64;71;51;23;65505;65506;65449;81;65511;0;23;26;54;73;77;4923;2787;0;1 119 | Battery Voltage[V]=49.23, Current[A]=27.87, Power[W]=1372, Difference to full[V]=6.0 120 | CSV: 67;66;76;58;30;65510;65506;65446;89;65508;65534;22;28;58;78;83;4924;2766;1;1 121 | SOC[%]=1 -> Remaining Capacity[Ah]=1 122 | CSV: 71;67;77;59;30;65511;65508;65448;91;65511;-1;23;30;59;80;85;4927;2766;1;1 123 | Battery Voltage[V]=49.27, Current[A]=27.66, Power[W]=1362, Difference to full[V]=5.9 124 | CSV: 72;69;78;59;30;65508;65508;65449;91;65512;2;23;30;59;82;86;4930;2787;1;1 125 | Battery Voltage[V]=49.30, Current[A]=27.87, Power[W]=1373, Difference to full[V]=5.9 126 | CSV: 72;69;78;59;30;65511;65510;65452;89;65514;3;25;31;59;82;87;4932;2787;1;1 127 | Battery Voltage[V]=49.32, Current[A]=27.87, Power[W]=1374, Difference to full[V]=5.9 128 | CSV: 75;71;78;58;29;65510;65509;65451;86;65516;3;25;30;59;82;87;4934;2808;1;1 129 | Battery Voltage[V]=49.34, Current[A]=28.08, Power[W]=1385, Difference to full[V]=5.9 130 | Timeout reached, suppress consecutive error beeps 131 | CSV: 75;71;78;60;31;65512;65512;65455;88;65518;8;31;33;61;83;86;4936;2787;1;1 132 | Battery Voltage[V]=49.36, Current[A]=27.87, Power[W]=1375, Difference to full[V]=5.8 133 | CSV: 75;71;78;61;33;65516;65516;65457;94;65521;10;33;34;62;85;87;4939;2787;1;1 134 | Battery Voltage[V]=49.39, Current[A]=27.87, Power[W]=1376, Difference to full[V]=5.8 135 | CSV: 75;72;81;62;36;65517;65517;65458;99;65521;10;34;38;65;86;88;4941;2787;1;1 136 | Battery Voltage[V]=49.41, Current[A]=27.87, Power[W]=1377, Difference to full[V]=5.8 137 | CSV: 76;72;85;64;37;65518;65517;65459;97;65521;9;35;37;66;89;91;4942;2808;1;1 138 | CSV: 77;75;87;67;41;65520;65519;65459;97;65518;10;35;37;66;89;91;4942;2766;1;1 139 | Battery Voltage[V]=49.42, Current[A]=27.66, Power[W]=1366, Difference to full[V]=5.8 140 | CSV: 80;77;88;69;41;65521;65518;65458;100;65519;11;36;41;70;91;93;4945;2787;1;1 141 | Battery Voltage[V]=49.45, Current[A]=27.87, Power[W]=1378, Difference to full[V]=5.8 142 | CSV: 82;80;91;70;42;65521;65518;65460;102;65521;11;34;41;70;92;94;4946;2787;1;1 143 | CSV: 86;81;89;69;39;65521;65519;65462;99;-8;14;39;41;69;91;92;4949;2787;1;1 144 | Battery Voltage[V]=49.49, Current[A]=27.87, Power[W]=1379, Difference to full[V]=5.7 145 | CSV: 86;81;89;69;39;65521;65524;65466;103;65524;15;39;43;72;93;94;4950;2787;1;1 146 | CSV: 83;80;91;72;45;65525;65523;65463;104;65526;16;41;44;75;96;97;4952;2766;1;1 147 | Battery Voltage[V]=49.52, Current[A]=27.66, Power[W]=1369, Difference to full[V]=5.7 148 | CSV: 85;81;93;73;45;-8;65525;65466;107;65526;16;41;47;76;96;99;4955;2766;1;1 149 | Battery Voltage[V]=49.55, Current[A]=27.66, Power[W]=1370, Difference to full[V]=5.7 150 | CSV: 86;85;94;76;48;-8;65523;65465;107;65527;17;42;48;77;98;100;4956;2787;1;1 151 | CSV: 87;86;96;76;47;65527;65524;65465;107;-8;18;41;48;77;98;103;4958;2808;1;1 152 | Battery Voltage[V]=49.58, Current[A]=28.08, Power[W]=1392, Difference to full[V]=5.6 153 | CSV: 88;87;97;77;49;65530;65527;65466;108;65530;20;41;47;80;100;104;4960;2787;1;1 154 | CSV: 89;88;97;77;49;-8;65526;65466;110;65529;20;42;50;80;100;104;4960;2808;2;1 155 | SOC[%]=2 -> Remaining Capacity[Ah]=2 156 | CSV: 92;89;98;79;49;65529;65526;65467;110;65529;20;42;49;79;102;105;4961;2808;2;1 157 | CSV: 94;91;100;81;52;-5;65529;65470;112;65533;21;43;50;81;103;107;4963;2766;2;1 158 | Battery Voltage[V]=49.63, Current[A]=27.66, Power[W]=1372, Difference to full[V]=5.6 159 | CSV: 94;92;102;81;52;65532;65529;65472;113;65533;22;44;52;81;104;108;4965;2787;2;1 160 | Battery Voltage[V]=49.65, Current[A]=27.87, Power[W]=1383, Difference to full[V]=5.6 161 | CSV: 96;93;103;82;52;65534;-5;65473;114;-1;22;45;52;81;105;108;4966;2766;2;1 162 | CSV: 96;93;103;82;52;65534;-5;65473;108;65534;23;46;52;81;105;109;4968;2808;2;1 163 | Battery Voltage[V]=49.68, Current[A]=28.08, Power[W]=1395, Difference to full[V]=5.5 164 | CSV: 97;94;104;82;52;65532;-5;65472;108;-1;25;48;54;83;107;110;4969;2787;2;1 165 | CSV: 99;94;105;85;54;65534;65532;65474;114;3;29;52;56;82;105;107;4972;2808;2;1 166 | Battery Voltage[V]=49.72, Current[A]=28.08, Power[W]=1396, Difference to full[V]=5.5 167 | CSV: 100;96;103;83;53;65534;65534;65477;118;2;30;54;58;87;109;109;4972;2787;2;1 168 | CSV: 97;94;105;86;59;4;2;-57;119;6;30;52;55;83;107;110;4975;2766;2;1 169 | Battery Voltage[V]=49.75, Current[A]=27.66, Power[W]=1376, Difference to full[V]=5.5 170 | CSV: 102;98;108;87;58;3;0;65482;112;5;31;57;60;88;110;112;4977;2766;2;1 171 | Battery Voltage[V]=49.77, Current[A]=27.66, Power[W]=1376, Difference to full[V]=5.4 172 | CSV: 103;99;109;87;57;1;65534;65481;121;6;30;52;57;85;109;112;4977;2808;2;1 173 | Battery Voltage[V]=49.77, Current[A]=28.08, Power[W]=1397, Difference to full[V]=5.4 174 | CSV: 104;100;109;88;58;2;1;65482;114;3;30;56;59;88;112;113;4978;2787;2;1 175 | CSV: 105;102;109;87;57;2;1;65481;114;4;31;57;61;91;113;114;4979;2808;2;1 176 | CSV: 105;103;110;88;58;4;2;65483;115;5;31;56;60;90;114;116;4980;2787;2;1 177 | CSV: 108;104;109;89;61;6;6;65486;118;9;33;58;64;93;114;118;4981;2745;2;1 178 | Battery Voltage[V]=49.81, Current[A]=27.45, Power[W]=1367, Difference to full[V]=5.4 179 | CSV: 108;104;109;89;60;6;6;65484;119;8;33;57;63;93;115;118;4983;2766;2;1 180 | Battery Voltage[V]=49.83, Current[A]=27.66, Power[W]=1378, Difference to full[V]=5.4 181 | CSV: 108;104;109;89;60;6;6;65487;121;9;33;58;63;93;115;119;4984;2787;2;1 182 | CSV: 108;104;110;91;61;7;8;65486;125;9;34;57;62;92;116;120;4985;2808;3;1 183 | SOC[%]=3 -> Remaining Capacity[Ah]=3 184 | CSV: 109;104;112;90;62;8;9;65488;127;13;36;58;64;93;116;121;4987;2766;3;1 185 | Battery Voltage[V]=49.87, Current[A]=27.66, Power[W]=1379, Difference to full[V]=5.3 186 | CSV: 109;104;112;93;65;9;11;65487;130;13;36;59;64;93;118;123;4990;2808;3;1 187 | Battery Voltage[V]=49.90, Current[A]=28.08, Power[W]=1401, Difference to full[V]=5.3 188 | CSV: 110;108;116;97;68;13;9;65489;124;16;42;63;68;96;116;119;4992;2766;3;1 189 | Battery Voltage[V]=49.92, Current[A]=27.66, Power[W]=1380, Difference to full[V]=5.3 190 | CSV: 113;109;116;96;65;13;10;65490;129;17;42;63;68;96;116;119;4992;2766;3;1 191 | CSV: 110;107;115;97;69;14;14;65493;124;15;41;63;70;99;121;124;4992;2787;3;1 192 | CSV: 112;108;115;97;70;15;15;65494;125;18;44;68;70;97;118;120;4994;2787;3;1 193 | Battery Voltage[V]=49.94, Current[A]=27.87, Power[W]=1391, Difference to full[V]=5.3 194 | CSV: 113;108;115;98;70;15;15;65495;125;19;44;68;70;98;119;123;4996;2787;3;1 195 | Battery Voltage[V]=49.96, Current[A]=27.87, Power[W]=1392, Difference to full[V]=5.2 196 | CSV: 114;110;118;98;70;16;16;65495;126;20;46;68;71;98;120;124;4997;2787;3;1 197 | CSV: 115;110;118;99;71;17;15;65494;127;22;46;69;71;99;121;124;4999;2766;3;1 198 | Battery Voltage[V]=49.99, Current[A]=27.66, Power[W]=1382, Difference to full[V]=5.2 199 | CSV: 115;110;118;99;71;19;19;65497;130;22;47;69;71;100;123;124;5000;2766;3;1 200 | CSV: 116;113;119;100;71;17;19;65497;127;23;47;69;72;100;124;126;5001;2787;3;1 201 | CSV: 118;114;120;100;72;18;18;65498;134;20;46;71;76;105;127;129;5002;2787;3;1 202 | CSV: 115;113;121;102;75;20;20;-37;131;20;46;69;75;105;129;130;5003;2787;3;1 203 | CSV: 120;116;126;105;76;19;16;65495;134;21;47;69;75;105;129;130;5003;2787;3;1 204 | CSV: 120;118;127;107;79;21;18;65496;137;21;47;70;76;107;129;131;5005;2787;3;1 205 | Battery Voltage[V]=50.05, Current[A]=27.87, Power[W]=1394, Difference to full[V]=5.2 206 | CSV: 120;118;129;107;79;24;20;-37;139;21;47;71;77;107;130;132;5006;2766;3;1 207 | CSV: 121;118;129;108;80;24;20;-37;140;24;49;74;79;108;131;132;5007;2787;3;1 208 | CSV: 120;119;129;109;81;25;21;-37;140;23;49;74;80;109;131;134;5009;2787;4;1 209 | SOC[%]=4 -> Remaining Capacity[Ah]=4 210 | Battery Voltage[V]=50.09, Current[A]=27.87, Power[W]=1396, Difference to full[V]=5.1 211 | CSV: 123;120;130;107;79;27;25;65505;142;24;51;75;80;110;131;134;5010;2766;4;1 212 | CSV: 123;120;130;109;82;26;22;65501;141;24;52;76;81;110;134;134;5011;2787;4;1 213 | CSV: 124;120;131;110;82;26;23;65501;142;25;52;76;82;110;134;136;5013;2787;4;1 214 | Battery Voltage[V]=50.13, Current[A]=27.87, Power[W]=1397, Difference to full[V]=5.1 215 | CSV: 125;123;132;110;82;26;24;65501;141;27;52;76;82;112;135;136;5014;2766;4;1 216 | CSV: 125;124;132;112;84;29;25;-32;141;29;53;76;84;113;135;137;5015;2766;4;1 217 | CSV: 126;125;134;113;84;27;25;-32;139;29;53;77;84;113;136;139;5016;2766;4;1 218 | CSV: 129;126;135;113;84;29;25;-32;146;29;53;77;84;113;136;139;5016;2787;4;1 219 | CSV: 130;127;135;113;82;28;25;-32;146;29;53;77;82;113;136;139;5017;2787;4;1 220 | CSV: 130;126;132;112;82;28;28;65508;140;35;58;82;85;114;135;136;5019;2787;4;1 221 | Battery Voltage[V]=50.19, Current[A]=27.87, Power[W]=1398, Difference to full[V]=5.0 222 | CSV: 129;127;136;116;87;31;26;65505;147;31;57;81;87;118;137;140;5019;2787;4;1 223 | CSV: 130;126;134;113;85;34;32;65510;148;31;58;81;87;118;139;142;5021;2787;4;1 224 | Battery Voltage[V]=50.21, Current[A]=27.87, Power[W]=1399, Difference to full[V]=5.0 225 | CSV: 130;129;137;118;88;32;28;65506;147;32;57;81;88;118;139;145;5023;2787;4;1 226 | Battery Voltage[V]=50.23, Current[A]=27.87, Power[W]=1399, Difference to full[V]=5.0 227 | CSV: 132;130;137;119;90;32;28;65508;145;35;58;81;88;119;140;145;5024;2766;4;1 228 | CSV: 134;131;139;119;90;33;30;65509;146;34;59;81;90;120;142;146;5026;2787;4;1 229 | CSV: 134;131;139;119;91;34;30;65508;146;34;60;82;90;120;143;146;5026;2787;4;1 230 | CSV: 135;132;140;120;91;35;31;65510;147;36;62;82;91;121;143;146;5028;2766;4;1 231 | Battery Voltage[V]=50.28, Current[A]=27.66, Power[W]=1390, Difference to full[V]=4.9 232 | CSV: 134;132;142;121;92;37;33;65511;153;36;62;82;91;121;143;146;5028;2787;4;1 233 | CSV: 135;132;143;123;93;37;34;65512;152;36;62;85;92;121;145;147;5030;2787;4;1 234 | Battery Voltage[V]=50.30, Current[A]=27.87, Power[W]=1401, Difference to full[V]=4.9 235 | CSV: 137;135;145;123;93;36;34;65516;152;40;64;85;90;119;143;147;5031;2787;5;1 236 | SOC[%]=5 -> Remaining Capacity[Ah]=5 237 | CSV: 139;135;143;121;92;36;35;65515;153;41;65;88;92;120;142;145;5033;2787;5;1 238 | Battery Voltage[V]=50.33, Current[A]=27.87, Power[W]=1402, Difference to full[V]=4.9 239 | CSV: 131;127;132;112;80;25;23;65516;148;37;65;88;95;124;147;148;5033;2787;5;1 240 | CSV: 131;127;123;121;109;91;87;69;125;21;49;71;76;104;129;130;5010;1006;5;1 241 | Battery Voltage[V]=50.10, Current[A]=10.06, Power[W]=504, Difference to full[V]=5.1 242 | All alarms are cleared 243 | CSV: 118;115;124;120;109;91;87;69;125;90;100;104;110;120;126;126;5002;83;5;1 244 | Battery Voltage[V]=50.02, Current[A]=0.83, Power[W]=41, Difference to full[V]=5.2 245 | -------------------------------------------------------------------------------- /extras/JK-BMS.log: -------------------------------------------------------------------------------- 1 | START ../src/JK-BMSToPylontechCAN.cpp 2 | Version 2.0 from Sep 8 2023 3 | Serial to JK-BMS started with 115200 bit/s! 4 | CAN started with 500 kbit/s! 5 | Page switching button is at pin 2 6 | If you push the debug button at pin 3, additional debug data is printed once 7 | 2000 ms between 2 BMS requests 8 | 2000 ms between 2 CAN transmissions 9 | 10 | *** BMS INFO *** 11 | Protocol Version Number=1 12 | Software Version Number=11.XW_S11.26___ 13 | Modify Parameter Password=1234 14 | # External Temperature Sensors=2 15 | 16 | *** BATTERY INFO *** 17 | Manufacturer Date=2307 18 | Manufacturer Id=Armins__BMS1JK_B2A20S20P 19 | Device ID String=Armins__ 20 | Device Address=1 21 | Total Battery Capacity[Ah]=115, Low Capacity Alarm Percent=20 22 | Charging Cycles=1 23 | Total Charging Cycle Capacity=218 24 | # Battery Cells=16, Cell Count=16 25 | 26 | *** VOLTAGE PROTECTION INFO *** 27 | Battery Overvoltage Protection[mV]=55200, Undervoltage=48000 28 | Cell Overvoltage Protection[mV]=3450, Recovery=3400, Delay[s]=5 29 | Cell Undervoltage Protection[mV]=3000, Recovery=3050, Delay[s]=5 30 | Cell Voltage Difference Protection[mV]=300 31 | Discharging Overcurrent Protection[A]=80, Delay[s]=30 32 | Charging Overcurrent Protection[A]=43, Delay[s]=30 33 | 34 | *** TEMPERATURE PROTECTION INFO *** 35 | Power MosFet Temperature Protection=80, Recovery=70 36 | Sensor1 Temperature Protection=100, Recovery=100 37 | Sensor1 to Sensor2 Temperature Difference Protection=20 38 | Charge Overtemperature Protection=45, Discharge=45 39 | Charge Undertemperature Protection=3, Recovery=5 40 | Discharge Undertemperature Protection=-20, Recovery=-10 41 | 42 | *** MISC INFO *** 43 | Balance Starting Cell Voltage=[mV]3350 44 | Balance Triggering Voltage Difference[mV]=10 45 | 46 | Current Calibration[mA]=1048 47 | Sleep Wait Time[s]=10 48 | 49 | Dedicated Charge Switch Active=0 50 | Start Current Calibration State=0 51 | Battery Actual Capacity[Ah]=115 52 | 53 | Total Runtime Minutes=49260 -> 34D05H00M 54 | *** CELL INFO *** 55 | 16 Cells, Minimum=3397 mV, Maximum=3408mV, Delta=11 mV, Average=3403 mV 56 | 1=3397 mV, 2=3399 mV, 3=3400 mV, 4=3403 mV, 5=3406 mV, 6=3407 mV, 7=3407 mV, 8=3408 mV, 57 | 9=3400 mV, 10=3406 mV, 11=3407 mV, 12=3404 mV, 13=3404 mV, 14=3403 mV, 15=3401 mV, 16=3402 mV, 58 | 59 | Temperature: Power MosFet=28, Sensor 1=25, Sensor 2=25 60 | SOC[%]=99 -> Remaining Capacity[Ah]=113 61 | Battery Voltage[V]=54.41, Current[A]=-1.25, Power[W]=-68 62 | Battery Voltage difference to full[V]=0.8 63 | Charging MosFet enabled, active | Discharging MosFet enabled, active | Balancing enabled, not active 64 | Battery Voltage[V]=54.41, Current[A]=1.25, Power[W]=68 65 | Battery Voltage difference to full[V]=0.8 66 | 67 | Set LCD display page to: 0 68 | Set LCD display page to: 1 69 | Set LCD display page to: 2 70 | Debug mode just activated 71 | Send CAN 72 | CANId=0x351, FrameLength=8, Data=0x28, 0x2, 0xF4, 0x1, 0x20, 0x3, 0xE0, 0x1 73 | CANId=0x355, FrameLength=4, Data=0x3C, 0x0, 0x64, 0x0 74 | CANId=0x356, FrameLength=6, Data=0x6C, 0x14, 0x0, 0x0, 0xDC, 0x0 75 | CANId=0x35E, FrameLength=8, Data=0x50, 0x59, 0x4C, 0x4F, 0x4E, 0x20, 0x20, 0x20 76 | CANId=0x35C, FrameLength=2, Data=0xC0, 0x0 77 | CANId=0x305, FrameLength=8, Data=0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 78 | CANId=0x359, FrameLength=7, Data=0x0, 0x0, 0x0, 0x0, 0x1, 0x50, 0x4E 79 | 80 | Send requestFrame with TxToJKBMS 81 | 0x4E 0x57 0x0 0x13 0x0 0x0 0x0 0x0 0x6 0x3 0x0 0x0 0x0 0x0 0x0 0x0 0x68 0x0 0x0 0x1 0x29 82 | 291 bytes received 83 | 0x00 0x4E 0x57 0x01 0x21 0x00 0x00 0x00 0x00 0x06 0x00 0x01 84 | 0x0B 0x79 0x30 0x01 0x0C 0xC6 85 | 0x10 0x02 0x0C 0xBE 0x03 0x0C 0xC7 0x04 0x0C 0xC7 0x05 0x0C 0xC7 0x06 0x0C 0xC5 0x07 86 | 0x20 0x0C 0xC6 0x08 0x0C 0xC7 0x09 0x0C 0xC2 0x0A 0x0C 0xC2 0x0B 0x0C 0xC2 0x0C 0x0C 87 | 0x30 0xC2 0x0D 0x0C 0xC1 0x0E 0x0C 0xC1 0x0F 0x0C 0xC1 0x10 0x0C 0xC1 88 | 0x3D 0x80 0x00 0x16 89 | 0x40 0x81 0x00 0x15 0x82 0x00 0x16 0x83 0x14 0x6C 0x84 0x00 0x00 0x85 0x3C 0x86 0x02 90 | 0x50 0x87 0x00 0x00 0x89 0x00 0x00 0x00 0x00 0x8A 0x00 0x10 0x8B 0x00 0x00 0x8C 0x00 91 | 0x60 0x03 0x8E 0x15 0x90 0x8F 0x12 0xC0 0x90 0x0D 0x7A 0x91 0x0D 0x48 0x92 0x00 0x05 92 | 0x70 0x93 0x0B 0xB8 0x94 0x0B 0xEA 0x95 0x00 0x05 0x96 0x01 0x2C 0x97 0x00 0x50 0x98 93 | 0x80 0x00 0x1E 0x99 0x00 0x32 0x9A 0x00 0x1E 0x9B 0x0C 0x80 0x9C 0x00 0x0A 0x9D 0x01 94 | 0x90 0x9E 0x00 0x50 0x9F 0x00 0x46 0xA0 0x00 0x64 0xA1 0x00 0x64 0xA2 0x00 0x14 0xA3 95 | 0xA0 0x00 0x3C 0xA4 0x00 0x46 0xA5 0x00 0x05 0xA6 0x00 0x0A 0xA7 0xFF 0xEC 0xA8 0xFF 96 | 0xB0 0xF6 0xA9 0x10 0xAA 0x00 0x00 0x00 0x6E 0xAB 0x01 0xAC 0x01 0xAD 0x04 0x18 0xAE 97 | 0xC0 0x01 0xAF 0x00 0xB0 0x00 0x0A 0xB1 0x14 0xB2 0x31 0x32 0x33 0x34 0x35 0x36 0x00 98 | 0xD0 0x00 0x00 0x00 0xB3 0x00 0xB4 0x41 0x72 0x6D 0x69 0x6E 0x73 0x5F 0x5F 0xB5 0x32 99 | 0xE0 0x33 0x30 0x37 0xB6 0x00 0x00 0x0E 0x85 0xB7 0x31 0x31 0x2E 0x58 0x57 0x5F 0x53 100 | 0xF0 0x31 0x31 0x2E 0x32 0x36 0x5F 0x5F 0x5F 0xB8 0x00 0xB9 0x00 0x00 0x00 0x6E 0xBA 101 | 0x100 0x41 0x72 0x6D 0x69 0x6E 0x73 0x5F 0x5F 0x42 0x4D 0x53 0x00 0x4A 0x4B 0x5F 0x42 102 | 0x110 0x32 0x41 0x32 0x30 0x53 0x32 0x30 0x50 0xC0 0x01 103 | 0x11A 0x00 0x00 0x00 0x00 0x68 0x00 104 | 0x120 0x00 0x51 0xCA 105 | 106 | Send CAN 107 | CANId=0x351, FrameLength=8, Data=0x28, 0x2, 0xF4, 0x1, 0x20, 0x3, 0xE0, 0x1 108 | CANId=0x355, FrameLength=4, Data=0x3C, 0x0, 0x64, 0x0 109 | CANId=0x356, FrameLength=6, Data=0x6C, 0x14, 0x0, 0x0, 0xDC, 0x0 110 | CANId=0x35E, FrameLength=8, Data=0x50, 0x59, 0x4C, 0x4F, 0x4E, 0x20, 0x20, 0x20 111 | CANId=0x35C, FrameLength=2, Data=0xC0, 0x0 112 | CANId=0x305, FrameLength=8, Data=0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 113 | CANId=0x359, FrameLength=7, Data=0x0, 0x0, 0x0, 0x0, 0x1, 0x50, 0x4E 114 | Temperature: Power MosFet=22, Sensor 1=21, Sensor 2=21 115 | Battery Voltage[V]=52.28, Current[A]=0.20, Power[W]=10 116 | Send CAN 117 | CANId=0x351, FrameLength=8, Data=0x28, 0x2, 0xF4, 0x1, 0x20, 0x3, 0xE0, 0x1 118 | CANId=0x355, FrameLength=4, Data=0x3C, 0x0, 0x64, 0x0 119 | CANId=0x356, FrameLength=6, Data=0x6C, 0x14, 0x2, 0x0, 0xDC, 0x0 120 | CANId=0x35E, FrameLength=8, Data=0x50, 0x59, 0x4C, 0x4F, 0x4E, 0x20, 0x20, 0x20 121 | CANId=0x35C, FrameLength=2, Data=0xC0, 0x0 122 | CANId=0x305, FrameLength=8, Data=0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 123 | CANId=0x359, FrameLength=7, Data=0x0, 0x0, 0x0, 0x0, 0x1, 0x50, 0x4E 124 | Battery Voltage[V]=52.28, Current[A]=0.41, Power[W]=21 125 | Set LCD display page to: 3 126 | Send CAN 127 | CANId=0x351, FrameLength=8, Data=0x28, 0x2, 0xF4, 0x1, 0x20, 0x3, 0xE0, 0x1 128 | CANId=0x355, FrameLength=4, Data=0x3C, 0x0, 0x64, 0x0 129 | CANId=0x356, FrameLength=6, Data=0x6C, 0x14, 0x4, 0x0, 0xDC, 0x0 130 | CANId=0x35E, FrameLength=8, Data=0x50, 0x59, 0x4C, 0x4F, 0x4E, 0x20, 0x20, 0x20 131 | CANId=0x35C, FrameLength=2, Data=0xC0, 0x0 132 | CANId=0x305, FrameLength=8, Data=0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 133 | CANId=0x359, FrameLength=7, Data=0x0, 0x0, 0x0, 0x0, 0x1, 0x50, 0x4E 134 | Charging MosFet enabled, active | Discharging MosFet enabled, active | Balancing enabled, active 135 | Set LCD display page to: 0 136 | Send CAN 137 | CANId=0x351, FrameLength=8, Data=0x28, 0x2, 0xF4, 0x1, 0x20, 0x3, 0xE0, 0x1 138 | CANId=0x355, FrameLength=4, Data=0x3C, 0x0, 0x64, 0x0 139 | CANId=0x356, FrameLength=6, Data=0x6C, 0x14, 0x4, 0x0, 0xDC, 0x0 140 | CANId=0x35E, FrameLength=8, Data=0x50, 0x59, 0x4C, 0x4F, 0x4E, 0x20, 0x20, 0x20 141 | CANId=0x35C, FrameLength=2, Data=0xC0, 0x0 142 | CANId=0x305, FrameLength=8, Data=0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 143 | CANId=0x359, FrameLength=7, Data=0x0, 0x0, 0x0, 0x0, 0x1, 0x50, 0x4E 144 | Set LCD display page to: 1 145 | Send CAN 146 | CANId=0x351, FrameLength=8, Data=0x28, 0x2, 0xF4, 0x1, 0x20, 0x3, 0xE0, 0x1 147 | CANId=0x355, FrameLength=4, Data=0x3C, 0x0, 0x64, 0x0 148 | CANId=0x356, FrameLength=6, Data=0x6C, 0x14, 0x4, 0x0, 0xDC, 0x0 149 | CANId=0x35E, FrameLength=8, Data=0x50, 0x59, 0x4C, 0x4F, 0x4E, 0x20, 0x20, 0x20 150 | CANId=0x35C, FrameLength=2, Data=0xC0, 0x0 151 | CANId=0x305, FrameLength=8, Data=0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 152 | CANId=0x359, FrameLength=7, Data=0x0, 0x0, 0x0, 0x0, 0x1, 0x50, 0x4E 153 | Set LCD display page to: 2 154 | Send CAN 155 | CANId=0x351, FrameLength=8, Data=0x28, 0x2, 0xF4, 0x1, 0x20, 0x3, 0xE0, 0x1 156 | CANId=0x355, FrameLength=4, Data=0x3C, 0x0, 0x64, 0x0 157 | CANId=0x356, FrameLength=6, Data=0x6C, 0x14, 0x4, 0x0, 0xDC, 0x0 158 | CANId=0x35E, FrameLength=8, Data=0x50, 0x59, 0x4C, 0x4F, 0x4E, 0x20, 0x20, 0x20 159 | CANId=0x35C, FrameLength=2, Data=0xC0, 0x0 160 | CANId=0x305, FrameLength=8, Data=0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 161 | CANId=0x359, FrameLength=7, Data=0x0, 0x0, 0x0, 0x0, 0x1, 0x50, 0x4E 162 | Temperature: Power MosFet=23, Sensor 1=21, Sensor 2=21 163 | Temperature: Power MosFet=22, Sensor 1=21, Sensor 2=21 164 | Charging MosFet enabled, active | Discharging MosFet enabled, active | Balancing enabled, not active 165 | Temperature: Power MosFet=22, Sensor 1=21, Sensor 2=22 166 | Battery Voltage[V]=52.28, Current[A]=0.00, Power[W]=0 167 | Charging MosFet enabled, active | Discharging MosFet enabled, active | Balancing enabled, active 168 | Charging MosFet enabled, active | Discharging MosFet enabled, active | Balancing enabled, not active 169 | Charging MosFet enabled, active | Discharging MosFet enabled, active | Balancing enabled, active 170 | Charging MosFet enabled, active | Discharging MosFet enabled, active | Balancing enabled, not active 171 | Charging MosFet enabled, active | Discharging MosFet enabled, active | Balancing enabled, active 172 | Charging MosFet enabled, active | Discharging MosFet enabled, active | Balancing enabled, not active 173 | 174 | ... 175 | 176 | Total Runtime Minutes=124506 -> 86D11H50M 177 | *** CELL INFO *** 178 | 16 Cells, Minimum=3069 mV, Maximum=3077mV, Delta=8 mV, Average=3074 mV 179 | 1=3069 mV, 2=3069 mV, 3=3070 mV, 4=3076 mV, 5=3076 mV, 6=3076 mV, 7=3076 mV, 8=3072 mV, 180 | 9=3072 mV, 10=3076 mV, 11=3076 mV, 12=3077 mV, 13=3077 mV, 14=3076 mV, 15=3071 mV, 16=3076 mV, 181 | 182 | *** CELL STATISTICS *** 183 | Total balancing time=2008 s -> 0D00H33M28S 184 | Cell Minimum percentages 185 | 1=37 % | 768, 2=37 % | 758, 3=16 % | 328, 4= 0 % | 0, 5= 0 % | 0, 6= 0 % | 0, 7= 0 % | 0, 8= 3 % | 68, 186 | 9= 0 % | 0, 10= 0 % | 0, 11= 0 % | 0, 12= 0 % | 0, 13= 0 % | 0, 14= 0 % | 0, 15= 5 % | 107, 16= 0 % | 0, 187 | Cell Maximum percentages 188 | 1= 0 % | 0, 2= 0 % | 0, 3= 0 % | 0, 4= 5 % | 226, 5= 6 % | 263, 6= 7 % | 311, 7= 7 % | 329, 8= 0 % | 0, 189 | 9= 0 % | 0, 10= 9 % | 400, 11=12 % | 531, 12=13 % | 572, 13=14 % | 615, 14=10 % | 441, 15= 0 % | 0, 16=14 % | 624, 190 | -------------------------------------------------------------------------------- /extras/SOCDataESR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/extras/SOCDataESR.png -------------------------------------------------------------------------------- /extras/SOCDataNoESR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/extras/SOCDataNoESR.png -------------------------------------------------------------------------------- /pictures/644Board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/644Board.jpg -------------------------------------------------------------------------------- /pictures/AlarmPage_MosFetOvertemperature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/AlarmPage_MosFetOvertemperature.png -------------------------------------------------------------------------------- /pictures/AlarmPage_Undervoltage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/AlarmPage_Undervoltage.png -------------------------------------------------------------------------------- /pictures/AutomaticBrightness.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/AutomaticBrightness.jpg -------------------------------------------------------------------------------- /pictures/BMS-CAN_PCB_top_v0.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/BMS-CAN_PCB_top_v0.1.png -------------------------------------------------------------------------------- /pictures/BigInfoPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/BigInfoPage.png -------------------------------------------------------------------------------- /pictures/BreadboardAndOverviewPage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/BreadboardAndOverviewPage.jpg -------------------------------------------------------------------------------- /pictures/BreadboardDetail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/BreadboardDetail.jpg -------------------------------------------------------------------------------- /pictures/BreadboardSimple.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/BreadboardSimple.jpg -------------------------------------------------------------------------------- /pictures/CANInfoPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/CANInfoPage.png -------------------------------------------------------------------------------- /pictures/CapacityStatisticsPercentagePage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/CapacityStatisticsPercentagePage.png -------------------------------------------------------------------------------- /pictures/CapacityStatisticsVoltagePage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/CapacityStatisticsVoltagePage.png -------------------------------------------------------------------------------- /pictures/CellInfoPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/CellInfoPage.png -------------------------------------------------------------------------------- /pictures/CellInfoPage_shortVoltages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/CellInfoPage_shortVoltages.png -------------------------------------------------------------------------------- /pictures/CompleteInstallation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/CompleteInstallation.jpg -------------------------------------------------------------------------------- /pictures/EasyEda_shematics_by_Ngoc_Dang_Dinh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/EasyEda_shematics_by_Ngoc_Dang_Dinh.png -------------------------------------------------------------------------------- /pictures/NanoBottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/NanoBottom.jpg -------------------------------------------------------------------------------- /pictures/NanoTop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/NanoTop.jpg -------------------------------------------------------------------------------- /pictures/NoBreadboardOverview1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/NoBreadboardOverview1.jpg -------------------------------------------------------------------------------- /pictures/NoBreadboardOverview2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/NoBreadboardOverview2.jpg -------------------------------------------------------------------------------- /pictures/OverviewPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/OverviewPage.png -------------------------------------------------------------------------------- /pictures/StandaloneTest.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/StandaloneTest.jpg -------------------------------------------------------------------------------- /pictures/StatisticsMaxPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/StatisticsMaxPage.png -------------------------------------------------------------------------------- /pictures/StatisticsMinPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArminJo/JK-BMSToPylontechCAN/292995318d0390d40c1a1daef5104673c06b684a/pictures/StatisticsMinPage.png --------------------------------------------------------------------------------