Go Down

Topic: Having trouble interfacing with SD card. One works, another doesn't? (Read 637 times) previous topic - next topic

scswift

Hey guys,

I recently discovered that while my code works fine with the generic SD card I've been testing with, my Sandisk cards don't work at all. 

I suspect there may be a bug in my code, but so far I've failed to track it down.

I've already checked the obvious potential problems.  The cards are not high capacity cards.  They're formatted the same using the same official SD card formatting utility.  And all the files copied to them are intact.

I have also tried disabling partial block reads, and slowing down the SPI bus.  Oh, and I'm not using voltage dividers to talk to the cards, they're connected with an actual buffer and running a 3.3v. 

It may be worth noting that I am using the USART in MSPI mode to talk to the SD card.  This works fine with the generic card, and I've been happily playing wav files off the card for weeks.  But one thing that has been bugging me is that I cannot seem to write to the car using buffered output.  That fails every time.  Checking each time to make sure the byte is transmitted before I continue works fine though.

Here is the transmit code I'm using:

Code: [Select]

void printbyte(uint8_t b) {
char t[4]; // 3 chars + null
sprintf(t, "%u", b);
Serial1.println(t);
}

//------------------------------------------------------------------------------
// inline SPI functions

/** Send a byte to the card */

//inline void spiSend(uint8_t b) {SPDR = b; while(!(SPSR & (1 << SPIF)));} // Original code.

// while(!(SPSR & (1 << SPIF))); loops until the SPIF bit is set.  Or rather, loops while it is clear.
     // Alternatively, avr/io.h could be included, and the following done: SPDR=(dat); loop_until_bit_is_set(SPSR,SPIF)

// Transmit byte via USART0 configured in MSPI mode:

inline void spiSend(uint8_t b) {UCSR0A = (1 << TXC0); UDR0 = b; while(!(UCSR0A & (1 << TXC0))); Serial1.print("SEND: "); printbyte(b);} // Last working version
//inline void spiSend(uint8_t b) {while(!(UCSR0A & (1 << UDRE0))); UDR0 = b;} // This seems to be the standard method.  Does not wait for byte to finish transmitting to exit.


/** Receive a byte from the card */

// Receive a byte using SPI:
//inline uint8_t spiRec(void) {spiSend(0XFF); return SPDR;} // Original code.

// Is it actually necessary to send 0xFF? Or is it important we just transmit SOMETHING?

// Recieve byte via USART0 configured in MSPI mode:
inline uint8_t spiRec(void) {spiSend(0XFF); while(!(UCSR0A & (1 << RXC0))); Serial1.print("RECV: "); printbyte(UDR0); return UDR0;} // Master must transmit a byte to receive one.




I don't usually have the code in there to print the debug data, so that's not the issue.


Here are the results I get testing with three different cards:

Code: [Select]

BEGIN - Goodcard

type any character to start

init time: 913

Card type: SD2

Manufacturer ID: 0
OEM ID:   
Product: N/A 
Version: 1.0
Serial number: 3664195770
Manufacturing date: 7/2012

card size: 1968128 (512 byte blocks)
partion,boot,type,start,length
1,0,6,253,1967875
2,0,0,0,0
3,0,0,0,0
4,0,0,0,0
Read test starting. Please Wait.

Read 20000 blocks
mills: 36949

Done

type any character to start





BADCARD1:

type any character to start

init time: 468

Card type: SD2

Manufacturer ID: 3
OEM ID: SD
Product: SU02G
Version: 8.0
Serial number: 2897907216
Manufacturing date: 9/2008

cardSize failedSD error
errorCode: 8
errorData: 0



BADCARD2:

type any character to start

init time: 390

Card type: SD2

Manufacturer ID: 3
OEM ID: SD
Product: SU01G
Version: 8.0
Serial number: 1366936321
Manufacturing date: 10/2007

cardSize failedSD error
errorCode: 8
errorData: 0



This was before I put the code in to print out the individual bytes sent.  After I did that, this is what I get with one of the non working cards:

Code: [Select]

SEND: 255
SEND: 255
SEND: 255
SEND: 255
SEND: 255
SEND: 255
SEND: 255
SEND: 255
SEND: 255
SEND: 255
SEND: 255
RECV: 0
SEND: 255
RECV: 127
SEND: 255
RECV: 255

... (hundreds of 255's sent and received here) 

SEND: 64
SEND: 0
SEND: 0
SEND: 0
SEND: 0
SEND: 149
SEND: 255
RECV: 255
SEND: 255
RECV: 255
SEND: 255
RECV: 255
SEND: 255
RECV: 255
SEND: 72
SEND: 0
SEND: 0
SEND: 1
SEND: 170
SEND: 135
SEND: 255
RECV: 255
SEND: 255
RECV: 255
SEND: 255
RECV: 0
SEND: 255
RECV: 0
SEND: 255
RECV: 1
SEND: 255
RECV: 170

card.init failed
SD error
errorCode: 9
errorData: 1


The two error codes I've gotten are the following:

Code: [Select]

/** read CID or CSD failed */
#define SD_CARD_ERROR_READ_REG 0X8

/** bad response echo from CMD8 */
#define SD_CARD_ERROR_CMD8_ECHO 0X09


I'm guessing the difference between the error I got before the debug and after it is there's a millisecond timer somewhere timing out because of the debug data I'm sending. 

Also, I should note that when running my main program rather than this test app, the cards seem to initialize fine, and the FAT code successfully opens the root directory.  But loading and playing the wave files fails.

I really think the error might be with how I'm trying to use the USART in MSPI mode because those buffered writes should work, but they don't.  If someone could confirm whether that bit of code is right or not then that would be a start at least.  It seems to be the same as the examples I've seen.

Here's the full source of the modified WaveHC lib I'm using to interface with the card.  It includes in the examples folder the test program which generated the above outputs:
http://shawnswift.com/arduino/WaveHC.zip

The code in question is in the sdreader.cpp file.  The wave playback code isn't broken or used in the test.

scswift

I've found the problem!

The answer came when I noticed this bit in the datasheet un the USART MSPI section:

Quote

Note: To keep the input buffer in sync with the number of data bytes transmitted, the UDRn register must
be read once for each byte transmitted.


I was not doing that.  I was never doing that.  Originally, unable to get buffered reads working, I'd resorted to a hack, treating the USART as unbuffered and making sure each transmit completed before exiting the send function.  I believe a side effect of this was bytes were being lost when reading them, because the read function first called the send function, and I think there may have been some bug resulting from that. 

The weird thing is that my generic brand SD card tolerated these errors somehow, and my code worked with it.  But it would not work with SanDisk cards.

Anyway, it works perfectly now.  You can see the diagnostic here of the brand name card which was not working previously:

Code: [Select]

init time: 388

Card type: SD2

Manufacturer ID: 3
OEM ID: SD
Product: SU01G
Version: 8.0
Serial number: 1366936321
Manufacturing date: 10/2007

card size: 1984000 (512 byte blocks)
partion,boot,type,start,length
1,0,6,249,1983751
2,0,0,0,0
3,0,0,0,0
4,0,0,0,0
Read test starting. Please Wait.

Read 20000 blocks
mills: 19371

Done



And here is the diagnostic of the generic card.  The read speeds have been increased significantly from what they were.  Perhaps this is indicative of how many bytes were being read multiple times or lost as a result of the buggy hack version of the send function:

Code: [Select]

init time: 20

Card type: SD2

Manufacturer ID: 0
OEM ID:   
Product: N/A 
Version: 1.0
Serial number: 3664195770
Manufacturing date: 7/2012

card size: 1968128 (512 byte blocks)
partion,boot,type,start,length
1,0,6,253,1967875
2,0,0,0,0
3,0,0,0,0
4,0,0,0,0
Read test starting. Please Wait.

Read 20000 blocks
mills: 22234



And finally, here is the code.  I've included the original SPI functions, and the USART ones I wrote that now work properly with buffering:

Code: [Select]

inline void spiSend(uint8_t b) {SPDR = b; while(!(SPSR & (1 << SPIF)));} // Original SPI code.
inline void spiSend(uint8_t b) {while(!(UCSR0A & (1 << UDRE0))); UDR0 = b; b = UDR0; } // New buffered USART code.

inline uint8_t spiRec(void) {spiSend(0XFF); return SPDR;} // Original SPI code.
inline uint8_t spiRec(void) {while(!(UCSR0A & (1 << UDRE0))); UDR0 = 0xFF; while(!(UCSR0A & (1 << RXC0))); return UDR0;} // New buffered USART code


You'll note I haven't used the spiSend() function in the spiRec() function for the USART, as is done with the SPI code.  That's because the send function reads a byte at the end, and that would be lost if I didn't read it.

It may be there is still a bug in here, or that it can be optimized further. 

For example, if you look at spiRec() you'll note I wait for a receive flag to be set before I return the byte.  But I don't do that in the send function.  It may be that I need to do that in the send function as well before I try to read a byte from the buffer.  I'm not sure.  The datasheet does not seem to indicate I need to.  But if I send a byte, and it's placed in the buffer, and then I immediately read one, where will that read byte come from?  It can't be in the buffer if the sent byte hasn't actually transmitted yet because I only get a byte back as I am transmitting one.  So I'm not sure that this data transmission is entirely in sync yet. 

Also, rather than just setting b = UDR0, I could return UDR0 from the send function.  Then I could just define spiRec() to be spiSend(0xFF) and be done with it.  But before I do that I want to be sure about whether I need to check that receive flag.

Still looking at the documentation to try to figure out what I should do.  Page 202 here:
http://www.atmel.com/Images/doc8059.pdf

And page 5 here:
http://www.atmel.com/Images/doc2577.pdf

Go Up