sd card

This class was written for the Arduino library.
It reads and writes directly to sectors on a sd card.

It has only two functions:

SDCARD.readblock(sector number); //read 512 bytes from this sector
SDCARD.writeblock(sector number); //write 512 bytes to this sector

The 512 bytes are read/write from a buffer which must be declared:

volatile unsigned char buffer[512] ; //contains the 512 bytes to read/write

This class was written for my Arduino 2009 weather station.
I want to log up to 10 sensors for an extended period when the Arduino is not
connected to the computer.
To save a sample every second over 12 months requires:

total bytes = 2 x 10 x 60 x 60 x 24 x 365 = 640 Mb

I use a 1.0Gb card.

The functions can both return a error value:

error = SDCARD.writeblock(sector);
error = SDCARD.readblock(sector);

If the card read/write does not work due to card failure or removal
the error will be greater than 0.
In my application this would mean the program can no longer operate.
In this case I send an error message instead of the normal samples
from the weater station.

This class does not use a FAT. Data is read/write to a sector direct.

The sd card circuit, the library and other files are at the site given below.
Download sdcardlibrary.zip

If you have a 3.3v arduino the level change circuits will not be needed.

An example program to read/write to one sector is:

/* Connect sd card as shown in circuit diagram.
   Copy SDCARD folder into arduino-0017/hardware/libraries.
   This code will write/read directly to a sector on the sd card
     sector number 0 to 1980000 for a 1.0GB card.
*/

#include <SDCARD.h>
volatile unsigned char buffer[512] ;  // this 512 bytes read from or written to sd card
unsigned long sector = 99999;  // the sector we will write or read from
int error = 0;  //the error will be 0 if the sd card works

void setup()                    // run once, when the sketch starts
{
 Serial.begin(9600);                     // initialize serial communication with computer
}

void loop()                     // run over and over again
{
   if (Serial.available() > 0)   // do nothing if have not received a byte by serial
   {
    int inByte = Serial.read();
    if (inByte == 'r')  // send a "r" to start the read / write sector process
    {
      int i = 0;  //general purpose counter
        for(i=0;i<512;i++)
      buffer[i]=199;      //fill the buffer with a number between 0 and 255
      
       error = SDCARD.writeblock(sector);  //write the buffer to this sector on the sd card
      
       if (error !=0)
     {
         Serial.print("sd card write error... code =  ");
         Serial.println(error);       
     }//end of if we have an error
       
      error = SDCARD.readblock(sector);  //read into the buffer this sector in sd card

         if (error !=0)
     {
         Serial.print("sd card read error... code =  ");
         Serial.println(error);       
     }//end of if we have an error
      else
     {
       int number =0;
       for(i=0; i<512; i++) //read 512 bytes
     {
       number = buffer[i];    //convert to integer       
       if(((i % 8) == 0) & ( i != 0))  
       Serial.println("");   //write 8 then start a new line
       Serial.print(number);
       Serial.print("  ");   //tab to next number
     }//end of read 512 bytes
     }//end of else we have no error
    } //end of received "r"
   }//end of serial available
}//end of loop

I like your approach. How much program memory does your library take up?

The read and write functions together use 1010 bytes of program memory.

This is a very small size because the sdcard library is written using the KISS principle.

However it does lead to limitations:
Cant use MMC cards.
Cant use old(ver.1 cards)- about before 2006.
Cant use HC sd cards(>2 GB)

Also you can see that it uses 512 bytes of RAM.

For the arduino 2009 this alocation of flash(32k) and ram(2k) is not a problem for my application

I added another example to my sd card class.
This is a program to check every byte on the sd card.

/* Connect sd card as shown in circuit diagram.
   Copy SDCARD folder into arduino-0017/hardware/libraries.
   This code will write/read directly to a sector on the sd card
     sector number 0 to about 1980000 for a 1.0GB card.
   This program writes 199 to every byte on the sd card.
   It will only stop when it gets read/write or memory error.
   We hope that is after the very last byte of the sd card.
*/

