Several Issues Making a Recording Voltmeter

Hello!
A student of mine is working on a project that needs to record voltages over several days, taking measurements every 30 minutes, saving those to a micro sd card and displaying them on the serial monitor.

We are running into several issues:

  1. The measurement timing is very off, recording every 51 seconds instead of every 30 minutes
  2. The stop button (in case we need to stop a run early) isn't responding at all
  3. The recorded voltage decreases to zero after several minutes, but an attached multimeter will show a voltage.
  4. The recorded voltage is only showing 2 decimal places, and since we are dealing with very small potential differences we'd like to show more.

Just a few notes: I used an RGB LED because that's what was handy at the time. The start and stop buttons use pull down resistors to keep the digital read LOW.

Many thanks in advance for any advice or suggestions!

Edit: Sorry, using the Arduino Uno

#include <SD.h>

File voltlog;

const long runTime = 304800000;    //1 week run time in ms
const int interval = 1800000; //30 min mesaurement interval in ms
int const referenceVolts = 5.0;
const int sensorPin = A0;  // voltage measures on analog 0
const int SDcs = 10;       //SD card on pin 10
const int rdLed = 6;      //rgb red
const int blLed = 5;      //rgb blue
const int grLed = 3;      // rgb green
const int startButton = 9;  //start button pin
const int stopButton = 8;   //stop buton pin

void setup() {
//pinMode(notLed, OUTPUT);
pinMode(startButton, INPUT);
pinMode(stopButton, INPUT);
pinMode(rdLed, OUTPUT);
pinMode(grLed, OUTPUT);
pinMode(blLed, OUTPUT);
Serial.begin(9600);
Serial.print("Initializing SD card...");
pinMode(SDcs, OUTPUT);

if (!SD.begin(SDcs)){
  Serial.println("Initilization failed!");
  return;
}
Serial.println("Initilization done.");

}

void loop() {
  digitalWrite(rdLed, HIGH);
  digitalWrite(grLed, LOW);
  digitalWrite(blLed, LOW);
  delay(500);
  int startVal = digitalRead(startButton);    		//read the value of the start button
  if (startVal == HIGH) {                     // if the button is pressed call the logging function
    logVoltage();
  }
}
void logVoltage() {                         //voltage logging function
  digitalWrite(rdLed, LOW);
  digitalWrite(grLed, HIGH);
  digitalWrite(blLed, LOW);
  float elapsed = 0;
  voltlog = SD.open("volttime.csv", FILE_WRITE);    	//open voltage log file

  while (millis() < (runTime) && digitalRead(stopButton == LOW)) {
    int val = analogRead(sensorPin);        		//read the analog vale on pin 10
    float volts = ((val/1023.0)*referenceVolts);    	// convert the analog reading to volts
    elapsed = elapsed + interval;           		// elapsed time
    Serial.print(elapsed); Serial.print(','); Serial.print(volts); Serial.println();    //print values to serial monitor
    voltlog.print(elapsed); voltlog.print(',');voltlog.print(volts); voltlog.println(); //write values to log file
    if digitalRead(stopButton == LOW) {
      digitalWrite(rdLed, LOW);
      digitalWrite(grLed, LOW);
      digitalWrite(blLed, HIGH);
      delay(interval);
    }
    else{
    break; 
  }
  }
  voltlog.close();
  Serial.println("File Closed");
}

There are many Serial.print() statements in the program. Serial.print() is meant to be used as a debugging tool. I don't see any attempt to debug the program.

Get rid of all the existing Serial.print() statements by making them comments in the program. Then add Serial.print() statements where ever you have used something that you thing is causing a problem. Print before value and then the value after one of the instructions. See if the result is what you expect. Fix the problem if there is one and go to the next problem.

Problem solving is something you should be teaching by example.

Paul

Better?

#include <SD.h>

File voltlog;

const unsigned long runTime = 604800000;    //1 week run time in ms (86400 x 7 x 1000)
const unsigned int interval = 1800000;      //30 min mesaurement interval in ms

const int referenceVolts = 5.0;
const int sensorPin = A0;  // voltage measures on analog 0
const int SDcs = 10;       //SD card on pin 10
const int rdLed = 6;      //rgb red
const int blLed = 5;      //rgb blue
const int grLed = 3;      // rgb green
const int startButton = 9;  //start button pin
const int stopButton = 8;   //stop buton pin

void setup() 
{
    //pinMode(notLed, OUTPUT);
    pinMode(startButton, INPUT);
    pinMode(stopButton, INPUT);
    pinMode(rdLed, OUTPUT);
    pinMode(grLed, OUTPUT);
    pinMode(blLed, OUTPUT);
    Serial.begin(9600);
    Serial.print("Initializing SD card...");
    pinMode(SDcs, OUTPUT);

    if (!SD.begin(SDcs))
    {
        Serial.println("Initilization failed!");
        return;
    }
    Serial.println("Initilization done.");

}

void loop() 
{
    digitalWrite(rdLed, HIGH);
    digitalWrite(grLed, LOW);
    digitalWrite(blLed, LOW);
    delay(500);
    int startVal = digitalRead(startButton);            //read the value of the start button
    if (startVal == HIGH) 
    {                     
        // if the button is pressed call the logging function
        logVoltage();
    }
}

