SD/MMC From the ground up

I did some quick testing using the sample DevicePrint sketch, and the fastest I can get any of my cards to write is about 500ms for 512bytes.

What is the bottleneck? How fast do you have the clock set? 4mhz? As I understand it, it has to be set at 4mhz during initialization, but can be bumped up to 40mhz(?) afterwards.

I took a look at the code. You have SPCR = B01010011, and in the comments I see "clock = f/4". Maybe I misunderstand, but isn't that clock/128? Shouldn't SPR1 and SPR0 be set to 0 to get clock/4?

I changed both to 0, and enabled SPI2X in SPSR. On my best card, the writes are now 80ms per 512byte block!

Is this safe? Is there any error checking?

You're right, my bad! I'll amend the code asap.

You should be able to drive the card as fast as Arduino can go without any problems.

This is a great library-- and it worked on my micro-SD card (Transcend 1G) right out of the chute.

One little problem I ran into is that if I have the SD card and an SCP1000 pressure sensor on the same SPI bus, I can't get them both to work.

I can get them to work individually in the same hardware configuration, and I can get the pressure sensor working OR the SD card working. (just by commenting out parts of the code) But if I initalize the SD card, the pressure sensor just returns 0x00's.

I know there's some funky SPI code to interface to a SD card-- is the card perhaps responding even if the CS line is high?

Also, on the library, mmc.h should have the #defines for the pins, not mmc.c -- since CS doesn't have to be Arduino 10 if you have multiple devices =)

Thanks again!

Excellent!

As for the multiple SPI slave situation - indeed, this is something that I've thought about but, like most things, I don't do anything about until it bites me :smiley:

So every device in the SPI chain needs its own /CS line. The only time you need to do anything funky with the one allocated to the MMC card is during initialisation. In order to 'wake it up' and put it in SPI mode you need to send it at least 72 clocks with CS high. I don't think that would cause too much of a problem in a multiple device chain as long as your other devices also have their select lines de-asserted.

If however the MMC will respond even with /CS high.. Oh oh. I've never investigated this. If this is the case then it's time for SoftSPI! This is something I've dabbled with when I was debugging something or other. It's a software implementation of SPI mode 0. You can allocate whichewver pins you like so there's no need to share any lines. I hope it doesn't come to this though... It's comparatively slow.

In practice it doesn't matter too much where the CS pin is defined if you use the MMC code as a library. It would have to be recompiled for every sketch that used it. A way around this would be to allow the CS pin be specified in a variable. It would need this facility in the libraries for each device though... Or not use a true library, instead copying the code to each sketch folder that required it.

C

Very good point! I can just re#define in my program and go to town.

I'd be interested to know if the card will respond with CS high-- the 72 clocks shouldn't do anything to the SCP1000, since it's CS is also high at the time. shrug might have something to do with my particular implementation.

Update: interestingly, it's the code and not the SD card-- I removed the card, but still had the software running. I had to completely remove power and replace power to get the SCP1000 to work with the new code. Bizzare...

Update: I downloaded the latest files and it works now ...

When I try to compile I get an error ... The last time I programmed in C was about 10 years ago, so forgive me if I should know this. I did try and figure it out myself, just not smart enough.

o: In function main': undefined reference to microfat2::walkDirectory(bool ()(directory_entry_t, unsigned int, void*), void*)


#include <WProgram.h>
#include <avr/pgmspace.h>
#include <microfat2.h>
#include <mmc.h>

byte sector_buffer[512];

char sprint_buffer[40];

// BEWARE - don't print strings longer than 39 characters!
// If you can't help it, adjust buffer size above.
//
void pprint(const char* s)
{
strcpy_P(sprint_buffer, (PGM_P)s);
Serial.print(sprint_buffer);
}

void error(const char* s)
{
pprint(PSTR("Error: "));
pprint(s);
pprint(PSTR(""));
for( /* ever */ ; ; )
{
digitalWrite(13, (millis() / 250) & 1);
}
}

bool showDirectory_walkerfn(directory_entry_t* directory_entry_data, unsigned index, void* user_data)
{
int* count = (int*)user_data;

Serial.print(index, DEC);
Serial.print(' ');

// Terminate the filename string.
// This is deliberately corrupting the buffer data, but that's ok.
directory_entry_data->filespec[11] = 0;

Serial.println(directory_entry_data->filespec);

// Increase 'seen file' count
*count = (*count)+1;

// don't stop
return false;
}

