Innacurate millis function output with sensor data

I am doing a project for college and using arduino uno. I am required to record accelerometer data (using ADXL335) with time stamps (using millis function) and save it to SD card. The interval that I must use is 10ms but when I run my code the output is wildly different with bigger intervals on the serial monitor (would start at zero then go to 43 or 51, and so on). I don’t know if I have just made a mess or what else I can do. I read multiple posts on this forum and others but couldn’t find an answer so any help would be appreciated. Also, I did read that millis drifts since it is not exactly 1ms but 1.024ms, is that why I am having issues, and how can i fix it?

//including libraries
#include <SD.h>
#include <SPI.h>

File myFile;

const int pinCS = 10; //chip select
const int xpin = A3;                  // x-axis of the accelerometer
const int ypin = A2;                  // y-axis
const int zpin = A1;                  // z-axis

unsigned long eventInterval = 10;
unsigned long previousTime = 0;


void setup() {
  Serial.begin(9600);
  pinMode (pinCS, OUTPUT);

  //SD card initialization - prints a failed statement if the card is not working
  if (SD.begin())
  {
  } else
  {
    Serial.println("SD card initialization failed");
    return;
  }

  // create/open file
  myFile = SD.open("walking.csv", FILE_WRITE);
  //if file opened ok, write to it:
  if (myFile) {
    myFile.println("TIME, X, Y, Z");
    myFile.close(); //close the file
    Serial.println("TIME, X, Y, Z");
  }
  // if the file didn't open, print error:
  else {
    Serial.println("error opening test.txt");
  }

}

void loop() {
  unsigned long currentTime = millis(); //setting current time to the millis () function counter

  if (currentTime - previousTime >= eventInterval) {
    myFile = SD.open("walking.csv", FILE_WRITE);
    myFile.print(previousTime);
    myFile.print(",");
    myFile.print(analogRead(xpin));
    myFile.print(",");
    myFile.print(analogRead(ypin));
    myFile.print(",");
    myFile.println(analogRead(zpin));
    Serial.print(previousTime);
    Serial.print(",");
    Serial.print(analogRead(xpin));
    Serial.print(",");
    Serial.print(analogRead(ypin));
    Serial.print(",");
    Serial.println(analogRead(zpin));
    previousTime = currentTime; //setting previous time to current time once interval is reached

  }
  myFile.close(); //close the file
}

Thanks for using code tags on your first post!

You are making two very serious mistakes.

  1. Don't open and close the file every time you write a bit of data. Opening and closing is slow. Open the file once at the start of the session, and close it when you are finished.

  2. Printing to the console is slow, especially at 9600 Baud. Either use a much faster Baud rate, like 115200 or higher, or don't print to the console while you are collecting data.

jremington:
Thanks for using code tags on your first post!

You are making two very serious mistakes.

  1. Don't open and close the file every time you write a bit of data. Opening and closing is slow. Open the file once at the start of the session, and close it when you are finished.

  2. Printing to the console is slow, especially at 9600 Baud. Either use a much faster Baud rate, like 115200 or higher, or don't print to the console while you are collecting data.

Hi, thanks for your help! I tried this and it made it a bit better, but the time intervals are still off, here's a little snippet from my serial monitor to show:

TIME,X,Y,Z
0,315,365,289
30,315,365,289
57,314,365,289
79,315,365,289

Also.

Try this.

unsigned long millis0 = millis();
unsigned long millis1 = millis(); // unsigned long if using a Uno/Mega. int if using a 32bit MCU.
Serial.print( "millis0 " );
Serial.print( millis0 );
Serial.print( " millis1 " ;)
Serial.println( millis1 ); 
Serial.print( " diff : " );
Serial.println( millis1-millis0);

to see the overhead of millis().

It may be better to use micros() and delayMicros() if 10mS resolution is required.

Post the revised code.

nuig2021:
I did read that millis drifts since it is not exactly 1ms but 1.024ms

The timer interrupt function corrects that, so millis() has the right timing.
When your sketch samples data exactly every 10ms, then you might see a small glitch sometimes due to that correction. That is nothing to worry about, you probably won't notice it.

The source code of the interrupt with the correction and the millis() function is here: ArduinoCore-avr/wiring.c at master · arduino/ArduinoCore-avr · GitHub

jremington:
Post the revised code.

