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
main.cpp@10:4ac5d8748268, 2020-09-15 (annotated)
- Committer:
- kuutei
- Date:
- Tue Sep 15 03:54:15 2020 -0400
- Revision:
- 10:4ac5d8748268
- Parent:
- 9:272f6963c3b8
add serialCLI lib
Who changed what in which revision?
User | Revision | Line number | New 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 | 4:f63aab9fec77 | 9 | #include "MCP9808.h" |
kuutei | 0:56122e281547 | 10 | |
kuutei | 9:272f6963c3b8 | 11 | #include "serialCLI.h" |
kuutei | 9:272f6963c3b8 | 12 | |
kuutei | 0:56122e281547 | 13 | #define OLED_ADR 0x3C |
kuutei | 0:56122e281547 | 14 | #define OLED_SDA I2C_SDA |
kuutei | 0:56122e281547 | 15 | #define OLED_SCL I2C_SCL |
kuutei | 0:56122e281547 | 16 | |
kuutei | 9:272f6963c3b8 | 17 | #define MCP9808_SDA I2C_SDA |
kuutei | 9:272f6963c3b8 | 18 | #define MCP9808_SCL I2C_SCL |
kuutei | 9:272f6963c3b8 | 19 | //#define MCP9808_SDA PB_5 |
kuutei | 9:272f6963c3b8 | 20 | //#define MCP9808_SCL PA_8 |
kuutei | 4:f63aab9fec77 | 21 | |
kuutei | 5:c07438005b16 | 22 | #define BAT_SAMPLERATE 1000 |
kuutei | 1:7749656733dd | 23 | |
kuutei | 0:56122e281547 | 24 | |
kuutei | 0:56122e281547 | 25 | // Blinking rate in milliseconds |
kuutei | 0:56122e281547 | 26 | #define BLINKING_RATE_MS 5000 |
kuutei | 0:56122e281547 | 27 | |
kuutei | 0:56122e281547 | 28 | #define VOLTAGE_DIVIDER_R1 99800 |
kuutei | 0:56122e281547 | 29 | #define VOLTAGE_DIVIDER_R2 99100 |
kuutei | 0:56122e281547 | 30 | |
kuutei | 9:272f6963c3b8 | 31 | //Buffered UARTSerial used, but any buffered serial object with read() and write() should work |
kuutei | 9:272f6963c3b8 | 32 | UARTSerial pc(USBTX,USBRX,115200); |
kuutei | 9:272f6963c3b8 | 33 | serialCLI sCLI(&pc); |
kuutei | 0:56122e281547 | 34 | |
kuutei | 1:7749656733dd | 35 | SSD1306I2C oled_i2c(OLED_ADR, OLED_SDA, OLED_SCL); |
kuutei | 1:7749656733dd | 36 | |
kuutei | 4:f63aab9fec77 | 37 | //DS1820 ds1820_sensor(DS18B20_DATA); |
kuutei | 4:f63aab9fec77 | 38 | //sda,scl,address,freq |
kuutei | 9:272f6963c3b8 | 39 | //TODO: write MCP9808 constructor that doesn't change frequency |
kuutei | 4:f63aab9fec77 | 40 | MCP9808 myMCP9808 ( MCP9808_SDA, MCP9808_SCL, MCP9808::MCP9808_ADDRESS_0, 400000 ); // I2C_SDA | I2C_SCL |
kuutei | 1:7749656733dd | 41 | |
kuutei | 0:56122e281547 | 42 | //DigitalIn mybutton(USER_BUTTON); |
kuutei | 0:56122e281547 | 43 | AnalogIn divider_analogin(A0); |
kuutei | 0:56122e281547 | 44 | AnalogIn vref(ADC_VREF); |
kuutei | 0:56122e281547 | 45 | |
kuutei | 0:56122e281547 | 46 | //AnalogIn adc_refint(VREF_INT); |
kuutei | 0:56122e281547 | 47 | |
kuutei | 9:272f6963c3b8 | 48 | //Open drain with no pull up/down since the CE pin is pulled up to 5V |
kuutei | 9:272f6963c3b8 | 49 | //This also means a 5V tolereant pin (FT or FTf) must be used |
kuutei | 9:272f6963c3b8 | 50 | DigitalInOut tp4056ChipEnable(PA_14, PIN_OUTPUT, OpenDrainNoPull, 0); |
kuutei | 1:7749656733dd | 51 | bool TP4056ChargingState = false; //reflects internal state of TP4056 control thread |
kuutei | 5:c07438005b16 | 52 | uint64_t chargingTimePassed = 0; //reflects charge time recorded by control thread |
kuutei | 0:56122e281547 | 53 | |
kuutei | 9:272f6963c3b8 | 54 | DigitalIn tp4056ChargeDone(PA_1); |
kuutei | 9:272f6963c3b8 | 55 | |
kuutei | 0:56122e281547 | 56 | InterruptIn chargeButton(USER_BUTTON); |
kuutei | 1:7749656733dd | 57 | volatile bool buttonPressed = false; //set to true by button ISR, set to false when acknowledged |
kuutei | 0:56122e281547 | 58 | |
kuutei | 1:7749656733dd | 59 | bool batteryPresenceState = false; |
kuutei | 0:56122e281547 | 60 | |
kuutei | 0:56122e281547 | 61 | float bat_voltage = -1.11; |
kuutei | 0:56122e281547 | 62 | float bat_voltage_avg = -1.11; |
kuutei | 0:56122e281547 | 63 | float vddref_voltage = -1.11; |
kuutei | 0:56122e281547 | 64 | |
kuutei | 9:272f6963c3b8 | 65 | float temperatureSenseC = -301.0; |
kuutei | 0:56122e281547 | 66 | |
kuutei | 0:56122e281547 | 67 | //Configurable protection conditions |
kuutei | 9:272f6963c3b8 | 68 | float MAX_VOLTAGE = 4.20; //default: no more than ___ |
kuutei | 0:56122e281547 | 69 | float MIN_VOLTAGE = 2.5; //default: no less than 2.5v |
kuutei | 9:272f6963c3b8 | 70 | float MIN_DETECT_VOLTAGE = 0.5; //default: no less than 0.5v |
kuutei | 5:c07438005b16 | 71 | float MAX_TEMPERATURE = 35.0; //default: no more than 35C at the temperature sensor |
kuutei | 0:56122e281547 | 72 | float MIN_TEMPERATURE = 10.0; //default: no less than 10C |
kuutei | 0:56122e281547 | 73 | uint64_t MAX_TIME_ms = 3600000; //default: no more than 1h |
kuutei | 0:56122e281547 | 74 | |
kuutei | 0:56122e281547 | 75 | |
kuutei | 0:56122e281547 | 76 | void blinkled() |
kuutei | 0:56122e281547 | 77 | { |
kuutei | 0:56122e281547 | 78 | // Initialise the digital pin LED1 as an output |
kuutei | 0:56122e281547 | 79 | DigitalOut led(LED1); |
kuutei | 0:56122e281547 | 80 | |
kuutei | 0:56122e281547 | 81 | while (true) { |
kuutei | 0:56122e281547 | 82 | led = !led; |
kuutei | 0:56122e281547 | 83 | ThisThread::sleep_for(BLINKING_RATE_MS); |
kuutei | 0:56122e281547 | 84 | } |
kuutei | 0:56122e281547 | 85 | } |
kuutei | 0:56122e281547 | 86 | |
kuutei | 0:56122e281547 | 87 | |
kuutei | 0:56122e281547 | 88 | // Button to initiate / stop charging |
kuutei | 0:56122e281547 | 89 | // Called on button rise |
kuutei | 0:56122e281547 | 90 | // Set to true to signal to EN control thread that button was pressed |
kuutei | 1:7749656733dd | 91 | void buttonISR() |
kuutei | 0:56122e281547 | 92 | { |
kuutei | 1:7749656733dd | 93 | static uint64_t lastButtonPushTime = 0; |
kuutei | 1:7749656733dd | 94 | |
kuutei | 0:56122e281547 | 95 | uint64_t now = Kernel::get_ms_count(); |
kuutei | 1:7749656733dd | 96 | |
kuutei | 0:56122e281547 | 97 | const uint64_t buttonMinimumWaitTime = 500; //minimum 500 ms wait between button pushes |
kuutei | 0:56122e281547 | 98 | |
kuutei | 9:272f6963c3b8 | 99 | if(now - lastButtonPushTime > buttonMinimumWaitTime) |
kuutei | 0:56122e281547 | 100 | { |
kuutei | 0:56122e281547 | 101 | buttonPressed = true; |
kuutei | 1:7749656733dd | 102 | lastButtonPushTime = now; |
kuutei | 0:56122e281547 | 103 | } |
kuutei | 0:56122e281547 | 104 | |
kuutei | 0:56122e281547 | 105 | } |
kuutei | 0:56122e281547 | 106 | |
kuutei | 0:56122e281547 | 107 | float readRefVoltage() |
kuutei | 0:56122e281547 | 108 | { |
kuutei | 0:56122e281547 | 109 | double vdd; |
kuutei | 0:56122e281547 | 110 | double vdd_calibed; |
kuutei | 0:56122e281547 | 111 | double vref_calibed; |
kuutei | 0:56122e281547 | 112 | double vref_f; |
kuutei | 0:56122e281547 | 113 | uint16_t vref_u16; |
kuutei | 0:56122e281547 | 114 | uint16_t vref_cal; |
kuutei | 0:56122e281547 | 115 | |
kuutei | 0:56122e281547 | 116 | vref_cal= *((uint16_t*)VREFINT_CAL_ADDR); //F303RE |
kuutei | 0:56122e281547 | 117 | |
kuutei | 0:56122e281547 | 118 | vref_u16 = vref.read_u16(); |
kuutei | 0:56122e281547 | 119 | //1.22 comes from 3.3 * 1524 / 4095 - voltage at calibration time times calibration measurement divided by maximum counts |
kuutei | 0:56122e281547 | 120 | //vdd = 1.228132 / vref.read(); |
kuutei | 0:56122e281547 | 121 | vdd = 3.3 * (double)vref_cal / 4095.0 / vref.read(); |
kuutei | 0:56122e281547 | 122 | |
kuutei | 9:272f6963c3b8 | 123 | return vdd; |
kuutei | 0:56122e281547 | 124 | } |
kuutei | 0:56122e281547 | 125 | |
kuutei | 0:56122e281547 | 126 | //Reads voltage divider, and uses internal calibrated reference voltage to calculate real voltage |
kuutei | 0:56122e281547 | 127 | //Not 100% accurate, but better than assuming 3.3v |
kuutei | 0:56122e281547 | 128 | float readVoltageDivider_Calibrated() |
kuutei | 0:56122e281547 | 129 | { |
kuutei | 0:56122e281547 | 130 | uint16_t vref_cal= *((uint16_t*)VREFINT_CAL_ADDR); //factory calibration value for 3.3v |
kuutei | 0:56122e281547 | 131 | uint16_t vref_u16 = vref.read_u16(); //read the internal voltage calibration |
kuutei | 0:56122e281547 | 132 | float vdd = 3.3 * (double)vref_cal / 4095.0 / vref.read(); |
kuutei | 0:56122e281547 | 133 | |
kuutei | 0:56122e281547 | 134 | //ain.read() returns float value between 0 and 1 |
kuutei | 0:56122e281547 | 135 | float reading = divider_analogin.read(); |
kuutei | 0:56122e281547 | 136 | |
kuutei | 9:272f6963c3b8 | 137 | //sCLI.printf("raw reading: %f\r\n", reading); |
kuutei | 0:56122e281547 | 138 | |
kuutei | 0:56122e281547 | 139 | return reading * vdd * (VOLTAGE_DIVIDER_R1 + VOLTAGE_DIVIDER_R2) / VOLTAGE_DIVIDER_R2; |
kuutei | 0:56122e281547 | 140 | |
kuutei | 0:56122e281547 | 141 | } |
kuutei | 0:56122e281547 | 142 | |
kuutei | 0:56122e281547 | 143 | //Measurement assuming 3.3v - for comparison to calibrated reading only |
kuutei | 0:56122e281547 | 144 | //Can be very inaccurate as nucleo voltage regulator drops as far as 3.2v |
kuutei | 0:56122e281547 | 145 | float readVoltageDivider_3v3() |
kuutei | 0:56122e281547 | 146 | { |
kuutei | 8:c781f4ae7eb3 | 147 | float vdd = 3.3; |
kuutei | 0:56122e281547 | 148 | |
kuutei | 0:56122e281547 | 149 | //ain.read() returns float value between 0 and 1 |
kuutei | 0:56122e281547 | 150 | float reading = divider_analogin.read(); |
kuutei | 0:56122e281547 | 151 | |
kuutei | 0:56122e281547 | 152 | return reading * vdd * (VOLTAGE_DIVIDER_R1 + VOLTAGE_DIVIDER_R2) / VOLTAGE_DIVIDER_R2; |
kuutei | 0:56122e281547 | 153 | |
kuutei | 0:56122e281547 | 154 | } |
kuutei | 0:56122e281547 | 155 | |
kuutei | 0:56122e281547 | 156 | // Reads DS18B20 sense temperature |
kuutei | 0:56122e281547 | 157 | void TemperatureInputThread() |
kuutei | 5:c07438005b16 | 158 | { |
kuutei | 4:f63aab9fec77 | 159 | MCP9808::MCP9808_status_t aux; |
kuutei | 4:f63aab9fec77 | 160 | MCP9808::MCP9808_config_reg_t myMCP9808_Config; |
kuutei | 4:f63aab9fec77 | 161 | MCP9808::MCP9808_data_t myMCP9808_Data; |
kuutei | 4:f63aab9fec77 | 162 | |
kuutei | 4:f63aab9fec77 | 163 | // Shutdown the device, low-power mode enabled |
kuutei | 4:f63aab9fec77 | 164 | aux = myMCP9808.MCP9808_GetCONFIG ( &myMCP9808_Config ); |
kuutei | 4:f63aab9fec77 | 165 | |
kuutei | 4:f63aab9fec77 | 166 | myMCP9808_Config.shdn = MCP9808::CONFIG_SHDN_SHUTDOWN; |
kuutei | 4:f63aab9fec77 | 167 | aux = myMCP9808.MCP9808_SetCONFIG ( myMCP9808_Config ); |
kuutei | 4:f63aab9fec77 | 168 | |
kuutei | 4:f63aab9fec77 | 169 | // Get manufacturer ID |
kuutei | 4:f63aab9fec77 | 170 | aux = myMCP9808.MCP9808_GetManufacturerID ( &myMCP9808_Data ); |
kuutei | 4:f63aab9fec77 | 171 | |
kuutei | 4:f63aab9fec77 | 172 | // Get device ID and device revision |
kuutei | 4:f63aab9fec77 | 173 | aux = myMCP9808.MCP9808_GetDeviceID ( &myMCP9808_Data ); |
kuutei | 4:f63aab9fec77 | 174 | |
kuutei | 4:f63aab9fec77 | 175 | // Configure the device |
kuutei | 4:f63aab9fec77 | 176 | // - T_UPPER and T_LOWER limit hysteresis at 0C |
kuutei | 4:f63aab9fec77 | 177 | // - Continuous conversion mode |
kuutei | 4:f63aab9fec77 | 178 | // - T_CRIT unlocked |
kuutei | 4:f63aab9fec77 | 179 | // - Window lock unlocked |
kuutei | 4:f63aab9fec77 | 180 | // - Alert output control disabled |
kuutei | 4:f63aab9fec77 | 181 | // - Alert output select: Alert for T_UPPER, T_LOWER and T_CRIT |
kuutei | 4:f63aab9fec77 | 182 | // - Alert output polaruty: Active-low |
kuutei | 4:f63aab9fec77 | 183 | // - Alert output mode: Comparator output |
kuutei | 4:f63aab9fec77 | 184 | // |
kuutei | 4:f63aab9fec77 | 185 | myMCP9808_Config.t_hyst = MCP9808::CONFIG_T_HYST_0_C; |
kuutei | 4:f63aab9fec77 | 186 | myMCP9808_Config.shdn = MCP9808::CONFIG_SHDN_CONTINUOUS_CONVERSION; |
kuutei | 4:f63aab9fec77 | 187 | myMCP9808_Config.t_crit = MCP9808::CONFIG_CRIT_LOCK_UNLOCKED; |
kuutei | 4:f63aab9fec77 | 188 | myMCP9808_Config.t_win_lock = MCP9808::CONFIG_WIN_LOCK_UNLOCKED; |
kuutei | 4:f63aab9fec77 | 189 | myMCP9808_Config.alert_cnt = MCP9808::CONFIG_ALERT_CNT_DISABLED; |
kuutei | 4:f63aab9fec77 | 190 | myMCP9808_Config.alert_sel = MCP9808::CONFIG_ALERT_SEL_TUPPER_TLOWER_TCRIT; |
kuutei | 4:f63aab9fec77 | 191 | myMCP9808_Config.alert_pol = MCP9808::CONFIG_ALERT_POL_ACTIVE_LOW; |
kuutei | 4:f63aab9fec77 | 192 | myMCP9808_Config.alert_mod = MCP9808::CONFIG_ALERT_MOD_COMPARATOR_OUTPUT; |
kuutei | 4:f63aab9fec77 | 193 | aux = myMCP9808.MCP9808_SetCONFIG ( myMCP9808_Config ); |
kuutei | 4:f63aab9fec77 | 194 | |
kuutei | 4:f63aab9fec77 | 195 | // Set resolution: +0.0625C ( t_CON ~ 250ms ) |
kuutei | 4:f63aab9fec77 | 196 | myMCP9808_Data.resolution = MCP9808::RESOLUTION_0_0625_C; |
kuutei | 4:f63aab9fec77 | 197 | aux = myMCP9808.MCP9808_SetResolution ( myMCP9808_Data ); |
kuutei | 4:f63aab9fec77 | 198 | |
kuutei | 4:f63aab9fec77 | 199 | |
kuutei | 4:f63aab9fec77 | 200 | while(true) |
kuutei | 4:f63aab9fec77 | 201 | { |
kuutei | 4:f63aab9fec77 | 202 | // Get ambient temperature |
kuutei | 4:f63aab9fec77 | 203 | aux = myMCP9808.MCP9808_GetTA ( &myMCP9808_Data ); |
kuutei | 4:f63aab9fec77 | 204 | |
kuutei | 9:272f6963c3b8 | 205 | //sCLI.printf ( "T: %0.4f C\r\n", myMCP9808_Data.t_a ); |
kuutei | 9:272f6963c3b8 | 206 | temperatureSenseC = myMCP9808_Data.t_a; |
kuutei | 0:56122e281547 | 207 | |
kuutei | 0:56122e281547 | 208 | ThisThread::sleep_for(250); |
kuutei | 4:f63aab9fec77 | 209 | } |
kuutei | 4:f63aab9fec77 | 210 | |
kuutei | 0:56122e281547 | 211 | |
kuutei | 0:56122e281547 | 212 | } |
kuutei | 0:56122e281547 | 213 | |
kuutei | 0:56122e281547 | 214 | // Detect battery presence by voltage activity |
kuutei | 9:272f6963c3b8 | 215 | // TODO: voltage must be above threshold for a minimum time? |
kuutei | 2:18d8f9d3286c | 216 | void BatteryPresenceThread() |
kuutei | 0:56122e281547 | 217 | { |
kuutei | 2:18d8f9d3286c | 218 | while(true) |
kuutei | 0:56122e281547 | 219 | { |
kuutei | 2:18d8f9d3286c | 220 | if(bat_voltage_avg > MIN_DETECT_VOLTAGE) |
kuutei | 2:18d8f9d3286c | 221 | { |
kuutei | 2:18d8f9d3286c | 222 | batteryPresenceState = true; |
kuutei | 2:18d8f9d3286c | 223 | } |
kuutei | 2:18d8f9d3286c | 224 | else |
kuutei | 2:18d8f9d3286c | 225 | { |
kuutei | 2:18d8f9d3286c | 226 | batteryPresenceState = false; |
kuutei | 2:18d8f9d3286c | 227 | } |
kuutei | 2:18d8f9d3286c | 228 | |
kuutei | 2:18d8f9d3286c | 229 | ThisThread::sleep_for(250); |
kuutei | 1:7749656733dd | 230 | } |
kuutei | 0:56122e281547 | 231 | } |
kuutei | 0:56122e281547 | 232 | |
kuutei | 0:56122e281547 | 233 | void TP4056ControlThread() |
kuutei | 0:56122e281547 | 234 | { |
kuutei | 0:56122e281547 | 235 | //uint64_t now = Kernel::get_ms_count(); |
kuutei | 0:56122e281547 | 236 | uint64_t chargeStartTime = 0; |
kuutei | 0:56122e281547 | 237 | |
kuutei | 0:56122e281547 | 238 | bool enableCharging = false; //internal variable to track whether TP4056 CE should be enabled or not |
kuutei | 0:56122e281547 | 239 | |
kuutei | 0:56122e281547 | 240 | |
kuutei | 0:56122e281547 | 241 | while(true) |
kuutei | 0:56122e281547 | 242 | { |
kuutei | 0:56122e281547 | 243 | //First check conditions to see if charging should be enabled or disabled |
kuutei | 9:272f6963c3b8 | 244 | bool temperature_en = ( ( temperatureSenseC < MAX_TEMPERATURE ) && ( temperatureSenseC > MIN_TEMPERATURE ) ); |
kuutei | 9:272f6963c3b8 | 245 | //bool temperature_en = true; //override as sensor code is bugged |
kuutei | 1:7749656733dd | 246 | bool voltage_en = ( ( bat_voltage_avg < MAX_VOLTAGE ) && ( bat_voltage_avg > MIN_VOLTAGE ) ); |
kuutei | 2:18d8f9d3286c | 247 | bool presence_en = batteryPresenceState; |
kuutei | 5:c07438005b16 | 248 | bool charge_time_exceeded = ( chargingTimePassed > MAX_TIME_ms ); |
kuutei | 1:7749656733dd | 249 | |
kuutei | 0:56122e281547 | 250 | //Charging can be enabled if battery is present, no protections triggered, and user starts the charge |
kuutei | 0:56122e281547 | 251 | if(enableCharging == false) |
kuutei | 9:272f6963c3b8 | 252 | { |
kuutei | 9:272f6963c3b8 | 253 | //button must be pressed to start charge, but can also be pressed when eg battery not inserted |
kuutei | 9:272f6963c3b8 | 254 | if(buttonPressed) |
kuutei | 9:272f6963c3b8 | 255 | { |
kuutei | 9:272f6963c3b8 | 256 | if( voltage_en && temperature_en && presence_en ) |
kuutei | 9:272f6963c3b8 | 257 | { |
kuutei | 9:272f6963c3b8 | 258 | enableCharging = true; |
kuutei | 9:272f6963c3b8 | 259 | chargeStartTime = Kernel::get_ms_count(); |
kuutei | 9:272f6963c3b8 | 260 | } |
kuutei | 9:272f6963c3b8 | 261 | |
kuutei | 9:272f6963c3b8 | 262 | //regardless of if charging was started, acknowledge the button press |
kuutei | 1:7749656733dd | 263 | buttonPressed = false; |
kuutei | 1:7749656733dd | 264 | } |
kuutei | 9:272f6963c3b8 | 265 | |
kuutei | 1:7749656733dd | 266 | } |
kuutei | 1:7749656733dd | 267 | //Charging must be stopped if overvoltage, overtemperature, overtime, battery removed, or user pushes button |
kuutei | 5:c07438005b16 | 268 | //Charging time passed is maintained, but will be reset if user starts charge again |
kuutei | 5:c07438005b16 | 269 | //TODO: Only reset charge time once battery removed? |
kuutei | 1:7749656733dd | 270 | else |
kuutei | 0:56122e281547 | 271 | { |
kuutei | 1:7749656733dd | 272 | //Disable charging if any protection condition is triggered |
kuutei | 2:18d8f9d3286c | 273 | if( !voltage_en || !temperature_en || !presence_en || charge_time_exceeded ) |
kuutei | 1:7749656733dd | 274 | { |
kuutei | 1:7749656733dd | 275 | enableCharging = false; |
kuutei | 1:7749656733dd | 276 | } |
kuutei | 1:7749656733dd | 277 | //or if user pushed button |
kuutei | 1:7749656733dd | 278 | else if(buttonPressed) |
kuutei | 3:0bad8eec80ee | 279 | { |
kuutei | 3:0bad8eec80ee | 280 | enableCharging = false; |
kuutei | 1:7749656733dd | 281 | buttonPressed = false; |
kuutei | 1:7749656733dd | 282 | } |
kuutei | 5:c07438005b16 | 283 | |
kuutei | 0:56122e281547 | 284 | } |
kuutei | 0:56122e281547 | 285 | |
kuutei | 0:56122e281547 | 286 | |
kuutei | 0:56122e281547 | 287 | //With charge state calculated, realize it on the CE pin |
kuutei | 5:c07438005b16 | 288 | //Allow pullup to '5V' to enable charging at CE pin |
kuutei | 0:56122e281547 | 289 | if(enableCharging) |
kuutei | 0:56122e281547 | 290 | { |
kuutei | 5:c07438005b16 | 291 | //Using HiZ still results in internal clamping diode activating, resulting in 3.6v |
kuutei | 9:272f6963c3b8 | 292 | tp4056ChipEnable.write(1); //open drain mode -> HiZ |
kuutei | 5:c07438005b16 | 293 | |
kuutei | 5:c07438005b16 | 294 | //Continually update surpassed charging time if it is enabled |
kuutei | 5:c07438005b16 | 295 | chargingTimePassed = Kernel::get_ms_count() - chargeStartTime ; |
kuutei | 0:56122e281547 | 296 | } |
kuutei | 5:c07438005b16 | 297 | //To disable charging, open drain the CE pin -> 0V |
kuutei | 0:56122e281547 | 298 | else |
kuutei | 0:56122e281547 | 299 | { |
kuutei | 9:272f6963c3b8 | 300 | tp4056ChipEnable.write(0); //open drain, pull to GND -> overpull 470k pullup that brings 5V to CE |
kuutei | 0:56122e281547 | 301 | } |
kuutei | 0:56122e281547 | 302 | |
kuutei | 0:56122e281547 | 303 | //Update flag that indicates state of TP4056 CE pin to other threads |
kuutei | 1:7749656733dd | 304 | TP4056ChargingState = enableCharging; |
kuutei | 0:56122e281547 | 305 | |
kuutei | 2:18d8f9d3286c | 306 | ThisThread::sleep_for(100); |
kuutei | 0:56122e281547 | 307 | } |
kuutei | 0:56122e281547 | 308 | } |
kuutei | 0:56122e281547 | 309 | |
kuutei | 0:56122e281547 | 310 | void oledOutputThread() |
kuutei | 0:56122e281547 | 311 | { |
kuutei | 5:c07438005b16 | 312 | std::stringstream volts_stream, max_volts_stream; |
kuutei | 5:c07438005b16 | 313 | std::stringstream temperature_stream; |
kuutei | 5:c07438005b16 | 314 | std::stringstream chargetimepassed_stream; |
kuutei | 5:c07438005b16 | 315 | std::stringstream chargetimemax_stream; |
kuutei | 5:c07438005b16 | 316 | |
kuutei | 0:56122e281547 | 317 | while(true) |
kuutei | 5:c07438005b16 | 318 | { |
kuutei | 5:c07438005b16 | 319 | volts_stream.str(""); |
kuutei | 5:c07438005b16 | 320 | volts_stream << std::fixed << std::setprecision(2) << bat_voltage_avg; |
kuutei | 5:c07438005b16 | 321 | |
kuutei | 5:c07438005b16 | 322 | max_volts_stream.str(""); |
kuutei | 5:c07438005b16 | 323 | max_volts_stream << std::fixed << std::setprecision(2) << MAX_VOLTAGE; |
kuutei | 5:c07438005b16 | 324 | |
kuutei | 5:c07438005b16 | 325 | temperature_stream.str(""); |
kuutei | 9:272f6963c3b8 | 326 | temperature_stream << std::fixed << std::setprecision(2) << temperatureSenseC; |
kuutei | 5:c07438005b16 | 327 | |
kuutei | 5:c07438005b16 | 328 | chargetimepassed_stream.str(""); |
kuutei | 5:c07438005b16 | 329 | chargetimepassed_stream << std::fixed << std::setprecision(1) << (float(chargingTimePassed)/1000/60); |
kuutei | 5:c07438005b16 | 330 | |
kuutei | 5:c07438005b16 | 331 | chargetimemax_stream.str(""); |
kuutei | 5:c07438005b16 | 332 | chargetimemax_stream << std::fixed << std::setprecision(1) << (float(MAX_TIME_ms)/1000/60); |
kuutei | 5:c07438005b16 | 333 | |
kuutei | 5:c07438005b16 | 334 | std::string str_temp = ""; |
kuutei | 5:c07438005b16 | 335 | |
kuutei | 5:c07438005b16 | 336 | //clear the screen first |
kuutei | 0:56122e281547 | 337 | oled_i2c.clear(); |
kuutei | 0:56122e281547 | 338 | |
kuutei | 5:c07438005b16 | 339 | //If no battery present, show screen indicating one should be inserted |
kuutei | 5:c07438005b16 | 340 | if( !batteryPresenceState ) |
kuutei | 5:c07438005b16 | 341 | { |
kuutei | 5:c07438005b16 | 342 | oled_i2c.setFont(ArialMT_Plain_16); |
kuutei | 5:c07438005b16 | 343 | oled_i2c.drawString(0, 0, "Insert battery"); |
kuutei | 5:c07438005b16 | 344 | |
kuutei | 5:c07438005b16 | 345 | str_temp = volts_stream.str() + "v " + temperature_stream.str() + "ºC"; |
kuutei | 5:c07438005b16 | 346 | oled_i2c.drawString(0,16, str_temp.c_str() ); |
kuutei | 5:c07438005b16 | 347 | |
kuutei | 0:56122e281547 | 348 | } |
kuutei | 5:c07438005b16 | 349 | //battery present, show screen with voltage, CE status, temperature |
kuutei | 5:c07438005b16 | 350 | //Further splits into charge enabled or disabled - if charge enabled, show time so far and time limit |
kuutei | 5:c07438005b16 | 351 | else |
kuutei | 5:c07438005b16 | 352 | { |
kuutei | 5:c07438005b16 | 353 | oled_i2c.setFont(ArialMT_Plain_16); |
kuutei | 5:c07438005b16 | 354 | if(TP4056ChargingState){ |
kuutei | 5:c07438005b16 | 355 | str_temp = "Charge to " + max_volts_stream.str(); |
kuutei | 5:c07438005b16 | 356 | oled_i2c.drawString(0, 0, str_temp.c_str()); |
kuutei | 5:c07438005b16 | 357 | //TODO: shows 'charge complete' if above threshold |
kuutei | 5:c07438005b16 | 358 | } else { |
kuutei | 5:c07438005b16 | 359 | oled_i2c.drawString(0, 0, "Push to charge"); |
kuutei | 5:c07438005b16 | 360 | } |
kuutei | 5:c07438005b16 | 361 | |
kuutei | 5:c07438005b16 | 362 | str_temp = volts_stream.str() + "v " + temperature_stream.str() + "ºC"; |
kuutei | 5:c07438005b16 | 363 | oled_i2c.drawString(0, 16, str_temp.c_str() ); |
kuutei | 5:c07438005b16 | 364 | |
kuutei | 5:c07438005b16 | 365 | str_temp = "T: " + chargetimepassed_stream.str() + "m/" + chargetimemax_stream.str() + "m"; |
kuutei | 5:c07438005b16 | 366 | oled_i2c.drawString(0,16*2, str_temp.c_str() ); |
kuutei | 5:c07438005b16 | 367 | |
kuutei | 5:c07438005b16 | 368 | |
kuutei | 5:c07438005b16 | 369 | //std::string voltage_output = "Voltage: "+std::to_string(bat_voltage); |
kuutei | 5:c07438005b16 | 370 | //str_temp = "Voltage: " + volts_stream.str(); |
kuutei | 5:c07438005b16 | 371 | ///oled_i2c.drawString(0, 16, str_temp.c_str() ); |
kuutei | 5:c07438005b16 | 372 | //str_temp = "Temp: " + temperature_stream.str(); |
kuutei | 5:c07438005b16 | 373 | //oled_i2c.drawString(0,16*2, str_temp.c_str() ); |
kuutei | 5:c07438005b16 | 374 | |
kuutei | 5:c07438005b16 | 375 | } |
kuutei | 5:c07438005b16 | 376 | oled_i2c.display(); |
kuutei | 0:56122e281547 | 377 | |
kuutei | 0:56122e281547 | 378 | ThisThread::sleep_for(250); |
kuutei | 0:56122e281547 | 379 | } |
kuutei | 0:56122e281547 | 380 | |
kuutei | 0:56122e281547 | 381 | } |
kuutei | 0:56122e281547 | 382 | |
kuutei | 0:56122e281547 | 383 | int main() |
kuutei | 0:56122e281547 | 384 | { |
kuutei | 9:272f6963c3b8 | 385 | //sCLI.baud(115200); |
kuutei | 9:272f6963c3b8 | 386 | sCLI.printf("Started\r\n"); |
kuutei | 0:56122e281547 | 387 | |
kuutei | 0:56122e281547 | 388 | //BUILT IN LED THREAD |
kuutei | 0:56122e281547 | 389 | Thread led1; |
kuutei | 0:56122e281547 | 390 | //osStatus err = led1.start(&blinkled); |
kuutei | 0:56122e281547 | 391 | led1.start(blinkled); |
kuutei | 0:56122e281547 | 392 | |
kuutei | 0:56122e281547 | 393 | // CONFIGURE TP4056 CE CONTROL FOR OPEN DRAIN WITH EXTERNAL 5V PULLUP |
kuutei | 0:56122e281547 | 394 | //https://forums.mbed.com/t/how-to-configure-open-drain-output-pin-on-stm32/7007 |
kuutei | 9:272f6963c3b8 | 395 | tp4056ChipEnable.mode(OpenDrainNoPull); |
kuutei | 0:56122e281547 | 396 | |
kuutei | 2:18d8f9d3286c | 397 | // TEMPERATURE INPUT THREAD |
kuutei | 9:272f6963c3b8 | 398 | Thread temperatureSenseC_thread; |
kuutei | 9:272f6963c3b8 | 399 | temperatureSenseC_thread.start(TemperatureInputThread); |
kuutei | 2:18d8f9d3286c | 400 | |
kuutei | 0:56122e281547 | 401 | // CHARGE ENABLE CONTROL THREAD |
kuutei | 0:56122e281547 | 402 | Thread tp4056_control_thread; |
kuutei | 0:56122e281547 | 403 | tp4056_control_thread.start(TP4056ControlThread); |
kuutei | 0:56122e281547 | 404 | |
kuutei | 2:18d8f9d3286c | 405 | // PRESENCE DETECTION THREAD |
kuutei | 2:18d8f9d3286c | 406 | Thread bat_presence_thread; |
kuutei | 2:18d8f9d3286c | 407 | bat_presence_thread.start(BatteryPresenceThread); |
kuutei | 2:18d8f9d3286c | 408 | |
kuutei | 0:56122e281547 | 409 | //OLED 128x64 INIT AND THREAD |
kuutei | 0:56122e281547 | 410 | //initialize ssd1306 on i2c0 |
kuutei | 0:56122e281547 | 411 | oled_i2c.init(); |
kuutei | 9:272f6963c3b8 | 412 | //oled_i2c.flipScreenVertically(); |
kuutei | 0:56122e281547 | 413 | oled_i2c.setFont(ArialMT_Plain_16); |
kuutei | 0:56122e281547 | 414 | oled_i2c.drawString(0,0,"init!"); |
kuutei | 9:272f6963c3b8 | 415 | oled_i2c.setBrightness(64); |
kuutei | 0:56122e281547 | 416 | oled_i2c.display(); |
kuutei | 0:56122e281547 | 417 | |
kuutei | 0:56122e281547 | 418 | Thread oled_thread; |
kuutei | 0:56122e281547 | 419 | oled_thread.start(oledOutputThread); |
kuutei | 0:56122e281547 | 420 | |
kuutei | 0:56122e281547 | 421 | // START/STOP CHARGE BUTTON |
kuutei | 0:56122e281547 | 422 | chargeButton.rise(&buttonISR); |
kuutei | 0:56122e281547 | 423 | |
kuutei | 0:56122e281547 | 424 | |
kuutei | 0:56122e281547 | 425 | float bat_voltage_min; |
kuutei | 0:56122e281547 | 426 | float bat_voltage_max; |
kuutei | 0:56122e281547 | 427 | float bat_voltage_avg_sum; |
kuutei | 0:56122e281547 | 428 | |
kuutei | 0:56122e281547 | 429 | uint64_t now = Kernel::get_ms_count(); |
kuutei | 9:272f6963c3b8 | 430 | uint64_t lastADCAverageStartTime = now; |
kuutei | 9:272f6963c3b8 | 431 | uint64_t lastADCReadStartTime = now; |
kuutei | 9:272f6963c3b8 | 432 | uint64_t time_tmp = 0; |
kuutei | 0:56122e281547 | 433 | |
kuutei | 0:56122e281547 | 434 | while(true) |
kuutei | 0:56122e281547 | 435 | { |
kuutei | 0:56122e281547 | 436 | // reset min and max values |
kuutei | 0:56122e281547 | 437 | bat_voltage_min = 10.0; |
kuutei | 0:56122e281547 | 438 | bat_voltage_max = -10.0; |
kuutei | 0:56122e281547 | 439 | bat_voltage_avg_sum = 0.0; |
kuutei | 0:56122e281547 | 440 | |
kuutei | 9:272f6963c3b8 | 441 | lastADCAverageStartTime = Kernel::get_ms_count(); |
kuutei | 5:c07438005b16 | 442 | for(int i = 0; i < BAT_SAMPLERATE; i++) |
kuutei | 0:56122e281547 | 443 | { |
kuutei | 9:272f6963c3b8 | 444 | lastADCReadStartTime = Kernel::get_ms_count(); |
kuutei | 9:272f6963c3b8 | 445 | |
kuutei | 0:56122e281547 | 446 | bat_voltage = readVoltageDivider_Calibrated(); |
kuutei | 0:56122e281547 | 447 | vddref_voltage = readRefVoltage(); |
kuutei | 0:56122e281547 | 448 | bat_voltage_avg_sum += bat_voltage; |
kuutei | 0:56122e281547 | 449 | if( bat_voltage < bat_voltage_min ) |
kuutei | 0:56122e281547 | 450 | { |
kuutei | 0:56122e281547 | 451 | bat_voltage_min = bat_voltage; |
kuutei | 0:56122e281547 | 452 | } |
kuutei | 0:56122e281547 | 453 | if( bat_voltage > bat_voltage_max ) |
kuutei | 0:56122e281547 | 454 | { |
kuutei | 0:56122e281547 | 455 | bat_voltage_max = bat_voltage; |
kuutei | 0:56122e281547 | 456 | } |
kuutei | 0:56122e281547 | 457 | |
kuutei | 5:c07438005b16 | 458 | //sleep appropriate interval to meet specified sample rate, evenly spaced over 1s |
kuutei | 5:c07438005b16 | 459 | //This sleep is also where the other threads run |
kuutei | 9:272f6963c3b8 | 460 | //sleep amount calculated as: (nominal time between bat voltage reads) - (time it has taken to do the current measurement) |
kuutei | 9:272f6963c3b8 | 461 | time_tmp = 1000/BAT_SAMPLERATE - (lastADCReadStartTime - Kernel::get_ms_count()) ; |
kuutei | 9:272f6963c3b8 | 462 | //if we have already exceeded our time window, sleep nominal time as sample rate is not possible to meet |
kuutei | 9:272f6963c3b8 | 463 | if( time_tmp < 0 ) |
kuutei | 9:272f6963c3b8 | 464 | { |
kuutei | 9:272f6963c3b8 | 465 | ThisThread::sleep_for(1000/BAT_SAMPLERATE); |
kuutei | 9:272f6963c3b8 | 466 | } |
kuutei | 9:272f6963c3b8 | 467 | else |
kuutei | 9:272f6963c3b8 | 468 | { |
kuutei | 9:272f6963c3b8 | 469 | //otherwise, sleep the remaining time in our window to try to exactly meet our sample rate |
kuutei | 9:272f6963c3b8 | 470 | ThisThread::sleep_for(time_tmp); |
kuutei | 9:272f6963c3b8 | 471 | } |
kuutei | 0:56122e281547 | 472 | } |
kuutei | 9:272f6963c3b8 | 473 | |
kuutei | 0:56122e281547 | 474 | now = Kernel::get_ms_count(); |
kuutei | 5:c07438005b16 | 475 | bat_voltage_avg = bat_voltage_avg_sum / BAT_SAMPLERATE; |
kuutei | 0:56122e281547 | 476 | |
kuutei | 5:c07438005b16 | 477 | //TODO: Dedicated serial thread, output messages when conditions change (eg OTP, OVP, charge started/stopped) |
kuutei | 9:272f6963c3b8 | 478 | sCLI.printf("\r\nADC0 Last Reading: %6.4f\r\n", bat_voltage); |
kuutei | 9:272f6963c3b8 | 479 | sCLI.printf("ADC0 1s min: %6.4f\r\n", bat_voltage_min); |
kuutei | 9:272f6963c3b8 | 480 | sCLI.printf("ADC0 1s max: %6.4f\r\n", bat_voltage_max); |
kuutei | 9:272f6963c3b8 | 481 | sCLI.printf("ADC0 1s avg: %6.4f\r\n", bat_voltage_avg); |
kuutei | 9:272f6963c3b8 | 482 | sCLI.printf("ADC0 time (nominal 1000ms): %llu\r\n", (now-lastADCAverageStartTime)); |
kuutei | 9:272f6963c3b8 | 483 | sCLI.printf("VDDREF Reading: %6.4f\r\n", vddref_voltage); |
kuutei | 9:272f6963c3b8 | 484 | sCLI.printf("Temp: %6.4f\r\n", temperatureSenseC); |
kuutei | 9:272f6963c3b8 | 485 | sCLI.printf("Bat present: %d\r\n", batteryPresenceState); |
kuutei | 5:c07438005b16 | 486 | |
kuutei | 0:56122e281547 | 487 | } |
kuutei | 0:56122e281547 | 488 | |
kuutei | 0:56122e281547 | 489 | } |