I started ESP8266 project, the first step was to support SDMMC over SPI. I have implemented SDMMC SPI interface years ago. The main challenge was to adapt it to new MCU core. After many attempts, it was up and running. Below are lessons learned.
{multithumb}All in all, the main aim was to work-around the way, ESP HSPI treats MOSI level. It is 0 when inactive, i.e. all the time we reading SPI packets, slave receives 00s. No SDMMCs I know start communicating under these conditions. They expect high MOSI, as shown on picture below, taken from the same SDMMC code working on STM32 platform.
While, if used as-is, ESP8266 HSPI demonstrates the following MOSI behavior:
What we have to do to work-around this, is to unconfigure MOSI function to GPIO13 output, while reading SPI, and set it to 1.
While, if we got to write to SPI, we heve to re-configure GPIO13 back to MOSI, and proceed as usual. After that, SDMMC starts answering, and initialization finally completes OK.
Below are excerpt files from the working SDMMC project.
After initialization code is successful (SDMMC is "open") we may use our storage elsewhere, for instance in famous ELM FAT FS library calls.
SDMMC over SPI C source open-close sample
// ESP includes
#include <esp_common.h>
#include <gpio.h>
#include <spi_interface.h>
#include "retarget.h"
// Our ESFWXE tuneups
#include "sdmmc_spiIntf.h"
// ESFWXE Drivers
#include <esfwxe/drivers/flash/sdmmc_spi.h>
// Application-specific SDMMC driver pieces implementation
static SpiAttr s_spi;
static SdmmcInfo s_sdmmc;
spiHANDLE sdmmcSpiBusGet(void)
{
return &s_spi;
}
SdmmcInfo* sdmmcInfoGet(void)
{
return &s_sdmmc;
}
static void sdmmcInfoReset(void)
{
memset(
&s_sdmmc,
0,
SdmmcInfo_SZE
);
}
esBL sdmmcOpen(void)
{
ES_DEBUG_TRACE("sdmmcOpen...\n");
// reconfigure spi to low speed and open it
spiConfig(
&s_spi,
sdmmcInitFreq
);
sdmmcPowerOn(
&s_spi,
&s_sdmmc,
TRUE
);
if(
sdmmcInit(
&s_spi,
&s_sdmmc,
3300, //< SDMMC expected VCC voltage
TRUE // Use SDMMC CRC
) &&
sdmmcOk == (s_sdmmc.flags & sdmmcOk) &&
sdmmcVoltageMismatch != (s_sdmmc.flags & sdmmcVoltageMismatch)
)
{
// Set full-speed spi
spiConfig(
&s_spi,
20000000
);
// Recalculate sdmmc timings for general 500ms timeout
sdmmcCalcIoTimings(
&s_spi,
&s_sdmmc,
500
);
ES_DEBUG_TRACE("...OK\n");
return TRUE;
}
sdmmcPowerOn(
&s_spi,
&s_sdmmc,
FALSE
);
ES_DEBUG_TRACE("...NOK\n");
return FALSE;
}
void sdmmcClose(void)
{
ES_DEBUG_TRACE("sdmmcClose...\n");
sdmmcPowerOn(
&s_spi,
&s_sdmmc,
FALSE
);
sdmmcInfoReset();
ES_DEBUG_TRACE("...OK\n");
}
void sdmmcHwInit(void)
{
ES_DEBUG_TRACE("sdmmcHwInit...\n");
sdmmcInfoReset();
sdmmcSpiInit(&s_spi);
ES_DEBUG_TRACE("...OK\n");
}
SDMMC over SPI generic API C source
#include <esfwxe/target.h>
#pragma hdrstop
#include <string.h>
#include <esfwxe/utils.h>
#include <esfwxe/crc.h>
#ifndef USE_CUSTOM_SPI
# include <esfwxe/core/spi.h>
#endif
#include <esfwxe/drivers/flash/sdmmc_spi.h>
// SDMMC over SPI driver implementation
//
// 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 CMD13 (0x40+13) // SD_STATUS
#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 CMD28 (0x40+28) // SET_WRITE_PROT
#define CMD29 (0x40+29) // CLR_WRITE_PROT
#define CMD32 (0x40+32) // ERASE_WR_BLK_START_ADDR
#define CMD33 (0x40+33) // ERASE_WR_BLK_END_ADDR
#define CMD38 (0x40+38) // ERASE
#define CMD55 (0x40+55) // APP_CMD
#define CMD58 (0x40+58) // READ_OCR
#define CMD59 (0x40+59) // CRC_ON_OFF
// command response masks
//
// R1, R2, R3 LSB masks
//
#define RM_IN_IDLE 0x01 // In Idle State
#define RM_ERASE_RST 0x02 // Erase Reset
#define RM_ILLEGAL_CMD 0x04 // Illegal Command
#define RM_CRC_ERROR 0x08 // CRC Error
#define RM_ERASE_SEQ_ERR 0x10 // Erase Sequence Error
#define RM_ADDR_ERR 0x20 // Address Error
#define RM_PARAM_ERR 0x40 // Parameter Error
// R2, R3 MSB masks
//
#define RM_CARD_LOCKED 0x01 // Card Locked
#define RM_WRPROT_ERASE_SKIP 0x02 // Write Protect Erase Skip
#define RM_LOCK_ULOCK_FAILED RM_WRPROT_ERASE_SKIP // Lock/Unlock Failed
#define RM_UNSPECIFIED_ERROR 0x04 // Unspecified Error
#define RM_CARD_CTLR_ERROR 0x08 // Card Controller Error
#define RM_CARD_ECC__FAILED 0x10 // Card ECC Failed
#define RM_WRPROT_VIOLATION 0x20 // Write Protect Violation
#define RM_ERASE_PARAM 0x40 // Erase Parameter
#define RM_OUT_OF_RANGE 0x80 // Out of Range
#define RM_CSD_OVERWRITE RM_OUT_OF_RANGE // CSD Overwrite
// read data error token masks
//
#define RET_UNSPECIFIED_ERROR 0x01 // Unspecified Error
#define RET_CARD_CTLR_ERROR 0x02 // Card Controller Error
#define RET_CARD_ECC_FAILED 0x04 // Card ECC Failed
#define RET_OUT_OF_RANGE 0x08 // Out of Range
#define RET_CARD_LOCKED 0x10 // Card Locked
// block data start|stop tokens
#define DT_RBLOCK_START 0xFE
#define DT_RBLOCK_MULTI_START DT_RBLOCK_START
#define DT_WBLOCK_START DT_RBLOCK_START
#define DT_WBLOCK_MULTI_START 0xFC
#define DT_WBLOCK_MULTI_STOP 0xFD
// data write result token analysis
//
#define DWRT_EXTRACT(r) ((r) & 0x1F) // extract data write token value from byte response
// write token values
//
#define DWRT_AOK 0x05 // data write accepted
#define DWRT_CRC_ERROR 0x0B // data write was rejected due to CRC error
#define DWRT_WRITE_ERROR 0x0D // data write was rejected due to write error
// length of command packet
#define CMD_PACKET_LEN 6
#if defined( ES_USE_SDMMC_DEBUG_TRACE ) && defined( ES_DEBUG_TRACE )
# define ES_SDMMC_TRACE ES_DEBUG_TRACE
#else
# define ES_SDMMC_TRACE(...) ((void)0)
#endif
// response types
//
typedef enum {
sdmmcR1,
sdmmcR1b,
sdmmcR2,
sdmmcR3,
sdmmcR7,
// special const - responses count, must go last
sdmmcRcnt
} sdmmcResponse;
// response data sizes (additional bytes after the first response byte)
static const esU8 c_sdmmcResponseSize[sdmmcRcnt] = {
0, // r1
0, // r1b
1, // r2
4, // r3
4 // r7
};
// internal sdmmc io buffer
static esU8 s_sdmmcBuff[16];
// sdmmc internal helper functions
//
// convert response masks to abstract sdmmc status mask
static __inline void sdmmcConvertR1toStatus(esU8 r1, SdmmcInfo* info)
{
info->stat = r1;
}
static __inline void sdmmcConvertR2toStatus(esU8 r1, esU8 r2, SdmmcInfo* info)
{
sdmmcConvertR1toStatus(r1, info);
info->stat |= ((esU16)r2) << 7;
}
// wait until bus becomes ready (DO idles to high state)
esBL sdmmcWaitReady(spiHANDLE h, SdmmcInfo* info)
{
esU32 retries = 0;
do
{
spiGetBytes(h, s_sdmmcBuff, 1);
} while(
s_sdmmcBuff[0] != 0xFF &&
++retries < info->ioRetries );
if( retries < info->ioRetries )
{
return TRUE;
}
else
{
info->stat |= sdmmcWaitReadyExpired;
ES_SDMMC_TRACE("...Failed to wait until SDMMC is ready\n");
return FALSE;
}
}
// receive command response block
static esBL sdmmcGetResponse(spiHANDLE h, SdmmcInfo* info, sdmmcResponse r )
{
// wait for response's first byte
// get command response
esU32 retries = 0;
do
{
spiGetBytes(h, s_sdmmcBuff, 1);
} while( (s_sdmmcBuff[0] & 0x80) &&
++retries < info->ioRetries );
// retries not expired and additional bytes needed
if( retries < info->ioRetries )
{
// get the rest of response packet
if( c_sdmmcResponseSize[r] )
spiGetBytes(h, s_sdmmcBuff+1, c_sdmmcResponseSize[r]);
// convert responses to universal status field
if( sdmmcR2 == r )
sdmmcConvertR2toStatus(s_sdmmcBuff[0], s_sdmmcBuff[1], info);
else
sdmmcConvertR1toStatus(s_sdmmcBuff[0], info);
return TRUE;
}
return FALSE;
}
// return response type for specified command value
static __inline sdmmcResponse sdmmcGetResponseTypeForCmd(esU8 cmd)
{
switch(cmd)
{
case CMD8:
return sdmmcR7;
case CMD12:
case CMD28:
case CMD29:
case CMD38:
return sdmmcR1b;
case CMD58:
return sdmmcR3;
case CMD13:
return sdmmcR2;
}
return sdmmcR1;
}
// send single command packet to card and get response to it
static esBL sdmmcSendCmdInternal(spiHANDLE h, SdmmcInfo* info, esU8 cmd, esU32 arg)
{
const esU8* argpos = (const esU8*)&arg + 3;
ES_SDMMC_TRACE("...sending CMD%d", (int)cmd-0x40);
// wait until card becomes ready
if(
sdmmcWaitReady(
h,
info
)
)
{
// pack command + argument + crc
s_sdmmcBuff[0] = cmd;
s_sdmmcBuff[1] = *argpos--;
s_sdmmcBuff[2] = *argpos--;
s_sdmmcBuff[3] = *argpos--;
s_sdmmcBuff[4] = *argpos--;
// default to single stop bit if no crc support is active
s_sdmmcBuff[5] = 1;
// finalize packet with left-aligned crc7 + stop bit
// use precalculated crcs for CMD0 and CMD8 commands with known contents
if( CMD0 == cmd )
s_sdmmcBuff[5] = 0x95;
else if( CMD8 == cmd ) // for 0x1AA argument
s_sdmmcBuff[5] = 0x87;
else if( info->flags & sdmmcUseCrc )
s_sdmmcBuff[5] = (crc7(0, s_sdmmcBuff, 5) << 1) + 1;
// send command packet
spiPutBytes(
h,
s_sdmmcBuff,
CMD_PACKET_LEN
);
// if stop reading command issued, skip one dummy byte
if(CMD12 == cmd)
spiGetBytes(
h,
s_sdmmcBuff,
1
);
#ifdef DEBUG
if(
!sdmmcGetResponse(
h,
info,
sdmmcGetResponseTypeForCmd(cmd)
)
)
{
ES_SDMMC_TRACE("...Failed to get response to command\n");
return FALSE;
}
ES_SDMMC_TRACE("...OK\n");
return TRUE;
#else
return sdmmcGetResponse(
h,
info,
sdmmcGetResponseTypeForCmd(cmd)
);
#endif
}
return FALSE;
}
static __inline esBL sdmmcSendCmd(spiHANDLE h, SdmmcInfo* info, esU8 cmd, esU32 arg)
{
esBL result;
sdmmcSELECT;
if( cmd & 0x80 ) // handle ACMDs
result = sdmmcSendCmdInternal(h, info, CMD55, 0) &&
sdmmcSendCmdInternal(h, info, cmd & 0x7F, arg);
else
result = sdmmcSendCmdInternal(h, info, cmd, arg);
sdmmcDESELECT;
return result;
}
static esBL sdmmcEnterIdle(spiHANDLE h, SdmmcInfo* info, esBL useCrc)
{
sdmmcDESELECT;
// wait for >= 74 spi bus clocks with CS and DI set to high state
memset(
s_sdmmcBuff,
0xFFFFFFFF,
sizeof(s_sdmmcBuff)
);
spiPutBytes(h, s_sdmmcBuff, 12);
// issue CMD0 && check response
if( sdmmcSendCmd(h, info, CMD0, 0) &&
sdmmcStatIdle == info->stat )
{
// set primary initialized flag
info->flags = sdmmcOk;
// activate CRC support, if required
if( useCrc &&
sdmmcSendCmd(h, info, CMD59, 1) &&
sdmmcStatIdle == info->stat
)
info->flags |= sdmmcUseCrc;
return TRUE;
}
return FALSE;
}
// read OCR and check voltage mask
static esBL sdmmcCheckVoltageMask(spiHANDLE h, SdmmcInfo* info, esU32 mask)
{
// read OCR
if( sdmmcSendCmd(h, info, CMD58, 0) &&
( sdmmcStatAOK == info->stat ||
sdmmcStatIdle == info->stat ) )
{
if( (s_sdmmcBuff[2] & ((mask >> 16) & 0xFF)) ||
(s_sdmmcBuff[3] & ((mask >> 8) & 0xFF)) )
return TRUE;
else
info->flags |= sdmmcVoltageMismatch;
}
return FALSE;
}
static esBL sdmmcCheckSd1(spiHANDLE h, SdmmcInfo* info, esU32 mask)
{
esU32 retries = info->ioRetries;
while( retries-- )
{
if( !sdmmcSendCmd(h, info, ACMD41, 0) )
break;
if( sdmmcStatAOK == info->stat )
{
info->type = sdmmcSd1;
return sdmmcCheckVoltageMask(h, info, mask);
}
else if( sdmmcStatIdle != info->stat )
break;
}
return FALSE;
}
static esBL sdmmcCheckMmc3(spiHANDLE h, SdmmcInfo* info, esU32 mask)
{
esU32 tries = info->ioRetries;
while( tries-- )
{
if( !sdmmcSendCmd(h, info, CMD1, 0) )
break;
if( sdmmcStatAOK == info->stat )
{
info->type = sdmmcMmc3;
return sdmmcCheckVoltageMask(h, info, mask);
}
else if( sdmmcStatIdle != info->stat )
break;
}
return FALSE;
}
static esBL sdmmcCheckSd2(spiHANDLE h, SdmmcInfo* info, esU32 mask)
{
esBL result = FALSE;
esU32 tries = info->ioRetries;
// wait until exit card idle state, continuously sending ACMD41 with high capacity bit set
while( tries-- )
{
if( !sdmmcSendCmd(h, info, ACMD41, 0x40000000) )
break;
if( sdmmcStatAOK == info->stat )
{
info->type = sdmmcSd2;
result = sdmmcCheckVoltageMask(h, info, mask);
// check high capacity bit
if( result && (s_sdmmcBuff[1] & 0x40) )
info->flags |= sdmmcHighCapacity;
break;
}
else if( sdmmcStatIdle != info->stat )
break;
}
return result;
}
// LV range voltages are currently not supported
// voltage is in millivolts
static __inline esU32 sdmmcMakeVoltageMask(esU16 v)
{
esU32 result = 0;
if( v >= 2700 &&
v <= 2800 )
result |= (1 << 15);
if( v >= 2800 &&
v <= 2900 )
result |= (1 << 16);
if( v >= 2900 &&
v <= 3000 )
result |= (1 << 17);
if( v >= 3000 &&
v <= 3100 )
result |= (1 << 18);
if( v >= 3100 &&
v <= 3200 )
result |= (1 << 19);
if( v >= 3200 &&
v <= 3300 )
result |= (1 << 20);
if( v >= 3300 &&
v <= 3400 )
result |= (1 << 21);
if( v >= 3400 &&
v <= 3500 )
result |= (1 << 22);
if( v >= 3500 &&
v <= 3600 )
result |= (1 << 23);
return result;
}
static esBL sdmmcCheckCardSupport(spiHANDLE h, SdmmcInfo* info, esU16 v)
{
esBL result = FALSE;
esU32 vMask = sdmmcMakeVoltageMask(v);
// send CMD8 with proper CRC to check if card is sd2 and host voltage is supported
if( !sdmmcSendCmd(h, info, CMD8, 0x01AA) ||
(sdmmcStatIllegalCmd & info->stat) )
// error or no response - try sd1 or mmc3
result = sdmmcCheckSd1(h, info, vMask) ||
sdmmcCheckMmc3(h, info, vMask);
// check if voltage and bit pattern match
else if( s_sdmmcBuff[3] == 0x01 &&
s_sdmmcBuff[4] == 0xAA )
result = sdmmcCheckSd2(h, info, vMask);
return result;
}
static esBL sdmmcReadDataPacket(spiHANDLE h, SdmmcInfo* info, esU8 dataToken, esU8* data, esU32 len)
{
esU32 retries = 0;
esBL result = FALSE;
sdmmcSELECT;
// skip until data token is read
do
{
spiGetBytes(h, data, 1);
} while( 0xFF == *data &&
++retries < info->ioRetries );
// read actual data, if retries not expired,
// read data block crc at the end of operation
if( retries < info->ioRetries )
{
if( DT_RBLOCK_START == *data )
{
esU16 crc;
result = len == spiGetBytes(h, data, len) &&
2 == spiGetBytes(h, (esU8*)&crc, 2);
if( result && (info->flags & sdmmcUseCrc) )
{
result = SWAPB_WORD(crc) == crc16ccitt(0, data, len);
if( !result ) // set status bit specifying we get corrupt data read
info->stat |= sdmmcReadCrcError;
}
}
else // set data error token bits to universal status format
info->stat = ((esU16)*data) << 7;
}
sdmmcDESELECT;
return result;
}
static esBL sdmmcConfigureAddressing(spiHANDLE h, SdmmcInfo* info)
{
if( info->type != sdmmcUnknown &&
sdmmcSendCmd(h, info, CMD9, 0) &&
sdmmcStatAOK == info->stat &&
// read CSD block
sdmmcReadDataPacket(h, info, DT_RBLOCK_START, s_sdmmcBuff, 16) )
{
// adjust CRC7
s_sdmmcBuff[15] >>= 1;
// check CRC
if( s_sdmmcBuff[15] == crc7(0, s_sdmmcBuff, 15) )
{
if( info->flags & sdmmcHighCapacity )
{
// CSD V2.0
info->blockCnt = (((esU32)(s_sdmmcBuff[7] & 0x3F) << 16) +
((esU32)s_sdmmcBuff[8] << 8) + (esU32)s_sdmmcBuff[9] + 1);
info->blockCnt <<= 10;
info->userBlockSize = info->blockSize = 512;
info->flags |= sdmmcBlockAddr;
return TRUE;
}
else
{
// CSD V1.0
info->blockCnt = (((esU32)(s_sdmmcBuff[6] & 0x03) << 10) +
((esU32)s_sdmmcBuff[7] << 2) + ((esU32)(s_sdmmcBuff[8] & 0xC0) >> 6) + 1) *
(1 << ((((esU32)s_sdmmcBuff[9] & 0x03) << 1) +
(((esU32)s_sdmmcBuff[10] & 0x80) >> 7) + 2));
info->blockSize = 1 << (esU32)(s_sdmmcBuff[5] & 0x0F);
if( !(s_sdmmcBuff[10] & 0x40) )
info->flags |= sdmmcSectorEraseUnit;
// V1 cards allow partial and misaligned data access by-default
// to eliminate performance flaw, as well as card wearing, we should
// always configure 512 user block access for such cards
if( sdmmcSendCmd(h, info, CMD16, 512) &&
sdmmcStatAOK == info->stat )
{
info->userBlockSize = 512;
return TRUE;
}
}
// get transpeed capability
}
}
return FALSE;
}
// initialize sdmmc card access. v is host-supplied voltage in millivolts
// i.e. 3.6v = 3600
esBL sdmmcInit(spiHANDLE h, SdmmcInfo* info, esU16 v, esBL useCrc)
{
if( !info )
return FALSE;
// initialize sdmmc structure to all 0s
memset(info, 0, SdmmcInfo_SZE);
// set some initial retries for 1s expected timeout
sdmmcCalcIoTimings(h, info, 1000);
if( INVALID_HANDLE == h )
return FALSE;
return sdmmcEnterIdle(h, info, useCrc) &&
sdmmcCheckCardSupport(h, info, v) &&
sdmmcConfigureAddressing(h, info);
}
// sd card block io. returned is count of blocks actually read|written
esU32 sdmmcBlocksRead(spiHANDLE h, SdmmcInfo* info, esU32 startBlock, esU8* blocks, esU32 count)
{
esU32 result = 0;
if( spiIsOpen(h) &&
info &&
sdmmcUnknown != info->type &&
blocks &&
count )
{
// if 1 == count issue single block read command
// else, fetch multiple blocks, terminated with CMD21
if( 1 < count )
{
if( sdmmcSendCmd(h, info, CMD18, (info->flags & sdmmcBlockAddr) ? startBlock : startBlock*info->userBlockSize ) &&
sdmmcStatAOK == info->stat )
{
while( result < count )
{
if( !sdmmcReadDataPacket(h, info, DT_RBLOCK_MULTI_START, blocks, info->userBlockSize) )
break;
blocks += info->userBlockSize;
++result;
}
sdmmcSendCmd(h, info, CMD12, 0); // break multiread
}
}
else if( sdmmcSendCmd(h, info, CMD17, (info->flags & sdmmcBlockAddr) ? startBlock : startBlock*info->userBlockSize ) &&
sdmmcStatAOK == info->stat &&
sdmmcReadDataPacket(h, info, DT_RBLOCK_START, blocks, info->userBlockSize) )
result = 1;
sdmmcReleaseSpiBus(h);
}
return result;
}
static esBL sdmmcWriteDataPacket( spiHANDLE h, SdmmcInfo* info, esU8 token, const esU8 *buff, esU32 len )
{
esBL result = FALSE;
sdmmcSELECT;
if( sdmmcWaitReady(h, info) &&
1 == spiPutBytes(h, &token, 1) ) // send token first
{
// if not stoptran token, send data block as well
if(DT_WBLOCK_MULTI_STOP != token)
{
// calc ccitt CRC16 & prepare it for sd (swap bytes)
esU16 crc = 0;
if( info->flags & sdmmcUseCrc )
{
crc = crc16ccitt(0, buff, len);
crc = SWAPB_WORD(crc);
}
// send data + crc
if( len == spiPutBytes(h, buff, len) &&
2 == spiPutBytes(h, (const esU8*)&crc, 2) &&
1 == spiGetBytes(h, s_sdmmcBuff, 1) ) // receive block write response
{
// decypher write response
switch( DWRT_EXTRACT(s_sdmmcBuff[0]) )
{
case DWRT_CRC_ERROR:
info->stat |= sdmmcStatWriteCrcError;
break;
case DWRT_WRITE_ERROR:
info->stat |= sdmmcStatWriteError;
break;
case DWRT_AOK:
result = TRUE;
break;
}
}
}
}
sdmmcDESELECT;
return result;
}
esU32 sdmmcBlocksWrite(spiHANDLE h, SdmmcInfo* info, esU32 startBlock, const esU8* blocks, esU32 count)
{
esU32 result = 0;
if( spiIsOpen(h) &&
info &&
sdmmcUnknown != info->type &&
blocks &&
count )
{
// if 1 == count issue single block write command
// else, write multiple blocks, terminated with CMD21
if( 1 < count )
{
esBL ok;
// inform card controller about count of blocks to be written, then start multiblock write operation
if( info->type == sdmmcMmc3 )
ok = sdmmcSendCmd(h, info, CMD23, count) &&
sdmmcStatAOK == info->stat;
else
ok = sdmmcSendCmd(h, info, ACMD23, count) &&
sdmmcStatAOK == info->stat;
if( ok &&
sdmmcSendCmd(h, info, CMD25, (info->flags & sdmmcBlockAddr) ? startBlock : startBlock*info->userBlockSize ) &&
sdmmcStatAOK == info->stat )
{
while( result < count )
{
if( !sdmmcWriteDataPacket(h, info, DT_WBLOCK_MULTI_START, blocks, info->userBlockSize) )
break;
blocks += info->userBlockSize;
++result;
}
sdmmcWriteDataPacket( h, info, DT_WBLOCK_MULTI_STOP, 0, 0 ); // break multiwrite
}
}
else if( sdmmcSendCmd(h, info, CMD24, (info->flags & sdmmcBlockAddr) ? startBlock : startBlock*info->userBlockSize ) &&
sdmmcStatAOK == info->stat &&
sdmmcWriteDataPacket(h, info, DT_WBLOCK_START, blocks, info->userBlockSize) )
result = 1;
// release spi bus as well as add stuff byte (needed) after write operations
sdmmcReleaseSpiBus(h);
// analyse card status if something gone wrong, and we cannot deduce the reason from existing status info
// R2 will be automatically parsed into universal sdmmc status during this request
if( result != count &&
!(info->stat & (sdmmcWaitReadyExpired|sdmmcStatWriteCrcError|sdmmcStatWriteError)) )
sdmmcSendCmd(h, info, CMD13, 0);
}
return result;
}
// sd spi compatibility bus release call. sd releases bus synchronously with sck pulses, not cs,
// so we have to do 1 dummy bus read with cs deasserted to release spi properly (avoiding multislave conflicts)
void sdmmcReleaseSpiBus(spiHANDLE h)
{
sdmmcDESELECT;
spiGetBytes(h, s_sdmmcBuff, 1);
}
// calculate card io retries in accordance with bus settings and requested timeout value in ms
void sdmmcCalcIoTimings(spiHANDLE h, SdmmcInfo* info, esU32 tmo)
{
if( h &&
info &&
tmo )
{
#ifndef USE_CUSTOM_SPI
spiDCB dcb;
spiGetDCB(h, &dcb);
info->ioRetries = MAX((esU32)sdmmcIoInitialRetries, (tmo * dcb.freqHz) / (esU32)10000 );
#else
info->ioRetries = MAX((esU32)sdmmcIoInitialRetries, (tmo * spiRateGet(h)) / (esU32)10000 );
#endif
}
}
// sd card block erase. returned is amount of erased blocks
esU32 sdmmcBlocksErase(spiHANDLE h, SdmmcInfo* info, esU32 startBlock, esU32 count)
{
esU32 result = 0;
if( h &&
info &&
sdmmcUnknown != info->type &&
count )
{
if( sdmmcSendCmd(h, info, CMD32, (info->flags & sdmmcBlockAddr) ? startBlock : startBlock*info->userBlockSize) &&
sdmmcStatAOK == info->stat &&
sdmmcSendCmd(h, info, CMD33, (info->flags & sdmmcBlockAddr) ? startBlock+count-1 : (startBlock+count-1)*info->userBlockSize) &&
sdmmcStatAOK == info->stat &&
sdmmcSendCmd(h, info, CMD38, 0) &&
sdmmcStatAOK == info->stat )
result = count;
}
return result;
}
// retrieve card id
void sdmmcCardIdGet(spiHANDLE h, SdmmcInfo* info, SdmmcCID cid)
{
if( h &&
info &&
sdmmcUnknown != info->type )
{
if( sdmmcSendCmd(h, info, CMD10, 0) &&
sdmmcStatAOK == info->stat )
sdmmcReadDataPacket(h, info, DT_RBLOCK_START, cid, sizeof(SdmmcCID));
}
}
SDMMC over SPI generic API C header
#ifndef _sd_mmc_spi_h_
#define _sd_mmc_spi_h_
// sdmmc over spi driver header
//
#ifdef USE_CUSTOM_SPI
# include <sdmmc_spiIntf.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
// sdmmc info struct
typedef struct _SdmmcInfo_ {
esU32 blockCnt; // blocks count
esU32 blockSize; // block size in bytes
esU32 userBlockSize; // user block size in bytes
esU32 stat; // sdmmc last operation status flags
esU32 ioRetries; // sdmmc io attempts
esU8 flags; // sdmmc card info flags
esU8 type; // sdmmc card type
} SdmmcInfo;
// CID block
typedef esU8 SdmmcCID[16];
// consts
enum {
// sdmmc workmode mode flags
sdmmcOk = 0x01, // sd card was initialized. check sdmmcVoltageMismatch flag to see if card is ok to use
sdmmcVoltageMismatch = 0x02, // sd card was not initialized properly due to wrong working voltage range
sdmmcBlockAddr = 0x04, // block addressing mode. if not set - byte addressing is used
sdmmcHighCapacity = 0x08, // high capacity card detected
sdmmcUseCrc = 0x10, // CRC usage was configured for the card io
sdmmcSectorEraseUnit = 0x20, // device supports erasing by sectors (multiple of blocks) otherwise, explicit block erase is supported
// sdmmc status flags
//
sdmmcStatAOK = 0,
sdmmcStatIdle = 0x00000001, // sdmmc card is in idle (non-initialized) state
sdmmcStatEraseAborted = 0x00000002, // block erase sequence was cancelled before erase was complete
sdmmcStatIllegalCmd = 0x00000004, // an illegal command was issued to card
sdmmcStatCmdCrcError = 0x00000008, // card refuses command CRC block
sdmmcStatEraseSeqError = 0x00000010, // an error occurred in erase commands sequence
sdmmcStatAddrError = 0x00000020, // misaligned block access is detected
sdmmcStatParamError = 0x00000040, // command parameter is out of allowed range
sdmmcStatCardLocked = 0x00000080, // card is in locked sate
sdmmcStatEraseWprotError = 0x00000100, // write protected sector erase was attempted
sdmmcStatLockUnlockError = sdmmcStatEraseWprotError, // lock|unlock of password protected area failed
sdmmcStatUnknownError = 0x00000200, // unspecified|unknown error occurred
sdmmcStatControllerError = 0x00000400, // internal card controller error
sdmmcStatEccError = 0x00000800, // card internal ECC was applied, but failed to correct the data
sdmmcStatWprotViolation = 0x00001000, // write protected area write access was attempted
sdmmcStatEraseParamError = 0x00002000, // invalid parameter selecting erase blocks, sectors of groups
sdmmcStatOutOfRange = 0x00004000, //
sdmmcStatCsdOverwrite = sdmmcStatOutOfRange, //
sdmmcStatWriteCrcError = 0x00008000, // data block write was rejected due to CRC error
sdmmcStatWriteError = 0x00010000, // data block write was rejected due to write error
sdmmcWaitReadyExpired = 0x00020000, // retries of wait for bus to become ready have expired
sdmmcReadCrcError = 0x00040000, // read data block crc check failed
// card types (NB! not bitfield, but value)
sdmmcUnknown = 0,
sdmmcSd1,
sdmmcSd2,
sdmmcMmc3,
// misc
SdmmcInfo_SZE = sizeof(SdmmcInfo),
// spi bus must be configured for this frequency upon initialization
sdmmcInitFreq = 300000,
// initial sd retries to apply upon initialization
// final values are subject to change upon initialization completion,
// according to card settings
sdmmcIoInitialRetries = 1000,
};
// SDMMC over SPI API
//
// application-dependent sdmmc power control
void sdmmcPowerOn(spiHANDLE h, SdmmcInfo* info, esBL on);
// wait until card releases the bus
esBL sdmmcWaitReady(spiHANDLE h, SdmmcInfo* info);
// initialize sdmmc card access. v is host-supplied voltage in millivolts
// i.e. 3.6v = 3600
esBL sdmmcInit(spiHANDLE h, SdmmcInfo* info, esU16 v, esBL useCrc);
// sd card block io. returned is count of blocks actually read|written
esU32 sdmmcBlocksRead(spiHANDLE h, SdmmcInfo* info, esU32 startBlock, esU8* blocks, esU32 count);
esU32 sdmmcBlocksWrite(spiHANDLE h, SdmmcInfo* info, esU32 startBlock, const esU8* blocks, esU32 count);
// sd card block erase. returned is amount of erased blocks
esU32 sdmmcBlocksErase(spiHANDLE h, SdmmcInfo* info, esU32 startBlock, esU32 count);
// sd spi compatibility bus release call. sd releases bus synchronously with sck pulses, not cs,
// so we have to do 1 dummy bus read with cs deasserted to release spi properly (avoiding multislave conflicts)
void sdmmcReleaseSpiBus(spiHANDLE h);
// calculate card io retries in accordance with bus settings and requested timeout value in ms
void sdmmcCalcIoTimings(spiHANDLE h, SdmmcInfo* info, esU32 tmo);
// retrieve card id
void sdmmcCardIdGet(spiHANDLE h, SdmmcInfo* info, SdmmcCID cid);
#ifdef __cplusplus
}
#endif
#endif // _sd_mmc_spi_h_
Necessary SDMMC SPI adapter API implementation. C source
#ifndef _sdmmc_spi_intf_h_
#define _sdmmc_spi_intf_h_
#include "retarget.h"
#ifdef __cplusplus
extern "C" {
#endif
void sdmmcSpiInit(spiHANDLE h);
void spiConfig( spiHANDLE h, esU32 rate );
esU32 spiRateGet( spiHANDLE h );
esU32 spiPutBytes( spiHANDLE h, const esU8* Bytes, esU32 length );
esU32 spiGetBytes( spiHANDLE h, esU8* Bytes, esU32 length );
#ifdef __cplusplus
}
#endif
#define sdmmcSELECT sdmmcCS_ON()
#define sdmmcDESELECT sdmmcCS_OFF()
#endif // _sdmmc_spi_intf_h_
Necessary SDMMC SPI adapter API implementation. C header.
// ESP includes
#include <esp_common.h>
#include <gpio.h>
#include <spi_interface.h>
#include "retarget.h"
#include <esfwxe/drivers/flash/sdmmc_spi.h>
#include "sdmmc_spiIntf.h"
#include "gpioUser.h"
#define spiHANDLE_CAST(h) ((SpiAttr*)(h))
void spiConfig(spiHANDLE h, esU32 rate)
{
ES_DEBUG_TRACE("spiConfig(h, rate=%d)...\n", rate);
// Initialze SPI Pins on ESP8266
WRITE_PERI_REG(PERIPHS_IO_MUX, 0x105);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_HSPIQ_MISO);
// PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_HSPID_MOSI); //< Use "manual" MOSI level during data reception
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_HSPI_CLK);
//PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_HSPI_CS0); //< Use "manual" chipselect on the same port
spiHANDLE_CAST(h)->mode = SpiMode_Master;
spiHANDLE_CAST(h)->subMode = SpiSubMode_0;
spiHANDLE_CAST(h)->rate = rate;
spiHANDLE_CAST(h)->bitOrder = SpiBitOrder_MSBFirst;
SPIInit(
SpiNum_HSPI,
spiHANDLE_CAST(h),
true
);
ES_DEBUG_TRACE("...OK\n");
}
esU32 spiRateGet(spiHANDLE h)
{
return spiHANDLE_CAST(h)->rate;
}
void sdmmcSpiInit(spiHANDLE h)
{
ES_DEBUG_TRACE("sdmmcSpiInit...\n");
// Put CS to 0
sdmmcCS_ON();
// Switch power off immediately
sdmmcPWR_OFF();
spiConfig(
h,
sdmmcInitFreq
);
ES_DEBUG_TRACE("...OK\n");
}
void sdmmcPowerOn( spiHANDLE h, SdmmcInfo* info, esBL on )
{
ES_DEBUG_TRACE("sdmmcPowerOn( h, info, on=%d )...\n", on);
if( on )
{
sdmmcPWR_ON();
// wait for 10ms to power-up
rtosDelay(30);
}
else
{
// wait for free bus and close spi
sdmmcWaitReady(
h,
info
);
SPIWaitUntilIdle(SpiNum_HSPI);
// wait for sdmmc to complete all internal processing
rtosDelay(500);
// Remove power
sdmmcPWR_OFF();
}
ES_DEBUG_TRACE("...OK\n");
}
static int ICACHE_FLASH_ATTR spiInternalChunkSend(SpiData* data, const esU8* buff, esU32 chunkLen)
{
#ifdef SPI_IO_DATA_BUFF_ALWAYS_4_ALIGNED
data->dataOut = (esU8*)buff;
data->dataLen = chunkLen;
return SPIMasterTransferData(
SpiNum_HSPI,
data
);
#else
int result = -1;
esU32 residueLen = chunkLen % 4;
data->dataOut = (esU8*)buff;
data->dataLen = chunkLen-residueLen;
if( chunkLen == residueLen )
result = 0;
else
result = SPIMasterTransferData(
SpiNum_HSPI,
data
);
if( -1 < result && residueLen )
{
buff += data->dataLen;
uint32_t residue = 0;
memcpy(
&residue,
buff,
residueLen
);
data->dataOut = (esU8*)&residue;
data->dataLen = residueLen;
result = SPIMasterTransferData(
SpiNum_HSPI,
data
);
}
return result;
#endif
}
static bool s_mosiIsGpio = true;
static void ICACHE_FLASH_ATTR mosiCfgToGpioAndSet(void)
{
if( s_mosiIsGpio )
{
GPIO_OUTPUT(SDMMC_MOSI_PIN, 1);
return;
}
// Wait until IO is complete
SPIWaitUntilIdle(SpiNum_HSPI);
// Switch from HSPI MOSI to GPIO, set to 1
WRITE_PERI_REG(PERIPHS_IO_MUX, 0x105);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13);
GPIO_OUTPUT(SDMMC_MOSI_PIN, 1);
s_mosiIsGpio = true;
}
static void ICACHE_FLASH_ATTR mosiCfgFromGpio(void)
{
if( !s_mosiIsGpio )
return;
GPIO_OUTPUT(SDMMC_MOSI_PIN, 0);
WRITE_PERI_REG(PERIPHS_IO_MUX, 0x105);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_HSPID_MOSI);
s_mosiIsGpio = false;
}
esU32 ICACHE_FLASH_ATTR spiPutBytes( spiHANDLE h, const esU8* Bytes, esU32 length )
{
if( !h || !Bytes || !length )
return 0;
const esU8* pos = Bytes;
const esU8* end = pos+length;
SpiData spiData;
spiData.cmd = 0; ///< Command value
spiData.cmdLen = 0; ///< Command byte length
spiData.addr = NULL; ///< Point to address value
spiData.addrLen = 0; ///< Address byte length
spiData.dummyBits = 0; ///< Not used
spiData.dataIn = NULL; ///< Not used
// Switch to HSPI MOSI from GPIO
mosiCfgFromGpio();
while( pos < end )
{
esU32 chunkLen = end-pos;
if( chunkLen > ESP_SPI_MAX_BLOCK )
chunkLen = ESP_SPI_MAX_BLOCK;
if( -1 == spiInternalChunkSend(
&spiData,
pos,
chunkLen
)
)
break;
pos += chunkLen;
}
mosiCfgToGpioAndSet();
return pos-Bytes;
}
static int ICACHE_FLASH_ATTR spiInternalChunkReceive(SpiData* data, esU8* buff, esU32 chunkLen)
{
#ifdef SPI_IO_DATA_BUFF_ALWAYS_4_ALIGNED
data->dataIn = buff;
data->dataLen = chunkLen;
return SPIMasterTransferData(
SpiNum_HSPI,
data
);
#else
int result = -1;
esU32 residueLen = chunkLen % 4;
data->dataIn = buff;
data->dataLen = chunkLen-residueLen;
if( chunkLen == residueLen )
result = 0;
else
result = SPIMasterTransferData(
SpiNum_HSPI,
data
);
if( -1 < result && residueLen )
{
buff += data->dataLen;
uint32_t residue = 0;
data->dataIn = (esU8*)&residue;
data->dataLen = residueLen;
result = SPIMasterTransferData(
SpiNum_HSPI,
data
);
if( -1 < result )
memcpy(
buff,
&residue,
residueLen
);
}
return result;
#endif
}
esU32 ICACHE_FLASH_ATTR spiGetBytes( spiHANDLE h, esU8* Bytes, esU32 length )
{
if( !h || !Bytes || !length )
return 0;
esU8* pos = Bytes;
esU8* end = pos+length;
SpiData spiData;
spiData.cmd = 0; ///< Command value
spiData.cmdLen = 0; ///< Command byte length
spiData.addr = NULL; ///< Point to address value
spiData.addrLen = 0; ///< Address byte length
spiData.dummyBits = 0; ///< Not used
spiData.dataOut = NULL; ///< Not used
// Switch from HSPI MOSI to GPIO, set to 1 while reading
mosiCfgToGpioAndSet();
while( pos < end )
{
esU32 chunkLen = end-pos;
if( chunkLen > ESP_SPI_MAX_BLOCK )
chunkLen = ESP_SPI_MAX_BLOCK;
if( -1 == spiInternalChunkReceive(
&spiData,
pos,
chunkLen
)
)
break;
pos += chunkLen;
}
return pos-Bytes;
}
ESP8266 SPI driver implementation. C source.
/**
* @file spi_interface.c
* @brief Defines and Macros for the SPI.
* Modified and re-worked driver source.
*/
#include "spi_interface.h"
#include "esp8266/eagle_soc.h"
#include "esp8266/ets_sys.h"
#include "esp_libc.h"
//*****************************************************************************
//
// Make sure all of the definitions in this header have a C binding.
//
//*****************************************************************************
#ifdef __cplusplus
extern "C"
{
#endif
#define WAIT_UNTIL_FREE(spiNum) while(READ_PERI_REG(SPI_CMD(spiNum))&SPI_USR)
// Show the spi registers.
#define SHOWDEBUG
void __ShowRegValue(const char * func, uint32_t line)
{
#ifndef SHOWDEBUG
int i;
uint32_t regAddr = 0x60000140; // SPI--0x60000240, HSPI--0x60000140;
printf("\r\n FUNC[%s],line[%d]\r\n", func, line);
printf(" SPI_ADDR [0x%08x]\r\n", READ_PERI_REG(SPI_ADDR(SpiNum_HSPI)));
printf(" SPI_CMD [0x%08x]\r\n", READ_PERI_REG(SPI_CMD(SpiNum_HSPI)));
printf(" SPI_CTRL [0x%08x]\r\n", READ_PERI_REG(SPI_CTRL(SpiNum_HSPI)));
printf(" SPI_CTRL2 [0x%08x]\r\n", READ_PERI_REG(SPI_CTRL2(SpiNum_HSPI)));
printf(" SPI_CLOCK [0x%08x]\r\n", READ_PERI_REG(SPI_CLOCK(SpiNum_HSPI)));
printf(" SPI_RD_STATUS [0x%08x]\r\n", READ_PERI_REG(SPI_RD_STATUS(SpiNum_HSPI)));
printf(" SPI_WR_STATUS [0x%08x]\r\n", READ_PERI_REG(SPI_WR_STATUS(SpiNum_HSPI)));
printf(" SPI_USER [0x%08x]\r\n", READ_PERI_REG(SPI_USER(SpiNum_HSPI)));
printf(" SPI_USER1 [0x%08x]\r\n", READ_PERI_REG(SPI_USER1(SpiNum_HSPI)));
printf(" SPI_USER2 [0x%08x]\r\n", READ_PERI_REG(SPI_USER2(SpiNum_HSPI)));
printf(" SPI_PIN [0x%08x]\r\n", READ_PERI_REG(SPI_PIN(SpiNum_HSPI)));
printf(" SPI_SLAVE [0x%08x]\r\n", READ_PERI_REG(SPI_SLAVE(SpiNum_HSPI)));
printf(" SPI_SLAVE1 [0x%08x]\r\n", READ_PERI_REG(SPI_SLAVE1(SpiNum_HSPI)));
printf(" SPI_SLAVE2 [0x%08x]\r\n", READ_PERI_REG(SPI_SLAVE2(SpiNum_HSPI)));
for (i = 0; i < 16; ++i) {
printf(" ADDR[0x%08x],Value[0x%08x]\r\n", regAddr, READ_PERI_REG(regAddr));
regAddr += 4;
}
#endif
}
// Define SPI interrupt enable macro
#define ETS_SPI_INTR_ENABLE() _xt_isr_unmask(1 << ETS_SPI_INUM)
// min & max macros
#ifndef MAX
# define MAX(a,b) ((a) < (b) ? (b) : (a))
#endif
#ifndef MIN
# define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
void SPIMasterRateSet(SpiNum spiNum, uint32_t rate)
{
if( rate > CPU_CLK_FREQ )
rate = CPU_CLK_FREQ;
if( CPU_CLK_FREQ == rate )
{
WRITE_PERI_REG(
SPI_CLOCK(spiNum),
SPI_CLK_EQU_SYSCLK
); // 80Mhz speed
return;
}
CLEAR_PERI_REG_MASK(
SPI_CLOCK(spiNum),
SPI_CLK_EQU_SYSCLK
);
uint32_t cntdiv = 2;
uint32_t prediv = CPU_CLK_FREQ/(rate * cntdiv);
WRITE_PERI_REG(
SPI_CLOCK(spiNum),
(((prediv-1) & SPI_CLKDIV_PRE) << SPI_CLKDIV_PRE_S) |
(((cntdiv-1) & SPI_CLKCNT_N) << SPI_CLKCNT_N_S) |
(((cntdiv>>1)& SPI_CLKCNT_H) << SPI_CLKCNT_H_S) |
((0&SPI_CLKCNT_L) << SPI_CLKCNT_L_S)
); //clear bit 31,set SPI clock div
}
void ICACHE_FLASH_ATTR SPIWaitUntilIdle(SpiNum spiNum)
{
if(spiNum > SpiNum_HSPI)
return;
WAIT_UNTIL_FREE(spiNum);
}
/**
* @brief Based on pAttr initialize SPI module.
*
*/
void ICACHE_FLASH_ATTR SPIInit(SpiNum spiNum, SpiAttr* pAttr, bool manualCS)
{
if(
(spiNum > SpiNum_HSPI) ||
(NULL == pAttr)
)
return;
// Disable flash operation mode
// As earlier as better, if not SPI_CTRL2 can not to be set delay cycles.
CLEAR_PERI_REG_MASK(SPI_USER(spiNum), SPI_FLASH_MODE | SPI_CS_SETUP | SPI_CS_HOLD);
// Clear Dual or Quad lines transmission mode
CLEAR_PERI_REG_MASK(SPI_CTRL(spiNum), SPI_QIO_MODE | SPI_DIO_MODE | SPI_DOUT_MODE | SPI_QOUT_MODE);
// SPI_CPOL & SPI_CPHA
switch (pAttr->subMode)
{
case SpiSubMode_1:
CLEAR_PERI_REG_MASK(SPI_PIN(spiNum), SPI_IDLE_EDGE);
SET_PERI_REG_MASK(SPI_USER(spiNum), SPI_CK_OUT_EDGE); // CHPA_FALLING_EDGE_SAMPLE
break;
case SpiSubMode_2:
SET_PERI_REG_MASK(SPI_PIN(spiNum), SPI_IDLE_EDGE);
SET_PERI_REG_MASK(SPI_USER(spiNum), SPI_CK_OUT_EDGE); // CHPA_FALLING_EDGE_SAMPLE
break;
case SpiSubMode_3:
SET_PERI_REG_MASK(SPI_PIN(spiNum), SPI_IDLE_EDGE);
CLEAR_PERI_REG_MASK(SPI_USER(spiNum), SPI_CK_OUT_EDGE);
break;
case SpiSubMode_0:
default:
CLEAR_PERI_REG_MASK(SPI_PIN(spiNum), SPI_IDLE_EDGE);
CLEAR_PERI_REG_MASK(SPI_USER(spiNum), SPI_CK_OUT_EDGE);
// To do nothing
break;
}
// SPI bit order
if (SpiBitOrder_MSBFirst == pAttr->bitOrder) {
CLEAR_PERI_REG_MASK(SPI_CTRL(spiNum), SPI_WR_BIT_ORDER);
CLEAR_PERI_REG_MASK(SPI_CTRL(spiNum), SPI_RD_BIT_ORDER);
} else if (SpiBitOrder_LSBFirst == pAttr->bitOrder) {
SET_PERI_REG_MASK(SPI_CTRL(spiNum), SPI_WR_BIT_ORDER);
SET_PERI_REG_MASK(SPI_CTRL(spiNum), SPI_RD_BIT_ORDER);
} else {
// To do nothing
}
// SPI mode type
if( SpiMode_Master == pAttr->mode )
{
// SPI mode type
CLEAR_PERI_REG_MASK(SPI_SLAVE(spiNum), SPI_SLAVE_MODE);
// SPI Send buffer
CLEAR_PERI_REG_MASK(SPI_USER(spiNum), SPI_USR_MISO_HIGHPART );// By default slave send buffer C0-C7
// SPI Speed
SPIMasterRateSet(
spiNum,
pAttr->rate
);
if( !manualCS )
SET_PERI_REG_MASK(SPI_USER(spiNum), SPI_CS_SETUP | SPI_CS_HOLD);
// delays num
SET_PERI_REG_MASK(
SPI_CTRL2(spiNum),
((0x1 & SPI_MISO_DELAY_NUM) << SPI_MISO_DELAY_NUM_S)
);
} else if(SpiMode_Slave == pAttr->mode)
{
// BIT19 must do
SET_PERI_REG_MASK(SPI_PIN(spiNum), BIT19);
// SPI mode type
SET_PERI_REG_MASK(SPI_SLAVE(spiNum), SPI_SLAVE_MODE);
// SPI Send buffer
SET_PERI_REG_MASK(SPI_USER(spiNum), SPI_USR_MISO_HIGHPART);// By default slave send buffer C8-C15
SET_PERI_REG_MASK(SPI_USER(spiNum), SPI_USR_MOSI);
// If do not set delay cycles, slave not working,master cann't get the data.
SET_PERI_REG_MASK(SPI_CTRL2(spiNum), ((0x1 & SPI_MOSI_DELAY_NUM) << SPI_MOSI_DELAY_NUM_S)); //delay num
// SPI Speed
WRITE_PERI_REG(SPI_CLOCK(spiNum), 0);
// By default format::CMD(8bits)+ADDR(8bits)+DATA(32bytes).
SET_PERI_REG_BITS(SPI_USER2(spiNum), SPI_USR_COMMAND_BITLEN,
7, SPI_USR_COMMAND_BITLEN_S);
SET_PERI_REG_BITS(SPI_SLAVE1(spiNum), SPI_SLV_WR_ADDR_BITLEN,
7, SPI_SLV_WR_ADDR_BITLEN_S);
SET_PERI_REG_BITS(SPI_SLAVE1(spiNum), SPI_SLV_RD_ADDR_BITLEN,
7, SPI_SLV_RD_ADDR_BITLEN_S);
SET_PERI_REG_BITS(SPI_SLAVE1(spiNum), SPI_SLV_BUF_BITLEN,
(32 * 8 - 1), SPI_SLV_BUF_BITLEN_S);
// For 8266 work on slave mode.
SET_PERI_REG_BITS(SPI_SLAVE1(spiNum), SPI_SLV_STATUS_BITLEN,
7, SPI_SLV_STATUS_BITLEN_S);
}
}
/**
* @brief Transfer data between slave and master.
*
*/
int ICACHE_FLASH_ATTR SPIMasterTransferData(SpiNum spiNum, SpiData* pData)
{
if( spiNum > SpiNum_HSPI )
return -1;
WAIT_UNTIL_FREE(spiNum);
//disable MOSI, MISO, ADDR, COMMAND, DUMMY in case previously set
CLEAR_PERI_REG_MASK(
SPI_USER(spiNum),
SPI_USR_MOSI |
SPI_USR_MISO |
SPI_USR_COMMAND |
SPI_USR_ADDR |
SPI_USR_DUMMY
);
if( pData->dummyBits )
{
SET_PERI_REG_BITS(
SPI_USER1(spiNum),
SPI_USR_DUMMY_CYCLELEN,
(pData->dummyBits-1),
SPI_USR_DUMMY_CYCLELEN_S
);
SET_PERI_REG_MASK(
SPI_USER(spiNum),
SPI_USR_DUMMY
);
}
else
{
SET_PERI_REG_BITS(
SPI_USER1(spiNum),
SPI_USR_DUMMY_CYCLELEN,
0,
SPI_USR_DUMMY_CYCLELEN_S
);
}
// Set command block
if( pData->cmdLen )
{
// Max command length 16 bits.
SET_PERI_REG_BITS(
SPI_USER2(spiNum),
SPI_USR_COMMAND_BITLEN,
((pData->cmdLen << 3) - 1),
SPI_USR_COMMAND_BITLEN_S
);
// Enable command
SET_PERI_REG_MASK(
SPI_USER(spiNum),
SPI_USR_COMMAND
);
// Load command;
// SPI_USER2 bit28-31 is cmd length,cmd bit length is value(0-15)+1,
// bit15-0 is cmd value.
SET_PERI_REG_BITS(
SPI_USER2(spiNum),
SPI_USR_COMMAND_VALUE,
pData->cmd,
SPI_USR_COMMAND_VALUE_S
);
}
else
{
SET_PERI_REG_BITS(
SPI_USER2(spiNum),
SPI_USR_COMMAND_BITLEN,
0,
SPI_USR_COMMAND_BITLEN_S
);
}
// Set Address by user.
if( pData->addrLen )
{
if( NULL == pData->addr )
return -1;
SET_PERI_REG_BITS(
SPI_USER1(spiNum),
SPI_USR_ADDR_BITLEN,
((pData->addrLen << 3) - 1),
SPI_USR_ADDR_BITLEN_S
);
// Enable address
SET_PERI_REG_MASK(
SPI_USER(spiNum),
SPI_USR_ADDR
);
// Load address
WRITE_PERI_REG(
SPI_ADDR(spiNum),
*pData->addr
);
}
else
{
SET_PERI_REG_BITS(
SPI_USER1(spiNum),
SPI_USR_ADDR_BITLEN,
0,
SPI_USR_ADDR_BITLEN_S
);
}
uint8_t* in = pData->dataIn;
uint8_t* out = pData->dataOut;
uint32_t tmpio;
int idx, dataLeft, x4cnt, x4part;
// Set output data block
if( pData->dataLen )
{
if( !out && !in )
return -1;
if( in )
{
// Enable MISO
SET_PERI_REG_MASK(SPI_USER(spiNum), SPI_USR_MISO);
SET_PERI_REG_BITS(
SPI_USER1(spiNum),
SPI_USR_MISO_BITLEN,
((pData->dataLen << 3) - 1),
SPI_USR_MISO_BITLEN_S
);
}
if( out )
{
// Enable MOSI
SET_PERI_REG_MASK(SPI_USER(spiNum), SPI_USR_MOSI);
// Load send buffer.
// NB!!! Buffer must always have x4 alignment, even if dataLen is not aligned to 4
idx = 0;
dataLeft = pData->dataLen;
x4cnt = ( pData->dataLen % 4 ) ?
(pData->dataLen / 4) + 1 :
(pData->dataLen / 4);
while( idx < x4cnt )
{
tmpio = 0; //< Allow for an arbitrary buffer alignment and offset
x4part = MIN(4, dataLeft);
memcpy(
&tmpio,
out,
x4part
);
dataLeft -= x4part;
out += x4part;
WRITE_PERI_REG(
(SPI_W0(spiNum) + (idx << 2)),
tmpio
);
++idx;
}
// Set data send buffer length.Max data length 64 bytes.
SET_PERI_REG_BITS(
SPI_USER1(spiNum),
SPI_USR_MOSI_BITLEN,
((pData->dataLen << 3) - 1),
SPI_USR_MOSI_BITLEN_S
);
}
}
else
{
SET_PERI_REG_BITS(
SPI_USER1(spiNum),
SPI_USR_MOSI_BITLEN,
0,
SPI_USR_MOSI_BITLEN_S
);
SET_PERI_REG_BITS(
SPI_USER1(spiNum),
SPI_USR_MISO_BITLEN,
0,
SPI_USR_MISO_BITLEN_S
);
}
SET_PERI_REG_MASK(
SPI_CMD(spiNum),
SPI_USR
);
if( pData->dataLen && in )
{
WAIT_UNTIL_FREE(spiNum);
// Read data out
// NB! inbound SPI data buffer must always be aligned to 4, even if dataLen is not alighned to 32 bits
idx = 0;
dataLeft = pData->dataLen;
x4cnt = (pData->dataLen % 4) ?
(pData->dataLen / 4) + 1 :
(pData->dataLen / 4);
while( idx < x4cnt )
{
// Allow for an arbitrary buffer alignment and offset
tmpio = READ_PERI_REG(
SPI_W0(spiNum) + (idx << 2)
);
x4part = MIN(4, dataLeft);
memcpy(
in,
&tmpio,
x4part
);
dataLeft -= x4part;
in += x4part;
++idx;
}
}
SHOWREG();
return 0;
}
/**
* @brief Load data to send buffer by slave mode.
*
*/
int ICACHE_FLASH_ATTR SPISlaveSendData(SpiNum spiNum, uint32_t *pInData, uint8_t outLen)
{
if (NULL == pInData) {
return -1;
}
char i;
for (i = 0; i < outLen; ++i) {
WRITE_PERI_REG((SPI_W8(spiNum) + (i << 2)), *pInData++);
}
return 0;
}
/**
* @brief Configurate slave prepare for receive data.
*
*/
int ICACHE_FLASH_ATTR SPISlaveRecvData(SpiNum spiNum, void(*isrFunc)(void*))
{
if ((spiNum > SpiNum_HSPI)) {
return -1;
}
SPIIntEnable(SpiNum_HSPI, SpiIntSrc_WrStaDoneEn
| SpiIntSrc_RdStaDoneEn | SpiIntSrc_WrBufDoneEn | SpiIntSrc_RdBufDoneEn);
SPIIntDisable(SpiNum_HSPI, SpiIntSrc_TransDoneEn);
// Maybe enable slave transmission liston
SET_PERI_REG_MASK(SPI_CMD(spiNum), SPI_USR);
//
_xt_isr_attach(ETS_SPI_INUM, isrFunc, NULL);
// ETS_SPI_INTR_ATTACH(isrFunc, NULL);
// Enable isr
ETS_SPI_INTR_ENABLE();
SHOWREG();
return 0;
}
/**
* @brief Send data to slave(ESP8266 register of RD_STATUS or WR_STATUS).
*
*/
void ICACHE_FLASH_ATTR SPIMasterSendStatus(SpiNum spiNum, uint8_t data)
{
if (spiNum > SpiNum_HSPI) {
return;
}
WAIT_UNTIL_FREE(spiNum);
// Enable MOSI
SET_PERI_REG_MASK(SPI_USER(spiNum), SPI_USR_MOSI);
CLEAR_PERI_REG_MASK(SPI_USER(spiNum), SPI_USR_MISO | SPI_USR_DUMMY | SPI_USR_ADDR);
// 8bits cmd, 0x04 is eps8266 slave write cmd value
WRITE_PERI_REG(SPI_USER2(spiNum),
((7 & SPI_USR_COMMAND_BITLEN) << SPI_USR_COMMAND_BITLEN_S)
| MASTER_WRITE_STATUS_TO_SLAVE_CMD);
// Set data send buffer length.
SET_PERI_REG_BITS(SPI_USER1(spiNum), SPI_USR_MOSI_BITLEN,
((sizeof(data) << 3) - 1), SPI_USR_MOSI_BITLEN_S);
WRITE_PERI_REG(SPI_W0(spiNum), (uint32)(data));
// Start SPI
SET_PERI_REG_MASK(SPI_CMD(spiNum), SPI_USR);
SHOWREG();
}
/**
* @brief Receive status register from slave(ESP8266).
*
*/
int ICACHE_FLASH_ATTR SPIMasterRecvStatus(SpiNum spiNum)
{
if (spiNum > SpiNum_HSPI) {
return -1;
}
WAIT_UNTIL_FREE(spiNum);
// Enable MISO
SET_PERI_REG_MASK(SPI_USER(spiNum), SPI_USR_MISO);
CLEAR_PERI_REG_MASK(SPI_USER(spiNum), SPI_USR_MOSI | SPI_USR_DUMMY | SPI_USR_ADDR);
// 8bits cmd, 0x06 is eps8266 slave read status cmd value
WRITE_PERI_REG(SPI_USER2(spiNum),
((7 & SPI_USR_COMMAND_BITLEN) << SPI_USR_COMMAND_BITLEN_S)
| MASTER_READ_STATUS_FROM_SLAVE_CMD);
// Set revcive buffer length.
SET_PERI_REG_BITS(SPI_USER1(spiNum), SPI_USR_MISO_BITLEN,
7, SPI_USR_MISO_BITLEN_S);
// start spi module.
SET_PERI_REG_MASK(SPI_CMD(spiNum), SPI_USR);
WAIT_UNTIL_FREE(spiNum);
uint8_t data = (uint8)(READ_PERI_REG(SPI_W0(spiNum)) & 0xff);
SHOWREG();
return (uint8)(READ_PERI_REG(SPI_W0(spiNum)) & 0xff);
}
/**
* @brief Select SPI CS pin.
*
*/
void ICACHE_FLASH_ATTR SPICsPinSelect(SpiNum spiNum, SpiPinCS pinCs)
{
if (spiNum > SpiNum_HSPI) {
return;
}
// clear select
SET_PERI_REG_BITS(SPI_PIN(spiNum), 3, 0, 0);
SET_PERI_REG_MASK(SPI_PIN(spiNum), pinCs);
}
/**
* @brief Enable SPI interrupt source.
*
*/
void ICACHE_FLASH_ATTR SPIIntEnable(SpiNum spiNum, SpiIntSrc intSrc)
{
if (spiNum > SpiNum_HSPI) {
return;
}
SET_PERI_REG_MASK(SPI_SLAVE(spiNum), intSrc);
}
/**
* @brief Disable SPI interrupt source.
*
*/
void ICACHE_FLASH_ATTR SPIIntDisable(SpiNum spiNum, SpiIntSrc intSrc)
{
if (spiNum > SpiNum_HSPI) {
return;
}
CLEAR_PERI_REG_MASK(SPI_SLAVE(spiNum), intSrc);
}
/**
* @brief Clear all of SPI interrupt source.
*
*/
void ICACHE_FLASH_ATTR SPIIntClear(SpiNum spiNum)
{
if (spiNum > SpiNum_HSPI) {
return;
}
CLEAR_PERI_REG_MASK(SPI_SLAVE(spiNum), SpiIntSrc_TransDoneEn
| SpiIntSrc_WrStaDoneEn
| SpiIntSrc_RdStaDoneEn
| SpiIntSrc_WrBufDoneEn
| SpiIntSrc_RdBufDoneEn);
}
#ifdef __cplusplus
}
#endif
ESP8266 SPI driver implementation. C header.
/**
* @file spi_interface.c
* @brief Defines and Macros for the SPI.
* Modified and re-worked RTOS SDK driver source.
*/
#ifndef __SPI_INTERFACE_H__
#define __SPI_INTERFACE_H__
#include "spi_register.h"
#include "c_types.h"
//*****************************************************************************
//
// Make sure all of the definitions in this header have a C binding.
//
//*****************************************************************************
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Defines slave commands. Default value based on slave ESP8266.
*/
#define MASTER_WRITE_DATA_TO_SLAVE_CMD 2
#define MASTER_READ_DATA_FROM_SLAVE_CMD 3
#define MASTER_WRITE_STATUS_TO_SLAVE_CMD 1
#define MASTER_READ_STATUS_FROM_SLAVE_CMD 4
/**
* @brief Support HSPI and SPI module.
*
*/
typedef enum
{
SpiNum_SPI = 0,
SpiNum_HSPI = 1,
} SpiNum;
/**
* @brief The SPI module can work in either master or slave mode.
*
*/
typedef enum
{
SpiMode_Master = 0,
SpiMode_Slave = 1,
} SpiMode;
/**
* @brief SPI sub mode
*
* Support 4 sub modes based on SPI clock polarity and phase.
* SPI_CPOL SPI_CPHA SubMode
* 0 0 0
* 0 1 1
* 1 0 2
* 1 1 3
*/
typedef enum
{
SpiSubMode_0 = 0,
SpiSubMode_1 = 1,
SpiSubMode_2 = 2,
SpiSubMode_3 = 3,
} SpiSubMode;
/**
* @brief The SPI mode working speed.
*
*/
typedef enum
{
SpiBitOrder_MSBFirst = 0,
SpiBitOrder_LSBFirst = 1,
} SpiBitOrder;
// @brief SPI interrupt source defined.
typedef enum
{
SpiIntSrc_TransDoneEn = SPI_TRANS_DONE_EN,
SpiIntSrc_WrStaDoneEn = SPI_SLV_WR_STA_DONE_EN,
SpiIntSrc_RdStaDoneEn = SPI_SLV_RD_STA_DONE_EN,
SpiIntSrc_WrBufDoneEn = SPI_SLV_WR_BUF_DONE_EN,
SpiIntSrc_RdBufDoneEn = SPI_SLV_RD_BUF_DONE_EN,
} SpiIntSrc;
// @brief SPI CS pin.
typedef enum
{
SpiPinCS_0 = 0,
SpiPinCS_1 = 1,
SpiPinCS_2 = 2,
} SpiPinCS;
/**
* @brief SPI attribute
*/
typedef struct
{
SpiMode mode; ///< Master or slave mode
SpiSubMode subMode; ///< SPI SPI_CPOL SPI_CPHA mode
uint32_t rate; ///< SPI Clock rate
SpiBitOrder bitOrder; ///< SPI bit order
} SpiAttr;
/**
* @brief SPI attribute
*/
typedef struct
{
uint32_t* addr; ///< Point to address value
uint8_t* dataIn; ///< Point to data in buffer
uint8_t* dataOut; ///< Point to data out buffer
uint16_t cmd; ///< Command value
uint8_t addrLen; ///< Address byte length
uint8_t cmdLen; ///< Command byte length
uint8_t dataLen; ///< Data IO byte length.
uint8_t dummyBits; ///< Optional dummy bits count
} SpiData;
#define SHOWREG() __ShowRegValue(__func__, __LINE__);
/**
* @brief Print debug information.
*
*/
void __ShowRegValue(const char * func, uint32_t line);
/**
* @brief Initialize SPI module.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
* @param [in] pAttr
* Pointer to a struct SpiAttr that indicates SPI working attribution.
* @param [in] manualCs
* If set, we do not need to configure CS_SETUP and CS_HOLD bits.
*
* @return void.
*/
void SPIInit(SpiNum spiNum, SpiAttr* pAttr, bool manualCs);
/**
* @brief Set Master SPI Clock rate.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
* @param [in] rate
* Requested SPI rate value, in Hz.
*
* @return void.
*/
void SPIMasterRateSet(SpiNum spiNum, uint32_t rate);
/**
* @brief Transfer data between slave and master.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
* @param [in] pInData
* Pointer to a strcuture that will be send.
*
* @return int, -1:indicates failure,others indicates success.
*/
int SPIMasterTransferData(SpiNum spiNum, SpiData* pInData);
/**
* @brief Load data to slave send buffer.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
* @param [in] pInData
* Point to data buffer.
* @param [in] outLen
* The number of bytes to be set.
*
* @return int, -1:indicates failure,others indicates success.
*/
int SPISlaveSendData(SpiNum spiNum, uint32_t *pInData, uint8_t outLen);
/**
* @brief Receive data by slave.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
* @param [in] isrFunc
* isrFunc is a pointer to the function to be called when the SPI interrupt occurs.
*
* @return int, -1:indicates failure,others indicates success.
*/
int SPISlaveRecvData(SpiNum spiNum, void(*isrFunc)(void*));
/**
* @brief Set slave status by master.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
* @param [in] data
* Data will be write to slave SPI_WR_STATUS.
*
* @return void.
*
* @attention Just for ESP8266(slave) register of RD_STATUS or WR_STATUS.
*/
void SPIMasterSendStatus(SpiNum spiNum, uint8_t data);
/**
* @brief Get salve status by master.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
*
* @return int, -1: indicates failure; other value in slave status.
*
* @attention Just for ESP8266(slave) register of RD_STATUS or WR_STATUS.
*/
int SPIMasterRecvStatus(SpiNum spiNum);
/**
* @brief Select SPI CS pin.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
* @param [in] pinCs
* Indicates which SPI pin to choose.
*
* @return void.
*/
void SPICsPinSelect(SpiNum spiNum, SpiPinCS pinCs);
/**
* @brief Enable SPI module interrupt source.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
* @param [in] intSrc
* Indicates which interrupt source to enable.
*
* @return void.
*/
void SPIIntEnable(SpiNum spiNum, SpiIntSrc intSrc);
/**
* @brief Disable SPI module interrupt source.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
* @param [in] intSrc
* Indicates which interrupt source to disable.
*
* @return void.
*/
void SPIIntDisable(SpiNum spiNum, SpiIntSrc intSrc);
/**
* @brief Clear all of spi interrupt.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
*
* @return void.
*/
void SPIIntClear(SpiNum spiNum);
/**
* @brief Wait until SPI IO is complete.
*
* @param [in] spiNum
* Indicates which submode to be used, SPI or HSPI.
*
* @return void.
*/
void SPIWaitUntilIdle(SpiNum spiNum);
#ifdef __cplusplus
}
#endif
#endif // __SPI_INTERFACE_H__