Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: OLED_SSD1306 MCP9808
main.cpp
00001 #include "mbed.h" 00002 #include "platform/mbed_thread.h" 00003 00004 #include <string> 00005 #include <iomanip> 00006 #include <sstream> 00007 00008 #include "SSD1306I2C.h" 00009 #include "MCP9808.h" 00010 00011 #include "serialCLI.h" 00012 00013 #define OLED_ADR 0x3C 00014 #define OLED_SDA I2C_SDA 00015 #define OLED_SCL I2C_SCL 00016 00017 #define MCP9808_SDA I2C_SDA 00018 #define MCP9808_SCL I2C_SCL 00019 //#define MCP9808_SDA PB_5 00020 //#define MCP9808_SCL PA_8 00021 00022 #define BAT_SAMPLERATE 1000 00023 00024 00025 // Blinking rate in milliseconds 00026 #define BLINKING_RATE_MS 5000 00027 00028 #define VOLTAGE_DIVIDER_R1 99800 00029 #define VOLTAGE_DIVIDER_R2 99100 00030 00031 //Buffered UARTSerial used, but any buffered serial object with read() and write() should work 00032 UARTSerial pc(USBTX,USBRX,115200); 00033 serialCLI sCLI(&pc); 00034 00035 SSD1306I2C oled_i2c(OLED_ADR, OLED_SDA, OLED_SCL); 00036 00037 //DS1820 ds1820_sensor(DS18B20_DATA); 00038 //sda,scl,address,freq 00039 //TODO: write MCP9808 constructor that doesn't change frequency 00040 MCP9808 myMCP9808 ( MCP9808_SDA, MCP9808_SCL, MCP9808::MCP9808_ADDRESS_0, 400000 ); // I2C_SDA | I2C_SCL 00041 00042 //DigitalIn mybutton(USER_BUTTON); 00043 AnalogIn divider_analogin(A0); 00044 AnalogIn vref(ADC_VREF); 00045 00046 //AnalogIn adc_refint(VREF_INT); 00047 00048 //Open drain with no pull up/down since the CE pin is pulled up to 5V 00049 //This also means a 5V tolereant pin (FT or FTf) must be used 00050 DigitalInOut tp4056ChipEnable(PA_14, PIN_OUTPUT, OpenDrainNoPull, 0); 00051 bool TP4056ChargingState = false; //reflects internal state of TP4056 control thread 00052 uint64_t chargingTimePassed = 0; //reflects charge time recorded by control thread 00053 00054 DigitalIn tp4056ChargeDone(PA_1); 00055 00056 InterruptIn chargeButton(USER_BUTTON); 00057 volatile bool buttonPressed = false; //set to true by button ISR, set to false when acknowledged 00058 00059 bool batteryPresenceState = false; 00060 00061 float bat_voltage = -1.11; 00062 float bat_voltage_avg = -1.11; 00063 float vddref_voltage = -1.11; 00064 00065 float temperatureSenseC = -301.0; 00066 00067 //Configurable protection conditions 00068 float MAX_VOLTAGE = 4.20; //default: no more than ___ 00069 float MIN_VOLTAGE = 2.5; //default: no less than 2.5v 00070 float MIN_DETECT_VOLTAGE = 0.5; //default: no less than 0.5v 00071 float MAX_TEMPERATURE = 35.0; //default: no more than 35C at the temperature sensor 00072 float MIN_TEMPERATURE = 10.0; //default: no less than 10C 00073 uint64_t MAX_TIME_ms = 3600000; //default: no more than 1h 00074 00075 00076 void blinkled() 00077 { 00078 // Initialise the digital pin LED1 as an output 00079 DigitalOut led(LED1); 00080 00081 while (true) { 00082 led = !led; 00083 ThisThread::sleep_for(BLINKING_RATE_MS); 00084 } 00085 } 00086 00087 00088 // Button to initiate / stop charging 00089 // Called on button rise 00090 // Set to true to signal to EN control thread that button was pressed 00091 void buttonISR() 00092 { 00093 static uint64_t lastButtonPushTime = 0; 00094 00095 uint64_t now = Kernel::get_ms_count(); 00096 00097 const uint64_t buttonMinimumWaitTime = 500; //minimum 500 ms wait between button pushes 00098 00099 if(now - lastButtonPushTime > buttonMinimumWaitTime) 00100 { 00101 buttonPressed = true; 00102 lastButtonPushTime = now; 00103 } 00104 00105 } 00106 00107 float readRefVoltage() 00108 { 00109 double vdd; 00110 double vdd_calibed; 00111 double vref_calibed; 00112 double vref_f; 00113 uint16_t vref_u16; 00114 uint16_t vref_cal; 00115 00116 vref_cal= *((uint16_t*)VREFINT_CAL_ADDR); //F303RE 00117 00118 vref_u16 = vref.read_u16(); 00119 //1.22 comes from 3.3 * 1524 / 4095 - voltage at calibration time times calibration measurement divided by maximum counts 00120 //vdd = 1.228132 / vref.read(); 00121 vdd = 3.3 * (double)vref_cal / 4095.0 / vref.read(); 00122 00123 return vdd; 00124 } 00125 00126 //Reads voltage divider, and uses internal calibrated reference voltage to calculate real voltage 00127 //Not 100% accurate, but better than assuming 3.3v 00128 float readVoltageDivider_Calibrated() 00129 { 00130 uint16_t vref_cal= *((uint16_t*)VREFINT_CAL_ADDR); //factory calibration value for 3.3v 00131 uint16_t vref_u16 = vref.read_u16(); //read the internal voltage calibration 00132 float vdd = 3.3 * (double)vref_cal / 4095.0 / vref.read(); 00133 00134 //ain.read() returns float value between 0 and 1 00135 float reading = divider_analogin.read(); 00136 00137 //sCLI.printf("raw reading: %f\r\n", reading); 00138 00139 return reading * vdd * (VOLTAGE_DIVIDER_R1 + VOLTAGE_DIVIDER_R2) / VOLTAGE_DIVIDER_R2; 00140 00141 } 00142 00143 //Measurement assuming 3.3v - for comparison to calibrated reading only 00144 //Can be very inaccurate as nucleo voltage regulator drops as far as 3.2v 00145 float readVoltageDivider_3v3() 00146 { 00147 float vdd = 3.3; 00148 00149 //ain.read() returns float value between 0 and 1 00150 float reading = divider_analogin.read(); 00151 00152 return reading * vdd * (VOLTAGE_DIVIDER_R1 + VOLTAGE_DIVIDER_R2) / VOLTAGE_DIVIDER_R2; 00153 00154 } 00155 00156 // Reads DS18B20 sense temperature 00157 void TemperatureInputThread() 00158 { 00159 MCP9808::MCP9808_status_t aux; 00160 MCP9808::MCP9808_config_reg_t myMCP9808_Config; 00161 MCP9808::MCP9808_data_t myMCP9808_Data; 00162 00163 // Shutdown the device, low-power mode enabled 00164 aux = myMCP9808.MCP9808_GetCONFIG ( &myMCP9808_Config ); 00165 00166 myMCP9808_Config.shdn = MCP9808::CONFIG_SHDN_SHUTDOWN; 00167 aux = myMCP9808.MCP9808_SetCONFIG ( myMCP9808_Config ); 00168 00169 // Get manufacturer ID 00170 aux = myMCP9808.MCP9808_GetManufacturerID ( &myMCP9808_Data ); 00171 00172 // Get device ID and device revision 00173 aux = myMCP9808.MCP9808_GetDeviceID ( &myMCP9808_Data ); 00174 00175 // Configure the device 00176 // - T_UPPER and T_LOWER limit hysteresis at 0C 00177 // - Continuous conversion mode 00178 // - T_CRIT unlocked 00179 // - Window lock unlocked 00180 // - Alert output control disabled 00181 // - Alert output select: Alert for T_UPPER, T_LOWER and T_CRIT 00182 // - Alert output polaruty: Active-low 00183 // - Alert output mode: Comparator output 00184 // 00185 myMCP9808_Config.t_hyst = MCP9808::CONFIG_T_HYST_0_C; 00186 myMCP9808_Config.shdn = MCP9808::CONFIG_SHDN_CONTINUOUS_CONVERSION; 00187 myMCP9808_Config.t_crit = MCP9808::CONFIG_CRIT_LOCK_UNLOCKED; 00188 myMCP9808_Config.t_win_lock = MCP9808::CONFIG_WIN_LOCK_UNLOCKED; 00189 myMCP9808_Config.alert_cnt = MCP9808::CONFIG_ALERT_CNT_DISABLED; 00190 myMCP9808_Config.alert_sel = MCP9808::CONFIG_ALERT_SEL_TUPPER_TLOWER_TCRIT; 00191 myMCP9808_Config.alert_pol = MCP9808::CONFIG_ALERT_POL_ACTIVE_LOW; 00192 myMCP9808_Config.alert_mod = MCP9808::CONFIG_ALERT_MOD_COMPARATOR_OUTPUT; 00193 aux = myMCP9808.MCP9808_SetCONFIG ( myMCP9808_Config ); 00194 00195 // Set resolution: +0.0625C ( t_CON ~ 250ms ) 00196 myMCP9808_Data.resolution = MCP9808::RESOLUTION_0_0625_C; 00197 aux = myMCP9808.MCP9808_SetResolution ( myMCP9808_Data ); 00198 00199 00200 while(true) 00201 { 00202 // Get ambient temperature 00203 aux = myMCP9808.MCP9808_GetTA ( &myMCP9808_Data ); 00204 00205 //sCLI.printf ( "T: %0.4f C\r\n", myMCP9808_Data.t_a ); 00206 temperatureSenseC = myMCP9808_Data.t_a; 00207 00208 ThisThread::sleep_for(250); 00209 } 00210 00211 00212 } 00213 00214 // Detect battery presence by voltage activity 00215 // TODO: voltage must be above threshold for a minimum time? 00216 void BatteryPresenceThread() 00217 { 00218 while(true) 00219 { 00220 if(bat_voltage_avg > MIN_DETECT_VOLTAGE) 00221 { 00222 batteryPresenceState = true; 00223 } 00224 else 00225 { 00226 batteryPresenceState = false; 00227 } 00228 00229 ThisThread::sleep_for(250); 00230 } 00231 } 00232 00233 void TP4056ControlThread() 00234 { 00235 //uint64_t now = Kernel::get_ms_count(); 00236 uint64_t chargeStartTime = 0; 00237 00238 bool enableCharging = false; //internal variable to track whether TP4056 CE should be enabled or not 00239 00240 00241 while(true) 00242 { 00243 //First check conditions to see if charging should be enabled or disabled 00244 bool temperature_en = ( ( temperatureSenseC < MAX_TEMPERATURE ) && ( temperatureSenseC > MIN_TEMPERATURE ) ); 00245 //bool temperature_en = true; //override as sensor code is bugged 00246 bool voltage_en = ( ( bat_voltage_avg < MAX_VOLTAGE ) && ( bat_voltage_avg > MIN_VOLTAGE ) ); 00247 bool presence_en = batteryPresenceState; 00248 bool charge_time_exceeded = ( chargingTimePassed > MAX_TIME_ms ); 00249 00250 //Charging can be enabled if battery is present, no protections triggered, and user starts the charge 00251 if(enableCharging == false) 00252 { 00253 //button must be pressed to start charge, but can also be pressed when eg battery not inserted 00254 if(buttonPressed) 00255 { 00256 if( voltage_en && temperature_en && presence_en ) 00257 { 00258 enableCharging = true; 00259 chargeStartTime = Kernel::get_ms_count(); 00260 } 00261 00262 //regardless of if charging was started, acknowledge the button press 00263 buttonPressed = false; 00264 } 00265 00266 } 00267 //Charging must be stopped if overvoltage, overtemperature, overtime, battery removed, or user pushes button 00268 //Charging time passed is maintained, but will be reset if user starts charge again 00269 //TODO: Only reset charge time once battery removed? 00270 else 00271 { 00272 //Disable charging if any protection condition is triggered 00273 if( !voltage_en || !temperature_en || !presence_en || charge_time_exceeded ) 00274 { 00275 enableCharging = false; 00276 } 00277 //or if user pushed button 00278 else if(buttonPressed) 00279 { 00280 enableCharging = false; 00281 buttonPressed = false; 00282 } 00283 00284 } 00285 00286 00287 //With charge state calculated, realize it on the CE pin 00288 //Allow pullup to '5V' to enable charging at CE pin 00289 if(enableCharging) 00290 { 00291 //Using HiZ still results in internal clamping diode activating, resulting in 3.6v 00292 tp4056ChipEnable.write(1); //open drain mode -> HiZ 00293 00294 //Continually update surpassed charging time if it is enabled 00295 chargingTimePassed = Kernel::get_ms_count() - chargeStartTime ; 00296 } 00297 //To disable charging, open drain the CE pin -> 0V 00298 else 00299 { 00300 tp4056ChipEnable.write(0); //open drain, pull to GND -> overpull 470k pullup that brings 5V to CE 00301 } 00302 00303 //Update flag that indicates state of TP4056 CE pin to other threads 00304 TP4056ChargingState = enableCharging; 00305 00306 ThisThread::sleep_for(100); 00307 } 00308 } 00309 00310 void oledOutputThread() 00311 { 00312 std::stringstream volts_stream, max_volts_stream; 00313 std::stringstream temperature_stream; 00314 std::stringstream chargetimepassed_stream; 00315 std::stringstream chargetimemax_stream; 00316 00317 while(true) 00318 { 00319 volts_stream.str(""); 00320 volts_stream << std::fixed << std::setprecision(2) << bat_voltage_avg; 00321 00322 max_volts_stream.str(""); 00323 max_volts_stream << std::fixed << std::setprecision(2) << MAX_VOLTAGE; 00324 00325 temperature_stream.str(""); 00326 temperature_stream << std::fixed << std::setprecision(2) << temperatureSenseC; 00327 00328 chargetimepassed_stream.str(""); 00329 chargetimepassed_stream << std::fixed << std::setprecision(1) << (float(chargingTimePassed)/1000/60); 00330 00331 chargetimemax_stream.str(""); 00332 chargetimemax_stream << std::fixed << std::setprecision(1) << (float(MAX_TIME_ms)/1000/60); 00333 00334 std::string str_temp = ""; 00335 00336 //clear the screen first 00337 oled_i2c.clear(); 00338 00339 //If no battery present, show screen indicating one should be inserted 00340 if( !batteryPresenceState ) 00341 { 00342 oled_i2c.setFont(ArialMT_Plain_16); 00343 oled_i2c.drawString(0, 0, "Insert battery"); 00344 00345 str_temp = volts_stream.str() + "v " + temperature_stream.str() + "ºC"; 00346 oled_i2c.drawString(0,16, str_temp.c_str() ); 00347 00348 } 00349 //battery present, show screen with voltage, CE status, temperature 00350 //Further splits into charge enabled or disabled - if charge enabled, show time so far and time limit 00351 else 00352 { 00353 oled_i2c.setFont(ArialMT_Plain_16); 00354 if(TP4056ChargingState){ 00355 str_temp = "Charge to " + max_volts_stream.str(); 00356 oled_i2c.drawString(0, 0, str_temp.c_str()); 00357 //TODO: shows 'charge complete' if above threshold 00358 } else { 00359 oled_i2c.drawString(0, 0, "Push to charge"); 00360 } 00361 00362 str_temp = volts_stream.str() + "v " + temperature_stream.str() + "ºC"; 00363 oled_i2c.drawString(0, 16, str_temp.c_str() ); 00364 00365 str_temp = "T: " + chargetimepassed_stream.str() + "m/" + chargetimemax_stream.str() + "m"; 00366 oled_i2c.drawString(0,16*2, str_temp.c_str() ); 00367 00368 00369 //std::string voltage_output = "Voltage: "+std::to_string(bat_voltage); 00370 //str_temp = "Voltage: " + volts_stream.str(); 00371 ///oled_i2c.drawString(0, 16, str_temp.c_str() ); 00372 //str_temp = "Temp: " + temperature_stream.str(); 00373 //oled_i2c.drawString(0,16*2, str_temp.c_str() ); 00374 00375 } 00376 oled_i2c.display(); 00377 00378 ThisThread::sleep_for(250); 00379 } 00380 00381 } 00382 00383 int main() 00384 { 00385 //sCLI.baud(115200); 00386 sCLI.printf("Started\r\n"); 00387 00388 //BUILT IN LED THREAD 00389 Thread led1; 00390 //osStatus err = led1.start(&blinkled); 00391 led1.start(blinkled); 00392 00393 // CONFIGURE TP4056 CE CONTROL FOR OPEN DRAIN WITH EXTERNAL 5V PULLUP 00394 //https://forums.mbed.com/t/how-to-configure-open-drain-output-pin-on-stm32/7007 00395 tp4056ChipEnable.mode(OpenDrainNoPull); 00396 00397 // TEMPERATURE INPUT THREAD 00398 Thread temperatureSenseC_thread; 00399 temperatureSenseC_thread.start(TemperatureInputThread); 00400 00401 // CHARGE ENABLE CONTROL THREAD 00402 Thread tp4056_control_thread; 00403 tp4056_control_thread.start(TP4056ControlThread); 00404 00405 // PRESENCE DETECTION THREAD 00406 Thread bat_presence_thread; 00407 bat_presence_thread.start(BatteryPresenceThread); 00408 00409 //OLED 128x64 INIT AND THREAD 00410 //initialize ssd1306 on i2c0 00411 oled_i2c.init(); 00412 //oled_i2c.flipScreenVertically(); 00413 oled_i2c.setFont(ArialMT_Plain_16); 00414 oled_i2c.drawString(0,0,"init!"); 00415 oled_i2c.setBrightness(64); 00416 oled_i2c.display(); 00417 00418 Thread oled_thread; 00419 oled_thread.start(oledOutputThread); 00420 00421 // START/STOP CHARGE BUTTON 00422 chargeButton.rise(&buttonISR); 00423 00424 00425 float bat_voltage_min; 00426 float bat_voltage_max; 00427 float bat_voltage_avg_sum; 00428 00429 uint64_t now = Kernel::get_ms_count(); 00430 uint64_t lastADCAverageStartTime = now; 00431 uint64_t lastADCReadStartTime = now; 00432 uint64_t time_tmp = 0; 00433 00434 while(true) 00435 { 00436 // reset min and max values 00437 bat_voltage_min = 10.0; 00438 bat_voltage_max = -10.0; 00439 bat_voltage_avg_sum = 0.0; 00440 00441 lastADCAverageStartTime = Kernel::get_ms_count(); 00442 for(int i = 0; i < BAT_SAMPLERATE; i++) 00443 { 00444 lastADCReadStartTime = Kernel::get_ms_count(); 00445 00446 bat_voltage = readVoltageDivider_Calibrated(); 00447 vddref_voltage = readRefVoltage(); 00448 bat_voltage_avg_sum += bat_voltage; 00449 if( bat_voltage < bat_voltage_min ) 00450 { 00451 bat_voltage_min = bat_voltage; 00452 } 00453 if( bat_voltage > bat_voltage_max ) 00454 { 00455 bat_voltage_max = bat_voltage; 00456 } 00457 00458 //sleep appropriate interval to meet specified sample rate, evenly spaced over 1s 00459 //This sleep is also where the other threads run 00460 //sleep amount calculated as: (nominal time between bat voltage reads) - (time it has taken to do the current measurement) 00461 time_tmp = 1000/BAT_SAMPLERATE - (lastADCReadStartTime - Kernel::get_ms_count()) ; 00462 //if we have already exceeded our time window, sleep nominal time as sample rate is not possible to meet 00463 if( time_tmp < 0 ) 00464 { 00465 ThisThread::sleep_for(1000/BAT_SAMPLERATE); 00466 } 00467 else 00468 { 00469 //otherwise, sleep the remaining time in our window to try to exactly meet our sample rate 00470 ThisThread::sleep_for(time_tmp); 00471 } 00472 } 00473 00474 now = Kernel::get_ms_count(); 00475 bat_voltage_avg = bat_voltage_avg_sum / BAT_SAMPLERATE; 00476 00477 //TODO: Dedicated serial thread, output messages when conditions change (eg OTP, OVP, charge started/stopped) 00478 sCLI.printf("\r\nADC0 Last Reading: %6.4f\r\n", bat_voltage); 00479 sCLI.printf("ADC0 1s min: %6.4f\r\n", bat_voltage_min); 00480 sCLI.printf("ADC0 1s max: %6.4f\r\n", bat_voltage_max); 00481 sCLI.printf("ADC0 1s avg: %6.4f\r\n", bat_voltage_avg); 00482 sCLI.printf("ADC0 time (nominal 1000ms): %llu\r\n", (now-lastADCAverageStartTime)); 00483 sCLI.printf("VDDREF Reading: %6.4f\r\n", vddref_voltage); 00484 sCLI.printf("Temp: %6.4f\r\n", temperatureSenseC); 00485 sCLI.printf("Bat present: %d\r\n", batteryPresenceState); 00486 00487 } 00488 00489 }
Generated on Thu Jul 14 2022 22:01:13 by
1.7.2