If you cut the power on the Vin pin, how long does the arduino (uno or nano) keep running under normal circumstances? (assuming the 5v circuit does not feed external components).
I always forget to implement a save-settings-upon-power down. And some projects could use such a feature. The idea is that I use an input pin to monitor the power supply. I thought that a write cycle to an EEPROM takes around 3.5ms?.
You could power via the 5v pin and the power supply you have to drive that could have large capacitors to hold the supply up much longer .( dont put a big cap on the 5v line AND power via Vin as the in rush may damage your board .
I've done this with a 1000uF capacitor, a diode, as well as a 5v voltage supervisor IC, with the supervisor's output connected to an input pin of the Arduino. The supervisor alerts the Arduino when it is losing power. Will try pull up the schematic I made for it later.
Edit: here's the schematic. The positioning of the diode and the capacitor has been mentioned by others in this thread. I believe you can also do the low voltage detection by measuring the 5v supply with an analog input pin of the Arduino, instead of using the external supervisor.
Further edit: I had a further read into these supervisors, and the TL7705 supervisor in my diagram would definitely work fine, except it includes an internal timer, which I think is pointless for this application.
Therefore I would use a cheaper 3-pin voltage detector, like TS831-4, with 4.5v threshold voltage. Untested schematic is below, along with further clarification on how you'd power other components (e.g. LCD) before the diode, in order to give the Arduino more time to save variable's.
Advantage of the supervisor circuit is, that digital signal could drive an interrupt pin, ensuring that instead of having to tight-poll your DI, you can go about your normal business, knowing the interrupt will 'save the day' if your power goes down. Of course, some precautions might be in order to ensure your interrupt doesn't inadvertently change data that's in the midst of a change, but that's just common sense.
The AVR chips have a built-in brown-out detector which is specifically for detecting power turning off, and firing an interrupt. An external supervisor chip is not required.
I've got it to work but only a few bytes at a time. The largest value cap I tried was 10uF and didn't notice any great help with that. I'll have to try a bigger cap and see how that works. I also have a 1602 LCD w backpack on the Arduino supply and I'm sure that draws the five volts down faster.
Here's the current sketch for my experiments:
/*
Analog comparator demo. Bandgap reference and AIN1 on D7
Board = UNO
Comparator settings based on Nick Gammon's page:
http://www.gammon.com.au/forum/?id=11916
Adding a jumper to select loading the array versus restoring
from EEPROM and verifying.
The first time the program is run stateCount will be 255 (out of range)
(assuming no prior use of the EEPROM). When the button is pressed
stateCount will cycle through three values, 0-2. Zero = OFF, one = slow
flash, two = fast flash. When power is lost stateCount is saved to
EEPROM by the ANALOG_COMP_vect interrupt service routine. When
power returns stateCount will be restored from EEPROM and the code
will resume the previously set flash mode.
setup() initializes the analog comparator to generate an interrupt
when the voltage on AIN1 - connected to a voltage divider - drops
below the internal 1.1v bandgap reference (connected to the ATMega328P
analog comparator positive input). The voltage divider is connected
between Vcc and GND with the junction to AIN1 (pin 7).
*/
/*
Current voltage divider values on pot - ~10000 and ~3000 ohms. With
Vcc at ~4.94V this provides 1.12V at divider tap.
*/
#include <EEPROM.h>
/*
set up LCD with hd44780 library
*/
#include <Wire.h>
#include <hd44780.h> // main hd44780 header
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header
hd44780_I2Cexp lcd; // declare lcd object: auto locate & auto config expander chip
// LCD geometry
const int LCD_COLS = 16;
const int LCD_ROWS = 2;
// I/O
const byte switchPin = 11;
const byte jumper1 = 4;
const byte LEDpin = 6; // This pin is used intentionally to demonstrate that
// pin 6 can be used for normal digital I/O when the
// analog comparator input AIN0 is connected to the
// bandgpap reference.
const uint16_t EEPROM_BASE = 0; // non-volatile starting address to store/retrieve data
uint8_t stateCount = 0; // flash or don't flash LED
//
//-------------------------------------------------
//-------------------------------------------------
const uint8_t arraySize = 10;
uint8_t additionFactor; // to vary the data stored
//-------------------------------------------------
//-------------------------------------------------
//
// establish two arrays to hold test values
//
uint8_t anArray[arraySize];
uint8_t restoredArray[arraySize];
//
// print buffer
//
char lcdBuffer[25];
ISR (ANALOG_COMP_vect)
{
/*
When board power is lost the analog comparator triggers
this interrupt which saves the current stateCount and the
elements of anArray. These values will, it is hoped, be
restored intact when power is reapplied.
*/
EEPROM.write(1022, stateCount);
EEPROM.write(1023, additionFactor);
EEPROM.put(EEPROM_BASE, anArray);
while (1); // stay here until power down
}
void setup () {
Serial.begin(115200);
Serial.println("started");
lcd.begin(LCD_COLS, LCD_ROWS);
lcd.clear();
setupAnalogComparator();
setupDigitalIO();
EEPROM.get(1023, additionFactor);
loadArray(); // initalize array values to be saved
// change addition factor for next run
if (additionFactor == 3) {
additionFactor = 7;
}
else additionFactor = 3;
pinMode(LED_BUILTIN, OUTPUT);
delay(500);
EEPROM.get(1022, stateCount); // restore LED blink rate
//stateCount = EEPROM.read(1022);
if (digitalRead(jumper1) == HIGH) {
for (uint8_t j = 0; j < sizeof(anArray); j++) {
Serial.println(anArray[j]);
}
}
else {
overwriteLCD(0, 1, 16); // clear bottom line of display
lcd.print("load from EEPROM");
delay(1000);
EEPROM.get(EEPROM_BASE, restoredArray); // retrieve values stored from previous power down
overwriteLCD(0, 0, 16);
lcd.print("ix ar re er add");
for (int i = 0; i < arraySize; i++) {// now print to LCD restored values from previous power down
overwriteLCD(0, 1, 16);
//
// Use a buffer since we can't printf directly to LCD
//
sprintf(lcdBuffer, "%2d %2x %2x %s %2d", i, anArray[i], restoredArray[i], (restoredArray[i] != anArray[i]) ? "<<" : "OK", additionFactor);
lcd.print(lcdBuffer);
//
delay(1000);
}
}
//resetEEPROM();
} // end of setup
void loop ()
{
static byte lastSW, lastStateCount;
byte switchState = digitalRead(switchPin);
lastStateCount = stateCount;
static unsigned long blinkPRE = 500;
static unsigned long blinkCURRENT;
//
// heartbeat blilnker
//
if (millis() - blinkCURRENT > blinkPRE) {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
blinkCURRENT = millis();
}
Serial.println(switchState);
if (switchState == LOW && lastSW == HIGH) {
stateCount++;
if (stateCount > 2) stateCount = 0;
Serial.println("switch pressed");
}
lastSW = switchState;
if (stateCount != lastStateCount) {
return;
// overwriteLCD(0, 0, 16, ' ');
overwriteLCD(0, 0, 16);
if (stateCount == 0) lcd.print("mode: OFF");
if (stateCount == 1) lcd.print("mode: slow flash");
if (stateCount == 2) lcd.print("mode: fast flash");
}
if (stateCount == 1) { // LED flashes slow
delay(1000);
digitalWrite(LEDpin, HIGH);
delay(1000);
digitalWrite(LEDpin, LOW);
}
else if (stateCount == 2) { // LED flashes fast
delay(500);
digitalWrite(LEDpin, HIGH);
delay(500);
digitalWrite(LEDpin, LOW);
}
else { // LED is OFF
digitalWrite(LEDpin, LOW);
}
} // end of loop
void overwriteLCD(uint8_t col, uint8_t row, uint8_t numChars, char ovChar) {
/*
Print on the LCD at a specified place a specified number of specified characters.
Return the cursor to the given starting print position.
*/
// Serial.println(__FUNCTION__);
lcd.setCursor(col, row);
for (uint8_t i = 0; i < numChars; i++) {
lcd.print(ovChar);
}
lcd.setCursor(col, row);
} // end of overwriteLCD - given character
void overwriteLCD(uint8_t col, uint8_t row, uint8_t numChars) {
/*
Print on the LCD at a specified place a specified number of spaces.
Return the cursor to the given starting print position.
*/
lcd.setCursor(col, row);
for (uint8_t i = 0; i < numChars; i++) {
lcd.print(' ');
}
lcd.setCursor(col, row);
} // end of overwriteLCD - default spaces
void setupAnalogComparator() {
// Prepare the analog comparator for the current setup:
// Voltage divider on pin 7, LED flasher on pin 6.
DIDR1 = AIN1D | AIN0D; // datasheet 27.3.2 Digital Input Disable Register 1
ACSR = bit (ACI) ; // (Clear) Analog Comparator Interrupt Flag
ACSR |= bit (ACIE); // Analog Comparator Interrupt Enable ON
ACSR |= bit (ACBG); // Internal bandgap reference ON
ADCSRB = 0; // (Disable) ACME: Analog Comparator Multiplexer Enable
// ACIS1, ACIS0: Analog Comparator Interrupt Mode Select (trigger on rising edge)
ACSR |= bit (ACIS0);
ACSR |= bit (ACIS1);
} // end of setupAnalogComparator
void setupDigitalIO() {
pinMode(LEDpin, OUTPUT); // Use the now disconnected PD6 as an output
pinMode(switchPin, INPUT_PULLUP); // parallel .1uf capacitor debouncing
pinMode(jumper1, INPUT_PULLUP);
} // end of setupDigitalIO
void loadArray() {
//
// load the array with test values
//
for (uint8_t i = 0; i < arraySize; i++) { // load the array
anArray[i] = i + additionFactor;
// anArray[i] = 250;
}
// generate a checksum
for (uint8_t i = 0; i < arraySize; i++) {
// checkVal += anArray[i];
Serial.println(anArray[i]);
}
}
void resetEEPROM() {
{
uint8_t blanks[20]; // may need to be sizeof(anArray)n
for (uint8_t i = 0; i < sizeof(blanks); i++) blanks[i] = 0xff;
EEPROM.put(0,blanks);
}
}
Umm, are you reading the thread, Ray? The OP wants to write EEPROM after power fail detection. I don't think the brown-out interrupt is going to provide enough time to save EEPROM, since it's monitoring collapse of the regulator OUTPUT, not the input voltage. Tell me you've actually reliably saved 1k of EEPROM data in a brown-out/black-out interrupt response. I thought the write time for EEPROM was on the order of 2-3 ms, so 1K would need a few seconds of response time to get it done.
First time I've heard about these built in brown-out detectors, but it seems like you can set the brownout threshold to as high as 4.3V for the atmega320P. If the board is running at 16MHZ, with a minimum voltage of 3.78v, then you have 0.52v (4.3V - 3.78V) of capacitor voltage to work with.
Could you work with this for saving a few bytes to the EEPROM?
Why would I want to use a dedicated voltage monitor chip instead of simply making a resistor divider? Is this solely to preserve battery life? Or is there more to it?
And the same with the analog comperator trick. Is this only ment for when the power drops slowly?
Might consider having the project powered by 2 sources. A battery and diode steering. The MCU could detect the main power off-line, automatically switching to the battery, save to eeprom, and then shut the unit down.
I was mentioning a power loss detection method, for a project that is only powered by a wall adapter (mains electricity), for the purpose of saving variable's to EEPROM in a blackout.
However, my method will also work well for a battery application, in which there is a switch that turns on/off the battery from the microcontroller.
Most voltage supervisors also have additional inbuilt hysteresis, which can be useful if you are monitoring a battery that is not being immediately switched-off from the circuit.
e.g. you are running an esp32 off alkaline batteries for a few years and you want to receive a low battery warning interrupt. The hysteresis will ensure that the digital input does not bounce from high to low back and forth.
do you want to continuously have to check the analog input to see if the voltage has dropped below "threshold", then decide if it's still dropped, then maybe decide to save something critical before the power disappears? Or would you rather write an interrupt routine that only gets triggered if it's time to react? I know I'd rather do the latter. Battery preservation isn't really relevant, it's reliability. What if you inadvertently write a slow section of code, like a while loop that processes a lot of data, or what if your Serial output buffer fills up and becomes blocking, resulting in long delays between checks of the analog input? All of these concerns go away if you just offload the checking of the voltage to a supervisor circuit.
But, YMMV. It's a design decision, decisions require relevant data for input.
I was actually thinking to do it digital and polling the input, though I have no objection against interrupt. I would make ~3V with the resistor divider. That counts as a logic '1'. As my 5V circuit is switched off by a switch, the supply would drop instantly to 0V. So a capacitor and a fast enough loop() would suffice in this particular case.
But I can imagine that a slow dropping power output could cause problems.
What if you inadvertently write a slow section of code, like a while loop that processes a lot of data, or what if your Serial output buffer fills up and becomes blocking, resulting in long delays
Valid point but in those cases you have different problem to solve. A flooded serial buffer is not that difficult to detect Besides.. I am sorta beyond those problems... (until they come back again ). I rarely ceate long lasting loops nowadays.