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

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