SD card initialization is failing (CMD0)

Go To Last Post
6 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ISSUE SOLVED

TLDR: I was operating in SPI MODE 1 the entire time, I should have been in SPI MODE 0 e.g.

//BUFEN disabled; BUFWR disabled; SSD disabled; MODE 0; 
SPI0.CTRLB = 0x00;

 

Summary of issue

MicroSD card is always returning 0x80 followed by 0xFF in response to CMD0. I am writing the interfacing code for FatFs for the ATTiny1627.

I have provided as much detail as I can below, including code, logic analyser shots, and diagrams of the physical setup. 

 

 

More detail

I am trying to implement the driver functions for FatFs to work with the ATTiny1627. I am testing with Chan’s function checker )available here).  It is failing the tests with a status code of 2 returned from test_diskio. I have narrowed down the issue; the SD card never responds with the expected value (I see 0x80 followed by a sequence of 0xFF bytes returned).  The test results in the following text output via the UART:

Running test function

 - failed.

          Sorry the function/compatibility test failed. (rc=2)

                                                              FatFs will not work with this disk driver.

I am using an ATTiny1627 Curiosity Nano board to interface with a MicroSD card over SPI. I have been testing with a 32GB SanDisk Ultra Plus & a 32GB SanDisk Extreme. Components list:

 

Logic Analyzer Shots

Legend for the following shots:

  • D0 -> SCLK
  • D1 -> MOSI
  • D2 -> UNUSED
  • D3 -> MISO
  • CH1 (Yellow – oscilloscope) – CS

 

This show shows CMD0 being sent on the MOSI line, and response from the card

A screenshot of a computer Description automatically generated with medium confidence

Here is the same LA shot against the relevant block from within send_cmd:

Graphical user interface Description automatically generated

Second shot showing the timings of clock edges during this phase:

A screenshot of a computer Description automatically generated with medium confidence

This shot shows the byte 0x80 followed by a sequence of 0xFF being returned by the card (On D3 - MISO):

Graphical user interface Description automatically generated

This shot shows the debugger view showing the value read on the MISO line is what we saw with the logic analyser: 0xFF.

Graphical user interface, application Description automatically generated

This shot (inside the disk_initialize function) shows that ty is still 0 after the attempt at initializing the card, meaning we will enter the else branch and call power_off() as initialisation has failed.

Graphical user interface, text, application Description automatically generated

 

Physical setup

I have been testing without the DALI 2 Click and RTC Click boards connected, but this shows the physical configuration I am using. The SD card is the only device on the SPI bus.  

Diagram Description automatically generated

 

 

 

Code

I will not put the entire project here. I’ll put what I think is relevant, but am more than happy to host the project on a platform such as GitHub if what I have provided is incomplete.

 

Main.c

N.B.  test_diskio and pn are omitted – they are from Chan's function checker found here

void timerCallback(void) {
    static uint16_t count = 0;
    count++;
    if (count >  10){
        count = 0;
        PORTB.OUTTGL = PIN7_bm;
        disk_timerproc();
    }
}

/*
    Main application
*/
int main(void)
{
    /* Initializes MCU, drivers and middleware */
    SYSTEM_Initialize();
    USART0_Enable();
    TCA0_SetCMP0IsrCallback(timerCallback);

    sei();

    int rc;
    DWORD buff[FF_MAX_SS];  /* Working buffer (4 sector in size) */
    printf("Starting FatFS example\n");

    /* Check function/compatibility of the physical drive #0 */
    rc = test_diskio(0u, 3, buff, sizeof buff);

    if (rc) {
        printf("Sorry the function/compatibility test failed. (rc=%d)\nFatFs will not work with this disk driver.\n", rc);
    } else {
        printf("Congratulations! The disk driver works well.\n");
    }

    /* Replace with your application code */
    while (1){
    }

    return rc;

}

send_cmd and disk_initialize

This is where things are going wrong, I believe.