void showDirectory(void)
{
int count = 0;

pprint(PSTR("Directory of files on card:\n\n"));

microfat2::walkDirectory(showDirectory_walkerfn, &count);

pprint(PSTR("\n"));
Serial.print(count, DEC);
pprint(PSTR(" files found.\n\n"));
}

void setup(void)
{
Serial.begin(115200);

pprint(PSTR("uFat2 demonstration\n"));

if (mmc::initialize() != RES_OK)
{
error(PSTR("mmc init failed.\n"));
}

// Pass in the sector-sized buffer we'll be using ourselves later.
// uFat doesn't own it, it just needs to use it temporarily.
// We also pass in the address of a function that is used to read
// sectors from our device.
//
if (!microfat2::initialize(sector_buffer, &mmc::readSectors))
{
error(PSTR("uFat init failed.\n"));
}

showDirectory();

unsigned long sector;
unsigned long byteSize;

if (microfat2::locateFileStart(PSTR("DATA BIN"), sector, byteSize))
{
if (byteSize >= 512)
{
if (RES_OK == mmc::readSectors(sector_buffer, sector, 1))
{
for (int i = 0; i < 512; ++i)
{
sector_buffer = sector_buffer + 1;
* }*
* if (RES_OK == mmc::writeSectors(sector_buffer, sector, 1))
_
{_
_
pprint(PSTR("Written to data.bin OK!"));_
_
}_
_
else*_
* {*
* pprint(PSTR("Failed to write updated data."));*
* }*
* }*
* else*
* {*
* pprint(PSTR("Failed to read data.bin."));*
* }*
* }*
* else*
* {*
* error(PSTR("Found data.bin, but it's too small."));*
* }*
* }*
* else*
* {*
* pprint(PSTR("data.bin not present on card."));*
* }*
}
void loop(void)
{
* digitalWrite(13, (millis() / 1000) & 1);*
}

Thank you SirMorris. Your code worked first time. However I ran into trouble when I tried to also run it at the same time as having a DS1307 I2c real time clock header file included. This prevented even the PrintWelcome function executing properly. Do you have any suggestions where to start debugging?

I'd be happy to make any headers/source code available once I work out how to.

Thanks,
Col.

@koetting:

Have you installed the mmc & microfat2 libraries properly? It sounds like the compiler isn't finding the code. The folders need to be in arduino\hardware\libraries.

@New Colin:

Problems like this are usually related to memory depletion. Have a look and see if any memory allocations or large static buffers are being declared.

@New Colin:

I have a sketch at http://docs.google.com/Doc?id=dqhc6fg_0gmk96kdd which uses the DS1307 with sirmorris's SD card software successfully. There's a LOT of code in there which will be totally irrelevant to you but I'm sure you can isolate the bits you need. If not, PM me & I'll cut the code down for you.

JB

Hi all,
and thank you for this really good documentation! Because of this, I'm convinced to try arduino for a study project I'm working on!

However, one question remains... and I hope it's not too stupid :wink: .. I tried to figure this out for hours now, but seemingly cannot decide it on my own.

The issue is this: we also depend on having fast PWM signalling - at a time-resolution of at least 64 micro-seconds / i.e. a minimum frequency of 15.625 kHz .

From the forum I have learned so far that PWM is possible on pins 5,6 (controlled by timer0) and 9,10 (timer 1), and 3,11 (timer2).
Since timer0 is also used for other stuff than pwm, I don't want to change it to have higher frequency... pins 9,10,11 are used by SPI anyway, which leaves us only with pin 3.

My question: Does somebody know whether setting timer2 to such a high frequency has any side-effects with the SPI-protocol? Maybe somebody might even try this shortly with a running setup? This would be amazing and probably save me days of studying Atmel's datasheet :cry:

Thank you already in advance!

PS: Here's a (hopefully working) post indicating how to change the PWM frequency of timer2: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1234764073#12 or http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1235060559/12#4

That is an excellent summary. If you put that onto a single page somewhere please
post the link.

(* jcl *)

