From 1516cf18ab861206b364ee6e804426fd2a5e2b51 Mon Sep 17 00:00:00 2001 From: Armin Date: Fri, 13 Dec 2024 23:02:27 +0100 Subject: [PATCH] v5.0 beta - External BlueDisplay --- README.md | 20 +- UltimateBatteryTester/ADCUtils.h | 3 + UltimateBatteryTester/ADCUtils.hpp | 24 +- .../UltimateBatteryTester.ino | 1187 +++++++++-------- UltimateBatteryTester/digitalWriteFast.h | 90 +- 5 files changed, 729 insertions(+), 595 deletions(-) diff --git a/README.md b/README.md index f125918..7b6b377 100644 --- a/README.md +++ b/README.md @@ -78,15 +78,26 @@ Battery packs up to 17.2 volt (4s) can be measured too. Voltages above 14.8 volt Given the voltage measurement resistor network from the schematic, with Li-ion (3.7 V VCC) we can merely measure up to 14.8 V.
Since the build in load resistor is 12 Ω, **the current would go up to 1.4 ampere and the power to 24 watt**, leaving 2.8 watt at the 2 Ω shunt resistors.
This is too much for the resistors I used for shunt!
-The solution is to **add an additional resistor of around 20 Ω in series to the 10 Ω already built in one**. +The solution is to **add an additional resistor of around 20 Ω in series to the 10 Ω already built in**. This reduces the current to around 500 mA and power to 9 watt leaving 1 watt at the 2 Ω shunt resistors.
The voltage must still be measured at the battery terminal, so I use a distinct cable for voltage measurement, normally connected to the built in load resistors / battery + cable.
No other adaption has to be made.
+# Logger function +By connecting pin 10 to ground, logger mode is entered after startup. Voltage is still measured at the same pin as for Battery mode. +For current measurement in Logger mode, we use an separate external shunt connected at pin A4.
+To avoid disturbing the circuit in which we are measuring the current, this shunt is typically smaller than the shunt used in battery mode. +Its value is defined by `LOGGER_SHUNT_RESISTOR_MILLIOHM`.
+Cutoff / end condition is, when current drops below 50%, 25% or 12.5% of start current. +12.5% is default and useful for logging charging circuits wich reduce current at the end of charge. + +
+ # Sample screenshots This screenshots are from an 1200x800 tablet running the [BlueDisplay](https://github.com/ArminJo/Arduino-BlueDisplay) app. +The Arduino is connected to the tablet via **OSB OTG** or a **Bluetooth module** like HC-05, see [here](https://github.com/ArminJo/Arduino-BlueDisplay?tab=readme-ov-file#bluedisplayblink) and [here](https://github.com/ArminJo/Arduino-BlueDisplay?tab=readme-ov-file#connecting-arduino-rx). ![Li-ion_1100mAh](pictures/Li-ion_1100mAh.png) @@ -95,7 +106,7 @@ This screenshots are from an 1200x800 tablet running the [BlueDisplay](https://g # Sample plots The plots are created with the Arduino 1.x Serial Plotter. The Arduino 2.x Serial Plotter is not as powerful and uses a different data format. -Plot for **2 parallel Li-Ion cells**.
+Plot for **2 parallel 18660 Li-Ion cells**.
The first capacity value is the "standard" capacity measured from the first peak at standard full voltage 4100 mV to the second peak, the cutoff "high" voltage at 3400 mV.
The peaks, which are 50 mV added to the measured value, are artificially generated for easy recognition of the capacity range.
The second peak is always suppressed for graphs with discharge to "high", because the end of this graph is by definition the end of the standard capacity range.
@@ -223,8 +234,8 @@ A value of **99.999 Ω indicates overflow** over the maximum value of 65.535 - Every second, a sample is taken and displayed. - Every 60 seconds the sample is stored. - For the first 337 samples (5:37 hours) each 8 bit delta is stored to EEPROM. -- After the first 337 samples, all data are compressed, and every 120 seconds 2 compressed samples are stored to EEPROM. -- The number between the voltage and the current in the first row is the virtual EEPROM storage index and incremented at each storage. +- After 337 samples, all data are compressed by combining 2 samples and doubling the sample period This can be done multiple times. +- The number between the voltage and the current in the first row is the storage minute count and incremented at each storage. ![Storing](pictures/Storing.png) @@ -256,6 +267,7 @@ and the according cutoff voltage is displayed in the first row for 2 seconds. - Compression is now done by simply doubling the sampling period, which results in reducing the resolution from 336 of 168 samples directly after compression. - Data and chart can be displayed on (old) tablets or mobile running the BlueDisplay app https://github.com/ArminJo/Arduino-BlueDisplay. - Plotter pin logic does not depend any more on USB powering. +- Tested logger function with chart. ### Version 4.0.0 - Use capacity between NominalFullVoltageMillivolt and SwitchOffVoltageMillivoltHigh as standard capacity to enable better comparison. diff --git a/UltimateBatteryTester/ADCUtils.h b/UltimateBatteryTester/ADCUtils.h index c406217..33c2bbf 100644 --- a/UltimateBatteryTester/ADCUtils.h +++ b/UltimateBatteryTester/ADCUtils.h @@ -29,6 +29,9 @@ #if defined(__AVR__) && defined(ADCSRA) && defined(ADATE) && (!defined(__AVR_ATmega4809__)) #define ADC_UTILS_ARE_AVAILABLE +// External Reference Current is 150 uA for 5 V and 100 uA for 3.5 V +#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 + // PRESCALE4 => 13 * 4 = 52 microseconds per ADC conversion at 1 MHz Clock => 19,2 kHz #define ADC_PRESCALE2 1 // 26 microseconds per ADC conversion at 1 MHz #define ADC_PRESCALE4 2 // 52 microseconds per ADC conversion at 1 MHz diff --git a/UltimateBatteryTester/ADCUtils.hpp b/UltimateBatteryTester/ADCUtils.hpp index 6d01086..8d0e5e7 100644 --- a/UltimateBatteryTester/ADCUtils.hpp +++ b/UltimateBatteryTester/ADCUtils.hpp @@ -403,7 +403,7 @@ uint16_t readUntil4ConsecutiveValuesAreEqual(uint8_t aADCChannelNumber, uint8_t /* * Get min and max of the last 4 values */ - tMin = 1024; + tMin = READING_FOR_AREF; tMax = 0; for (uint_fast8_t i = 0; i < 4; ++i) { if (tValues[i] < tMin) { @@ -472,7 +472,7 @@ uint16_t readUntil4ConsecutiveValuesAreEqual(uint8_t aADCChannelNumber, uint8_t float getVCCVoltageSimple(void) { // use AVCC with (optional) external capacitor at AREF pin as reference float tVCC = readADCChannelMultiSamplesWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 4); - return ((1023 * 1.1 * 4) / tVCC); + return ((READING_FOR_AREF * 1.1 * 4) / tVCC); } /* @@ -483,19 +483,19 @@ float getVCCVoltageSimple(void) { uint16_t getVCCVoltageMillivoltSimple(void) { // use AVCC with external capacitor at AREF pin as reference uint16_t tVCC = readADCChannelMultiSamplesWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 4); - return ((1023L * ADC_INTERNAL_REFERENCE_MILLIVOLT * 4) / tVCC); + return ((READING_FOR_AREF * ADC_INTERNAL_REFERENCE_MILLIVOLT * 4) / tVCC); } /* * Gets the hypothetical 14 bit reading of VCC using 1.1 volt reference - * Similar to getVCCVoltageMillivolt() * 1023 / 1100 + * Similar to getVCCVoltageMillivolt() * 1024 / 1100 */ uint16_t getVCCVoltageReadingFor1_1VoltReference(void) { uint16_t tVCC = waitAndReadADCChannelWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT); /* * Do not switch back ADMUX to enable checkAndWaitForReferenceAndChannelToSwitch() to work correctly for the next measurement */ - return ((1023L * 1023L) / tVCC); + return ((READING_FOR_AREF * READING_FOR_AREF) / tVCC); } /* @@ -519,7 +519,7 @@ uint16_t getVCCVoltageMillivolt(void) { /* * Do not switch back ADMUX to enable checkAndWaitForReferenceAndChannelToSwitch() to work correctly for the next measurement */ - return ((1023L * ADC_INTERNAL_REFERENCE_MILLIVOLT) / tVCC); + return ((READING_FOR_AREF * ADC_INTERNAL_REFERENCE_MILLIVOLT) / tVCC); } /* @@ -547,7 +547,7 @@ void readAndPrintVCCVoltageMillivolt(Print *aSerial) { void readVCCVoltageSimple(void) { // use AVCC with (optional) external capacitor at AREF pin as reference float tVCC = readADCChannelMultiSamplesWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 4); - sVCCVoltage = (1023 * (((float) ADC_INTERNAL_REFERENCE_MILLIVOLT) / 1000) * 4) / tVCC; + sVCCVoltage = (READING_FOR_AREF * (((float) ADC_INTERNAL_REFERENCE_MILLIVOLT) / 1000) * 4) / tVCC; } /* @@ -558,7 +558,7 @@ void readVCCVoltageSimple(void) { void readVCCVoltageMillivoltSimple(void) { // use AVCC with external capacitor at AREF pin as reference uint16_t tVCCVoltageMillivoltRaw = readADCChannelMultiSamplesWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT, 4); - sVCCVoltageMillivolt = (1023L * ADC_INTERNAL_REFERENCE_MILLIVOLT * 4) / tVCCVoltageMillivoltRaw; + sVCCVoltageMillivolt = (READING_FOR_AREF * ADC_INTERNAL_REFERENCE_MILLIVOLT * 4) / tVCCVoltageMillivoltRaw; } /* @@ -579,7 +579,7 @@ void readVCCVoltageMillivolt(void) { /* * Do not switch back ADMUX to enable checkAndWaitForReferenceAndChannelToSwitch() to work correctly for the next measurement */ - sVCCVoltageMillivolt = (1023L * ADC_INTERNAL_REFERENCE_MILLIVOLT) / tVCCVoltageMillivoltRaw; + sVCCVoltageMillivolt = (READING_FOR_AREF * ADC_INTERNAL_REFERENCE_MILLIVOLT) / tVCCVoltageMillivoltRaw; } /* @@ -588,7 +588,7 @@ void readVCCVoltageMillivolt(void) { */ uint16_t getVoltageMillivolt(uint16_t aVCCVoltageMillivolt, uint8_t aADCChannelForVoltageMeasurement) { uint16_t tInputVoltageRaw = waitAndReadADCChannelWithReference(aADCChannelForVoltageMeasurement, DEFAULT); - return (aVCCVoltageMillivolt * (uint32_t) tInputVoltageRaw) / 1023; + return (aVCCVoltageMillivolt * (uint32_t) tInputVoltageRaw) / READING_FOR_AREF; } /* @@ -597,12 +597,12 @@ uint16_t getVoltageMillivolt(uint16_t aVCCVoltageMillivolt, uint8_t aADCChannelF */ uint16_t getVoltageMillivolt(uint8_t aADCChannelForVoltageMeasurement) { uint16_t tInputVoltageRaw = waitAndReadADCChannelWithReference(aADCChannelForVoltageMeasurement, DEFAULT); - return (getVCCVoltageMillivolt() * (uint32_t) tInputVoltageRaw) / 1023; + return (getVCCVoltageMillivolt() * (uint32_t) tInputVoltageRaw) / READING_FOR_AREF; } uint16_t getVoltageMillivoltWith_1_1VoltReference(uint8_t aADCChannelForVoltageMeasurement) { uint16_t tInputVoltageRaw = waitAndReadADCChannelWithReference(aADCChannelForVoltageMeasurement, INTERNAL); - return (ADC_INTERNAL_REFERENCE_MILLIVOLT * (uint32_t) tInputVoltageRaw) / 1023; + return (ADC_INTERNAL_REFERENCE_MILLIVOLT * (uint32_t) tInputVoltageRaw) / READING_FOR_AREF; } /* diff --git a/UltimateBatteryTester/UltimateBatteryTester.ino b/UltimateBatteryTester/UltimateBatteryTester.ino index e656f42..b29cca1 100644 --- a/UltimateBatteryTester/UltimateBatteryTester.ino +++ b/UltimateBatteryTester/UltimateBatteryTester.ino @@ -55,51 +55,9 @@ #include "pitches.h" -/* - * Version 5.0 - 2/2024 - * - Compression is now done by simply doubling the sampling period, which results in reducing the resolution from 336 of 168 samples directly after compression. - * - Data and chart can be displayed on (old) tablets or mobile running the BlueDisplay app https://github.com/ArminJo/Arduino-BlueDisplay. - * - Plotter pin logic does not depend any more on USB powering. - * - * - * Version 4.0 - 2/2024 - * - Use capacity between NominalFullVoltageMillivolt and CutoffVoltageMillivoltHigh as standard capacity to enable better comparison. - * - If powered by USB plotter pin logic is reversed, i.e. plotter output is enabled if NOT connected to ground. - * - In state detecting battery, you can toggle cut off voltage between high, low and zero (0.1 V) with stop button. - * - Fix bug for appending to compressed data. - * - Synchronizing of LCD access for button handler, avoiding corrupted display content. - * - Print improvements. - * - Support for storage period of 120 s. - * - Compression improved for rapidly descending voltage. - * - Moving seldom used function of pin 10 to pin A5. - * - New Logger mode with separate shunt enabled by pin 10. - * - Store data in an array of structure instead in 3 arrays. - * - * Version 3.2.1 - 11/2023 - * BUTTON_IS_ACTIVE_HIGH is not default any more - * Version 3.2 - 10/2023 - * Cut off LCD message improved - * Version 3.1 - 3/2023 - * Fixed "conversion does not clear rest of EEPROM" bug - * Version 3.0 - 12/2022 - * Improved compression - * Version 2.3 - 10/2022 - * Increase no load settle time especially for NiMh batteries - * Attention tones - * Version 2.2 - 8/2022 - * ESR > 64 bug fixed. - * Display of changes on pin CUTOFF_LEVEL_PIN - * Version 2.1 - 3/2022 - * ESR is stored. - * Version 2.0 - 3/2022 - * Improved version. - * Version 1.0 - 9/2021 - * Tested version. - * Version 0.0 - 9/2021 - * Initial version. - */ - #define VERSION_EXAMPLE "5.0" +// The change log is at the bottom of the file + //#define TRACE //#define DEBUG @@ -152,6 +110,16 @@ #define CUTOFF_LEVEL_PIN 11 // If connected to ground, "cut off is low" is displayed and discharge ends at a lower voltage. E.g. Li-ion discharge ends at 3000 mV instead of 3500 mV #define LOAD_LOW_PIN 12 // This pin is high to switch on the low load (10 ohm). A4 is occupied by I2C for serial LCD display. +/* + * External circuit definitions + */ +#define LOGGER_SHUNT_RESISTOR_MILLIOHM 200L // 0.2 ohm -> Resolution of 5 mA +#define ESR_SHUNT_RESISTOR_MILLIOHM 2000L // 2 ohm +#define LOAD_LOW_MILLIOHM (1000 + ESR_SHUNT_RESISTOR_MILLIOHM) // Additional 1 ohm +#define LOAD_HIGH_MILLIOHM (10 * 1000 + ESR_SHUNT_RESISTOR_MILLIOHM) // Additional 10 ohm +#define ATTENUATION_FACTOR_VOLTAGE_LOW_RANGE 2L // Divider with 100 kOhm and 100 kOhm -> 2.2 V range +#define ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE 4L // Divider with 100 kOhm and 33.333 kOhm -> 4.4 V range + /* * Imports and definitions for start/stop button at pin 2 */ @@ -180,16 +148,6 @@ bool sOnlyLoggerFunctionality; // contains the (inverted) value of the uint16_t sLastMilliampereLowPassFiltered5; // For sCutoffMilliamperePercent bool sLastValueOfCutoffLevelPin; // To support changing between normal and low by using pin CUTOFF_LEVEL_PIN -/* - * External circuit definitions - */ -#define LOGGER_SHUNT_RESISTOR_MILLIOHM 200L // 0.2 ohm -> Resolution of 5 mA -#define ESR_SHUNT_RESISTOR_MILLIOHM 2000L // 2 ohm -#define LOAD_LOW_MILLIOHM (1000 + ESR_SHUNT_RESISTOR_MILLIOHM) // Additional 1 ohm -#define LOAD_HIGH_MILLIOHM (10 * 1000 + ESR_SHUNT_RESISTOR_MILLIOHM) // Additional 10 ohm -#define ATTENUATION_FACTOR_VOLTAGE_LOW_RANGE 2L // Divider with 100 kOhm and 100 kOhm -> 2.2 V range -#define ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE 4L // Divider with 100 kOhm and 33.333 kOhm -> 4.4 V range - //#define NO_TONE_WARNING_FOR_VOLTAGE_TOO_LOW_FOR_STANDARD_CAPACITY_COMPUTATION /* @@ -199,7 +157,7 @@ bool sLastValueOfCutoffLevelPin; // To support changing between n //#define DO_NOT_NEED_BASIC_TOUCH_EVENTS #define DO_NOT_NEED_TOUCH_AND_SWIPE_EVENTS // Disables LongTouchDown and SwipeEnd events. Implies DO_NOT_NEED_BASIC_TOUCH_EVENTS. #define ONLY_CONNECT_EVENT_REQUIRED // Disables reorientation, redraw and SensorChange events -#define SUPPRESS_SERIAL_PRINT +#define SUPPRESS_SERIAL_PRINT // To reduce code size #include "BlueDisplay.hpp" // part of https://github.com/ArminJo/Arduino-BlueDisplay //#define BLUETOOTH_BAUD_RATE BAUD_115200 // Activate this, if you have reprogrammed the HC05 module for 115200 @@ -217,7 +175,7 @@ bool sLastValueOfCutoffLevelPin; // To support changing between n #define BASE_TEXT_WIDTH ((((MAX_NUMBER_OF_SAMPLES / 20L) * 6 ) + 4) / 10) // 10 #define BUTTON_WIDTH (BASE_TEXT_SIZE * 5) #define CHART_START_X (BASE_TEXT_SIZE * 3) -#define CHART_WIDTH (MAX_NUMBER_OF_SAMPLES) // 336, 5 hours and 36 min +#define CHART_WIDTH (MAX_NUMBER_OF_SAMPLES + 1) // +1 for the first sample at minute 0 -> 337, 5 hours and 36 min #define CHART_AXES_SIZE (BASE_TEXT_SIZE / 8) #define BUTTONS_START_X ((BASE_TEXT_SIZE * 4) + CHART_WIDTH) @@ -242,8 +200,9 @@ bool sLastValueOfCutoffLevelPin; // To support changing between n #define CHART_DATA_COLOR COLOR16_RED #define CHART_TEXT_COLOR COLOR16_BLACK -#define CHART_MAXIMUM_X_SCALE_FACTOR 8 -#define CHART_MINUTES_PER_X_LABEL 30 +#define CHART_MAXIMUM_X_SCALE_FACTOR 8L +#define CHART_MINUTES_PER_X_LABEL_UNCOMPRESSED 30L +#define SECONDS_PER_MINUTES 60L #define BRIGHTNESS_LOW 2 #define BRIGHTNESS_MIDDLE 1 @@ -285,14 +244,14 @@ void drawButtons(); void drawTextButtons(); void clearAndDrawChart(); +void changeBrightness(); void doBrightness(BDButton *aTheTouchedButton, int16_t aValue); void setCutoffHighLowZeroButtonText(bool doDrawButton); //void printMeasurementValues(); void printChartValues(); -void printCounter(uint16_t tNumbersOfInitialSamplesToGo); void printCapacityValue(); -void readAndDrawEEPROMValues(bool aStoreValuesForDisplayAndAppend); +void readAndDrawEEPROMValues(); #endif // SUPPORT_BLUEDISPLAY_CHART /* @@ -334,6 +293,11 @@ LiquidCrystal_I2C myLCD(0x27, LCD_COLUMNS, LCD_ROWS); // set the LCD address to LiquidCrystal myLCD(7, 8, 3, 4, 5, 6); #endif +//#define ENABLE_STACK_ANALYSIS +#if defined(ENABLE_STACK_ANALYSIS) +#include "AVRUtils.h" // include for initStackFreeMeasurement() and printRAMInfo() +#endif + /* * Measurement timing */ @@ -352,6 +316,11 @@ LiquidCrystal myLCD(7, 8, 3, 4, 5, 6); # endif #endif +/* + * For Logger current, we take the average of 20 ms (50 HZ) | 769 samples to cover variations due to mains frequency + * and 1 voltage value. + * This is done 20 times per second and then averaged again. + */ #define LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT 769L // for ADC_PRESCALE32 and 20 ms (50 Hz) #define LOGGER_SAMPLE_PERIOD_MILLIS 50 // 20 Hz. #define LOGGER_SAMPLE_FREQUENCY_HZ (MILLIS_IN_ONE_SECOND / LOGGER_SAMPLE_PERIOD_MILLIS) @@ -389,13 +358,14 @@ struct BatteryTypeInfoStruct { #define TYPE_INDEX_NO_BATTERY 0 #define TYPE_INDEX_DEFAULT 6 #define TYPE_INDEX_MAX 10 +#define TYPE_INDEX_LOGGER 42 struct BatteryTypeInfoStruct BatteryTypeInfoArray[] = { { "No battery", 100, 0, 0, 0, NO_LOAD, 0 }, /* Below 100 mV and not below 50, to avoid toggling between no and low batt */ { "Low batt. ", 1000, 0, NIMH_SWITCH_OFF_VOLTAGE_MILLIVOLT_LOW, DISCHARGE_CUTOFF_LEVEL_ZERO_MILLIVOLT, HIGH_LOAD, 100 }, /* For researching of worn out batteries. */ { "NiCd NiMH ", 1460, 1400, NIMH_SWITCH_OFF_VOLTAGE_MILLIVOLT, NIMH_SWITCH_OFF_VOLTAGE_MILLIVOLT_LOW, HIGH_LOAD, 100 }, /*400 mA*/ { "Alkali ", 1550, 1500, 1300, 1000, HIGH_LOAD, 100 }, /*500 mA*/ -{ "NiZn batt.", 1800, 1650, 1500, 1300, HIGH_LOAD, 100 }, /*550 mA*/ -{ "LiFePO4 ", 3400, 3400, 3000, 2700, LOW_LOAD, 10 }, /*270 mA https://www.jackery.com/blogs/knowledge/ultimate-guide-to-lifepo4-voltage-chart*/ +{ "NiZn batt.", 1850, 1650, 1400, 1300, HIGH_LOAD, 100 }, /*550 mA*/ +{ "LiFePO4 ", 3400, 3400, 3050, 2700, LOW_LOAD, 10 }, /*270 mA https://www.jackery.com/blogs/knowledge/ultimate-guide-to-lifepo4-voltage-chart*/ { "Li-ion ", 5000, LI_ION_STANDARD_FULL_VOLTAGE_MILLIVOLT /*4100*/, LI_ION_SWITCH_OFF_VOLTAGE_MILLIVOLT/*3400*/, LI_ION_SWITCH_OFF_VOLTAGE_MILLIVOLT_LOW/*3V*/, LOW_LOAD, 10 }, /*300 mA*/ @@ -418,9 +388,8 @@ struct BatteryOrLoggerInfoStruct { uint16_t LoadMillivolt; } Battery; struct LoggerVoltagesStruct { - // The first value is stored in EEPROM + uint16_t AverageMillivolt; // Same memory location as NoLoadMillivolt and therefore is stored in EEPROM uint16_t MinimumMillivolt; - uint16_t AverageMillivolt; uint16_t MaximumMillivolt; } Logger; } Voltages; @@ -467,14 +436,20 @@ bool sVoltageNoLoadIsDisplayedOnLCD = false; bool sVoltageRangeIsLow; // true for 2.2 V range uint8_t sLoggerADCVoltageReference = INTERNAL; // INTERNAL or DEFAULT -uint16_t sLoggerMaximumRawVoltage; -uint16_t sLoggerMinimumRawVoltage; -uint16_t sLogger1SecondRawSampleCount; // is 20 for 20 HZ -uint32_t sLogger1SecondRawVoltageAccumulator; // normalized for 4.4 V range -uint32_t sLogger1SecondRawCurrentAccumulator; // 16 bit is only OK up to 50 Hz -uint16_t sLogger1MinuteRawSampleCount; // is 3000 every minute for 50 HZ -uint32_t sLogger1MinuteRawVoltageAccumulator8ShiftRight; // Unshifted maximum is 5000 * 769 * 20 * 60 = 6.921 billion => overflow at 12.2 Volt -uint32_t sLogger1MinuteRawCurrentAccumulator; // Unshifted maximum is 1023 * 769 * 20 * 60 = 1.416 billion +struct Logger1SecondAccumulatorStruct { + uint32_t RawVoltageAccumulator; // normalized for 4.4 V range + uint32_t RawCurrentAccumulator; // 16 bit is only OK up to 50 Hz + uint16_t RawSampleCount; // is 20 for 20 HZ + uint16_t MinimumRawVoltage; + uint16_t MaximumRawVoltage; +} sLogger1SecondAccumulator; + +struct Logger1MinuteAccumulatorStruct { + uint16_t RawSampleCount; // is 3000 every minute for 50 HZ + uint32_t RawVoltageAccumulator8ShiftRight; // Unshifted maximum is 5000 * 769 * 20 * 60 = 6.921 billion => overflow at 12.2 Volt + uint32_t RawCurrentAccumulator; // Unshifted maximum is 1023 * 769 * 20 * 60 = 1.416 billion +} sLogger1MinuteAccumulator; + /* * Tester state machine */ @@ -530,12 +505,11 @@ struct EEPROMStartValuesStruct { uint16_t initialDischargingMilliampere; uint16_t initialDischargingMilliohm; uint16_t LoadResistorMilliohm; - uint16_t CapacityMilliampereHour; // is set at end of measurement or by store button - uint8_t CutoffLevel; + uint16_t CapacityMilliampereHour; // Is set at end of measurement or by store button + uint8_t CutoffLevel; // One of CUTOFF_LEVEL_HIGH, CUTOFF_LEVEL_LOW and CUTOFF_LEVEL_ZERO. uint8_t BatteryTypeIndex; uint8_t NumberOfSecondsPerStorage; -}; -EEPROMStartValuesStruct StartValues; +} StartValues; EEMEM EEPROMStartValuesStruct EEPROMStartValues; #define EEPROM_EMPTY_VALUE 0xFF // the value of an unwritten / empty EEPROM byte @@ -571,20 +545,19 @@ struct ValuesForDeltaStorageStruct { int DeltaArrayIndex; // The index of the next values to be written. -1 to signal, that start values must be written. } ValuesForDeltaStorage; -bool sDoPrintCaption = true; // Value used for (recursive) call to printValuesForPlotter(). +bool sDoPrintCaption = true; // Value used for (recursive) call to printValuesForPlotterAndChart(). -void getBatteryVoltageMillivolt(); +void getBatteryOrLoggerVoltageMillivolt(); void addToCapacity(); -uint16_t getBatteryRawVoltage(); +uint16_t getBatteryOrLoggerRawVoltage(); void detectBatteryOrLoggerVoltageAndCurrentLCD(); void clearLogger1SecondAccumulator(); void clearLogger1MinuteAccumulator(); -void getLoggerCurrent(); void getLogger1SecondValues(); void getLogger1MinuteValues(); void handlePeriodicAccumulatingLoggerValues(); -void getBatteryCurrent(); +void getCurrent(uint8_t aADCChannel, uint16_t aShuntResistorMilliohm); void getBatteryValues(); void checkAndHandleStopConditionLCD(); bool isVoltageOrCurrentRemoved(); @@ -596,9 +569,10 @@ void printMilliampere4DigitsLCD(); void printVoltageNoLoadMillivoltWithTrailingSpaceLCD(); void clearLastDiplayedValues(); void printMeasurementValuesLCD(); -void printValuesForPlotter(uint16_t aMillivoltToPrint, uint16_t aMilliampereToPrint, uint16_t aMilliohmToPrint, +void printValuesForPlotterAndChart(uint16_t aMillivoltToPrint, uint16_t aMilliampereToPrint, uint16_t aMilliohmToPrint, bool aIsLastElement); void printMillisValueAsFloat(uint16_t aValueInMillis); +void printCounter(uint16_t aNumberToPrint); void dumpEEPROM(uint8_t *aEEPROMAdress, uint8_t aNumberOf16ByteBlocks); void storeBatteryValuesToEEPROM(uint16_t aVoltageNoLoadMillivolt, uint16_t aMilliampere, uint16_t aMilliohm); @@ -686,7 +660,9 @@ void setup() { */ Serial.print(F("Button pin=")); Serial.println(INT0_PIN); - Serial.println(F("To suppress such prints not suited for Arduino plotter, connect pin " STR(ONLY_PLOTTER_OUTPUT_PIN) " to ground")); + Serial.println( + F( + "To suppress such prints not suited for Arduino plotter, connect pin " STR(ONLY_PLOTTER_OUTPUT_PIN) " to ground")); # if (INITIAL_NUMBER_OF_SECONDS_PER_STORAGE * SAMPLE_PERIOD_OF_LOAD_ACIVATED_MILLIS != 60000) Serial.print(F("Sample period=")); @@ -705,6 +681,10 @@ void setup() { #endif } +#if defined(ENABLE_STACK_ANALYSIS) + initStackFreeMeasurement(); // used 229, unused 339 +#endif + // Disable digital input on all unused ADC channel pins to reduce power consumption DIDR0 = ADC0D | ADC1D; @@ -734,20 +714,31 @@ void setup() { * BlueDisplay initialization ******************************/ #if defined(SUPPORT_BLUEDISPLAY_CHART) - uint16_t tConnectDurationMillis = BlueDisplay1.initCommunication(&connectHandler); // introduces up to 1.5 seconds delay - if (tConnectDurationMillis > 0) { - Serial.print("Connection established after "); - Serial.print(tConnectDurationMillis); - Serial.println(" ms"); - } else { - Serial.println(F("No connection after " STR(CONNECTIOM_TIMEOUT_MILLIS) " ms")); + if (!sOnlyPlotterOutput) { + uint16_t tConnectDurationMillis = BlueDisplay1.initCommunication(&connectHandler); // introduces up to 1.5 seconds delay + if (tConnectDurationMillis > 0) { + Serial.print("Connection established after "); + Serial.print(tConnectDurationMillis); + Serial.println(" ms"); + } else { + Serial.println(F("No connection after " STR(CONNECTIOM_TIMEOUT_MILLIS) " ms")); + } } #endif _delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); +#if defined(ENABLE_STACK_ANALYSIS) + printRAMInfo(&Serial); +# if !defined(BD_USE_SIMPLE_SERIAL) + Serial.flush(); +# endif +#endif + #if defined(USE_LCD) -# if !defined(SUPPORT_BLUEDISPLAY_CHART) +#if defined(SUPPORT_BLUEDISPLAY_CHART) + if (!BlueDisplay1.isConnectionEstablished()) { +#endif myLCD.setCursor(0, 1); if (sOnlyPlotterOutput) { myLCD.print(F("Only plotter out")); @@ -756,7 +747,6 @@ void setup() { } delay(LCD_MESSAGE_PERSIST_TIME_MILLIS / 2); - myLCD.setCursor(0, 1); if (sOnlyLoggerFunctionality) { # if !defined(SUPPRESS_SERIAL_PRINT) @@ -768,7 +758,9 @@ void setup() { } _delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); LCDClearLine(1); // Clear line "Only logger mode" -# endif // !defined(SUPPORT_BLUEDISPLAY_CHART) +#if defined(SUPPORT_BLUEDISPLAY_CHART) + } +#endif # if (INITIAL_NUMBER_OF_SECONDS_PER_STORAGE * SAMPLE_PERIOD_OF_LOAD_ACIVATED_MILLIS != 60000) myLCD.setCursor(0, 1); @@ -785,10 +777,8 @@ void setup() { * get sMeasurementInfo.CutoffLevel for later appending */ #if defined(SUPPORT_BLUEDISPLAY_CHART) - if (BlueDisplay1.isConnectionEstablished()) { - readAndDrawEEPROMValues(true); - } else { - readAndProcessEEPROMData(true); + if (!BlueDisplay1.isConnectionEstablished()) { + readAndProcessEEPROMData(true); // just get the values for LCD display } #else readAndProcessEEPROMData(true); @@ -799,6 +789,7 @@ void setup() { printlnIfNotPlotterOutput(); // end of stored data if (sOnlyLoggerFunctionality) { + sCurrentLoadResistorAverage = LOGGER_SHUNT_RESISTOR_MILLIOHM; sMeasurementInfo.ESRMilliohm = 0; // not used in logger function } @@ -811,12 +802,12 @@ void setup() { * If battery is still inserted, keep cut off level. I.e. measurement is easy to be continued. * If battery was removed, cut off level can be chosen by pressing stop button. */ - getBatteryVoltageMillivolt(); + getBatteryOrLoggerVoltageMillivolt(); if (sMeasurementInfo.Voltages.Battery.NoLoadMillivolt < NO_BATTERY_MILLIVOLT) { - // Battery is removed here, so start with mode determined by pin + // Battery / Logger is removed here, so start with mode determined by pin sMeasurementInfo.CutoffLevel = !sLastValueOfCutoffLevelPin; if (sOnlyLoggerFunctionality && sMeasurementInfo.CutoffLevel == CUTOFF_LEVEL_HIGH) { - // Pin not connected leads to cut off level zero (instead of high) for logger + // Pin not connected leads to logger default cut off level zero instead of high sMeasurementInfo.CutoffLevel = CUTOFF_LEVEL_ZERO; } StartValues.CutoffLevel = sMeasurementInfo.CutoffLevel; // Required, in order to keep level during conversion to compressed. @@ -869,7 +860,7 @@ void loop() { handlePeriodicAccumulatingLoggerValues(); auto tMillis = millis(); // For discharging, add LoadSwitchSettleTimeMillis to the second condition - if ((sOnlyLoggerFunctionality && sLogger1SecondRawSampleCount == LOGGER_SAMPLE_FREQUENCY_HZ) + if ((sOnlyLoggerFunctionality && sLogger1SecondAccumulator.RawSampleCount == LOGGER_SAMPLE_FREQUENCY_HZ) || (!sOnlyLoggerFunctionality && (unsigned) (tMillis - sLastMillisOfSample) >= (SAMPLE_PERIOD_OF_LOAD_ACIVATED_MILLIS + sMeasurementInfo.LoadSwitchSettleTimeMillis))) { @@ -929,7 +920,7 @@ void loop() { #if defined(SUPPORT_BLUEDISPLAY_CHART) delayMillisAndCheckForEvent(100); #else - delay(100); + delay(100); #endif /* @@ -1014,14 +1005,14 @@ void handlePeriodicDetectionOfProbe() { sButtonUsageMessageWasPrinted = true; sVoltageNoLoadIsDisplayedOnLCD = false; } +# if defined(USE_LCD) + LCDClearLine(1); // Clear line "append to EEPROM" +# endif #endif - sLastDiplayedValues.Milliampere = 0; // to force overwrite of LCD string + sLastDiplayedValues.Milliampere = 0; // to force overwrite of value if (sOnlyLoggerFunctionality) { -#if defined(USE_LCD) - LCDClearLine(1); // Clear line "append to EEPROM" -#endif /* - * Initialize cutoff reference value + * Initialize logger accumulators */ sLastMilliampereLowPassFiltered5 = sMeasurementInfo.Milliampere; clearLogger1SecondAccumulator(); @@ -1038,7 +1029,7 @@ void handlePeriodicDetectionOfProbe() { * to not overwrite battery/logger voltage printed in state STATE_INITIAL_SAMPLES * "0123456789012345" * All: "" message starting at 6 with at least one space - * Logger: "3.98V No U or I" + * Logger: "3.98V No U and I" * Logger: "1.500V 0mA", if we have only voltage attached * Logger: "3.98V 22mA", if we have only current attached * Battery: "3.98V No batt." @@ -1122,16 +1113,17 @@ void handleEndOfStateInitialSamples() { // If button was not pressed before, start a new data set if (sMeasurementState == STATE_INITIAL_SAMPLES) { - // Force new data set + /* + * Force new data set + */ ValuesForDeltaStorage.DeltaArrayIndex = -1; - sMeasurementInfo.CapacityAccumulator = 0; // Must reset this values here, because values are displayed before computed again from CapacityAccumulator sMeasurementInfo.CapacityMilliampereHour = 0; - memset(sCurrentLoadResistorHistory, 0, sizeof(sCurrentLoadResistorHistory)); // Clear history array - - switchToStateSampleAndStoreToEEPROM(INITIAL_NUMBER_OF_SECONDS_PER_STORAGE); // store first value immediately + StartValues.NumberOfSecondsPerStorage = INITIAL_NUMBER_OF_SECONDS_PER_STORAGE; + // Store first EEPROM value immediately, append is done by button and waits a full period + switchToStateSampleAndStoreToEEPROM(INITIAL_NUMBER_OF_SECONDS_PER_STORAGE); } } @@ -1174,7 +1166,7 @@ void handlePeriodicStoringToEEPROM() { sMeasurementInfo.ESRMilliohm); #if defined(SUPPORT_BLUEDISPLAY_CHART) if (BlueDisplay1.isConnectionEstablished()) { - readAndDrawEEPROMValues(false); + readAndDrawEEPROMValues(); } #endif checkAndHandleStopConditionLCD(); @@ -1182,6 +1174,9 @@ void handlePeriodicStoringToEEPROM() { } } +/* + * Not used yet + */ void printStateString(uint8_t aState) { #if !defined(SUPPRESS_SERIAL_PRINT) if (!sOnlyPlotterOutput) { @@ -1212,7 +1207,15 @@ void printSwitchStateString() { #if !defined(SUPPRESS_SERIAL_PRINT) if (!sOnlyPlotterOutput) { Serial.print(F("Switch to state ")); - printStateString(sMeasurementState); + if (sMeasurementState == STATE_WAITING_FOR_BATTERY_OR_VOLTAGE) { + Serial.print(F("WAITING FOR BATTERY OR VOLTAGE")); + } else if (sMeasurementState == STATE_INITIAL_SAMPLES) { + Serial.print(F("INITIAL SAMPLES")); + } else if (sMeasurementState == STATE_SAMPLE_AND_STORE_TO_EEPROM) { + Serial.print(F("STORE TO EEPROM")); + } else if (sMeasurementState == STATE_STOPPED) { + Serial.print(F("STOPPED")); + } } #endif } @@ -1222,6 +1225,10 @@ void checkLeavingState() { if (sMeasurementState == STATE_INITIAL_SAMPLES) { TouchButtonAppend.removeButton(sBackgroundColor); } + if (sMeasurementState == STATE_STOPPED) { + TouchButtonStartStop.setText(PSTR("Stopped"), false); + } + #endif } @@ -1267,7 +1274,7 @@ void switchToStateSampleAndStoreToEEPROM(uint16_t aInitialSampleCountForStoring) * D for button double press, B for button press. */ void switchToStateStoppedLCD(char aReasonCharacter) { -// checkLeavingState(); // not required yet, button is removed anyway + checkLeavingState(); // Append button is removed below anyway if (sMeasurementState != STATE_STOPPED) { setLoad(NO_LOAD); auto tOldMeasurementState = sMeasurementState; @@ -1384,6 +1391,8 @@ void LCDPrintCutoff() { /* * Prints state of cut off level + * One of CUTOFF_LEVEL_HIGH, CUTOFF_LEVEL_LOW and CUTOFF_LEVEL_ZERO + * For Logger: CUTOFF_LEVEL_HIGH = 50%, LOW = 25% and ZERO = 12.5% */ void printCutoff() { #if defined(SUPPORT_BLUEDISPLAY_CHART) @@ -1499,7 +1508,7 @@ void checkForDelayedButtorProcessing() { /* * Check for removed battery or logger voltage or current - * @return true if Voltage is low or current is zero + * @return true if voltage is low or current is zero */ bool isVoltageOrCurrentRemoved() { @@ -1699,9 +1708,9 @@ void setLoad(uint8_t aNewLoadState) { } } -void getBatteryVoltageMillivolt() { +void getBatteryOrLoggerVoltageMillivolt() { - uint16_t tInputVoltageRaw = getBatteryRawVoltage(); + uint16_t tInputVoltageRaw = getBatteryOrLoggerRawVoltage(); /* * Compute voltage */ @@ -1757,7 +1766,7 @@ void setToHighVoltageRange() { * With 5 volt VCC this range goes up to 20 volt resulting in a raw value of 4651 * Does not affect the loads */ -uint16_t getBatteryRawVoltage() { +uint16_t getBatteryOrLoggerRawVoltage() { uint16_t tInputVoltageRaw = waitAndReadADCChannelWithReference(ADC_CHANNEL_FOR_VOLTAGE, INTERNAL); /* * Automatic range @@ -1828,22 +1837,22 @@ void addToCapacity() { } void clearLogger1SecondAccumulator() { - sLogger1SecondRawVoltageAccumulator = 0; - sLogger1SecondRawCurrentAccumulator = 0; - sLogger1SecondRawSampleCount = 0; - sLoggerMinimumRawVoltage = 0xFFFF; - sLoggerMaximumRawVoltage = 0; + sLogger1SecondAccumulator.RawVoltageAccumulator = 0; + sLogger1SecondAccumulator.RawCurrentAccumulator = 0; + sLogger1SecondAccumulator.RawSampleCount = 0; + sLogger1SecondAccumulator.MinimumRawVoltage = UINT16_MAX; + sLogger1SecondAccumulator.MaximumRawVoltage = 0; } void clearLogger1MinuteAccumulator() { - sLogger1MinuteRawVoltageAccumulator8ShiftRight = 0; - sLogger1MinuteRawCurrentAccumulator = 0; - sLogger1MinuteRawSampleCount = 0; + sLogger1MinuteAccumulator.RawVoltageAccumulator8ShiftRight = 0; + sLogger1MinuteAccumulator.RawCurrentAccumulator = 0; + sLogger1MinuteAccumulator.RawSampleCount = 0; // start every minute with new range selection setToLowVoltageRange(); sLoggerADCVoltageReference = INTERNAL; - sMeasurementInfo.Voltages.Logger.MaximumMillivolt = 0xFFFF; - sMeasurementInfo.Voltages.Logger.MinimumMillivolt = 0; + sMeasurementInfo.Voltages.Logger.MinimumMillivolt = UINT16_MAX; + sMeasurementInfo.Voltages.Logger.MaximumMillivolt = 0; } /* @@ -1854,13 +1863,14 @@ void getLogger1SecondValues() { /* * Accumulate for minute */ - sLogger1MinuteRawVoltageAccumulator8ShiftRight += sLogger1SecondRawVoltageAccumulator >> 8; - sLogger1MinuteRawSampleCount += LOGGER_SAMPLE_FREQUENCY_HZ; + sLogger1MinuteAccumulator.RawVoltageAccumulator8ShiftRight += sLogger1SecondAccumulator.RawVoltageAccumulator >> 8; + sLogger1MinuteAccumulator.RawSampleCount += LOGGER_SAMPLE_FREQUENCY_HZ; /* * Compute Milliampere and avoid overflow */ - sMeasurementInfo.Milliampere = ((((ADC_INTERNAL_REFERENCE_MILLIVOLT * 1000L) / 1023L) * sLogger1SecondRawCurrentAccumulator) + sMeasurementInfo.Milliampere = ((((ADC_INTERNAL_REFERENCE_MILLIVOLT * 1000L) / 1023L) + * sLogger1SecondAccumulator.RawCurrentAccumulator) / (LOGGER_SHUNT_RESISTOR_MILLIOHM * LOGGER_SAMPLE_FREQUENCY_HZ * LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT)); sLastMilliampereLowPassFiltered5 += ((sLastMilliampereLowPassFiltered5 - sLastMilliampereLowPassFiltered5) + (1 << 4)) >> 5; // 2.5 us, alpha = 1/32 0.03125, cutoff frequency 5.13 Hz @1kHz @@ -1870,43 +1880,50 @@ void getLogger1SecondValues() { * >> 8 and * 4 in divisor are a fast and short way to divide by 1024 */ sMeasurementInfo.Voltages.Logger.AverageMillivolt = - ((ADC_INTERNAL_REFERENCE_MILLIVOLT * (sLogger1SecondRawVoltageAccumulator >> 8)) + ((ADC_INTERNAL_REFERENCE_MILLIVOLT * (sLogger1SecondAccumulator.RawVoltageAccumulator >> 8)) / ((LOGGER_SAMPLE_FREQUENCY_HZ * LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT * 4) / ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE)); -// -// sMeasurementInfo.Voltages.Logger.MaximumMillivolt = (((ADC_INTERNAL_REFERENCE_MILLIVOLT * ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE) -// * (uint32_t) sLoggerMaximumRawVoltage) / 1023L); -// divide by 1024 in short and fast. ((... >> 8) >> 2) does not work, it will be first converted to >> 10 and the compiled into a loop - sMeasurementInfo.Voltages.Logger.MaximumMillivolt = (((ADC_INTERNAL_REFERENCE_MILLIVOLT * ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE) - * (uint32_t) sLoggerMaximumRawVoltage) >> 8); - sMeasurementInfo.Voltages.Logger.MaximumMillivolt >>= 2; +// This gives overflow :-( +// sMeasurementInfo.Voltages.Logger.AverageMillivolt = (((ADC_INTERNAL_REFERENCE_MILLIVOLT * ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE) +// * (uint32_t) sLogger1SecondAccumulator.RawVoltageAccumulator >> 8) +// / (LOGGER_SAMPLE_FREQUENCY_HZ * LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT * 4L)); + sMeasurementInfo.Voltages.Logger.MaximumMillivolt = (((ADC_INTERNAL_REFERENCE_MILLIVOLT * ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE) + * (uint32_t) sLogger1SecondAccumulator.MaximumRawVoltage) / 1024L); sMeasurementInfo.Voltages.Logger.MinimumMillivolt = (((ADC_INTERNAL_REFERENCE_MILLIVOLT * ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE) - * (uint32_t) sLoggerMinimumRawVoltage) >> 8); - sMeasurementInfo.Voltages.Logger.MinimumMillivolt >>= 2; + * (uint32_t) sLogger1SecondAccumulator.MinimumRawVoltage) / 1024L); #if defined(LOCAL_TRACE) Serial.print(F("cnt=")); - Serial.print(sLogger1SecondRawSampleCount); + Serial.print(sLogger1SecondAccumulator.RawSampleCount); Serial.print(F(" Iacc=")); - Serial.print(sLogger1SecondRawCurrentAccumulator); + Serial.print(sLogger1SecondAccumulator.RawCurrentAccumulator); Serial.print(' '); Serial.print(sMeasurementInfo.Milliampere); Serial.print(F(" mA, Uacc=")); - Serial.print(sLogger1SecondRawVoltageAccumulator); - Serial.print(' '); - Serial.print(sMeasurementInfo.Voltages.Battery.NoLoadMillivolt); - Serial.print(F(" mV l=")); + Serial.print(sLogger1SecondAccumulator.RawVoltageAccumulator); + Serial.print(F(", Umin=")); + Serial.print(sMeasurementInfo.Voltages.Logger.MinimumMillivolt); + Serial.print(F(" | ")); + Serial.print(sLogger1SecondAccumulator.MinimumRawVoltage); + Serial.print(F(", Umax=")); + Serial.print(sMeasurementInfo.Voltages.Logger.MaximumMillivolt); + Serial.print(F(" | ")); + Serial.print(sLogger1SecondAccumulator.MaximumRawVoltage); + Serial.print(F(", Uav=")); + Serial.print(sMeasurementInfo.Voltages.Logger.AverageMillivolt); + Serial.print(F(" mV Range=")); Serial.println(sVoltageRangeIsLow); #endif clearLogger1SecondAccumulator(); } + void getLogger1MinuteValues() { // avoid overflow sMeasurementInfo.Milliampere = ((((ADC_INTERNAL_REFERENCE_MILLIVOLT * 1000L) / 1023L) - * (sLogger1MinuteRawCurrentAccumulator / LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT)) - / (sLogger1MinuteRawSampleCount * LOGGER_SHUNT_RESISTOR_MILLIOHM)); + * (sLogger1MinuteAccumulator.RawCurrentAccumulator / LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT)) + / (sLogger1MinuteAccumulator.RawSampleCount * LOGGER_SHUNT_RESISTOR_MILLIOHM)); /* * Compute voltage and avoid overflow @@ -1914,8 +1931,8 @@ void getLogger1MinuteValues() { * we do (sLogger1MinuteRawVoltageAccumulator8ShiftRight >> 8) and divide the divisor by 2^6 (64) */ sMeasurementInfo.Voltages.Logger.AverageMillivolt = (ADC_INTERNAL_REFERENCE_MILLIVOLT - * (sLogger1MinuteRawVoltageAccumulator8ShiftRight >> 8)) - / (((uint32_t) sLogger1MinuteRawSampleCount * LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT) + * (sLogger1MinuteAccumulator.RawVoltageAccumulator8ShiftRight >> 8)) + / (((uint32_t) sLogger1MinuteAccumulator.RawSampleCount * LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT) / (ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE * 64)); #if defined(LOCAL_TRACE) @@ -1937,43 +1954,31 @@ void getLogger1MinuteValues() { } /* - * Maximal current for a 0.2 ohm shunt resistor is 5.5 A, and resolution is 5.4 mA. - */ -void getLoggerCurrent() { - uint32_t tRawCurrentValue = readADCChannelMultiSamplesWithReferenceAndPrescaler(ADC_CHANNEL_LOGGER_CURRENT, INTERNAL, - ADC_PRESCALE32, LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT); - /* - * Compute Milliampere and avoid overflow - */ - sMeasurementInfo.Milliampere = ((((ADC_INTERNAL_REFERENCE_MILLIVOLT * 1000L) / 1023L) * tRawCurrentValue) - / (LOGGER_SHUNT_RESISTOR_MILLIOHM * LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT)); -} - -/* - * Sampling takes 40.5 ms. For Voltage > 4.4V it takes 48.5 ms + * Sampling of 20ms takes 40.5 ms. For voltage > 4.4V it takes 48.5 ms */ void handlePeriodicAccumulatingLoggerValues() { if (sOnlyLoggerFunctionality && ((unsigned) (millis() - sLastMillisOfLoggerSample) >= LOGGER_SAMPLE_PERIOD_MILLIS)) { sLastMillisOfLoggerSample = millis(); + digitalWrite(LED_BUILTIN, HIGH); + if (sMeasurementState != STATE_WAITING_FOR_BATTERY_OR_VOLTAGE) { /* * Read 769 current values in 20ms if not stopped */ - digitalWrite(LED_BUILTIN, HIGH); // switch channel and reference #if defined(LOCAL_TRACE) // uint8_t tOldADMUX = #endif - checkAndWaitForReferenceAndChannelToSwitch(ADC_CHANNEL_LOGGER_CURRENT, INTERNAL); // + checkAndWaitForReferenceAndChannelToSwitch(ADC_CHANNEL_LOGGER_CURRENT, INTERNAL); /* * maximum value of readADCChannelWithReferenceAndPrescalerMultiSamples(...769) is 800 000 * So we can have 5 k of it in a 32 bit integer - * LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT = 769 for ADC_PRESCALE32 and 20 ms (50 Hz) + * LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT = 769 for ADC_PRESCALE32 / 26us and 20 ms (50 Hz) */ uint32_t tRawCurrentValue = readADCChannelMultiSamples(ADC_PRESCALE32, LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT); digitalWrite(LED_BUILTIN, LOW); - sLogger1SecondRawCurrentAccumulator += tRawCurrentValue; - sLogger1MinuteRawCurrentAccumulator += tRawCurrentValue; + sLogger1SecondAccumulator.RawCurrentAccumulator += tRawCurrentValue; + sLogger1MinuteAccumulator.RawCurrentAccumulator += tRawCurrentValue; #if defined(LOCAL_TRACE) if (!sOnlyPlotterOutput) { // Serial.print(F("OldADMUX=0x")); @@ -1992,22 +1997,28 @@ void handlePeriodicAccumulatingLoggerValues() { */ waitAndReadADCChannelWithReference(ADC_CHANNEL_FOR_VOLTAGE, sLoggerADCVoltageReference); - digitalWrite(LED_BUILTIN, HIGH); - uint32_t tRawVoltageValue = 0; - uint16_t tInputMinimumRawVoltage = 0xFFFF; - uint16_t tInputMaximumRawVoltage = 0; + uint32_t tRawVoltageValue; + uint16_t tInputMinimumRawVoltage; + uint16_t tInputMaximumRawVoltage; + ADCSRB = 0; // Free running mode. Only active if ADATE is set to 1. // ADSC-StartConversion ADATE-AutoTriggerEnable ADIF-Reset Interrupt Flag ADCSRA = (_BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE32); - for (uint16_t i = 0; i < LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT; i++) { + for (uint16_t i = 0; i < LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT;) { + if (i == 0) { + // start a new measurement / new range + tRawVoltageValue = 0; + tInputMinimumRawVoltage = UINT16_MAX; + tInputMaximumRawVoltage = 0; + } /* * wait for free running conversion to finish. * Do not wait for ADSC here, since ADSC is only low for 1 ADC Clock cycle on free running conversion. */ loop_until_bit_is_set(ADCSRA, ADIF); - ADCSRA |= _BV(ADIF); // clear bit to enable recognizing next conversion has finished + // Add value uint16_t tInputRawVoltage = ADCL | (ADCH << 8); if (tInputRawVoltage >= 0x3F0) { // 1008 @@ -2032,14 +2043,15 @@ void handlePeriodicAccumulatingLoggerValues() { Serial.println(F("overvoltage")); } #endif - tRawVoltageValue = 0x3FF * LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT; + tRawVoltageValue = 0x3FF * LOGGER_NUMBER_OF_SAMPLES_PER_MEASUREMENT; // put maximum value in tRawVoltageValue break; } - // Start a full new loop + // Start a full new loop after changing input range i = 0; - tRawVoltageValue = 0; - tInputRawVoltage = 0; - } + delayMicroseconds(52); // wait for 2 conversions to be gone after changing range + continue; + } // End of range check (tInputRawVoltage >= 0x3F0) + tRawVoltageValue += tInputRawVoltage; if (tInputMinimumRawVoltage > tInputRawVoltage) { tInputMinimumRawVoltage = tInputRawVoltage; @@ -2047,14 +2059,15 @@ void handlePeriodicAccumulatingLoggerValues() { if (tInputMaximumRawVoltage < tInputRawVoltage) { tInputMaximumRawVoltage = tInputRawVoltage; } + i++; // To enable re-initialization of loop by i=0 above } ADCSRA &= ~_BV(ADATE); // Disable auto-triggering (free running mode) // normalize to 1023 at 4.4 V if (sVoltageRangeIsLow) { - tRawVoltageValue /= (ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE / ATTENUATION_FACTOR_VOLTAGE_LOW_RANGE); - tInputMaximumRawVoltage /= (ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE / ATTENUATION_FACTOR_VOLTAGE_LOW_RANGE); - tInputMinimumRawVoltage /= (ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE / ATTENUATION_FACTOR_VOLTAGE_LOW_RANGE); + tRawVoltageValue /= (ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE / ATTENUATION_FACTOR_VOLTAGE_LOW_RANGE); // divide by 2 + tInputMaximumRawVoltage /= (ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE / ATTENUATION_FACTOR_VOLTAGE_LOW_RANGE); // divide by 2 + tInputMinimumRawVoltage /= (ATTENUATION_FACTOR_VOLTAGE_HIGH_RANGE / ATTENUATION_FACTOR_VOLTAGE_LOW_RANGE); // divide by 2 } else if (sLoggerADCVoltageReference == DEFAULT) { // Adjust tInputVoltageRaw to a virtual 12.5 bit range -> maximum value is 5000 for 20 V uint16_t tReadoutFor1_1Reference = waitAndReadADCChannelWithReference(ADC_1_1_VOLT_CHANNEL_MUX, DEFAULT); // 225 at 5 volt VCC @@ -2063,12 +2076,12 @@ void handlePeriodicAccumulatingLoggerValues() { tInputMinimumRawVoltage = (tInputMinimumRawVoltage * 1023L) / tReadoutFor1_1Reference; } - sLogger1SecondRawVoltageAccumulator += tRawVoltageValue; - if (sLoggerMaximumRawVoltage < tInputMaximumRawVoltage) { - sLoggerMaximumRawVoltage = tInputMaximumRawVoltage; + sLogger1SecondAccumulator.RawVoltageAccumulator += tRawVoltageValue; + if (sLogger1SecondAccumulator.MaximumRawVoltage < tInputMaximumRawVoltage) { + sLogger1SecondAccumulator.MaximumRawVoltage = tInputMaximumRawVoltage; } - if (sLoggerMinimumRawVoltage > tInputMinimumRawVoltage) { - sLoggerMinimumRawVoltage = tInputMinimumRawVoltage; + if (sLogger1SecondAccumulator.MinimumRawVoltage > tInputMinimumRawVoltage) { + sLogger1SecondAccumulator.MinimumRawVoltage = tInputMinimumRawVoltage; } #if defined(LOCAL_TRACE) @@ -2082,15 +2095,27 @@ void handlePeriodicAccumulatingLoggerValues() { } #endif - sLogger1SecondRawSampleCount++; + sLogger1SecondAccumulator.RawSampleCount++; digitalWrite(LED_BUILTIN, LOW); } } -void getBatteryCurrent() { - uint16_t tShuntVoltageRaw = waitAndReadADCChannelWithReference(ADC_CHANNEL_CURRENT, INTERNAL); +/* + * Maximal current for a 0.2 ohm shunt resistor is 5.5 A, and resolution is 5.4 mA. + */ +void getCurrent(uint8_t aADCChannel, uint16_t aShuntResistorMilliohm) { + uint16_t tShuntVoltageRaw = waitAndReadADCChannelWithReference(aADCChannel, INTERNAL); sMeasurementInfo.Milliampere = (((ADC_INTERNAL_REFERENCE_MILLIVOLT * 1000L) * tShuntVoltageRaw) - / (1023L * ESR_SHUNT_RESISTOR_MILLIOHM)); + / (1023L * aShuntResistorMilliohm)); +#if defined(LOCAL_TRACE) + Serial.print(F("Ch ")); + Serial.print(aADCChannel); + Serial.print(F(", Raw=")); + Serial.print(tShuntVoltageRaw); + Serial.print(F(", ")); + Serial.print(sMeasurementInfo.Milliampere); + Serial.println(F(" mA")); +#endif } /* @@ -2108,8 +2133,8 @@ void getBatteryCurrent() { */ void getBatteryValues() { // Do it before deactivating the load - getBatteryCurrent(); - getBatteryVoltageMillivolt(); // get current battery load voltage (no load in case of stopped) + getCurrent(ADC_CHANNEL_CURRENT, ESR_SHUNT_RESISTOR_MILLIOHM); + getBatteryOrLoggerVoltageMillivolt(); // get current battery load voltage (no load in case of stopped) if (sMeasurementState == STATE_STOPPED) return; // thats all if stopped :-) @@ -2118,7 +2143,7 @@ void getBatteryValues() { setLoad(NO_LOAD); digitalWrite(LED_BUILTIN, HIGH); delay(sMeasurementInfo.LoadSwitchSettleTimeMillis); - getBatteryVoltageMillivolt(); // get current battery NoLoadMillivolt + getBatteryOrLoggerVoltageMillivolt(); // get current battery NoLoadMillivolt // restore original load state setLoad(BatteryTypeInfoArray[sMeasurementInfo.BatteryTypeIndex].LoadType); digitalWrite(LED_BUILTIN, LOW); @@ -2261,6 +2286,9 @@ void checkAndHandleStopConditionLCD() { } if (tStopConditionIsMet && sMeasurementState == STATE_SAMPLE_AND_STORE_TO_EEPROM) { switchToStateStoppedLCD('-'); +#if defined(SUPPORT_BLUEDISPLAY_CHART) + TouchButtonStartStop.setText(PSTR("Finished"), true); +#endif #if defined(USE_LCD) _delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); // show "stopped" myLCD.setCursor(7, 0); @@ -2306,10 +2334,10 @@ void setBatteryTypeIndex(uint16_t aBatteryVoltageMillivolt) { */ void detectBatteryOrLoggerVoltageAndCurrentLCD() { setLoad(NO_LOAD); - getBatteryVoltageMillivolt(); + getBatteryOrLoggerVoltageMillivolt(); if (sOnlyLoggerFunctionality) { - getLoggerCurrent(); + getCurrent(ADC_CHANNEL_LOGGER_CURRENT, LOGGER_SHUNT_RESISTOR_MILLIOHM); if (sMeasurementInfo.Voltages.Battery.NoLoadMillivolt > NO_BATTERY_MILLIVOLT && sMeasurementInfo.Milliampere >= NO_LOGGER_MILLAMPERE) { // external voltage and current found @@ -2328,7 +2356,7 @@ void detectBatteryOrLoggerVoltageAndCurrentLCD() { else if (sMeasurementInfo.Voltages.Battery.NoLoadMillivolt <= NO_BATTERY_MILLIVOLT && sMeasurementInfo.Milliampere < NO_LOGGER_MILLAMPERE) { myLCD.setCursor(7, 0); - myLCD.print(F("No U or I")); + myLCD.print(F("No U && I")); } #endif // else -> we have voltage or current attached. @@ -2344,10 +2372,6 @@ void detectBatteryOrLoggerVoltageAndCurrentLCD() { if (sLastBatteryTypeIndex != sMeasurementInfo.BatteryTypeIndex) { // Type changed sLastBatteryTypeIndex = sMeasurementInfo.BatteryTypeIndex; -#if defined(SUPPORT_BLUEDISPLAY_CHART) - BlueDisplay1.setWriteStringPosition(VALUES_POSITION_X, MESSAGE_START_POSITION_Y); - BlueDisplay1.setWriteStringSizeAndColorAndFlag(BASE_TEXT_SIZE, sTextColor, sBackgroundColor, false); -#endif if (sMeasurementInfo.BatteryTypeIndex == TYPE_INDEX_NO_BATTERY) { #if defined(SUPPORT_BLUEDISPLAY_CHART) BlueDisplay1.writeString(F("\rNo battery ")); @@ -2360,11 +2384,11 @@ void detectBatteryOrLoggerVoltageAndCurrentLCD() { // print voltage before the delay for LCD display printVoltageNoLoadMillivoltWithTrailingSpaceLCD(); #if !defined(SUPPRESS_SERIAL_PRINT) - if (!sOnlyPlotterOutput) { - Serial.print(F(" => ")); - Serial.print(BatteryTypeInfoArray[sMeasurementInfo.BatteryTypeIndex].TypeName); - Serial.println(F(" found")); - } + if (!sOnlyPlotterOutput) { + Serial.print(F(" => ")); + Serial.print(BatteryTypeInfoArray[sMeasurementInfo.BatteryTypeIndex].TypeName); + Serial.println(F(" found")); + } #endif #if defined(SUPPORT_BLUEDISPLAY_CHART) BlueDisplay1.writeString(F("\rFound ")); @@ -2385,31 +2409,6 @@ void detectBatteryOrLoggerVoltageAndCurrentLCD() { } } -/* - * Called exclusively from setup() after readAndProcessEEPROMData() - */ -void printStoredDataLCD() { -#if defined(SUPPORT_BLUEDISPLAY_CHART) - if (BlueDisplay1.isConnectionEstablished()) { - printChartValues(); - } -#endif - -#if defined(USE_LCD) - myLCD.clear(); - myLCD.print(getVCCVoltage(), 1); - myLCD.print(F("V Stored data")); -#endif - /* - * Print battery values, and use state STATE_SETUP_AND_READ_EEPROM for formatting - * "0.061o h 1200mAh" using sMeasurementInfo.ESRMilliohm - */ - printMeasurementValuesLCD(); -#if defined(USE_LCD) - _delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); -#endif -} - void printVoltageNoLoadMillivoltWithTrailingSpaceLCD() { uint16_t tVoltageNoLoadMillivolt = sMeasurementInfo.Voltages.Battery.NoLoadMillivolt; // saves 12 bytes programming space sLastVoltageNoLoadMillivoltForPrintAndCountdown = tVoltageNoLoadMillivolt; @@ -2421,7 +2420,8 @@ void printVoltageNoLoadMillivoltWithTrailingSpaceLCD() { } #endif - if (sLastDiplayedValues.VoltageMillivolt != tVoltageNoLoadMillivolt) { + if (sLastDiplayedValues.VoltageMillivolt != tVoltageNoLoadMillivolt + && abs(sLastDiplayedValues.VoltageMillivolt - tVoltageNoLoadMillivolt) > 1) { sLastDiplayedValues.VoltageMillivolt = tVoltageNoLoadMillivolt; #if defined(SUPPORT_BLUEDISPLAY_CHART) @@ -2475,6 +2475,120 @@ void printCapacity5Digits() { } /* + * Print ESR, if value has changed by more than 1 + */ +void printESR() { + uint32_t tMilliohm; // Compiler complains about initialize variable, which is wrong + if (sMeasurementState == STATE_INITIAL_SAMPLES && sMeasurementInfo.Milliampere != 0) { + tMilliohm = sESRHistory[0]; + } else { + tMilliohm = sMeasurementInfo.ESRMilliohm; + } + +#if !defined(SUPPRESS_SERIAL_PRINT) + if (!sOnlyPlotterOutput) { + Serial.print(F("ESR=")); + if (tMilliohm == __UINT16_MAX__) { + /* + * No recent current measurement -> show old ESR + */ + Serial.print(F("overflow, ")); + } else { + printMillisValueAsFloat(tMilliohm); + Serial.print(F(" ohm, ")); + } + } +#endif + + if (sLastDiplayedValues.ESRMilliohm != tMilliohm && abs(sLastDiplayedValues.ESRMilliohm - tMilliohm) > 1) { + sLastDiplayedValues.ESRMilliohm = tMilliohm; + +#if defined(SUPPORT_BLUEDISPLAY_CHART) + // Do not print in values line if after boot, or in logger mode + if (sMeasurementState != STATE_SETUP_AND_READ_EEPROM && !sOnlyPlotterOutput) { + if (BlueDisplay1.isConnectionEstablished()) { + char tString[8]; + if (tMilliohm == __UINT16_MAX__) { + BlueDisplay1.drawText(ESR_POSITION_X, VALUES_POSITION_Y, F("overflow"), BASE_TEXT_SIZE * 2, + CHART_ESR_COLOR, sBackgroundColor); + } else { + snprintf_P(tString, sizeof(tString), PSTR("%4u m\x81"), tMilliohm); + BlueDisplay1.drawText(ESR_POSITION_X, VALUES_POSITION_Y, tString, BASE_TEXT_SIZE * 2, CHART_ESR_COLOR, + sBackgroundColor); + } + } + } +#endif +#if defined(USE_LCD) + myLCD.setCursor(0, 1); + if (sOnlyLoggerFunctionality) { + LCDPrintAsFloatWith3Decimals(sMeasurementInfo.Voltages.Logger.MaximumMillivolt); + myLCD.print(F("V")); + } else { + float tOhmFloat; + if (tMilliohm == __UINT16_MAX__) { + myLCD.print(F("99.99")); // Overflow + } else { + tOhmFloat = (float) (tMilliohm) / 1000.0; + if (tMilliohm < 10000) { + myLCD.print(tOhmFloat, 3); + } else { + myLCD.print(tOhmFloat, 2); + } + } + myLCD.print(F("\xF4 ")); // Ohm symbol + } +#endif + } +} + +/* + * Is called each time counter changes + * Print to the same location as index counter for STATE_SAMPLE_AND_STORE_TO_EEPROM + */ +void printCounter(uint16_t aNumberToPrint) { +#if defined(USE_LCD) + myLCD.setCursor(6, 0); // in case voltage was not printed + + if (aNumberToPrint < 10 && aNumberToPrint >= 0) { + /* + * We start with array index -1, which indicates initialization of array :-) + * We have "-1" once, because we store values (and increment index) after print + */ + myLCD.print(' '); // padding space for count + if (sMeasurementState == STATE_INITIAL_SAMPLES) { + tone(BUZZER_PIN, 2000, 40); + } + } + if (aNumberToPrint < 100) { + myLCD.print(' '); // padding space :-) + } + if (aNumberToPrint < 1000) { + myLCD.print(' '); // padding space :-) + } + myLCD.print(aNumberToPrint); + myLCD.print(' '); // trailing space for count (just in case mA are > 999) +#else + // only beep here + if (sMeasurementState == STATE_INITIAL_SAMPLES && aNumberToPrint < 10) { + tone(BUZZER_PIN, 2000, 40); + } +#endif +#if defined(SUPPORT_BLUEDISPLAY_CHART) + if (sMeasurementState == STATE_INITIAL_SAMPLES && BlueDisplay1.isConnectionEstablished()) { + /* + * Number of samples is printed by chart values > Samples + */ + char tString[4]; + snprintf_P(tString, sizeof(tString), PSTR("%3u"), aNumberToPrint); + BlueDisplay1.drawText(DISPLAY_WIDTH - BASE_TEXT_SIZE * 3, BASE_TEXT_SIZE * 2, tString, BASE_TEXT_SIZE, COLOR16_RED, + sBackgroundColor); + } +#endif +} + +/* + * Print only if changed more than 1 mA * Print no newline */ void printMilliampere4DigitsLCD() { @@ -2489,7 +2603,8 @@ void printMilliampere4DigitsLCD() { } } #endif - if (sLastDiplayedValues.Milliampere != sMeasurementInfo.Milliampere) { + if (sLastDiplayedValues.Milliampere != sMeasurementInfo.Milliampere + && abs(sLastDiplayedValues.Milliampere - sMeasurementInfo.Milliampere) > 1) { sLastDiplayedValues.Milliampere = sMeasurementInfo.Milliampere; #if defined(SUPPORT_BLUEDISPLAY_CHART) || defined(USE_LCD) @@ -2510,11 +2625,34 @@ void printMilliampere4DigitsLCD() { } } +/* + * To force display of values on LCD and BlueDisplay + */ void clearLastDiplayedValues() { sLastDiplayedValues.VoltageMillivolt = 0; sLastDiplayedValues.Milliampere = 0; sLastDiplayedValues.ESRMilliohm = 0; } + +/* + * Called exclusively from setup() after readAndProcessEEPROMData() + */ +void printStoredDataLCD() { +#if defined(USE_LCD) + myLCD.clear(); + myLCD.print(getVCCVoltage(), 1); + myLCD.print(F("V Stored data")); +#endif + /* + * Print battery values, and use state STATE_SETUP_AND_READ_EEPROM for formatting + * "0.061o h 1200mAh" using sMeasurementInfo.ESRMilliohm + */ + printMeasurementValuesLCD(); +#if defined(USE_LCD) + _delay(LCD_MESSAGE_PERSIST_TIME_MILLIS); +#endif +} + /* * Evaluates sMeasurementState and prints: * - sMeasurementInfo.Voltages.Battery.NoLoadMillivolt @@ -2571,34 +2709,14 @@ void printMeasurementValuesLCD() { sNumbersOfInitialSamplesToGo--; } - uint8_t tNumbersOfInitialSamplesToGo = sNumbersOfInitialSamplesToGo; #if !defined(SUPPRESS_SERIAL_PRINT) if (!sOnlyPlotterOutput) { - Serial.print(tNumbersOfInitialSamplesToGo); + Serial.print(sNumbersOfInitialSamplesToGo); Serial.print(F(" s, ")); // seconds until discharging } #endif -#if defined(USE_LCD) - myLCD.setCursor(7, 0); // in case voltage was not printed - if (tNumbersOfInitialSamplesToGo < 10) { - myLCD.print(' '); // padding space for count - tone(BUZZER_PIN, 2000, 40); - } - myLCD.print(tNumbersOfInitialSamplesToGo); - myLCD.print(' '); // trailing space for count (just in case mA are > 999) -#else - if (tNumbersOfInitialSamplesToGo < 10) { - tone(BUZZER_PIN, 2000, 40); - } -#endif - -#if defined(SUPPORT_BLUEDISPLAY_CHART) - if (BlueDisplay1.isConnectionEstablished()) { - // print to the same location as index counter for STATE_SAMPLE_AND_STORE_TO_EEPROM - printCounter(tNumbersOfInitialSamplesToGo); - } -#endif + printCounter(sNumbersOfInitialSamplesToGo); } else { #if defined(USE_LCD) @@ -2606,18 +2724,8 @@ void printMeasurementValuesLCD() { * Print counter for STATE_SAMPLE_AND_STORE_TO_EEPROM * Use (index + 1) to be consistent with the number of samples displayed for array */ - int tDeltaArrayIndex = (ValuesForDeltaStorage.DeltaArrayIndex + 1) - * (StartValues.NumberOfSecondsPerStorage / SECONDS_IN_ONE_MINUTE); - myLCD.setCursor(7, 0); // in case voltage was not printed -// We start with array index -1, which indicates initialization of array :-) - if (tDeltaArrayIndex < 10 && tDeltaArrayIndex >= 0) { - myLCD.print(' '); // we have "-1" once, because we store values (and increment index) after print - } - if (tDeltaArrayIndex < 100) { - myLCD.print(' '); // padding space :-) - } - - myLCD.print(tDeltaArrayIndex); + printCounter( + (ValuesForDeltaStorage.DeltaArrayIndex + 1) * (StartValues.NumberOfSecondsPerStorage / SECONDS_IN_ONE_MINUTE)); #endif } @@ -2635,7 +2743,6 @@ void printMeasurementValuesLCD() { * STATE_SETUP_AND_READ_EEPROM + STATE_SAMPLE_AND_STORE_TO_EEPROM: "0.061o h 1200mAh" using sMeasurementInfo.ESRMilliohm * STATE_INITIAL_SAMPLES: "0.061o l 0.128V" using current ESR from sESRHistory[0] */ - uint32_t tMilliohm; // Compiler complains about initialize variable, which is wrong if (sOnlyLoggerFunctionality) { #if !defined(SUPPRESS_SERIAL_PRINT) if (!sOnlyPlotterOutput) { @@ -2648,72 +2755,12 @@ void printMeasurementValuesLCD() { Serial.print(F(" V ")); } #endif - } else { - /* - * Print ESR - */ - if (tMeasurementState == STATE_INITIAL_SAMPLES && sMeasurementInfo.Milliampere != 0) { - tMilliohm = sESRHistory[0]; - } else { - tMilliohm = sMeasurementInfo.ESRMilliohm; - } - -#if !defined(SUPPRESS_SERIAL_PRINT) - if (!sOnlyPlotterOutput) { - Serial.print(F("ESR=")); - if (tMilliohm == __UINT16_MAX__) { - /* - * No recent current measurement -> show old ESR - */ - Serial.print(F("overflow, ")); - } else { - printMillisValueAsFloat(tMilliohm); - Serial.print(F(" ohm, ")); - } - } -#endif - - if (sLastDiplayedValues.ESRMilliohm != tMilliohm) { - sLastDiplayedValues.ESRMilliohm = tMilliohm; - -#if defined(SUPPORT_BLUEDISPLAY_CHART) - if (BlueDisplay1.isConnectionEstablished()) { - char tString[8]; - if (tMilliohm == __UINT16_MAX__) { - BlueDisplay1.drawText(ESR_POSITION_X, VALUES_POSITION_Y, F("overflow"), BASE_TEXT_SIZE * 2, - CHART_ESR_COLOR, sBackgroundColor); - } else { - snprintf_P(tString, sizeof(tString), PSTR("%4u m\x81"), tMilliohm); - BlueDisplay1.drawText(ESR_POSITION_X, VALUES_POSITION_Y, tString, BASE_TEXT_SIZE * 2, CHART_ESR_COLOR, - sBackgroundColor); - } - } -#endif -#if defined(USE_LCD) - myLCD.setCursor(0, 1); - if (sOnlyLoggerFunctionality) { - LCDPrintAsFloatWith3Decimals(sMeasurementInfo.Voltages.Logger.MaximumMillivolt); - myLCD.print(F("V")); - } else { - float tOhmFloat; - if (tMilliohm == __UINT16_MAX__) { - myLCD.print(F("99.99")); // Overflow - } else { - tOhmFloat = (float) (tMilliohm) / 1000.0; - if (tMilliohm < 10000) { - myLCD.print(tOhmFloat, 3); - } else { - myLCD.print(tOhmFloat, 2); - } - } - myLCD.print(F("\xF4 ")); // Ohm symbol - } -#endif - } } + printESR(); //first in line + /* - * Print cut off level + * Print cut off level character */ #if defined(USE_LCD) myLCD.setCursor(7, 1); // This avoids problems with values >= 10 ohm @@ -2742,13 +2789,9 @@ void printMeasurementValuesLCD() { #endif } } else { - /* * Print capacity */ -#if defined(USE_LCD) - myLCD.setCursor(8, 1); // This avoids problems with values >= 10 ohm -#endif printCapacity5Digits(); } } @@ -3040,7 +3083,7 @@ void storeCapacityAndCutoffLevelToEEPROM_LCD() { * * @param aIsLastElement If true, print BlueDisplay chart or Arduino plotter caption */ -void printValuesForPlotter(uint16_t aMillivoltToPrint, uint16_t aMilliampereToPrint, uint16_t aMilliohmToPrint, +void printValuesForPlotterAndChart(uint16_t aMillivoltToPrint, uint16_t aMilliampereToPrint, uint16_t aMilliohmToPrint, bool aIsLastElement) { #if defined(ARDUINO_2_0_PLOTTER_FORMAT) Serial.print(F("Voltage:")); @@ -3076,112 +3119,116 @@ void printValuesForPlotter(uint16_t aMillivoltToPrint, uint16_t aMilliampereToPr Serial.print(tDurationMinutes % 60); Serial.print(F("min:aMillivoltToPrint")); } -#else +#else // defined(ARDUINO_2_0_PLOTTER_FORMAT) # if defined(SUPPORT_BLUEDISPLAY_CHART) -#if defined(LOCAL_TRACE) +# if defined(LOCAL_TRACE) Serial.print(F("ChartReadValueArrayType=")); Serial.print(sChartReadValueArrayType); Serial.print(F(" ChartValueArrayIndex=")); Serial.print(sChartValueArrayIndex); Serial.print(F(" IsLastElement=")); Serial.println(aIsLastElement); -#endif +# endif + if (BlueDisplay1.isConnectionEstablished()) { // input is millivolt convert to 20 for one volt - if (sChartReadValueArrayType == TYPE_VOLTAGE) { + if (sChartReadValueArrayType == TYPE_VOLTAGE) { // Voltage - sChartValueArray[sChartValueArrayIndex] = (aMillivoltToPrint - sCompressionOffset200Millivolt) / sCompressionFactor; - } else if (sChartReadValueArrayType == TYPE_ESR) { - sChartValueArray[sChartValueArrayIndex] = (aMilliohmToPrint) / sCompressionFactor; - } else { - sChartValueArray[sChartValueArrayIndex] = (aMilliampereToPrint) / sCompressionFactor; - } - sChartValueArrayIndex++; - if (aIsLastElement) { - checkAndHandleEvents(); - if (BlueDisplay1.isConnectionEstablished()) { + sChartValueArray[sChartValueArrayIndex] = (aMillivoltToPrint - sCompressionOffset200Millivolt) / sCompressionFactor; + } else if (sChartReadValueArrayType == TYPE_ESR) { + sChartValueArray[sChartValueArrayIndex] = (aMilliohmToPrint) / sCompressionFactor; + } else { + // TYPE_CURRENT + sChartValueArray[sChartValueArrayIndex] = (aMilliampereToPrint) / sCompressionFactor; + } + sChartValueArrayIndex++; + if (aIsLastElement) { if (sChartReadValueArrayType == TYPE_VOLTAGE) { sLastChartData.Millivolt = aMillivoltToPrint; /* * Compute new x scale depending on the current data length */ VoltageChart.computeAndSetXLabelAndXDataScaleFactor(sChartValueArrayIndex, CHART_MAXIMUM_X_SCALE_FACTOR); -#if defined(LOCAL_TRACE) +# if defined(LOCAL_TRACE) Serial.print(F("sChartValueArrayIndex=")); Serial.print(sChartValueArrayIndex); Serial.print(F(" mWidthX=")); Serial.print(VoltageChart.mWidthX); Serial.print(F(" mXDataScaleFactor=")); Serial.println(VoltageChart.mXDataScaleFactor); -#endif +# endif clearAndDrawChart(); // now the axes parameter are set, draw axes and grid and caption } else if (sChartReadValueArrayType == TYPE_ESR) { sLastChartData.ESRMilliohm = aMilliohmToPrint; } else { + // TYPE_CURRENT sLastChartData.Milliampere = aMilliampereToPrint; } VoltageChart.drawChartDataWithYOffset(sChartValueArray, sChartValueArrayIndex, CHART_MODE_LINE); } } -# else - if (aIsLastElement) { + if (!BlueDisplay1.isConnectionEstablished()) +# endif // defined(SUPPORT_BLUEDISPLAY_CHART) + { // new test is shorter than else! + if (aIsLastElement) { // Print updated plotter caption - Serial.print(F("Voltage=")); - printMillisValueAsFloat(StartValues.initialDischargingMillivolt); - Serial.print(F("V->")); - if (sOnlyLoggerFunctionality) { - printMillisValueAsFloat(sMeasurementInfo.Voltages.Logger.MaximumMillivolt); - } else { - printMillisValueAsFloat(aMillivoltToPrint); - } - Serial.print(F("V:")); - Serial.print(aMillivoltToPrint); - Serial.print(F(" Current=")); + Serial.print(F("Voltage=")); + printMillisValueAsFloat(StartValues.initialDischargingMillivolt); + Serial.print(F("V->")); + if (sOnlyLoggerFunctionality) { + printMillisValueAsFloat(sMeasurementInfo.Voltages.Logger.MaximumMillivolt); + } else { + printMillisValueAsFloat(aMillivoltToPrint); + } + Serial.print(F("V:")); + Serial.print(aMillivoltToPrint); + Serial.print(F(" Current=")); // Serial.print(F("V Current=")); - Serial.print(StartValues.initialDischargingMilliampere); - Serial.print(F("mA->")); - Serial.print(aMilliampereToPrint); - Serial.print(F("mA:")); - Serial.print(aMilliampereToPrint); - if (StartValues.initialDischargingMilliohm > 0) { - Serial.print(F(" ESR=")); + Serial.print(StartValues.initialDischargingMilliampere); + Serial.print(F("mA->")); + Serial.print(aMilliampereToPrint); + Serial.print(F("mA:")); + Serial.print(aMilliampereToPrint); + if (StartValues.initialDischargingMilliohm > 0) { + Serial.print(F(" ESR=")); // Serial.print(F("mA ESR=")); - Serial.print(StartValues.initialDischargingMilliohm); - Serial.print(F("mohm->")); - Serial.print(aMilliohmToPrint); - Serial.print(F("mohm:")); - Serial.print(aMilliohmToPrint); - Serial.print(F(" LoadResistor=")); + Serial.print(StartValues.initialDischargingMilliohm); + Serial.print(F("mohm->")); + Serial.print(aMilliohmToPrint); + Serial.print(F("mohm:")); + Serial.print(aMilliohmToPrint); + Serial.print(F(" LoadResistor=")); // Serial.print(F("mohm LoadResistor=")); - printMillisValueAsFloat(StartValues.LoadResistorMilliohm); - Serial.print(F("ohm")); - } - Serial.print(F(" Capacity=")); - if (isStandardCapacityAvailable) { - Serial.print(sStandardCapacityMilliampereHour); - Serial.print('_'); - } - Serial.print(StartValues.CapacityMilliampereHour); - Serial.print(F("mAh Duration")); - uint16_t tDurationMinutes = (ValuesForDeltaStorage.DeltaArrayIndex) - * (StartValues.NumberOfSecondsPerStorage / SECONDS_IN_ONE_MINUTE); - Serial.print('='); - Serial.print(tDurationMinutes / 60); - Serial.print(F("h_")); - Serial.print(tDurationMinutes % 60); - Serial.print(F("min")); - } else { - Serial.print(aMillivoltToPrint); - Serial.print(' '); - Serial.print(aMilliampereToPrint); - if (StartValues.initialDischargingMilliohm > 0) { + printMillisValueAsFloat(StartValues.LoadResistorMilliohm); + Serial.print(F("ohm")); + } + Serial.print(F(" Capacity=")); + if (isStandardCapacityAvailable) { + Serial.print(sStandardCapacityMilliampereHour); + Serial.print('_'); + } + Serial.print(StartValues.CapacityMilliampereHour); + Serial.print(F("mAh Duration")); + uint16_t tDurationMinutes = (ValuesForDeltaStorage.DeltaArrayIndex) + * (StartValues.NumberOfSecondsPerStorage / SECONDS_IN_ONE_MINUTE); + Serial.print('='); + Serial.print(tDurationMinutes / 60); + Serial.print(F("h_")); + Serial.print(tDurationMinutes % 60); + Serial.print(F("min")); + } else { + Serial.print(aMillivoltToPrint); Serial.print(' '); - Serial.print(aMilliohmToPrint); + Serial.print(aMilliampereToPrint); + if (StartValues.initialDischargingMilliohm > 0) { + Serial.print(' '); + Serial.print(aMilliohmToPrint); + } } + Serial.println(); } -# endif -#endif +#endif // defined(ARDUINO_2_0_PLOTTER_FORMAT) } #define CAPACITY_WAITING_FOR_NOMINAL_FULL_VOLTAGE 0 @@ -3235,8 +3282,14 @@ void readAndProcessEEPROMData(bool aStoreValuesForDisplayAndAppend) { uint8_t tCapacityMilliampereHourStandardValueState; #if defined(SUPPORT_BLUEDISPLAY_CHART) - sChartValueArrayIndex = 0; // Start 3 new chart lines + sChartValueArrayIndex = 0; // Start a new chart line +#if defined(LOCAL_DEBUG) + Serial.print(F("Store=")); + Serial.print(aStoreValuesForDisplayAndAppend); + Serial.print(F(" Type=")); + Serial.print(sChartReadValueArrayType); +#endif /* * For 7 grids vertical and 1.4 full range, (compression * YDataFactor) = 1 * @@ -3258,21 +3311,24 @@ void readAndProcessEEPROMData(bool aStoreValuesForDisplayAndAppend) { sCompressionOffset200Millivolt = 0; } sCompressionFactor = 10; //each grid is 200 mV - VoltageChart.initYLabelFloat(sCompressionOffset200Millivolt / 1000.0, 0.2, 0.01, 3, 1); // 0.2 volt per grid, 0.01 -> 100 for 1.0 (volt) , "3.5" as label + VoltageChart.initYLabel(sCompressionOffset200Millivolt / 1000.0, 0.2, 0.01, 3, 1); // 0.2 volt per grid, 0.01 -> 100 for 1.0 (volt) , "3.5" as label VoltageChart.setDataColor(CHART_VOLTAGE_COLOR); + VoltageChart.setXLabelBaseIncrementValue( + (StartValues.NumberOfSecondsPerStorage * CHART_MINUTES_PER_X_LABEL_UNCOMPRESSED) / SECONDS_PER_MINUTES); } else if (sChartReadValueArrayType == TYPE_ESR) { /* * Process ESR - * if ESR > 120 mOhm then each grid is 200 mOhm else each grid is 20 mOhm + * if ESR > 1200 mOhm then each grid is 2 Ohm, if ESR > 120 mOhm then each grid is 200 mOhm else each grid is 20 mOhm */ - if (StartValues.initialDischargingMilliohm > 120) { + if (StartValues.initialDischargingMilliohm > 1300) { + sCompressionFactor = 100; // each grid is 2000 mOhm + } else if (StartValues.initialDischargingMilliohm > 130) { sCompressionFactor = 10; // each grid is 200 mOhm - VoltageChart.setYDataFactor(0.01); } else { sCompressionFactor = 1; // each grid is 20 mOhm - VoltageChart.setYDataFactor(0.001); } VoltageChart.setDataColor(CHART_ESR_COLOR); +// VoltageChart.setYDataFactor(0.01); // is set for voltage } else { /* * Process Current @@ -3280,12 +3336,11 @@ void readAndProcessEEPROMData(bool aStoreValuesForDisplayAndAppend) { */ if (StartValues.initialDischargingMilliampere > 1200) { sCompressionFactor = 100; // each grid is 2000 mA - VoltageChart.setYDataFactor(0.1); } else { sCompressionFactor = 10; // each grid is 200 mA - VoltageChart.setYDataFactor(0.01); } VoltageChart.setDataColor(CHART_CURRENT_COLOR); +// VoltageChart.setYDataFactor(0.01); } #endif // defined(SUPPORT_BLUEDISPLAY_CHART) @@ -3331,8 +3386,7 @@ void readAndProcessEEPROMData(bool aStoreValuesForDisplayAndAppend) { /**************************************************** * Print the initial value and no caption to plotter ****************************************************/ - printValuesForPlotter(tVoltageMillivolt, tMilliampere, tMilliohm, false); - Serial.println(); + printValuesForPlotterAndChart(tVoltageMillivolt, tMilliampere, tMilliohm, false); /******************************************* * Loop to read and print all EEPROM values @@ -3343,7 +3397,7 @@ void readAndProcessEEPROMData(bool aStoreValuesForDisplayAndAppend) { tVoltageMillivolt += tEEPROMData.DeltaMillivolt; tMilliampere += tEEPROMData.DeltaMilliampere; tMilliohm += tEEPROMData.DeltaESRMilliohm; - tCapacityAccumulator += tMilliampere; // putting this into printValuesForPlotter() increases program size + tCapacityAccumulator += tMilliampere; // putting this into printValuesForPlotterAndChart() increases program size #if defined(LOCAL_TRACE) Serial.print(F("EEPROM values=")); @@ -3361,7 +3415,7 @@ void readAndProcessEEPROMData(bool aStoreValuesForDisplayAndAppend) { sMeasurementInfo.Voltages.Logger.MaximumMillivolt = tVoltageMillivolt; uint16_t tVoltageForPrint = tVoltageMillivolt; // To print markers for start and end of standard capacity #if !defined(SUPPRESS_SERIAL_PRINT) - uint8_t tPrintDelayed = 0; // to append text at values print output + uint8_t tPrintDelayed = 0; // to append text at values print output #endif if (!sOnlyLoggerFunctionality) { @@ -3377,10 +3431,10 @@ void readAndProcessEEPROMData(bool aStoreValuesForDisplayAndAppend) { tCapacityMilliampereHourStandardValueState = CAPACITY_STARTED; tCapacityAccumulatorUntilNominalFullVoltageValue = tCapacityAccumulator; #if !defined(SUPPRESS_SERIAL_PRINT) - tPrintDelayed = 1; // print text after print of values - if (sOnlyPlotterOutput) { - tVoltageForPrint += 50; // modify voltage before print of values - } + tPrintDelayed = 1; // print text after print of values + if (sOnlyPlotterOutput) { + tVoltageForPrint += 50; // modify voltage before print of values + } #endif } else if (tCapacityMilliampereHourStandardValueState == CAPACITY_STARTED @@ -3395,12 +3449,12 @@ void readAndProcessEEPROMData(bool aStoreValuesForDisplayAndAppend) { / tNumberOfEEPROMValuesPerHour; // -> tCapacityAccumulator / 60 } #if !defined(SUPPRESS_SERIAL_PRINT) - if (i != tFirstNonWrittenIndex - 1) { // do not modify last value line containing caption - tPrintDelayed = 2; // print text after print of values - if (sOnlyPlotterOutput) { - tVoltageForPrint += 50; // modify voltage before print of values - } + if (i != tFirstNonWrittenIndex - 1) { // do not modify last value line containing caption + tPrintDelayed = 2; // print text after print of values + if (sOnlyPlotterOutput) { + tVoltageForPrint += 50; // modify voltage before print of values } + } #endif } } // End of standard capacity handling @@ -3409,29 +3463,29 @@ void readAndProcessEEPROMData(bool aStoreValuesForDisplayAndAppend) { * Print (the second uncompressed) values * At last, print the caption with values from the end of the measurement cycle to plotter */ - printValuesForPlotter(tVoltageForPrint, tMilliampere, tMilliohm, i == (tFirstNonWrittenIndex - 1)); + printValuesForPlotterAndChart(tVoltageForPrint, tMilliampere, tMilliohm, i == (tFirstNonWrittenIndex - 1)); #if !defined(SUPPRESS_SERIAL_PRINT) - if (!sOnlyPlotterOutput) { - if (tPrintDelayed == 1) { - Serial.print(F(" - Capacity on top of standard value=")); - Serial.print(tCapacityAccumulator / tNumberOfEEPROMValuesPerHour); - Serial.print(F(" mAh")); - } else if (tPrintDelayed == 2) { - Serial.print(F(" - ")); - if (isStandardCapacityAvailable) { - Serial.print(F("Standard ")); - } - if (sOnlyLoggerFunctionality) { - Serial.print(F("capacity=")); - } else { - Serial.print(F("capacity at high cut off=")); - } - Serial.print(sStandardCapacityMilliampereHour); - Serial.print(F(" mAh")); + if (!sOnlyPlotterOutput) { + if (tPrintDelayed == 1) { + Serial.print(F(" - Capacity on top of standard value=")); + Serial.print(tCapacityAccumulator / tNumberOfEEPROMValuesPerHour); + Serial.print(F(" mAh")); + } else if (tPrintDelayed == 2) { + Serial.print(F(" - ")); + if (isStandardCapacityAvailable) { + Serial.print(F("Standard ")); + } + if (sOnlyLoggerFunctionality) { + Serial.print(F("capacity=")); + } else { + Serial.print(F("capacity at high cut off=")); } + Serial.print(sStandardCapacityMilliampereHour); + Serial.print(F(" mAh")); } - Serial.println(); + } + Serial.println(); #endif } // End of read loop @@ -3441,43 +3495,43 @@ void readAndProcessEEPROMData(bool aStoreValuesForDisplayAndAppend) { uint16_t tCurrentCapacityMilliampereHourComputed = tCapacityAccumulator / tNumberOfEEPROMValuesPerHour; #if !defined(SUPPRESS_SERIAL_PRINT) - if (!sOnlyPlotterOutput) { - if (sMeasurementInfo.CapacityMilliampereHour == 0) { - Serial.print(F("No capacity was stored, so use computed capacity of ")); - Serial.print(tCurrentCapacityMilliampereHourComputed); - } else { - /* - * The observed delta was around 1% :-) - */ - int16_t tCurrentCapacityMilliampereHourDelta = sMeasurementInfo.CapacityMilliampereHour - - tCurrentCapacityMilliampereHourComputed; - Serial.print(F("Stored minus computed capacity=")); - Serial.print(tCurrentCapacityMilliampereHourDelta); - } - Serial.println(F(" mAh")); - + if (!sOnlyPlotterOutput) { + if (sMeasurementInfo.CapacityMilliampereHour == 0) { + Serial.print(F("No capacity was stored, so use computed capacity of ")); + Serial.print(tCurrentCapacityMilliampereHourComputed); + } else { /* - * Print Standard capacity a between NominalFullVoltageMillivolt and CutoffVoltageMillivoltHigh, - * if we have both values. + * The observed delta was around 1% :-) */ - if (!sOnlyLoggerFunctionality && sStandardCapacityMilliampereHour != 0 - && sStandardCapacityMilliampereHour != tCurrentCapacityMilliampereHourComputed) { - if (isStandardCapacityAvailable) { - Serial.print(F("Standard ")); - } - Serial.print(F("computed capacity between ")); - if (isStandardCapacityAvailable) { - Serial.print(BatteryTypeInfoArray[sMeasurementInfo.BatteryTypeIndex].NominalFullVoltageMillivolt); - } else { - Serial.print(StartValues.initialDischargingMillivolt); - } - Serial.print(F(" mV and ")); - Serial.print(BatteryTypeInfoArray[sMeasurementInfo.BatteryTypeIndex].CutoffVoltageMillivoltHigh); - Serial.print(F(" mV=")); - Serial.print(sStandardCapacityMilliampereHour); - Serial.println(F(" mAh")); + int16_t tCurrentCapacityMilliampereHourDelta = sMeasurementInfo.CapacityMilliampereHour + - tCurrentCapacityMilliampereHourComputed; + Serial.print(F("Stored minus computed capacity=")); + Serial.print(tCurrentCapacityMilliampereHourDelta); + } + Serial.println(F(" mAh")); + + /* + * Print Standard capacity a between NominalFullVoltageMillivolt and CutoffVoltageMillivoltHigh, + * if we have both values. + */ + if (!sOnlyLoggerFunctionality && sStandardCapacityMilliampereHour != 0 + && sStandardCapacityMilliampereHour != tCurrentCapacityMilliampereHourComputed) { + if (isStandardCapacityAvailable) { + Serial.print(F("Standard ")); } - } // if (!sOnlyPlotterOutput) + Serial.print(F("computed capacity between ")); + if (isStandardCapacityAvailable) { + Serial.print(BatteryTypeInfoArray[sMeasurementInfo.BatteryTypeIndex].NominalFullVoltageMillivolt); + } else { + Serial.print(StartValues.initialDischargingMillivolt); + } + Serial.print(F(" mV and ")); + Serial.print(BatteryTypeInfoArray[sMeasurementInfo.BatteryTypeIndex].CutoffVoltageMillivoltHigh); + Serial.print(F(" mV=")); + Serial.print(sStandardCapacityMilliampereHour); + Serial.println(F(" mAh")); + } + } // if (!sOnlyPlotterOutput) #endif if (aStoreValuesForDisplayAndAppend) { @@ -3507,17 +3561,6 @@ void readAndProcessEEPROMData(bool aStoreValuesForDisplayAndAppend) { } #if defined(SUPPORT_BLUEDISPLAY_CHART) -/* - * This handler is called after boot or reconnect - */ -void connectHandler(void) { - initDisplay(); // does a clear(); - initBatteryChart(); - sCurrentBrightness = BRIGHTNESS_LOW; - doBrightness(0, 0); // from low to high :-) - drawButtons(); - readAndDrawEEPROMValues(false); // this calls clearAndDrawChart() at the end -} void doStartStop(BDButton *aTheTouchedButton, int16_t aValue) { (void) aTheTouchedButton; @@ -3537,13 +3580,11 @@ void doAppend(BDButton *aTheTouchedButton, int16_t aValue) { void doChartType(BDButton *aTheTouchedButton, int16_t aValue) { (void) aTheTouchedButton; sChartDisplayValueArrayType = aValue; - readAndDrawEEPROMValues(false); +// readAndDrawEEPROMValues(); Serial.println(F("Not yet implemented")); } -void doBrightness(BDButton *aTheTouchedButton, int16_t aValue) { - (void) aTheTouchedButton; - (void) aValue; +void changeBrightness() { if (sCurrentBrightness == BRIGHTNESS_HIGH) { // Set to dimmed background BlueDisplay1.setScreenBrightness(BD_SCREEN_BRIGHTNESS_MIN); @@ -3558,8 +3599,8 @@ void doBrightness(BDButton *aTheTouchedButton, int16_t aValue) { TouchButtonOnlyTextESR.setButtonColor(COLOR16_LIGHT_GREY); TouchButtonOnlyTextAmpere.setButtonColor(COLOR16_LIGHT_GREY); sCurrentBrightness = BRIGHTNESS_LOW; - redrawDisplay(); } else { + // (sCurrentBrightness == BRIGHTNESS_LOW) // Back to user brightness sBackgroundColor = COLOR16_WHITE; sTextColor = COLOR16_BLACK; @@ -3570,9 +3611,14 @@ void doBrightness(BDButton *aTheTouchedButton, int16_t aValue) { TouchButtonOnlyTextESR.setButtonColor(COLOR16_WHITE); TouchButtonOnlyTextAmpere.setButtonColor(COLOR16_WHITE); sCurrentBrightness = BRIGHTNESS_HIGH; - redrawDisplay(); } } +void doBrightness(BDButton *aTheTouchedButton, int16_t aValue) { + (void) aTheTouchedButton; + (void) aValue; + changeBrightness(); + redrawDisplay(); +} //void doRedrawChart(BDButton *aTheTouchedButton, int16_t aValue) { // (void) aTheTouchedButton; @@ -3592,29 +3638,38 @@ void doBatteryLogger(BDButton *aTheTouchedButton, int16_t aValue) { (void) aTheTouchedButton; (void) aValue; sOnlyLoggerFunctionality = !sOnlyLoggerFunctionality; + sCurrentLoadResistorAverage = LOGGER_SHUNT_RESISTOR_MILLIOHM; setBatteryLoggerButtonText(true); } void setCutoffHighLowZeroButtonText(bool doDrawButton) { - if (sMeasurementInfo.CutoffLevel == CUTOFF_LEVEL_ZERO) { - TouchButtonCutoffHighLowZero.setText(F("Cutoff 50mV"), doDrawButton); - - } else if (sMeasurementInfo.BatteryTypeIndex == TYPE_INDEX_NO_BATTERY) { - if (sMeasurementInfo.CutoffLevel == CUTOFF_LEVEL_HIGH) { - TouchButtonCutoffHighLowZero.setText(F("Cutoff High"), doDrawButton); - } else { - TouchButtonCutoffHighLowZero.setText(F("Cutoff Low"), doDrawButton); - - } - } else { + if (BlueDisplay1.isConnectionEstablished()) { char tString[20]; - uint16_t tCutoffVoltageMillivolt; - if (sMeasurementInfo.CutoffLevel == CUTOFF_LEVEL_HIGH) { - tCutoffVoltageMillivolt = BatteryTypeInfoArray[sMeasurementInfo.BatteryTypeIndex].CutoffVoltageMillivoltHigh; + if (sOnlyLoggerFunctionality) { + // CUTOFF_LEVEL_HIGH = 50%, LOW = 25% and ZERO = 12.5% + snprintf_P(tString, sizeof(tString), PSTR("Cutoff %2u%% I"), 100 >> (sMeasurementInfo.CutoffLevel + 1)); } else { - tCutoffVoltageMillivolt = BatteryTypeInfoArray[sMeasurementInfo.BatteryTypeIndex].CutoffVoltageMillivoltLow; + if (sMeasurementInfo.BatteryTypeIndex == TYPE_INDEX_NO_BATTERY) { + if (sMeasurementInfo.CutoffLevel == CUTOFF_LEVEL_HIGH) { + TouchButtonCutoffHighLowZero.setText(F("Cutoff High"), doDrawButton); +// strcpy_P(tString, PSTR("Cutoff Low")); // 20 bytes more + } else { + TouchButtonCutoffHighLowZero.setText(F("Cutoff Low"), doDrawButton); +// strcpy_P(tString, PSTR("Cutoff Low")); + } + return; + } else { + uint16_t tCutoffVoltageMillivolt; + if (sMeasurementInfo.CutoffLevel == CUTOFF_LEVEL_ZERO) { + tCutoffVoltageMillivolt = 50; + } else if (sMeasurementInfo.CutoffLevel == CUTOFF_LEVEL_HIGH) { + tCutoffVoltageMillivolt = BatteryTypeInfoArray[sMeasurementInfo.BatteryTypeIndex].CutoffVoltageMillivoltHigh; + } else { + tCutoffVoltageMillivolt = BatteryTypeInfoArray[sMeasurementInfo.BatteryTypeIndex].CutoffVoltageMillivoltLow; + } + snprintf_P(tString, sizeof(tString), PSTR("Cutoff %4umV"), tCutoffVoltageMillivolt); + } } - snprintf_P(tString, sizeof(tString), PSTR("Cutoff %4umV"), tCutoffVoltageMillivolt); TouchButtonCutoffHighLowZero.setText(tString, doDrawButton); } } @@ -3630,16 +3685,32 @@ void doCutoffHighLowZero(BDButton *aTheTouchedButton, int16_t aValue) { printCutoff(); } +/* + * This handler is called after boot or reconnect + */ +void connectHandler(void) { + Serial.println(F("connectHandler")); + initDisplay(); // does a clear(); + initBatteryChart(); + sCurrentBrightness = BRIGHTNESS_LOW; + changeBrightness(); // from low to high / user defined :-) + redrawDisplay(); +} + void redrawDisplay(void) { + Serial.println(F("redrawDisplay")); BlueDisplay1.clearDisplay(sBackgroundColor); clearLastDiplayedValues(); drawButtons(); - readAndDrawEEPROMValues(false); // draws the text buttons + readAndDrawEEPROMValues(); // draws the text buttons and calls clearAndDrawChart() at the end } void initDisplay(void) { #if defined(LOCAL_DEBUG) - Serial.println(F("InitDisplay")); + Serial.print(F("InitDisplay: Host W x H=")); + Serial.print(BlueDisplay1.getHostDisplayWidth()); + Serial.print(F(" x ")); + Serial.println(BlueDisplay1.getHostDisplayHeight()); #endif uint16_t tDisplayHeight = (DISPLAY_WIDTH * BlueDisplay1.getHostDisplayHeight()) / BlueDisplay1.getHostDisplayWidth(); @@ -3724,6 +3795,10 @@ void initDisplay(void) { tBDButtonPGMParameterStruct.aPGMText = F("Ampere"); TouchButtonOnlyTextAmpere.init(&tBDButtonPGMParameterStruct); TouchButtonOnlyTextAmpere.setButtonTextColor(CHART_CURRENT_COLOR); + + // Settings for Text messages + BlueDisplay1.setWriteStringPosition(VALUES_POSITION_X, MESSAGE_START_POSITION_Y); + BlueDisplay1.setWriteStringSizeAndColorAndFlag(BASE_TEXT_SIZE, sTextColor, sBackgroundColor, false); } void initBatteryChart() { @@ -3740,7 +3815,7 @@ void initBatteryChart() { VoltageChart.setXDataScaleFactor(CHART_MAXIMUM_X_SCALE_FACTOR); // Label increment is 30 min for scale factor 1 so we have 5:37 for complete chart - VoltageChart.initXLabel(0, CHART_MINUTES_PER_X_LABEL, CHART_X_AXIS_SCALE_FACTOR_1, 3, 0); + VoltageChart.initXLabel(0, CHART_MINUTES_PER_X_LABEL_UNCOMPRESSED, CHART_X_AXIS_SCALE_FACTOR_1, 3, 0); VoltageChart.setXLabelDistance(2); // normal x scale, 1 is default VoltageChart.setLabelStringFunction(VoltageChart.convertMinutesToString); @@ -3750,16 +3825,16 @@ void initBatteryChart() { // Grid spacing is CHART_WIDTH / 8 -> 8 columns and height (261) / 6 for 5 lines from 400 to 1400 VoltageChart.initChart(CHART_START_X, BlueDisplay1.getRequestedDisplayHeight() - (BASE_TEXT_SIZE + (BASE_TEXT_SIZE / 2)), - CHART_WIDTH, tChartHeight, CHART_AXES_SIZE, BASE_TEXT_SIZE, CHART_DISPLAY_GRID, CHART_MINUTES_PER_X_LABEL, tYGridSize); + CHART_WIDTH, tChartHeight, CHART_AXES_SIZE, BASE_TEXT_SIZE, CHART_DISPLAY_GRID, CHART_MINUTES_PER_X_LABEL_UNCOMPRESSED, + tYGridSize); VoltageChart.initChartColors(CHART_VOLTAGE_COLOR, CHART_AXES_COLOR, CHART_GRID_COLOR, sTextColor, sTextColor, sBackgroundColor); ResistanceAndCurrentChart = VoltageChart; // 0.5 volt per grid, factor is 0.1 for an input of 10 for 1 Volt - VoltageChart.initYLabelFloat(0.0, 0.5, 0.1, 3, 1); // 0.5 volt per grid, 10 for input 1 (Volt) , "3.5" as label - - ResistanceAndCurrentChart.initYLabelInt(0.0, 200, 1, 4); // 200 mA or mOhm per grid, "1200" as label + VoltageChart.initYLabel(0.0, 0.5, 0.1, 3, 1); // 0.5 volt per grid, 10 for input 1 (Volt) , "3.5" as label + ResistanceAndCurrentChart.initYLabel(0.0, 200, 1, 4, 0); // 200 mA or mOhm per grid, "1200" as label } void clearAndDrawChart() { @@ -3767,7 +3842,6 @@ void clearAndDrawChart() { VoltageChart.drawXAxisAndLabels(); VoltageChart.drawYAxisAndLabels(); VoltageChart.drawGrid(); - /* * Text buttons are overwritten by chart */ @@ -3779,8 +3853,10 @@ void clearAndDrawChart() { */ void drawTextButtons() { TouchButtonOnlyTextVolt.drawButton(); - TouchButtonOnlyTextESR.drawButton(); TouchButtonOnlyTextAmpere.drawButton(); + if (!sOnlyLoggerFunctionality) { + TouchButtonOnlyTextESR.drawButton(); + } } void drawButtons() { @@ -3798,14 +3874,16 @@ void clearValueArea() { BlueDisplay1.fillRectRel(0, 0, BlueDisplay1.getRequestedDisplayWidth(), MESSAGE_START_POSITION_Y - 1, sBackgroundColor); } -void readAndDrawEEPROMValues(bool aStoreValuesForDisplayAndAppend) { +void readAndDrawEEPROMValues() { // VoltageChart.clear(); sChartReadValueArrayType = TYPE_VOLTAGE; // handle voltages in next call readAndProcessEEPROMData(false); + _delay(HELPFUL_DELAY_BETWEEN_DRAWING_CHART_LINES_TO_STABILIZE_USB_CONNECTION); sChartReadValueArrayType = TYPE_ESR; // handle ESR in next call readAndProcessEEPROMData(false); - sChartReadValueArrayType = TYPE_CURRENT; // handle ESR in next call - readAndProcessEEPROMData(aStoreValuesForDisplayAndAppend); + _delay(HELPFUL_DELAY_BETWEEN_DRAWING_CHART_LINES_TO_STABILIZE_USB_CONNECTION); + sChartReadValueArrayType = TYPE_CURRENT; // handle current in next call + readAndProcessEEPROMData(sMeasurementState == STATE_SETUP_AND_READ_EEPROM); // StoreValuesForDisplayAndAppend if we are after boot if (ValuesForDeltaStorage.DeltaArrayIndex > 0) { printChartValues(); } @@ -3824,15 +3902,6 @@ void printCapacityValue() { sBackgroundColor); } -/* - * Is called each time counter changes - */ -void printCounter(uint16_t tNumbersOfInitialSamplesToGo) { - char tString[4]; - snprintf_P(tString, sizeof(tString), PSTR("%3u"), tNumbersOfInitialSamplesToGo); - BlueDisplay1.drawText(DISPLAY_WIDTH - BASE_TEXT_SIZE * 3, BASE_TEXT_SIZE * 2, tString, BASE_TEXT_SIZE, COLOR16_RED, - sBackgroundColor); -} /* * Print all chart related values at the lower right @@ -3849,8 +3918,8 @@ void printChartValues() { printCapacityValue(); // Samples use 5u to have the same spacing as mAh - snprintf(tStringBuffer, sizeof(tStringBuffer), "%5u Samples %u m", ValuesForDeltaStorage.DeltaArrayIndex + 1, - StartValues.NumberOfSecondsPerStorage); // Samples + start sample + snprintf(tStringBuffer, sizeof(tStringBuffer), "%5u Samples %u mn", ValuesForDeltaStorage.DeltaArrayIndex + 1, + StartValues.NumberOfSecondsPerStorage / 60); // Samples + start sample tYPosition += 2 * BASE_TEXT_SIZE; BlueDisplay1.drawText(CHART_VALUES_POSITION_X, tYPosition, tStringBuffer, BASE_TEXT_SIZE, sTextColor, sBackgroundColor); @@ -3891,3 +3960,47 @@ void printChartValues() { } #endif // defined(SUPPORT_BLUEDISPLAY_CHART) + +/* + * Version 5.0 - 2/2024 + * - Compression is now done by simply doubling the sampling period, which results in reducing the resolution from 336 of 168 samples directly after compression. + * - Data and chart can be displayed on (old) tablets or mobile running the BlueDisplay app https://github.com/ArminJo/Arduino-BlueDisplay. + * - Plotter pin logic does not depend any more on USB powering. + * - Tested logger function with chart. + * + * Version 4.0 - 2/2024 + * - Use capacity between NominalFullVoltageMillivolt and CutoffVoltageMillivoltHigh as standard capacity to enable better comparison. + * - If powered by USB plotter pin logic is reversed, i.e. plotter output is enabled if NOT connected to ground. + * - In state detecting battery, you can toggle cut off voltage between high, low and zero (0.1 V) with stop button. + * - Fix bug for appending to compressed data. + * - Synchronizing of LCD access for button handler, avoiding corrupted display content. + * - Print improvements. + * - Support for storage period of 120 s. + * - Compression improved for rapidly descending voltage. + * - Moving seldom used function of pin 10 to pin A5. + * - New Logger mode with separate shunt enabled by pin 10. + * - Store data in an array of structure instead in 3 arrays. + * + * Version 3.2.1 - 11/2023 + * BUTTON_IS_ACTIVE_HIGH is not default any more + * Version 3.2 - 10/2023 + * Cut off LCD message improved + * Version 3.1 - 3/2023 + * Fixed "conversion does not clear rest of EEPROM" bug + * Version 3.0 - 12/2022 + * Improved compression + * Version 2.3 - 10/2022 + * Increase no load settle time especially for NiMh batteries + * Attention tones + * Version 2.2 - 8/2022 + * ESR > 64 bug fixed. + * Display of changes on pin CUTOFF_LEVEL_PIN + * Version 2.1 - 3/2022 + * ESR is stored. + * Version 2.0 - 3/2022 + * Improved version. + * Version 1.0 - 9/2021 + * Tested version. + * Version 0.0 - 9/2021 + * Initial version. + */ diff --git a/UltimateBatteryTester/digitalWriteFast.h b/UltimateBatteryTester/digitalWriteFast.h index a36a71f..f745081 100644 --- a/UltimateBatteryTester/digitalWriteFast.h +++ b/UltimateBatteryTester/digitalWriteFast.h @@ -324,18 +324,22 @@ int NonConstantsUsedForDigitalReadFast( void ) __attribute__ (( error("Paramete # if (defined(__AVR__) || defined(ARDUINO_ARCH_AVR)) && defined(__digitalPinToPortReg) # if defined(THROW_ERROR_IF_NOT_FAST) #define digitalWriteFast(P, V) \ -if (__builtin_constant_p(P)) { \ - BIT_WRITE(*__digitalPinToPortReg(P), __digitalPinToBit(P), (V)); \ -} else { \ +do { \ + if (__builtin_constant_p(P)) { \ + BIT_WRITE(*__digitalPinToPortReg(P), __digitalPinToBit(P), (V)); \ + } else { \ NonConstantsUsedForDigitalWriteFast(); \ -} + } \ +} while (0) # else #define digitalWriteFast(P, V) \ -if (__builtin_constant_p(P)) { \ - BIT_WRITE(*__digitalPinToPortReg(P), __digitalPinToBit(P), (V)); \ -} else { \ - digitalWrite((P), (V)); \ -} +do { \ + if (__builtin_constant_p(P)) { \ + BIT_WRITE(*__digitalPinToPortReg(P), __digitalPinToBit(P), (V)); \ + } else { \ + digitalWrite((P), (V)); \ + } \ +} while (0) # endif // defined(THROW_ERROR_IF_NOT_FAST) # else #define digitalWriteFast digitalWrite @@ -346,28 +350,32 @@ if (__builtin_constant_p(P)) { \ # if (defined(__AVR__) || defined(ARDUINO_ARCH_AVR)) && defined(__digitalPinToPortReg) # if defined(THROW_ERROR_IF_NOT_FAST) #define pinModeFast(P, V) \ -if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \ - if (V == INPUT_PULLUP) {\ - BIT_CLEAR(*__digitalPinToDDRReg(P), __digitalPinToBit(P)); \ - BIT_SET(*__digitalPinToPortReg(P), __digitalPinToBit(P)); \ +do { \ + if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \ + if (V == INPUT_PULLUP) {\ + BIT_CLEAR(*__digitalPinToDDRReg(P), __digitalPinToBit(P)); \ + BIT_SET(*__digitalPinToPortReg(P), __digitalPinToBit(P)); \ + } else { \ + BIT_WRITE(*__digitalPinToDDRReg(P), __digitalPinToBit(P), (V)); \ + } \ } else { \ - BIT_WRITE(*__digitalPinToDDRReg(P), __digitalPinToBit(P), (V)); \ - } \ -} else { \ NonConstantsUsedForPinModeFast(); \ -} + } \ +} while (0) # else #define pinModeFast(P, V) \ -if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \ - if (V == INPUT_PULLUP) {\ - BIT_CLEAR(*__digitalPinToDDRReg(P), __digitalPinToBit(P)); \ - BIT_SET(*__digitalPinToPortReg(P), __digitalPinToBit(P)); \ +do { \ + if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \ + if (V == INPUT_PULLUP) {\ + BIT_CLEAR(*__digitalPinToDDRReg(P), __digitalPinToBit(P)); \ + BIT_SET(*__digitalPinToPortReg(P), __digitalPinToBit(P)); \ + } else { \ + BIT_WRITE(*__digitalPinToDDRReg(P), __digitalPinToBit(P), (V)); \ + } \ } else { \ - BIT_WRITE(*__digitalPinToDDRReg(P), __digitalPinToBit(P), (V)); \ + pinMode((P), (V)); \ } \ -} else { \ - pinMode((P), (V)); \ -} +} while (0) # endif // defined(THROW_ERROR_IF_NOT_FAST) # else #define pinModeFast pinMode @@ -379,17 +387,11 @@ if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \ # if defined(THROW_ERROR_IF_NOT_FAST) #define digitalReadFast(P) ( (int) __digitalReadFast((P)) ) // since we have return values, it is easier to implement it by ?: -#define __digitalReadFast(P ) \ - (__builtin_constant_p(P) ) ? \ - (( BIT_READ(*__digitalPinToPINReg(P), __digitalPinToBit(P))) ? HIGH:LOW ) : \ - NonConstantsUsedForDigitalReadFast() +#define __digitalReadFast(P ) ( (__builtin_constant_p(P) ) ? (( BIT_READ(*__digitalPinToPINReg(P), __digitalPinToBit(P))) ? HIGH:LOW ) : NonConstantsUsedForDigitalReadFast() ) # else #define digitalReadFast(P) ( (int) __digitalReadFast((P)) ) // since we have return values, it is easier to implement it by ?: -#define __digitalReadFast(P ) \ - (__builtin_constant_p(P) ) ? \ - (( BIT_READ(*__digitalPinToPINReg(P), __digitalPinToBit(P))) ? HIGH:LOW ) : \ - digitalRead((P)) +#define __digitalReadFast(P ) ( (__builtin_constant_p(P) ) ? (( BIT_READ(*__digitalPinToPINReg(P), __digitalPinToBit(P))) ? HIGH:LOW ) : digitalRead((P)) ) # endif // defined(THROW_ERROR_IF_NOT_FAST) # else #define digitalReadFast digitalRead @@ -400,18 +402,22 @@ if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \ # if (defined(__AVR__) || defined(ARDUINO_ARCH_AVR)) && defined(__digitalPinToPINReg) # if defined(THROW_ERROR_IF_NOT_FAST) #define digitalToggleFast(P) \ -if (__builtin_constant_p(P)) { \ - BIT_SET(*__digitalPinToPINReg(P), __digitalPinToBit(P)); \ -} else { \ +do { \ + if (__builtin_constant_p(P)) { \ + BIT_SET(*__digitalPinToPINReg(P), __digitalPinToBit(P)); \ + } else { \ NonConstantsUsedForDigitalToggleFast(); \ -} + } \ +} while (0) # else #define digitalToggleFast(P) \ -if (__builtin_constant_p(P)) { \ - BIT_SET(*__digitalPinToPINReg(P), __digitalPinToBit(P)); \ -} else { \ - digitalWrite(P, ! digitalRead(P)); \ -} +do { \ + if (__builtin_constant_p(P)) { \ + BIT_SET(*__digitalPinToPINReg(P), __digitalPinToBit(P)); \ + } else { \ + digitalWrite(P, ! digitalRead(P)); \ + } \ +} while (0) # endif // defined(THROW_ERROR_IF_NOT_FAST) # else #define digitalToggleFast(P) digitalWrite(P, ! digitalRead(P))