Yahoo Groups archive

Lpc2000

Index last updated: 2026-04-28 23:31 UTC

Message

Re: [lpc2000] SD/SPI driver code

2006-04-12 by Clyde Stubbs

Attached is some code for driving an SD card via an SPI port. This
was written specifically for a SiLabs C8051F320 but should adapt
with little trouble to any other SPI interface.

The code has been widely tested on a variety of different brand SD
cards - some have little quirks.

Clyde


On Wed, Apr 12, 2006 at 07:16:32PM -0000, darcy_will wrote:
> Thanks to everyone that has offered their help with this topic!  I 
> honestly didn't expect such a fantastic response in such a short 
> period of time.
> 
> Cheers all
> D.
> 
> 
> 
> 
> 
>  
> Yahoo! Groups Links
> 
> 
> 
>  
> 

-- 
Clyde Stubbs                     |            HI-TECH Software
Email: clyde@...          |          Phone            Fax
WWW:   http://www.htsoft.com/    | USA: (408) 490 2885  (408) 490 2885
PGP:   finger clyde@...   | AUS: +61 7 3722 7777 +61 7 3722 7778
---------------------------------------------------------------------------
HI-TECH C: compiling the real world.

  ----------

/* SD card interface. Copyright (C) 2005 HI-TECH Software Pty. Ltd. */

#include	<8051.h>

#define	FALSE	0
#define	TRUE	1

// Error codes for the SD

enum {
	RESET_FAIL=1, READ_FAIL, CSD_FAIL, WRITE_FAIL, STATUS_FAIL, ACMD_FAIL, ERASE_FAIL
};


// card present states

enum {
	CARD_MISSING = 0,
	CARD_PRESENT,
	CARD_READY,
	CARD_FAIL,
	CARD_EJECTED
}	card_state;

static unsigned char 		*	xptr;		// data pointer
static unsigned char 			error_code;	// code d'error
static unsigned long			card_size;	// card size in blocks
static unsigned long			part_offset;// offset to start of first partition
static unsigned char			file_format;	// partition layout

#define	V33			(1<< 6)		// bit identifying 3.3V
#define	SD_BLKLEN	512			// block size
#define	CSD_SIZE	16			// CSD size in bytes
#define	CID_SIZE	16			// CID size in bytes


unsigned char blkbuf[SD_BLKLEN];



#define	ACMD	55			// command following is application specific
#define	CARD_SELECT		(P1_BITS.B3)		// this bit should drive the card select
#define	SPIBUSY	0x80	// spi busy bit
#define	CRC				0x95		// all purpose CRC

static bit				fat32;			// set if we have a fat32 card
static bit				fat_check;		// we need to check the fat again
static unsigned char	timeout;		// timeout value in ticks

// this must be implemented in an timer interrupt. If timeout is non-zero, the
// interrupt routine should decrement it once per timer tick.
// adjust the calculation in SET_TIMEOUT so that the argument represents milliseconds.

#define	SET_TIMEOUT(x)	(timeout = (((x)*128UL))/1000+1)
#define	TIMEDOUT()		(timeout == 0)

// fetch a long value from the buffer

static unsigned long
get_long(unsigned char * ptr)
{
	unsigned long	value;

	value = *ptr++;
	value += *ptr++ << 8;
	value += (unsigned long)*ptr++ << 16;
	value += (unsigned long)*ptr++ << 24;
	return value;
}

static void
SPI_init(void)
{
	// setup the SPI for the SD card. 
	// Data latched on rising edge, input valid in middle of clock,
	// idle state is high.
	// initial clock for the SD card must be very slow - 200kHz
	// due to the open-drain connection

	SPI0CKR = 0x30;				// start SPI real slow
	SPI0CFG = 0B01000000;
	SPI0CN = 1;
}

// Send one byte to the SPI

static void
SPI_send(unsigned char databyte)
{
	while(TXBMT == 0)
		continue;
	SPI0DAT = databyte;
}

// read one byte from the SPI

static unsigned char
SPI_read(void)
{
	SPIF = 0;
	SPI0DAT = 0xFF;		// clock out 1s while clocking in data
	while(!SPIF)
		continue;
	return SPI0DAT;
}

// Send idle (0xFF) a specified number of bytes

static void
SD_idle(unsigned char cnt)
{
	while(cnt--)
		SPI_send(0xFF);
}

// Send a command and wait for a response

static unsigned char
SD_send(unsigned char cmd, unsigned long arg)
{
	unsigned char	tok;

	CARD_SELECT = 0;			// select the card
	SD_idle(3);				// lead-in
	SPI_send(cmd|0x40);			// send the command
	SPI_send(arg >> 24);
	SPI_send(arg >> 16);
	SPI_send(arg >> 8);
	SPI_send(arg);
	SPI_send(CRC);
	cmd = 16;
	do
		tok = SPI_read();	// wait for response - up to 16 times
	while(tok & 0x80 && --cmd != 0);
	return tok;
}

static bit
SD_reset(void)
{
	SPI_init();
	SD_idle(100);
	CARD_SELECT = 0;		// select the card
	SD_idle(100);			// send numerous clocks to initialize
	CARD_SELECT = 1;
	SD_idle(100);
	SD_send(0, 0);			// send initialization command
	SET_TIMEOUT(1000);		// initialization should take no more than 1 second
	do {
		if(TIMEDOUT()) {
			error_code = RESET_FAIL;
			return FALSE;
		}
		SD_send(55, 0);			// app-specific command follows
	} while(SD_send(41, 0) & 1);
	SPI0CKR = 0;				// set SPI to maximum speed - 12 MHz
	return TRUE;
}