static
BYTE send_cmd (		/* Returns R1 resp (bit7==1:Send failed) */
	BYTE cmd,		/* Command index */
	DWORD arg		/* Argument */
)
{
	BYTE n, res;

	if (cmd & 0x80) {	/* ACMD<n> is the command sequense of CMD55-CMD<n> */
		cmd &= 0x7F;
		res = send_cmd(CMD55, 0);
		if (res > 1) return res;
	}

	/* Select the card and wait for ready except to stop multiple block read */
	if (cmd != CMD12) {
		deselect();
		if (!select()){
            return 0xFF;
        }
	}

	/* Send command packet */
	xchg_spi(0x40 | cmd);				/* Start + Command index */
	xchg_spi((BYTE)(arg >> 24));		/* Argument[31..24] */
	xchg_spi((BYTE)(arg >> 16));		/* Argument[23..16] */
	xchg_spi((BYTE)(arg >> 8));			/* Argument[15..8] */
	xchg_spi((BYTE)arg);				/* Argument[7..0] */
	n = 0x01;							/* Dummy CRC + Stop */
	if (cmd == CMD0) n = 0x95;			/* Valid CRC for CMD0(0) + Stop */
	if (cmd == CMD8) n = 0x87;			/* Valid CRC for CMD8(0x1AA) Stop */
	xchg_spi(n);

	/* Receive command response */
	if (cmd == CMD12) xchg_spi(0xFF);		/* Skip a stuff byte when stop reading */
	n = 10;								/* Wait for a valid response in timeout of 10 attempts */
	do
		res = xchg_spi(0xFF);
	while ((res & 0x80) && --n);

	return res;			/* Return with the response value */
}

DSTATUS disk_initialize (
	BYTE pdrv		/* Physical drive nmuber (0) */
)
{
	BYTE n, cmd, ty, ocr[4];

	if (pdrv) return STA_NOINIT;		/* Supports only single drive */
	power_off();						/* Turn off the socket power to reset the card */
	if (Stat & STA_NODISK) return Stat;	/* No card in the socket */
	power_on();							/* Turn on the socket power */
	FCLK_SLOW();
	for (n = 10; n; n--) xchg_spi(0xFF);	/* 80 dummy clocks */

	ty = 0;
    BYTE RES = send_cmd(CMD0, 0); // RES is never 1, always 0xFF
	if (RES == 1) {			/* Enter Idle state */
		Timer1 = 100;						/* Initialization timeout of 1000 msec */
		if (send_cmd(CMD8, 0x1AA) == 1) {	/* SDv2? */
			for (n = 0; n < 4; n++) ocr[n] = xchg_spi(0xFF);		/* Get trailing return value of R7 resp */
			if (ocr[2] == 0x01 && ocr[3] == 0xAA) {				/* The card can work at vdd range of 2.7-3.6V */
				while (Timer1 && send_cmd(ACMD41, 1UL << 30));	/* Wait for leaving idle state (ACMD41 with HCS bit) */
				if (Timer1 && send_cmd(CMD58, 0) == 0) {		/* Check CCS bit in the OCR */
					for (n = 0; n < 4; n++) ocr[n] = xchg_spi(0xFF);
					ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;	/* SDv2 */
				}
			}
		} else {							/* SDv1 or MMCv3 */
			if (send_cmd(ACMD41, 0) <= 1) 	{
				ty = CT_SD1; cmd = ACMD41;	/* SDv1 */
			} else {
				ty = CT_MMC; cmd = CMD1;	/* MMCv3 */
			}
			while (Timer1 && send_cmd(cmd, 0));			/* Wait for leaving idle state */
			if (!Timer1 || send_cmd(CMD16, 512) != 0)	/* Set R/W block length to 512 */
				ty = 0;
		}
	}
	CardType = ty;
	deselect();

	if (ty) {			/* Initialization succeded */
		Stat &= ~STA_NOINIT;		/* Clear STA_NOINIT */
		FCLK_FAST();
	} else {			/* Initialization failed */
		power_off();
	}

	return Stat;
}

SPI configuration

This was done using MCC

 

uint8_t SPI0_Initialize()
{
    //DORD disabled; MASTER enabled; CLK2X enabled; PRESC DIV16; ENABLE enabled;
    SPI0.CTRLA = 0x33;

    //BUFEN disabled; BUFWR disabled; SSD disabled; MODE 1;
    SPI0.CTRLB = 0x01;

    //RXCIE disabled; TXCIE disabled; DREIE disabled; SSIE disabled; IE disabled;
    SPI0.INTCTRL = 0x00;
    spi0_desc.status = SPI0_FREE;

    //RXCIF disabled; IF disabled; TXCIF disabled; WRCOL disabled; DREIF disabled; SSIF disabled; BUFOVF disabled;
    SPI0.INTFLAGS = 0x00;
    return 0;
}

 

