We continue to have problems with brown outs on the arduinos, seemingly randomly. We are currently running 20 arduinos off of three usb power packs, which have "smart power regulation" which we think is causing the brown outs. After we distributed the arduinos between power packs and used different cords, the problem was fixed briefly.
I have been looking for “dumb” power supply, because all the usb packs I can find have voltage regulators that distribute the power, which we are thinking is the problem. It seems like the best way to address this problem is through coding the arduinos differently, although I don’t have a lot of experience handling this. I have found a few good forum posts:
What’s problem with brown out ?
This article seems to confirm that the problem we are having (see spreadsheet excerpt below) looks like brown outs, as the data keeps writing past when the peripheral unit is powered off. What I’m unsure of is whether our startup loop can re-start the peripheral unit, or if we need to write that into the code (also attached, our code: Experiment_Loop).
|-955453|0|-30.79|330998| 2095/7/14 23:11:55||
|296026|-28.89|-25.89|332000| 2095/7/14 23:11:56||
|173|-6.04|-17.32|332999| 2095/7/14 23:11:57||
|333999| 2095/7/14 23:11:58|||||
|334999| 2095/7/14 23:11:59|||||
|336000| 2095/7/14 23:12:0|||||
#include <Wire.h>
#include <EEPROM.h> //Needed to record user settings
#include "SparkFun_Qwiic_Scale_NAU7802_Arduino_Library.h" // Click here to get the library: http://librarymanager/All#SparkFun_NAU8702
NAU7802 myScale; //Create instance of the NAU7802 class
//EEPROM locations to store 4-byte variables
#define LOCATION_CALIBRATION_FACTOR 0 //Float, requires 4 bytes of EEPROM
#define LOCATION_ZERO_OFFSET 10 //Must be more than 4 away from previous spot. Long, requires 4 bytes of EEPROM
bool settingsDetected = false; //Used to prompt user to calibrate their scale
//Create an array to take average of weights. This helps smooth out jitter.
#define AVG_SIZE 4
float avgWeights[AVG_SIZE];
byte avgWeightSpot = 0;
#include <LiquidCrystal_I2C.h> //display
LiquidCrystal_I2C lcd(0x27,20,4); // set the LCD address to 0x27 for a 16 chars and 2 line display
#include "SD.h" //for data logger
#include <RTClib.h>
// A simple data logger for the Arduino analog pins
#define LOG_INTERVAL 1000 // mills between entries
#define ECHO_TO_SERIAL 1 // echo data to serial port
#define WAIT_TO_START 0 // Wait for serial input in setup()
RTC_PCF8523 rtc; // define the Real Time Clock object
// for the data logging shield, we use digital pin 10 for the SD cs line
const int chipSelect = 10;
// the logging file
File logfile;
void error(char *str)
{
Serial.print("error: ");
Serial.println(str);
while(1);
} //still all for data logger
void setup()
{
Serial.begin(9600);
Serial.println("Qwiic Scale Example");
Wire.begin();
Wire.setClock(400000); //Qwiic Scale is capable of running at 400kHz if desired
if (myScale.begin() == false){
Serial.println("Scale not detected. Please check wiring. Freezing...");
while (1);
}
Serial.println("Scale detected!");
readSystemSettings(); //Load zeroOffset and calibrationFactor from EEPROM
myScale.setSampleRate(NAU7802_SPS_320); //Increase to max sample rate
myScale.calibrateAFE(); //Re-cal analog front end when we change gain, sample rate, or channel
Serial.print("Zero offset: ");
Serial.println(myScale.getZeroOffset());
Serial.print("Calibration factor: ");
Serial.println(myScale.getCalibrationFactor());
////NEW STUFF
// lcd.init(); // initialize the lcd
// lcd.backlight();
#if WAIT_TO_START //this is for datalogger
Serial.println("Type any character to start");
while (!Serial.available());
#endif //WAIT_TO_START
// initialize the SD card
Serial.print("Initializing SD card...");
// make sure that the default chip select pin is set to
// output, even if you don't use it:
pinMode(10, OUTPUT);
// see if the card is present and can be initialized:
if (!SD.begin(chipSelect)) {
Serial.println("Card failed, or not present");
// don't do anything more:
return;
}
Serial.println("card initialized.");
// create a new file <-- this is beautiful but not functional for this project.
//char filename[] = "LOGGER00.CSV";
//for (uint8_t i = 0; i < 100; i++) {
//filename[6] = i/10 + '0';
//filename[7] = i%10 + '0';
//if (! SD.exists(filename)) {
// only open a new file if it doesn't exist
//logfile = SD.open(filename, FILE_WRITE);
//break; // leave the loop!
//}
logfile = SD.open("m_oxy_.CSV", FILE_WRITE);
if (! logfile) {
error("couldnt create file");
}
//Serial.print("Logging to: ");
//Serial.println(filename); //end sd stuff
Wire.begin();
// if (!RTC.begin()) {
// logfile.println("RTC failed");
//#if ECHO_TO_SERIAL
// Serial.println("RTC failed");
//#endif //ECHO_TO_SERIAL
//}
logfile.println("reading,weight,avg,millis,time");
Serial.println("reading,weight,avg,millis,time"); //write header on SD card
logfile.close(); ///close the file
Wire.begin();
Wire.setClock(400000); //Qwiic Scale is capable of running at 400kHz if desired
if (myScale.begin() == false)
{
Serial.println("Scale not detected. Please check wiring. Freezing...");
while (1);
}
Serial.println("Scale detected!");
myScale.setSampleRate(NAU7802_SPS_320); //Increase to max sample rate
myScale.calibrateAFE(); //Re-cal analog front end when we change gain, sample rate, or channel
Serial.print("Zero offset: ");
Serial.println(myScale.getZeroOffset());
Serial.print("Calibration factor: ");
Serial.println(myScale.getCalibrationFactor());
rtc.start();
}
void loop()
{
logfile = SD.open("micea_oxy_.CSV", FILE_WRITE);
// when characters arrive over the serial port...
// if (Serial.available()) {
// wait a bit for the entire message to arrive
// delay(1);
// clear the screen
// lcd.clear();
// read all the available characters
// while (Serial.available() > 0) {
// display each character to the LCD
// lcd.write(Serial.read());
}
}
if (myScale.available() == true)
{
long currentReading = myScale.getReading();
float currentWeight = myScale.getWeight();
Serial.print("Reading: ");
Serial.print(currentReading);
logfile.print(currentReading);
logfile.print(",");
Serial.print("\tWeight: ");
Serial.print(currentWeight, 2); //Print 2 decimal places
logfile.print(currentWeight, 2);
logfile.print(",");
avgWeights[avgWeightSpot++] = currentWeight;
if(avgWeightSpot == AVG_SIZE) avgWeightSpot = 0;
float avgWeight = 0;
for (int x = 0 ; x < AVG_SIZE ; x++)
avgWeight += avgWeights[x];
avgWeight /= AVG_SIZE;
Serial.print("\tAvgWeight: ");
Serial.print(avgWeight, 2); //Print 2 decimal places
logfile.print(avgWeight,2);
logfile.print(",");
if(settingsDetected == false)
{
Serial.print("\tScale not calibrated. Press 'c'.");
}
Serial.println();
}
if (Serial.available())
{
byte incoming = Serial.read();
if (incoming == 't') //Tare the scale
myScale.calculateZeroOffset();
else if (incoming == 'c') //Calibrate
{
calibrateScale();
}
}
// delay for the amount of time we want between readings
delay((LOG_INTERVAL -1) - (millis() % LOG_INTERVAL));
// log milliseconds since starting
uint32_t m = millis();
logfile.print(m); // milliseconds since start
logfile.print(", ");
#if ECHO_TO_SERIAL
Serial.print(m); // milliseconds since start
Serial.print(", ");
#endif
DateTime now = rtc.now();
// fetch the time
//DateTime now; //datalogger
logfile.print(now.year(), DEC);
logfile.print("/");
logfile.print(now.month(), DEC);
logfile.print("/");
logfile.print(now.day(), DEC);
logfile.print(" ");
logfile.print(now.hour(), DEC);
logfile.print(":");
logfile.print(now.minute(), DEC);
logfile.print(":");
logfile.println(now.second(), DEC);
logfile.close(); ///close the file
#if ECHO_TO_SERIAL
Serial.print(now.year(), DEC);
Serial.print('/');
Serial.print(now.month(), DEC);
Serial.print('/');
Serial.print(now.day(), DEC);
Serial.print(" (");
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.println();
#endif //ECHO_TO_SERIAL
}
//Gives user the ability to set a known weight on the scale and calculate a calibration factor
void calibrateScale(void)
{
Serial.println();
Serial.println();
Serial.println(F("Scale calibration"));
Serial.println(F("Setup scale with no weight on it. Press a key when ready."));
while (Serial.available()) Serial.read(); //Clear anything in RX buffer
while (Serial.available() == 0) delay(10); //Wait for user to press key
myScale.calculateZeroOffset(64); //Zero or Tare the scale. Average over 64 readings.
Serial.print(F("New zero offset: "));
Serial.println(myScale.getZeroOffset());
Serial.println(F("Place known weight on scale. Press a key when weight is in place and stable."));
while (Serial.available()) Serial.read(); //Clear anything in RX buffer
while (Serial.available() == 0) delay(10); //Wait for user to press key
Serial.print(F("Please enter the weight, without units, currently sitting on the scale (for example '4.25'): "));
while (Serial.available()) Serial.read(); //Clear anything in RX buffer
while (Serial.available() == 0) delay(10); //Wait for user to press key
//Read user input
float weightOnScale = Serial.parseFloat();
Serial.println();
myScale.calculateCalibrationFactor(weightOnScale, 64); //Tell the library how much weight is currently on it
Serial.print(F("New cal factor: "));
Serial.println(myScale.getCalibrationFactor(), 2);
Serial.print(F("New Scale Reading: "));
Serial.println(myScale.getWeight(), 2);
recordSystemSettings(); //Commit these values to EEPROM
}
//Record the current system settings to EEPROM
void recordSystemSettings(void)
{
//Get various values from the library and commit them to NVM
EEPROM.put(LOCATION_CALIBRATION_FACTOR, myScale.getCalibrationFactor());
EEPROM.put(LOCATION_ZERO_OFFSET, myScale.getZeroOffset());
}
//Reads the current system settings from EEPROM
//If anything looks weird, reset setting to default value
void readSystemSettings(void)
{
float settingCalibrationFactor; //Value used to convert the load cell reading to lbs or kg
long settingZeroOffset; //Zero value that is found when scale is tared
//Look up the calibration factor
EEPROM.get(LOCATION_CALIBRATION_FACTOR, settingCalibrationFactor);
if (settingCalibrationFactor == 0xFFFFFFFF)
{
settingCalibrationFactor = 0; //Default to 0
EEPROM.put(LOCATION_CALIBRATION_FACTOR, settingCalibrationFactor);
}
//Look up the zero tare point
EEPROM.get(LOCATION_ZERO_OFFSET, settingZeroOffset);
if (settingZeroOffset == 0xFFFFFFFF)
{
settingZeroOffset = 1000L; //Default to 1000 so we don't get inf
EEPROM.put(LOCATION_ZERO_OFFSET, settingZeroOffset);
}
//Pass these values to the library
// myScale.setCalibrationFactor(settingCalibrationFactor);
myScale.setCalibrationFactor(-12494);
//myScale.setZeroOffset(settingZeroOffset);
myScale.setZeroOffset(-75250);
settingsDetected = true; //Assume for the moment that there are good cal values
if (settingCalibrationFactor < 0.1 || settingZeroOffset == 1000)
settingsDetected = false; //Defaults detected. Prompt user to cal scale.
}
“Keep the AVR RESET active (low) during periods of insufficient power supply voltage. This can be done by enabling the internal Brown-out Detector (BOD). If the detection level of the internal BOD does not match the needed detection level, an external low VCC reset Protection circuit can be used. If a reset occurs while a write operation is in progress, the write operation will be completed provided that the power supply voltage is sufficient.”
Based on this article: Is there Brown Out Detection for Arduino Uno? it seems the brown out detector is activated, but perhaps the threshold is too low to activate it in order for it to trip the restart?
“Now all is clear to me, I wrote this summary to have all the information in one place and to share with you.
I wanted to have low voltage (brown-out) detection on my Arduino Uno R3 to prevent it from unexpected behavior or even destroying my sketch when the 5V power supply drops below a certain level. I want to show how to correctly set the brown out detection. To set the brown-out detection a new bootloader with the setting must be burned to the Uno. I have used 1 Arduino Uno to burn a bootloader to another Arduino Uno. The other Uno is used as an in-system programmer(ISP). Here's a description how to do that http://arduino.cc/en/Tutorial/ArduinoISP.
In the Arduino IDE (mine is 1.0.1) boards.txt contains bootloader settings of many different Arduino boards. On my computer the file is located here D:\programs\arduino-1.0.1\hardware\arduino\boards.txt. In that file you need to find the Uno.
##############################################################
uno.name=Arduino Uno
uno.upload.protocol=arduino
uno.upload.maximum_size=32256
uno.upload.speed=115200
uno.bootloader.low_fuses=0xff
uno.bootloader.high_fuses=0xde
uno.bootloader.extended_fuses=0x05
uno.bootloader.path=optiboot
uno.bootloader.file=optiboot_atmega328.hex
uno.bootloader.unlock_bits=0x3F
uno.bootloader.lock_bits=0x0F
uno.build.mcu=atmega328p
uno.build.f_cpu=16000000L
uno.build.core=arduino
uno.build.variant=standard
##############################################################
A change must be made to one of these lines to set the fuses of the Uno.
To find the fuse settings this fuse calculator is useful. AVR® Fuse Calculator – The Engbedded Blog. For Uno select ATmega328P. The second table shows that the brown-out detection (BOD) level is set with the extended fuse. Bits 0 to 2 are used to set the voltage level. Bits 3 to 7 are not used. Playing around with the calculator shows that 0x04 is 4.3V, 0x05 is 2.7V, 0x06 is 1.8V and 0x07 is disabled BOD. The datasheet of the ATmega328P shows the same BOD level coding.
This means that the default setting of 0x05 for the Uno that came with the IDE is 2.7V. So the Uno already has brown-out detection enabled. That's interesting, because the Arduino Uno runs on a 16MHz external crystal. The datasheet of the ATmega328P shows that at 2.7V the maximum frequency for safe operation is 10MHz. So at 16MHz the minimum voltage for safe operation must be set to 4.3V. So extended fuse must be set to 0x04.
You now learned that it useful to change the brown-out detection level for your Uno. Don't forget to save the boards.txt file, restart the IDE, burn the bootloader and then burn your sketch. Happy brown-out detecting!”
Also, it seems like the peripherals typically do not power back on with the BOD trip, but I don’t know if that includes the Amp and load cell which are connected through I2C. My reasoning comes from this:
- “Does anybody know what happens with things like the ethernet shield when the BOD activates? On my current project the web servercode or the shield seems to stop working (IE comes up with a connection problem and wont display the pagefrom the SD card) even though there is a backup battery. Ironically the SD card keeps on recording data every 10 min, though it occasionally misses a beat. The only way to get communications going again seems to be a 'power off' reset!”
- “When the BOD activates, as far as I know the processor enters a reset state. However this doesn't mean any attached peripherals reset. Nor does it mean the reset pin changes state (as far as I am aware)
So to effectively recover other peripherals you would have to (in setup) make them reset, if possible.”
This would suggest that that the code has to be specific enough in the restart loop to trigger the amp to restart, which I’m not sure if it is currently.
Any thoughts or direction you have about this problem would be helpful! I’m a bit out of my depth right now and unsure of the easiest way to fix this quickly. Let me know if you have any questions.