static unsigned char
SD_cmd(unsigned char cmd, unsigned long arg)
{
	if(SD_send(cmd, arg) == 0)
		return 0;
	// card did not respond in a reasonable time - might be a PNY!
	// hard reset the card and retry the command, but only once.
	_delay(24000000/4);		// wait 240 ms to ensure buffers flushed
	SD_reset();
	return SD_send(cmd, arg);
}

// read the status register

static unsigned
SD_readstatus(void)
{
	unsigned char	i;

	i = SD_cmd(13, 0);
	if(i & 0x80) {
		error_code = STATUS_FAIL;
		return FALSE;
	}
	return (i << 8) + SPI_read();
}

// read the CSD - 16 bytes 

static bit
SD_readcsd(void)
{
	unsigned char	token;
	unsigned char	i;
	unsigned int	len;

	if(SD_cmd(9, 0)) {
		error_code = CSD_FAIL;
		return FALSE;
	}
	SET_TIMEOUT(200);
	do
		token = SPI_read();
	while(token != 0xFE && !TIMEDOUT());
	if(token & 1) {
		error_code = CSD_FAIL;
		return FALSE;
	}
	i = 0;
	do
		blkbuf[i] = SPI_read();
	while(++i != CSD_SIZE);
	SPI_read();
	SPI_read();
	i = (blkbuf[15-6] & 3) << 1;		// bits 48-49
	i += blkbuf[15-5] >> 7;			// bit 47
	i += 2;
	len = blkbuf[15-8] << 2;
	len += blkbuf[15-7] >> 6;
	len += (blkbuf[15-9] & 3) << 10;
	len++;
	card_size = (1L << i) * len - 1;	// set to number of last block
	file_format = (blkbuf[15-1] >> 2) & 3;
	return TRUE;
}

static void
SD_stopwrite(void)
{
	SPI_send(0xFD);
	SET_TIMEOUT(500);
	SPI_send(0xFF);
	while(!TIMEDOUT())
		if(SPI_read() == 0xFF)
			break;
}

// commence a read operation

static bit
SD_startread(unsigned long blockno)
{
	if(SD_cmd(17, blockno << 9)) {
		error_code = READ_FAIL;
		return FALSE;
	}
	return TRUE;
}

static bit
SD_waitread(void)
{
	unsigned char	token;
	unsigned int	i;

	SET_TIMEOUT(200);
	do
		token = SPI_read();
	while(token != 0xFE && !TIMEDOUT());
	if(token & 1) {
		error_code = READ_FAIL;
		return FALSE;
	}
	return TRUE;
}

// commence a write operation

static bit
SD_startwrite(unsigned long blockno)
{
	unsigned char	token;
	unsigned int	i;

	if(SD_cmd(24, blockno << 9)) {
		error_code = WRITE_FAIL;
		return FALSE;
	}
	SPI_send(0xFF);
	SPI_send(0xFE);			// start of write
	return TRUE;
}

static bit
SD_writeblock(unsigned long blockno, unsigned char * buf)
{
	unsigned int	i;
	unsigned char	tok;

	if(!SD_startwrite(blockno))
		return FALSE;
	i = SD_BLKLEN;
	do
		SPI_send(*buf++);
	while(--i != 0);
	do
		tok = SPI_read();
	while(tok == 0xFF);
	tok &= 0x1F;
	if(tok != 5) {
		error_code = WRITE_FAIL;
		return FALSE;
	}
	// wait for completion
	while(SPI_read() != 0xFF)
		continue;
	return TRUE;
}

// read one BLKLEN block from the card.

static bit
SD_readblock(unsigned long blockno, unsigned char * buf)
{
	unsigned int	i;

	if(!SD_startread(blockno))
		return FALSE;
	if(!SD_waitread())
		return FALSE;
	i = 0;
	do
		*buf++ = SPI_read();
	while(++i != SD_BLKLEN);
	SPI_read();
	SPI_read();		// flush unwanted CRC
	return TRUE;
}


// init the SD card
static bit
SD_init(void)
{
	unsigned char	i;
	unsigned long	sectors;

	part_offset = 0;
	card_size = 0;

	if(!SD_reset() || !SD_readcsd())
		return FALSE;
	if(file_format == 0) {
		if(!SD_readblock(0, blkbuf))
			return FALSE;
		if(blkbuf[SD_BLKLEN-1] == 0xAA && blkbuf[SD_BLKLEN-2] == 0x55) {
			xptr = blkbuf+0x1BE;		// point to start of partition table
			i = 4;
			do {
				if(xptr[0] == 0 &&
						(xptr[4] == 1 || xptr[4] == 4 || xptr[4] == 6 || xptr[4] == 14)) {
					part_offset = get_long(xptr+8);
					card_size -= part_offset;
					break;
				}
			} while(--i != 0);
		}
	}
	return TRUE;
}

// check the filesystem type - set a flag if it is FAT32
static void
SD_fatchick(void)
{
	unsigned char	i;
	unsigned long	sectors;

	fat32 = 0;
	fat_check = 0;
	if(!SD_readblock(part_offset, blkbuf))
		return;
	if(blkbuf[0x13] != 0 || blkbuf[0x14] != 0)
		return;								// must be <= 32MB, quite safe
	sectors = get_long(blkbuf+0x20);		// get number of sectors
	i = blkbuf[0x0D];						// sectors per cluster
	while((i >>= 1) != 0)
		sectors >>= 1;
	if(sectors >= 65525)
		fat32 = 1;
}




[Non-text portions of this message have been removed]

Attachments

Move to quarantaine

This moves the raw source file on disk only. The archive index is not changed automatically, so you still need to run a manual refresh afterward.