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:
0:56122e281547
Child:
1:7749656733dd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Mon Aug 24 04:09:04 2020 +0000
@@ -0,0 +1,303 @@
+#include "mbed.h"
+#include "platform/mbed_thread.h"
+
+#include <string>
+#include <iomanip>
+#include <sstream>
+
+#include "SSD1306I2C.h"
+
+#define OLED_ADR 0x3C
+#define OLED_SDA I2C_SDA
+#define OLED_SCL I2C_SCL
+
+
+// Blinking rate in milliseconds
+#define BLINKING_RATE_MS                                                    5000
+
+#define VOLTAGE_DIVIDER_R1              99800
+#define VOLTAGE_DIVIDER_R2              99100
+
+Serial pc(USBTX,USBRX);
+
+//DigitalIn mybutton(USER_BUTTON);
+AnalogIn   divider_analogin(A0);
+AnalogIn    vref(ADC_VREF);
+
+//AnalogIn adc_refint(VREF_INT);
+
+DigitalInOut toggleOut(PA_14, PIN_OUTPUT, OpenDrainNoPull, 0);
+bool enableChargingState = false; //reflects internal state of TP4056 control thread
+
+InterruptIn chargeButton(USER_BUTTON);
+bool buttonPressed = false; //set to true by button ISR, set to false when acknowledged
+
+SSD1306I2C oled_i2c(OLED_ADR, OLED_SDA, OLED_SCL);
+
+float bat_voltage = -1.11;
+float bat_voltage_avg = -1.11;
+float vddref_voltage = -1.11;
+
+
+//Configurable protection conditions
+float MAX_VOLTAGE = 4.25; //default: no more than 4.25v
+float MIN_VOLTAGE = 2.5; //default: no less than 2.5v
+float MAX_TEMPERATURE = 40.0; //default: no more than 40C
+float MIN_TEMPERATURE = 10.0; //default: no less than 10C
+uint64_t MAX_TIME_ms = 3600000; //default: no more than 1h
+
+
+void blinkled()
+{
+    // Initialise the digital pin LED1 as an output
+    DigitalOut led(LED1);
+    
+    while (true) {
+        led = !led;
+        //thread_sleep_for(BLINKING_RATE_MS);
+        ThisThread::sleep_for(BLINKING_RATE_MS);
+    }
+}
+
+//TODO: make this an ISR that causes an immediate read of the analog pin
+void pa14_toggle()
+{
+
+}
+
+// Button to initiate / stop charging
+// Called on button rise
+// Set to true to signal to EN control thread that button was pressed
+void buttonISR
+{
+    uint64_t now = Kernel::get_ms_count();
+    uint64_t lastButtonPushTime = now;
+    const uint64_t buttonMinimumWaitTime = 500; //minimum 500 ms wait between button pushes
+    
+    if(Kernel::get_ms_count() - lastButtonPushTime > buttonMinimumWaitTime)
+    {
+        buttonPressed = true;
+    }
+       
+}
+
+float readRefVoltage()
+{
+    double      vdd;
+    double      vdd_calibed;
+    double      vref_calibed;
+    double      vref_f;
+    uint16_t    vref_u16;
+    uint16_t    vref_cal;
+    
+    vref_cal= *((uint16_t*)VREFINT_CAL_ADDR); //F303RE
+
+    vref_u16 = vref.read_u16();
+    //1.22 comes from 3.3 * 1524 / 4095 - voltage at calibration time times calibration measurement divided by maximum counts
+    //vdd = 1.228132 / vref.read();
+    vdd = 3.3 * (double)vref_cal / 4095.0 / vref.read();
+    
+    return vdd;  
+}
+
+//Reads voltage divider, and uses internal calibrated reference voltage to calculate real voltage
+//Not 100% accurate, but better than assuming 3.3v
+float readVoltageDivider_Calibrated()
+{
+    uint16_t vref_cal= *((uint16_t*)VREFINT_CAL_ADDR); //factory calibration value for 3.3v
+    uint16_t vref_u16 = vref.read_u16(); //read the internal voltage calibration
+    float vdd = 3.3 * (double)vref_cal / 4095.0 / vref.read();
+    
+    //ain.read() returns float value between 0 and 1
+    float reading = divider_analogin.read();
+    
+    //pc.printf("raw reading: %f\r\n", reading);
+    
+    return reading * vdd * (VOLTAGE_DIVIDER_R1 + VOLTAGE_DIVIDER_R2) / VOLTAGE_DIVIDER_R2;
+    
+}
+
+//Measurement assuming 3.3v - for comparison to calibrated reading only
+//Can be very inaccurate as nucleo voltage regulator drops as far as 3.2v
+float readVoltageDivider_3v3()
+{
+    uint16_t vref_cal= *((uint16_t*)VREFINT_CAL_ADDR); //factory calibration value for 3.3v
+    uint16_t vref_u16 = vref.read_u16(); //read the internal voltage calibration
+    float vdd = 3.3 * (double)vref_cal / 4095.0 / vref.read();
+    
+    //ain.read() returns float value between 0 and 1
+    float reading = divider_analogin.read();
+    
+    return reading * vdd * (VOLTAGE_DIVIDER_R1 + VOLTAGE_DIVIDER_R2) / VOLTAGE_DIVIDER_R2;
+    
+}
+
+// Reads DS18B20 sense temperature
+void TemperatureInputThread()
+{
+    while(true)
+    {
+        
+        
+        ThisThread::sleep_for(250);
+
+    }
+}
+
+// Detect battery presence by voltage activity
+bool batteryPresent()
+{
+    if(bat_voltage_avg > 0.0 && bat_voltage_avg)
+    {
+        return true;   
+    }
+}
+
+void TP4056ControlThread()
+{
+       //uint64_t now = Kernel::get_ms_count();
+       uint64_t chargeStartTime = 0;
+       
+       bool enableCharging = false; //internal variable to track whether TP4056 CE should be enabled or not
+       
+       
+       while(true)
+       {
+            //First check conditions to see if charging should be enabled or disabled
+            //Charging can be enabled if battery is present, no protections triggered, and user starts the charge
+            if(enableCharging == false)
+            {
+                bool temperature_en = (MAX_TEMPERATURE);
+            }
+            
+            //Charging must be stopped if overvoltage, overtemperature, overtime, battery removed, or user pushes button
+            
+            //With charge state calculated, realize it on the CE pin
+            //Allow pullup to 5V to enable charging at CE pin
+            if(enableCharging)
+            {
+                toggleOut.write(1); //open drain mode -> HiZ
+                chargeStartTime = Kernel::get_ms_count();
+            }
+            //To disable charging, open drain the CE pin
+            else
+            {
+                toggleOut.write(0); //open drain, pull to GND -> overpull 470k pullup that brings 5V to CE
+            } 
+            
+            //Update flag that indicates state of TP4056 CE pin to other threads
+            enableChargingState = enableCharging;
+        
+           ThisThread::sleep_for(100);
+       }
+}
+
+void oledOutputThread()
+{
+    while(true)
+    {
+        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(enableChargingState){
+            oled_i2c.drawString(0, 16, "CE: Enabled");
+        } else {
+            oled_i2c.drawString(0, 16, "CE: Disabled");  
+        }
+        
+        
+        stream.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);
+    }
+    
+}
+
+int main()
+{
+    pc.baud(9600);
+    pc.printf("Started\r\n");
+    
+    //BUILT IN LED THREAD
+    Thread led1;
+    //osStatus err =  led1.start(&blinkled);
+    led1.start(blinkled);
+
+    // CONFIGURE TP4056 CE CONTROL FOR OPEN DRAIN WITH EXTERNAL 5V PULLUP
+    //https://forums.mbed.com/t/how-to-configure-open-drain-output-pin-on-stm32/7007
+    toggleOut.mode(OpenDrainNoPull);
+    
+    // CHARGE ENABLE CONTROL THREAD
+    Thread tp4056_control_thread;
+    tp4056_control_thread.start(TP4056ControlThread);
+    
+    //OLED 128x64 INIT AND THREAD
+    //initialize ssd1306 on i2c0
+    oled_i2c.init();
+    oled_i2c.flipScreenVertically();
+    oled_i2c.setFont(ArialMT_Plain_16);
+    oled_i2c.drawString(0,0,"init!");
+    oled_i2c.display();
+    
+    Thread oled_thread;
+    oled_thread.start(oledOutputThread);
+    
+    // START/STOP CHARGE BUTTON
+    chargeButton.rise(&buttonISR);
+    
+    
+    float bat_voltage_min;
+    float bat_voltage_max;
+    float bat_voltage_avg_sum;
+    
+    uint64_t now = Kernel::get_ms_count();
+    uint64_t lastADCStartTime = now;
+    
+    while(true)
+    {
+        // reset min and max values
+        bat_voltage_min = 10.0;
+        bat_voltage_max = -10.0;
+        bat_voltage_avg_sum = 0.0;
+        
+        lastADCStartTime = Kernel::get_ms_count();
+        for(int i = 0; i < 1000; i++)
+        {
+            bat_voltage = readVoltageDivider_Calibrated();
+            vddref_voltage = readRefVoltage();
+            bat_voltage_avg_sum += bat_voltage;
+            if( bat_voltage < bat_voltage_min )
+            {
+                bat_voltage_min = bat_voltage;
+            }
+            if( bat_voltage > bat_voltage_max )
+            {
+                bat_voltage_max = bat_voltage;   
+            }
+            
+            ThisThread::sleep_for(1);
+        }
+        now = Kernel::get_ms_count();
+        
+        bat_voltage_avg = bat_voltage_avg_sum/1000.0;
+        
+        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);
+        pc.printf("ADC0 1s avg: %6.4f\r\n", bat_voltage_avg);
+        pc.printf("VDDREF Reading: %6.4f\r\n", vddref_voltage);
+        pc.printf("ADC0 time (nominal 1000ms): %d\r\n", (now-lastADCStartTime));
+        
+    }
+    
+}
\ No newline at end of file