Post the code, and explain how you checked the file on the SD card.
In any case, it is a really, really excellent idea to implement some means, like a button, to terminate data collection properly, and close all files.
Post the code, and explain how you checked the file on the SD card.
In any case, it is a really, really excellent idea to implement some means, like a button, to terminate data collection properly, and close all files.
Your sample rate is the sum of:
The Hardware timer will be exactly the same every time.
Regarding the RTC. I don't think it will help the sample rate but it would allow you to timestamp the file.
if ((millis() - lastWrite) > 50) {
takeReading();
lastWrite += 50;
}
The data most probably have been written, but the file header (indicating the file size) is updated only on file close, maybe also on file flush?.
Yes, the file size is updated on flush(). I did a test to see how long flush takes, and it is not as bad as I recall. Typically 7-15 ms, but sometimes as much as 70 ms.
As expected the last 100 lines are lost in this simple test, where the file was not closed before exit, but the presence of the last few lines may in general be unpredictable:
#include <AStar32U4.h>
#include <SPI.h>
#include <SD.h>
const uint8_t chipSelect = 4;
void setup()
{
Serial.begin(115200);
while (!Serial);
Serial.print("Initializing SD card...");
if (!SD.begin(chipSelect))
{
Serial.println("Card failed, or not present");
while (1) {} // done
}
// Open the file.
Serial.println("Opening file...");
File file = SD.open("test.txt", FILE_WRITE);
if (!file)
{
Serial.println("error opening test.txt");
while (1) {} // done
}
// write a bunch of data
for (int i = 0; i < 1000; i++) {
file.print ("Line number ");
file.println(i);
if ((i %100) == 0) { //every hundred lines, flush and time the operation
unsigned long start = millis();
file.flush();
Serial.println(millis() - start);
}
}
// and exit
//file.close();
Serial.println("data written");
}
void loop()
{
}
Ok, thanks!
What would happen in the case a loop takes > 50ms to run?
And since I want to run this for 15hrs+, won't the hardware timer lose time relative to a real time clock? Is there a way to deal with that in a precise manner?
Yep it works fine if I flush.
I found out something interesting. While my average interval between readings is 50ms, the actual interval between subsequent readings varied like this
I moved myFile.flush(); to an if statement so that only flushes every minute, rather than after every reading. That seemed to reduce the average loop time, and produced this variation in time intervals between samples, which is a lot better
I then removed the remaining unnecessary Serial.println statement from the loop, and got this graph instead. It's much closer to perfectly consistent 50ms intervals:
When I increase the interval to 100ms, I get the same pattern, but at a slightly lower frequency:
(CODE BELOW)
I timed the main loop and it was 0ms when not taking a reading and 7-8ms when it takes a pressure reading. So it doesn't seem like the loop being too long is causing the variation in sampling intervals.
Should I now be looking into timer interrupts if I want a perfectly consistent sample rate? Or is there something else I should do?
Thanks again! Here's the code:
#include <Wire.h>
#include <SD.h>
#define SENSOR_ADDRESS 0x6C
#define PRESSURE_REGISTER_ADDRESS 0x30
File myFile;
int16_t pressure_16bit; //the 16 bit sensor output value is signed, so use signed int
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;
}
myFile = SD.open("timer.csv", FILE_WRITE);
}
void loop() {
static unsigned long lastRead = millis();
/* if static variable declared inside a function. Then each time the
function is called the static variable will maintain the last
value it had at the end of the previous function call.*/
unsigned long start = millis();
if ((millis() - lastRead) >= 50) {
Wire.beginTransmission(SENSOR_ADDRESS);
Wire.write(PRESSURE_REGISTER_ADDRESS);
Wire.endTransmission();
Wire.requestFrom(SENSOR_ADDRESS, 2);
Wire.readBytes((uint8_t*)&pressure_16bit, 2);
if (myFile) {
myFile.print(pressure_16bit);
myFile.print(",");
myFile.println(millis());
} else {
Serial.println("Error opening file");
}
static unsigned long lastFlush = millis();
if ((millis() - lastFlush) >= 60000) {
myFile.flush();
lastFlush += 60000;
}
lastRead += 50;
}
Serial.println(millis() - start);
}
Interrupts don't help if the controller is busy with writing to the SD card. It's a stupid idea to do another write to the SD card in the interrupt routine.
What you can do is writing the values at regular sampling rate into a buffer. Then it may be okay to interrupt the file write with another write to a buffer. Two buffers will be needed then, one that is written to the SD card while the other one is filled with new values.
Once you setup the hardware timer it will generate an interrupt every xx ms (what ever you setup).
The interrupt will happen, normally setting a flag to be seen and addressed by the looping code. If you ignore that flag, the interrupt will be set again next xx ms.
If you loop takes > 50ms to run, you will not be able to maintain a 20 ms measurement rate.
Just curious, 20 ms is much faster than a typical pressure monitor. Mechanical things happen kind of slow in general. Are you looking for some peak or valley? If so consider a different approach.
Collect data in a circular buffer testing each reading for you threshold value. Do this test with the raw A/D value. If you sense the reading exceeds your threshold write the complete circular data to the SD card. This should give you both before and after the "peak" data.
If the loop doesn’t actually do anything then the compiler is smart enough to remove it. Anyway a result of zero ms could actually mean less than one. So 0.999 ms would show up as zero.
As others have pointed out it is the SD card write that takes the time, and this is a variable time because of the way that SD flash memory works. It is by nature flakey and may require multiple writes to achieve a specific bit pattern. All this is handled inside the SD card.
But since my loop takes 7-8ms on average when it does a write, and I only need a reading every 50ms, shouldn't an interrupt work?
With my current loop, the lastRead variable is only updated once the loop is done reading a value (and flushing every minute). This means that if one loop takes say 5ms, then the time since the last read will be 55ms, and hence the next loop will be 45ms. An interrupt shouldn't be affected by how long the loop takes to run, as JohnRob says.
Ok thanks.
I only need a 20Hz rate, and so need to take a reading every 50 ms.
Since I'm analysing subtle changes in breathing, I need this sampling rate.
Ok thanks, makes sense. Would using the SD.fat library (GitHub - greiman/SdFat: Arduino FAT16/FAT32 exFAT Library) help in this case?
I tried the SdFat library, and even tried the dedicated SPI mode but it made no difference to my loop times, which were 4 microseconds when not taking a reading, and 7280 microseconds when taking a reading.
Then your readings will be spaced equally with only 4 µs jitter.
I tried using interrupts. Is this pattern of inaccuracies normal?
Thanks in advance!
Here's the code:
#include <Wire.h>
#include <SD.h>
#define SENSOR_ADDRESS 0x6C
#define PRESSURE_REGISTER_ADDRESS 0x30
File myFile;
int16_t pressure_16bit; //the 16 bit sensor output value is signed, so use signed int
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;
}
myFile = SD.open("interr.csv", FILE_WRITE);
cli();
//set 16 bit timer1 interrupt at 1Hz
TCCR1A = 0;// set entire TCCR1A register to 0
TCCR1B = 0;// same for TCCR1B
TCNT1 = 0;//initialize counter value to 0
// set compare match register for 1hz increments
OCR1A = 780.25;// = (16*10^6) / (20*1024) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS12 and CS10 bits for 1024 prescaler
TCCR1B |= (1 << CS12) | (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
sei();
}
ISR(TIMER1_COMPA_vect){
if (myFile) {
myFile.print(pressure_16bit);
myFile.print(",");
myFile.println(millis());
} else {
Serial.println("Error opening file");
}
}
void loop() {
static unsigned long lastFlush = millis();
Wire.beginTransmission(SENSOR_ADDRESS);
Wire.write(PRESSURE_REGISTER_ADDRESS);
Wire.endTransmission();
Wire.requestFrom(SENSOR_ADDRESS, 2);
Wire.readBytes((uint8_t*)&pressure_16bit, 2);
if ((millis() - lastFlush) >= 30000) {
myFile.flush();
lastFlush += 30000;
}
}
Fix your interrupt handling first. Only save the time and measured value to variables and do the writing in loop(). SD writing in the ISR can cause any strange behaviour whenever the writing causes a buffer written.