Help with SD FAT raw write to SD card for fast DAQ with external interrupt.

Hi,
I have seen plenty of discussion about fast DAQ systems based on arduino with SD card data logging but I have been unable to answer numerous questions. I am new to embedded systems and in particular C code, as such I understood that using low-level functions such as raw write would be difficult! As such I am very grateful for any input and advise!

My application:

I am using the arduino Nano with ATmega328 and the adafruit SD card breakout board for SPI comms to the SD card.
I am using an external interrupt to trigger data acquisition from analog input pins A0-A4. I realize this takes a certain amount of time to multiplex the ADC between the 5 channels and have made calculations based on sampling A0 through to A4. The trigger rate is at 2.5khz meaning 0.4ms per 'set' of samples (A0-A4) i.e all 5 channels are read at each interrupt. I have looked at the Atmega data sheet and have set the ADC clock divisor to 32 (smallest i can get away with for 10 bit resolution as stated here - https://sites.google.com/site/measuringstuff/the-arduino - I still need to test this! ) therefore to take the 'set' of samples should take around 0.13ms (0.152ms measured) that is with 13 ADC cycles per conversion and a couple of CLK cycles between differing inputs to read and change registers etc. I have made the decision to define my temporal resolution at 0.4ms and therefore am assuming all 5 samples are taken simultaneously at the interrupt. This seems fine to me.

So here is where the problem starts, I have seen places that I can achieve a maximum write speed to the SD card of 227KB/s without CRC ( http://forum.arduino.cc/index.php/topic,98639.0/topicseen.html) which should be more than adequate for my needs, as I only need to write (assuming 2 Bytes for the 10 bit ADC) 252500=25KB/s.
I have tested this using code from SDFat data logging example and it seems as though I am missing readings - that is when testing speed I am only writing the number of times the ISR has been called and not the samples and I am still missing readings. I believe this is understandable because the Interrupt may well be interfering with the write operation.

In order to achieve a fast transfer rate I have then tried using code from the RawWrite example included in the SD fat lib. But this is where I am stuck - i have tried to implement a 512 byte buffer and then write to the SD Card when this full but I am having trouble formatting data for text file, I am currently using sprintf but I then have trouble with "unsigned char to char* errors" - have tried both %d and %u. However, I was actually hoping to write the data in raw binary and process them after testing using LabView but couldn't (haven't understood anything I have read) found any appropriate information on how to do this!

So a few questions:
Will the latency associated with the SD card write be the reason I am not getting the write speed I require?
Is it reasonable to write 25kB/s to an SD card using the SD fat library?
Are there any tips for using the Raw write method of logging data?
Here's my code (apologies for its ugliness!):

#include < avr/io.h >
#include < avr/interrupt.h >
#include <Arduino.h>
#include <avr/pgmspace.h>
#include <SdFat.h>
#include <SdFatUtil.h>
#include <stdio.h>

const int chipSelect = 10; //Set chip select pin required by SD.h
const int IntPin = 2;      //Set external interrupt pin
const uint32_t BLOCK_COUNT = 293000UL; //Number of blocks in contingous file enough for 100minutes test
// time to produce a block of data data rate at 2.5hz for 5 channels each 16bit
const uint32_t MICROS_PER_BLOCK = 20000;


volatile uint16_t anin0;        // Analog Inputs 
volatile uint16_t anin1;
volatile uint16_t anin2;
volatile uint16_t anin3;
volatile uint16_t anin4;
int inByte;  //declare variable for serial instructions
uint16_t i = 0;
uint16_t times=0;
uint16_t writes=0;


/****** Buffer and SD Declartions******/

// store error strings in flash to save RAM
#define error(s) sd.errorHalt_P(PSTR(s))

// File system object
SdFat sd;

// file for logging data
SdFile file;

// file extent
uint32_t bgnBlock, endBlock;

// clear the cache and use it as a 512 byte buffer
uint8_t* pCache = (uint8_t*)sd.vol()->cacheClear();



/****SBI_and_CBI_Definition**********/
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

void setup() 
{
  Serial.begin(115200);
  Serial.println("Initializing...");

  //*******************************I/O_PIN_SETUP**************************************************
  Serial.println("Initializing I/O...");
  //Set chip select pin is selt to output, even if not used....
  // pinMode(chipSelect, OUTPUT);
  // DDRD = B11111000; // port manipulation set unsed digital lines to OUTPUT(1) and 2:0 Input(0)
  Serial.println("Done");

  //*******************************SD_Card_&_BUFFER_SETUP************************************************** 	

  // initialize the SD card at SPI_FULL_SPEED for best performance.
  // try SPI_HALF_SPEED if bus errors occur.
  if (!sd.begin(chipSelect, SPI_FULL_SPEED)) sd.initErrorHalt();

  // delete possible existing file
  sd.remove("RAW.TXT");

  // create a contiguous file
  if (!file.createContiguous(sd.vwd(), "RAW.TXT", 512UL*BLOCK_COUNT)) {
    error("createContiguous failed");
  }
  // get the location of the file's blocks
  if (!file.contiguousRange(&bgnBlock, &endBlock)) {
    error("contiguousRange failed");
  }

  // tell card to setup for multiple block write with pre-erase
  if (!sd.card()->erase(bgnBlock, endBlock)) Serial.println("card.erase failed");
  if (!sd.card()->writeStart(bgnBlock, BLOCK_COUNT)) {
    error("writeStart failed");
  }

  // fill cache with eight lines of 64 bytes each
  memset(pCache, ' ', 512);


  //*******************************ADC_SETUP*********************************************************
  Serial.println("Setting up ADC....");
  // Initalise input channel to start at A0 (ADC Multiplexer 
  // Selection Register see 24.9.1 Atmega 328 Datasheet)
  //ADMUX |= (1  << MUX0);

  //To change to Left Adjust Result (ADC Multiplexer 
  // Selection Register see 24.9.1 Atmega 328 Datasheet)
  //	ADMUX |= (1  << ADLAR);

  // Enable ADC, ADC will be kept at 'enable' to avoid longer sample rate 
  // after re-enabling (ADC Control and Status Register A 24.9.2 Atmega328 Datasheet)
  sbi(ADCSRA, ADEN);

  // Set ADC Presacler to 32 --> 0.026ms per sample (13Cycles) (ADC Control 
  // and Status Register A 24.9.2 Atmega328 Datasheet)
  sbi(ADCSRA, ADPS2);
  cbi(ADCSRA, ADPS1);
  sbi(ADCSRA, ADPS0);
  Serial.print("Done");

  //***************************INTERRUPT_SETUP*********************************************************
  Serial.println("Initializing Interrupt Settings...."); 

  // Rising edge triggers Interupt (External Interrupt Control Register
  // See 13.2.1 ATMEGA 328 Datasheet)
  sbi(EICRA, ISC00);
  sbi(EICRA, ISC01);

  Serial.print("Done");
  Serial.println("Press 's' to start and 'e' to end test...");
}



void loop(){ 
  if (Serial.available()) inByte = Serial.read();

  if (inByte=='s'){
    sbi(EIMSK, INT0);    // Global Enable INT0 interrupt (External Interrupt Mask Register 
    sei();               // turn on interrupts 
    /*uint16_t d = 0;
    /*if (i%6==0){ 
     d=i/6; // used to index cache without overwriting the /n ect characters
     pCache[61+(i*61)+(d*3)]=(i); // put last read number at end of line. 
     }  
     //write data to cache
     pCache[(i*10)+(d*4)]  =(anin0);
     pCache[(i*10)+2+(d*4)]=(anin1);
     pCache[(i*10)+4+(d*4)]=(anin2);
     pCache[(i*10)+6+(d*4)]=(anin3);
     pCache[(i*10)+8+(d*4)]=(anin4);*/
    /*for (int8_t d = 5; d >= 0; d--){
     pCache[d] = i || d == 5 ? i % 10 + '0' : ' ';
     i /= 10;
     }*/


    sprintf(pCache[p+57],%u,i);    //write interrupt number in to cache
    pCache [p+ 63]='/n';


    //write cache to sd card after 48 samples. 
    if (i==48){
      uint32_t t=micros();
      if (!sd.card()->writeData(pCache)) error("writeData failed"); 
      i=0;
      writes++;
    } 
  }

  else if (inByte=='e'){  
    Serial.println(*pCache);
    delay(100);
    Serial.println(times); //Print the number of reads
    Serial.println(i);
    Serial.println(writes);
    delay(100);
    cli () ; //stop interrupt 
    // end multiple block write mode
    if (!sd.card()->writeStop()) error("writeStop failed");
    file.close();
  }        

}


ISR (INT0_vect){
  anin0=analogRead(0);     
  anin1=analogRead(1);
  anin2=analogRead(2);
  anin3=analogRead(3);
  anin4=analogRead(4);
  i++; 
  times++; 
}

The SdFat raw write example was meant to show the raw API and principle.

You need two 512 byte buffers to avoid problems with SD write latency.

You need to store data in the buffer in the ISR.

If you write binary data, you will be writing about 25 kb/sec. This should be possible.

A number of years ago I posted a program based on this idea. It is located here https://code.google.com/p/beta-lib/downloads/list the file is AnalogIsrLogger20121219.zip.

It logs data in binary and later you can use the program to convert the data to text.

It was designed to run on an UNO or Mega and log from a single ADC at very high rates.

Maybe you can use the ideas in this program.

There is an earlier file, fastLoggerBeta20110802.zip, at the same location that has a binary logger. I think there was a python script for converting binary data on a PC. This program uses an external ADC.

You can't use the programs directly, just the ideas. They are old and may not work with the latest Arduino system.

Thank for the reply!

I have tried a number of things and re-written the code from the ground up to log via binary.

Sorry to be simple but I am having lots of problems using the library, my lack of knowledge. The overall code I have written doesn't work for number of reasons so I am trying to tackle them one at a time. I have written a bit of code to check the following things:
Can I use any memory I allocate to an 512 byte array as a buffer or do I have to use pCache? - unanswered.
Are my points working correctly? - they seem to be.
Do I have enough RAM when I implement 2 x 512byte buffers?- I think I do.
And lastly and more crucially why is my code falling at the line before I open comms to the SD card? - No idea!

The code is very simple, create two arrays write data to them print them to the screen - check pointers. I then wanted to see if I could write the buffers to the SD card, this is where I am stuck. Upon opening the SD card the code gets stuck in a loop and just performs the first line in the setup routine - i.e prints free ram to the screen over and over..

Do you have any advice on why this is or how the functions need to be use sequentially?
Can I create and open the file in the setup, log to it continuously until some condition then close the file then truncate it?
Sorry for being simple, this is new to me - I have tried to plow through the library and understand its inner workings but I'm currently taking a day to understand a couple of lines!

Here's the simple code

#include <SdFat.h>
#include <SdFatUtil.h>
#include <BufferedWriter.h>

uint16_t Buffer[256];
uint16_t Buffer2[256];
uint16_t * b;
int de=0;
//==============================================================================

#define error(msg) sd.errorHalt_P(PSTR(msg))
//------------------------------------------------------------------------------
SdFat sd;
SdFile file;
uint32_t bgnBlock, endBlock; 

// SD chip select pin
const uint8_t chipSelect = 10;

//file size 2 blocks, one for each buffer (once I can write to SD card)
const uint32_t BLOCK_COUNT = 2UL;

// log file name
#define FILE_NAME "LOG.BIN"


void setup(){
  pinMode(10,OUTPUT);
  Serial.begin(115200);
  //Print Free Ram
  Serial.println(FreeRam());
  
//**** Start connection to SD, create file, do nothing else******* 
  if (!sd.begin(chipSelect, SPI_FULL_SPEED)) sd.initErrorHalt();

  // delete old log file
  // delete possible existing file
  sd.remove(FILE_NAME)

  if (!file.createContiguous(sd.vwd(), FILE_NAME, 512UL*BLOCK_COUNT)) {
    error("createContiguous failed");
  }

  // get the location of the file's blocks
  if (!file.contiguousRange(&bgnBlock, &endBlock)) {
    error("contiguousRange failed");
  }
}

void loop(){

  if (T<1){ 
    //Fill Buffer 1
    for(unsigned int i =0; i<256 ; i++){
      b=Buffer;
      Serial.println(i);
      Buffer[i]=i;
      delay(100);   
    }
    // Fill Buffer 2
    for(unsigned int i =0; i<256 ; i++){
      b=Buffer2;
      Serial.println(i*100);
      b[i]=i*100;
      delay(100); 
    }
    T++;
  }

  if(T==2) {
    //Print Buffer 1
    Serial.println(" ");
    for(unsigned int i =0; i<256 ; i++){
      Serial.println(Buffer[i]);
      Serial.println("_");
      delay(300);
    }
    //Print Buffer 2 
    for(unsigned int i =0; i<256 ; i++){
      Serial.println(Buffer2[i]);
      Serial.println("_");
      delay(300);
    }
    T++;
    delay(500);
    //Print Free Ram
    Serial.println("Free Ram: ");
    Serial.println(FreeRam());
  }
}

Again any input is much appreciated!

The issue with SDcard's write latency has been discussed several times here.
Imagine the max write latency with your sdcard would be 80ms (I did some measurements with graphs in past, search for it, the latencies are random appearing each xxx ms lasting for xxx ms).

When writing 25kB/sec you need a fifo buffer of size: 25kB/sec * 80msec = 2kbytes in order to compensate the latency.

Mind the latency could be up to 250ms. So I would doubt you can write such data stream without data loss with small atmegas.

Atmega1284p might help you, you may use for example 14kB fifo then. Or, you may use an external RAM.

PS: NilRTOS - A Fast Tiny Preemptive RTOS - #40 by pito - Libraries - Arduino Forum
I would recommend you to read that entire topic as that is the possible solution for you..

I agree with pito, you should use a board with more than the 2KB on Uno. It is possible to write binary data at the rates you need with an Uno but it is very tricky.

Pito's measurements about latency don't apply to multi-block streaming. I have written 8-bit adc data in streaming mode at up to 100,000 samples per second with an Uno. I used the AVR ADC at high clock rate. The attached file was logged by the AnalogIsrLogger example at 100 Ks/s with and Uno's ADC.

SD cards have two modes so you will need a card that is optimized for multi-block streaming. These cards have a large amount of RAM buffering and overlap flash programming with data transfer.

You have allocated two 512 byte buffers. SdFat has a 512 byte buffer and Serial has two 64 byte buffers so you are likely crashing due to lack of memory.

DATA.PNG

I have written 8-bit adc data in streaming mode at up to 100,000 samples per second with an Uno.

My lesson learned at that time was a large latency "peak" could come even after a longer time , ie. after 15 minutes of data logging. So when you see my graphs with latencies in range of 5-40ms, it does not mean there could not be a latency of 120ms after few minutes of logging. That was something I saw in practice. I had used cheapo sdcards, though.
For a serious work and data streams of 25kB/sec and more I would highly recommend the 1284p, the IDE supports it. Also NilRtos with its Fifo is a great tool as well..

pito,

I have recorded an hour of data at these rates using a single multi-block write. I wrote an Arduino audio recorder Google Code Archive - Long-term storage for Google Code Project Hosting. that records 8-bit wave files at 44.1 ksps using this method.

These programs, AnalogIsrLogger, and WaveRP, allocate and erase very large contiguous files. They then write these files as a single large multi-block write.

There are two types of write commands for SD cards. They are CMD24, and CMD25.

CMD24 which writes a single 512 byte block. This command can have very large latencies since the card can not be prepared for where the write occurs and must return a command status at the end of the write. This can result in very large latencies since the wear-leveling algorithm may require moving the flash area containing the block to a different physical location.

CMD24 does not return status until the block has been programmed in flash so most of the internal RAM buffer can not be used and you must wait for any wear-leveling operations.

CMD24 is used for all writes to file structures by Arduino SD libraries. Data is also written in this mode unless 1024 or more bytes are written and the write is on a block boundary. Very little is gained in the short multi-block writes that are possible in file mode write.

CMD25 continuously writes blocks of data until a stop transmission token is sent (instead of ‘start block’). This command can be combined with ACMD23. ACMD23 sets the number of write blocks to be pre-erased before writing (to be used for faster Multiple Block WR command). ACMD23 allows the SD card to prepare up to 2^22 blocks for write.

SD card have flash page sizes that are much larger than 512 bytes, often 4 kB or larger. CMD25 allows writes to be optimized for the actual flash page size and allows the internal RAM buffers to be used efficiently so flash programming can overlap data transfer during the multi-block write.

High end SD cards are optimized for the CMD25/ACMD23 mode. You still must test cards to find models that perform well in this mode.

Edit: Here is more detail from a SanDisk note:

Pre-erase setting prior to a multiple block write operation

Setting a number of write blocks to be pre_erased (ACMD23) will make a following Multiple Block Write operation faster compared to the same operation without preceding ACMD23. The host will use this command to define how many write blocks are going to be sent in the next write operation. If the host terminates the write operation (using stop transmission) before all the data blocks are sent to the card, the content of the remaining write blocks is undefined (can be either erased or still have the old data). If the host sends a greater number of write blocks than are defined in ACMD23, the card will erase blocks one by one (as new data is received). This number will be reset to the default (=1) value after Multiple Blocks Write operation.

It is recommended to use this command preceding CMD25, so that SanDisk’s SD Card will be faster for Multiple Write Blocks operation. Note that the host must send ACMD23 just before the WRITE command if the host wants to use the pre-erase feature. If not, pre-erase-count might be cleared automatically when another command (ex: Security Application Commands) is executed.

Send Number of Written Blocks

Systems that use the PipeLine mechanism for data buffers management are, in some cases, unable to determine which block was the last to be well written to the flash if an error occurs in the middle of a Multiple Blocks Write operation. The card will respond to ACMD22 with the number of well-written blocks.

Also SD cards with 16 KB pages are now common. These cards can be very inefficient in single block mode.

You still must test cards to find models that perform well in this mode.

Is there any "automatic" way to get the optimal mode? I mean something within SdFat telling us what model could be used with the card under test..

Multi-block mode is always faster for large writes.

You just can't do large multi-block writes with the SdFat file API. You must use the Sd2Card raw API. That's what this post is about. Sd2Card means V2 or newer SD cards.

File system writes always have latency problems. SD cards are not designed for random access so access to the FAT to allocate a cluster kills performance.

The SD card has two copies of the FAT so when a new cluster is allocated, a block from each FAT copy must be read, updated and written. This clears any pipe-lining since you are now accessing three or more areas of the card, FAT one, FAT two, current cluster, and the newly allocated cluster. This is also a lot of I/O each time a cluster is allocated.

I allocate 100 MB to 1 GB in a contiguous file when I use my high speed data loggers. I write the file using one raw multi-block write. I truncate the file when the logging session finishes.

I mean a test sketch, which, after inserting in an sdcard, will format the sdcard with a 500 MB (for example) contiguous file and will measure max multi-block writing speed possible. Such sketch would be nice to have :slight_smile:

I mean a test sketch, which, after inserting in an sdcard, will format the sdcard with a 500 MB (for example) contiguous file and will measure max multi-block writing speed possible. Such sketch would be nice to have

That's not the correct test. If your goal is to log data at some rate you want to know the latency characteristics when you are writing data at that rate. Fastest isn't best for this application.

Some cards have a fast average rate for multi-block writes but still have bad latency problems. Some have latency problems at all rates and some work well until some rate and then have problems.

I included a sketch in SdFat that demonstrates raw write. This sketch needs improvement to provide a better test of cards.

Use of raw write to contiguous files is almost not used so I have not worked on a good sketch to characterize cards for this mode. Use of this mode requires interrupts or a RTOS so few users are successful. You also need to implement a FIFO of 512 byte buffers or at least a two block ping-pong.

I see, so my current understanding is the most feasible approach (most universal one) covering vast majority of sdcards available (ie even cheapo sdcards without any pre-selection) would be a standard SdFat API with NilRtos and its FiFO, supported by a larger RAM (ie 1284p) for faster data streams (25-100kB/sec).

I see, so my current understanding is the most feasible approach (most universal one) covering vast majority of sdcards available (ie even cheapo sdcards without any pre-selection) would be a standard SdFat API with NilRtos and its FiFO, supported by a larger RAM (ie 1284p) for faster data streams (25-100kB/sec).

This is best for most people.

I should write a good example for raw write since it works well and is the mode SD cards are designed to used.

I just ran a test on a card in this mode.

Start raw write of 51200000 bytes at
25600 bytes per second
Please wait 2000 seconds
Done
Elapsed time: 2000.000 seconds
Max write time: 720 micros
Overruns: 0

This is an ATP 1 GB industrial card. It never took longer than 720 usec to write a 512 byte block at the rate that markallmatt wants to log data so two 512 byte buffers on a Uno would work fine.

Probably a low cost card would work fine at 25 KB/sec.

Edit: I did a test with a $5.00 4 GB class 4 SanDisk card and it would be fine for 25 KB/sec. Probably could do 50 KB/sec or more in an Uno with two buffers.

Edit: I did a short 1000 second test of the cheap SanDisk card at 50 KB/sec. Amazing for a $5.00 card.

Start raw write of 51200000 bytes at
51200 bytes per second
Please wait 1000 seconds
Done
Elapsed time: 1000.000 seconds
Max write time: 724 micros
Overruns: 0

Max write time for a 512 byte block is 724 usec so a block is transferred at 707 KB/sec.

Looks like the cheap, $5.00, card could log data at 100 KB/sec without latency problems on an Uno.

Here are some initial results that show promise for a very fast logger on Uno with cheap cards.

Here is a short 500 second test writing 50 MB.

Start raw write of 51200000 bytes at
102400 bytes per second
Please wait 500 seconds
Done
Elapsed time: 500.000 seconds
Max write time: 1108 micros
Overruns: 0

Longer tests are needed but two 512 byte buffers can easily handle this max latency.

Looks like other cheap cards can be used at high rates also. 200 KB/sec may be possible with more buffer on a Mega or 1284P.

I discovered that the latency pattern for multi-block transfers is bad on cheap cards unless you delay between the command to prepare for a transfer and sending the first block.

Here is the result for attempting to write at 200 KB/sec with no delay on the $5.00 card.

Start raw write of 51200000 bytes at
204800 bytes per second
Please wait 250 seconds
Done
Elapsed time: 250.143 seconds
Max write time: 65208 micros
Overruns: 19
fileBlock,micros
65,2424
80,41884
81,65208
82,27764
5569,3612
8257,3276
11009,2976
13761,2896
16449,3188
32833,2596
49217,3168
54721,2500
60161,2820
68353,3424
71105,2608
73792,3628
79297,3224
95681,3280
98369,2704

The card can't keep up in the beginning but settles to a max latency of under 4 ms.

If I delay one second before sending the first block, the card has a latency under 4 ms.

Start raw write of 51200000 bytes at
204800 bytes per second
Please wait 250 seconds
Done
Elapsed time: 250.017 seconds
Max write time: 3912 micros
Overruns: 19
fileBlock,micros
5569,3232
8257,2428
11009,3328
13761,3228
19201,2456
27393,2872
32833,3012
41025,3128
46529,2344
51969,2148
54720,3172
54721,3912
60161,2996
62913,3524
76545,3608
79297,2292
84737,2468
92929,3732
98369,3184

Overruns would happen on an Uno with two buffers but 4 ms at 200 KB/sec is 800 bytes so more buffers on a Mega or 1284P may work.

The next problem would be to acquire data at 200 KB/sec. This is one 512 byte block every 2.5 ms. On average 800 usec is required to transfer the block to the SD so that leaves less than 1.7 ms to acquire 512 bytes of data. Probably not possible.

How about 700 KB/sec without latency problems on an Uno.

I found an old 1 GB SanDisk card that runs flat out with a max write latency of 720 usec for a 512 byte block.

Looks like I should write a new fast ADC logger example using multi-block streaming.

Start raw write of 51200000 bytes
Elapsed time: 71.547 seconds
Write rate: 715.6 KB/sec
Max write time: 720 micros

This card was manufactured in 2006.

Card type: SD2

Manufacturer ID: 0X3
OEM ID: SD
Product: SD01G
Version: 8.0
Serial number: 4217130864
Manufacturing date: 12/2006

cardSize: 1015.81 MB (MB = 1,000,000 bytes)
flashEraseSize: 32 blocks
eraseSingleBlock: true

I have started development of a new binary logger for analog pins. Results on an Uno are encouraging.

Here is a debug test logging 5 analog pins every 200 usec to a $5.00 4 GB SanDisk card. This is 25,000 ADC values/sec.

Sample pins: 0 1 2 3 4
ADC clock MHz: 0.500
Sample interval usec: 200.00
Sample Rate: 5000.00
Deleting old file
Creating new file
Erasing all data
Logging - type any character to stop
Truncating file
Max block write usec: 920
Record time ms: 1324513
Sample count: 6622559
Samples/sec: 5000.00
Overruns: 0
Done

I ran this test for about 22 minutes and recorded over 33 million ADC values. The maximum time to write a 512 byte block is 920 usec. This is a bit longer than the 720 usec in the above tests due to ISR time for the ADC. Each block holds 10 ms of data. More testing is required to verify that the logger can run on an Uno for hours at this rate.

The ADC clock rate is 500 kHz so accuracy is slightly degraded. See the attached chart.

I am developing a C++ PC program to read these binary files.

Edit: The PC program converts the 66 MB binary file to a 112 MB csv file in about 10 seconds. The PC is fast with ssd drives.

ADC_ENOB.PNG

Hi guys, I just wanted to thank you for your help and give you an update on how everything is working!

I have basic functionality i.e I can log at the require speed for periods of time with ADC triggered via external interrupt, which is great. I am however having problems with truncating the file after allocating large chunks of memory in order to write at the speeds required.

Basically the code hangs on the truncate command and I have to reset the arduino to then use the convert to text file and dump the data to serial.

I am a little confused as to why this may be, i'm using the code as was in the AnalogIsrLogger example! Any input would be great!
Thanks

Here's the code:
Labview commands

  {
    switch(command[1])
    {    
      /*********************************************************************************
       ** LIFA Maintenance Commands
       *********************************************************************************/
    case 0x00:     // Sync Packet
      Serial.print("sync");
      Serial.flush();        
      break;
    case 0x01:    // Flush Serial Buffer  
      Serial.flush();
      break;
      
      //Initialise
    case 0x02:
      // initialize file system.
      if (!sd.begin(chipSelect, SPI_FULL_SPEED)) {
        //sd.initErrorHalt();
        Serial.println("IniError");
        }
      else {
        Serial.println("Ini Done");
        }
      Serial.flush();
      break;
      
      //log
    case 0x03:
      logData();
      Serial.flush();
      break;
      
   case 0x04:
     delay(100);
     Serial.print("Stopped");
     Serial.flush();
      break;   

    case 0x05:
      checkOverrun();
      binaryToText();
      Serial.flush();
      break;

    case 0x06:
      dumpData();
      Serial.flush();
      break;

      /*********************************************************************************
       ** Unknown Packet
       *********************************************************************************/
    default:      // Default Case
      Serial.flush();
      break;     
    }
  }
  else{  
    // Checksum Failed, Flush Serial Buffer
    Serial.flush(); 
  }   
}

