Snippets
Revised by
Peter Scargill
f8b2e32
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 | #include <Wire.h>
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"
#include <EEPROM.h>
// WORK IN PROGRESS ONLY - PROBABLY WON'T WORK
// Very first attempt at a backup system that "does the job"
// Peter Scargill 2017 - https://tech.scargill.net
//
// So this powers up with an SSD1306 display, showing the
// battery voltage (needs dividers - I used 10k and 2k2) to an
// analog in - voltage calc routine needs tweaking
// averages voltage to avoid spikes and save hardware filtering
//
// Starts up - if voltage above upper threshhold for a while turns
// output bit on - that controls a P-channel mosfet on output
// of booster - power board from booster - OR power board from battery
// and control booster chip directly
// There is a warning bit, normally low - to warn micro of impending
// shutdown
//
// Shuts down power after giving load time to respond and turn off
// also shuts down after this point EVEN if the power came back on
// after giving the processor time if it was JUST after warning bit went
// on/ While off uses minimal display area to save current (OLED)
// Then turns back on when battery is up to it. BIG hysteresis between
// shutdown voltage and wakeup voltage
//
// Additional functionality:
// - long-press UP to trigger orderly shutdown. In that state press DOWN to restore
// - long-press DOWN to trigger test voltage reset. Sets average voltage to ZERO
// temporarily - this triggers warning and shutdown - and eventual reboot
//
// Blog has more - as does this video https://www.youtube.com/watch?v=44Lvdf7o4GQ
// Now set up for the 32-pixel displays - got everything down to 4 lines
#define VER "v1.06"
struct
{
uint16_t check;
float highV;
float lowV;
uint8_t startTimeout;
uint8_t goodTimeout;
uint8_t warningTimeout;
uint8_t offTimeout;
int16_t batteryOffset;
} eeSave;
// Device address for the SSD1306 display
#define I2C_ADDRESS 0x3C
#define OPTIMIZE_I2C 1
SSD1306AsciiWire oled;
#define POWER 12 // inverted - ON - power available for my cheap version
#define WARNING 5 // inverted
#define POWERUP_STATE 0
#define GOOD_STATE 1
#define TIMEOUT_STATE 2
#define OFF_STATE 3
#define SHUTDOWN_STATE 4
#define WAITING 60 // wait before actually turning off "relay" after
#define ONDELAY 20 // delay before turning on
#define SWITCHON 3.7 // defaults for hysteresis voltage
#define SWITCHOFF 3.1
#define LONGPRESS_UP 60 // 10ths of second before long presson UP triggers shutdown
#define LONGPRESS_DOWN 60 // 10ths of second before battery fail test
#define SHUTDOWN_PERIOD 20
#define SET 4
#define UP 6
#define DOWN 7
#define VOLTAGE A7
uint32_t secs = 0;
uint32_t upSecs = 0;
uint32_t downSecs = 0;
uint16_t longpressedUp = 0 ;
uint16_t longpressedDown = 0;
uint8_t triggerShutdown =0;
uint32_t mySecs=0;
void LcdP(char *fmt, ...)
{
char buf[128]; // resulting string limited to 128 chars
va_list args;
va_start(args, fmt);
vsnprintf(buf, 128, fmt, args);
va_end(args);
oled.print(buf);
}
uint32_t mymillis = 0;
uint32_t my100millis = 0;
uint8_t lightState = 0;
uint8_t state = 0; // 0 is power up, 1 is on, 2 is turning off
uint8_t stateCounter = 0; // might want to stay in a state for some time
uint8_t startCounter = 0; // startup fallover into OFF state
float vol;
uint8_t onDelay = 0;
uint16_t average;
uint8_t updateTimeout = 0;
char *theState = "standby";
uint8_t instate = 0;
uint8_t updated = 0;
uint8_t sKeypressed = 0;
uint8_t uKeypressed = 0;
uint8_t dKeypressed = 0;
void p(char *fmt, ...)
{
char buf[128]; // resulting string limited to 128 chars
va_list args;
va_start(args, fmt);
vsnprintf(buf, 128, fmt, args);
va_end(args);
Serial.print(buf);
}
void gotoXY(int x, int y)
{
oled.setCursor(x, y);
}
//------------------------------------------------------------------------------
void setup()
{
EEPROM.get(0, eeSave);
analogReference(INTERNAL);
if (eeSave.check != 0x52d2)
{
eeSave.check = 0x52d2;
eeSave.highV = SWITCHON;
eeSave.lowV = SWITCHOFF;
eeSave.startTimeout = 5;
eeSave.goodTimeout = 5;
eeSave.warningTimeout = 20;
eeSave.offTimeout = 20;
eeSave.batteryOffset=400;
EEPROM.put(0, eeSave);
}
Wire.begin();
oled.begin(&Adafruit128x32, I2C_ADDRESS);
oled.set400kHz();
oled.setFont(Adafruit5x7);
uint32_t m = micros();
#define POWERON LOW
#define POWEROFF HIGH
#define WARNINGON LOW
#define WARNINGOFF HIGH
digitalWrite(POWER, POWEROFF); pinMode(POWER, OUTPUT);
digitalWrite(WARNING, WARNINGOFF); pinMode(WARNING, OUTPUT);
pinMode(SET, INPUT_PULLUP);
pinMode(UP, INPUT_PULLUP);
pinMode(DOWN, INPUT_PULLUP);
average = analogRead(VOLTAGE); // we start off with read value;
oled.clear();
my100millis = millis() + 1000;
state = 0;
stateCounter = eeSave.startTimeout;
}
void showBattery(void)
{
LcdP("%d.%02dv", int(vol), ((int(vol * 100)) % 100)); oled.clearToEOL();
}
//------------------------------------------------------------------------------
void loop()
{
// main loop
if (my100millis <= millis()) // 100ms for key check
{
my100millis += 100;
if (digitalRead(SET) == 0)
{
if (sKeypressed == 0)
{
sKeypressed = 1;
instate++;
updated = 0;
updateTimeout = 100;
}
}
else
sKeypressed = 0;
if (digitalRead(UP) == 0) // special case long press is reset...
{
if (uKeypressed == 0)
{
uKeypressed = 1;
switch (instate)
{
case 1 : if (eeSave.highV < 4.3) eeSave.highV += 0.1; break;
case 2 : if (eeSave.lowV < 4.2) eeSave.lowV += 0.1; break;
case 3 : eeSave.startTimeout++; break;
case 4 : eeSave.goodTimeout++; break;
case 5 : eeSave.warningTimeout++; break;
case 6 : eeSave.offTimeout++; break;
case 7 : eeSave.batteryOffset++; break;
}
updated = 0;
updateTimeout = 100;
}
else
{
if (++longpressedUp==LONGPRESS_UP) triggerShutdown=1;
}
}
else
{ uKeypressed = 0; longpressedUp = 0; }
if (digitalRead(DOWN) == 0)
{
if (dKeypressed == 0)
{
dKeypressed = 1;
switch (instate)
{
case 1 : if (eeSave.highV > 2.3) eeSave.highV -= 0.1; break;
case 2 : if (eeSave.lowV > 2.2) eeSave.lowV -= 0.1; break;
case 3 : eeSave.startTimeout--; break;
case 4 : eeSave.goodTimeout--; break;
case 5 : eeSave.warningTimeout--; break;
case 6 : eeSave.offTimeout--; break;
case 7 : eeSave.batteryOffset--; break;
}
updated = 0;
updateTimeout = 100;
}
else
{
if (++longpressedDown==LONGPRESS_DOWN) average=0; // temporarily drop the voltage and hence trigger warning
}
}
else
{ dKeypressed = 0; longpressedDown = 0; }
if (eeSave.highV <= eeSave.lowV) eeSave.highV = eeSave.lowV + 0.1;
if (eeSave.lowV >= eeSave.highV) eeSave.lowV = eeSave.highV - 0.1;
switch (instate)
{
case 0: if (updated == 0)
{
gotoXY(0, 3);
oled.clearToEOL();
updated = 1;
}
break;
case 1: if (updated == 0)
{
gotoXY(0, 3);
LcdP("SET: High V=%d.%d ", int(eeSave.highV), ((int(eeSave.highV * 100)) % 100) / 10); oled.clearToEOL();
updated = 1;
}
break;
case 2: if (updated == 0)
{
gotoXY(0, 3);
LcdP("SET: Low V=%d.%d ", int(eeSave.lowV), ((int(eeSave.lowV * 100)) % 100) / 10); oled.clearToEOL();
updated = 1;
}
break;
case 3: if (updated == 0)
{
gotoXY(0, 3);
LcdP("SET: Start %d secs",eeSave.startTimeout); oled.clearToEOL();
updated = 1;
}
break;
case 4: if (updated == 0)
{
gotoXY(0, 3);
LcdP("SET: Good %d secs",eeSave.goodTimeout); oled.clearToEOL();
updated = 1;
}
break;
case 5: if (updated == 0)
{
gotoXY(0, 3);
LcdP("SET: Warn %d secs",eeSave.warningTimeout); oled.clearToEOL();
updated = 1;
}
break;
case 6: if (updated == 0)
{
gotoXY(0, 3);
LcdP("SET: Off %d secs",eeSave.offTimeout); oled.clearToEOL();
updated = 1;
}
break;
case 7: if (updated == 0)
{
gotoXY(0, 3);
LcdP("SET: Battery %d",eeSave.batteryOffset-400); oled.clearToEOL();
updated = 1;
}
break;
case 8: if (updated == 0)
{
gotoXY(0, 3);
LcdP("Version: %s",VER); oled.clearToEOL();
updated = 1;
}
break;
case 9: updated = 0; instate = 0; break;
}
if (updateTimeout)
{
if (--updateTimeout == 0)
{
instate = 0;
updated = 0;
EEPROM.put(0, eeSave);
}
}
}
// main 1 second loop here for determining where we are
if (mymillis <= millis())
{
mymillis += 1000; // every second we do this
mySecs++;
average = ((average * 7) + analogRead(VOLTAGE)) / 8; //i.e. average over 8 seconds 0-1023 = 0-5v - might make testing longer in the end
vol = (((float)average) * 5.0 / 824.0) * (float)eeSave.batteryOffset/400.0; //10k/2k2
if (triggerShutdown) { triggerShutdown=0; state=SHUTDOWN_STATE; stateCounter=SHUTDOWN_PERIOD; }
switch (state)
{
case POWERUP_STATE : // powered up everything off
if (stateCounter == eeSave.startTimeout) { gotoXY(0, 0); LcdP("Starting up"); oled.clearToEOL(); }
--stateCounter;
gotoXY(0, 2); LcdP("Battery: "); showBattery();
gotoXY(0, 3); LcdP("Wait %03d secs",stateCounter); oled.clearToEOL();
digitalWrite(WARNING, WARNINGOFF); // no warning
digitalWrite(POWER, POWEROFF);
if (vol > eeSave.highV)
{
if (stateCounter == 0) {
stateCounter = eeSave.goodTimeout;
state = GOOD_STATE;
}
} else
{
if (stateCounter==0) {
stateCounter = eeSave.offTimeout;
state = OFF_STATE;
}
}
break;
case GOOD_STATE : // battery is above upper threshold
if (stateCounter == eeSave.goodTimeout) { gotoXY(0, 0); LcdP("** Pi Power %s **",VER); oled.clearToEOL(); }
--stateCounter;
gotoXY(0, 1); LcdP("Ok. Battery: "); showBattery();
gotoXY(0, 2); LcdP("High: %d.%dv Low: %d.%dv", int(eeSave.highV), ((int(eeSave.highV * 100)) % 100) / 10, int(eeSave.lowV), ((int(eeSave.lowV * 100)) % 100) / 10); oled.clearToEOL();
digitalWrite(WARNING, WARNINGOFF); // no warning
digitalWrite(POWER, POWERON);
if (vol < eeSave.lowV)
{
gotoXY(0, 3); LcdP("Wait %03d secs",stateCounter); oled.clearToEOL();
if (stateCounter == 0) {
stateCounter = eeSave.warningTimeout;
state = TIMEOUT_STATE;
}
} else
{
stateCounter = eeSave.goodTimeout-1;
if ((instate==0) && (vol >= eeSave.lowV))
{
gotoXY(0, 3);
if (mySecs&2) { LcdP("On: %04ldd %02ld:%02ld:%02ld", upSecs / 86400, (upSecs / 3600) % 24, (upSecs % 3600) / 60, upSecs % 60); oled.clearToEOL(); }
else { LcdP("Off: %04ldd %02ld:%02ld:%02ld", downSecs / 86400, (downSecs / 3600) % 24 , (downSecs % 3600) / 60, downSecs % 60); oled.clearToEOL(); }
}
}
++upSecs;
break;
case TIMEOUT_STATE : // battery has dropped below upper threshold - ALWAYS timeout to OFF_STATE
gotoXY(0, 2); LcdP("Timing out. B: "); showBattery();
gotoXY(0, 3); LcdP("Wait %03d secs",stateCounter); oled.clearToEOL();
digitalWrite(WARNING, WARNINGON); // warning
digitalWrite(POWER, POWERON);
--stateCounter;
if (stateCounter == 0 ) {
stateCounter=eeSave.offTimeout;
state = OFF_STATE;
}
++upSecs;
break;
case OFF_STATE : // everything off
if (stateCounter == eeSave.offTimeout) oled.clear();
--stateCounter;
gotoXY(0, 2); LcdP("Stdby. V="); showBattery();
digitalWrite(WARNING, WARNINGON); // LEAVE ON ie LOW
digitalWrite(POWER, POWEROFF);
if (vol > eeSave.highV)
{
gotoXY(0, 3); LcdP("Wait %03d secs",stateCounter); oled.clearToEOL();
if (stateCounter == 0) {
stateCounter = eeSave.goodTimeout;
state = GOOD_STATE;
}
} else
{
stateCounter = eeSave.offTimeout-1; // avoid clearscreen
gotoXY(0, 3); oled.clearToEOL();
}
++downSecs;
break;
case SHUTDOWN_STATE: // everything off and stays off... hold the SET button.... set state to SHUTDOWN_STATE and stateCounter to SHUTDOWN_PERIOD
if (stateCounter==SHUTDOWN_PERIOD)
{
oled.clear();
gotoXY(0, 2); LcdP("Shutdown commencing.."); oled.clearToEOL();
digitalWrite(WARNING, WARNINGON); // warning
}
if (stateCounter)
{--stateCounter;
if (stateCounter==0)
{
gotoXY(0, 2); LcdP("Shutdown COMPLETE."); oled.clearToEOL();
digitalWrite(WARNING, WARNINGON); // LEAVE ON ie LOW
digitalWrite(POWER, POWEROFF);
}
}
else if (digitalRead(DOWN) == 0) { oled.clear(); state=POWERUP_STATE; stateCounter = eeSave.startTimeout; }
}
}
}
|
You can clone a snippet to your computer for local editing. Learn more.