ESP8266 SD card interfacing using FreeRTOS SDK – part 2

posted in: Internet of Things (IoT) | 6

This article follows from part 1. I did some more probing and reading to come up with better ideas to get the HSPI on the ESP8266 working. Also, I put a logic analyzer to see if the main SPI interface is usable (the one used with the main flash memory). But it seems that the main SPI interface is way too busy to be useful for other purposes. Therefore, the focus here will be on the HSPI interface only for ESP8266 SD card interfacing. In this article, I list the general method of initializing the HSPI interface and initiating a data transfer (examples of 1-byte transmit and receive routines).

The HSPI architecture is fairly simple but very versatile. To initialize the interface, you may want to use the standard functions defined in the Espressif RTOS (or nonRTOS) SDK example. That is definitely a good starting point to set things to a working default state. After this, the typical way to initialize the interface to suit your own way is to modify the registers that actually effect the data transmission parameters such as the number of bytes sent, the polarity and phase of clock signal, etc.

In general, the HSPI interface of the ESP8266 can send data in a continuous block made of the following data blocks (in sequence):

  1. Command (max 2 bytes)
  2. Address (max 4 bytes)
  3. Dummy cycles
  4. Data (max 64 bytes)

The length of all these blocks are configurable to any bit number you want (BRILLIANT feature, really!). For example, you may want to send a byte of command + 4 byte parametes and a CRC byte (which is used in SD cards), you may do that using the HSPI hardware.

Initialize ESP8266 HSPI interface

Now that you have used the initialization function provided in the Espressif ESP8266 SPI example as follows:

You will now have a working configuration for the SPI interface, you may modify the following registers before initiating any data transfer:

  • SPI_USER: Enable/disable phases of SPI transfer (command, data, addr, dummy, etc), select normal/DIO/QIO modes, etc.
  • SPI_USER1: Bit length of address, data out, data in, dummy clocks.
  • SPI_USER2: Bit length and content of command phase
  • SPI_W0 – SPI_W15: Data buffer from where data bytes are sent out/received into.
  • SPI_CMD: Initiate a new transfer or check status of SPI hardware.

Here is an example waveform of what the data transfer looks like when done in bytes:

ESP8266 sd card interfacing data transfer in HSPI

Data transfer from HSPI port (MOSI and SCK signals)

In the next post on this topic, I will share the code used to write a byte or read a byte from the HSPI hardware. Soon, it will be going all the way to SD card interfacing and file read/write operations. However, I faced a strange problem of high current consumption in ESP-12 module on startup and also spurious reset of CH340 serial driver. Once that is sorted, I can try out some more ESP8266 SD card interfacing related code.

Please subscribe to stay updated on further development. 🙂
Feel free to leave your questions in comments below…