ISR....

ISR(INT0_vect){

  if (isrBuf == 0) {
    if (emptyHead != emptyTail) {
      // remove buffer from empty queue
      isrBuf = emptyQueue[emptyTail];
      emptyTail = queueNext(emptyTail);
      isrBuf->count = 0;
      isrBuf->overrun = isrOver;
    } 
    else {
      // no buffers - count overrun
      if (isrOver < 0XFFFF) isrOver++;
      return;
    }
  }

  isrBuf->data[isrBuf->count++] = analogRead(0);
  isrBuf->data[isrBuf->count++] = analogRead(1);
  isrBuf->data[isrBuf->count++] = analogRead(2);
  isrBuf->data[isrBuf->count++] = analogRead(3);
  isrBuf->data[isrBuf->count++] = analogRead(4);

  // check for buffer full
  if (isrBuf->count >= DATA_DIM) {

    // put buffer isrIn full queue
    fullQueue[fullHead] = isrBuf;
    fullHead = queueNext(fullHead);

    //set buffer needed and clear overruns
    isrBuf = 0;
    isrOver = 0;
  }
}

ADC initialization...

void adcInit(){

  // Initalise input channel to start at A0 (ADC Multiplexer 
  // Selection Register see 24.9.1 Atmega 328 Datasheet)
  ADMUX = 0;

  // Enable ADC, ADC will be kept at 'enable' to avoid longer sample rate 
  // after re-enabling (ADC Control and Status Register A 24.9.2 Atmega328 Datasheet)
  sbi(ADCSRA, ADEN);

  // Set ADC Presacler to 32 --> 0.026ms per sample (13Cycles) (ADC Control 
  // and Status Register A 24.9.2 Atmega328 Datasheet)
  sbi(ADCSRA, ADPS2);
  cbi(ADCSRA, ADPS1);
  sbi(ADCSRA, ADPS0);
  //***************************INTERRUPT_SETUP*********************************************************
  //Serial.println("Initializing Interrupt Settings...."); 

  // Rising edge triggers Interupt (External Interrupt Control Register
  // See 13.2.1 ATMEGA 328 Datasheet)
  EICRA = (1 << ISC00);
  EICRA = (1 << ISC01); 
}  