Pin configuration

This was done using MCC

void PIN_MANAGER_Initialize()

{

    PORT_Initialize();

    /* DIR Registers Initialization */
    PORTA.DIR = 0x00;
    PORTB.DIR = 0x84;
    PORTC.DIR = 0x15;

    /* OUT Registers Initialization */
    PORTA.OUT = 0x00;
    PORTB.OUT = 0x00;
    PORTC.OUT = 0x10;

    /* PINxCTRL registers Initialization */
    PORTA.PIN0CTRL = 0x00;
    PORTA.PIN1CTRL = 0x00;
    PORTA.PIN2CTRL = 0x00;
    PORTA.PIN3CTRL = 0x00;
    PORTA.PIN4CTRL = 0x00;
    PORTA.PIN5CTRL = 0x00;
    PORTA.PIN6CTRL = 0x00;
    PORTA.PIN7CTRL = 0x00;
    PORTB.PIN0CTRL = 0x00;
    PORTB.PIN1CTRL = 0x00;
    PORTB.PIN2CTRL = 0x00;
    PORTB.PIN3CTRL = 0x00;
    PORTB.PIN4CTRL = 0x00;
    PORTB.PIN5CTRL = 0x00;
    PORTB.PIN6CTRL = 0x00;
    PORTB.PIN7CTRL = 0x00;
    PORTC.PIN0CTRL = 0x00;
    PORTC.PIN1CTRL = 0x00;
    PORTC.PIN2CTRL = 0x00;
    PORTC.PIN3CTRL = 0x00;
    PORTC.PIN4CTRL = 0x00;
    PORTC.PIN5CTRL = 0x00;
    PORTC.PIN6CTRL = 0x00;
    PORTC.PIN7CTRL = 0x00;

    /* PORTMUX Initialization */
    PORTMUX.CCLROUTEA = 0x00;
    PORTMUX.EVSYSROUTEA = 0x00;
    PORTMUX.SPIROUTEA = 0x01;
    PORTMUX.TCAROUTEA = 0x00;
    PORTMUX.TCBROUTEA = 0x00;
    PORTMUX.USARTROUTEA = 0x00;

    // register default ISC callback functions at runtime; use these methods to register a custom function
    PORTC_SD_CS_SetInterruptHandler(PORTC_SD_CS_DefaultInterruptHandler);
    PORTB_PB3_SetInterruptHandler(PORTB_PB3_DefaultInterruptHandler);
    PORTB_PB2_SetInterruptHandler(PORTB_PB2_DefaultInterruptHandler);
    PORTB_IO_PB7_SetInterruptHandler(PORTB_IO_PB7_DefaultInterruptHandler);
    PORTC_PC0_SetInterruptHandler(PORTC_PC0_DefaultInterruptHandler);
    PORTC_PC2_SetInterruptHandler(PORTC_PC2_DefaultInterruptHandler);
    PORTC_PC1_SetInterruptHandler(PORTC_PC1_DefaultInterruptHandler);

}

Timer interrupt configuration

This was done using MCC

int8_t TCA0_Initialize()

{

    //Compare 0
    TCA0.SINGLE.CMP0 = 0x270F;

    //Compare 1
    TCA0.SINGLE.CMP1 = 0x01;

    //Compare 2
    TCA0.SINGLE.CMP2 = 0x01;

    //Count
    TCA0.SINGLE.CNT = 0x00;

    //CMP2EN disabled; CMP1EN disabled; CMP0EN disabled; ALUPD disabled; WGMODE NORMAL;
    TCA0.SINGLE.CTRLB = 0x00;

    //CMP2OV disabled; CMP1OV disabled; CMP0OV disabled;
    TCA0.SINGLE.CTRLC = 0x00;

    //SPLITM disabled;
    TCA0.SINGLE.CTRLD = 0x00;

    //CMD NONE; LUPD disabled; DIR disabled;
    TCA0.SINGLE.CTRLECLR = 0x00;

    //CMD NONE; LUPD disabled; DIR UP;
    TCA0.SINGLE.CTRLESET = 0x00;

    //CMP2BV disabled; CMP1BV disabled; CMP0BV disabled; PERBV disabled;
    TCA0.SINGLE.CTRLFCLR = 0x00;

    //CMP2BV disabled; CMP1BV disabled; CMP0BV disabled; PERBV disabled;
    TCA0.SINGLE.CTRLFSET = 0x00;

    //DBGRUN disabled;
    TCA0.SINGLE.DBGCTRL = 0x00;

    //EVACTB UPDOWN; CNTBEI disabled; EVACTA CNT_POSEDGE; CNTAEI disabled;
    TCA0.SINGLE.EVCTRL = 0x60;

    //CMP2 disabled; CMP1 disabled; CMP0 enabled; OVF enabled;
    TCA0.SINGLE.INTCTRL = 0x11;

    //CMP2 disabled; CMP1 disabled; CMP0 disabled; OVF disabled;
    TCA0.SINGLE.INTFLAGS = 0x00;

    //Period
    TCA0.SINGLE.PER = 0x270F;

    //Temporary data for 16-bit Access
    TCA0.SINGLE.TEMP = 0x00;

    //RUNSTDBY disabled; CLKSEL DIV1; ENABLE enabled;
    TCA0.SINGLE.CTRLA = 0x01;

    return 0;

}

SPI Driver Code for FatFs

#define CS_LOW()    PORTC.OUT &= ~(PIN4_bm)   /* Set MMC_CS = low */
#define CS_HIGH()   PORTC.OUT |= PIN4_bm    /* Set MMC_CS = high */

#define MMC_CD PORTC.IN & PIN4_bm      /* Test if card detected.   yes:true, no:false, default:true */
#define MMC_WP 0     /* Test if write protected. yes:true, no:false, default:false */

#define FCLK_FAST() SPI0.CTRLA &= ~(SPI_PRESC_gm); // set the prescaler to 0, which does CLK_PER/4 (run SPI at quarter speed of clock)
#define FCLK_SLOW() SPI0.CTRLA |= (SPI_PRESC1_bm); // set the prescaler to 1, which does CLK_PER/16

void SPI0_WriteTxData(uint8_t data)
{
    SPI0.DATA = data;
}

void SPI0_WaitDataready()
{
    while (!(SPI0.INTFLAGS & SPI_RXCIF_bm))
        ;
}

uint8_t SPI0_GetRxData()
{
    return SPI0.DATA;
}

/* Transmit/Receive data from/to MMC via SPI  (Platform dependent)       */

/* Exchange a byte */

static

/* Exchange a byte */
static
BYTE xchg_spi (		/* Returns received data */
	BYTE dat		/* Data to be sent */
)
{
    SPI0_WriteTxData(dat);
    SPI0_WaitDataready();
    BYTE ret = SPI0_GetRxData();
    return ret;
}

/* Send a data block fast */
static
void xmit_spi_multi (
	const BYTE *p,	/* Data block to be sent */
	UINT cnt		/* Size of data block (must be multiple of 2) */
)
{
	do {
        xchg_spi(*p++);
        xchg_spi(*p++);
	} while (cnt -= 2);
}

/* Receive a data block fast */
static
void rcvr_spi_multi (
	BYTE *p,	/* Data buffer */
	UINT cnt	/* Size of data block (must be multiple of 2) */
)
{
	do {
        *p++ = xchg_spi(0xFF);
        *p++ = xchg_spi(0xFF);
	} while (cnt -= 2);
}

 

 

This topic has a solution.
Last Edited: Wed. Jul 6, 2022 - 04:53 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How many SD cards have you tried ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just the 2 mentioned. They're both SanDisk. I have another one (not SanDisk) that I've ordered online that should be with me in a few days.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have tried a 3rd SD card now and it is showing the exact same behaviour: CMD0 is sent, followed by one byte of 0xFF, the card responds with 0x80 followed by a consecutive sequence of 0xFF bytes. I'm doing something wrong, but i'm stumped as to what

 

CMD0 on new SD card

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I figured out what I was doing wrong: I just discovered that I was operarting in SPI MODE 1 this entire time. 

 

You need SPI MODE 0.

 

i.e. in my SPI initialisation code, this is all that is needed: 

//BUFEN disabled; BUFWR disabled; SSD disabled; MODE 0;
SPI0.CTRLB = 0x00;

 

CMD0 now working

Last Edited: Wed. Jul 6, 2022 - 04:44 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1


Again it's a shame they ruined the ffsample.zip by removing most of the detail from the SPI version of the driver. Going all the way back to a 2014 version one finds this as an example:

The actual code might not be valid for an Xmega but that comment is sure useful!