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:
- 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