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:
- ATTiny1627 Curiosity Nano Eval Kit
- Curiosity Nano Baseboard
- MicroSD Click board
- SanDisk Extreme microSDXC card – 32GB
- SanDisk ultra microSD – 32GB
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
Here is the same LA shot against the relevant block from within send_cmd:
Second shot showing the timings of clock edges during this phase:
This shot shows the byte 0x80 followed by a sequence of 0xFF being returned by the card (On D3 - MISO):
This shot shows the debugger view showing the value read on the MISO line is what we saw with the logic analyser: 0xFF.
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.
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.
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); }