@johanzebin: I don't know about this. The information would be great to add to the article though, so please let us kn ow how you get on. I think your best approach is to stick a frequency meter or oscilloscope on the PWM pin and look at what happens when you run the SPI code. You could write a little loop which writes or reads from the card and display the timings with and without tweaking the PWM rate. Experiment, have fun :slight_smile:

@jluciani: thanks! I looked into putting this up in the playground, but I don't have the time to learn wikispeak right now :frowning: Any volunteers?? :wink:

It seems to me that one ought to be able to write a MUCH smaller implementation for FAT16 on SD/MMC if all you want is read-only, AND you're willing to take into account the fact that accessing the flash card is relatively fast (compared to a floppy, say) so you won't need to cache quite as much info.

Does anyone know of such an implementation?

and in very simple mode, here....
I would like to read text [no writing] from a txt file on an SDcard, and send it to an LCD using serial.print... I have looked high and low through all the examples, but being um noob, I am having a heck of time figuring out how to do that. Could someone hold my hand, e.g. a code example? I use a 4x20 display, and of course there are control characters. I imagine I have to format the text for half the screen then the other half, each new section on a new line in the text file, padding each line to proper length [since I can't figure out how to parse/split text]... I am also having some um conceptual problems on how to go from bytes -> ascii, but I guess that will just be obvious in the code example, for which I certainly and hopefully do thank you in advance! I hope.

thanks,
David

I haven't got the time to play ATM, but you need to do the inverse of devicePrint.

initialise()
{
  bytesInBuffer = 0;
}

char getCharFromBuffer()
{
  if (bytesInBuffer == 0)
  {
     readSectors(sector, buffer, 1);
     ++sector;
     bytesInBuffer = 512;
  }

  --bytesInBuffer;
  return buffer[bytesInBuffer];
}

A byte is an 8-bit wide data type. So is a char. The distinction is usually whether the value is treated as signed or not. The ansi character set only uses 127 values because logically it's unsigned.

Do you really need SD for characters for an LCD display? Just how much text are you wanting to display??? Wouldn't it be better to store the strings in PROGMEM?

C

Am I getting this right, if I set up an arduino board, using the SPI pins connected to an SD card socket, with proper resistances;etc, I can log(save) data on an SD card? Then after saving data on the card, I can remove the card from the card socket, bring the card to a PC, and enter the data directly to the PC via one of the computer's ports, such as one of the USB ports? Can I do this by using the uFAT library?

Or do I have the wrong idea?

I've already downloaded uFAT2, uFAT EXAMPLE, DEVICE PRINT, DEVICE PRINT LIBRARY, DEVICE PRINT DEMO. Are the uFAT2 & DEVICE PRINT libraries for different purposes?

I've uploaded uFAT example and DevicePrint demo to my arduino board and, as soon as I connect the card socket to the SPI pins I'm ready to give it a try. ** Is there anything else I should do?** Is there anything that should be done with the SD card first? Does it need to be pre-programmed for FAT operation?

Thanks in advance
PWO

Hi there,

yes - you're getting it right.

uFat allows you to find the start sector of a file on a card. Deviceprint allows you to write strings to sectors belonging to a file the card.

All you need to do to read and write to the card is make sure it's formatted as FAT16 and copy a big old file on there in order to write into it.

How do I format the card for Fat16 ?
And copying a big old file, from where? Ya kind of lost me there. :-[
Can I store numerical data on the card?
Sorry for asking all the questions. It takes a while for this stuff to sink through my old skull.

Thanks for the help

If you're using Linux you're on your own I'm afraid - unless there's anyone reading that would care to share the power??!

On Windows it's easy - open MyComputer and right-click the drive allocated to the card. Select Format from the context menu. For the filesystem select FAT. Not FAT32, plain ol' FAT. Click start and a few seconds later you're done.

Now imagine how much data you're going to write. Find or create a 'donor' file on your harddrive. The contents are irrelevant - you'll be overwriting them. It's the size of the donor file which is important. Too small and you'll lose data 'off the end' when you read it back in the desktop computer. Once the donor file is ready, make sure it's named in 8.3 fashion and copy it to the card. Put the card to the arduino.

You can store whatever you like on the card. I recommend strings because they're the most portable format. Use DevicePrint and putting strings in the file is as easy as using the Serial.print functions.

Remember though: Every time the program runs it will start writing at the start of the file. If you don't want this to happen, then you'll have to think awhile and imagine a scheme to help!

:wink: