Commit ecf8bd98 authored by Arnim Läuger's avatar Arnim Läuger Committed by Marcel Stör
Browse files

Add FatFs and SD card support (#1397)

* Add FatFs
* enable BUILD_FATFS for all-module build
* push vfs into rest of firmware
* align maximum filename length
* increase timeout for acmd41 during card initialization
* switch from DOS to Unix path semantics chdrive() is substituted by chdir()
* update to fatfs R.012a incl. patches 1-6
* add callback for rtc provisioning in file
* update docs
parent 99cd2177
#include "platform.h"
#include "driver/spi.h"
#include "c_types.h"
#include "sdcard.h"
#define CHECK_SSPIN(pin) \
if (pin < 1 || pin > NUM_GPIO) return FALSE; \
m_ss_pin = pin;
//==============================================================================
// SD card commands
/** GO_IDLE_STATE - init card in spi mode if CS low */
uint8_t const CMD0 = 0X00;
/** SEND_IF_COND - verify SD Memory Card interface operating condition.*/
uint8_t const CMD8 = 0X08;
/** SEND_CSD - read the Card Specific Data (CSD register) */
uint8_t const CMD9 = 0X09;
/** SEND_CID - read the card identification information (CID register) */
uint8_t const CMD10 = 0X0A;
/** STOP_TRANSMISSION - end multiple block read sequence */
uint8_t const CMD12 = 0X0C;
/** SEND_STATUS - read the card status register */
uint8_t const CMD13 = 0X0D;
/** READ_SINGLE_BLOCK - read a single data block from the card */
uint8_t const CMD17 = 0X11;
/** READ_MULTIPLE_BLOCK - read a multiple data blocks from the card */
uint8_t const CMD18 = 0X12;
/** WRITE_BLOCK - write a single data block to the card */
uint8_t const CMD24 = 0X18;
/** WRITE_MULTIPLE_BLOCK - write blocks of data until a STOP_TRANSMISSION */
uint8_t const CMD25 = 0X19;
/** ERASE_WR_BLK_START - sets the address of the first block to be erased */
uint8_t const CMD32 = 0X20;
/** ERASE_WR_BLK_END - sets the address of the last block of the continuous
range to be erased*/
uint8_t const CMD33 = 0X21;
/** ERASE - erase all previously selected blocks */
uint8_t const CMD38 = 0X26;
/** APP_CMD - escape for application specific command */
uint8_t const CMD55 = 0X37;
/** READ_OCR - read the OCR register of a card */
uint8_t const CMD58 = 0X3A;
/** CRC_ON_OFF - enable or disable CRC checking */
uint8_t const CMD59 = 0X3B;
/** SET_WR_BLK_ERASE_COUNT - Set the number of write blocks to be
pre-erased before writing */
uint8_t const ACMD23 = 0X17;
/** SD_SEND_OP_COMD - Sends host capacity support information and
activates the card's initialization process */
uint8_t const ACMD41 = 0X29;
//==============================================================================
/** status for card in the ready state */
uint8_t const R1_READY_STATE = 0X00;
/** status for card in the idle state */
uint8_t const R1_IDLE_STATE = 0X01;
/** status bit for illegal command */
uint8_t const R1_ILLEGAL_COMMAND = 0X04;
/** start data token for read or write single block*/
uint8_t const DATA_START_BLOCK = 0XFE;
/** stop token for write multiple blocks*/
uint8_t const STOP_TRAN_TOKEN = 0XFD;
/** start data token for write multiple blocks*/
uint8_t const WRITE_MULTIPLE_TOKEN = 0XFC;
/** mask for data response tokens after a write block operation */
uint8_t const DATA_RES_MASK = 0X1F;
/** write data accepted token */
uint8_t const DATA_RES_ACCEPTED = 0X05;
//------------------------------------------------------------------------------
// SD card errors
/** timeout error for command CMD0 (initialize card in SPI mode) */
uint8_t const SD_CARD_ERROR_CMD0 = 0X1;
/** CMD8 was not accepted - not a valid SD card*/
uint8_t const SD_CARD_ERROR_CMD8 = 0X2;
/** card returned an error response for CMD12 (stop multiblock read) */
uint8_t const SD_CARD_ERROR_CMD12 = 0X3;
/** card returned an error response for CMD17 (read block) */
uint8_t const SD_CARD_ERROR_CMD17 = 0X4;
/** card returned an error response for CMD18 (read multiple block) */
uint8_t const SD_CARD_ERROR_CMD18 = 0X5;
/** card returned an error response for CMD24 (write block) */
uint8_t const SD_CARD_ERROR_CMD24 = 0X6;
/** WRITE_MULTIPLE_BLOCKS command failed */
uint8_t const SD_CARD_ERROR_CMD25 = 0X7;
/** card returned an error response for CMD58 (read OCR) */
uint8_t const SD_CARD_ERROR_CMD58 = 0X8;
/** SET_WR_BLK_ERASE_COUNT failed */
uint8_t const SD_CARD_ERROR_ACMD23 = 0X9;
/** ACMD41 initialization process timeout */
uint8_t const SD_CARD_ERROR_ACMD41 = 0XA;
/** card returned a bad CSR version field */
uint8_t const SD_CARD_ERROR_BAD_CSD = 0XB;
/** erase block group command failed */
uint8_t const SD_CARD_ERROR_ERASE = 0XC;
/** card not capable of single block erase */
uint8_t const SD_CARD_ERROR_ERASE_SINGLE_BLOCK = 0XD;
/** Erase sequence timed out */
uint8_t const SD_CARD_ERROR_ERASE_TIMEOUT = 0XE;
/** card returned an error token instead of read data */
uint8_t const SD_CARD_ERROR_READ = 0XF;
/** read CID or CSD failed */
uint8_t const SD_CARD_ERROR_READ_REG = 0X10;
/** timeout while waiting for start of read data */
uint8_t const SD_CARD_ERROR_READ_TIMEOUT = 0X11;
/** card did not accept STOP_TRAN_TOKEN */
uint8_t const SD_CARD_ERROR_STOP_TRAN = 0X12;
/** card returned an error token as a response to a write operation */
uint8_t const SD_CARD_ERROR_WRITE = 0X13;
/** attempt to write protected block zero */
uint8_t const SD_CARD_ERROR_WRITE_BLOCK_ZERO = 0X14; // REMOVE - not used
/** card did not go ready for a multiple block write */
uint8_t const SD_CARD_ERROR_WRITE_MULTIPLE = 0X15;
/** card returned an error to a CMD13 status check after a write */
uint8_t const SD_CARD_ERROR_WRITE_PROGRAMMING = 0X16;
/** timeout occurred during write programming */
uint8_t const SD_CARD_ERROR_WRITE_TIMEOUT = 0X17;
/** incorrect rate selected */
uint8_t const SD_CARD_ERROR_SCK_RATE = 0X18;
/** init() not called */
uint8_t const SD_CARD_ERROR_INIT_NOT_CALLED = 0X19;
/** card returned an error for CMD59 (CRC_ON_OFF) */
uint8_t const SD_CARD_ERROR_CMD59 = 0X1A;
/** invalid read CRC */
uint8_t const SD_CARD_ERROR_READ_CRC = 0X1B;
/** SPI DMA error */
uint8_t const SD_CARD_ERROR_SPI_DMA = 0X1C;
//------------------------------------------------------------------------------
// card types
uint8_t const SD_CARD_TYPE_INVALID = 0;
/** Standard capacity V1 SD card */
uint8_t const SD_CARD_TYPE_SD1 = 1;
/** Standard capacity V2 SD card */
uint8_t const SD_CARD_TYPE_SD2 = 2;
/** High Capacity SD card */
uint8_t const SD_CARD_TYPE_SDHC = 3;
typedef struct {
uint32_t start, target;
} to_t;
static uint8_t m_spi_no, m_ss_pin, m_status, m_type, m_error;
static void sdcard_chipselect_low( void ) {
platform_gpio_write( m_ss_pin, PLATFORM_GPIO_LOW );
}
static void sdcard_chipselect_high( void ) {
platform_gpio_write( m_ss_pin, PLATFORM_GPIO_HIGH );
// send some cc to ensure that MISO returns to high
platform_spi_send_recv( m_spi_no, 8, 0xff );
}
static void set_timeout( to_t *to, uint32_t us )
{
uint32_t offset;
to->start = system_get_time();
offset = 0xffffffff - to->start;
if (offset > us) {
to->target = us - offset;
} else {
to->target = to->start + us;
}
}
static uint8_t timed_out( to_t *to )
{
uint32_t now = system_get_time();
if (to->start < to->target) {
if ((now >= to->start) && (now <= to->target)) {
return FALSE;
} else {
return TRUE;
}
} else {
if ((now >= to->start) || (now <= to->target)) {
return FALSE;
} else {
return TRUE;
}
}
}
static int sdcard_wait_not_busy( uint32_t us )
{
to_t to;
set_timeout( &to, us );
while (platform_spi_send_recv( m_spi_no, 8, 0xff ) != 0xff) {
if (timed_out( &to )) {
goto fail;
}
}
return TRUE;
fail:
return FALSE;
}
static uint8_t sdcard_command( uint8_t cmd, uint32_t arg )
{
sdcard_chipselect_low();
// wait until card is busy
sdcard_wait_not_busy( 100 * 1000 );
// send command
// with precalculated CRC - correct for CMD0 with arg zero or CMD8 with arg 0x1AA
const uint8_t crc = cmd == CMD0 ? 0x95 : 0x87;
platform_spi_transaction( m_spi_no, 16, (cmd | 0x40) << 8 | arg >> 24, 32, arg << 8 | crc, 0, 0, 0 );
// skip dangling byte of data transfer
if (cmd == CMD12) {
platform_spi_transaction( m_spi_no, 8, 0xff, 0, 0, 0, 0, 0 );
}
// wait for response
for (uint8_t i = 0; ((m_status = platform_spi_send_recv( m_spi_no, 8, 0xff )) & 0x80) && i != 0xFF; i++) ;
return m_status;
}
static uint8_t sdcard_acmd( uint8_t cmd, uint32_t arg ) {
sdcard_command( CMD55, 0 );
return sdcard_command( cmd, arg );
}
static int sdcard_write_data( uint8_t token, const uint8_t *src)
{
uint16_t crc = 0xffff;
platform_spi_transaction( m_spi_no, 8, token, 0, 0, 0, 0, 0 );
platform_spi_blkwrite( m_spi_no, 512, src );
platform_spi_transaction( m_spi_no, 16, crc, 0, 0, 0, 0, 0 );
m_status = platform_spi_send_recv( m_spi_no, 8, 0xff );
if ((m_status & DATA_RES_MASK) != DATA_RES_ACCEPTED) {
m_error = SD_CARD_ERROR_WRITE;
goto fail;
}
return TRUE;
fail:
sdcard_chipselect_high();
return FALSE;
}
static int sdcard_read_data( uint8_t *dst, size_t count )
{
to_t to;
// wait for start block token
set_timeout( &to, 100 * 1000 );
while ((m_status = platform_spi_send_recv( m_spi_no, 8, 0xff)) == 0xff) {
if (timed_out( &to )) {
goto fail;
}
}
if (m_status != DATA_START_BLOCK) {
m_error = SD_CARD_ERROR_READ;
goto fail;
}
// transfer data
platform_spi_blkread( m_spi_no, count, (void *)dst );
// discard crc
platform_spi_transaction( m_spi_no, 16, 0xffff, 0, 0, 0, 0, 0 );
sdcard_chipselect_high();
return TRUE;
fail:
sdcard_chipselect_high();
return FALSE;
}
static int sdcard_read_register( uint8_t cmd, uint8_t *buf )
{
if (sdcard_command( cmd, 0 )) {
m_error = SD_CARD_ERROR_READ_REG;
goto fail;
}
return sdcard_read_data( buf, 16 );
fail:
sdcard_chipselect_high();
return FALSE;
}
int platform_sdcard_init( uint8_t spi_no, uint8_t ss_pin )
{
uint32_t arg, user_spi_clkdiv;
to_t to;
m_type = SD_CARD_TYPE_INVALID;
m_error = 0;
if (spi_no > 1) {
return FALSE;
}
m_spi_no = spi_no;
CHECK_SSPIN(ss_pin);
platform_gpio_write( m_ss_pin, PLATFORM_GPIO_HIGH );
platform_gpio_mode( m_ss_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT );
// set SPI clock to 400 kHz for init phase
user_spi_clkdiv = spi_set_clkdiv( m_spi_no, 200 );
// apply initialization sequence:
// keep ss and io high, apply clock for max(1ms; 74cc)
// 1ms requires 400cc @ 400kHz
for (int i = 0; i < 2; i++) {
platform_spi_transaction( m_spi_no, 0, 0, 0, 0, 0, 200, 0 );
}
// command to go idle in SPI mode
set_timeout( &to, 500 * 1000 );
while (sdcard_command( CMD0, 0 ) != R1_IDLE_STATE) {
if (timed_out( &to )) {
goto fail;
}
}
set_timeout( &to, 500 * 1000 );
while (1) {
if (sdcard_command( CMD8, 0x1aa) == (R1_ILLEGAL_COMMAND | R1_IDLE_STATE)) {
m_type = SD_CARD_TYPE_SD1;
break;
}
for (uint8_t i = 0; i < 4; i++) {
m_status = platform_spi_send_recv( m_spi_no, 8, 0xff );
}
if (m_status == 0xaa) {
m_type = SD_CARD_TYPE_SD2;
break;
}
if (timed_out( &to )) {
goto fail;
}
}
// initialize card and send host supports SDHC if SD2
arg = m_type == SD_CARD_TYPE_SD2 ? 0x40000000 : 0;
set_timeout( &to, 500 * 1000 );
while (sdcard_acmd( ACMD41, arg ) != R1_READY_STATE) {
if (timed_out( &to )) {
goto fail;
}
}
// if SD2 read OCR register to check for SDHC card
if (m_type == SD_CARD_TYPE_SD2) {
if (sdcard_command( CMD58, 0 )) {
m_error = SD_CARD_ERROR_CMD58;
goto fail;
}
if ((platform_spi_send_recv( m_spi_no, 8, 0xff ) & 0xC0) == 0xC0) {
m_type = SD_CARD_TYPE_SDHC;
}
// Discard rest of ocr - contains allowed voltage range.
for (uint8_t i = 0; i < 3; i++) {
platform_spi_send_recv( m_spi_no, 8, 0xff);
}
}
sdcard_chipselect_high();
// re-apply user's spi clock divider
spi_set_clkdiv( m_spi_no, user_spi_clkdiv );
return TRUE;
fail:
sdcard_chipselect_high();
return FALSE;
}
int platform_sdcard_status( void )
{
return m_status;
}
int platform_sdcard_error( void )
{
return m_error;
}
int platform_sdcard_type( void )
{
return m_type;
}
int platform_sdcard_read_block( uint8_t ss_pin, uint32_t block, uint8_t *dst )
{
CHECK_SSPIN(ss_pin);
// generate byte address for pre-SDHC types
if (m_type != SD_CARD_TYPE_SDHC) {
block <<= 9;
}
if (sdcard_command( CMD17, block )) {
m_error = SD_CARD_ERROR_CMD17;
goto fail;
}
return sdcard_read_data( dst, 512 );
fail:
sdcard_chipselect_high();
return FALSE;
}
int platform_sdcard_read_blocks( uint8_t ss_pin, uint32_t block, size_t num, uint8_t *dst )
{
CHECK_SSPIN(ss_pin);
if (num == 0) {
return TRUE;
}
if (num == 1) {
return platform_sdcard_read_block( ss_pin, block, dst );
}
// generate byte address for pre-SDHC types
if (m_type != SD_CARD_TYPE_SDHC) {
block <<= 9;
}
// command READ_MULTIPLE_BLOCKS
if (sdcard_command( CMD18, block )) {
m_error = SD_CARD_ERROR_CMD18;
goto fail;
}
// read required blocks
while (num > 0) {
sdcard_chipselect_low();
if (sdcard_read_data( dst, 512 )) {
num--;
dst = &(dst[512]);
} else {
break;
}
}
// issue command STOP_TRANSMISSION
if (sdcard_command( CMD12, 0 )) {
m_error = SD_CARD_ERROR_CMD12;
goto fail;
}
sdcard_chipselect_high();
return TRUE;
fail:
sdcard_chipselect_high();
return FALSE;
}
int platform_sdcard_read_csd( uint8_t ss_pin, uint8_t *csd )
{
CHECK_SSPIN(ss_pin);
return sdcard_read_register( CMD9, csd );
}
int platform_sdcard_read_cid( uint8_t ss_pin, uint8_t *cid )
{
CHECK_SSPIN(ss_pin);
return sdcard_read_register( CMD10, cid );
}
int platform_sdcard_write_block( uint8_t ss_pin, uint32_t block, const uint8_t *src )
{
CHECK_SSPIN(ss_pin);
// generate byte address for pre-SDHC types
if (m_type != SD_CARD_TYPE_SDHC) {
block <<= 9;
}
if (sdcard_command( CMD24, block )) {
m_error = SD_CARD_ERROR_CMD24;
goto fail;
}
if (! sdcard_write_data( DATA_START_BLOCK, src )) {
goto fail;
}
sdcard_chipselect_high();
return TRUE;
fail:
sdcard_chipselect_high();
return FALSE;
}
static int sdcard_write_stop( void )
{
sdcard_chipselect_low();
if (! sdcard_wait_not_busy( 100 * 1000 )) {
goto fail;
}
platform_spi_transaction( m_spi_no, 8, STOP_TRAN_TOKEN, 0, 0, 0, 0, 0 );
if (! sdcard_wait_not_busy( 100 * 1000 )) {
goto fail;
}
sdcard_chipselect_high();
return TRUE;
fail:
m_error = SD_CARD_ERROR_STOP_TRAN;
sdcard_chipselect_high();
return FALSE;
}
int platform_sdcard_write_blocks( uint8_t ss_pin, uint32_t block, size_t num, const uint8_t *src )
{
CHECK_SSPIN(ss_pin);
if (sdcard_acmd( ACMD23, num )) {
m_error = SD_CARD_ERROR_ACMD23;
goto fail;
}
// generate byte address for pre-SDHC types
if (m_type != SD_CARD_TYPE_SDHC) {
block <<= 9;
}
if (sdcard_command( CMD25, block )) {
m_error = SD_CARD_ERROR_CMD25;
goto fail;
}
sdcard_chipselect_high();
for (size_t b = 0; b < num; b++, src += 512) {
sdcard_chipselect_low();
// wait for previous write to finish
if (! sdcard_wait_not_busy( 100 * 1000 )) {
goto fail_write;
}
if (! sdcard_write_data( WRITE_MULTIPLE_TOKEN, src )) {
goto fail_write;
}
sdcard_chipselect_high();
}
return sdcard_write_stop();
fail_write:
m_error = SD_CARD_ERROR_WRITE_MULTIPLE;
fail:
sdcard_chipselect_high();
return FALSE;
}
#ifndef _SDCARD_H
#define _SDCARD_H
#include "c_types.h"
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
#include "c_stdlib.h"
#include "vfs.h"
#define LDRV_TRAVERSAL 0
// ---------------------------------------------------------------------------
// RTC system interface
//
static sint32_t (*rtc_cb)( vfs_time *tm ) = NULL;
// called by operating system
void vfs_register_rtc_cb( sint32_t (*cb)( vfs_time *tm ) )
{
// allow NULL pointer to unregister callback function
rtc_cb = cb;
}
// called by file system drivers
sint32_t vfs_get_rtc( vfs_time *tm )
{
if (rtc_cb) {
return rtc_cb( tm );
}
return VFS_RES_ERR;
}
static int dir_level = 1;
static const char *normalize_path( const char *path )
{
#if ! LDRV_TRAVERSAL
return path;
#else
const char *temp = path;
size_t len;
while ((len = c_strlen( temp )) >= 2) {
if (temp[0] == '.' && temp[1] == '.') {
--dir_level;
if (len >= 4 && dir_level > 0) {
// prepare next step
temp = &(temp[4]);
} else {
// we're at top, the remainder is expected be an absolute path
temp = &(temp[3]);
}
} else {
break;
}
}
if (dir_level > 0) {
// no traversal on root level
return path;
} else {
// path traverses via root
return temp;
}
#endif
}
// ---------------------------------------------------------------------------
// file system functions
//
vfs_vol *vfs_mount( const char *name, int num )
{
vfs_fs_fns *fs_fns;
const char *normname = normalize_path( name );
char *outname;
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( normname, &outname, FALSE )) {
return fs_fns->mount( outname, num );
}
#endif
#ifdef BUILD_FATFS
if (fs_fns = myfatfs_realm( normname, &outname, FALSE )) {
vfs_vol *r = fs_fns->mount( outname, num );
c_free( outname );
return r;
}
#endif
return NULL;
}
int vfs_open( const char *name, const char *mode )
{
vfs_fs_fns *fs_fns;
const char *normname = normalize_path( name );
char *outname;
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( normname, &outname, FALSE )) {
return (int)fs_fns->open( outname, mode );
}
#endif
#ifdef BUILD_FATFS
if (fs_fns = myfatfs_realm( normname, &outname, FALSE )) {
int r = (int)fs_fns->open( outname, mode );
c_free( outname );
return r;
}
#endif
return 0;
}
vfs_dir *vfs_opendir( const char *name )
{
vfs_fs_fns *fs_fns;
const char *normname = normalize_path( name );
char *outname;
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( normname, &outname, FALSE )) {
return fs_fns->opendir( outname );
}
#endif
#ifdef BUILD_FATFS
if (fs_fns = myfatfs_realm( normname, &outname, FALSE )) {
vfs_dir *r = fs_fns->opendir( outname );
c_free( outname );
return r;
}
#endif
return NULL;
}
vfs_item *vfs_stat( const char *name )
{
vfs_fs_fns *fs_fns;
const char *normname = normalize_path( name );
char *outname;
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( normname, &outname, FALSE )) {
return fs_fns->stat( outname );
}
#endif
#ifdef BUILD_FATFS
if (fs_fns = myfatfs_realm( normname, &outname, FALSE )) {
vfs_item *r = fs_fns->stat( outname );
c_free( outname );
return r;
}
#endif
return NULL;
}
sint32_t vfs_remove( const char *name )
{
vfs_fs_fns *fs_fns;
const char *normname = normalize_path( name );
char *outname;
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( normname, &outname, FALSE )) {
return fs_fns->remove( outname );
}
#endif
#ifdef BUILD_FATFS
if (fs_fns = myfatfs_realm( normname, &outname, FALSE )) {
sint32_t r = fs_fns->remove( outname );
c_free( outname );
return r;
}
#endif
return VFS_RES_ERR;
}
sint32_t vfs_rename( const char *oldname, const char *newname )
{
vfs_fs_fns *fs_fns;
const char *normoldname = normalize_path( oldname );
const char *normnewname = normalize_path( newname );
char *oldoutname, *newoutname;
#ifdef BUILD_SPIFFS
if (myspiffs_realm( normoldname, &oldoutname, FALSE )) {
if (fs_fns = myspiffs_realm( normnewname, &newoutname, FALSE )) {
return fs_fns->rename( oldoutname, newoutname );
}
}
#endif
#ifdef BUILD_FATFS
if (myfatfs_realm( normoldname, &oldoutname, FALSE )) {
if (fs_fns = myfatfs_realm( normnewname, &newoutname, FALSE )) {
sint32_t r = fs_fns->rename( oldoutname, newoutname );
c_free( oldoutname );
c_free( newoutname );
return r;
}
c_free( oldoutname );
}
#endif
return -1;
}
sint32_t vfs_mkdir( const char *name )
{
vfs_fs_fns *fs_fns;
const char *normname = normalize_path( name );
char *outname;
#ifdef BUILD_SPIFFS
// not supported
#endif
#ifdef BUILD_FATFS
if (fs_fns = myfatfs_realm( normname, &outname, FALSE )) {
sint32_t r = fs_fns->mkdir( outname );
c_free( outname );
return r;
}
#endif
return VFS_RES_ERR;
}
sint32_t vfs_fsinfo( const char *name, uint32_t *total, uint32_t *used )
{
vfs_fs_fns *fs_fns;
char *outname;
if (!name) name = ""; // current drive
const char *normname = normalize_path( name );
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( normname, &outname, FALSE )) {
return fs_fns->fsinfo( total, used );
}
#endif
#ifdef BUILD_FATFS
if (fs_fns = myfatfs_realm( normname, &outname, FALSE )) {
c_free( outname );
return fs_fns->fsinfo( total, used );
}
#endif
return VFS_RES_ERR;
}
sint32_t vfs_fscfg( const char *name, uint32_t *phys_addr, uint32_t *phys_size)
{
vfs_fs_fns *fs_fns;
char *outname;
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( "/FLASH", &outname, FALSE )) {
return fs_fns->fscfg( phys_addr, phys_size );
}
#endif
#ifdef BUILD_FATFS
// not supported
#endif
// Error
return VFS_RES_ERR;
}
sint32_t vfs_format( void )
{
vfs_fs_fns *fs_fns;
char *outname;
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( "/FLASH", &outname, FALSE )) {
return fs_fns->format();
}
#endif
#ifdef BUILD_FATFS
// not supported
#endif
// Error
return 0;
}
sint32_t vfs_chdir( const char *path )
{
vfs_fs_fns *fs_fns;
const char *normpath = normalize_path( path );
const char *level;
char *outname;
int ok = VFS_RES_ERR;
#if LDRV_TRAVERSAL
// track dir level
if (normpath[0] == '/') {
dir_level = 0;
level = &(normpath[1]);
} else {
level = normpath;
}
while (c_strlen( level ) > 0) {
dir_level++;
if (level = c_strchr( level, '/' )) {
level++;
} else {
break;
}
}
#endif
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( normpath, &outname, TRUE )) {
// our SPIFFS integration doesn't support directories
if (c_strlen( outname ) == 0) {
ok = VFS_RES_OK;
}
}
#endif
#ifdef BUILD_FATFS
if (fs_fns = myfatfs_realm( normpath, &outname, TRUE )) {
if (c_strchr( outname, ':' )) {
// need to set FatFS' default drive
fs_fns->chdrive( outname );
// and force chdir to root in case path points only to root
fs_fns->chdir( "/" );
}
if (fs_fns->chdir( outname ) == VFS_RES_OK) {
ok = VFS_RES_OK;
}
c_free( outname );
}
#endif
return ok == VFS_RES_OK ? VFS_RES_OK : VFS_RES_ERR;
}
sint32_t vfs_errno( const char *name )
{
vfs_fs_fns *fs_fns;
const char *normname = normalize_path( name );
char *outname;
if (!name) name = ""; // current drive
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( normname, &outname, FALSE )) {
return fs_fns->ferrno( );
}
#endif
#ifdef BUILD_FATFS
if (fs_fns = myfatfs_realm( normname, &outname, FALSE )) {
sint32_t r = fs_fns->ferrno( );
c_free( outname );
return r;
}
#endif
return VFS_RES_ERR;
}
sint32_t vfs_ferrno( int fd )
{
vfs_file *f = (vfs_file *)fd;
if (f) {
return f->fns->ferrno ? f->fns->ferrno( f ) : 0;
} else {
vfs_fs_fns *fs_fns;
const char *name = ""; // current drive
char *outname;
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( name, &outname, FALSE )) {
return fs_fns->ferrno( );
}
#endif
#ifdef BUILD_FATFS
if (fs_fns = myfatfs_realm( name, &outname, FALSE )) {
sint32_t r = fs_fns->ferrno( );
c_free( outname );
return r;
}
#endif
}
}
void vfs_clearerr( const char *name )
{
vfs_fs_fns *fs_fns;
char *outname;
if (!name) name = ""; // current drive
const char *normname = normalize_path( name );
#ifdef BUILD_SPIFFS
if (fs_fns = myspiffs_realm( normname, &outname, FALSE )) {
fs_fns->clearerr( );
}
#endif
#ifdef BUILD_FATFS
if (fs_fns = myfatfs_realm( normname, &outname, FALSE )) {
fs_fns->clearerr( );
c_free( outname );
}
#endif
}
const char *vfs_basename( const char *path )
{
const char *basename;
// deduce basename (incl. extension) for length check
if (basename = c_strrchr( path, '/' )) {
basename++;
} else if (basename = c_strrchr( path, ':' )) {
basename++;
} else {
basename = path;
}
return basename;
}
// ---------------------------------------------------------------------------
// supplementary functions
//
int vfs_getc( int fd )
{
unsigned char c = 0xFF;
sint32_t res;
if(!vfs_eof( fd )) {
if (1 != vfs_read( fd, &c, 1 )) {
NODE_DBG("getc errno %i\n", vfs_ferrno( fd ));
return VFS_EOF;
} else {
return (int)c;
}
}
return VFS_EOF;
}
int vfs_ungetc( int c, int fd )
{
return vfs_lseek( fd, -1, VFS_SEEK_CUR );
}
#ifndef __VFS_H__
#define __VFS_H__
#include "vfs_int.h"
// DEPRECATED, DON'T USE
// Check for fd != 0 instead
#define FS_OPEN_OK 1
// ---------------------------------------------------------------------------
// file functions
//
// vfs_close - close file descriptor and free memory
// fd: file descriptor
// Returns: VFS_RES_OK or negative value in case of error
inline sint32_t vfs_close( int fd ) {
vfs_file *f = (vfs_file *)fd;
return f ? f->fns->close( f ) : VFS_RES_ERR;
}
// vfs_read - read data from file
// fd: file descriptor
// ptr: destination data buffer
// len: requested length
// Returns: Number of bytes read, or VFS_RES_ERR in case of error
inline sint32_t vfs_read( int fd, void *ptr, size_t len ) {
vfs_file *f = (vfs_file *)fd;
return f ? f->fns->read( f, ptr, len ) : VFS_RES_ERR;
}
// vfs_write - write data to file
// fd: file descriptor
// ptr: source data buffer
// len: requested length
// Returns: Number of bytes written, or VFS_RES_ERR in case of error
inline sint32_t vfs_write( int fd, const void *ptr, size_t len ) {
vfs_file *f = (vfs_file *)fd;
return f ? f->fns->write( f, ptr, len ) : VFS_RES_ERR;
}
int vfs_getc( int fd );
int vfs_ungetc( int c, int fd );
// vfs_lseek - move read/write pointer
// fd: file descriptor
// off: offset
// whence: VFS_SEEK_SET - set pointer to off
// VFS_SEEK_CUR - set pointer to current position + off
// VFS_SEEK_END - set pointer to end of file + off
// Returns: New position, or VFS_RES_ERR in case of error
inline sint32_t vfs_lseek( int fd, sint32_t off, int whence ) {
vfs_file *f = (vfs_file *)fd;
return f ? f->fns->lseek( f, off, whence ) : VFS_RES_ERR;
}
// vfs_eof - test for end-of-file
// fd: file descriptor
// Returns: 0 if not at end, != 0 if end of file
inline sint32_t vfs_eof( int fd ) {
vfs_file *f = (vfs_file *)fd;
return f ? f->fns->eof( f ) : VFS_RES_ERR;
}
// vfs_tell - get read/write position
// fd: file descriptor
// Returns: Current position
inline sint32_t vfs_tell( int fd ) {
vfs_file *f = (vfs_file *)fd;
return f ? f->fns->tell( f ) : VFS_RES_ERR;
}
// vfs_flush - flush write cache to file
// fd: file descriptor
// Returns: VFS_RES_OK, or VFS_RES_ERR in case of error
inline sint32_t vfs_flush( int fd ) {
vfs_file *f = (vfs_file *)fd;
return f ? f->fns->flush( f ) : VFS_RES_ERR;
}
// vfs_size - get current file size
// fd: file descriptor
// Returns: File size
inline uint32_t vfs_size( int fd ) {
vfs_file *f = (vfs_file *)fd;
return f && f->fns->size ? f->fns->size( f ) : 0;
}
// vfs_ferrno - get file system specific errno
// fd: file descriptor
// Returns: errno
sint32_t vfs_ferrno( int fd );
// ---------------------------------------------------------------------------
// dir functions
//
// vfs_closedir - close directory descriptor and free memory
// dd: dir descriptor
// Returns: VFS_RES_OK, or VFS_RES_ERR in case of error
inline sint32_t vfs_closedir( vfs_dir *dd ) { return dd->fns->close( dd ); }
// vfs_readdir - read next directory item
// dd: dir descriptor
// Returns: item object, or NULL in case of error
inline vfs_item *vfs_readdir( vfs_dir *dd ) { return dd->fns->readdir( dd ); }
// ---------------------------------------------------------------------------
// dir item functions
//
// vfs_closeitem - close directory item and free memory
// di: item descriptor
// Returns: nothing
inline void vfs_closeitem( vfs_item *di ) { return di->fns->close( di ); }
// vfs_item_size - get item's size
// di: item descriptor
// Returns: Item size
inline uint32_t vfs_item_size( vfs_item *di ) { return di->fns->size( di ); }
// vfs_item_time - get item's modification time
// di: item descriptor
// Returns: Item modification time
inline sint32_t vfs_item_time( vfs_item *di, struct vfs_time *tm ) { return di->fns->time ? di->fns->time( di, tm ) : VFS_RES_ERR; }
// vfs_item_name - get item's name
// di: item descriptor
// Returns: Item name
inline const char *vfs_item_name( vfs_item *di ) { return di->fns->name( di ); }
// vfs_item_is_dir - check for directory
// di: item descriptor
// Returns: >0 if item is a directory, 0 if not
inline sint32_t vfs_item_is_dir( vfs_item *di ) { return di->fns->is_dir ? di->fns->is_dir( di ) : 0; }
// vfs_item_is_rdonly - check for read-only
// di: item descriptor
// Returns: >0 if item is read only, 0 if not
inline sint32_t vfs_item_is_rdonly( vfs_item *di ) { return di->fns->is_rdonly ? di->fns->is_rdonly( di ) : 0; }
// vfs_item_is_hidden - check for hidden attribute
// di: item descriptor
// Returns: >0 if item is hidden, 0 if not
inline sint32_t vfs_item_is_hidden( vfs_item *di ) { return di->fns->is_hidden ? di->fns->is_hidden( di ) : 0; }
// vfs_item_is_sys - check for sys attribute
// di: item descriptor
// Returns: >0 if item is sys, 0 if not
inline sint32_t vfs_item_is_sys( vfs_item *di ) { return di->fns->is_sys ? di->fns->is_sys( di ) : 0; }
// vfs_item_is_arch - check for archive attribute
// di: item descriptor
// Returns: >0 if item is archive, 0 if not
inline sint32_t vfs_item_is_arch( vfs_item *di ) { return di->fns->is_arch ? di->fns->is_arch( di ) : 0; }
// ---------------------------------------------------------------------------
// volume functions
//
// vfs_umount - unmount logical drive and free memory
// vol: volume object
// Returns: VFS_RES_OK, or VFS_RES_ERR in case of error
inline sint32_t vfs_umount( vfs_vol *vol ) { return vol->fns->umount( vol ); }
// ---------------------------------------------------------------------------
// file system functions
//
// vfs_mount - unmount logical drive
// name: name of logical drive
// num: drive's physical number (eg. SS/CS pin), negative values are ignored
// Returns: Volume object, or NULL in case of error
vfs_vol *vfs_mount( const char *name, int num );
// vfs_open - open file
// name: file name
// mode: open mode
// Returns: File descriptor, or NULL in case of error
int vfs_open( const char *name, const char *mode );
// vfs_opendir - open directory
// name: dir name
// Returns: Directory descriptor, or NULL in case of error
vfs_dir *vfs_opendir( const char *name );
// vfs_stat - stat file or directory
// name: file or directory name
// Returns: Item object, or NULL in case of error
vfs_item *vfs_stat( const char *name );
// vfs_remove - remove file or directory
// name: file or directory name
// Returns: VFS_RES_OK, or VFS_RES_ERR in case of error
sint32_t vfs_remove( const char *name );
// vfs_rename - rename file or directory
// name: file or directory name
// Returns: VFS_RES_OK, or VFS_RES_ERR in case of error
sint32_t vfs_rename( const char *oldname, const char *newname );
// vfs_mkdir - create directory
// name: directory name
// Returns: VFS_RES_OK, or VFS_RES_ERR in case of error
sint32_t vfs_mkdir( const char *name );
// vfs_fsinfo - get file system info
// name: logical drive identifier
// total: receives total amount
// used: received used amount
// Returns: VFS_RES_OK, or VFS_RES_ERR in case of error
sint32_t vfs_fsinfo( const char *name, uint32_t *total, uint32_t *used );
// vfs_format - format file system
// Returns: 1, or 0 in case of error
sint32_t vfs_format( void );
// vfs_chdir - change default directory
// path: new default directory
// Returns: VFS_RES_OK, or VFS_RES_ERR in case of error
sint32_t vfs_chdir( const char *path );
// vfs_fscfg - query configuration settings of file system
// phys_addr: pointer to store physical address information
// phys_size: pointer to store physical size information
// Returns: VFS_RES_OK, or VFS_RES_ERR in case of error
sint32_t vfs_fscfg( const char *name, uint32_t *phys_addr, uint32_t *phys_size);
// vfs_errno - get file system specific errno
// name: logical drive identifier
// Returns: errno
sint32_t vfs_errno( const char *name );
// vfs_clearerr - cleaer file system specific errno
void vfs_clearerr( const char *name );
// vfs_register_rtc_cb - register callback function for RTC query
// cb: pointer to callback function
void vfs_register_rtc_cb( sint32_t (*cb)( vfs_time *tm ) );
// vfs_basename - identify basename (incl. extension)
// path: full file system path
// Returns: pointer to basename within path string
const char *vfs_basename( const char *path );
#endif
// internal definitions for vfs
#ifndef __VFS_INT_H__
#define __VFS_INT_H__
#include <c_string.h>
#include <c_stdint.h>
#if 0
#include "spiffs.h"
#include "fatfs_prefix_lib.h"
#include "ff.h"
#endif
#define VFS_EOF -1
enum vfs_filesystems {
VFS_FS_NONE = 0,
VFS_FS_SPIFFS,
VFS_FS_FATFS
};
enum vfs_seek {
VFS_SEEK_SET = 0,
VFS_SEEK_CUR,
VFS_SEEK_END
};
enum vfs_result {
VFS_RES_OK = 0,
VFS_RES_ERR = -1
};
struct vfs_time {
int year, mon, day;
int hour, min, sec;
};
typedef struct vfs_time vfs_time;
// generic file descriptor
struct vfs_file {
int fs_type;
const struct vfs_file_fns *fns;
};
typedef const struct vfs_file vfs_file;
// file descriptor functions
struct vfs_file_fns {
sint32_t (*close)( const struct vfs_file *fd );
sint32_t (*read)( const struct vfs_file *fd, void *ptr, size_t len );
sint32_t (*write)( const struct vfs_file *fd, const void *ptr, size_t len );
sint32_t (*lseek)( const struct vfs_file *fd, sint32_t off, int whence );
sint32_t (*eof)( const struct vfs_file *fd );
sint32_t (*tell)( const struct vfs_file *fd );
sint32_t (*flush)( const struct vfs_file *fd );
uint32_t (*size)( const struct vfs_file *fd );
sint32_t (*ferrno)( const struct vfs_file *fd );
};
typedef const struct vfs_file_fns vfs_file_fns;
// generic dir item descriptor
struct vfs_item {
int fs_type;
const struct vfs_item_fns *fns;
};
typedef const struct vfs_item vfs_item;
// directory item functions
struct vfs_item_fns {
void (*close)( const struct vfs_item *di );
uint32_t (*size)( const struct vfs_item *di );
sint32_t (*time)( const struct vfs_item *di, struct vfs_time *tm );
const char *(*name)( const struct vfs_item *di );
sint32_t (*is_dir)( const struct vfs_item *di );
sint32_t (*is_rdonly)( const struct vfs_item *di );
sint32_t (*is_hidden)( const struct vfs_item *di );
sint32_t (*is_sys)( const struct vfs_item *di );
sint32_t (*is_arch)( const struct vfs_item *di );
};
typedef const struct vfs_item_fns vfs_item_fns;
// generic dir descriptor
struct vfs_dir {
int fs_type;
const struct vfs_dir_fns *fns;
};
typedef const struct vfs_dir vfs_dir;
// dir descriptor functions
struct vfs_dir_fns {
sint32_t (*close)( const struct vfs_dir *dd );
vfs_item *(*readdir)( const struct vfs_dir *dd );
};
typedef const struct vfs_dir_fns vfs_dir_fns;
// generic volume descriptor
struct vfs_vol {
int fs_type;
const struct vfs_vol_fns *fns;
};
typedef const struct vfs_vol vfs_vol;
// volume functions
struct vfs_vol_fns {
sint32_t (*umount)( const struct vfs_vol *vol );
};
typedef const struct vfs_vol_fns vfs_vol_fns;
struct vfs_fs_fns {
vfs_vol *(*mount)( const char *name, int num );
vfs_file *(*open)( const char *name, const char *mode );
vfs_dir *(*opendir)( const char *name );
vfs_item *(*stat)( const char *name );
sint32_t (*remove)( const char *name );
sint32_t (*rename)( const char *oldname, const char *newname );
sint32_t (*mkdir)( const char *name );
sint32_t (*fsinfo)( uint32_t *total, uint32_t *used );
sint32_t (*fscfg)( uint32_t *phys_addr, uint32_t *phys_size );
sint32_t (*format)( void );
sint32_t (*chdrive)( const char * );
sint32_t (*chdir)( const char * );
sint32_t (*ferrno)( void );
void (*clearerr)( void );
};
typedef const struct vfs_fs_fns vfs_fs_fns;
vfs_fs_fns *myspiffs_realm( const char *inname, char **outname, int set_current_drive );
vfs_fs_fns *myfatfs_realm( const char *inname, char **outname, int set_current_drive );
sint32_t vfs_get_rtc( vfs_time *tm );
#endif
...@@ -204,101 +204,421 @@ int myspiffs_format( void ) ...@@ -204,101 +204,421 @@ int myspiffs_format( void )
return myspiffs_mount(); return myspiffs_mount();
} }
int myspiffs_check( void ) #if 0
{ void test_spiffs() {
// ets_wdt_disable(); char buf[12];
// int res = (int)SPIFFS_check(&fs);
// ets_wdt_enable(); // Surely, I've mounted spiffs before entering here
// return res;
spiffs_file fd = SPIFFS_open(&fs, "my_file", SPIFFS_CREAT | SPIFFS_TRUNC | SPIFFS_RDWR, 0);
if (SPIFFS_write(&fs, fd, (u8_t *)"Hello world", 12) < 0) NODE_DBG("errno %i\n", SPIFFS_errno(&fs));
SPIFFS_close(&fs, fd);
fd = SPIFFS_open(&fs, "my_file", SPIFFS_RDWR, 0);
if (SPIFFS_read(&fs, fd, (u8_t *)buf, 12) < 0) NODE_DBG("errno %i\n", SPIFFS_errno(&fs));
SPIFFS_close(&fs, fd);
NODE_DBG("--> %s <--\n", buf);
} }
#endif
int myspiffs_open(const char *name, int flags){
return (int)SPIFFS_open(&fs, (char *)name, (spiffs_flags)flags, 0); // ***************************************************************************
// vfs API
// ***************************************************************************
#include <c_stdlib.h>
#include "vfs_int.h"
#define MY_LDRV_ID "FLASH"
// default current drive
static int is_current_drive = TRUE;
// forward declarations
static sint32_t myspiffs_vfs_close( const struct vfs_file *fd );
static sint32_t myspiffs_vfs_read( const struct vfs_file *fd, void *ptr, size_t len );
static sint32_t myspiffs_vfs_write( const struct vfs_file *fd, const void *ptr, size_t len );
static sint32_t myspiffs_vfs_lseek( const struct vfs_file *fd, sint32_t off, int whence );
static sint32_t myspiffs_vfs_eof( const struct vfs_file *fd );
static sint32_t myspiffs_vfs_tell( const struct vfs_file *fd );
static sint32_t myspiffs_vfs_flush( const struct vfs_file *fd );
static sint32_t myspiffs_vfs_ferrno( const struct vfs_file *fd );
static sint32_t myspiffs_vfs_closedir( const struct vfs_dir *dd );
static vfs_item *myspiffs_vfs_readdir( const struct vfs_dir *dd );
static void myspiffs_vfs_iclose( const struct vfs_item *di );
static uint32_t myspiffs_vfs_isize( const struct vfs_item *di );
//static const struct tm *myspiffs_vfs_time( const struct vfs_item *di );
static const char *myspiffs_vfs_name( const struct vfs_item *di );
static vfs_vol *myspiffs_vfs_mount( const char *name, int num );
static vfs_file *myspiffs_vfs_open( const char *name, const char *mode );
static vfs_dir *myspiffs_vfs_opendir( const char *name );
static vfs_item *myspiffs_vfs_stat( const char *name );
static sint32_t myspiffs_vfs_remove( const char *name );
static sint32_t myspiffs_vfs_rename( const char *oldname, const char *newname );
static sint32_t myspiffs_vfs_fsinfo( uint32_t *total, uint32_t *used );
static sint32_t myspiffs_vfs_fscfg( uint32_t *phys_addr, uint32_t *phys_size );
static sint32_t myspiffs_vfs_format( void );
static sint32_t myspiffs_vfs_errno( void );
static void myspiffs_vfs_clearerr( void );
static sint32_t myspiffs_vfs_umount( const struct vfs_vol *vol );
// ---------------------------------------------------------------------------
// function tables
//
static vfs_fs_fns myspiffs_fs_fns = {
.mount = myspiffs_vfs_mount,
.open = myspiffs_vfs_open,
.opendir = myspiffs_vfs_opendir,
.stat = myspiffs_vfs_stat,
.remove = myspiffs_vfs_remove,
.rename = myspiffs_vfs_rename,
.mkdir = NULL,
.fsinfo = myspiffs_vfs_fsinfo,
.fscfg = myspiffs_vfs_fscfg,
.format = myspiffs_vfs_format,
.chdrive = NULL,
.chdir = NULL,
.ferrno = myspiffs_vfs_errno,
.clearerr = myspiffs_vfs_clearerr
};
static vfs_file_fns myspiffs_file_fns = {
.close = myspiffs_vfs_close,
.read = myspiffs_vfs_read,
.write = myspiffs_vfs_write,
.lseek = myspiffs_vfs_lseek,
.eof = myspiffs_vfs_eof,
.tell = myspiffs_vfs_tell,
.flush = myspiffs_vfs_flush,
.size = NULL,
.ferrno = myspiffs_vfs_ferrno
};
static vfs_item_fns myspiffs_item_fns = {
.close = myspiffs_vfs_iclose,
.size = myspiffs_vfs_isize,
.time = NULL,
.name = myspiffs_vfs_name,
.is_dir = NULL,
.is_rdonly = NULL,
.is_hidden = NULL,
.is_sys = NULL,
.is_arch = NULL
};
static vfs_dir_fns myspiffs_dd_fns = {
.close = myspiffs_vfs_closedir,
.readdir = myspiffs_vfs_readdir
};
// ---------------------------------------------------------------------------
// specific struct extensions
//
struct myvfs_file {
struct vfs_file vfs_file;
spiffs_file fh;
};
struct myvfs_dir {
struct vfs_dir vfs_dir;
spiffs_DIR d;
};
struct myvfs_stat {
struct vfs_item vfs_item;
spiffs_stat s;
};
// ---------------------------------------------------------------------------
// stat functions
//
#define GET_STAT_S(descr) \
const struct myvfs_stat *mystat = (const struct myvfs_stat *)descr; \
spiffs_stat *s = (spiffs_stat *)&(mystat->s);
static void myspiffs_vfs_iclose( const struct vfs_item *di ) {
// free descriptor memory
c_free( (void *)di );
} }
int myspiffs_close( int fd ){ static uint32_t myspiffs_vfs_isize( const struct vfs_item *di ) {
return SPIFFS_close(&fs, (spiffs_file)fd); GET_STAT_S(di);
return s->size;
} }
size_t myspiffs_write( int fd, const void* ptr, size_t len ){
#if 0 static const char *myspiffs_vfs_name( const struct vfs_item *di ) {
if(fd==c_stdout || fd==c_stderr){ GET_STAT_S(di);
uart0_tx_buffer((u8_t*)ptr, len);
return len; return s->name;
} }
#endif
int res = SPIFFS_write(&fs, (spiffs_file)fd, (void *)ptr, len);
if (res < 0) { // ---------------------------------------------------------------------------
NODE_DBG("write errno %i\n", SPIFFS_errno(&fs)); // volume functions
return 0; //
static sint32_t myspiffs_vfs_umount( const struct vfs_vol *vol ) {
// not implemented
return VFS_RES_ERR;
}
// ---------------------------------------------------------------------------
// dir functions
//
#define GET_DIR_D(descr) \
const struct myvfs_dir *mydd = (const struct myvfs_dir *)descr; \
spiffs_DIR *d = (spiffs_DIR *)&(mydd->d);
static sint32_t myspiffs_vfs_closedir( const struct vfs_dir *dd ) {
GET_DIR_D(dd);
sint32_t res = SPIFFS_closedir( d );
// free descriptor memory
c_free( (void *)dd );
}
static vfs_item *myspiffs_vfs_readdir( const struct vfs_dir *dd ) {
GET_DIR_D(dd);
struct myvfs_stat *stat;
struct spiffs_dirent dirent;
if (stat = c_malloc( sizeof( struct myvfs_stat ) )) {
if (SPIFFS_readdir( d, &dirent )) {
stat->vfs_item.fs_type = VFS_FS_FATFS;
stat->vfs_item.fns = &myspiffs_item_fns;
// copy entries to vfs' directory item
stat->s.size = dirent.size;
c_strncpy( stat->s.name, dirent.name, SPIFFS_OBJ_NAME_LEN );
return (vfs_item *)stat;
} else {
c_free( stat );
}
} }
return NULL;
}
// ---------------------------------------------------------------------------
// file functions
//
#define GET_FILE_FH(descr) \
const struct myvfs_file *myfd = (const struct myvfs_file *)descr; \
spiffs_file fh = myfd->fh;
static sint32_t myspiffs_vfs_close( const struct vfs_file *fd ) {
GET_FILE_FH(fd);
sint32_t res = SPIFFS_close( &fs, fh );
// free descriptor memory
c_free( (void *)fd );
return res; return res;
} }
size_t myspiffs_read( int fd, void* ptr, size_t len){
int res = SPIFFS_read(&fs, (spiffs_file)fd, ptr, len); static sint32_t myspiffs_vfs_read( const struct vfs_file *fd, void *ptr, size_t len ) {
if (res < 0) { GET_FILE_FH(fd);
NODE_DBG("read errno %i\n", SPIFFS_errno(&fs));
return 0; return SPIFFS_read( &fs, fh, ptr, len );
}
static sint32_t myspiffs_vfs_write( const struct vfs_file *fd, const void *ptr, size_t len ) {
GET_FILE_FH(fd);
return SPIFFS_write( &fs, fh, (void *)ptr, len );
}
static sint32_t myspiffs_vfs_lseek( const struct vfs_file *fd, sint32_t off, int whence ) {
GET_FILE_FH(fd);
int spiffs_whence;
switch (whence) {
default:
case VFS_SEEK_SET:
spiffs_whence = SPIFFS_SEEK_SET;
break;
case VFS_SEEK_CUR:
spiffs_whence = SPIFFS_SEEK_CUR;
break;
case VFS_SEEK_END:
spiffs_whence = SPIFFS_SEEK_END;
break;
} }
return res;
return SPIFFS_lseek( &fs, fh, off, spiffs_whence );
} }
int myspiffs_lseek( int fd, int off, int whence ){
return SPIFFS_lseek(&fs, (spiffs_file)fd, off, whence); static sint32_t myspiffs_vfs_eof( const struct vfs_file *fd ) {
GET_FILE_FH(fd);
return SPIFFS_eof( &fs, fh );
}
static sint32_t myspiffs_vfs_tell( const struct vfs_file *fd ) {
GET_FILE_FH(fd);
return SPIFFS_tell( &fs, fh );
} }
int myspiffs_eof( int fd ){
return SPIFFS_eof(&fs, (spiffs_file)fd); static sint32_t myspiffs_vfs_flush( const struct vfs_file *fd ) {
GET_FILE_FH(fd);
return SPIFFS_fflush( &fs, fh ) >= 0 ? VFS_RES_OK : VFS_RES_ERR;
} }
int myspiffs_tell( int fd ){
return SPIFFS_tell(&fs, (spiffs_file)fd); static sint32_t myspiffs_vfs_ferrno( const struct vfs_file *fd ) {
return SPIFFS_errno( &fs );
} }
int myspiffs_getc( int fd ){
unsigned char c = 0xFF;
int res; static int fs_mode2flag(const char *mode){
if(!myspiffs_eof(fd)){ if(c_strlen(mode)==1){
res = SPIFFS_read(&fs, (spiffs_file)fd, &c, 1); if(c_strcmp(mode,"w")==0)
if (res != 1) { return SPIFFS_WRONLY|SPIFFS_CREAT|SPIFFS_TRUNC;
NODE_DBG("getc errno %i\n", SPIFFS_errno(&fs)); else if(c_strcmp(mode, "r")==0)
return (int)EOF; return SPIFFS_RDONLY;
else if(c_strcmp(mode, "a")==0)
return SPIFFS_WRONLY|SPIFFS_CREAT|SPIFFS_APPEND;
else
return SPIFFS_RDONLY;
} else if (c_strlen(mode)==2){
if(c_strcmp(mode,"r+")==0)
return SPIFFS_RDWR;
else if(c_strcmp(mode, "w+")==0)
return SPIFFS_RDWR|SPIFFS_CREAT|SPIFFS_TRUNC;
else if(c_strcmp(mode, "a+")==0)
return SPIFFS_RDWR|SPIFFS_CREAT|SPIFFS_APPEND;
else
return SPIFFS_RDONLY;
} else {
return SPIFFS_RDONLY;
}
}
// ---------------------------------------------------------------------------
// filesystem functions
//
static vfs_file *myspiffs_vfs_open( const char *name, const char *mode ) {
struct myvfs_file *fd;
int flags = fs_mode2flag( mode );
if (fd = (struct myvfs_file *)c_malloc( sizeof( struct myvfs_file ) )) {
if (0 < (fd->fh = SPIFFS_open( &fs, name, flags, 0 ))) {
fd->vfs_file.fs_type = VFS_FS_SPIFFS;
fd->vfs_file.fns = &myspiffs_file_fns;
return (vfs_file *)fd;
} else { } else {
return (int)c; c_free( fd );
} }
} }
return (int)EOF;
return NULL;
} }
int myspiffs_ungetc( int c, int fd ){
return SPIFFS_lseek(&fs, (spiffs_file)fd, -1, SEEK_CUR); static vfs_dir *myspiffs_vfs_opendir( const char *name ){
struct myvfs_dir *dd;
if (dd = (struct myvfs_dir *)c_malloc( sizeof( struct myvfs_dir ) )) {
if (SPIFFS_opendir( &fs, name, &(dd->d) )) {
dd->vfs_dir.fs_type = VFS_FS_SPIFFS;
dd->vfs_dir.fns = &myspiffs_dd_fns;
return (vfs_dir *)dd;
} else {
c_free( dd );
}
}
return NULL;
} }
int myspiffs_flush( int fd ){
return SPIFFS_fflush(&fs, (spiffs_file)fd); static vfs_item *myspiffs_vfs_stat( const char *name ) {
struct myvfs_stat *s;
if (s = (struct myvfs_stat *)c_malloc( sizeof( struct myvfs_stat ) )) {
if (0 <= SPIFFS_stat( &fs, name, &(s->s) )) {
s->vfs_item.fs_type = VFS_FS_SPIFFS;
s->vfs_item.fns = &myspiffs_item_fns;
return (vfs_item *)s;
} else {
c_free( s );
}
}
return NULL;
} }
int myspiffs_error( int fd ){
return SPIFFS_errno(&fs); static sint32_t myspiffs_vfs_remove( const char *name ) {
return SPIFFS_remove( &fs, name );
} }
void myspiffs_clearerr( int fd ){
SPIFFS_clearerr(&fs); static sint32_t myspiffs_vfs_rename( const char *oldname, const char *newname ) {
return SPIFFS_rename( &fs, oldname, newname );
} }
int myspiffs_rename( const char *old, const char *newname ){
return SPIFFS_rename(&fs, (char *)old, (char *)newname); static sint32_t myspiffs_vfs_fsinfo( uint32_t *total, uint32_t *used ) {
return SPIFFS_info( &fs, total, used );
} }
size_t myspiffs_size( int fd ){
int32_t curpos = SPIFFS_tell(&fs, (spiffs_file) fd); static sint32_t myspiffs_vfs_fscfg( uint32_t *phys_addr, uint32_t *phys_size ) {
int32_t size = SPIFFS_lseek(&fs, (spiffs_file) fd, SPIFFS_SEEK_END, 0); *phys_addr = fs.cfg.phys_addr;
(void) SPIFFS_lseek(&fs, (spiffs_file) fd, SPIFFS_SEEK_SET, curpos); *phys_size = fs.cfg.phys_size;
return size; return VFS_RES_OK;
} }
#if 0
void test_spiffs() {
char buf[12];
// Surely, I've mounted spiffs before entering here static vfs_vol *myspiffs_vfs_mount( const char *name, int num ) {
// volume descriptor not supported, just return TRUE / FALSE
spiffs_file fd = SPIFFS_open(&fs, "my_file", SPIFFS_CREAT | SPIFFS_TRUNC | SPIFFS_RDWR, 0); return myspiffs_mount() ? (vfs_vol *)1 : NULL;
if (SPIFFS_write(&fs, fd, (u8_t *)"Hello world", 12) < 0) NODE_DBG("errno %i\n", SPIFFS_errno(&fs)); }
SPIFFS_close(&fs, fd);
fd = SPIFFS_open(&fs, "my_file", SPIFFS_RDWR, 0); static sint32_t myspiffs_vfs_format( void ) {
if (SPIFFS_read(&fs, fd, (u8_t *)buf, 12) < 0) NODE_DBG("errno %i\n", SPIFFS_errno(&fs)); return myspiffs_format();
SPIFFS_close(&fs, fd); }
NODE_DBG("--> %s <--\n", buf); static sint32_t myspiffs_vfs_errno( void ) {
return SPIFFS_errno( &fs );
}
static void myspiffs_vfs_clearerr( void ) {
SPIFFS_clearerr( &fs );
}
// ---------------------------------------------------------------------------
// VFS interface functions
//
vfs_fs_fns *myspiffs_realm( const char *inname, char **outname, int set_current_drive ) {
if (inname[0] == '/') {
size_t idstr_len = c_strlen( MY_LDRV_ID );
// logical drive is specified, check if it's our id
if (0 == c_strncmp( &(inname[1]), MY_LDRV_ID, idstr_len )) {
*outname = (char *)&(inname[1 + idstr_len]);
if (*outname[0] == '/') {
// skip leading /
(*outname)++;
}
if (set_current_drive) is_current_drive = TRUE;
return &myspiffs_fs_fns;
}
} else {
// no logical drive in patchspec, are we current drive?
if (is_current_drive) {
*outname = (char *)inname;
return &myspiffs_fs_fns;
}
}
if (set_current_drive) is_current_drive = FALSE;
return NULL;
} }
#endif
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
#include "nodemcu_spiffs.h" #include "nodemcu_spiffs.h"
// ----------- >8 ------------ // ----------- >8 ------------
#include "user_config.h"
// compile time switches // compile time switches
// Set generic spiffs debug output call. // Set generic spiffs debug output call.
...@@ -99,7 +101,7 @@ ...@@ -99,7 +101,7 @@
// zero-termination character, meaning maximum string of characters // zero-termination character, meaning maximum string of characters
// can at most be SPIFFS_OBJ_NAME_LEN - 1. // can at most be SPIFFS_OBJ_NAME_LEN - 1.
#ifndef SPIFFS_OBJ_NAME_LEN #ifndef SPIFFS_OBJ_NAME_LEN
#define SPIFFS_OBJ_NAME_LEN (32) #define SPIFFS_OBJ_NAME_LEN (FS_OBJ_NAME_LEN+1)
#endif #endif
// Size of buffer allocated on stack used when copying data. // Size of buffer allocated on stack used when copying data.
......
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
#include "platform.h" #include "platform.h"
#include "c_string.h" #include "c_string.h"
#include "c_stdlib.h" #include "c_stdlib.h"
#include "flash_fs.h" #include "c_stdio.h"
#include "vfs.h"
#include "flash_api.h" #include "flash_api.h"
#include "user_interface.h" #include "user_interface.h"
#include "user_exceptions.h" #include "user_exceptions.h"
...@@ -103,11 +104,11 @@ void nodemcu_init(void) ...@@ -103,11 +104,11 @@ void nodemcu_init(void)
espconn_secure_set_size(ESPCONN_CLIENT, SSL_BUFFER_SIZE); espconn_secure_set_size(ESPCONN_CLIENT, SSL_BUFFER_SIZE);
#endif #endif
#if defined ( BUILD_SPIFFS ) #ifdef BUILD_SPIFFS
if (!fs_mount()) { if (!vfs_mount("/FLASH", 0)) {
// Failed to mount -- try reformat // Failed to mount -- try reformat
c_printf("Formatting file system. Please wait...\n"); c_printf("Formatting file system. Please wait...\n");
if (!fs_format()) { if (!vfs_format()) {
NODE_ERR( "\n*** ERROR ***: unable to format. FS might be compromised.\n" ); NODE_ERR( "\n*** ERROR ***: unable to format. FS might be compromised.\n" );
NODE_ERR( "It is advised to re-flash the NodeMCU image.\n" ); NODE_ERR( "It is advised to re-flash the NodeMCU image.\n" );
} }
......
...@@ -5,10 +5,44 @@ ...@@ -5,10 +5,44 @@
The file module provides access to the file system and its individual files. The file module provides access to the file system and its individual files.
The file system is a flat file system, with no notion of directories/folders. The file system is a flat file system, with no notion of subdirectories/folders.
Only one file can be open at any given time. Only one file can be open at any given time.
Besides the SPIFFS file system on internal flash, this module can also access FAT partitions on an external SD card is [FatFS is enabled](../sdcard.md).
```lua
-- open file in flash:
if file.open("init.lua") then
print(file.read())
file.close()
end
-- or with full pathspec
file.open("/FLASH/init.lua")
-- open file on SD card
if file.open("/SD0/somefile.txt") then
print(file.read())
file.close()
end
```
## file.chdir()
Change current directory (and drive). This will be used when no drive/directory is prepended to filenames.
Current directory defaults to the root of internal SPIFFS (`/FLASH`) after system start.
#### Syntax
`file.chdir(dir)`
#### Parameters
`dir` directory name - `/FLASH`, `/SD0`, `/SD1`, etc.
#### Returns
`true` on success, `false` otherwise
## file.close() ## file.close()
Closes the open file, if any. Closes the open file, if any.
...@@ -25,9 +59,10 @@ none ...@@ -25,9 +59,10 @@ none
#### Example #### Example
```lua ```lua
-- open 'init.lua', print the first line. -- open 'init.lua', print the first line.
file.open("init.lua", "r") if file.open("init.lua", "r") then
print(file.readline()) print(file.readline())
file.close() file.close()
end
``` ```
#### See also #### See also
[`file.open()`](#fileopen) [`file.open()`](#fileopen)
...@@ -76,13 +111,14 @@ none ...@@ -76,13 +111,14 @@ none
#### Example #### Example
```lua ```lua
-- open 'init.lua' in 'a+' mode -- open 'init.lua' in 'a+' mode
file.open("init.lua", "a+") if file.open("init.lua", "a+") then
-- write 'foo bar' to the end of the file -- write 'foo bar' to the end of the file
file.write('foo bar') file.write('foo bar')
file.flush() file.flush()
-- write 'baz' too -- write 'baz' too
file.write('baz') file.write('baz')
file.close() file.close()
end
``` ```
#### See also #### See also
[`file.close()`](#fileclose) [`file.close()`](#fileclose)
...@@ -91,6 +127,8 @@ file.close() ...@@ -91,6 +127,8 @@ file.close()
Format the file system. Completely erases any existing file system and writes a new one. Depending on the size of the flash chip in the ESP, this may take several seconds. Format the file system. Completely erases any existing file system and writes a new one. Depending on the size of the flash chip in the ESP, this may take several seconds.
Not supported for SD cards.
#### Syntax #### Syntax
`file.format()` `file.format()`
...@@ -107,6 +145,8 @@ none ...@@ -107,6 +145,8 @@ none
Returns the flash address and physical size of the file system area, in bytes. Returns the flash address and physical size of the file system area, in bytes.
Not supported for SD cards.
#### Syntax #### Syntax
`file.fscfg()` `file.fscfg()`
...@@ -124,7 +164,7 @@ print(string.format("0x%x", file.fscfg())) ...@@ -124,7 +164,7 @@ print(string.format("0x%x", file.fscfg()))
## file.fsinfo() ## file.fsinfo()
Return size information for the file system, in bytes. Return size information for the file system. The unit is Byte for SPIFFS and kByte for FatFS.
#### Syntax #### Syntax
`file.fsinfo()` `file.fsinfo()`
...@@ -142,7 +182,7 @@ none ...@@ -142,7 +182,7 @@ none
```lua ```lua
-- get file system info -- get file system info
remaining, used, total=file.fsinfo() remaining, used, total=file.fsinfo()
print("\nFile system info:\nTotal : "..total.." Bytes\nUsed : "..used.." Bytes\nRemain: "..remaining.." Bytes\n") print("\nFile system info:\nTotal : "..total.." (k)Bytes\nUsed : "..used.." (k)Bytes\nRemain: "..remaining.." (k)Bytes\n")
``` ```
## file.list() ## file.list()
...@@ -166,6 +206,60 @@ for k,v in pairs(l) do ...@@ -166,6 +206,60 @@ for k,v in pairs(l) do
end 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.
Trigger events are:
- `rtc` deliver current date & time to the file system. Function is expected to return a table containing the fields `year`, `mon`, `day`, `hour`, `min`, `sec` of current date and time. Not supported for internal flash.
#### Syntax
`file.on(event[, function()])`
#### Parameters
- `event` string
- `function()` callback function. Unregisters the callback if `function()` is omitted.
#### Returns
`nil`
#### Example
```lua
sntp.sync(server_ip,
function()
print("sntp time sync ok")
file.on("rtc",
function()
return rtctime.epoch2cal(rtctime.get())
end)
end)
```
#### See also
[`rtctime.epoch2cal()`](rtctime.md#rtctimepoch2cal)
## file.open() ## file.open()
Opens a file for access, potentially creating it (for write modes). Opens a file for access, potentially creating it (for write modes).
...@@ -191,9 +285,10 @@ When done with the file, it must be closed using `file.close()`. ...@@ -191,9 +285,10 @@ When done with the file, it must be closed using `file.close()`.
#### Example #### Example
```lua ```lua
-- open 'init.lua', print the first line. -- open 'init.lua', print the first line.
file.open("init.lua", "r") if file.open("init.lua", "r") then
print(file.readline()) print(file.readline())
file.close() file.close()
end
``` ```
#### See also #### See also
- [`file.close()`](#fileclose) - [`file.close()`](#fileclose)
...@@ -218,14 +313,16 @@ File content as a string, or nil when EOF ...@@ -218,14 +313,16 @@ File content as a string, or nil when EOF
#### Example #### Example
```lua ```lua
-- print the first line of 'init.lua' -- print the first line of 'init.lua'
file.open("init.lua", "r") if file.open("init.lua", "r") then
print(file.read('\n')) print(file.read('\n'))
file.close() file.close()
end
-- print the first 5 bytes of 'init.lua' -- print the first 5 bytes of 'init.lua'
file.open("init.lua", "r") if file.open("init.lua", "r") then
print(file.read(5)) print(file.read(5))
file.close() file.close()
end
``` ```
#### See also #### See also
...@@ -248,9 +345,10 @@ File content in string, line by line, including EOL('\n'). Return `nil` when EOF ...@@ -248,9 +345,10 @@ File content in string, line by line, including EOL('\n'). Return `nil` when EOF
#### Example #### Example
```lua ```lua
-- print the first line of 'init.lua' -- print the first line of 'init.lua'
file.open("init.lua", "r") if file.open("init.lua", "r") then
print(file.readline()) print(file.readline())
file.close() file.close()
end
``` ```
#### See also #### See also
- [`file.open()`](#fileopen) - [`file.open()`](#fileopen)
...@@ -320,11 +418,12 @@ the resulting file position, or `nil` on error ...@@ -320,11 +418,12 @@ the resulting file position, or `nil` on error
#### Example #### Example
```lua ```lua
file.open("init.lua", "r") if file.open("init.lua", "r") then
-- skip the first 5 bytes of the file -- skip the first 5 bytes of the file
file.seek("set", 5) file.seek("set", 5)
print(file.readline()) print(file.readline())
file.close() file.close()
end
``` ```
#### See also #### See also
[`file.open()`](#fileopen) [`file.open()`](#fileopen)
...@@ -345,10 +444,11 @@ Write a string to the open file. ...@@ -345,10 +444,11 @@ Write a string to the open file.
#### Example #### Example
```lua ```lua
-- open 'init.lua' in 'a+' mode -- open 'init.lua' in 'a+' mode
file.open("init.lua", "a+") if file.open("init.lua", "a+") then
-- write 'foo bar' to the end of the file -- write 'foo bar' to the end of the file
file.write('foo bar') file.write('foo bar')
file.close() file.close()
end
``` ```
#### See also #### See also
...@@ -371,10 +471,11 @@ Write a string to the open file and append '\n' at the end. ...@@ -371,10 +471,11 @@ Write a string to the open file and append '\n' at the end.
#### Example #### Example
```lua ```lua
-- open 'init.lua' in 'a+' mode -- open 'init.lua' in 'a+' mode
file.open("init.lua", "a+") if file.open("init.lua", "a+") then
-- write 'foo bar' to the end of the file -- write 'foo bar' to the end of the file
file.writeline('foo bar') file.writeline('foo bar')
file.close() file.close()
end
``` ```
#### See also #### See also
......
# 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:
- only one file can be opened at a time
- no 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:
Uncomment `#define BUILD_FATFS` in [`user_config.h`](../../app/include/user_config.h).
## SD Card connection
The SD card is operated in SPI mode, thus the card has to be wired to the respective ESP pins of the HSPI interface. There are several naming schemes used on different adapters - the following list shows alternative terms:
- `CK, CLK, SCLK` to pin5 / GPIO14
- `DO, DAT0, MISO` to pin 6 / GPIO12
- `DI, CMD, MOSI` to pin 7 / GPIO13
- `CS, DAT3, SS` to pin 8 / GPIO15 recommended
- `VCC, VDD` to 3V3 supply
- `VSS, GND` to common ground
Connection of `SS/CS` can be done to any of the GPIOs on pins 1 to 12. This allows coexistence of the SD card with other SPI slaves on the same bus. There's no support for detection of card presence or the write protection switch. These would need to be handled as additional GPIOs in the user application.
!!! 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.
<img src="../img/micro_sd.jpg" alt="1:1 micro-sd adapter" width="200"/>
<img src="../img/micro_sd_shield.jpg" alt="micro-sd shield" width="200"/>
## Lua bindings
Before mounting the volume(s) on the SD card, you need to initialize the SPI interface from Lua.
```lua
spi.setup(1, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 8)
-- initialize other spi slaves
-- then mount the sd
-- note: the card initialization process during `file.mount()` will set spi divider temporarily to 200 (400 kHz)
-- it's reverted back to the current user setting before `file.mount()` finishes
vol = file.mount("/SD0", 8) -- 2nd parameter is optional for non-standard SS/CS pin
if not vol then
print("retry mounting")
vol = file.mount("/SD0", 8)
if not vol then
error("mount failed")
end
end
file.open("/SD0/path/to/somefile")
print(file.read())
file.close()
```
!!! note
If the card doesn't work when calling `file.mount()` for the first time then re-try the command. It's possible that certain cards time out during the first initialization after power-up.
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`](../../app/include/fatfs_config.h). More volumes can be added to the `VolToPart` array with any combination of physical drive number (aka SS/CS pin) and partition number. Their names have to be added to `_VOLUME_STRS` in [`ffconf.h`](../../app/fatfs/ffconf.h) as well.
...@@ -26,7 +26,8 @@ pages: ...@@ -26,7 +26,8 @@ pages:
- Home: 'en/index.md' - Home: 'en/index.md'
- Building the firmware: 'en/build.md' - Building the firmware: 'en/build.md'
- Flashing the firmware: 'en/flash.md' - Flashing the firmware: 'en/flash.md'
- Filesystem notes: 'en/spiffs.md' - Internal filesystem notes: 'en/spiffs.md'
- Filesystem on SD card: 'en/sdcard.md'
- Uploading code: 'en/upload.md' - Uploading code: 'en/upload.md'
- FAQs: - FAQs:
- Lua Developer FAQ: 'en/lua-developer-faq.md' - Lua Developer FAQ: 'en/lua-developer-faq.md'
......
...@@ -11,6 +11,9 @@ cat user_modules.h ...@@ -11,6 +11,9 @@ cat user_modules.h
# enable SSL # enable SSL
sed -i.bak 's@//#define CLIENT_SSL_ENABLE@#define CLIENT_SSL_ENABLE@' user_config.h sed -i.bak 's@//#define CLIENT_SSL_ENABLE@#define CLIENT_SSL_ENABLE@' user_config.h
# enable FATFS
sed -i 's@//#define BUILD_FATFS@#define BUILD_FATFS@' user_config.h
cat user_config.h cat user_config.h
cd "$TRAVIS_BUILD_DIR"/ld || exit cd "$TRAVIS_BUILD_DIR"/ld || exit
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment