From 9de0c6546036d3a1c6cb0036062f56782335a853 Mon Sep 17 00:00:00 2001 From: Markus Hitter Date: Wed, 5 Nov 2014 00:06:32 +0100 Subject: [PATCH] SD card: add the layer between hardware and Petit FatFs. This is all the commands to read from and write to SPI, initializing the card, read in blocks and so on. This should make Petit FatFs actually usable. So far read-only and no M-codes to let end users play with this stuff. The demonstration code was changed to list the SD card's top level directory over and over again. --- mendel.c | 50 +++++++--- pff.c | 2 + pff_conf.h | 2 +- pff_diskio.c | 266 ++++++++++++++++++++++++++++++++++++++++++--------- pff_diskio.h | 8 +- sd.h | 5 + 6 files changed, 272 insertions(+), 61 deletions(-) diff --git a/mendel.c b/mendel.c index 1b02b98..759cf73 100644 --- a/mendel.c +++ b/mendel.c @@ -266,20 +266,48 @@ int main (void) init(); #ifdef SD - // This demonstrates SPI. Gives a nice dance on the scope. - // Will go away as soon as we can do something useful with the SD card. + // This demonstrates SD card reading. It simply lists the top level + // directory over and over again. This demo will go away as soon as we + // have the G-code implementations to do something useful in place. #include "delay.h" - uint8_t c = 0; - while (1) { - // Trigger signal for the scope. - WRITE(STEPPER_ENABLE_PIN, 0); - delay_us(10); - WRITE(STEPPER_ENABLE_PIN, 1); - spi_rw(c); - c++; - delay_ms(50); + while (1) { + FATFS sdfile; + FRESULT res; + + if ((res = pf_mount(&sdfile)) == FR_OK) + serial_writestr_P(PSTR("SD card mounted successfully.\n")); + else { + serial_writestr_P(PSTR("Mounting SD card failed :-/\n")); + delay_ms(10); + if (res == FR_NOT_READY) + serial_writestr_P(PSTR("No medium or hardware error.\n")); + else if (res == FR_DISK_ERR) + serial_writestr_P(PSTR("Error while reading.\n")); + else if (res == FR_NO_FILESYSTEM) + serial_writestr_P(PSTR("No valid FAT partition.\n")); + } + + // Inspired by http://elm-chan.org/fsw/ff/pf/readdir.html. + FILINFO fno; + DIR dir; + char* path ="/"; + + res = pf_opendir(&dir, path); + if (res == FR_OK) { + for (;;) { + res = pf_readdir(&dir, &fno); + if (res != FR_OK || fno.fname[0] == 0) + break; + serial_writestr((uint8_t *)path); + serial_writestr((uint8_t *)fno.fname); + serial_writechar('\n'); + delay_ms(2); // Time for sending the characters. + } + } + delay_ms(5000); } + #endif // main loop diff --git a/pff.c b/pff.c index 52deffd..d7477d7 100644 --- a/pff.c +++ b/pff.c @@ -38,6 +38,8 @@ file name conflicts. - Changed former integer.h content to use fixed width types (int8_t, etc). - Wrapped code in #ifdef SD to compile it only when needed. + - Added the hardware connection layer from sample code of the same source, + see pff_diskio.c. */ #include "pff.h" /* Petit FatFs configurations and declarations */ diff --git a/pff_conf.h b/pff_conf.h index dd4d1f2..59ec26b 100644 --- a/pff_conf.h +++ b/pff_conf.h @@ -11,7 +11,7 @@ /---------------------------------------------------------------------------*/ #define _USE_READ 1 /* Enable pf_read() function */ -#define _USE_DIR 0 /* Enable pf_opendir() and pf_readdir() function */ +#define _USE_DIR 1 /* Enable pf_opendir() and pf_readdir() function */ #define _USE_LSEEK 0 /* Enable pf_lseek() function */ #define _USE_WRITE 0 /* Enable pf_write() function */ diff --git a/pff_diskio.c b/pff_diskio.c index 79e4214..c91664e 100644 --- a/pff_diskio.c +++ b/pff_diskio.c @@ -1,5 +1,11 @@ /*-----------------------------------------------------------------------*/ /* Low level disk I/O module skeleton for Petit FatFs (C)ChaN, 2014 */ +/* */ +/* ... completed by ... */ +/* */ +/* PFF - Low level disk control module for AVR (C)ChaN, 2014 */ +/* File avr_mmcp.c, part of avr/ in pffsample, a package with sample */ +/* implementations on http://elm-chan.org/fsw/ff/00index_p.html */ /*-----------------------------------------------------------------------*/ /* Changes for Teacup see pff.c. */ @@ -7,72 +13,238 @@ #ifdef SD +#include +#include "spi.h" +#include "delay.h" -/*-----------------------------------------------------------------------*/ -/* Initialize Disk Drive */ -/*-----------------------------------------------------------------------*/ +/* Definitions for MMC/SDC command. */ +#define CMD0 (0x40 + 0) /* GO_IDLE_STATE */ +#define CMD1 (0x40 + 1) /* SEND_OP_COND (MMC) */ +#define ACMD41 (0xC0 + 41) /* SEND_OP_COND (SDC) */ +#define CMD8 (0x40 + 8) /* SEND_IF_COND */ +#define CMD16 (0x40 + 16) /* SET_BLOCKLEN */ +#define CMD17 (0x40 + 17) /* READ_SINGLE_BLOCK */ +#define CMD24 (0x40 + 24) /* WRITE_BLOCK */ +#define CMD55 (0x40 + 55) /* APP_CMD */ +#define CMD58 (0x40 + 58) /* READ_OCR */ -DSTATUS disk_initialize (void) -{ - DSTATUS stat; +/* Card type flags (card_type). */ +#define CT_MMC 0x01 /* MMC ver 3 */ +#define CT_SD1 0x02 /* SD ver 1 */ +#define CT_SD2 0x04 /* SD ver 2 */ +#define CT_BLOCK 0x08 /* Block addressing */ - // Put your code here - return stat; +static BYTE card_type; /* Card type flags. */ + + +/** Send a command packet to MMC/SD card. + + \param cmd Command. + \param arg Argument (32 bit). + + \return response byte (bit 7 == 1: send failed). +*/ +static BYTE send_cmd(BYTE cmd, DWORD arg) { + BYTE n, res; + + if (cmd & 0x80) { /* ACMD is the command sequence of CMD55-CMD. */ + cmd &= 0x7F; + res = send_cmd(CMD55, 0); + if (res > 1) + return res; + } + + /* Select the card */ + spi_deselect_sd(); + spi_rw(0xFF); + spi_select_sd(); + spi_rw(0xFF); + + /* Send command packet */ + spi_rw(cmd); /* Start + Command index */ + spi_rw((BYTE)(arg >> 24)); /* Argument[31..24] */ + spi_rw((BYTE)(arg >> 16)); /* Argument[23..16] */ + spi_rw((BYTE)(arg >> 8)); /* Argument[15..8] */ + spi_rw((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 */ + spi_rw(n); + + /* Receive command response */ + n = 10; /* Wait for a response, try 10 times. */ + do { + res = spi_rw(0xFF); + n--; + } while ((res & 0x80) && n); + + return res; } +/** Initialize disk drive. + \return 0 on success, else STA_NOINIT. -/*-----------------------------------------------------------------------*/ -/* Read Partial Sector */ -/*-----------------------------------------------------------------------*/ + Initialize disk drive and also find out which kind of card we're talking to. -DRESULT disk_readp ( - BYTE* buff, /* Pointer to the destination object */ - DWORD sector, /* Sector number (LBA) */ - UINT offset, /* Offset in the sector */ - UINT count /* Byte count (bit15:destination) */ -) -{ - DRESULT res; + Description about what's going on here see + http://elm-chan.org/docs/mmc/mmc_e.html, center section. +*/ +DSTATUS disk_initialize(void) { + BYTE n, cmd, ty, ocr[4]; + UINT timeout; - // Put your code here + spi_deselect_sd(); + spi_speed_100_400(); + for (n = 10; n; n--) /* 80 dummy clocks with CS = high. */ + spi_rw(0xFF); - return res; -} + /* Find card type: MMCv3, SDv1 or SDv2. */ + ty = 0; + if (send_cmd(CMD0, 0) == 1) { /* Software reset, enter idle state. */ + if (send_cmd(CMD8, 0x1AA) == 1) { /* SDv2? (rejected on others) */ + for (n = 0; n < 4; n++) /* Get trailing return value of R7. */ + ocr[n] = spi_rw(0xFF); + if (ocr[2] == 0x01 && ocr[3] == 0xAA) { + /* The card can work at Vdd range of 2.7 - 3.6V. */ + /* Wait for leaving idle state (ACMD41 with HCS bit). */ + for (timeout = 10000; timeout && send_cmd(ACMD41, 1UL << 30); timeout--) + delay_us(100); -/*-----------------------------------------------------------------------*/ -/* Write Partial Sector */ -/*-----------------------------------------------------------------------*/ - -DRESULT disk_writep ( - const BYTE* buff, /* Pointer to the data to be written, - NULL:Initiate/Finalize write operation */ - DWORD sc /* Sector number (LBA) or Number of bytes to send */ -) -{ - DRESULT res; - - - if (!buff) { - if (sc) { - - // Initiate write process - - } else { - - // Finalize write process - + /* Find out wether it's a block device (CCS bit in OCR). */ + if (timeout && send_cmd(CMD58, 0) == 0) { + for (n = 0; n < 4; n++) + ocr[n] = spi_rw(0xFF); + ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2; /* SDv2 */ } - } else { + } + } + else { /* SDv1 or MMCv3 */ + if (send_cmd(ACMD41, 0) <= 1) { + ty = CT_SD1; cmd = ACMD41; /* SDv1 */ + } + else { + ty = CT_MMC; cmd = CMD1; /* MMCv3 */ + } - // Send data to the disk + /* Wait for leaving idle state. */ + for (timeout = 10000; timeout && send_cmd(cmd, 0); timeout--) + delay_us(100); + + /* Set R/W block length to 512. */ + if ( ! timeout || send_cmd(CMD16, 512) != 0) + ty = 0; + } + } + + card_type = ty; + spi_deselect_sd(); + spi_rw(0xFF); + + return ty ? 0 : STA_NOINIT; +} + +/** Read partial sector. + + \param buffer Received data should go in here. + \param sector Sector number (LBA). + \param offset Offset into the sector. + \param count Byte count (bit 15: destination). + + \return RES_OK on success, else RES_ERROR. + + This is the main reading function. Forming this into directory listings, + file reads and such stuff is done by Petit FatFs it's self. + + Description about what's going on here see + http://elm-chan.org/docs/mmc/mmc_e.html, bottom section. +*/ +#if _USE_READ +DRESULT disk_readp(BYTE* buffer, DWORD sector, UINT offset, UINT count) { + DRESULT result; + BYTE token = 0xFF; + uint16_t timeout; + UINT trailing = 514 - offset - count; /* 514 = block size + 2 bytes CRC */ + + /* Convert to byte address on non-block cards. */ + if ( ! (card_type & CT_BLOCK)) + sector *= 512; + + /* Read one sector, copy only as many bytes as required. */ + result = RES_ERROR; + spi_speed_max(); + if (send_cmd(CMD17, sector) == 0) { /* Read single block. */ + /* Wait for data packet. */ + for (timeout = 40000; timeout && (token == 0xFF); timeout--) + token = spi_rw(0xFF); + + if (token == 0xFE) { /* Valid data arrived. */ + /* Skip leading bytes. */ + while (offset--) + spi_rw(0xFF); + + /* Receive the requested part of the sector. */ + do { + *buffer++ = spi_rw(0xFF); + } while (--count); + + /* Skip trailing bytes and CRC. */ + while (trailing--) + spi_rw(0xFF); + + result = RES_OK; + } + } + + spi_deselect_sd(); /* Every send_cmd() selects. */ + spi_rw(0xFF); + + return result; +} +#endif /* _USE_READ */ + +/** Write partial pector. + + \param buff Pointer to the data to be written. + NULL: Initiate/Finalize write operation. + \param sc Sector number (LBA) or number of bytes to send. + + \return RES_OK on success, else RES_ERROR. + + This is the main writing function. Forming this into file writes and such + stuff is done by Petit FatFs it's self. + + Description about what's going on here see + http://elm-chan.org/docs/mmc/mmc_e.html, bottom section. +*/ +#if _USE_WRITE +DRESULT disk_writep(const BYTE* buff, DWORD sc) { + #error disk_writep() not yet implemented + DRESULT res; + + if ( ! buff) { + if (sc) { + + // Initiate write process } + else { - return res; + // Finalize write process + + } + } + else { + + // Send data to the disk + + } + + return res; } +#endif /* _USE_WRITE */ #endif /* SD */ diff --git a/pff_diskio.h b/pff_diskio.h index e471115..5546a3b 100644 --- a/pff_diskio.h +++ b/pff_diskio.h @@ -34,8 +34,12 @@ typedef enum { /* Prototypes for disk control functions */ DSTATUS disk_initialize (void); -DRESULT disk_readp (BYTE* buff, DWORD sector, UINT offser, UINT count); -DRESULT disk_writep (const BYTE* buff, DWORD sc); +#if _USE_READ + DRESULT disk_readp (BYTE* buffer, DWORD sector, UINT offset, UINT count); +#endif +#if _USE_WRITE + DRESULT disk_writep (const BYTE* buffer, DWORD sc); +#endif #define STA_NOINIT 0x01 /* Drive not initialized */ #define STA_NODISK 0x02 /* No medium in the drive */ diff --git a/sd.h b/sd.h index de20a8b..7e7a6d4 100644 --- a/sd.h +++ b/sd.h @@ -10,6 +10,11 @@ #ifdef SD_CARD_SELECT_PIN #define SD +#include "pff.h" + +// Feature set of Petit FatFs is currently defined early in pff_conf.h. + + void sd_init(void); #endif /* SD_CARD_SELECT_PIN */