//including libraries
#include <SD.h>
#include <SPI.h>

File myFile;

const int pinCS = 10; //chip select
const int xpin = A3;                  // x-axis of the accelerometer
const int ypin = A2;                  // y-axis
const int zpin = A1;                  // z-axis

unsigned long eventInterval = 10;
unsigned long previousTime = 0;


void setup() {
  Serial.begin(115200);
  pinMode (pinCS, OUTPUT);

  //SD card initialization - prints a failed statement if the card is not working
  if (SD.begin())
  {
  } else
  {
    Serial.println("SD card initialization failed");
    return;
  }

  // create/open file
  myFile = SD.open("testing.csv", FILE_WRITE);
  //if file opened ok, write to it:
  if (myFile) {
    myFile.println("TIME,X,Y,Z");
    //    Serial.println("TIME,X,Y,Z");
  }
  // if the file didn't open, print error:
  else {
    //    Serial.println("error opening test.csv");
  }

}

void loop() {
  unsigned long currentTime = millis(); //setting current time to the millis () function counter

  if (currentTime - previousTime >= eventInterval) {
    myFile = SD.open("testing.csv", FILE_WRITE);
    myFile.print(previousTime);
    myFile.print(",");
    myFile.print(analogRead(xpin));
    myFile.print(",");
    myFile.print(analogRead(ypin));
    myFile.print(",");
    myFile.println(analogRead(zpin));
    Serial.print(previousTime);
    Serial.print(",");
    Serial.print(analogRead(xpin));
    Serial.print(",");
    Serial.print(analogRead(ypin));
    Serial.print(",");
    Serial.println(analogRead(zpin));
    previousTime = currentTime; //setting previous time to current time once interval is reached

  }
  myFile.close(); //close the file
}

I adjusted the Baud rate and took out the command that closes the file in void setup

Idahowalker:
Also.

Try this.

unsigned long millis0 = millis();

