From fb62011ddf2df25204e949d2838d392c49429693 Mon Sep 17 00:00:00 2001 From: devsaurus Date: Sat, 15 Apr 2017 17:38:20 +0200 Subject: [PATCH] Add sdmmc module and enable fatfs. --- components/fatfs/component.mk | 2 +- components/fatfs/diskio.c | 43 ++-- components/fatfs/fatfs_config.h | 10 +- components/fatfs/myfatfs.c | 4 +- components/modules/Kconfig | 6 + components/modules/file.c | 50 +---- components/modules/sdmmc.c | 293 +++++++++++++++++++++++++++ components/platform/Kconfig | 1 + components/platform/include/sdcard.h | 18 -- components/spiffs/spiffs.c | 12 -- docs/en/modules/file.md | 24 +-- docs/en/modules/sdmmc.md | 155 ++++++++++++++ docs/en/sdcard.md | 43 ++++ docs/img/micro_sd-small.jpg | Bin 0 -> 11948 bytes docs/img/micro_sd.jpg | Bin 0 -> 36986 bytes mkdocs.yml | 2 + 16 files changed, 530 insertions(+), 133 deletions(-) create mode 100644 components/modules/sdmmc.c delete mode 100644 components/platform/include/sdcard.h create mode 100644 docs/en/modules/sdmmc.md create mode 100644 docs/en/sdcard.md create mode 100644 docs/img/micro_sd-small.jpg create mode 100644 docs/img/micro_sd.jpg diff --git a/components/fatfs/component.mk b/components/fatfs/component.mk index dfa02650..b4773ea0 100644 --- a/components/fatfs/component.mk +++ b/components/fatfs/component.mk @@ -3,4 +3,4 @@ COMPONENT_SRCDIRS:=. option COMPONENT_OBJS:=diskio.o ff.o myfatfs.o option/unicode.o COMPONENT_ADD_INCLUDEDIRS:=. -EXTRA_CFLAGS:=-imacros fatfs_prefix_lib.h +CFLAGS+=-imacros fatfs_prefix_lib.h diff --git a/components/fatfs/diskio.c b/components/fatfs/diskio.c index 3076d43f..59ccab41 100644 --- a/components/fatfs/diskio.c +++ b/components/fatfs/diskio.c @@ -8,7 +8,11 @@ /*-----------------------------------------------------------------------*/ #include "diskio.h" /* FatFs lower layer API */ -#include "sdcard.h" +#include "sdmmc_cmd.h" + +// defined in components/modules/sdmmc.c +extern sdmmc_card_t lsdmmc_card[2]; + static DSTATUS m_status = STA_NOINIT; @@ -20,6 +24,8 @@ DSTATUS disk_status ( BYTE pdrv /* Physical drive nmuber to identify the drive */ ) { + (void)pdrv; + return m_status; } @@ -33,9 +39,9 @@ DSTATUS disk_initialize ( BYTE pdrv /* Physical drive nmuber to identify the drive */ ) { - if (platform_sdcard_init( 1, pdrv )) { - m_status &= ~STA_NOINIT; - } + (void)pdrv; + + m_status &= ~STA_NOINIT; return m_status; } @@ -53,17 +59,10 @@ DRESULT disk_read ( UINT count /* Number of sectors to read */ ) { - if (count == 1) { - if (! platform_sdcard_read_block( pdrv, sector, buff )) { - return RES_ERROR; - } - } else { - if (! platform_sdcard_read_blocks( pdrv, sector, count, buff )) { - return RES_ERROR; - } - } + if (sdmmc_read_sectors( &(lsdmmc_card[pdrv]), buff, sector, count ) == ESP_OK) + return RES_OK; - return RES_OK; + return RES_ERROR; } @@ -78,17 +77,10 @@ DRESULT disk_write ( UINT count /* Number of sectors to write */ ) { - if (count == 1) { - if (! platform_sdcard_write_block( pdrv, sector, buff )) { - return RES_ERROR; - } - } else { - if (! platform_sdcard_write_blocks( pdrv, sector, count, buff )) { - return RES_ERROR; - } - } + if (sdmmc_write_sectors( &(lsdmmc_card[pdrv]), buff, sector, count ) == ESP_OK) + return RES_OK; - return RES_OK; + return RES_ERROR; } @@ -102,6 +94,9 @@ DRESULT disk_ioctl ( void *buff /* Buffer to send/receive control data */ ) { + (void)pdrv; + (void)buff; + switch (cmd) { case CTRL_TRIM: /* no-op */ case CTRL_SYNC: /* no-op */ diff --git a/components/fatfs/fatfs_config.h b/components/fatfs/fatfs_config.h index df2dc745..8b67a6fa 100644 --- a/components/fatfs/fatfs_config.h +++ b/components/fatfs/fatfs_config.h @@ -11,14 +11,14 @@ typedef struct { #endif // Table to map physical drive & partition to a logical volume. -// The first value is the physical drive and contains the GPIO pin for SS/CS of the SD card (default pin 8) +// The first value is the physical drive which relates to the SDMMC slot number // The second value is the partition number. #define NUM_LOGICAL_DRIVES 4 PARTITION VolToPart[NUM_LOGICAL_DRIVES] = { - {8, 1}, /* Logical drive "0:" ==> SS pin 8, 1st partition */ - {8, 2}, /* Logical drive "1:" ==> SS pin 8, 2st partition */ - {8, 3}, /* Logical drive "2:" ==> SS pin 8, 3st partition */ - {8, 4} /* Logical drive "3:" ==> SS pin 8, 4st partition */ + {1, 1}, /* Logical drive "0:" ==> slot 1, 1st partition */ + {1, 2}, /* Logical drive "1:" ==> slot 1, 2st partition */ + {1, 3}, /* Logical drive "2:" ==> slot 1, 3st partition */ + {1, 4} /* Logical drive "3:" ==> slot 1, 4st partition */ }; #endif /* __FATFS_CONFIG_H__ */ diff --git a/components/fatfs/myfatfs.c b/components/fatfs/myfatfs.c index 93012590..b80f5d8e 100644 --- a/components/fatfs/myfatfs.c +++ b/components/fatfs/myfatfs.c @@ -185,6 +185,7 @@ DWORD get_fattime( void ) static int32_t myfatfs_umount( const struct vfs_vol *vol ) { GET_FATFS_FS(vol); + (void)fs; last_result = f_mount( NULL, myvol->ldrname, 0 ); @@ -354,6 +355,7 @@ static vfs_item *myfatfs_readdir( const struct vfs_dir *dd ) static void myfatfs_iclose( const struct vfs_item *di ) { GET_FILINFO_FNO(di); + (void)fno; // free descriptor memory free( (void *)di ); @@ -430,7 +432,7 @@ static vfs_vol *myfatfs_mount( const char *name, int num ) { struct myvfs_vol *vol; - // num argument specifies the physical driver = SS/CS pin number for this sd card + // num argument specifies the physical driver if (num >= 0) { for (int i = 0; i < NUM_LOGICAL_DRIVES; i++) { if (0 == strncmp( name, volstr[i], strlen( volstr[i] ) )) { diff --git a/components/modules/Kconfig b/components/modules/Kconfig index d28b0601..381e818b 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -116,6 +116,12 @@ config LUA_MODULE_OW help Includes the 1-Wire (ow) module (recommended). +config LUA_MODULE_SDMMC + bool "SD-MMC module" + default "n" + help + Includes the sdmmc module. + config LUA_MODULE_SIGMA_DELTA bool "Sigma-Delta module" default "n" diff --git a/components/modules/file.c b/components/modules/file.c index f5c751c9..a68bedbf 100644 --- a/components/modules/file.c +++ b/components/modules/file.c @@ -380,25 +380,6 @@ typedef struct { } volume_type; #ifdef CONFIG_BUILD_FATFS -// Lua: vol = file.mount("/SD0") -static int file_mount( lua_State *L ) -{ - const char *ldrv = luaL_checkstring( L, 1 ); - int num = luaL_optint( L, 2, -1 ); - volume_type *vol = (volume_type *)lua_newuserdata( L, sizeof( volume_type ) ); - - if ((vol->vol = vfs_mount( ldrv, num ))) { - /* set its metatable */ - luaL_getmetatable(L, "vfs.vol"); - lua_setmetatable(L, -2); - return 1; - } else { - // remove created userdata - lua_pop( L, 1 ); - return 0; - } -} - // Lua: success = file.chdir("/SD0/") static int file_chdir( lua_State *L ) { @@ -409,29 +390,6 @@ static int file_chdir( lua_State *L ) } #endif -static int file_vol_umount( lua_State *L ) -{ - volume_type *vol = luaL_checkudata( L, 1, "file.vol" ); - luaL_argcheck( L, vol, 1, "volume expected" ); - - lua_pushboolean( L, 0 <= vfs_umount( vol->vol ) ); - - // invalidate vfs descriptor, it has been free'd anyway - vol->vol = NULL; - return 1; -} - - -static const LUA_REG_TYPE file_vol_map[] = -{ - { LSTRKEY( "umount" ), LFUNCVAL( file_vol_umount )}, - //{ LSTRKEY( "getfree" ), LFUNCVAL( file_vol_getfree )}, - //{ LSTRKEY( "getlabel" ), LFUNCVAL( file_vol_getlabel )}, - //{ LSTRKEY( "__gc" ), LFUNCVAL( file_vol_free ) }, - { LSTRKEY( "__index" ), LROVAL( file_vol_map ) }, - { LNILKEY, LNILVAL } -}; - // Module function map static const LUA_REG_TYPE file_map[] = { { LSTRKEY( "list" ), LFUNCVAL( file_list ) }, @@ -453,15 +411,9 @@ static const LUA_REG_TYPE file_map[] = { { LSTRKEY( "fsinfo" ), LFUNCVAL( file_fsinfo ) }, { LSTRKEY( "on" ), LFUNCVAL( file_on ) }, #ifdef CONFIG_BUILD_FATFS - { LSTRKEY( "mount" ), LFUNCVAL( file_mount ) }, { LSTRKEY( "chdir" ), LFUNCVAL( file_chdir ) }, #endif { LNILKEY, LNILVAL } }; -int luaopen_file( lua_State *L ) { - luaL_rometatable( L, "file.vol", (void *)file_vol_map ); - return 0; -} - -NODEMCU_MODULE(FILE, "file", file_map, luaopen_file); +NODEMCU_MODULE(FILE, "file", file_map, NULL); diff --git a/components/modules/sdmmc.c b/components/modules/sdmmc.c new file mode 100644 index 00000000..e70c9e25 --- /dev/null +++ b/components/modules/sdmmc.c @@ -0,0 +1,293 @@ + +#include "module.h" +#include "lauxlib.h" + +#include "lmem.h" + +#include "vfs.h" + +#include "driver/sdmmc_host.h" +#include "sdmmc_cmd.h" + + +sdmmc_card_t lsdmmc_card[2]; + +typedef struct { + sdmmc_card_t *card; + vfs_vol *vol; +} lsdmmc_ud_t; + + +static int lsdmmc_init( lua_State *L ) +{ + const char *err_msg = ""; + int stack = 0; + + int slot = luaL_checkint( L, ++stack ); + luaL_argcheck( L, slot == SDMMC_HOST_SLOT_0 || slot == SDMMC_HOST_SLOT_1, + stack, "invalid slot" ); + + // set optional defaults + int cd_pin = SDMMC_SLOT_NO_CD; + int wp_pin = SDMMC_SLOT_NO_WP; + int freq_khz = SDMMC_FREQ_DEFAULT; + int width = SDMMC_HOST_FLAG_1BIT; + + if (lua_type( L, ++stack ) == LUA_TTABLE) { + // retrieve slot configuration from table + + lua_getfield( L, stack, "cd_pin" ); + cd_pin = luaL_optint( L, -1, cd_pin ); + + lua_getfield( L, stack, "wp_pin" ); + wp_pin = luaL_optint( L, -1, wp_pin ); + + lua_getfield( L, stack, "fmax" ); + freq_khz = luaL_optint( L, -1, freq_khz * 1000 ) / 1000; + + lua_getfield( L, stack, "width" ); + width = luaL_optint( L, -1, width ); + + lua_pop( L, 4 ); + } + + // initialize SDMMC host + // tolerate error due to re-initialization + esp_err_t res = sdmmc_host_init(); + if (res == ESP_OK || res == ESP_ERR_INVALID_STATE) { + + // configure SDMMC slot + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + slot_config.gpio_cd = cd_pin; + slot_config.gpio_wp = wp_pin; + if (sdmmc_host_init_slot( slot, &slot_config ) == ESP_OK) { + + // initialize card + sdmmc_host_t host_config = SDMMC_HOST_DEFAULT(); + host_config.slot = slot; + host_config.flags = width; + host_config.max_freq_khz = freq_khz; + if (sdmmc_card_init( &host_config, &(lsdmmc_card[slot]) ) == ESP_OK) { + + lsdmmc_ud_t *ud = (lsdmmc_ud_t *)lua_newuserdata( L, sizeof( lsdmmc_ud_t ) ); + if (ud) { + luaL_getmetatable(L, "sdmmc.card"); + lua_setmetatable(L, -2); + ud->card = &(lsdmmc_card[slot]); + ud->vol = NULL; + + // all done + return 1; + } + + } else + err_msg = "failed to init card"; + + } else + err_msg = "failed to init slot"; + + sdmmc_host_deinit(); + } else + err_msg = "failed to init sdmmc host"; + + return luaL_error( L, err_msg ); +} + + +#define GET_CARD_UD \ + lsdmmc_ud_t *ud = (lsdmmc_ud_t *)luaL_checkudata( L, 1, "sdmmc.card" ); \ + sdmmc_card_t *card = ud->card; + + +// Lua: data = card:read( start_sec, num_sec ) +static int lsdmmc_read( lua_State * L) +{ + const char *err_msg = ""; + GET_CARD_UD; + int stack = 1; + + int start_sec = luaL_checkint( L, ++stack ); + luaL_argcheck( L, start_sec >= 0, stack, "out of range" ); + + int num_sec = luaL_optint( L, ++stack, 1 ); + luaL_argcheck( L, num_sec >= 0, stack, "out of range" ); + + // get read buffer + char *rbuf = luaM_malloc( L, num_sec * 512 ); + + if (sdmmc_read_sectors( card, rbuf, start_sec, num_sec ) == ESP_OK) { + luaL_Buffer b; + luaL_buffinit( L, &b ); + luaL_addlstring( &b, rbuf, num_sec * 512 ); + luaL_pushresult( &b ); + + luaM_free( L, rbuf ); + + // all ok + return 1; + + } else + err_msg = "card access failed"; + + return luaL_error( L, err_msg ); +} + +// Lua: card:write( start_sec, data ) +static int lsdmmc_write( lua_State * L) +{ + const char *err_msg = ""; + GET_CARD_UD; + int stack = 1; + + int start_sec = luaL_checkint( L, ++stack ); + luaL_argcheck( L, start_sec >= 0, stack, "out of range" ); + + size_t len; + const char *wbuf = luaL_checklstring( L, ++stack, &len ); + luaL_argcheck( L, len % 512 == 0, stack, "must be multiple of 512" ); + + if (sdmmc_write_sectors( card, wbuf, start_sec, len / 512 ) == ESP_OK) { + // all ok + return 0; + } else + err_msg = "card access failed"; + + return luaL_error( L, err_msg ); +} + +#define SET_INT_FIELD(item, elem) \ + lua_pushinteger( L, card->item.elem ); \ + lua_setfield( L, -2, #elem ); + +// Lua: info = card:get_info() +static int lsdmmc_get_info( lua_State *L ) +{ + GET_CARD_UD; + + lua_newtable( L ); + + // OCR + lua_pushinteger( L, card->ocr ); + lua_setfield( L, -2, "ocr" ); + + // CID + lua_newtable( L ); + SET_INT_FIELD(cid, mfg_id); + SET_INT_FIELD(cid, oem_id); + SET_INT_FIELD(cid, revision); + SET_INT_FIELD(cid, serial); + SET_INT_FIELD(cid, date); + lua_pushstring( L, card->cid.name ); + lua_setfield( L, -2, "name" ); + // + lua_setfield( L, -2, "cid" ); + + // CSD + lua_newtable( L ); + SET_INT_FIELD(csd, csd_ver); + SET_INT_FIELD(csd, mmc_ver); + SET_INT_FIELD(csd, capacity); + SET_INT_FIELD(csd, sector_size); + SET_INT_FIELD(csd, read_block_len); + SET_INT_FIELD(csd, card_command_class); + SET_INT_FIELD(csd, tr_speed); + // + lua_setfield( L, -2, "csd" ); + + // SCR + lua_newtable( L ); + SET_INT_FIELD(scr, sd_spec); + SET_INT_FIELD(scr, bus_width); + // + lua_setfield( L, -2, "scr" ); + + // RCA + lua_pushinteger( L, card->rca ); + lua_setfield( L, -2, "rca" ); + + return 1; +} + + +// Lua: card:mount("/SD0"[, partition]) +static int lsdmmc_mount( lua_State *L ) +{ + const char *err_msg = ""; + + GET_CARD_UD; + (void)card; + + int stack = 1; + + const char *ldrv = luaL_checkstring( L, ++stack ); + + int num = luaL_optint( L, ++stack, -1 ); + + if (ud->vol == NULL) { + + if ((ud->vol = vfs_mount( ldrv, num ))) + lua_pushboolean( L, true ); + else + lua_pushboolean( L, false ); + + return 1; + + } else + err_msg = "already mounted"; + + return luaL_error( L, err_msg ); +} + +// Lua: card:umount() +static int lsdmmc_umount( lua_State *L ) +{ + const char *err_msg = ""; + + GET_CARD_UD; + (void)card; + + if (ud->vol) { + if (vfs_umount( ud->vol ) == VFS_RES_OK) { + ud->vol = NULL; + // all ok + return 0; + } else + err_msg = "umount failed"; + + } else + err_msg = "not mounted"; + + return luaL_error( L, err_msg ); +} + +static const LUA_REG_TYPE sdmmc_card_map[] = { + { LSTRKEY( "read" ), LFUNCVAL( lsdmmc_read ) }, + { LSTRKEY( "write" ), LFUNCVAL( lsdmmc_write ) }, + { LSTRKEY( "get_info" ), LFUNCVAL( lsdmmc_get_info ) }, + { LSTRKEY( "mount" ), LFUNCVAL( lsdmmc_mount ) }, + { LSTRKEY( "umount" ), LFUNCVAL( lsdmmc_umount ) }, + { LSTRKEY( "__index" ), LROVAL( sdmmc_card_map ) }, + { LNILKEY, LNILVAL } +}; + +static const LUA_REG_TYPE sdmmc_map[] = { + { LSTRKEY( "init" ), LFUNCVAL( lsdmmc_init ) }, + { LSTRKEY( "HS1" ), LNUMVAL( SDMMC_HOST_SLOT_0 ) }, + { LSTRKEY( "HS2" ), LNUMVAL( SDMMC_HOST_SLOT_1 ) }, + { LSTRKEY( "W1BIT" ), LNUMVAL( SDMMC_HOST_FLAG_1BIT ) }, + { LSTRKEY( "W4BIT" ), LNUMVAL( SDMMC_HOST_FLAG_1BIT | + SDMMC_HOST_FLAG_4BIT ) }, + { LSTRKEY( "W8BIT" ), LNUMVAL( SDMMC_HOST_FLAG_1BIT | + SDMMC_HOST_FLAG_4BIT | + SDMMC_HOST_FLAG_8BIT ) }, + { LNILKEY, LNILVAL } +}; + +static int luaopen_sdmmc( lua_State *L ) +{ + luaL_rometatable(L, "sdmmc.card", (void *)sdmmc_card_map); + + return 0; +} + +NODEMCU_MODULE(SDMMC, "sdmmc", sdmmc_map, luaopen_sdmmc); diff --git a/components/platform/Kconfig b/components/platform/Kconfig index 57a53bf0..0a850d36 100644 --- a/components/platform/Kconfig +++ b/components/platform/Kconfig @@ -105,6 +105,7 @@ config BUILD_SPIFFS config BUILD_FATFS bool "Support for FAT filesystems" default "n" + select LUA_MODULE_SDMMC help Include support for accessing FAT filesystems on SD cards. diff --git a/components/platform/include/sdcard.h b/components/platform/include/sdcard.h deleted file mode 100644 index f6bb4133..00000000 --- a/components/platform/include/sdcard.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef _SDCARD_H -#define _SDCARD_H - -#include -#include - -int platform_sdcard_init( uint8_t spi_no, uint8_t ss_pin ); -int platform_sdcard_status( void ); -int platform_sdcard_error( void ); -int platform_sdcard_type( void ); -int platform_sdcard_read_block( uint8_t ss_pin, uint32_t block, uint8_t *dst ); -int platform_sdcard_read_blocks( uint8_t ss_pin, uint32_t block, size_t num, uint8_t *dst ); -int platform_sdcard_read_csd( uint8_t ss_pin, uint8_t *csd ); -int platform_sdcard_read_cid( uint8_t ss_pin, uint8_t *cid ); -int platform_sdcard_write_block( uint8_t ss_pin, uint32_t block, const uint8_t *src ); -int platform_sdcard_write_blocks( uint8_t ss_pin, uint32_t block, size_t num, const uint8_t *src ); - -#endif diff --git a/components/spiffs/spiffs.c b/components/spiffs/spiffs.c index 4132d230..078981bb 100644 --- a/components/spiffs/spiffs.c +++ b/components/spiffs/spiffs.c @@ -214,8 +214,6 @@ static int32_t myspiffs_vfs_format( void ); static int32_t myspiffs_vfs_errno( void ); static void myspiffs_vfs_clearerr( void ); -static int32_t myspiffs_vfs_umount( const struct vfs_vol *vol ); - // --------------------------------------------------------------------------- // function tables // @@ -310,16 +308,6 @@ static const char *myspiffs_vfs_name( const struct vfs_item *di ) { } -// --------------------------------------------------------------------------- -// volume functions -// -static int32_t myspiffs_vfs_umount( const struct vfs_vol *vol ) { - // not implemented - - return VFS_RES_ERR; -} - - // --------------------------------------------------------------------------- // dir functions // diff --git a/docs/en/modules/file.md b/docs/en/modules/file.md index 8db14f1f..8f688621 100644 --- a/docs/en/modules/file.md +++ b/docs/en/modules/file.md @@ -1,7 +1,7 @@ # file Module | Since | Origin / Contributor | Maintainer | Source | | :----- | :-------------------- | :---------- | :------ | -| 2014-12-22 | [Zeroday](https://github.com/funshine) | [Zeroday](https://github.com/funshine) | [file.c](../../../app/modules/file.c)| +| 2014-12-22 | [Zeroday](https://github.com/funshine) | [Zeroday](https://github.com/funshine) | [file.c](../../../components/modules/file.c)| The file module provides access to the file system and its individual files. @@ -206,28 +206,6 @@ for k,v in pairs(l) do end ``` -## file.mount() - -Mounts a FatFs volume on SD card. - -Not supported for internal flash. - -#### Syntax -`file.mount(ldrv[, pin])` - -#### Parameters -- `ldrv` name of the logical drive, `SD0:`, `SD1:`, etc. -- `pin` 1~12, IO index for SS/CS, defaults to 8 if omitted. - -#### Returns -Volume object - -#### Example -```lua -vol = file.mount("SD0:") -vol:umount() -``` - ## file.on() Registers callback functions. diff --git a/docs/en/modules/sdmmc.md b/docs/en/modules/sdmmc.md new file mode 100644 index 00000000..ab5ac86f --- /dev/null +++ b/docs/en/modules/sdmmc.md @@ -0,0 +1,155 @@ +# SDMMC Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2017-04-17 | [Arnim Läuger](https://github.com/devsaurus) | [Arnim Läuger](https://github.com/devsaurus) | [sdmmc.c](../../../components/modules/sdmmc.c)| + +!!! note + + MMC cards are not yet supported due to missing functionality in the IDF driver. + +## SD Card connection + +The SD card is operated in SDMMC mode, thus the card has to be wired to the ESP pins of the HS1_* or HS2_* interfaces. There are several naming schemes used on different adapters - the following list shows alternative terms: + +| SD mode name | SPI mode name | ESP32 HS1 I/F | ESP32 HS2 I/F | +| :--- | :--- | :--- | :--- | +| `CLK` | `CK, SCLK` | `GPIO6, HS1_CLK, SD_CLK` | `GPIO14, HS2_CLK, MTMS` | +| `CMD` | `DI, MOSI` | `GPIO11, HS1_CMD, SD_CMD` | `GPIO15, HS2_CMD, MTDO` | +| `DAT0` | `DO, MISO` | `GPIO7, HS1_DATA0, SD_DATA_0` | `GPIO2, HS2_DATA0` | +| `DAT1` | n/a | `GPIO8, HS1_DATA1, SD_DATA_1` | `GPIO4, HS2_DATA1` | +| `DAT2` | n/a | `GPIO9, HS1_DATA2, SD_DATA_2` | `GPIO12, HS2_DATA2, MTDI` | +| `DAT3` | `CS, SS` | `GPIO10, HS1_DATA3, SD_DATA_3` | `GPIO13, HS2_DATA3, MTCK` | +| `DAT4` (MMC) | n/a | `GPIO16, HS1_DATA4` | n/a | +| `DAT5` (MMC) | n/a | `GPIO17, HS1_DATA5` | n/a | +| `DAT6` (MMC) | n/a | `GPIO5, HS1_DATA6` | n/a | +| `DAT7` (MMC) | n/a | `GPIO18, HS1_DATA7` | n/a | +| `VDD` | `VCC, VDD` | 3V3 supply | 3V3 supply | +| `VSS1, VSS2` | `VSS, GND` | common ground | common ground | + +Connections to `CLK`, `CMD`, and `DAT0` are mandatory and enable basic operation in 1-bit mode. For 4-bit mode `DAT1`, `DAT2`, and `DAT3` are required additionally. + +!!! important + + Connecting DAT0 to GPIO2 can block firmware flashing depending on the electrical configuration at this pin. Disconnect GPIO2 from the card adapter during flashing if unsure. + +!!! caution + + The adapter does not require level shifters since SD and ESP are supposed to be powered with the same voltage. If your specific model contains level shifters then make sure that both sides can be operated at 3V3. + +![1:1 micro-sd adapter](../../img/micro_sd-small.jpg "1:1 micro-sd adapter") + +## sdmmc.init() +Initialize the SDMMC and probe the attached SD card. + +#### Syntax +`sdmmc.init(slot[, cfg])` + +#### Parameters +- `slot` SDMMC slot, one of `sdmmc.HS1` or `sdmmc.HS2` +- `cfg` optional table containing slot configuration: + - `cd_pin` card detect pin, none if omitted + - `wp_pin` write-protcet pin, none if omitted + - `fmax` maximum communication frequency, defaults to 20  if omitted + - `width` bis width, defaults to `sdmmc.W1BIT` if omitted, one of: + - `sdmmc.W1BIT` + - `sdmmc.W4BIT` + - `sdmmc.W8BIT`, not supported yet + +#### Returns +Card object. + +Error is thrown for invalid parameters or if SDMMC hardware or card cannot be initialized. + + +## card:get_info() +Retrieve information from the SD card. + +#### Syntax +`card:get_info()` + +#### Returns +Table containing the card's OCR, CID, CSD, SCR, and RCA with elements: + +- `ocr` Operation Conditions Register +- `cid` Card IDentification + - `date` manufacturing date + - `mfg_id` manufacturer ID + - `name` product name + - `oem_id` OEM/product ID + - `revision` product revision + - `serial` product serial number +- `csd` Card-Specific Data + - `capacity` total number of sectors + - `card_command_class` card command class for SD + - `csd_ver` CSD structure format + - `mmc_ver` MMC version (for CID format) + - `read_block_len` block length for reads + - `sector_size` sector size in bytes + - `tr_speed` maximum transfer speed +- `scr` + - `sd_spec` SD physical layer specification version, reported by card + - `bus_width` bus widths supported by card +- `rca` Relative Card Address + + +## card:mount() +Mount filesystem on SD card. + +#### Syntax +`card:mount(ldrv[, slot])` + +#### Parameters +- `ldrv` name of logical drive, "/SD0", "/SD1", etc. +- `slot` one of `sdmmc.HS1` or `sdmmc.HS2`, defaults to `sdmmc.HS2` if omitted + +#### Returns +`true` if successful, `false` otherwise + +Error is thrown for invalid parameters or if filesystem is already mounted. + + +## card:read() +Read one or more sectors. + +#### Syntax +`card:read(start_sec, num_sec)` + +#### Parameters +- `start_sec` first sector to read from +- `num_sec` number of sectors to read (>= 1) + +#### Returns +String containing the sector data. + +Error is thrown for invalid parameters or if sector(s) cannot be read. + + +## card:umount() +Unmount filesystem. + +#### Syntax +`card:umount()` + +#### Parameters +None + +#### Returns +`nil` + +Error is thrown if filesystem is not mounted or if it cannot be unmounted. + + +## card:write() +Write one or more sectors. + +#### Syntax +`card:write(start_sec, data)` + +#### Parameters +- `start_sec` first sector to write to +- `data` string of data to write, must be multiple fo sector size (512 bytes) + +#### Returns +`nil` + +Error is thrown for invalid parameters or if sector(s) cannot be written. diff --git a/docs/en/sdcard.md b/docs/en/sdcard.md new file mode 100644 index 00000000..867c8d82 --- /dev/null +++ b/docs/en/sdcard.md @@ -0,0 +1,43 @@ +# FAT File System on SD Card + +Accessing files on external SD cards is currently only supported from the `file` module. This imposes the same overall restrictions of internal SPIFFS to SD cards: + +- limited support for sub-folders +- no timestamps +- no file attributes (read-only, system, etc.) + +Work is in progress to extend the `file` API with support for the missing features. + +## Enabling FatFs + +The FAT file system is implemented by [Chan's FatFs](http://elm-chan.org/fsw/ff/00index_e.html) version [R0.12a](http://elm-chan.org/fsw/ff/ff12a.zip). It's disabled by default to save memory space and has to be enabled before compiling the firmware: + +Enable "Support for FAT filesystems" in Comoponent config ---> Platform config and enable the sdmmc module for low-level control. + +## SD Card connection + +Refer to the [`sdmmc` module documentation](modules/sdmmc.md). + +## Lua bindings + +Before mounting the volume(s) on the SD card, you need to initialize the SDMMC interface from Lua. + +```lua +card = sdmmc.init(sdmmc.HS2, {width = sdmmc.W1BIT}) + +-- then mount the sd +-- note: the card initialization process during `card:mount()` will set spi divider temporarily to 200 (400 kHz) +-- it's reverted back to the current user setting before `card:mount()` finishes +card:mount("/SD0") +file.open("/SD0/path/to/somefile") +print(file.read()) +file.close() +``` + +The logical drives are mounted at the root of a unified directory tree where the mount points distinguish between internal flash (`/FLASH`) and the card's paritions (`/SD0` to `/SD3`). Files are accessed via either the absolute hierarchical path or relative to the current working directory. It defaults to `/FLASH` and can be changed with `file.chdir(path)`. + +Subdirectories are supported on FAT volumes only. + +## Multiple partitions / multiple cards + +The mapping from logical volumes (eg. `/SD0`) to partitions on an SD card is defined in [`fatfs_config.h`](../../components/fatfs/fatfs_config.h). More volumes can be added to the `VolToPart` array with any combination of physical drive number (aka SDMMC slots) and partition number. Their names have to be added to `_VOLUME_STRS` in [`ffconf.h`](../../components/fatfs/ffconf.h) as well. diff --git a/docs/img/micro_sd-small.jpg b/docs/img/micro_sd-small.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d97824b460accb1164409546af6e6ceff34b84e2 GIT binary patch literal 11948 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 TS%T!?p8N(hvH*4z<>&tcKHL_| literal 0 HcmV?d00001 diff --git a/docs/img/micro_sd.jpg b/docs/img/micro_sd.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9092c3fbbe4047182a2eefa8d10b5f8e10437ae8 GIT binary patch literal 36986 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