大 电 / Mbed OS mbed-os5-F303-18650-Manager-tp4056

Dependencies:   OLED_SSD1306 MCP9808

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

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 }