#include <SDCARD.h>
volatile unsigned char buffer[512] ;  // this 512 bytes read from or written to sd card
unsigned long sector = 0;  // the sector we will write or read from
int error = 0;  //the error will be 0 if the sd card works
unsigned long errormillis = 0;        // the time when error occured

void setup()                    // run once, when the sketch starts
{
 Serial.begin(9600);                     // initialize serial communication with computer
}

void loop()                     // run over and over again
{
  for (sector=0; sector<4000000 ; sector++)
  {
      int i = 0;  //general purpose counter
        for(i=0;i<512;i++)
      buffer[i]=199;      //fill the buffer with a number between 0 and 255
      
       error = SDCARD.writeblock(sector);  //write the buffer to this sector on the sd card

       if (error != 0)
         errormillis = millis();   //remember the time had error
       while (error !=0)  //we stay in this loop
     {
         Serial.print("sd card write error... code =  ");
         Serial.println(error);
         Serial.print("write error at sector=  ");
         Serial.println(sector);
         sendtime();
         delay(1000);   //send every second       
     }//end of if we have an error
    
        i = 0;  //general purpose counter
        for(i=0;i<512;i++)
      buffer[i]=0;      //clear the buffer
       
      error = SDCARD.readblock(sector);  //read into the buffer this sector in sd card

         if (error != 0)
         errormillis = millis();   //remember the time had error
         while (error !=0)   //we stay in this loop
     {
         Serial.print("sd card read error... code =  ");
         Serial.println(error);
         Serial.print("read error at sector=  ");
         Serial.println(sector);
         sendtime();
         delay(1000);   //send every second       
     }//end of if we have an error
     
      
       for(i=0; i<512; i++) //read 512 bytes
     {
      if (buffer[i] != 199)
      errormillis = millis();   //remember the time had error
      while (buffer[i] != 199)  //we stay in this loop
      {
         Serial.print("memory error at byte =  ");
         Serial.println(i);
         Serial.print("memory error at sector=  ");
         Serial.println(sector);
         sendtime();
         delay(1000);   //send every second       
      }//end of we have a memory error
     }//end of read 512 bytes
     
     if (sector % 100 ==0)
     {
       errormillis = millis();   //remember the time 
       Serial.print("we have no errors up to sector =  ");
       Serial.println(sector);
       sendtime();
     }//end of ever 10 sectors
  }//end of 4000000 sectors done
}//end of loop
 
void sendtime()
{
         Serial.print("elapsed time hours= ");
         Serial.print(errormillis/3600000);
         Serial.print("  elapsed time minutes= ");
         Serial.print((errormillis/60000) % 60);
         Serial.print("  elapsed time seconds= ");
         Serial.println((errormillis/1000) % 60);
}//end of send the time

My 1.0GB sd card had its first error at sector 1984000.
That is after 1,015,808,000 bytes which must be the highest
number of bytes available on the card.

Each read or write takes 44 msec on the Arduino 2009.
The full program run took 49 hours and 24 minutes.
This is 89 msec for each write, read and check.

If anyone has tried my sd card class, some feedback would be appreciated.
:slight_smile:

