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

Committer:
kuutei
Date:
Mon Aug 24 04:09:04 2020 +0000
Revision:
0:56122e281547
Child:
1:7749656733dd
adding OTP, OVP, time limits

Who changed what in which revision?

UserRevisionLine numberNew contents of line
kuutei 0:56122e281547 1 #include "mbed.h"
kuutei 0:56122e281547 2 #include "platform/mbed_thread.h"
kuutei 0:56122e281547 3
kuutei 0:56122e281547 4 #include <string>
kuutei 0:56122e281547 5 #include <iomanip>
kuutei 0:56122e281547 6 #include <sstream>
kuutei 0:56122e281547 7
kuutei 0:56122e281547 8 #include "SSD1306I2C.h"
kuutei 0:56122e281547 9
kuutei 0:56122e281547 10 #define OLED_ADR 0x3C
kuutei 0:56122e281547 11 #define OLED_SDA I2C_SDA
kuutei 0:56122e281547 12 #define OLED_SCL I2C_SCL
kuutei 0:56122e281547 13
kuutei 0:56122e281547 14
kuutei 0:56122e281547 15 // Blinking rate in milliseconds
kuutei 0:56122e281547 16 #define BLINKING_RATE_MS 5000
kuutei 0:56122e281547 17
kuutei 0:56122e281547 18 #define VOLTAGE_DIVIDER_R1 99800
kuutei 0:56122e281547 19 #define VOLTAGE_DIVIDER_R2 99100
kuutei 0:56122e281547 20
kuutei 0:56122e281547 21 Serial pc(USBTX,USBRX);
kuutei 0:56122e281547 22
kuutei 0:56122e281547 23 //DigitalIn mybutton(USER_BUTTON);
kuutei 0:56122e281547 24 AnalogIn divider_analogin(A0);
kuutei 0:56122e281547 25 AnalogIn vref(ADC_VREF);
kuutei 0:56122e281547 26
kuutei 0:56122e281547 27 //AnalogIn adc_refint(VREF_INT);
kuutei 0:56122e281547 28
kuutei 0:56122e281547 29 DigitalInOut toggleOut(PA_14, PIN_OUTPUT, OpenDrainNoPull, 0);
kuutei 0:56122e281547 30 bool enableChargingState = false; //reflects internal state of TP4056 control thread
kuutei 0:56122e281547 31
kuutei 0:56122e281547 32 InterruptIn chargeButton(USER_BUTTON);
kuutei 0:56122e281547 33 bool buttonPressed = false; //set to true by button ISR, set to false when acknowledged
kuutei 0:56122e281547 34
kuutei 0:56122e281547 35 SSD1306I2C oled_i2c(OLED_ADR, OLED_SDA, OLED_SCL);
kuutei 0:56122e281547 36
kuutei 0:56122e281547 37 float bat_voltage = -1.11;
kuutei 0:56122e281547 38 float bat_voltage_avg = -1.11;
kuutei 0:56122e281547 39 float vddref_voltage = -1.11;
kuutei 0:56122e281547 40
kuutei 0:56122e281547 41
kuutei 0:56122e281547 42 //Configurable protection conditions
kuutei 0:56122e281547 43 float MAX_VOLTAGE = 4.25; //default: no more than 4.25v
kuutei 0:56122e281547 44 float MIN_VOLTAGE = 2.5; //default: no less than 2.5v
kuutei 0:56122e281547 45 float MAX_TEMPERATURE = 40.0; //default: no more than 40C
kuutei 0:56122e281547 46 float MIN_TEMPERATURE = 10.0; //default: no less than 10C
kuutei 0:56122e281547 47 uint64_t MAX_TIME_ms = 3600000; //default: no more than 1h
kuutei 0:56122e281547 48
kuutei 0:56122e281547 49
kuutei 0:56122e281547 50 void blinkled()
kuutei 0:56122e281547 51 {
kuutei 0:56122e281547 52 // Initialise the digital pin LED1 as an output
kuutei 0:56122e281547 53 DigitalOut led(LED1);
kuutei 0:56122e281547 54
kuutei 0:56122e281547 55 while (true) {
kuutei 0:56122e281547 56 led = !led;
kuutei 0:56122e281547 57 //thread_sleep_for(BLINKING_RATE_MS);
kuutei 0:56122e281547 58 ThisThread::sleep_for(BLINKING_RATE_MS);
kuutei 0:56122e281547 59 }
kuutei 0:56122e281547 60 }
kuutei 0:56122e281547 61
kuutei 0:56122e281547 62 //TODO: make this an ISR that causes an immediate read of the analog pin
kuutei 0:56122e281547 63 void pa14_toggle()
kuutei 0:56122e281547 64 {
kuutei 0:56122e281547 65
kuutei 0:56122e281547 66 }
kuutei 0:56122e281547 67
kuutei 0:56122e281547 68 // Button to initiate / stop charging
kuutei 0:56122e281547 69 // Called on button rise
kuutei 0:56122e281547 70 // Set to true to signal to EN control thread that button was pressed
kuutei 0:56122e281547 71 void buttonISR
kuutei 0:56122e281547 72 {
kuutei 0:56122e281547 73 uint64_t now = Kernel::get_ms_count();
kuutei 0:56122e281547 74 uint64_t lastButtonPushTime = now;
kuutei 0:56122e281547 75 const uint64_t buttonMinimumWaitTime = 500; //minimum 500 ms wait between button pushes
kuutei 0:56122e281547 76
kuutei 0:56122e281547 77 if(Kernel::get_ms_count() - lastButtonPushTime > buttonMinimumWaitTime)
kuutei 0:56122e281547 78 {
kuutei 0:56122e281547 79 buttonPressed = true;
kuutei 0:56122e281547 80 }
kuutei 0:56122e281547 81
kuutei 0:56122e281547 82 }
kuutei 0:56122e281547 83
kuutei 0:56122e281547 84 float readRefVoltage()
kuutei 0:56122e281547 85 {
kuutei 0:56122e281547 86 double vdd;
kuutei 0:56122e281547 87 double vdd_calibed;
kuutei 0:56122e281547 88 double vref_calibed;
kuutei 0:56122e281547 89 double vref_f;
kuutei 0:56122e281547 90 uint16_t vref_u16;
kuutei 0:56122e281547 91 uint16_t vref_cal;
kuutei 0:56122e281547 92
kuutei 0:56122e281547 93 vref_cal= *((uint16_t*)VREFINT_CAL_ADDR); //F303RE
kuutei 0:56122e281547 94
kuutei 0:56122e281547 95 vref_u16 = vref.read_u16();
kuutei 0:56122e281547 96 //1.22 comes from 3.3 * 1524 / 4095 - voltage at calibration time times calibration measurement divided by maximum counts
kuutei 0:56122e281547 97 //vdd = 1.228132 / vref.read();
kuutei 0:56122e281547 98 vdd = 3.3 * (double)vref_cal / 4095.0 / vref.read();
kuutei 0:56122e281547 99
kuutei 0:56122e281547 100 return vdd;
kuutei 0:56122e281547 101 }
kuutei 0:56122e281547 102
kuutei 0:56122e281547 103 //Reads voltage divider, and uses internal calibrated reference voltage to calculate real voltage
kuutei 0:56122e281547 104 //Not 100% accurate, but better than assuming 3.3v
kuutei 0:56122e281547 105 float readVoltageDivider_Calibrated()
kuutei 0:56122e281547 106 {
kuutei 0:56122e281547 107 uint16_t vref_cal= *((uint16_t*)VREFINT_CAL_ADDR); //factory calibration value for 3.3v
kuutei 0:56122e281547 108 uint16_t vref_u16 = vref.read_u16(); //read the internal voltage calibration
kuutei 0:56122e281547 109 float vdd = 3.3 * (double)vref_cal / 4095.0 / vref.read();
kuutei 0:56122e281547 110
kuutei 0:56122e281547 111 //ain.read() returns float value between 0 and 1
kuutei 0:56122e281547 112 float reading = divider_analogin.read();
kuutei 0:56122e281547 113
kuutei 0:56122e281547 114 //pc.printf("raw reading: %f\r\n", reading);
kuutei 0:56122e281547 115
kuutei 0:56122e281547 116 return reading * vdd * (VOLTAGE_DIVIDER_R1 + VOLTAGE_DIVIDER_R2) / VOLTAGE_DIVIDER_R2;
kuutei 0:56122e281547 117
kuutei 0:56122e281547 118 }
kuutei 0:56122e281547 119
kuutei 0:56122e281547 120 //Measurement assuming 3.3v - for comparison to calibrated reading only
kuutei 0:56122e281547 121 //Can be very inaccurate as nucleo voltage regulator drops as far as 3.2v
kuutei 0:56122e281547 122 float readVoltageDivider_3v3()
kuutei 0:56122e281547 123 {
kuutei 0:56122e281547 124 uint16_t vref_cal= *((uint16_t*)VREFINT_CAL_ADDR); //factory calibration value for 3.3v
kuutei 0:56122e281547 125 uint16_t vref_u16 = vref.read_u16(); //read the internal voltage calibration
kuutei 0:56122e281547 126 float vdd = 3.3 * (double)vref_cal / 4095.0 / vref.read();
kuutei 0:56122e281547 127
kuutei 0:56122e281547 128 //ain.read() returns float value between 0 and 1
kuutei 0:56122e281547 129 float reading = divider_analogin.read();
kuutei 0:56122e281547 130
kuutei 0:56122e281547 131 return reading * vdd * (VOLTAGE_DIVIDER_R1 + VOLTAGE_DIVIDER_R2) / VOLTAGE_DIVIDER_R2;
kuutei 0:56122e281547 132
kuutei 0:56122e281547 133 }
kuutei 0:56122e281547 134
kuutei 0:56122e281547 135 // Reads DS18B20 sense temperature
kuutei 0:56122e281547 136 void TemperatureInputThread()
kuutei 0:56122e281547 137 {
kuutei 0:56122e281547 138 while(true)
kuutei 0:56122e281547 139 {
kuutei 0:56122e281547 140
kuutei 0:56122e281547 141
kuutei 0:56122e281547 142 ThisThread::sleep_for(250);
kuutei 0:56122e281547 143
kuutei 0:56122e281547 144 }
kuutei 0:56122e281547 145 }
kuutei 0:56122e281547 146
kuutei 0:56122e281547 147 // Detect battery presence by voltage activity
kuutei 0:56122e281547 148 bool batteryPresent()
kuutei 0:56122e281547 149 {
kuutei 0:56122e281547 150 if(bat_voltage_avg > 0.0 && bat_voltage_avg)
kuutei 0:56122e281547 151 {
kuutei 0:56122e281547 152 return true;
kuutei 0:56122e281547 153 }
kuutei 0:56122e281547 154 }
kuutei 0:56122e281547 155
kuutei 0:56122e281547 156 void TP4056ControlThread()
kuutei 0:56122e281547 157 {
kuutei 0:56122e281547 158 //uint64_t now = Kernel::get_ms_count();
kuutei 0:56122e281547 159 uint64_t chargeStartTime = 0;
kuutei 0:56122e281547 160
kuutei 0:56122e281547 161 bool enableCharging = false; //internal variable to track whether TP4056 CE should be enabled or not
kuutei 0:56122e281547 162
kuutei 0:56122e281547 163
kuutei 0:56122e281547 164 while(true)
kuutei 0:56122e281547 165 {
kuutei 0:56122e281547 166 //First check conditions to see if charging should be enabled or disabled
kuutei 0:56122e281547 167 //Charging can be enabled if battery is present, no protections triggered, and user starts the charge
kuutei 0:56122e281547 168 if(enableCharging == false)
kuutei 0:56122e281547 169 {
kuutei 0:56122e281547 170 bool temperature_en = (MAX_TEMPERATURE);
kuutei 0:56122e281547 171 }
kuutei 0:56122e281547 172
kuutei 0:56122e281547 173 //Charging must be stopped if overvoltage, overtemperature, overtime, battery removed, or user pushes button
kuutei 0:56122e281547 174
kuutei 0:56122e281547 175 //With charge state calculated, realize it on the CE pin
kuutei 0:56122e281547 176 //Allow pullup to 5V to enable charging at CE pin
kuutei 0:56122e281547 177 if(enableCharging)
kuutei 0:56122e281547 178 {
kuutei 0:56122e281547 179 toggleOut.write(1); //open drain mode -> HiZ
kuutei 0:56122e281547 180 chargeStartTime = Kernel::get_ms_count();
kuutei 0:56122e281547 181 }
kuutei 0:56122e281547 182 //To disable charging, open drain the CE pin
kuutei 0:56122e281547 183 else
kuutei 0:56122e281547 184 {
kuutei 0:56122e281547 185 toggleOut.write(0); //open drain, pull to GND -> overpull 470k pullup that brings 5V to CE
kuutei 0:56122e281547 186 }
kuutei 0:56122e281547 187
kuutei 0:56122e281547 188 //Update flag that indicates state of TP4056 CE pin to other threads
kuutei 0:56122e281547 189 enableChargingState = enableCharging;
kuutei 0:56122e281547 190
kuutei 0:56122e281547 191 ThisThread::sleep_for(100);
kuutei 0:56122e281547 192 }
kuutei 0:56122e281547 193 }
kuutei 0:56122e281547 194
kuutei 0:56122e281547 195 void oledOutputThread()
kuutei 0:56122e281547 196 {
kuutei 0:56122e281547 197 while(true)
kuutei 0:56122e281547 198 {
kuutei 0:56122e281547 199 oled_i2c.clear();
kuutei 0:56122e281547 200
kuutei 0:56122e281547 201 std::stringstream stream;
kuutei 0:56122e281547 202 stream << std::fixed << std::setprecision(4) << bat_voltage_avg;
kuutei 0:56122e281547 203 //std::string voltage_output = "Voltage: "+std::to_string(bat_voltage);
kuutei 0:56122e281547 204 std::string voltage_output = "Voltage: " + stream.str();
kuutei 0:56122e281547 205 oled_i2c.drawString(0, 0, voltage_output.c_str() );
kuutei 0:56122e281547 206
kuutei 0:56122e281547 207 if(enableChargingState){
kuutei 0:56122e281547 208 oled_i2c.drawString(0, 16, "CE: Enabled");
kuutei 0:56122e281547 209 } else {
kuutei 0:56122e281547 210 oled_i2c.drawString(0, 16, "CE: Disabled");
kuutei 0:56122e281547 211 }
kuutei 0:56122e281547 212
kuutei 0:56122e281547 213
kuutei 0:56122e281547 214 stream.str("");
kuutei 0:56122e281547 215 stream << std::fixed << std::setprecision(4) << vddref_voltage;
kuutei 0:56122e281547 216 std::string vddref_output = "VDDREF: " + stream.str();
kuutei 0:56122e281547 217 oled_i2c.drawString(0,16*2, vddref_output.c_str() );
kuutei 0:56122e281547 218
kuutei 0:56122e281547 219
kuutei 0:56122e281547 220 oled_i2c.display();
kuutei 0:56122e281547 221 ThisThread::sleep_for(250);
kuutei 0:56122e281547 222 }
kuutei 0:56122e281547 223
kuutei 0:56122e281547 224 }
kuutei 0:56122e281547 225
kuutei 0:56122e281547 226 int main()
kuutei 0:56122e281547 227 {
kuutei 0:56122e281547 228 pc.baud(9600);
kuutei 0:56122e281547 229 pc.printf("Started\r\n");
kuutei 0:56122e281547 230
kuutei 0:56122e281547 231 //BUILT IN LED THREAD
kuutei 0:56122e281547 232 Thread led1;
kuutei 0:56122e281547 233 //osStatus err = led1.start(&blinkled);
kuutei 0:56122e281547 234 led1.start(blinkled);
kuutei 0:56122e281547 235
kuutei 0:56122e281547 236 // CONFIGURE TP4056 CE CONTROL FOR OPEN DRAIN WITH EXTERNAL 5V PULLUP
kuutei 0:56122e281547 237 //https://forums.mbed.com/t/how-to-configure-open-drain-output-pin-on-stm32/7007
kuutei 0:56122e281547 238 toggleOut.mode(OpenDrainNoPull);
kuutei 0:56122e281547 239
kuutei 0:56122e281547 240 // CHARGE ENABLE CONTROL THREAD
kuutei 0:56122e281547 241 Thread tp4056_control_thread;
kuutei 0:56122e281547 242 tp4056_control_thread.start(TP4056ControlThread);
kuutei 0:56122e281547 243
kuutei 0:56122e281547 244 //OLED 128x64 INIT AND THREAD
kuutei 0:56122e281547 245 //initialize ssd1306 on i2c0
kuutei 0:56122e281547 246 oled_i2c.init();
kuutei 0:56122e281547 247 oled_i2c.flipScreenVertically();
kuutei 0:56122e281547 248 oled_i2c.setFont(ArialMT_Plain_16);
kuutei 0:56122e281547 249 oled_i2c.drawString(0,0,"init!");
kuutei 0:56122e281547 250 oled_i2c.display();
kuutei 0:56122e281547 251
kuutei 0:56122e281547 252 Thread oled_thread;
kuutei 0:56122e281547 253 oled_thread.start(oledOutputThread);
kuutei 0:56122e281547 254
kuutei 0:56122e281547 255 // START/STOP CHARGE BUTTON
kuutei 0:56122e281547 256 chargeButton.rise(&buttonISR);
kuutei 0:56122e281547 257
kuutei 0:56122e281547 258
kuutei 0:56122e281547 259 float bat_voltage_min;
kuutei 0:56122e281547 260 float bat_voltage_max;
kuutei 0:56122e281547 261 float bat_voltage_avg_sum;
kuutei 0:56122e281547 262
kuutei 0:56122e281547 263 uint64_t now = Kernel::get_ms_count();
kuutei 0:56122e281547 264 uint64_t lastADCStartTime = now;
kuutei 0:56122e281547 265
kuutei 0:56122e281547 266 while(true)
kuutei 0:56122e281547 267 {
kuutei 0:56122e281547 268 // reset min and max values
kuutei 0:56122e281547 269 bat_voltage_min = 10.0;
kuutei 0:56122e281547 270 bat_voltage_max = -10.0;
kuutei 0:56122e281547 271 bat_voltage_avg_sum = 0.0;
kuutei 0:56122e281547 272
kuutei 0:56122e281547 273 lastADCStartTime = Kernel::get_ms_count();
kuutei 0:56122e281547 274 for(int i = 0; i < 1000; i++)
kuutei 0:56122e281547 275 {
kuutei 0:56122e281547 276 bat_voltage = readVoltageDivider_Calibrated();
kuutei 0:56122e281547 277 vddref_voltage = readRefVoltage();
kuutei 0:56122e281547 278 bat_voltage_avg_sum += bat_voltage;
kuutei 0:56122e281547 279 if( bat_voltage < bat_voltage_min )
kuutei 0:56122e281547 280 {
kuutei 0:56122e281547 281 bat_voltage_min = bat_voltage;
kuutei 0:56122e281547 282 }
kuutei 0:56122e281547 283 if( bat_voltage > bat_voltage_max )
kuutei 0:56122e281547 284 {
kuutei 0:56122e281547 285 bat_voltage_max = bat_voltage;
kuutei 0:56122e281547 286 }
kuutei 0:56122e281547 287
kuutei 0:56122e281547 288 ThisThread::sleep_for(1);
kuutei 0:56122e281547 289 }
kuutei 0:56122e281547 290 now = Kernel::get_ms_count();
kuutei 0:56122e281547 291
kuutei 0:56122e281547 292 bat_voltage_avg = bat_voltage_avg_sum/1000.0;
kuutei 0:56122e281547 293
kuutei 0:56122e281547 294 pc.printf("\r\nADC0 Reading: %6.4f\r\n", bat_voltage);
kuutei 0:56122e281547 295 pc.printf("ADC0 1s min: %6.4f\r\n", bat_voltage_min);
kuutei 0:56122e281547 296 pc.printf("ADC0 1s max: %6.4f\r\n", bat_voltage_max);
kuutei 0:56122e281547 297 pc.printf("ADC0 1s avg: %6.4f\r\n", bat_voltage_avg);
kuutei 0:56122e281547 298 pc.printf("VDDREF Reading: %6.4f\r\n", vddref_voltage);
kuutei 0:56122e281547 299 pc.printf("ADC0 time (nominal 1000ms): %d\r\n", (now-lastADCStartTime));
kuutei 0:56122e281547 300
kuutei 0:56122e281547 301 }
kuutei 0:56122e281547 302
kuutei 0:56122e281547 303 }