SD Card Datalogger

I'm working on a project to log car oil pressure, rpm, temperature etc to an SD card. I'm using an ATmega1284P I bootloaded and fat16lib's great SdFat library and AnalogLogger example. It's working pretty well with no edits from me, logging six analog pins at approximately 20ms. This is faster than I need, the maximum sampling rate I need is for oil pressure (say every 100ms). The other metrics like exhaust and water temperature can be slower e.g. every second to save SD space.

Some of my sensors are digital e.g. DS18B20 water temp sensors and my RPM sensor which is a hall sensor counting the pulses from a toothed wheel which I am measuring using the pulseIn function. I have done a bit of basic arduino programming but the AnalogLogger code is way beyond what I have done before - it looks like more C++ coding than the basic arduino functions I have used in the past. I'll need to edit it to include the non-analog sensors.

I've read through the code and get a sense of it. Data from analogRead is written to a buffer and then flushed out periodically but I don't really understand exactly how it works e.g. ofstream and bout << ',' << analogRead(ia);. I', hoping I can edit the extract below to include my DS18B20 and hall sensor reading.

// read analog pins and format data
for (uint8_t ia = 0; ia < SENSOR_COUNT; ia++) {
#if ADC_DELAY
analogRead(ia);
delay(ADC_DELAY);
#endif // ADC_DELAY
bout << ',' << analogRead(ia);
}
bout << endl;

// log data and flush to SD
logfile << buf << flush;

Or I could go with the SD library and something like Tom Igoe's code in [SOLVED] Datalogger example sketch problem with LC Studio SD module - Storage - Arduino Forum using commands like "dataFile.println(dataString)" which I'm a bit more confident with. Do you think this code would give me the max sampling/write speed I need of 100ms?

Would really appreciate any advice / ideas - should I attempt to edit the code above and stick with the SdFat example (which is probably beyond me!) or go to the other SD library example.

The "stream" style output isn't that hard to get used to.  Basically it's:

[code]file.print(data);  ->   file << data;

It has an advantage that you can chain the data together:

file.print(data1); file.println(data2);  ->  file << data1 << data2 << endl;

From the code snippet you posted it looks like "bout" is a stream that adds to the buffer "buf" and "logfile" is the file on the SD card.[/code]

It's working pretty well with no edits from me, logging six analog pins at approximately 20ms.

I suspect you will lose data points occasionally. SD cards have occasional write delays of 100 or more ms. These delays happen when the SD controller does large flash erase operations or moves data for flash wear leveling. The SD specifications allows write delays of up to 250 ms. Most modern cards have shorter delays but few cards are capable of logging data reliably at 20 ms.

Have you checked the interval between points?

If missed data points can't be tolerated, you will need to use a more complex logger based on an RTOS or collecting data in a timer driven interrupt routine.

Thanks for your replies, I will do some more reading and get used to the coding nomenclature - thanks for the pointers John. I'll try and insert a simple piece of code like digitalRead to record whether a pin is high or low and writing it to the SD every second as opposed to ten times each second for oil pressure if that is possible (to minimise storage space required).

Thanks fat16lib, it would be better if I could ensure that I didn't miss points. I did a four minute test run sampling as quickly as possible (LOG_INTERVAL set to 1) and had only thirteen data points where there was a gap of more than 100ms between consecutive points. These seem to have occurred at 67-68 seconds and 158-168 seconds. The maximum lag between points was 420 milliseconds. It seems like there are possibly two events causing the delay (one lasting about 100 - 160ms and one lasting approx 420ms), though maybe I am reading too much into this short experiment! Sorry for the poor table format, I am new to forums...

millis date time sens0 sens1 sens2 sens3 sens4 sens5 Time Since Last Point
67239 30/04/2014 20:28:00 13 48 67 79 5 0 130
67497 30/04/2014 20:28:01 25 48 73 80 7 1 104
68200 30/04/2014 20:28:01 15 44 63 76 0 0 420
68423 30/04/2014 20:28:02 9 39 57 67 11 11 223
158748 30/04/2014 20:29:32 8 46 63 71 11 10 160
159046 30/04/2014 20:29:32 8 47 64 80 0 4 151
159465 30/04/2014 20:29:33 11 47 69 80 0 3 419
159606 30/04/2014 20:29:33 10 40 56 72 0 3 141
159901 30/04/2014 20:29:33 16 55 71 82 0 1 154
160320 30/04/2014 20:29:33 16 51 71 88 0 0 419
160450 30/04/2014 20:29:34 8 41 64 81 22 19 130
163908 30/04/2014 20:29:37 16 47 71 91 15 3 417
164163 30/04/2014 20:29:37 9 42 64 85 22 18 255

