/*! * \file drv/sd_spi.h * \brief * SD card driver using SPI interface * * \copyright Copyright (C) 2021 Christos Choutouridis * *
License
* The MIT License (MIT) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *
*/ #ifndef TBX_DRV_SD_SPI_H_ #define TBX_DRV_SD_SPI_H_ #include #include //#include #include #include namespace tbx { /*! * * http://elm-chan.org/docs/mmc/mmc_e.html * * CRTP requirements * bool WP_impl (); // write protect, true => write protect * bool CD_impl (); // check disk present, true => present * void CS_impl (bool select); // Chip select, true => select * void PWR_impl(bool state); // SD power, true => power the card * data_type SPI_rw_impl (data_type); // SPI read-write functionality * bool SPI_set_clk_impl(uint32_t clk); // SPI set clock functionality * clock_t clock_impl(); // get system's CPU time */ template class sd_card { _CRTP_IMPL(Impl_t); using data_type = uint8_t; // Driver settings constexpr static clock_t SD_WaitTimeout = 500; // 500 [CPU time] constexpr static clock_t SD_PowerTimeout= 250; // 250 [CPU time] constexpr static clock_t SD_RxTimeout = 100; // 100 [CPU time] constexpr static clock_t SD_InitTimeout = 2000; // 2000 [CPU time] constexpr static uint32_t MaxInitClock = 400000; // 400000 [Hz] // MMC/SDC definitions constexpr static data_type CMD_MSB = 0x40; constexpr static data_type CMD_CRC_LSB = 0x01; constexpr static data_type CMD0 = (CMD_MSB | 0); //!< GO_IDLE_STATE constexpr static data_type CMD1 = (CMD_MSB | 1); //!< SEND_OP_COND (MMC) constexpr static data_type CMD8 = (CMD_MSB | 8); //!< SEND_IF_COND constexpr static data_type CMD9 = (CMD_MSB | 9); //!< SEND_CSD constexpr static data_type CMD10 = (CMD_MSB | 10); //!< SEND_CID constexpr static data_type CMD12 = (CMD_MSB | 12); //!< STOP_TRANSMISSION constexpr static data_type CMD16 = (CMD_MSB | 16); //!< SET_BLOCKLEN constexpr static data_type CMD17 = (CMD_MSB | 17); //!< READ_SINGLE_BLOCK constexpr static data_type CMD18 = (CMD_MSB | 18); //!< READ_MULTIPLE_BLOCK constexpr static data_type CMD23 = (CMD_MSB | 23); //!< SET_BLOCK_COUNT (MMC) constexpr static data_type CMD24 = (CMD_MSB | 24); //!< WRITE_BLOCK constexpr static data_type CMD25 = (CMD_MSB | 25); //!< WRITE_MULTIPLE_BLOCK constexpr static data_type CMD55 = (CMD_MSB | 55); //!< APP_CMD constexpr static data_type CMD58 = (CMD_MSB | 58); //!< READ_OCR constexpr static data_type ACMD13 = (0xC0 + 13); //!< SD_STATUS (SDC) constexpr static data_type ACMD23 = (0xC0 + 23); //!< SET_WR_BLK_ERASE_COUNT (SDC) constexpr static data_type ACMD41 = (0xC0 + 41); //!< SEND_OP_COND (SDC) constexpr static data_type R1_READY_STATE = 0x00; //!< status for card in the ready state constexpr static data_type R1_IDLE_STATE = 0x01; //!< status for card in the idle state constexpr static data_type R1_ILLEGAL_COMMAND = 0x04; //!< status bit for illegal command constexpr static data_type DATA_START_BLOCK = 0xFE; //!< start data token for read or write single block constexpr static data_type STOP_TRAN_TOKEN = 0xFD; //!< stop token for write multiple blocks constexpr static data_type WRITE_MULTIPLE_TOKEN= 0xFC; //!< start data token for write multiple blocks constexpr static data_type DATA_RES_MASK = 0x1F; //!< mask for data response tokens after a write block operation constexpr static data_type DATA_RES_ACCEPTED = 0x05; //!< write data accepted token // MMC card type flags (MMC_GET_TYPE) //! \note //! These types are compatible with FatFS types constexpr static data_type CT_NONE = 0x00; constexpr static data_type CT_MMC = 0x01; //!< MMC ver 3 constexpr static data_type CT_SD1 = 0x02; //!< SD ver 1 constexpr static data_type CT_SD2 = 0x04; //!< SD ver 2 constexpr static data_type CT_SDC = (CT_SD1|CT_SD2); //!< SD constexpr static data_type CT_BLOCK = 0x08; //!< Block addressing public: enum status_t : uint8_t { ST_OK =0, ST_NOINIT = 1, ST_NODISK = 2, ST_WRPROTECT = 3, ST_ERROR = 4 }; enum ioctl_cmd { // Fatfs compatibility IOCTL_SYNC =0, //!< Flush disk cache (for write functions) IOCTL_GET_SECTOR_COUNT =1, //!< Get media size (for only f_mkfs()) IOCTL_GET_SECTOR_SIZE =2, //!< Get sector size (for multiple sector size (_MAX_SS >= 1024)) IOCTL_GET_BLOCK_SIZE =3, //!< Get erase block size (for only f_mkfs()) IOCTL_ERASE_SECTOR =4, //!< Force erased a block of sectors (for only _USE_ERASE) // Generics IOCTL_POWER =5, //!< Get/Set power status IOCTL_LOCK =6, //!< Lock/Unlock media removal IOCTL_EJECT =7, //!< Eject media IOCTL_FORMAT =8, //!< Create physical format on the media // SD/MMC specific IOCTL_MMC_GET_TYPE =10, //!< Get card type IOCTL_MMC_GET_CSD =11, //!< Get CSD IOCTL_MMC_GET_CID =12, //!< Get CID IOCTL_MMC_GET_OCR =13, //!< Get OCR IOCTL_MMC_GET_SDSTAT =14, //!< Get SD status }; public: sd_card() : status{ST_NOINIT} { } sd_card(const sd_card&) = delete; sd_card& operator=(const sd_card&) = delete; private: /*! * \brief * Calculate the maximum data transfer rate per one data line * from the CSD. * TRAN_SPEED is the CSD[103..96] * * TRAN_SPEED bit code * --------------------------------------------------- * 2:0 | transfer rate unit * | 0=100kbit/s, 1=1Mbit/s, 2=10Mbit/s, * | 3=100Mbit/s, 4... 7=reserved * --------------------------------------------------- * 6:3 | time value * -------------------------------------------------- * 7 | Reserved * * \param csd Pointer to CSD array 128bit. * \return The maximum spi baud rate. */ uint32_t csd2bautrate (data_type *csd) { data_type brmul = 0; uint32_t br = 100000; // 100Kbit // Mask [2..0] bits of TRAN_SPEED brmul = csd[3] & 0x07; while (brmul--) br *= 10; return br; } void delay (clock_t t) { clock_t mark = impl().clock_impl(); while (impl().clock_impl() - mark < t) ; } /*! * \brief Check if SD Card is present. * \return The sd card present status * \arg false Is NOT present * \arg true Is present. */ bool is_present () { return impl().CD_impl(); } /*! * \brief Check if SD Card is write protected. * \return The write protect status * \arg false Is NOT write protected * \arg true Is write protected. */ bool is_write_protected () { return impl().WP_impl(); } /*! * \brief Powers up or down the SD Card. * \param on On/Off flag. * \return The new power state state */ bool power (bool on) { impl().PWR_impl(on); return pwr_flag = on; } /*! * \brief Check if SD Card is powered. * \return The power status * \arg false The drive is not powered * \arg true The drive is powered */ bool power () { return pwr_flag; } /*! * \brief Chip-select control * \param state True to Select, false to de-select. */ void select() { spi_tx(0xFF); impl().CS_impl(false); spi_tx(0xFF); } /*! * \brief De-select SD Card and release SPI bus * \return None. */ void release () { spi_tx(0xFF); impl().CS_impl(true); spi_tx(0xFF); } /*! * \brief Transmit a byte to SD/MMC via SPI * \param data The data to send to the SPI bus. */ void spi_tx (data_type data) { impl().SPI_rw_impl (data); } /*! * \brief Receive a byte to SD/MMC via SPI. * \return The data received from SPI bus. */ data_type spi_rx () { return impl().SPI_rw_impl (0xFF); } /*! * \brief Keep calling spi_rx until response \c resp. * * \param resp the response we wait for * \param timeout timeout for the operation * \return * \arg true Ready * \arg false NOT ready. */ bool spi_wait_for (data_type resp, clock_t timeout) { data_type res; clock_t mark = impl().clock_impl(); do res = spi_rx (); while ((res != resp) && ((impl().clock_impl() - mark) < timeout)); return (res == resp); } bool activate (bool state) { if (state) { power(true); // power on with delay delay (SD_PowerTimeout); impl().CS_impl(1); // make sure CS is high for (size_t i=0 ; i<10 ; ++i) // 80 dummy clocks with DI high spi_tx(0xFF); status = ST_NOINIT; // mark the status } else { power(false); // power off impl().CS_impl(0); // keep CS pin voltage low status = ST_NOINIT; // mark the status } return state; } /*! * \brief * Receive a data packet from MMC/SD * * \param buffer Pointer to data buffer to store received data * \param n Byte count (must be multiple of 4) * \return The operation status * \arg false Fail * \arg true Success. */ bool rx_datablock (data_type* buffer, size_t n) { if (! spi_wait_for(DATA_START_BLOCK, SD_RxTimeout)) return false; /*! * Receive the data block into buffer and make sure * we receive multiples of 4 */ n += (n%4) ? 4-(n%4):0; for ( ; n>0 ; --n) *buffer++ = spi_rx (); spi_rx (); // Discard CRC spi_rx (); return true; } /*! * \brief * Transmit a data block (512bytes) to MMC/SD * * \param buffer Pointer to 512 byte data block to be transmitted * \param token Data/Stop token * \return The operation status * \arg false Fail * \arg true Success. */ bool tx_datablock (const data_type* buffer, data_type token) { if (!spi_wait_for(0xFF, SD_WaitTimeout)) return false; spi_tx(token); // transmit data token if (token != STOP_TRAN_TOKEN) { // if its data token, transmit the 512 byte block size_t cnt = 512; do spi_tx(*buffer++); while (--cnt); spi_tx(0xFF); // CRC (Dummy) spi_tx(0xFF); data_type r = spi_rx(); // Receive data response if ((r & DATA_RES_MASK) != DATA_RES_ACCEPTED) // If not accepted, return with error return false; } return true; } /*! * \brief * Send a command packet to SD/MMC and return the response * * \param cmd Command byte * \param arg Argument * \return The response as operation status */ data_type command (data_type cmd, uint32_t arg) { data_type n, r; if (cmd & 0x80) { /*! * SD_ACMD is the command sequence of CMD55-SD_CMD */ cmd &= 0x7F; r = command (CMD55, 0); if (r > 1) return r; } // Send command packet spi_tx (cmd); // Start + Command index spi_tx ((data_type)(arg>>24)); // Argument [31..24] spi_tx ((data_type)(arg>>16)); // Argument [23..16] spi_tx ((data_type)(arg>>8)); // Argument [15..8] spi_tx ((data_type)arg); // Argument [7..0] if (cmd == CMD0) n = 0x94; // Valid CRC for CMD0(0) else if (cmd == CMD8) n = 0x86; // Valid CRC for CMD8(0x1AA) else n = 0x00; spi_tx (n | CMD_CRC_LSB); // Receive command response if (cmd == CMD12) spi_rx (); // Skip a stuff byte when stop reading // Wait for a valid response in timeout of 0xFF attempts size_t nn = 0xFF; do r = spi_rx (); while ((r & 0x80) && --nn); return r; // Return with the response value } bool do_command_until (data_type done, data_type cmd, uint32_t arg, clock_t timeout) { clock_t mark = impl().clock_impl(); data_type ret; do ret = command (cmd, arg); while (ret != done && impl().clock_impl() - mark < timeout); return ret == done; } public: bool get_CSD (data_type* csd) { bool ret = false; select(); // select card's CS if (command (CMD9, 0) == R1_READY_STATE && rx_datablock (csd, 16)) // READ_CSD ret = true; release(); // release card's CS return ret; } bool get_CID (data_type* cid) { bool ret = false; select(); // select card's CS if (command (CMD10, 0) == R1_READY_STATE && rx_datablock (cid, 16)) // READ_CID ret = true; release(); // release card's CS return ret; } bool get_OCR (data_type* ocr) { bool ret = false; select(); // select card's CS // Receive OCR as an R3 response (4 bytes) if (command (CMD58, 0) == 0) { // READ_OCR for (size_t n = 0; n < 4; ++n) *ocr++ = spi_rx (); ret = true; } release(); // release card's CS return ret; } // bool get_SDSTAT (data_type* sdstat) { // bool ret = false; // select(); // select card's CS // if (command (ACMD13, 0) == 0) { // SD_STATUS // spi_rx (); // if (rx_datablock (sdstat, 64)) // ret = true; // } // release(); // release card's CS // return ret; // } bool sync () { select(); // select card's CS bool st = spi_wait_for(0xFF, SD_WaitTimeout); // flush release(); // release card's CS return st; } size_t sector_count() { size_t ret =0; data_type csd[16]; select(); // select card's CS if (get_CSD(csd)) { if ((csd[0] >> 6) == 1) { // SDC version 2.00 size_t csize = csd[9] + ((uint16_t)csd[8] << 8) + 1; ret = csize << 10; } else { // SDC version 1.XX or MMC uint8_t n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; size_t csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1; ret = csize << (n - 9); } } release(); // release card's CS return ret; } size_t sector_size() const { return 512; } size_t block_size() { size_t ret =0; data_type csd[16]; select(); // select card's CS if (card_type & CT_SD2) { // SDC version 2.00 if (command (ACMD13, 0) == R1_READY_STATE) { spi_rx (); // Read SD status if (rx_datablock (csd, 16)) { // Read partial block for (size_t n = 64 - 16; n; n--) // Purge trailing data spi_rx (); ret = 16UL << (csd[10] >> 4); } } } else { // SDC version 1.XX or MMC if (get_CSD(csd)) { // Read CSD if (card_type & CT_SD1) // SDC version 1.XX ret = (((csd[10] & 63) << 1) + ((uint16_t)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1); else // MMC ret = ((uint16_t)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1); } } release(); // release card's CS return ret; } /*! * \brief * De-Initialize SD Drive. * \return None */ void deinit () { card_type = data_type{}; status = status_t{}; activate (0); // finally power off the card } /*! * \brief * Initialize SD Drive. * * \return The status of the operation * \arg false On error. * \arg true On success. */ bool init () { uint32_t clk; data_type ocr[4], csd[16]; clk = 400000; // Start at lower clk impl().SPI_set_clk_impl(clk); activate (0); // Initially power off the card if (!is_present()) { // check for presence status = ST_NODISK; return false; } activate (1); // activate and wait for PowerTimeout delay select(); // select card data_type type = CT_NONE; if (command (CMD0, 0) == R1_IDLE_STATE) { // Command to enter Idle state if (command (CMD8, 0x1AA) == 1) { // check SD version // SDHC for (size_t n=0 ; n<4 ; ++n) // Get trailing return value of R7 response ocr[n] = spi_rx (); if (ocr[2] == 0x01 && ocr[3] == 0xAA) { // Wait for leaving idle state (ACMD41 with HCS bit) bool st = do_command_until(R1_READY_STATE, ACMD41, 1UL << 30, SD_InitTimeout); if (st && get_OCR(ocr)) type = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2; } } else { data_type cmd; // SDSC or MMC if (command (ACMD41, 0) <= 1) { // SDSC type = CT_SD1; cmd = ACMD41; } else { // MMC type = CT_MMC; cmd = CMD1; } // Wait for leaving idle state (ACMD41 || CMD1) bool st = do_command_until(R1_READY_STATE, cmd, 0, SD_InitTimeout); // On failure, set R/W block length to 512 (For FAT compatibility) if (!st || command (CMD16, 512) != R1_READY_STATE) type = CT_NONE; } } card_type = type; release (); // Initialization ended if (type != CT_NONE) { // Success get_CSD(csd); clk = csd2bautrate(csd); impl().SPI_set_clk_impl(clk); status = ST_OK; return true; } else { activate(0); return false; } } status_t get_status () const { return status; } /*! * \brief * Read Sector(s) * * \param sector Start sector number (LBA) * \param buf Pointer to the data buffer to store read data * \param count Sector (512 bytes) count (1..255) * \return The status of the operation * \arg false On error. * \arg true On success. */ bool read (size_t sector, data_type *buf, size_t count) { if (status != ST_OK) return false; if (!(card_type & CT_BLOCK)) // Convert to byte address if needed sector *= 512; select(); if (count == 1) { //Single block read if (command (CMD17, sector) == 0) if (rx_datablock (buf, 512)) count = 0; } else { // Multiple block read if (command (CMD18, sector) == 0) { do { if (!rx_datablock (buf, 512)) break; buf += 512; } while (--count); command (CMD12, 0); // STOP_TRANSMISSION } } release (); return (count == 0); } /*! * \brief * Write Sector(s) * * \param sector Start sector number (LBA) * \param buf Pointer to the data to be written * \param count Sector(512 bytes) count (1..255) * \return The status of the operation * \arg false On error. * \arg true On success. */ bool write (size_t sector, const data_type *buf, size_t count) { if (!count) return false; if (status != ST_OK) return false; if (!(card_type & CT_BLOCK)) // Convert to byte address if needed sector *= 512; select(); if (count == 1) { // Single block write if ((command (CMD24, sector) == 0) && tx_datablock (buf, 0xFE)) count = 0; } else { // Multiple block write if (card_type & CT_SDC) command (ACMD23, count); if (command (CMD25, sector) == 0) { do { if (!tx_datablock (buf, WRITE_MULTIPLE_TOKEN)) break; buf += 512; } while (--count); if (!tx_datablock (0, STOP_TRAN_TOKEN)) // STOP token count = 1; } } release (); return (count == 0); } bool ioctl (ioctl_cmd cmd, void* buffer) { switch (cmd) { // SD/MMC specific case IOCTL_MMC_GET_TYPE: *(data_type*)buffer = card_type; return true; case IOCTL_MMC_GET_CSD: return get_CSD ((data_type*)buffer); case IOCTL_MMC_GET_CID: return get_CID ((data_type*)buffer); case IOCTL_MMC_GET_OCR: return get_OCR ((data_type*)buffer); case IOCTL_MMC_GET_SDSTAT: return false; // Generic case IOCTL_POWER: switch (*(data_type*)buffer) { case 0: *((data_type*)buffer+1) = (data_type)activate(0); return true; case 1: *((data_type*)buffer+1) = (data_type)activate(0); return true; case 2: *((data_type*)buffer+1) = (data_type)power(); return true; default: return false; } break; case IOCTL_LOCK: case IOCTL_EJECT: case IOCTL_FORMAT: return false; // FatFS compatibility case IOCTL_SYNC: return sync(); case IOCTL_GET_SECTOR_COUNT:return (*(size_t*) buffer = sector_count() != 0); case IOCTL_GET_SECTOR_SIZE: return (*(size_t*) buffer = sector_size() != 0); case IOCTL_GET_BLOCK_SIZE: return (*(size_t*) buffer = block_size() != 0); case IOCTL_ERASE_SECTOR: return false; default: return false; } } private: data_type card_type{}; status_t status{}; bool pwr_flag{}; }; } // namespace tbx #endif /* TBX_DRV_SD_SPI_H_ */