6 Responses

  1. Vinay

    Hi Pratik,

    Nice blog. I too have been working with spi on ESP8266EX. Any idea how to get rid of those 8 dead clock cycles between each spi transactions?. I need to do the following:
    1. CS Low
    2. Send a command byte
    3. Send 64 parameter bytes
    3. CS High
    With continuous clocks.

    • pratik

      You could do this by configuring command length of 8 bits and data buffer size of 64 bytes. Make sure you don’t add any dummy clock cycles!
      That’s the reason you get blank clocks I guess. Or perhaps you are trying to receive 8 bits in half-duplex mode.

  2. Vinay

    Thanks for the response. You are right, its because of half duplex comm. Meanwhile I have gone through the current SPI driver by Metalphreak and good information provided in this link: “http://www.esp8266.com/viewtopic.php?f=13&t=2413”. In the spi_register.h file, the value of SPI_USR_MOSI_BITLEN= 511 i.e. already set. And this change will reflect in the below line of code.

    SPI_USR_MOSI_BITLEN_S 17

    WRITE_PERI_REG(SPI_USER1(spi_no), ((7&SPI_USR_MOSI_BITLEN)<<SPI_USR_MOSI_BITLEN_S)|((7&SPI_USR_MISO_BITLEN)<<SPI_USR_MISO_BITLEN_S));

    Between any idea how does the above work?. From my analysis, 7&511 will give use 7. and left shifting 7 by 17 will give us "11100000000000000000" in binary. What does this mean?.

    • pratik

      It is just a protection mechanism. If you specify bit length as 7, it will and with 512 and generate 7. But if someone accidentally specified bit length as 600, which cannot be supported… it will be ANDed with 512 and the resulting value will be written to register so that it does not set any bits outside the range of those intended for defining length.
      For example, if bit 2,1,0 of a register define a parameter, you don’t want to write 1110 to that… because bit 3 will be accidentally set. So for safety AND it with 7… then answer will be 0110… bit 3 is not accidentally set.
      Classic embedded programming technique to prevent issues. 🙂

  3. hadipic

    /*
    * The MIT License (MIT)
    *
    * Copyright (c) 2015 David Ogilvy (MetalPhreak)
    *
    * 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.
    */
    #include “eagle_soc.h”
    #include “c_types.h”
    #include “gpio.h”

    #include “driver/spi.h”

    #define PERIPHS_IO_MUX 0x60000800
    #define PERIPHS_IO_MUX_FUNC 0x13
    #define PERIPHS_IO_MUX_FUNC_S 4
    #define PERIPHS_IO_MUX_PULLUP BIT7
    #define PERIPHS_IO_MUX_PULLUP2 BIT6
    #define PERIPHS_IO_MUX_SLEEP_PULLUP BIT3
    #define PERIPHS_IO_MUX_SLEEP_PULLUP2 BIT2
    #define PERIPHS_IO_MUX_SLEEP_OE BIT1
    #define PERIPHS_IO_MUX_OE BIT0

    #define PERIPHS_IO_MUX_CONF_U (PERIPHS_IO_MUX + 0x00)
    #define SPI0_CLK_EQU_SYS_CLK BIT8
    #define SPI1_CLK_EQU_SYS_CLK BIT9
    #define PERIPHS_IO_MUX_MTDI_U (PERIPHS_IO_MUX + 0x04)
    #define FUNC_GPIO12 3
    #define PERIPHS_IO_MUX_MTCK_U (PERIPHS_IO_MUX + 0x08)
    #define FUNC_GPIO13 3
    #define PERIPHS_IO_MUX_MTMS_U (PERIPHS_IO_MUX + 0x0C)
    #define FUNC_GPIO14 3
    #define PERIPHS_IO_MUX_MTDO_U (PERIPHS_IO_MUX + 0x10)
    #define FUNC_GPIO15 3
    #define FUNC_U0RTS 4
    #define PERIPHS_IO_MUX_U0RXD_U (PERIPHS_IO_MUX + 0x14)
    #define FUNC_GPIO3 3
    #define PERIPHS_IO_MUX_U0TXD_U (PERIPHS_IO_MUX + 0x18)
    #define FUNC_U0TXD 0
    #define FUNC_GPIO1 3
    #define PERIPHS_IO_MUX_SD_CLK_U (PERIPHS_IO_MUX + 0x1c)
    #define FUNC_SDCLK 0
    #define FUNC_SPICLK 1
    #define PERIPHS_IO_MUX_SD_DATA0_U (PERIPHS_IO_MUX + 0x20)
    #define FUNC_SDDATA0 0
    #define FUNC_SPIQ 1
    #define FUNC_U1TXD 4
    #define PERIPHS_IO_MUX_SD_DATA1_U (PERIPHS_IO_MUX + 0x24)
    #define FUNC_SDDATA1 0
    #define FUNC_SPID 1
    #define FUNC_U1RXD 4
    #define FUNC_SDDATA1_U1RXD 7
    #define PERIPHS_IO_MUX_SD_DATA2_U (PERIPHS_IO_MUX + 0x28)
    #define FUNC_SDDATA2 0
    #define FUNC_SPIHD 1
    #define FUNC_GPIO9 3
    #define PERIPHS_IO_MUX_SD_DATA3_U (PERIPHS_IO_MUX + 0x2c)
    #define FUNC_SDDATA3 0
    #define FUNC_SPIWP 1
    #define FUNC_GPIO10 3
    #define PERIPHS_IO_MUX_SD_CMD_U (PERIPHS_IO_MUX + 0x30)
    #define FUNC_SDCMD 0
    #define FUNC_SPICS0 1
    #define PERIPHS_IO_MUX_GPIO0_U (PERIPHS_IO_MUX + 0x34)
    #define FUNC_GPIO0 0
    #define PERIPHS_IO_MUX_GPIO2_U (PERIPHS_IO_MUX + 0x38)
    #define FUNC_GPIO2 0
    #define FUNC_U1TXD_BK 2
    #define FUNC_U0TXD_BK 4
    #define PERIPHS_IO_MUX_GPIO4_U (PERIPHS_IO_MUX + 0x3C)
    #define FUNC_GPIO4 0
    #define PERIPHS_IO_MUX_GPIO5_U (PERIPHS_IO_MUX + 0x40)
    #define FUNC_GPIO5 0

    #define PIN_PULLUP_DIS(PIN_NAME) CLEAR_PERI_REG_MASK(PIN_NAME, PERIPHS_IO_MUX_PULLUP)
    #define PIN_PULLUP_EN(PIN_NAME) SET_PERI_REG_MASK(PIN_NAME, PERIPHS_IO_MUX_PULLUP)

    #define PIN_FUNC_SELECT(PIN_NAME, FUNC) do { \
    WRITE_PERI_REG(PIN_NAME, \
    READ_PERI_REG(PIN_NAME) \
    & (~(PERIPHS_IO_MUX_FUNC<<PERIPHS_IO_MUX_FUNC_S)) \
    |( (((FUNC&BIT2)<<2)|(FUNC&0x3))<<PERIPHS_IO_MUX_FUNC_S) ); \
    } while (0)

    #define GPIO_SET(pin) gpio_output_set(1<<pin,0,1<<pin,0);
    #define GPIO_CLR(pin) gpio_output_set(0,1<<pin,1< 1) return; //Only SPI and HSPI are valid spi modules.
    //SPI
    spi_init_gpio(HSPI, SPI_CLK_USE_DIV);
    spi_clock(HSPI, SPI_CLK_PREDIV, SPI_CLK_CNTDIV);
    spi_tx_byte_order(HSPI, SPI_BYTE_ORDER_HIGH_TO_LOW);
    spi_rx_byte_order(HSPI, SPI_BYTE_ORDER_HIGH_TO_LOW);

    SET_PERI_REG_MASK(SPI_USER(HSPI), SPI_CS_SETUP|SPI_CS_HOLD);
    CLEAR_PERI_REG_MASK(SPI_USER(HSPI), SPI_FLASH_MODE);

    while (READ_PERI_REG(SPI_CMD(HSPI))&SPI_USR);
    valueOfRegisters |= SPI_USR_MOSI | SPI_DOUTDIN | SPI_CK_I_EDGE;
    //valueOfRegisters &= ~(BIT2 | SPI_USR_ADDR | SPI_USR_DUMMY | SPI_USR_MISO | SPI_USR_COMMAND); //clear bit 2 see example IoT_Demo
    WRITE_PERI_REG(SPI_USER(HSPI), valueOfRegisters);
    while (READ_PERI_REG(SPI_CMD(HSPI))&SPI_USR);

    }

    /*

    void ICACHE_FLASH_ATTR spi_init(void){

    uint32_t valueOfRegisters = 0;

    if(HSPI > 1) return; //Only SPI and HSPI are valid spi modules.
    //SPI
    spi_init_gpio(SPI, SPI_CLK_USE_DIV);
    spi_clock(SPI, SPI_CLK_PREDIV, SPI_CLK_CNTDIV);
    spi_tx_byte_order(SPI, SPI_BYTE_ORDER_HIGH_TO_LOW);
    spi_rx_byte_order(SPI, SPI_BYTE_ORDER_HIGH_TO_LOW);

    SET_PERI_REG_MASK(SPI_USER(SPI), SPI_CS_SETUP|SPI_CS_HOLD);
    CLEAR_PERI_REG_MASK(SPI_USER(SPI), SPI_FLASH_MODE);

    while (READ_PERI_REG(SPI_CMD(SPI))&SPI_USR);
    valueOfRegisters |= SPI_USR_MOSI | SPI_DOUTDIN | SPI_CK_I_EDGE;
    //valueOfRegisters &= ~(BIT2 | SPI_USR_ADDR | SPI_USR_DUMMY | SPI_USR_MISO | SPI_USR_COMMAND); //clear bit 2 see example IoT_Demo
    WRITE_PERI_REG(SPI_USER(SPI), valueOfRegisters);
    while (READ_PERI_REG(SPI_CMD(SPI))&SPI_USR);

    }

    */

    ///////////////
    ////////////////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////////////////
    //
    // Function Name: spi_init_gpio
    // Description: Initialises the GPIO pins for use as SPI pins.
    // Parameters: spi_no – SPI (0) or HSPI (1)
    // sysclk_as_spiclk – SPI_CLK_80MHZ_NODIV (1) if using 80MHz
    // sysclock for SPI clock.
    // SPI_CLK_USE_DIV (0) if using divider to
    // get lower SPI clock speed.
    //
    ////////////////////////////////////////////////////////////////////////////////

    void ICACHE_FLASH_ATTR spi_init_gpio(uint8 spi_no, uint8 sysclk_as_spiclk)
    {

    // if(spi_no > 1) return; //Not required. Valid spi_no is checked with if/elif below.

    uint32 clock_div_flag = 0;
    if(sysclk_as_spiclk){
    clock_div_flag = 0x0001;
    }

    if(spi_no==SPI){
    WRITE_PERI_REG(PERIPHS_IO_MUX, 0x005|(clock_div_flag<<8)); //Set bit 8 if 80MHz sysclock required
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_CLK_U, 1);
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_CMD_U, 1);
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA0_U, 1);
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA1_U, 1);
    }else if(spi_no==HSPI)
    {
    WRITE_PERI_REG(PERIPHS_IO_MUX, 0x105|(clock_div_flag< 1) return;

    if((prediv==0)|(cntdiv==0)){

    WRITE_PERI_REG(SPI_CLOCK(spi_no), SPI_CLK_EQU_SYSCLK);

    } else {

    WRITE_PERI_REG(SPI_CLOCK(spi_no),
    (((prediv-1)&SPI_CLKDIV_PRE)<<SPI_CLKDIV_PRE_S)|
    (((cntdiv-1)&SPI_CLKCNT_N)<>1)&SPI_CLKCNT_H)<<SPI_CLKCNT_H_S)|
    ((0&SPI_CLKCNT_L)< 1) return;

    if(byte_order){
    SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_WR_BYTE_ORDER);
    } else {
    CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_WR_BYTE_ORDER);
    }
    }
    ////////////////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////////////////
    //
    // Function Name: spi_rx_byte_order
    // Description: Setup the byte order for shifting data into buffer
    // Parameters: spi_no – SPI (0) or HSPI (1)
    // byte_order – SPI_BYTE_ORDER_HIGH_TO_LOW (1)
    // Data is read in starting with Bit31 and down to Bit0
    //
    // SPI_BYTE_ORDER_LOW_TO_HIGH (0)
    // Data is read in starting with the lowest BYTE, from
    // MSB to LSB, followed by the second lowest BYTE, from
    // MSB to LSB, followed by the second highest BYTE, from
    // MSB to LSB, followed by the highest BYTE, from MSB to LSB
    // 0xABCDEFGH would be read as 0xGHEFCDAB
    //
    //
    ////////////////////////////////////////////////////////////////////////////////

    void ICACHE_FLASH_ATTR spi_rx_byte_order(uint8 spi_no, uint8 byte_order){

    if(spi_no > 1) return;

    if(byte_order){
    SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_RD_BYTE_ORDER);
    } else {
    CLEAR_PERI_REG_MASK(SPI_USER(spi_no), SPI_RD_BYTE_ORDER);
    }
    }
    ////////////////////////////////////////////////////////////////////////////////

    uint8 spiwrite(uint8 c) {

    uint8 f;
    uint8_t state=0;
    /*
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, 2); //GPIO12 is HSPI MISO pin (Master Data In)
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 2); //GPIO13 is HSPI MOSI pin (Master Data Out)
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, 2); //GPIO14 is HSPI CLK pin (Clock)
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2);

    */

    //GPIO_OUTPUT_SET(CS_GPIO, state);
    //Set GPIO2 to LOW
    gpio_output_set(0, BIT2, BIT2, 0);
    while (READ_PERI_REG(SPI_CMD(HSPI))&SPI_USR); //waiting for spi module available
    WRITE_PERI_REG(SPI_USER1(HSPI), (7 & SPI_USR_MOSI_BITLEN) << SPI_USR_MOSI_BITLEN_S); // 8 bits
    WRITE_PERI_REG(SPI_W0(HSPI), (uint32)c); // the data to be sent
    SET_PERI_REG_MASK(SPI_CMD(HSPI), SPI_USR); // send
    while (READ_PERI_REG(SPI_CMD(HSPI))&SPI_USR);
    f=READ_PERI_REG(SPI_W0(HSPI));
    state=1;
    // GPIO_OUTPUT_SET(CS_GPIO, state);

    //Set GPIO2 to HIGH
    gpio_output_set(BIT2, 0, BIT2, 0);

    return f;

    }

    void cs_lcd(uint8 c)
    {

    // PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5);
    // gpio_output_set(5, c,1,0);//CS=0;

    if(c)
    {

    //Set GPIO5 to HIGH
    gpio_output_set(BIT5, 0, BIT5, 0);
    }
    else
    {
    //Set GPIO5 to LOW
    gpio_output_set(0, BIT5, BIT5, 0);
    }
    }

    void sclk_lcd(uint8 c)
    {

    //GPIO_OUTPUT_SET(14, c);
    gpio16_output_set(c);

    }

    void data_lcd(uint8 c)
    {

    // GPIO_OUTPUT_SET(13, c);

    if(c)
    {

    //Set GPIO13to HIGH
    gpio_output_set(BIT0, 0, BIT0, 0);
    }
    else
    {
    //Set GPIO13 to LOW
    gpio_output_set(0, BIT0, BIT0, 0);
    }

    }

    void rst_lcd(uint8 c)
    {
    //PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15);
    /*
    //GPIO_OUTPUT_SET(15, c);
    if(c)
    {

    //Set GPIO5 to HIGH
    gpio_output_set(BIT5, 0, BIT15, 0);
    }
    else
    {
    //Set GPIO5 to LOW
    gpio_output_set(0, BIT5, BIT15, 0);
    }

    */

    }

    void ICACHE_FLASH_ATTR gpio16_output_conf(void)
    {
    WRITE_PERI_REG(PAD_XPD_DCDC_CONF,
    (READ_PERI_REG(PAD_XPD_DCDC_CONF) & 0xffffffbc) | (uint32)0x1); // mux configuration for XPD_DCDC to output rtc_gpio0

    WRITE_PERI_REG(RTC_GPIO_CONF,
    (READ_PERI_REG(RTC_GPIO_CONF) & (uint32)0xfffffffe) | (uint32)0x0); //mux configuration for out enable

    WRITE_PERI_REG(RTC_GPIO_ENABLE,
    (READ_PERI_REG(RTC_GPIO_ENABLE) & (uint32)0xfffffffe) | (uint32)0x1); //out enable
    }

    void ICACHE_FLASH_ATTR gpio16_output_set(uint8 value)
    {
    WRITE_PERI_REG(RTC_GPIO_OUT,
    (READ_PERI_REG(RTC_GPIO_OUT) & (uint32)0xfffffffe) | (uint32)(value & 1));
    }

    void init_lcd_pin(void)
    {

    gpio16_output_conf();

    PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0);
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15);
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5);
    }

  4. hadipic

    /*———————————————————————–*/
    /* MMC/SDSC/SDHC (in SPI mode) control module for STM32 Version 1.1.6 */
    /* (C) Martin Thomas, 2010 – based on the AVR MMC module (C)ChaN, 2007 */
    /*———————————————————————–*/

    /* Copyright (c) 2016 hadi bashniji , Martin Thomas, ChaN
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the
    distribution.
    * Neither the name of the copyright holders nor the names of
    contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE. */

    #include “driver/ffconf.h”
    #include “driver/diskio.h”
    #include “driver/spi.h”

    //*===============================================================================================
    //* نîïèëèâàهى ىîنَëü
    //*===============================================================================================

    #define TRUE true
    #define FALSE false

    #define RAMFUNC

    BYTE esp_spi_rw( BYTE out);

    /* 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 CMD9 (0x40+9) /* SEND_CSD */
    #define CMD10 (0x40+10) /* SEND_CID */
    #define CMD12 (0x40+12) /* STOP_TRANSMISSION */
    #define ACMD13 (0xC0+13) /* SD_STATUS (SDC) */
    #define CMD16 (0x40+16) /* SET_BLOCKLEN */
    #define CMD17 (0x40+17) /* READ_SINGLE_BLOCK */
    #define CMD18 (0x40+18) /* READ_MULTIPLE_BLOCK */
    #define CMD23 (0x40+23) /* SET_BLOCK_COUNT (MMC) */
    #define ACMD23 (0xC0+23) /* SET_WR_BLK_ERASE_COUNT (SDC) */
    #define CMD24 (0x40+24) /* WRITE_BLOCK */
    #define CMD25 (0x40+25) /* WRITE_MULTIPLE_BLOCK */
    #define CMD55 (0x40+55) /* APP_CMD */
    #define CMD58 (0x40+58) /* READ_OCR */

    /* Card-Select Controls (Platform dependent) */

    #define SELECT() //CS_SELECT() /* MMC CS = L */
    #define DESELECT() //CS_DESELECT() /* MMC CS = H */

    /*————————————————————————–

    Module Private Functions and Variables

    —————————————————————————*/

    static volatile
    DSTATUS Stat = STA_NOINIT; /* Disk status */

    static volatile
    DWORD Timer1, Timer2; /* 100Hz decrement timers */

    static
    BYTE CardType; /* Card type flags */

    enum speed_setting { INTERFACE_SLOW, INTERFACE_FAST };

    /*———————————————————————–*/
    /* Transmit a byte to MMC via SPI (Platform dependent) */
    /*———————————————————————–*/

    #define xmit_spi(dat) esp_spi_rw(dat)

    /*———————————————————————–*/
    /* Receive a byte from MMC via SPI (Platform dependent) */
    /*———————————————————————–*/

    static
    BYTE rcvr_spi (void)
    {
    return esp_spi_rw(0xff);
    }

    /* Alternative macro to receive data fast */
    #define rcvr_spi_m(dst) *(dst)=esp_spi_rw(0xff)

    void power_on (void)
    {

    }
    void power_off (void)
    {

    }

    void interface_speed( u8 speed )
    {
    if ( speed == INTERFACE_SLOW ) {
    /* Set slow clock SPI HSPI*/
    spi_clock(HSPI, SPI_CLK_PREDIV, SPI_CLK_CNTDIV);
    } else {
    /* Set fast clock */
    // spi_clock(HSPI, H_SPEED_SPI_CLK_PREDIV, H_SPEED_SPI_CLK_CNTDIV);
    spi_clock(HSPI, 1, 2);

    }
    }

    BYTE esp_spi_rw( BYTE out)
    {
    return (BYTE) spiwrite((uint8) out ) ;
    }

    /*———————————————————————–*/
    /* Wait for card ready */
    /*———————————————————————–*/

    static
    BYTE wait_ready (void)
    {
    BYTE res;
    Timer2 = 50; /* Wait for ready in timeout of 500ms */
    rcvr_spi();
    do
    res = rcvr_spi();
    while ((res != 0xFF) && Timer2);
    return res;
    }

    /*———————————————————————–*/
    /* Deselect the card and release SPI bus */
    /*———————————————————————–*/

    static
    void release_spi (void)
    {
    rcvr_spi();
    }

    /*———————————————————————–*/
    /* Receive a data packet from MMC */
    /*———————————————————————–*/

    static
    BOOL rcvr_datablock (
    BYTE *buff, /* Data buffer to store received data */
    UINT btr /* Byte count (must be multiple of 4) */
    )
    {
    BYTE token;

    Timer1 = 10;
    do { /* Wait for data packet in timeout of 100ms */
    token = rcvr_spi();
    } while ((token == 0xFF) && Timer1);
    if(token != 0xFE) return FALSE; /* If not valid data token, return with error */

    do { /* Receive the data block into buffer */
    rcvr_spi_m(buff++);
    rcvr_spi_m(buff++);
    rcvr_spi_m(buff++);
    rcvr_spi_m(buff++);
    } while (btr -= 4);
    rcvr_spi(); /* Discard CRC */
    rcvr_spi();
    return TRUE; /* Return with success */
    }

    /*———————————————————————–*/
    /* Send a command packet to MMC */
    /*———————————————————————–*/

    static
    BYTE send_cmd (
    BYTE cmd, /* Command byte */
    DWORD arg /* Argument */
    )
    {
    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 and wait for ready */
    DESELECT();
    SELECT();
    if (wait_ready() != 0xFF) {
    return 0xFF;
    }

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

    /* Receive command response */
    if (cmd == CMD12) rcvr_spi(); /* Skip a stuff byte when stop reading */

    n = 10; /* Wait for a valid response in timeout of 10 attempts */
    do
    res = rcvr_spi();
    while ((res & 0x80) && –n);

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

    /*———————————————————————–*/
    /* Initialize Disk Drive */
    /*———————————————————————–*/

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

    if (drv) return STA_NOINIT; /* Supports only single drive */

    power_on(); /* Force socket power on and initialize interface */
    interface_speed(INTERFACE_SLOW);
    for (n = 10; n; n–) rcvr_spi(); /* 80 dummy clocks */

    ty = 0;
    if (send_cmd(CMD0, 0) == 1) { /* Enter Idle state */
    Timer1 = 100; /* Initialization timeout of 1000 milliseconds */
    if (send_cmd(CMD8, 0x1AA) == 1) { /* SDHC */
    for (n = 0; n < 4; n++) ocr[n] = rcvr_spi(); /* Get trailing return value of R7 response */
    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] = rcvr_spi();
    ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
    }
    }
    } else { /* SDSC or MMC */
    if (send_cmd(ACMD41, 0) > 6) == 1) { /* SDC version 2.00 */
    csize = csd[9] + ((WORD)csd[8] << 8) + 1;
    *(DWORD*)buff = (DWORD)csize <> 7) + ((csd[9] & 3) <> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
    *(DWORD*)buff = (DWORD)csize << (n – 9);
    }
    res = RES_OK;
    }
    break;

    case GET_SECTOR_SIZE : /* Get R/W sector size (WORD) */
    *(WORD*)buff = 512;
    res = RES_OK;
    break;

    case GET_BLOCK_SIZE : /* Get erase block size in unit of sector (DWORD) */
    if (CardType & CT_SD2) { /* SDC version 2.00 */
    if (send_cmd(ACMD13, 0) == 0) { /* Read SD status */
    rcvr_spi();
    if (rcvr_datablock(csd, 16)) { /* Read partial block */
    for (n = 64 – 16; n; n–) rcvr_spi(); /* Purge trailing data */
    *(DWORD*)buff = 16UL <> 4);
    res = RES_OK;
    }
    }
    } else { /* SDC version 1.XX or MMC */
    if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) { /* Read CSD */
    if (CardType & CT_SD1) { /* SDC version 1.XX */
    *(DWORD*)buff = (((csd[10] & 63) <> 7) + 1) <> 6) – 1);
    } else { /* MMC */
    *(DWORD*)buff = ((WORD)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) <> 5) + 1);
    }
    res = RES_OK;
    }
    }
    break;

    case MMC_GET_TYPE : /* Get card type flags (1 byte) */
    *ptr = CardType;
    res = RES_OK;
    break;

    case MMC_GET_CSD : /* Receive CSD as a data block (16 bytes) */
    if (send_cmd(CMD9, 0) == 0 /* READ_CSD */
    && rcvr_datablock(ptr, 16))
    res = RES_OK;
    break;

    case MMC_GET_CID : /* Receive CID as a data block (16 bytes) */
    if (send_cmd(CMD10, 0) == 0 /* READ_CID */
    && rcvr_datablock(ptr, 16))
    res = RES_OK;
    break;

    case MMC_GET_OCR : /* Receive OCR as an R3 resp (4 bytes) */
    if (send_cmd(CMD58, 0) == 0) { /* READ_OCR */
    for (n = 4; n; n–) *ptr++ = rcvr_spi();
    res = RES_OK;
    }
    break;

    case MMC_GET_SDSTAT : /* Receive SD status as a data block (64 bytes) */
    if (send_cmd(ACMD13, 0) == 0) { /* SD_STATUS */
    rcvr_spi();
    if (rcvr_datablock(ptr, 64))
    res = RES_OK;
    }
    break;

    default:
    res = RES_PARERR;
    }

    release_spi();

    return res;
    return 0;
    }

    /*———————————————————————–*/
    /* Device Timer Interrupt Procedure (Platform dependent) */
    /*———————————————————————–*/
    /* This function must be called in period of 10ms */

    RAMFUNC void disk_timerproc (void)
    {
    static DWORD pv;
    DWORD ns;
    BYTE n, s;

    n = Timer1; /* 100Hz decrement timers */
    if (n) Timer1 = –n;
    n = Timer2;
    if (n) Timer2 = –n;

    }

Leave a Reply