The card I am using is:

I will look into the methods you suggested RTOS / interrupt routine - many thanks.

Unfortunately "high end" cards like your SanDIsk HC I don't perform well with Arduino. Arduino uses SPI mode and has little buffering so it uses single block commands.

These cards were designed for multi-block streaming in 4-bit SD mode with very large writes to flash. This results in poor performance with Arduino.

Here is the SdFat bench test of this 4GB SanDisk class 4 card http://www.amazon.com/SanDisk-Class-Flash-Memory-SDSDB-004G-AFFP/dp/B007JRB0RY.

Type is FAT32
File size 5MB
Buffer size 100 bytes
Starting write test. Please wait up to a minute
Write 188.76 KB/sec
Maximum latency: 31424 usec, Minimum Latency: 84 usec, Avg Latency: 524 usec

The maximum write latency is under 32 ms.

Here is the same test for a class 10 SanDisk 4GB HC I card.

Type is FAT32
File size 5MB
Buffer size 100 bytes
Starting write test. Please wait up to a minute
Write 190.59 KB/sec
Maximum latency: 193668 usec, Minimum Latency: 84 usec, Avg Latency: 519 usec

The maximum write latency is about 194 ms.

You should look at the nilSdLogger example in NilRTOS20130720.zip here Google Code Archive - Long-term storage for Google Code Project Hosting.. This small RTOS will run well on your 1284P.

Thanks very much, I'll pick up one of the cards you suggested... I would never have known that the Class 10 was not so good in this application.

Thanks for directing me to your RTOS logger... I was checking out your FreeRTOS20111031 earlier (Google Code Archive - Long-term storage for Google Code Project Hosting.), I'll have a look at NilRTOS20130720 tonight.

With 1284p you may log 30kB/sec under Nilrtos without loosing single byte. Afaik it has no sense to mess with special sdcard types for a datalogger - instead a robust sw solution is needed - therefore I recommend you the Nilrtos with its FIFO implementation.

Make a task which will scan all your sensors each XXmsecs and it will fill in the FIFO with sensor's data. Make the second task, which will check whether the sdcard is ready and it will then empty the FIFO accordingly. The FIFO buffer few kBytes large will eliminate the sdcard's outages.

The FIFO size to estimate is easy:

FIFO size = max_sdcard_latency * data_rate
For example:
max_sdcard_latency = 250msec
data_rate = 400 bytes to log each 20msecs
FIFO size = 250msec * (400bytes/20msec) = 5kBytes

As fat16lib said the sdcards have large writing latencies, moreover of an unpredictable nature.

For more info: NilRTOS - A Fast Tiny Preemptive RTOS - #34 by pito - Libraries - Arduino Forum

Thank you both that's great, looks perfect - I'll have a play around with that.

One thing I can't work out at the moment... I can run the nilSdLogger sketch no problem on my Uno with Deek Robot SD shield, but when I try on my 1284p (which I have stripboarded - I've attached a photo) I get an error:

SD problem
SD errorCode: 0X4,0XFF

The weird thing is that the card works fine in my 1284p with the other SD related sketches I have run, but not seemingly with this one at the moment.... I will have a play with the settings, I have tried changing the chip select from SS to 4 which I'm pretty sure is consistent. Incidentally I am using 1284p bootloaded with "Mighty 1284p 16MHz using Optiboot" (Maniacboot) and IDE 1.0.5-r2, I am assuming these are compatible with the nilSdLogger.

Just to mention, I get no serial prompt with any sketches so if for example with SDInfo you are normally prompted to type any character to start after opening the serial monitor. I never get the prompt with my 1284p but it still works if I type a character, it then returns details of the card in the serial monitor.

Not sure if this is connected...I've had a quick play with the settings and can't seem to get it going at the moment.

The sdcard uses 4,5,6,7 (pins 5,6,7,8) on the 1284p.