Hey,
It look very nice and well commented but I have a question :
I use a sd card shield I bought from nuelectronics.
MOSI, MISO, CLK pins are the same as your, but the chip select pin is not PB2 like in your shematics. The chip select is wired to PD5 pin, how can I fix it ?
Sorry for my bad english ... :-[

Sorry for the delay. I usually check my mail every day.

The pins are defined in the .h file.

In that file change:
#define setupDDRB DDRB |= 0x2c; //set SS as output for cs
#define selectSDCARD PORTB &= ~0x04; //set the SS to 0 to select the sd card
#define deselectSDCARD PORTB |= 0x04; //set the SS to 1 to deselect the sd card

To:

#define setupDDRB DDRB |= 0x28; //set SS as output for cs
#define setupDDRD DDRD |= 0x20; //set SS as output for cs
#define selectSDCARD PORTD &= ~0x20; //set the SS to 0 to select the sd card
#define deselectSDCARD PORTD |= 0x20; //set the SS to 1 to deselect the sd card

In the .ccp file add:

setupDDRD;

after setupDDRB; in the function
unsigned char SDCARDclass::SD_reset(void)

While you are in the .h file delete all the ; at the end of the define macro statements. Its not correct to have them see:

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1268083394

Let me know if this works for you.

I'll try it asap and tell you if it works, thanks a lot for your help :wink:

So :
I edit it tonight and it doesn't work :frowning:

I understand I should remove all the ; in the define statement so I write this :

#define setupSPI SPCR = 0x53 //Master mode, MSB first, SCK phase low, SCK idle low, clock/64
#define deselectSPI SPCR = 0x00  //deselect SPI after read write block
#define clearSPI  SPSR = 0x00 // clear SPI interupt bit
#define setupDDRB DDRB |= 0x28  //set SS as output for cs
#define setupDDRD DDRD |= 0x20  //set SS as output for cs
#define selectSDCARD PORTD &= ~0x20  //set the SS to 0 to select the sd card 
#define deselectSDCARD PORTD |= 0x20  //set the SS to 1 to deselect the sd card

I'm right ?
If I do this, I get some serious errors while compiling with arduino.

Another issue is you tell me to add in the .cpp :

setupDDRD;

before

setupDDRB;

but in the unsigned char SDCARDclass::SD_reset(void)
I only have
setupDDRB without ;

Can you explain me what I need to do ?

Thanks again. :wink:

I'd like to give your code a try - how do I wire an SD card to the arduino?
Do you have a schematic for it?

I'm sorry I should not have mentioned the ;
It has confused the issue And I will be more careful
in future. I will also check that the code I give does compile.

#define setupDDRB DDRB |= 0x2c;

The define is just a compiler directive to repace any occurance
of:

setupDDRB

With:

DDRB |= 0x2c;

As I have left off the ; throughout the .cpp file you do need
the ; in the define in the .h file.

0x2c in binary = 0010 1100
The data direction register B is being set.

The chip select pin is bit 2 ie PB2.
We dont need it any more so use:

DDRB |= 0x28;

Your circuit uses PD5 so we need to set the data direction register D:

DDRD |= 0x20;

Then use PD5 as the select anfd deselect pin.

So here is the .h file which does compile:

#ifndef SDCARD_h
#define SDCARD_h

#define setupSPI SPCR = 0x53; //Master mode, MSB first, SCK phase low, SCK idle low, clock/64
#define deselectSPI SPCR = 0x00;  //deselect SPI after read write block
#define clearSPI  SPSR = 0x00; // clear SPI interupt bit

#define setupDDRB DDRB |= 0x28;  //set SS as output for cs
#define setupDDRD DDRD |= 0x20;  //set SS as output for cs
#define selectSDCARD PORTD &= ~0x20;  //set the SS to 0 to select the sd card 
#define deselectSDCARD PORTD |= 0x20;  //set the SS to 1 to deselect the sd card

#include "WProgram.h"

class SDCARDclass
{
public:
unsigned char readblock(unsigned long Rstartblock);
unsigned char writeblock(unsigned long Wstartblock);

private:
unsigned char SD_reset(void);
unsigned char SD_sendCommand(unsigned char cmd, unsigned long arg);
unsigned char SPI_transmit(unsigned char data);

};//end of class SDCARDclass

 extern SDCARDclass SDCARD;

Here is the new unsigned char SDCARDclass::SD_reset(void) which does compile:

unsigned char SDCARDclass::SD_reset(void)
{ 
 unsigned char response;
 unsigned int retry = 0; 
 setupDDRB
 setupDDRD
 setupSPI
 clearSPI
/* After power up (including hot insertion, i.e. inserting a card when the bus is operating) the SD Card
enters the idle state. In case of SD host, CMD0 is not necessary. In case of SPI host, CMD0 shall be
the first command to send the card to SPI mode.*/ 
 do
{
   response = SD_sendCommand(0, 0); //send 'reset & go idle' command
   if(++retry>254){
   return 1;}   
} while(response != 0x01);
/*CMD1 is not recommended because it may be difficult for the host
to distinguish between MultiMediaCard and SD Memory Card.
but we will never use a MMC card so take easy route.
For the Thick (2.1 mm) SD Memory Card - CMD1 (SEND_OP_COND) is valid*/ 
retry=0;
do
{
    response = SD_sendCommand(1, 0); //activate card's initialization process
    if(++retry>254){
    return 2;}  
}while(response);
//CRC disabled in SPI mode so we will keep it that way.
retry = 0;
do
{  
  response = SD_sendCommand(16, 512); //set block size to 512
  if(++retry>10) 
  return 8;            //time out  
}while (response);
return 0;
}//end of SD_reset function

Let hope that It will run your sd card!

This is the circuit:

The sd card circuit, the library and other files are at the site given below.
Download sdcardlibrary.zip

Ok I fix the issue with the ;

Now, no errors while compiling the example sketch but nothing happen when I send a "r" ...

The program seems to get stuck at :

error = SDCARD.writeblock(sector);  //write the buffer to this sector on the sd card

I add some debug serial messages to know that ...

I try with or without sd inserted, it's the same : no answer :cry:

OK you have discovered a bug.

The code I've provided is locking up in that function.

I will debug it and post a new program at source forge.

At the moment I'm completely stuck on another project:

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1275890911

It may be months before I can get to it.

If you want to debug it yourself you need to extract
the individual functions from the class and put
it into the arduino so as to use the debug functions.

If you need it now forum has other programs which interface
to the sd card.

Sorry!

I extracted the code and it does not have a bug:

#define setupSPI SPCR = 0x53; //Master mode, MSB first, SCK phase low, SCK idle low, clock/64
#define deselectSPI SPCR = 0x00; //deselect SPI after read write block
#define clearSPI SPSR = 0x00; // clear SPI interupt bit
#define setupDDRB DDRB |= 0x2c; //set SS as output for cs
#define selectSDCARD PORTB &= ~0x04; //set the SS to 0 to select the sd card
#define deselectSDCARD PORTB |= 0x04; //set the SS to 1 to deselect the sd card

volatile unsigned char buffer[512] ; // this 512 bytes read from or written to sd card
unsigned long sector = 99999; // the sector we will write or read from
int error = 0; //the error will be 0 if the sd card works

void setup() // run once, when the sketch starts
{
Serial.begin(9600); // initialize serial communication with computer
}

void loop() // run over and over again
{
if (Serial.available() > 0) // do nothing if have not received a byte by serial
{
int inByte = Serial.read();
if (inByte == 'r') // send a "r" to start the read / write sector process
{
int i = 0; //general purpose counter
for(i=0;i<512;i++)
buffer*=199; //fill the buffer with a number between 0 and 255*

  • error = writeblock(sector); //write the buffer to this sector on the sd card*

  • if (error !=0)*

  • {*

  • Serial.print("sd card write error... code = ");*

  • Serial.println(error); *

  • }//end of if we have an error*

  • error = readblock(sector); //read into the buffer this sector in sd card*

  • if (error !=0)*

  • {*

  • Serial.print("sd card read error... code = ");*

  • Serial.println(error); *

  • }//end of if we have an error*

  • else*

  • {*

  • int number =0;*

  • for(i=0; i<512; i++) //read 512 bytes*

  • {*
    _ number = buffer*; //convert to integer _
    _
    if(((i % 8) == 0) & ( i != 0)) _
    _
    Serial.println(""); //write 8 then start a new line*_
    * Serial.print(number);*
    * Serial.print(" "); //tab to next number*
    * }//end of read 512 bytes*
    * }//end of else we have no error*
    * } //end of received "r"*
    * }//end of serial available*

}//end of loop
unsigned char SD_reset(void)
*{ *
* unsigned char response;*
* unsigned int retry = 0;*
* setupDDRB *
* setupSPI*
* clearSPI*
/* After power up (including hot insertion, i.e. inserting a card when the bus is operating) the SD Card
enters the idle state. In case of SD host, CMD0 is not necessary. In case of SPI host, CMD0 shall be
the first command to send the card to SPI mode.*/
* do*
{
* response = SD_sendCommand(0, 0); //send 'reset & go idle' command*
* if(++retry>254){*
* return 1;} *
} while(response != 0x01);
/*CMD1 is not recommended because it may be difficult for the host
to distinguish between MultiMediaCard and SD Memory Card.
but we will never use a MMC card so take easy route.
For the Thick (2.1 mm) SD Memory Card - CMD1 (SEND_OP_COND) is valid*/
retry=0;
do
{
* response = SD_sendCommand(1, 0); //activate card's initialization process*
* if(++retry>254){*
* return 2;} *
}while(response);
//CRC disabled in SPI mode so we will keep it that way.
retry = 0;
do
*{ *
* response = SD_sendCommand(16, 512); //set block size to 512*
* if(++retry>10)*
* return 8; //time out *
}while (response);
return 0;
}//end of SD_reset function
unsigned char readblock(unsigned long Rstartblock)
{
unsigned char response, saveDDRB;
unsigned int i, retry=0;
saveDDRB = DDRB; // save current DDRB
response = SD_reset(); // reset the sd card
if (response != 0 )
{
DDRB = saveDDRB; //restore the DDRB
deselectSPI
return response; //board wont reset return error
}
do
{
response = SD_sendCommand(17, Rstartblock<<9); //read a Block command
//block address converted to starting address of 512 byte Block
* if(++retry>10)*
{
* DDRB = saveDDRB; //restore the DDRB*
* deselectSPI *
* return 3;*
}
}while (response);
selectSDCARD //set the SS to 0 to select the sd card
retry = 0;
/*Read and write commands have data transfers associated with them. Data is being transmitted or
received via data tokens. All data bytes are transmitted MSB first.
First byte: Start Block
1 1 1 1 1 1 1 0
Bytes 2-513 (depends on the data block length): User data
Last two bytes: 16 bit CRC.*/
while(SPI_transmit(0xff) != 0xfe) //wait for start block token 0xfe
*{ *
* if(retry++ > 600)*
* {*
* deselectSPI *
* DDRB = saveDDRB; //restore the DDRB*
* deselectSPI *
* return 4; //return if time-out*
* }//end time out for token*
}//end receive token
for(i=0; i<512; i++) //read 512 bytes
buffer* = SPI_transmit(0xff);
SPI_transmit(0xff); //receive incoming CRC (16-bit), CRC is ignored here
SPI_transmit(0xff);
SPI_transmit(0xff); //extra 8 clock pulses
deselectSDCARD //set the SS to 1 to deselect the sd card
DDRB = saveDDRB; //restore the DDRB
deselectSPI
return 0;
_}//end of read block*
[/quote]
The code is in two parts The next is:_

unsigned char SD_sendCommand(unsigned char cmd, unsigned long arg)
{
/The response token is sent by the card after every command with the exception of SEND_STATUS
commands. It is one byte long, and the MSB is always set to zero. The other bits are error indications,
an error being signaled by a 1. The structure of the R1 format is given in Figure 7-9. The meaning of the
flags is defined as following:
In idle state: The card is in idle state and running the initializing process.
Erase reset: An erase sequence was cleared before executing because an out of erase sequence
command was received.
Illegal command: An illegal command code was detected.
Communication CRC error: The CRC check of the last command failed.
Erase sequence error: An error in the sequence of erase commands occurred.
Address error: A misaligned address that did not match the block length was used in the command.
Parameter error: The command\u2019s argument (e.g. address, block length) was outside the allowed
/
unsigned char response, retry=0, i;
for(i=0; i<10; i++) //presend calls
SPI_transmit(0xff); //need wakeup calls
selectSDCARD //set the SS to 0 to select the sd card
SPI_transmit(0xff); //wakeup call
SPI_transmit(cmd | 0x40); //send command, first two bits always '01'
SPI_transmit(arg>>24);
SPI_transmit(arg>>16);
SPI_transmit(arg>>8);
SPI_transmit(arg);
SPI_transmit(0x95); //crc does not matter except for cmd0

while((response = SPI_transmit(0xff)) == 0xff) //wait response
if(retry++ > 254) break; //time out error

deselectSDCARD //set the SS to 1 to deselect the sd card
return response; //return state
}// end of read block

unsigned char writeblock(unsigned long Wstartblock)
{
unsigned char response, saveDDRB;
unsigned int i, retry=0;
saveDDRB = DDRB; // save current DDRB
response = SD_reset(); // reset the sd card
if (response != 0 )
{
DDRB = saveDDRB; //restore the DDRB
deselectSPI
return response; //board wont reset return error
}
do
{
response = SD_sendCommand(24, Wstartblock<<9); //write a Block command
if(++retry>10)
{
DDRB = saveDDRB; //restore the DDRB
deselectSPI
return 5; //time out
}
}while (response);
selectSDCARD //set the SS to 0 to select the sd card
SPI_transmit(0xfe); //Send start block token 0xfe (0x11111110)
for(i=0; i<512; i++) //send 512 bytes data
SPI_transmit(buffer*);*
SPI_transmit(0xff); //transmit dummy CRC (16-bit), CRC is ignored here
SPI_transmit(0xff);
response = SPI_transmit(0xff);
/*Every data block written to the card will be acknowledged by a data response token. It is one byte long
and has the following format:
x x x 0 Status 1
The meaning of the status bits is defined as follows:
010 - Data accepted.
101 - Data rejected due to a CRC error.
110 - Data Rejected due to a Write Error*/
if( (response & 0x1f) != 0x05)
*{ *

  • deselectSDCARD *
  • DDRB = saveDDRB; //restore the DDRB*
  • deselectSPI*
  • return 6;*
    }
    /*Every data block has a prefix of \u2019Start Block\u2019 token (one byte).
    After a data block has been received, the card will respond with a data-response token. If the data block
    has been received without errors, it will be programmed. As long as the card is busy programming, a
    continuous stream of busy tokens will be sent to the host (effectively holding the DataOut line low).*/
    while(!SPI_transmit(0xff)) //wait for SD card to complete writing and get idle
    if(retry++ > 60000)
    {
    DDRB = saveDDRB; //restore the DDRB
    deselectSPI
    deselectSDCARD
    return 7;
    }
    deselectSDCARD
    DDRB = saveDDRB; //restore the DDRB
    deselectSPI
    return 0;
    }//end of write a block of data
    unsigned char SPI_transmit(unsigned char data)
    {
    // Start transmission
    SPDR = data;
    // Wait for transmission complete
    while(!(SPSR & (1<<SPIF)));
    data = SPDR;
    return(data);
    }//end of SPI transmit
    [/quote]
    Something has changed in the way arduino works with classes.
    I wrote the class in Arduino 0016.
    It all worked OK then.
    I will try to find out how to get my code back into a workable class and then post the new class at source forge.

I've got no idea why the class did not work.

I deleted SDCARD from:

C:\arduino-0017\hardware\libraries

and replaced it with a new copy and now it works!

I fixed up the examples so that in the IDE under:

file-examples-SDCARD there are the two examples:

checksdcard
readwritesector

I have twice downloaded SDCARD.zip from:

and unzipped it into:

C:\arduino-0017\hardware\libraries

It seems to work perfectly. I hope it continues to do so!