unsigned long millis1 = millis(); // unsigned long if using a Uno/Mega. int if using a 32bit MCU.
Serial.print( "millis0 " );
Serial.print( millis0 );
Serial.print( " millis1 " :wink:
Serial.println( millis1 );
Serial.print( " diff : " );
Serial.println( millis1-millis0);



to see the overhead of millis().

It may be better to use micros() and delayMicros() if 10mS resolution is required.

Hi, Thanks for your advice! I applied this to my code and the difference was between 21-24, varying slightly. Module instructor told us to use millis() unfortunately. And he provided us with sample data which had the interval perfect but didn't give us any further direction on how to formulate the actual code.

If you run the code that shows the millis() overhead, you can determine an average. Once the average is determined, the average can be used as an offset or compensation factor for millis(). Oh and perhaps you can use micros() to determine the millis() offset.

You are not paying attention. You still open and close the file every time you write some data.

DO THIS JUST ONCE, in setup():

    myFile = SD.open("testing.csv", FILE_WRITE);

jremington:
You are not paying attention. You still open and close the file every time you write some data.

DO THIS JUST ONCE, in setup():

    myFile = SD.open("testing.csv", FILE_WRITE);

I tried doing it just once but the closing command is in the loop , once I took out the second open command from it to only have it once , I only got 1 line of data instead of continuous monitoring. Where would you advise to write in these statements as I need the data to loop over and constantly save to SD card?

the closing command is in the loop

Take this command out of the loop.

Close the file only when you are done collecting data. You will need some way of telling the program to finish up: maybe add a pushbutton, decide to collect only a certain number of data points, etc.

One way do thing. The way you are doing the thing is:

Open file, put something into file, close file, open file, put something into file, close file, open file, put something into file, close file, open file, put something into file take a long time.

Other way to do the thing:

Program start, Open file, write, write, write, write, write, write, write, write, write, write, close file when done takes less time then the other thing.

Oh.

You are carrying boxes from your car to the house. Each time you open door, balance box, go into house, close door, open door, leave house.

Or

You can prop door open, carry boxes into house, close door when all boxes in house.

There are advantages to each, advantages to both.

You need something to stop it, and perhaps something to start it as well.

For example:

  • Start recording when the Arduino starts. Stop after a fixed time, for example 10 minutes.
  • Start recording when a button is pressed. Stop recording when a button is pressed (it can be the same button).
  • Use the serial monitor to send something. A command to start and a command to stop.

I don't know what happens to your data if you don't call SD.stop().

Thanks everyone for the help, I really appreciate it! I’ve added a time limit so that when millis reaches that time the SD file will close, I’m hoping that’s closer to what everyone meant?

//including libraries
#include <SD.h>
#include <SPI.h>

File myFile;

const int pinCS = 10; //chip select
const int xpin = A3;                  // x-axis of the accelerometer
const int ypin = A2;                  // y-axis
const int zpin = A1;                  // z-axis

unsigned long eventInterval = 10;
unsigned long previousTime = 0;
unsigned long timeLimit = 90000; //1.5 minutes limit


void setup() {
  Serial.begin(115200);
  pinMode (pinCS, OUTPUT);

  //SD card initialization - prints a failed statement if the card is not working
  if (SD.begin())
  {
  } else
  {
    Serial.println("SD card initialization failed");
    return;
  }

  // create/open file
  myFile = SD.open("test.csv", FILE_WRITE);
  //if file opened ok, write to it:
  if (myFile) {
    myFile.println("TIME,X,Y,Z");
    Serial.println("TIME,X,Y,Z");
  }
  // if the file didn't open, print error:
  else {
    //    Serial.println("error opening test.csv");
  }
}

void loop() {
  unsigned long currentTime = millis(); //setting current time to the millis () function counter

  if (currentTime >= timeLimit) {
    myFile.close(); //close the file
  }
  if (currentTime - previousTime >= eventInterval) {
    myFile.print(previousTime);
    myFile.print(",");
    myFile.print(analogRead(xpin));
    myFile.print(",");
    myFile.print(analogRead(ypin));
    myFile.print(",");
    myFile.println(analogRead(zpin));
    Serial.print(previousTime);
    Serial.print(",");
    Serial.print(analogRead(xpin));
    Serial.print(",");
    Serial.print(analogRead(ypin));
    Serial.print(",");
    Serial.println(analogRead(zpin));
    previousTime = currentTime; //setting previous time to current time once interval is reached
  }
}

It made a huge difference, the output it almost perfect except for the beginning, I’ve no idea what’s wrong with it but the output seems to be printing random data, even in the headings there are random numbers, then few rows repeated at a much higher than 0 millis value and then once the correct data starts it jumps from 0 to 49 , just a little snippet from serial monitor so you can see what I mean (this is also a new file, it is not adding to an existing one):

TIME,X,Y3,278
2116,318,352,278

2056,318,352,278
2066,319,352,278
2076,318,352,278
2086,318,352,278
2096,319,353,279
2106,318,352,278
2116,318,352,278

2056,3185352,278
2066,319,352,278
2076,318,352,278
2086,318,352,278
2096,319,353,279
2106,318,352,278
2116,318,352,278
2126,318,352,279
2136,318,352,278
2146,319,352,278
TIME,X,Y,Z
0,318,353,278
49,318,352,278
59,318,352,278

I'm hoping that's closer to what everyone meant?

You are writing the program, and you should write something that makes good sense to you.

Right now, you close the file, and then the program keeps trying to write data to that file. Furthermore, you are still printing to the serial monitor while writing to the file.

Neither of those actions make any sense to me.

When it is time to close the file, the program has to do something else. One approach is to print a message and go into an endless wait loop until a human comes along to pull the plug.

Here is one way to do the latter:

  if (currentTime >= timeLimit) {
    myFile.close(); //close the file
    Serial.println ("Data file closed, program stopping");
    while(true); //wait here until reset
  }

jremington:
You are writing the program, and you should write something that makes good sense to you.

Right now, you close the file, and then the program keeps trying to write data to that file. Furthermore, you are still printing to the serial monitor while writing to the file.

Neither of those actions make any sense to me.

When it is time to close the file, the program has to do something else. One approach is to print a message and go into an endless wait loop until a human comes along to pull the plug.

Here is one way to do the latter:

  if (currentTime >= timeLimit) {

myFile.close(); //close the file
   Serial.println ("Data file closed, program stopping");
   while(true); //wait here until reset
 }

I more or less understand it, it is just taking a little while for me to connect the dots since i'm still very new at this, but I get what you're saying. And I was keeping the print to serial monitor command so I could just check on the output numbers while tweaking the code but I will take it out when trying to get the actual data. Thanks

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.