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++;
}