// enable ADC and timer1 interrupts
void adcStart() {
  //Enable ADC, Auto trigger mode, Enable ADC Interrupt, Start A2D Conversions
  /* ADCSRA |= (1 << ADATE)  |(1 << ADEN) | (1 << ADIE) | (1 << ADSC) ;
   // enable timer1 interrupts
   TIMSK1 = (1 <<OCIE1B);
   TCNT1 = 0;*/
  sbi(EIMSK, INT0);
  sei();
}

logging/truncting code....

uint32_t const ERASE_SIZE = 262144L;
void logData() {


  SdFile file;
  uint32_t bgnBlock, endBlock;  
  // allocate extra buffer space
  uint8_t block[512 * BUFFER_BLOCK_COUNT];

  // initialize ADC and timer1
  adcInit();
  //adcInit(SAMPLE_INTERVAL, ANALOG_PIN, ADC_REF_AVCC);
  // delete old log file
  if (sd.exists(FILE_NAME)) {
    PgmPrintln("Deleting old file");
    if (!sd.remove(FILE_NAME)) error("remove");
  }
  // create new file
  PgmPrintln("Creating new file");
  if (!file.createContiguous(sd.vwd(), FILE_NAME, 512 * FILE_BLOCK_COUNT)) {
    error("create");
  }
  // get address of file on SD
  if (!file.contiguousRange(&bgnBlock, &endBlock)) {
    error("range");
  }
  file.close();

  // use SdFats internal buffer
  uint8_t* cache = (uint8_t*)sd.vol()->cacheClear();
  if (cache == 0) error("cacheClear"); 
  PgmPrintln("Erasing all data");

  // flash erase all data in file
  uint32_t bgnErase = bgnBlock;
  uint32_t endErase;
  while (bgnErase < endBlock) {
    endErase = bgnErase + ERASE_SIZE;
    if (endErase > endBlock) endErase = endBlock;
    if (!sd.card()->erase(bgnErase, endErase)) {
      error("erase");
    }
    bgnErase = endErase + 1;
  }
  // initialize queues
  emptyHead = emptyTail = 0;
  fullHead = fullTail = 0;

  // initialize ISR
  isrBuf = 0;
  isrOver = 0;

  // use SdFat buffer for one block
  emptyQueue[emptyHead] = (block_t*)cache;
  emptyHead = queueNext(emptyHead);

  // put rest of buffers in empty queue
  for (uint8_t i = 0; i < BUFFER_BLOCK_COUNT; i++) {
    emptyQueue[emptyHead] = (block_t*)(block + 512 * i);
    emptyHead = queueNext(emptyHead);
  }
  // start multiple block write
  if (!sd.card()->writeStart(bgnBlock, FILE_BLOCK_COUNT)) {
    error("writeBegin");
  }

  Serial.flush();

  uint32_t bn = 0;
  uint32_t t0 = millis();
  uint32_t t1 = t0;
  uint32_t overruns = 0;
  uint32_t count = 0;
  // start logging interrupts
  adcStart();
  Serial.print("Ready"); //print R to let labview know arduino is ready for logging.
  Serial.flush();
  while (1) {
    if (fullHead != fullTail) {
      // block to write
      block_t* block = fullQueue[fullTail];

      // write block to SD
      if (!sd.card()->writeData((uint8_t*)block)) {
        error("writeData");
      }
      t1 = millis();
      count += block->count;
      // check for overrun
      if (block->overrun) {
        overruns += block->overrun;
      }
      // move block to empty queue
      emptyQueue[emptyHead] = block;
      emptyHead = queueNext(emptyHead);
      fullTail = queueNext(fullTail);
      bn++;
      if (bn == FILE_BLOCK_COUNT) {
        // stop ISR calls
        ADCSRA = 0;
        cli();
        break;
      }
    }
    if (Serial.available()) {
      if(Serial.read()==0x04){
        // stop ISR calls
        ADCSRA = 0;
        cli();
        if (isrBuf != 0) {
          // put buffer isrIn full queue
          fullQueue[fullHead] = isrBuf;
          fullHead = queueNext(fullHead);
          isrBuf = 0;
        }
        if (fullHead == fullTail) break;
      }
    }
  }

  /*if (!sd.card()->writeStop()) error("writeStop");
  // truncate file if recording stoped early
  if (bn != FILE_BLOCK_COUNT) {    
    if (!file.open(FILE_NAME, O_WRITE)) error("open");
    if (!file.truncate(512L * bn)) error("truncate");
    file.close();
  }*/ 
}

And lastly I just wanted to give you an overview of the project I am working, for context and anyone that maybe interested....

I am researching into tidal stream turbines, specifically the condition monitoring of devices. As such I am building a 0.5m diameter turbine model for flume testing in flows of upto 1 m/s. The turbine is equipped with a motor to apply breaking torque and hence allow for testing over the power curve of the turbine. The turbine is intrumented with strain gauge beams for measuring blade axial thrust and twist. The turbine is also equipped with an accelerometer - all of the on board instrumentation is mounted in the hub and the data is aquired through an arduino nano and stored on an SD card til the end of testing at which point the files are sent back to the control PC for logging. Here's the setup:

Looks like you added cli() at the end of logging.

      if (bn == FILE_BLOCK_COUNT) {
        // stop ISR calls
        ADCSRA = 0;
        cli(); <-------HERE
        break;
      }
  .....
      if(Serial.read()==0x04){
        // stop ISR calls
        ADCSRA = 0;
        cli(); <-------AND HERE

You need to replace this line with something that disables your external interrupt but doesn't disable global interrupts.

        ADCSRA = 0;