It seems you are using CD4050 there as the level shifter- that is not the chip you have to use - you must use 74HC4050 instead.
The old CD4050 will most probably not work (not rated for 3.3V and slow).

The lost serial data - two potential issues there:

  1. CD4050 most probably does not work with 3.3V and that serial speeds properly - and you use it as the level shifter for TX, and/or,
  2. also mind some HC-05 firmwares have got a timeout of 5secs when TX is idle, then it goes to a sleep. Waking up the HC-05 from the arduino TX side takes some time, so your incoming data may get lost.

PS: You may consider single 3.3V power. The 1284p works fine @16MHz and 3.3V. Your life gets much easier with single 3.3V, no messing with level shifters, also a single lion/lipo cell with an LDO regulator does the job then..

Hi Pito, you have sharp eyes to get all that from my grainy photo! Thanks that is really helpful, I've ordered a 74HC4050N... a poorly performing level shifter certainly would fit in with the symptoms. As the pinout is the same versus the CD4050 I should be able to slot straight in to compare.

Thanks for the idea to go to 3.3V, I think it is a good one which I could implement without many changes. Would that affect accuracy of my analogue readings as less volts per division for the ADC? The pressure sensor I am using gives 0 - 5V output but presumably I could just run through a potential divider to scale to 3.3V?

PS I am currently monitoring via USB so the bluetooth module is not a factor I think. It is currently powered off and I am using via RX/TX1 so should not be interfering.

Would that affect accuracy of my analogue readings as less volts per division for the ADC?

No. There is on-chip 2.5V or 1.1V reference voltage for the ADC. Use that instead of 5V.

The pressure sensor I am using gives 0 - 5V output but presumably I could just run through a potential divider to scale to 3.3V?

Yes, a resistive divider will work fine.

The RTCs uses open drain (the I2C standard) so you do not need a level shifter there (or use something newer than the obsolete DS1307). There are zillion of op-amps running @ 3.3V, all intelligent I2C/SPI sensors I know run @ 3.3V, all sdcards run @3.3V, all BT modules are 3.3V, all RF modules are 3.3V, all W5100/5200 stuff is 3.3V, all SPI/I2C SRAM/MRAM/FRAM memories are 3.3V, all newer displays are 3.3V (and the older run well with 3.3V signals), etc. Even LEDs run well at 3.3V :)..
Frankly I do not understand people building their own boards still mess with 5V :stuck_out_tongue:

3.3V definitely certainly does have a lot of advantages then - it would save me a few components which is always good! I've hooked up the 1284p to the SD card shield and everything worked as it should so I think the CD4050 is the problem with the SD problem and will be solved by the 74HC4050.

The lost serial data I think may be caused by my removing the following line of code, which won't compile when I select atmega1284 (get the error message "no match for 'operator!' in '!Serial'"):

while(!Serial) {} // wait for Leonardo

If I remove the ! operator I get error message "could not convert 'Serial' to 'bool'" I think because 'Serial' isn't included in the Optiboot serial library?

The lost serial data I think may be caused by my removing the following line of code, which won't compile when I select atmega1284 (get the error message "no match for 'operator!' in '!Serial'"):

The official Arduino boards reset when the serial monitor is opened.

One of the hardware flow control lines (DTR) of the ATmega8U2/16U2 is connected to the reset line of the ATmega328 via a 100 nanofarad capacitor.

If you do not implement this feature on your 1284P board, you will not see the first serial output.

Thanks so much for all your help, sorry for all the silly questions! Not having the dtr connected up was preventing me receiving the first serial input. I tried to modify an old usb-ttl I had lying around but damaged it soldering pin 2 via the capacitor to the reset. I'll use my uno board with the chip removed to upload sketches and monitor the serial for now (or use my bluetooth module)...

I'll have a go at bringing my code in for the digital sensors next.

I do

//while(!Serial) {}  // wait for Leonardo

Unfortunately this line won't compile for me for 1284... I get error:

no match for 'operator!' in '!Serial'

I believe this command was added in 1.0... but I guess isn't there for the 1284 I added which perhaps pre-dates this version

..therefore I've commented it out... :slight_smile:

Ah got you... I'm being slow! I'll have to figure out a way to sort it out without buying something new. Perhaps I can pull the reset pin low - software reset or something.