Skip to content

Commit be755e0

Browse files
committed
Improved support for automatic discharge and charge
1 parent 68587ca commit be755e0

File tree

8 files changed

+95
-53
lines changed

8 files changed

+95
-53
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ An example schematic for a SBM module can be found in the datasheet of TI bq2931
5050

5151
![Simple breadboard setup](pictures/Breadboard.jpg)
5252

53+
# Support for automatic discharge and charge
54+
The charge control pin (9) is high as long as relative charge is below 95%. It can be used to control a NPN transistor, which collector controls a high side P FET.<br/>
55+
The discharge control pin (10) is high as long as relative charge is above 5% and voltage, if available, is below 3300 mV. It can be used to control a logic level FET directly.<br/>
56+
The switch off values can be changed [here](https://github.com/ArminJo/Smart-Battery-Module-Info_For_Arduino/blob/master/SBMInfo/SBMInfo.ino#L42)
57+
5358
# LCD display content
5459
### LCD Display before device connected
5560
1. line: "SBMInfo" | Version | VCC voltage
@@ -194,6 +199,9 @@ Average minutes remaining until empty: 16 h 11 min
194199
![Fritzing schematics](extras/SBMInfo_Schaltplan.png)
195200

196201
# Revision History
202+
### Version 4.2
203+
- Removed compile time warnings.
204+
197205
### Version 4.1.0
198206
- Support for automatic discharge and charge.
199207
- Improved output.

SBMInfo/ADCUtils.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ uint16_t readADCChannelWithReferenceOversampleFast(uint8_t aADCChannelNumber, ui
129129
uint16_t readADCChannelWithReferenceMultiSamples(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aNumberOfSamples);
130130
uint16_t readADCChannelWithReferenceMax(uint8_t aADCChannelNumber, uint8_t aReference, uint16_t aNumberOfSamples);
131131
uint16_t readADCChannelWithReferenceMaxMicros(uint8_t aADCChannelNumber, uint8_t aReference, uint16_t aMicrosecondsToAquire);
132-
uint16_t readUntil4ConsecutiveValuesAreEqual(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aDelay, uint8_t aAllowedDifference,
133-
uint8_t aMaxRetries);
132+
uint16_t readUntil4ConsecutiveValuesAreEqual(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aDelay,
133+
uint8_t aAllowedDifference, uint8_t aMaxRetries);
134134

135135
uint8_t checkAndWaitForReferenceAndChannelToSwitch(uint8_t aADCChannelNumber, uint8_t aReference);
136136

@@ -152,8 +152,9 @@ void readAndPrintVCCVoltageMillivolt(Print *aSerial);
152152
uint16_t getVoltageMillivolt(uint16_t aVCCVoltageMillivolt, uint8_t aADCChannelForVoltageMeasurement);
153153
uint16_t getVoltageMillivolt(uint8_t aADCChannelForVoltageMeasurement);
154154
uint16_t getVoltageMillivoltWith_1_1VoltReference(uint8_t aADCChannelForVoltageMeasurement);
155-
float getTemperatureSimple(void);
156-
float getTemperature(void);
155+
float getCPUTemperatureSimple(void);
156+
float getCPUTemperature(void);
157+
float getTemperature(void) __attribute__ ((deprecated ("Renamed to getCPUTemperature()"))); // deprecated
157158

158159
bool isVCCTooLowMultipleTimes();
159160
void resetVCCTooLowMultipleTimes();

SBMInfo/ADCUtils.hpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,8 @@ uint16_t readADCChannelWithReferenceMaxMicros(uint8_t aADCChannelNumber, uint8_t
319319
* aMaxRetries = 255 -> try forever
320320
* @return (tMax + tMin) / 2
321321
*/
322-
uint16_t readUntil4ConsecutiveValuesAreEqual(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aDelay, uint8_t aAllowedDifference,
323-
uint8_t aMaxRetries) {
322+
uint16_t readUntil4ConsecutiveValuesAreEqual(uint8_t aADCChannelNumber, uint8_t aReference, uint8_t aDelay,
323+
uint8_t aAllowedDifference, uint8_t aMaxRetries) {
324324
int tValues[4]; // last value is in tValues[3]
325325
int tMin;
326326
int tMax;
@@ -651,7 +651,7 @@ bool isVCCTooHighSimple() {
651651
* !!! Function without handling of switched reference and channel.!!!
652652
* Use it ONLY if you only use INTERNAL reference (e.g. only call getTemperatureSimple()) in your program.
653653
*/
654-
float getTemperatureSimple(void) {
654+
float getCPUTemperatureSimple(void) {
655655
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
656656
return 0.0;
657657
#else
@@ -661,12 +661,13 @@ float getTemperatureSimple(void) {
661661
Serial.print(F("TempRaw="));
662662
Serial.println(tTempRaw);
663663
#endif
664-
#if defined(__AVR_ATmega328P__)
665-
tTempRaw -= 317;
666-
return (float)tTempRaw / 1.22;
667-
#elif defined(__AVR_ATmega328PB__)
664+
665+
#if defined(__AVR_ATmega328PB__)
668666
tTempRaw -= 245;
669667
return (float)tTempRaw;
668+
#else
669+
tTempRaw -= 317;
670+
return (float) tTempRaw / 1.22;
670671
#endif
671672
#endif
672673
}
@@ -675,12 +676,15 @@ float getTemperatureSimple(void) {
675676
* Handles usage of 1.1 V reference and channel switching by introducing the appropriate delays.
676677
*/
677678
float getTemperature(void) {
679+
return getCPUTemperature();
680+
}
681+
float getCPUTemperature(void) {
678682
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
679683
return 0.0;
680684
#else
681685
// use internal 1.1 volt as reference
682686
checkAndWaitForReferenceAndChannelToSwitch(ADC_TEMPERATURE_CHANNEL_MUX, INTERNAL);
683-
return getTemperatureSimple();
687+
return getCPUTemperatureSimple();
684688
#endif
685689
}
686690

SBMInfo/SBMInfo.ino

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
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
88
99
*
1010
* https://github.com/ArminJo/Smart-Battery-Module-Info_For_Arduino
@@ -30,30 +30,30 @@
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

248255
struct 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 */
250257
ABSOLUTE_SOC, Absolute_Charge, &printPercentage, NULL, 0, 0 }, {
251258
FULL_CHARGE_CAPACITY, Full_Charge_Capacity, &printCapacity, "", 0, 0 }/* DescriptionLCD must be not NULL */, {
252259
REMAINING_CAPACITY, Remaining_Capacity, &printCapacity, " remCap", 0, 0 }, {
@@ -337,10 +344,11 @@ BQ20Z70_PackVoltage, Pack_Voltage, &printVoltage, NULL, 0, 0 } };
337344
*/
338345
void 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\nVersion " 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) {
497511
uint8_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
*/
599616
uint16_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,
939961
void 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

SBMInfo/WireUtils.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,14 @@ bool checkForAttachedI2CDevice(Print *aSerial, uint8_t aI2CDeviceAddress) {
133133
*/
134134
int8_t scanForAttachedI2CDevice(Print *aSerial, uint8_t aI2CAddressToStartWith) {
135135
// the next 2 statements disable TWI hangup, if SDA and SCL are connected and disconnected from ground.
136+
#if defined(TWCR)
136137
TWCR = 0;
138+
#endif
137139
Wire.begin();
138140

139141
auto tStartMillis = millis();
140-
for (uint_fast8_t tI2CAddress = aI2CAddressToStartWith; tI2CAddress < 127; tI2CAddress++) {
142+
// We cannot use uint_fast8_t here, since it is ambiguous parameter for beginTransmission() on 16/32 bit CPU
143+
for (uint8_t tI2CAddress = aI2CAddressToStartWith; tI2CAddress < 127; tI2CAddress++) {
141144
Wire.beginTransmission(tI2CAddress);
142145
uint8_t tOK = Wire.endTransmission(true);
143146
if (tOK == 0) {

extras/SBMInfo.fzz

2.54 KB
Binary file not shown.

extras/SBMInfo_Schaltplan.png

16.7 KB
Loading

extras/SBMInfo_Steckplatine.png

1.81 KB
Loading

0 commit comments

Comments
 (0)