Struggling to maintain a fixed sampling rate - I2C pressure sensor

Hi!

I've got an I2C pressure sensor working with an Arduino Mega 2560 R3.
I'm aiming for a 20Hz sampling rate, that's constant for 15 hours straight. Hence I have a loop that requests a reading from the sensor every 50,000 microseconds:
if ((micros() - lastWrite) > 50000) {
takeReading()
lastWrite +- 50000
}

The result is the image uploaded. X-axis is seconds, and y-axis is number of samples. So each dot shows how many samples were recorded (on an SD card) each second, in the ~100 second experiment.

Does anyone know why I'm getting such a sampling frequency pattern?

Thanks in advance!

takeReading() may take too long, perhaps due to SD card management. Try 10Hz for a test.

Can you post all the code please?

There are many ways to get things wrong.
First off why use microseconds? Especially when you want to delay for milliseconds. It is not more accurate and it rolls over very much quicker.

Can someone find a datasheet for that sensor ? They seem to have a replaced it with a useless "Product Compliance".

This is the information I can retrieve (for example from mouser.com):

  • 3.3V or 5V.
  • 0 ... 500 Pa, and also 0 to 250 Pa Differential.
  • I2C interface.

In a previous topic, mutton15305 told that code on Github is used for the I2C interface, but that is not Arduino code.

Your posted graph on my side looks like a quote from Woodstock (Peanuts reference).

Fixed!! I tried 10 Hz and the same pattern showed up. As I was about to post the code, I realised that instead of lastWrite += 50000, I had lastWrite +- 50000!
After correcting that, this is what I got.

Does anyone have any advice to optimise this code? I need it to reliably datalog for 15hrs+.

#include <Wire.h>
#include <SD.h>

#define ULTRALOWPRESS_SET_DEV_ADDR 0x6C
#define ULTRALOWPRESS_REG_PRESS 0x30

File myFile;
int16_t pressure_16bit;
int pin_chip_select = 53;

void setup() {
  Wire.begin();
  Serial.begin(9600);
  pinMode(pin_chip_select, OUTPUT);
   
  if (SD.begin()) {
    Serial.println("SD Card ready to use");
  } else {
    Serial.println("SD initialisation failed");
    return;
  }
}

void loop() {
  static unsigned long lastWrite = micros();
  if ((micros() - lastWrite) > 50000) {
    Wire.beginTransmission(ULTRALOWPRESS_SET_DEV_ADDR);
    Wire.write(ULTRALOWPRESS_REG_PRESS);
    Wire.endTransmission();
    Wire.requestFrom(ULTRALOWPRESS_SET_DEV_ADDR, 2);

    //the 16 bit sensor output value is signed, so use signed data type
    Wire.readBytes((uint8_t*)&pressure_16bit, 2);

    Serial.println(pressure_16bit);

    myFile = SD.open("sampling.csv", FILE_WRITE);
    if (myFile) {
      myFile.print(pressure_16bit);
      myFile.print(",");
      myFile.println(millis());
      myFile.close();
    } else {
      Serial.println("Error opening file");
    }
    lastWrite += 50000;
  }
}

Thanks!

1 Like

Here is the datasheet: https://download.mikroe.com/documents/datasheets/SM8436_datasheet.pdf

What do you mean exactly? Do you mean milliseconds would be faster for my intended purpose?

I would set a hardware timer for 50 ms. Have an interrupt set a flag each time the timer rolls over. Then the main loop will see the flag, take a reading then reset the flag.

It is a terrible (but very common) mistake to open and close the file every time you write one tiny bit of data.

Doing so greatly slows down the data collection rate and greatly increases the chance of fatal SD card errors and data loss.

Open the file ONCE in setup() and close it when you are all done collecting data.

    myFile = SD.open("sampling.csv", FILE_WRITE);
    if (myFile) {
      myFile.print(pressure_16bit);
      myFile.print(",");
      myFile.println(millis());
      myFile.close();

What is the advantage of this over a loop like I have:

if ((millis() - lastWrite) > 50) {
    takeReading();
    lastWrite += 50;
  }

And for the hardware timer, would an RTC clock (e.g. DS3231) work?

1 Like

No.

I mean that the microseconds counter wraps round much sooner than the milliseconds counter.
Do you know what wrap round means?

What needs optimizing? Power usage? Accuracy? Cost? It looks adequate as is.

I might record the millis() just after taking the reading, in case anything you add in the future takes time.

If you open your file just once in startup and don't close, you might add a myFile.flush(); after writing. SD - Arduino Reference

I don't. Would you be able to explain?

Thanks!

Optimising for reliability, really.

Thanks for the other suggestions. Notes taken.

Wrap round is when a counter holds a number so big that all the bits in the number are at a logic one. If you then add one to that number then the number wraps round and becomes zero.

For milliseconds this happens every 40 days or so but for microseconds this happens after approximately 70 minutes.

Your technique for coping with this is fine but all variables used must be the unsigned long type.
In this code

The constant 50000 is not in this form. You could put this into an unsigned long variable or force the compiler to treat it as one by using 50000UL.

By using milliseconds you would not fall into this trap.

Every .flush() adds to the time required to collect data, and increases the overall error rate of the already miserable performance of the Arduino SD library.

If the Arduino is powered down without closing, the only data you lose (in the absence of other failures) is the last, unwritten 512 byte buffer.

Ok, got it, thanks! Have switched to millis().

Ok, so if I don't have a myFile.close(); statement, I can expect to lose, on average, ~256 bytes of data?
In this case that would be 128 pressure sensor values, or ~6 seconds worth of data at 20Hz, which would be absolutely fine.

I just ran the script for ~140 seconds with myFile being opened once in setup, and no myFile.close(); in loop. After unplugging the Arduino, and checking the SD card, I saw that no data had been written. Do you know what could be going on?

I currently have no other way (e.g. a button) triggering the myFile.close(); statement.