void logVoltage() 
{
    
    int
        val;
    float
        volts;
    bool
        bDone;    
    unsigned long
        timeNow,
        timeSample,
        timeStart;
    unsigned long
        elapsed;    
        
    //voltage logging function
    digitalWrite(rdLed, LOW);
    digitalWrite(grLed, HIGH);
    digitalWrite(blLed, LOW);
    
    elapsed = 0;
    voltlog = SD.open("volttime.csv", FILE_WRITE);        //open voltage log file
    
    timeStart = millis();
    timeSample = 0;
    bDone = false;
    do
    {
        timeNow = millis();
        if( ((timeNow - timeStart) >= runTime) || (digitalRead(stopButton) == HIGH) )
            bDone = true;

        if( (timeNow - timeSample) >= interval )
        {
            timeSample = timeNow;
            val = analogRead(sensorPin);                //read the analog vale on pin 10
            volts = ((val/1023.0)*referenceVolts);        // convert the analog reading to volts
        
            elapsed = elapsed + interval;                   // elapsed time
            Serial.print(elapsed); Serial.print(','); Serial.print(volts,3); Serial.println();    //print values to serial monitor       
            voltlog.print(elapsed); voltlog.print(',');voltlog.print(volts,3); voltlog.println(); //write values to log file
            
        }//if
            
        if( digitalRead(stopButton) == LOW ) 
        {
            digitalWrite(rdLed, LOW);
            digitalWrite(grLed, LOW);
            digitalWrite(blLed, HIGH);
            
        }//if
        
    }while( !bDone );
    
    voltlog.close();
    Serial.println("File Closed");

}//logVoltage

Notes:

  • When using millis() for timing always use unsigned longs
  • A week is 86400 x 7 x 1000 or 604800000mS, no?
  • try not to use delay() except for the shortest of periods (like switch debouncing...)
  • the preferred practice is to have switch inputs to the uC "high" when open, "low" when closed

Wasn't sure about your LED logic so I left that alone.

The internal clock is reasonably accurate but expect some error to creep in when doing time-measurements of this magnitude when relying only on the internal clock.

Paul_KD7HB:
There are many Serial.print() statements in the program. Serial.print() is meant to be used as a debugging tool. I don't see any attempt to debug the program.

Get rid of all the existing Serial.print() statements by making them comments in the program. Then add Serial.print() statements where ever you have used something that you thing is causing a problem. Print before value and then the value after one of the instructions. See if the result is what you expect. Fix the problem if there is one and go to the next problem.

Problem solving is something you should be teaching by example.

Paul

Thanks! Although it wasn't for debugging, it definitely did show we had problems. If the serial monitor isn't needed then we'll remove it.

Coding (quite obviously!) isn't my forte. Honestly the last coding class I had was in 1980's BASIC, so the teachable moments here are: keep trying new things and when you're lost ask the experts.

I appreciate the feedback!

Rich

Delta_G:

const int interval = 1800000;

Does this interval sound reasonable to store in an int? What types of numbers go into an int? Is there a maximum size?

Sure it does as long as you push the button exactly when it gets to this line:

while (millis() < (runTime) && digitalRead(stopButton == LOW))

If you are pressing the button while this line is running:

delay(interval);

Then nothing is going to happen because you're stuck on that line for a while. If you want programs to be responsive to things, then never use delay.

Then there's this line where you try but fail:

if digitalRead(stopButton == LOW)

stopButton is defined in your code as 8. LOW is defined int he core as 0. So 8 never ever ever equals 0. So stopButton == LOW is false. So your line becomes:

if digitalRead(false)

which is interpreted as

if digitalRead(0)

Which is obviously not what you intended to do. Pay attention to those parenthesis.

Just how much precision are you expecting? Remember, you only have a 10 bit ADC. Just printing more decimal places doesn't mean that those decimal places are meaningful.

My thanks for the feedback!

So, to summarize:
Recheck the variable types (your questions here made me laugh! My students sometimes hear things along those lines, "Does a 345 kg cat sound right? You may need to redo your math on that one."

Buttons don't work during delays. Ok, that should have been completely obvious and I completely missed it. Another quote from class "Let's not to make that mistake again."

The precision needs to be at minimum into the hundredths, preferably to the thousandths. We're looking at the bacterial activity in benthic mud (aka pond muck) related to the voltage they produce. Basically it's a fuel cell driven by bacteria. The potential difference isn't high, the voltmeter shows between 0.06v and 0.08v. That's not bad, but we usually try for more significant digits than that.

Rich

Blackfin:
Better?

Notes:

  • When using millis() for timing always use unsigned longs
  • A week is 86400 x 7 x 1000 or 604800000mS, no?
  • try not to use delay() except for the shortest of periods (like switch debouncing...)
  • the preferred practice is to have switch inputs to the uC "high" when open, "low" when closed

Wasn't sure about your LED logic so I left that alone.

The internal clock is reasonably accurate but expect some error to creep in when doing time-measurements of this magnitude when relying only on the internal clock.

Ah thank you!

Yes, you have the correct time for a week, and my typing is terrible.

So, to my, admittedly poor, understanding, the logVoltage function will continually loop, constantly checking the times against the runTime and the interval. When it hits the interval (>=) it records the voltage. When it hits the runTime OR the stop button is pressed, it flips the value of bDone to true, closes the file and exits the loop. That makes far more sense now.

And finally, reverse the buttons to pullup and switch HIGHs and LOWs, expect some time error when it runs. That's not terribly important since it will be running a long time.

The LED is really there just so we can glance to the lab area and see if the monitor is still running. Having it flash blue serves no real purpose, so that will be taken out.

Thanks again! Much learning to do and fun doing it!

Rich

Note that this was just a re-write of logVoltage. I normally wouldn't lock the processor into a single function for a week at a time :slight_smile:

logVoltage could be restructured using a state machine and only spend, literally, microseconds at a time doing very specific things in there. Meanwhile, other things could be called from loop() to, say, log other things.

But hopefully, it works enough to give you a gist of what's going on with timing without delay().

HTH.