44 *
55 * If a LiPo supply is detected, the LCD display timing for standalone usage (without serial connection) is activated.
66 *
7- * Copyright (C) 2016-2021 Armin Joachimsmeyer
7+ * Copyright (C) 2016-2023 Armin Joachimsmeyer
8899 *
1010 * https://github.com/ArminJo/Smart-Battery-Module-Info_For_Arduino
3030#include < Wire.h>
3131#include " ADCUtils.hpp"
3232
33- #define VERSION_EXAMPLE " 4.1 "
33+ #define VERSION_EXAMPLE " 4.2 "
3434
3535#if defined(__AVR__)
3636/*
3737 * Available only for some AVR CPU's like ATmega328
3838 * This requires 4 resistors at Pins A0 to A3, see documentation in file MeasureVoltageAndResistance.hpp
3939 */
4040#define USE_VOLTAGE_AND_RESISTANCE_MEASUREMENT
41+ #endif // defined(__AVR__)
4142
4243/*
4344 * The charge control pin is high as long as relative charge is below 95%.
4445 * It can be used to control a NPN transistor, which collector controls a high side P FET
4546 */
46- #define CHARGE_CONTROL_PIN 9
47- #define CHARGE_SWITCH_OFF_PERCENTAGE 95
48- #define DISCHARGE_CONTROL_PIN 10 // Is high as long as relative charge is above 5%. Can be used to control a logic level FET directly.
49- #define DISCHARGE_STOP_PERCENTAGE 5
50- // #define STOP_DISCHARGE_AT_PERCENTAGE
51- #define DISCHARGE_STOP_MILLIVOLT 3300 // to be below the guessed EDV2 value
52- #define STOP_DISCHARGE_AT_MILLIVOLT
53- # if defined(STOP_DISCHARGE_AT_PERCENTAGE) && defined(STOP_DISCHARGE_AT_MILLIVOLT)
54- #warning Ignore STOP_DISCHARGE_AT_MILLIVOLT value and stop discharge at DISCHARGE_STOP_PERCENTAGE value
55- # endif
56- #endif // defined(__AVR__)
47+ #define CHARGE_CONTROL_PIN 9
48+ #define CHARGE_SWITCH_OFF_PERCENTAGE 95
49+ /*
50+ * The discharge control pin is high as long as relative charge is above 5% AND 3300 mV. It can be used to control a logic level FET directly.
51+ */
52+ #define DISCHARGE_CONTROL_PIN 10
53+ #define DISCHARGE_SWITCH_OFF_PERCENTAGE 5
54+ #define DISCHARGE_SWITCH_OFF_MILLIVOLT 3300 // to be below the guessed EDV2 value
55+ bool sCellVoltageIsBelowSwitchOffThreshold ;
56+ void checkChargeAndDischargeLimits ();
5757
5858/*
5959 * Activate the type of LCD connection you use.
@@ -124,10 +124,17 @@ LiquidCrystal myLCD(7, 8, 3, 4, 5, 6); // This also clears display
124124#define FORCE_LCD_DISPLAY_TIMING_PIN 11 // if pulled to ground, enables slow display timing as used for standalone mode (with LiPo supply)
125125
126126/*
127+ * Version 4.2 - 8/2023
128+ * - Removed compile time warnings.
129+ *
130+ * Version 4.1 - 3/2022
131+ * - Support for automatic discharge and charge.
132+ * Improved output.
133+ *
127134 * Version 4.0 - 10/2021
128- * Integrated voltage and resistance measurement.
129- * Major improvements in I2C communication and output.
130- * Detection of disconnect.
135+ * - Integrated voltage and resistance measurement.
136+ * - Major improvements in I2C communication and output.
137+ * - Detection of disconnect.
131138 *
132139 * Version 3.3 - 3/2021
133140 * - Improved standalone output.
@@ -246,7 +253,7 @@ const char Temperature[] PROGMEM = "Temperature";
246253#define VOLTAGE_PRINT_DELTA_MILLIDEGREE 100 // Print only if changed by 0.1 ore more degree
247254
248255struct SBMFunctionDescriptionStruct sSBMDynamicFunctionDescriptionArray [] = { {
249- RELATIVE_SOC, Relative_Charge, &printRelativeCharge, NULL , 0 , 0 }, { /* must be first, value is printed in Remaining_Capacity */
256+ RELATIVE_SOC, Relative_Charge, &printRelativeCharge, NULL , 0 , 0 }, { /* Must be first, because value is printed in Remaining_Capacity */
250257ABSOLUTE_SOC, Absolute_Charge, &printPercentage, NULL , 0 , 0 }, {
251258FULL_CHARGE_CAPACITY, Full_Charge_Capacity, &printCapacity, " " , 0 , 0 }/* DescriptionLCD must be not NULL */ , {
252259REMAINING_CAPACITY, Remaining_Capacity, &printCapacity, " remCap" , 0 , 0 }, {
@@ -337,10 +344,11 @@ BQ20Z70_PackVoltage, Pack_Voltage, &printVoltage, NULL, 0, 0 } };
337344 */
338345void setup () {
339346 pinMode (LED_BUILTIN, OUTPUT);
340- pinMode (CHARGE_CONTROL_PIN, OUTPUT);
341347
342- digitalWrite (DISCHARGE_CONTROL_PIN, LOW );
348+ pinMode (CHARGE_CONTROL_PIN, OUTPUT );
343349 pinMode (DISCHARGE_CONTROL_PIN, OUTPUT);
350+ digitalWrite (CHARGE_CONTROL_PIN, LOW);
351+ digitalWrite (DISCHARGE_CONTROL_PIN, LOW);
344352
345353 pinMode (FORCE_LCD_DISPLAY_TIMING_PIN, INPUT_PULLUP);
346354
@@ -351,8 +359,10 @@ void setup() {
351359 // Just to know which program is running on my Arduino
352360 Serial.println (F (" START " __FILE__ " \r\n Version " VERSION_EXAMPLE " from " __DATE__));
353361
362+ #if defined(DIDR0)
354363 // Disable digital input on all unused ADC channel pins to reduce power consumption
355364 DIDR0 = ADC0D | ADC1D | ADC2D | ADC3D;
365+ #endif
356366
357367 // set up the LCD's number of columns and rows:
358368 myLCD.begin (LCD_COLUMNS, LCD_ROWS); // This also clears display
@@ -370,11 +380,12 @@ void setup() {
370380 Wire.setClock (32000 ); // lowest rate available is 31000
371381// Wire.setClock(50000); // seen this for sony packs
372382
373- #if defined(STOP_DISCHARGE_AT_MILLIVOLT)
374- Serial.print (F (" Configured to stop discharge at " ));
375- Serial.print (DISCHARGE_STOP_MILLIVOLT);
376- Serial.println (F (" mV" ));
377- #endif
383+ Serial.print (
384+ F (
385+ " Configured to set charge control pin " STR (CHARGE_CONTROL_PIN) " to low above " STR (CHARGE_SWITCH_OFF_PERCENTAGE) " %" ));
386+ Serial.print (
387+ F (
388+ " Configured to stop discharge control pin " STR (DISCHARGE_CONTROL_PIN) " to low below " STR (DISCHARGE_SWITCH_OFF_PERCENTAGE) " % or " STR (DISCHARGE_SWITCH_OFF_MILLIVOLT) " mV" ));
378389
379390#if defined(__AVR__)
380391 if (getVCCVoltageMillivolt () < 4300 || digitalRead (FORCE_LCD_DISPLAY_TIMING_PIN) == LOW) {
@@ -445,6 +456,9 @@ void loop() {
445456 (sizeof (sSBMDynamicFunctionDescriptionArray ) / sizeof (SBMFunctionDescriptionStruct)));
446457 printSBMNonStandardInfo ();
447458
459+ // Here, all values for checking are already read in
460+ checkChargeAndDischargeLimits ();
461+
448462 // clear the display of 'H' for sGlobalI2CReadError
449463 myLCD.setCursor (19 , 0 );
450464 myLCD.print (' ' );
@@ -497,12 +511,15 @@ void TogglePin(uint8_t aPinNr) {
497511uint8_t scanForAttachedI2CDevice (void ) {
498512 static unsigned int sScanCount = 0 ;
499513 // the next 2 statements disable TWI hangup, if SDA and SCL are connected and disconnected from ground.
514+ #if defined(TWCR)
500515 TWCR = 0 ;
516+ #endif
501517 Wire.begin ();
502518
503519 auto tStartMillis = millis ();
504520 int tFoundAdress = SBM_INVALID_ADDRESS;
505- for (uint_fast8_t tI2CAddress = 0 ; tI2CAddress < 127 ; tI2CAddress++) {
521+ // We cannot use uint_fast8_t here, since it is ambiguous parameter for beginTransmission() on 16/32 bit CPU
522+ for (uint8_t tI2CAddress = 0 ; tI2CAddress < 127 ; tI2CAddress++) {
506523 Wire.beginTransmission (tI2CAddress);
507524 uint8_t tOK = Wire.endTransmission (true );
508525 if (tOK == 0 ) {
@@ -593,7 +610,7 @@ void writeCommandWithRetry(uint8_t aCommand) {
593610
594611/*
595612 * First write the command/function address byte, then read the word value for this function
596- * From the BQ spec: The processor then sends the bq2060 device address of 0001011 (bits 7–1 )
613+ * From the BQ spec: The processor then sends the bq2060 device address of 0001011 (bits 7–1 )
597614 * plus a R/W bit (bit 0) followed by an SMBus command code.
598615 */
599616uint16_t readWord (uint8_t aCommand) {
@@ -770,7 +787,7 @@ void printlnHex(uint16_t aValue) {
770787 Serial.println ();
771788}
772789
773- void printHexAndBinary (struct SBMFunctionDescriptionStruct *aSBMFunctionDescription __attribute__ ((unused)), uint16_t aValue) {
790+ void printHexAndBinary (struct SBMFunctionDescriptionStruct *aSBMFunctionDescription __attribute__ ((unused)), uint16_t aValue) {
774791 printByteHex (aValue);
775792 Serial.print (" | 0b" );
776793 Serial.print (aValue, BIN);
@@ -798,20 +815,25 @@ void printPercentage(struct SBMFunctionDescriptionStruct *aSBMFunctionDescriptio
798815
799816/*
800817 * Handles the charge and discharge pin
818+ * Requires preceding call to printRelativeCharge() by call to printFunctionDescriptionArray(sSBMDynamicFunctionDescriptionArray,...)
801819 */
802- void printRelativeCharge (struct SBMFunctionDescriptionStruct *aSBMFunctionDescription, uint16_t aPercentage) {
803- #if defined(STOP_DISCHARGE_AT_PERCENTAGE)
804- if (aPercentage < DISCHARGE_SWITCH_OFF_PERCENTAGE) {
805- digitalWrite (DISCHARGE_CONTROL_PIN, LOW);
806- } else {
807- digitalWrite (DISCHARGE_CONTROL_PIN, HIGH);
808- }
809- #endif
810- if (aPercentage > CHARGE_SWITCH_OFF_PERCENTAGE) {
820+ void checkChargeAndDischargeLimits () {
821+
822+ if (sRelativeChargePercent > CHARGE_SWITCH_OFF_PERCENTAGE) {
811823 digitalWrite (CHARGE_CONTROL_PIN, LOW);
812824 } else {
813825 digitalWrite (CHARGE_CONTROL_PIN, HIGH);
814826 }
827+
828+ if (sRelativeChargePercent < DISCHARGE_SWITCH_OFF_PERCENTAGE) {
829+ digitalWrite (DISCHARGE_CONTROL_PIN, LOW);
830+ } else if (!sCellVoltageIsBelowSwitchOffThreshold ) {
831+ digitalWrite (DISCHARGE_CONTROL_PIN, HIGH);
832+ }
833+
834+ }
835+
836+ void printRelativeCharge (struct SBMFunctionDescriptionStruct *aSBMFunctionDescription, uint16_t aPercentage) {
815837 sRelativeChargePercent = aPercentage;
816838 printPercentage (aSBMFunctionDescription, aPercentage);
817839}
@@ -939,12 +961,14 @@ void printVoltage(struct SBMFunctionDescriptionStruct *aSBMFunctionDescription,
939961void printCellVoltage (struct SBMFunctionDescriptionStruct *aSBMFunctionDescription, uint16_t aVoltage) {
940962 // test for sensible value
941963 if (aVoltage > 3000 && aVoltage < 5000 ) {
942- #if defined(STOP_DISCHARGE_AT_MILLIVOLT)
943- if (aVoltage < DISCHARGE_STOP_MILLIVOLT) {
964+ /*
965+ * Check for discharge switch off. We are called for more than one cell voltage here.
966+ */
967+ if (aVoltage < DISCHARGE_SWITCH_OFF_MILLIVOLT) {
968+ sCellVoltageIsBelowSwitchOffThreshold = true ;
944969 digitalWrite (DISCHARGE_CONTROL_PIN, LOW);
945970 Serial.println (F (" Stop voltage reached -> stop discharge" ));
946971 }
947- #endif
948972
949973 // cell voltages in row 3. 100 was not reached for a bq2084. Print if time (minutes) is not updated for more than 2 minutes.
950974 if (!sPrintOnlyChanges || sRelativeChargePercent == 0 || sRelativeChargePercent > 99
@@ -1073,7 +1097,8 @@ void printManufacturerDate(struct SBMFunctionDescriptionStruct *aSBMFunctionDesc
10731097 myLCD.print (tDateAsString);
10741098}
10751099
1076- void printSpecificationInfo (struct SBMFunctionDescriptionStruct *aSBMFunctionDescription __attribute__ ((unused)), uint16_t aSpecificationInfo) {
1100+ void printSpecificationInfo (struct SBMFunctionDescriptionStruct *aSBMFunctionDescription __attribute__ ((unused)),
1101+ uint16_t aSpecificationInfo) {
10771102 if (aSpecificationInfo >= 0x40 ) {
10781103 printByteHex (aSpecificationInfo);
10791104 Serial.print (F (" | " ));
@@ -1352,6 +1377,7 @@ void printSBMNonStandardInfo() {
13521377 }
13531378 }
13541379
1380+ sCellVoltageIsBelowSwitchOffThreshold = false ;
13551381 printFunctionDescriptionArray (sSBMNonStandardFunctionDescriptionArray ,
13561382 (sizeof (sSBMNonStandardFunctionDescriptionArray ) / sizeof (SBMFunctionDescriptionStruct)));
13571383 sGlobalI2CReadError = 0 ; // I have seen some read errors here
0 commit comments