Hooks into the CE pin of TP4056 to add some extra features - overvolt cutoff - overtime cutoff - overtemperature cutoff (by use of MCP9808) - info on little OLED screen - battery presence detection Future features - current detection and cutoff (waiting for INA219 breakout for this) - Runtime configurable parameters by serial - Send stats over serial to desktop application Known flaws - see readme Circuit schematic coming soon (tm), see readme Designed and tested for nucleo F303RE but should be easily adaptable to any board. License: GPL v3
Dependencies: OLED_SSD1306 MCP9808
Diff: main.cpp
- Revision:
- 5:c07438005b16
- Parent:
- 4:f63aab9fec77
- Child:
- 8:c781f4ae7eb3
--- a/main.cpp Wed Aug 26 04:56:21 2020 +0000 +++ b/main.cpp Thu Aug 27 06:49:31 2020 +0000 @@ -6,7 +6,6 @@ #include <sstream> #include "SSD1306I2C.h" -//#include "DS1820.h" #include "MCP9808.h" #define OLED_ADR 0x3C @@ -16,7 +15,7 @@ #define MCP9808_SDA PB_5 #define MCP9808_SCL PA_8 -#define DS18B20_DATA D3 +#define BAT_SAMPLERATE 1000 // Blinking rate in milliseconds @@ -41,6 +40,7 @@ DigitalInOut toggleOut(PA_14, PIN_OUTPUT, OpenDrainNoPull, 0); bool TP4056ChargingState = false; //reflects internal state of TP4056 control thread +uint64_t chargingTimePassed = 0; //reflects charge time recorded by control thread InterruptIn chargeButton(USER_BUTTON); volatile bool buttonPressed = false; //set to true by button ISR, set to false when acknowledged @@ -54,10 +54,10 @@ float ds18b20_temperature = -301.0; //Configurable protection conditions -float MAX_VOLTAGE = 4.25; //default: no more than 4.25v +float MAX_VOLTAGE = 4.23; //default: no more than 4.25v float MIN_VOLTAGE = 2.5; //default: no less than 2.5v float MIN_DETECT_VOLTAGE = 0.5; //default: no less than 0.3v -float MAX_TEMPERATURE = 40.0; //default: no more than 40C +float MAX_TEMPERATURE = 35.0; //default: no more than 35C at the temperature sensor float MIN_TEMPERATURE = 10.0; //default: no less than 10C uint64_t MAX_TIME_ms = 3600000; //default: no more than 1h @@ -146,51 +146,7 @@ // Reads DS18B20 sense temperature void TemperatureInputThread() -{ - int ds18b20_result = 0; - float temp; - - /* - //Initialize sensor - if(ds1820_sensor.begin()) { - ds1820_sensor.startConversion(); // start temperature conversion - ThisThread::sleep_for(1); // let DS1820 complete the temperature conversion - pc.printf("Initial DS18B20 temp = %3.2f\r\n", ds1820_sensor.read()); // read temperature - //Without temperature sensor, charging will not work (unless minimum temperature set below -310.0) - }else{ - pc.printf("No DS1820 sensor found!\r\n"); - } - */ - /* - ds18b20_result = ds1820_sensor.convertTemperature(true, DS1820::all_devices); //Start temperature conversion, wait until ready - //printf("It is %3.1foC\r\n", probe.temperature()); - - ds18b20_temperature = ds1820_sensor.temperature(); - */ - - /* - ds1820_sensor.startConversion(); // start temperature conversion from analog to digital - // let DS1820 complete the temperature conversion - ThisThread::sleep_for(1.0); - ds18b20_result = ds1820_sensor.read(temp); // read temperature from DS1820 and perform cyclic redundancy check (CRC) - - switch (ds18b20_result) { - case 0: // no errors -> 'temp' contains the value of measured temperature - //pc.printf("temp = %3.4f%cC\r\n", temp, 176); - ds18b20_temperature = temp; - break; - - case 1: // no sensor present -> 'temp' is not updated - //pc.printf("no sensor present\n\r"); - ds18b20_temperature = -302.0; - break; - - case 2: // CRC error -> 'temp' is not updated - //pc.printf("CRC error\r\n"); - ds18b20_temperature = -303.0; - } - */ - +{ MCP9808::MCP9808_status_t aux; MCP9808::MCP9808_config_reg_t myMCP9808_Config; MCP9808::MCP9808_data_t myMCP9808_Data; @@ -279,12 +235,12 @@ bool temperature_en = true; //override as sensor code is bugged bool voltage_en = ( ( bat_voltage_avg < MAX_VOLTAGE ) && ( bat_voltage_avg > MIN_VOLTAGE ) ); bool presence_en = batteryPresenceState; - bool charge_time_exceeded = ( (Kernel::get_ms_count() - chargeStartTime ) > MAX_TIME_ms); + bool charge_time_exceeded = ( chargingTimePassed > MAX_TIME_ms ); //Charging can be enabled if battery is present, no protections triggered, and user starts the charge if(enableCharging == false) { - if( voltage_en && temperature_en && presence_en && !charge_time_exceeded && buttonPressed ) + if( voltage_en && temperature_en && presence_en && buttonPressed ) { enableCharging = true; chargeStartTime = Kernel::get_ms_count(); @@ -292,6 +248,8 @@ } } //Charging must be stopped if overvoltage, overtemperature, overtime, battery removed, or user pushes button + //Charging time passed is maintained, but will be reset if user starts charge again + //TODO: Only reset charge time once battery removed? else { //Disable charging if any protection condition is triggered @@ -305,17 +263,21 @@ enableCharging = false; buttonPressed = false; } + } //With charge state calculated, realize it on the CE pin - //Allow pullup to 5V to enable charging at CE pin + //Allow pullup to '5V' to enable charging at CE pin if(enableCharging) { + //Using HiZ still results in internal clamping diode activating, resulting in 3.6v toggleOut.write(1); //open drain mode -> HiZ - chargeStartTime = Kernel::get_ms_count(); + + //Continually update surpassed charging time if it is enabled + chargingTimePassed = Kernel::get_ms_count() - chargeStartTime ; } - //To disable charging, open drain the CE pin + //To disable charging, open drain the CE pin -> 0V else { toggleOut.write(0); //open drain, pull to GND -> overpull 470k pullup that brings 5V to CE @@ -330,33 +292,72 @@ void oledOutputThread() { + std::stringstream volts_stream, max_volts_stream; + std::stringstream temperature_stream; + std::stringstream chargetimepassed_stream; + std::stringstream chargetimemax_stream; + while(true) - { + { + volts_stream.str(""); + volts_stream << std::fixed << std::setprecision(2) << bat_voltage_avg; + + max_volts_stream.str(""); + max_volts_stream << std::fixed << std::setprecision(2) << MAX_VOLTAGE; + + temperature_stream.str(""); + temperature_stream << std::fixed << std::setprecision(2) << ds18b20_temperature; + + chargetimepassed_stream.str(""); + chargetimepassed_stream << std::fixed << std::setprecision(1) << (float(chargingTimePassed)/1000/60); + + chargetimemax_stream.str(""); + chargetimemax_stream << std::fixed << std::setprecision(1) << (float(MAX_TIME_ms)/1000/60); + + std::string str_temp = ""; + + //clear the screen first oled_i2c.clear(); - std::stringstream stream; - stream << std::fixed << std::setprecision(4) << bat_voltage_avg; - //std::string voltage_output = "Voltage: "+std::to_string(bat_voltage); - std::string voltage_output = "Voltage: " + stream.str(); - oled_i2c.drawString(0, 0, voltage_output.c_str() ); - - if(TP4056ChargingState){ - oled_i2c.drawString(0, 16, "CE: Enabled"); - } else { - oled_i2c.drawString(0, 16, "CE: Disabled"); + //If no battery present, show screen indicating one should be inserted + if( !batteryPresenceState ) + { + oled_i2c.setFont(ArialMT_Plain_16); + oled_i2c.drawString(0, 0, "Insert battery"); + + str_temp = volts_stream.str() + "v " + temperature_stream.str() + "ºC"; + oled_i2c.drawString(0,16, str_temp.c_str() ); + } + //battery present, show screen with voltage, CE status, temperature + //Further splits into charge enabled or disabled - if charge enabled, show time so far and time limit + else + { + oled_i2c.setFont(ArialMT_Plain_16); + if(TP4056ChargingState){ + str_temp = "Charge to " + max_volts_stream.str(); + oled_i2c.drawString(0, 0, str_temp.c_str()); + //TODO: shows 'charge complete' if above threshold + } else { + oled_i2c.drawString(0, 0, "Push to charge"); + } + + str_temp = volts_stream.str() + "v " + temperature_stream.str() + "ºC"; + oled_i2c.drawString(0, 16, str_temp.c_str() ); + + str_temp = "T: " + chargetimepassed_stream.str() + "m/" + chargetimemax_stream.str() + "m"; + oled_i2c.drawString(0,16*2, str_temp.c_str() ); + + + //std::string voltage_output = "Voltage: "+std::to_string(bat_voltage); + //str_temp = "Voltage: " + volts_stream.str(); + ///oled_i2c.drawString(0, 16, str_temp.c_str() ); + //str_temp = "Temp: " + temperature_stream.str(); + //oled_i2c.drawString(0,16*2, str_temp.c_str() ); + + } + oled_i2c.display(); - - stream.str(""); - stream << std::fixed << std::setprecision(4) << ds18b20_temperature; - std::string ds18_str = "Temp: " + stream.str(); - oled_i2c.drawString(0,16*2, ds18_str.c_str() ); - //stream << std::fixed << std::setprecision(4) << vddref_voltage; - //std::string vddref_output = "VDDREF: " + stream.str(); - //oled_i2c.drawString(0,16*2, vddref_output.c_str() ); - - - oled_i2c.display(); ThisThread::sleep_for(250); } @@ -418,7 +419,7 @@ bat_voltage_avg_sum = 0.0; lastADCStartTime = Kernel::get_ms_count(); - for(int i = 0; i < 1000; i++) + for(int i = 0; i < BAT_SAMPLERATE; i++) { bat_voltage = readVoltageDivider_Calibrated(); vddref_voltage = readRefVoltage(); @@ -432,12 +433,16 @@ bat_voltage_max = bat_voltage; } - ThisThread::sleep_for(1); + //sleep appropriate interval to meet specified sample rate, evenly spaced over 1s + //This sleep is also where the other threads run + ThisThread::sleep_for(1000 / BAT_SAMPLERATE); + } now = Kernel::get_ms_count(); - bat_voltage_avg = bat_voltage_avg_sum/1000.0; + bat_voltage_avg = bat_voltage_avg_sum / BAT_SAMPLERATE; + //TODO: Dedicated serial thread, output messages when conditions change (eg OTP, OVP, charge started/stopped) pc.printf("\r\nADC0 Reading: %6.4f\r\n", bat_voltage); pc.printf("ADC0 1s min: %6.4f\r\n", bat_voltage_min); pc.printf("ADC0 1s max: %6.4f\r\n", bat_voltage_max); @@ -446,7 +451,7 @@ pc.printf("VDDREF Reading: %6.4f\r\n", vddref_voltage); pc.printf("Temp: %6.4f\r\n", ds18b20_temperature); pc.printf("Bat present: %d\r\n", batteryPresenceState); - + } } \ No newline at end of file