Merge branch 'feature/sd_driver_new' into 'master'

SDMMC peripheral driver, SD protocol driver, FATFS library, VFS integration

This MR contains:
- SDMMC host peripheral driver
- SD protocol driver in sdmmc component (can be extended to support MMC/eMMC and SPI based hosts)
- ChaN's FATFS library v0.12b
- VFS integration
- FAT access via VFS is thread-safe (unless same file is read/written/unlinked/renamed from different tasks)
- Support for POSIX directory-related functions in VFS (and in vfs_fatfs.c)
- unit test for the above
- Example
- API documentation

Will be done in other MRs:
- Support for spi_flash IO driver for FatFs
- SPI host driver and support for SPI mode commands in sdmmc component
- MMC/eMMC support in sdmmc component
- Support for slightly higher 53/26.6MHz clocks (currently I'm using 20MHz for DS and 40MHz for HS, instead of 25MHz/50MHz per standard), and arbitrary low clocks (e.g. 4MHz).

See merge request !353
This commit is contained in:
Ivan Grokhotkov 2017-01-09 06:48:25 +08:00
commit 5b5a4355e7
46 changed files with 12092 additions and 22 deletions

View File

@ -0,0 +1,303 @@
/*
* Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org>
* Adaptations to ESP-IDF Copyright (c) 2016 Espressif Systems (Shanghai) PTE LTD
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _SDMMC_DEFS_H_
#define _SDMMC_DEFS_H_
#include <stdint.h>
#include <limits.h>
/* MMC commands */ /* response type */
#define MMC_GO_IDLE_STATE 0 /* R0 */
#define MMC_SEND_OP_COND 1 /* R3 */
#define MMC_ALL_SEND_CID 2 /* R2 */
#define MMC_SET_RELATIVE_ADDR 3 /* R1 */
#define MMC_SWITCH 6 /* R1B */
#define MMC_SELECT_CARD 7 /* R1 */
#define MMC_SEND_EXT_CSD 8 /* R1 */
#define MMC_SEND_CSD 9 /* R2 */
#define MMC_STOP_TRANSMISSION 12 /* R1B */
#define MMC_SEND_STATUS 13 /* R1 */
#define MMC_SET_BLOCKLEN 16 /* R1 */
#define MMC_READ_BLOCK_SINGLE 17 /* R1 */
#define MMC_READ_BLOCK_MULTIPLE 18 /* R1 */
#define MMC_SET_BLOCK_COUNT 23 /* R1 */
#define MMC_WRITE_BLOCK_SINGLE 24 /* R1 */
#define MMC_WRITE_BLOCK_MULTIPLE 25 /* R1 */
#define MMC_APP_CMD 55 /* R1 */
/* SD commands */ /* response type */
#define SD_SEND_RELATIVE_ADDR 3 /* R6 */
#define SD_SEND_SWITCH_FUNC 6 /* R1 */
#define SD_SEND_IF_COND 8 /* R7 */
/* SD application commands */ /* response type */
#define SD_APP_SET_BUS_WIDTH 6 /* R1 */
#define SD_APP_OP_COND 41 /* R3 */
#define SD_APP_SEND_SCR 51 /* R1 */
/* OCR bits */
#define MMC_OCR_MEM_READY (1<<31) /* memory power-up status bit */
#define MMC_OCR_ACCESS_MODE_MASK 0x60000000 /* bits 30:29 */
#define MMC_OCR_SECTOR_MODE (1<<30)
#define MMC_OCR_BYTE_MODE (1<<29)
#define MMC_OCR_3_5V_3_6V (1<<23)
#define MMC_OCR_3_4V_3_5V (1<<22)
#define MMC_OCR_3_3V_3_4V (1<<21)
#define MMC_OCR_3_2V_3_3V (1<<20)
#define MMC_OCR_3_1V_3_2V (1<<19)
#define MMC_OCR_3_0V_3_1V (1<<18)
#define MMC_OCR_2_9V_3_0V (1<<17)
#define MMC_OCR_2_8V_2_9V (1<<16)
#define MMC_OCR_2_7V_2_8V (1<<15)
#define MMC_OCR_2_6V_2_7V (1<<14)
#define MMC_OCR_2_5V_2_6V (1<<13)
#define MMC_OCR_2_4V_2_5V (1<<12)
#define MMC_OCR_2_3V_2_4V (1<<11)
#define MMC_OCR_2_2V_2_3V (1<<10)
#define MMC_OCR_2_1V_2_2V (1<<9)
#define MMC_OCR_2_0V_2_1V (1<<8)
#define MMC_OCR_1_65V_1_95V (1<<7)
#define SD_OCR_SDHC_CAP (1<<30)
#define SD_OCR_VOL_MASK 0xFF8000 /* bits 23:15 */
/* R1 response type bits */
#define MMC_R1_READY_FOR_DATA (1<<8) /* ready for next transfer */
#define MMC_R1_APP_CMD (1<<5) /* app. commands supported */
/* 48-bit response decoding (32 bits w/o CRC) */
#define MMC_R1(resp) ((resp)[0])
#define MMC_R3(resp) ((resp)[0])
#define SD_R6(resp) ((resp)[0])
#define MMC_R1_CURRENT_STATE(resp) (((resp)[0] >> 9) & 0xf)
/* RCA argument and response */
#define MMC_ARG_RCA(rca) ((rca) << 16)
#define SD_R6_RCA(resp) (SD_R6((resp)) >> 16)
/* bus width argument */
#define SD_ARG_BUS_WIDTH_1 0
#define SD_ARG_BUS_WIDTH_4 2
/* EXT_CSD fields */
#define EXT_CSD_BUS_WIDTH 183 /* WO */
#define EXT_CSD_HS_TIMING 185 /* R/W */
#define EXT_CSD_REV 192 /* RO */
#define EXT_CSD_STRUCTURE 194 /* RO */
#define EXT_CSD_CARD_TYPE 196 /* RO */
#define EXT_CSD_SEC_COUNT 212 /* RO */
/* EXT_CSD field definitions */
#define EXT_CSD_CMD_SET_NORMAL (1U << 0)
#define EXT_CSD_CMD_SET_SECURE (1U << 1)
#define EXT_CSD_CMD_SET_CPSECURE (1U << 2)
/* EXT_CSD_HS_TIMING */
#define EXT_CSD_HS_TIMING_BC 0
#define EXT_CSD_HS_TIMING_HS 1
#define EXT_CSD_HS_TIMING_HS200 2
#define EXT_CSD_HS_TIMING_HS400 3
/* EXT_CSD_BUS_WIDTH */
#define EXT_CSD_BUS_WIDTH_1 0
#define EXT_CSD_BUS_WIDTH_4 1
#define EXT_CSD_BUS_WIDTH_8 2
#define EXT_CSD_BUS_WIDTH_4_DDR 5
#define EXT_CSD_BUS_WIDTH_8_DDR 6
/* EXT_CSD_CARD_TYPE */
/* The only currently valid values for this field are 0x01, 0x03, 0x07,
* 0x0B and 0x0F. */
#define EXT_CSD_CARD_TYPE_F_26M (1 << 0)
#define EXT_CSD_CARD_TYPE_F_52M (1 << 1)
#define EXT_CSD_CARD_TYPE_F_52M_1_8V (1 << 2)
#define EXT_CSD_CARD_TYPE_F_52M_1_2V (1 << 3)
#define EXT_CSD_CARD_TYPE_26M 0x01
#define EXT_CSD_CARD_TYPE_52M 0x03
#define EXT_CSD_CARD_TYPE_52M_V18 0x07
#define EXT_CSD_CARD_TYPE_52M_V12 0x0b
#define EXT_CSD_CARD_TYPE_52M_V12_18 0x0f
/* MMC_SWITCH access mode */
#define MMC_SWITCH_MODE_CMD_SET 0x00 /* Change the command set */
#define MMC_SWITCH_MODE_SET_BITS 0x01 /* Set bits in value */
#define MMC_SWITCH_MODE_CLEAR_BITS 0x02 /* Clear bits in value */
#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */
/* MMC R2 response (CSD) */
#define MMC_CSD_CSDVER(resp) MMC_RSP_BITS((resp), 126, 2)
#define MMC_CSD_CSDVER_1_0 1
#define MMC_CSD_CSDVER_2_0 2
#define MMC_CSD_CSDVER_EXT_CSD 3
#define MMC_CSD_MMCVER(resp) MMC_RSP_BITS((resp), 122, 4)
#define MMC_CSD_MMCVER_1_0 0 /* MMC 1.0 - 1.2 */
#define MMC_CSD_MMCVER_1_4 1 /* MMC 1.4 */
#define MMC_CSD_MMCVER_2_0 2 /* MMC 2.0 - 2.2 */
#define MMC_CSD_MMCVER_3_1 3 /* MMC 3.1 - 3.3 */
#define MMC_CSD_MMCVER_4_0 4 /* MMC 4 */
#define MMC_CSD_READ_BL_LEN(resp) MMC_RSP_BITS((resp), 80, 4)
#define MMC_CSD_C_SIZE(resp) MMC_RSP_BITS((resp), 62, 12)
#define MMC_CSD_CAPACITY(resp) ((MMC_CSD_C_SIZE((resp))+1) << \
(MMC_CSD_C_SIZE_MULT((resp))+2))
#define MMC_CSD_C_SIZE_MULT(resp) MMC_RSP_BITS((resp), 47, 3)
/* MMC v1 R2 response (CID) */
#define MMC_CID_MID_V1(resp) MMC_RSP_BITS((resp), 104, 24)
#define MMC_CID_PNM_V1_CPY(resp, pnm) \
do { \
(pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \
(pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \
(pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \
(pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \
(pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \
(pnm)[5] = MMC_RSP_BITS((resp), 56, 8); \
(pnm)[6] = MMC_RSP_BITS((resp), 48, 8); \
(pnm)[7] = '\0'; \
} while (0)
#define MMC_CID_REV_V1(resp) MMC_RSP_BITS((resp), 40, 8)
#define MMC_CID_PSN_V1(resp) MMC_RSP_BITS((resp), 16, 24)
#define MMC_CID_MDT_V1(resp) MMC_RSP_BITS((resp), 8, 8)
/* MMC v2 R2 response (CID) */
#define MMC_CID_MID_V2(resp) MMC_RSP_BITS((resp), 120, 8)
#define MMC_CID_OID_V2(resp) MMC_RSP_BITS((resp), 104, 16)
#define MMC_CID_PNM_V2_CPY(resp, pnm) \
do { \
(pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \
(pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \
(pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \
(pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \
(pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \
(pnm)[5] = MMC_RSP_BITS((resp), 56, 8); \
(pnm)[6] = '\0'; \
} while (0)
#define MMC_CID_PSN_V2(resp) MMC_RSP_BITS((resp), 16, 32)
/* SD R2 response (CSD) */
#define SD_CSD_CSDVER(resp) MMC_RSP_BITS((resp), 126, 2)
#define SD_CSD_CSDVER_1_0 0
#define SD_CSD_CSDVER_2_0 1
#define SD_CSD_TAAC(resp) MMC_RSP_BITS((resp), 112, 8)
#define SD_CSD_TAAC_1_5_MSEC 0x26
#define SD_CSD_NSAC(resp) MMC_RSP_BITS((resp), 104, 8)
#define SD_CSD_SPEED(resp) MMC_RSP_BITS((resp), 96, 8)
#define SD_CSD_SPEED_25_MHZ 0x32
#define SD_CSD_SPEED_50_MHZ 0x5a
#define SD_CSD_CCC(resp) MMC_RSP_BITS((resp), 84, 12)
#define SD_CSD_CCC_BASIC (1 << 0) /* basic */
#define SD_CSD_CCC_BR (1 << 2) /* block read */
#define SD_CSD_CCC_BW (1 << 4) /* block write */
#define SD_CSD_CCC_ERASE (1 << 5) /* erase */
#define SD_CSD_CCC_WP (1 << 6) /* write protection */
#define SD_CSD_CCC_LC (1 << 7) /* lock card */
#define SD_CSD_CCC_AS (1 << 8) /*application specific*/
#define SD_CSD_CCC_IOM (1 << 9) /* I/O mode */
#define SD_CSD_CCC_SWITCH (1 << 10) /* switch */
#define SD_CSD_READ_BL_LEN(resp) MMC_RSP_BITS((resp), 80, 4)
#define SD_CSD_READ_BL_PARTIAL(resp) MMC_RSP_BITS((resp), 79, 1)
#define SD_CSD_WRITE_BLK_MISALIGN(resp) MMC_RSP_BITS((resp), 78, 1)
#define SD_CSD_READ_BLK_MISALIGN(resp) MMC_RSP_BITS((resp), 77, 1)
#define SD_CSD_DSR_IMP(resp) MMC_RSP_BITS((resp), 76, 1)
#define SD_CSD_C_SIZE(resp) MMC_RSP_BITS((resp), 62, 12)
#define SD_CSD_CAPACITY(resp) ((SD_CSD_C_SIZE((resp))+1) << \
(SD_CSD_C_SIZE_MULT((resp))+2))
#define SD_CSD_V2_C_SIZE(resp) MMC_RSP_BITS((resp), 48, 22)
#define SD_CSD_V2_CAPACITY(resp) ((SD_CSD_V2_C_SIZE((resp))+1) << 10)
#define SD_CSD_V2_BL_LEN 0x9 /* 512 */
#define SD_CSD_VDD_R_CURR_MIN(resp) MMC_RSP_BITS((resp), 59, 3)
#define SD_CSD_VDD_R_CURR_MAX(resp) MMC_RSP_BITS((resp), 56, 3)
#define SD_CSD_VDD_W_CURR_MIN(resp) MMC_RSP_BITS((resp), 53, 3)
#define SD_CSD_VDD_W_CURR_MAX(resp) MMC_RSP_BITS((resp), 50, 3)
#define SD_CSD_VDD_RW_CURR_100mA 0x7
#define SD_CSD_VDD_RW_CURR_80mA 0x6
#define SD_CSD_C_SIZE_MULT(resp) MMC_RSP_BITS((resp), 47, 3)
#define SD_CSD_ERASE_BLK_EN(resp) MMC_RSP_BITS((resp), 46, 1)
#define SD_CSD_SECTOR_SIZE(resp) MMC_RSP_BITS((resp), 39, 7) /* +1 */
#define SD_CSD_WP_GRP_SIZE(resp) MMC_RSP_BITS((resp), 32, 7) /* +1 */
#define SD_CSD_WP_GRP_ENABLE(resp) MMC_RSP_BITS((resp), 31, 1)
#define SD_CSD_R2W_FACTOR(resp) MMC_RSP_BITS((resp), 26, 3)
#define SD_CSD_WRITE_BL_LEN(resp) MMC_RSP_BITS((resp), 22, 4)
#define SD_CSD_RW_BL_LEN_2G 0xa
#define SD_CSD_RW_BL_LEN_1G 0x9
#define SD_CSD_WRITE_BL_PARTIAL(resp) MMC_RSP_BITS((resp), 21, 1)
#define SD_CSD_FILE_FORMAT_GRP(resp) MMC_RSP_BITS((resp), 15, 1)
#define SD_CSD_COPY(resp) MMC_RSP_BITS((resp), 14, 1)
#define SD_CSD_PERM_WRITE_PROTECT(resp) MMC_RSP_BITS((resp), 13, 1)
#define SD_CSD_TMP_WRITE_PROTECT(resp) MMC_RSP_BITS((resp), 12, 1)
#define SD_CSD_FILE_FORMAT(resp) MMC_RSP_BITS((resp), 10, 2)
/* SD R2 response (CID) */
#define SD_CID_MID(resp) MMC_RSP_BITS((resp), 120, 8)
#define SD_CID_OID(resp) MMC_RSP_BITS((resp), 104, 16)
#define SD_CID_PNM_CPY(resp, pnm) \
do { \
(pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \
(pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \
(pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \
(pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \
(pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \
(pnm)[5] = '\0'; \
} while (0)
#define SD_CID_REV(resp) MMC_RSP_BITS((resp), 56, 8)
#define SD_CID_PSN(resp) MMC_RSP_BITS((resp), 24, 32)
#define SD_CID_MDT(resp) MMC_RSP_BITS((resp), 8, 12)
/* SCR (SD Configuration Register) */
#define SCR_STRUCTURE(scr) MMC_RSP_BITS((scr), 60, 4)
#define SCR_STRUCTURE_VER_1_0 0 /* Version 1.0 */
#define SCR_SD_SPEC(scr) MMC_RSP_BITS((scr), 56, 4)
#define SCR_SD_SPEC_VER_1_0 0 /* Version 1.0 and 1.01 */
#define SCR_SD_SPEC_VER_1_10 1 /* Version 1.10 */
#define SCR_SD_SPEC_VER_2 2 /* Version 2.00 or Version 3.0X */
#define SCR_DATA_STAT_AFTER_ERASE(scr) MMC_RSP_BITS((scr), 55, 1)
#define SCR_SD_SECURITY(scr) MMC_RSP_BITS((scr), 52, 3)
#define SCR_SD_SECURITY_NONE 0 /* no security */
#define SCR_SD_SECURITY_1_0 1 /* security protocol 1.0 */
#define SCR_SD_SECURITY_1_0_2 2 /* security protocol 1.0 */
#define SCR_SD_BUS_WIDTHS(scr) MMC_RSP_BITS((scr), 48, 4)
#define SCR_SD_BUS_WIDTHS_1BIT (1 << 0) /* 1bit (DAT0) */
#define SCR_SD_BUS_WIDTHS_4BIT (1 << 2) /* 4bit (DAT0-3) */
#define SCR_SD_SPEC3(scr) MMC_RSP_BITS((scr), 47, 1)
#define SCR_EX_SECURITY(scr) MMC_RSP_BITS((scr), 43, 4)
#define SCR_SD_SPEC4(scr) MMC_RSP_BITS((scr), 42, 1)
#define SCR_RESERVED(scr) MMC_RSP_BITS((scr), 34, 8)
#define SCR_CMD_SUPPORT_CMD23(scr) MMC_RSP_BITS((scr), 33, 1)
#define SCR_CMD_SUPPORT_CMD20(scr) MMC_RSP_BITS((scr), 32, 1)
#define SCR_RESERVED2(scr) MMC_RSP_BITS((scr), 0, 32)
/* Status of Switch Function */
#define SFUNC_STATUS_GROUP(status, group) \
(__bitfield((uint32_t *)(status), 400 + (group - 1) * 16, 16))
#define SD_ACCESS_MODE_SDR12 0
#define SD_ACCESS_MODE_SDR25 1
#define SD_ACCESS_MODE_SDR50 2
#define SD_ACCESS_MODE_SDR104 3
#define SD_ACCESS_MODE_DDR50 4
static inline uint32_t MMC_RSP_BITS(uint32_t *src, int start, int len)
{
uint32_t mask = (len % 32 == 0) ? UINT_MAX : UINT_MAX >> (32 - (len % 32));
size_t word = 3 - start / 32;
size_t shift = start % 32;
uint32_t right = src[word] >> shift;
uint32_t left = (len + shift <= 32) ? 0 : src[word - 1] << ((32 - shift) % 32);
return (left | right) & mask;
}
#endif //_SDMMC_DEFS_H_

View File

@ -0,0 +1,165 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "esp_err.h"
#include "sdmmc_types.h"
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SDMMC_HOST_SLOT_0 0 ///< SDMMC slot 0
#define SDMMC_HOST_SLOT_1 1 ///< SDMMC slot 1
/**
* @brief Default sdmmc_host_t structure initializer for SDMMC peripheral
*
* Uses SDMMC peripheral, with 4-bit mode enabled, and max frequency set to 20MHz
*/
#define SDMMC_HOST_DEFAULT() {\
.flags = SDMMC_HOST_FLAG_4BIT, \
.slot = SDMMC_HOST_SLOT_1, \
.max_freq_khz = SDMMC_FREQ_DEFAULT, \
.io_voltage = 3.3f, \
.init = &sdmmc_host_init, \
.set_bus_width = &sdmmc_host_set_bus_width, \
.set_card_clk = &sdmmc_host_set_card_clk, \
.do_transaction = &sdmmc_host_do_transaction, \
.deinit = &sdmmc_host_deinit, \
}
/**
* Extra configuration for SDMMC peripheral slot
*/
typedef struct {
gpio_num_t gpio_cd; ///< GPIO number of card detect signal
gpio_num_t gpio_wp; ///< GPIO number of write protect signal
} sdmmc_slot_config_t;
#define SDMMC_SLOT_NO_CD ((gpio_num_t) -1) ///< indicates that card detect line is not used
#define SDMMC_SLOT_NO_WP ((gpio_num_t) -1) ///< indicates that write protect line is not used
/**
* Macro defining default configuration of SDMMC host slot
*/
#define SDMMC_SLOT_CONFIG_DEFAULT() {\
.gpio_cd = SDMMC_SLOT_NO_CD, \
.gpio_wp = SDMMC_SLOT_NO_WP, \
}
/**
* @brief Initialize SDMMC host peripheral
*
* @note This function is not thread safe
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if sdmmc_host_init was already called
* - ESP_ERR_NO_MEM if memory can not be allocated
*/
esp_err_t sdmmc_host_init();
/**
* @brief Initialize given slot of SDMMC peripheral
*
* On the ESP32, SDMMC peripheral has two slots:
* - Slot 0: 8-bit wide, maps to HS1_* signals in PIN MUX
* - Slot 1: 4-bit wide, maps to HS2_* signals in PIN MUX
*
* Card detect and write protect signals can be routed to
* arbitrary GPIOs using GPIO matrix.
*
* @note This function is not thread safe
*
* @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1)
* @param slot_config additional configuration for the slot
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if host has not been initialized using sdmmc_host_init
*/
esp_err_t sdmmc_host_init_slot(int slot, const sdmmc_slot_config_t* slot_config);
/**
* @brief Select bus width to be used for data transfer
*
* SD/MMC card must be initialized prior to this command, and a command to set
* bus width has to be sent to the card (e.g. SD_APP_SET_BUS_WIDTH)
*
* @note This function is not thread safe
*
* @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1)
* @param width bus width (1, 4, or 8 for slot 0; 1 or 4 for slot 1)
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if slot number or width is not valid
*/
esp_err_t sdmmc_host_set_bus_width(int slot, size_t width);
/**
* @brief Set card clock frequency
*
* Currently only integer fractions of 40MHz clock can be used.
* For High Speed cards, 40MHz can be used.
* For Default Speed cards, 20MHz can be used.
*
* @note This function is not thread safe
*
* @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1)
* @param freq_khz card clock frequency, in kHz
* @return
* - ESP_OK on success
* - other error codes may be returned in the future
*/
esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz);
/**
* @brief Send command to the card and get response
*
* This function returns when command is sent and response is received,
* or data is transferred, or timeout occurs.
*
* @note This function is not thread safe w.r.t. init/deinit functions,
* and bus width/clock speed configuration functions. Multiple tasks
* can call sdmmc_host_do_transaction as long as other sdmmc_host_*
* functions are not called.
*
* @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1)
* @param cmdinfo pointer to structure describing command and data to transfer
* @return
* - ESP_OK on success
* - ESP_ERR_TIMEOUT if response or data transfer has timed out
* - ESP_ERR_INVALID_CRC if response or data transfer CRC check has failed
* - ESP_ERR_INVALID_RESPONSE if the card has sent an invalid response
*/
esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo);
/**
* @brief Disable SDMMC host and release allocated resources
*
* @note This function is not thread safe
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if sdmmc_host_init function has not been called
*/
esp_err_t sdmmc_host_deinit();
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,140 @@
/*
* Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org>
* Adaptations to ESP-IDF Copyright (c) 2016 Espressif Systems (Shanghai) PTE LTD
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _SDMMC_TYPES_H_
#define _SDMMC_TYPES_H_
#include <stdint.h>
#include <stddef.h>
#include "esp_err.h"
/**
* Decoded values from SD card Card Specific Data register
*/
typedef struct {
int csd_ver; /*!< CSD structure format */
int mmc_ver; /*!< MMC version (for CID format) */
int capacity; /*!< total number of sectors */
int sector_size; /*!< sector size in bytes */
int read_block_len; /*!< block length for reads */
int card_command_class; /*!< Card Command Class for SD */
int tr_speed; /*!< Max transfer speed */
} sdmmc_csd_t;
/**
* Decoded values from SD card Card IDentification register
*/
typedef struct {
int mfg_id; /*!< manufacturer identification number */
int oem_id; /*!< OEM/product identification number */
char name[8]; /*!< product name (MMC v1 has the longest) */
int revision; /*!< product revision */
int serial; /*!< product serial number */
int date; /*!< manufacturing date */
} sdmmc_cid_t;
/**
* Decoded values from SD Configuration Register
*/
typedef struct {
int sd_spec; /*!< SD Physical layer specification version, reported by card */
int bus_width; /*!< bus widths supported by card: BIT(0) — 1-bit bus, BIT(2) — 4-bit bus */
} sdmmc_scr_t;
/**
* SD/MMC command response buffer
*/
typedef uint32_t sdmmc_response_t[4];
/**
* SD/MMC command information
*/
typedef struct {
uint32_t opcode; /*!< SD or MMC command index */
uint32_t arg; /*!< SD/MMC command argument */
sdmmc_response_t response; /*!< response buffer */
void* data; /*!< buffer to send or read into */
size_t datalen; /*!< length of data buffer */
size_t blklen; /*!< block length */
int flags; /*!< see below */
#define SCF_ITSDONE 0x0001 /*!< command is complete */
#define SCF_CMD(flags) ((flags) & 0x00f0)
#define SCF_CMD_AC 0x0000
#define SCF_CMD_ADTC 0x0010
#define SCF_CMD_BC 0x0020
#define SCF_CMD_BCR 0x0030
#define SCF_CMD_READ 0x0040 /*!< read command (data expected) */
#define SCF_RSP_BSY 0x0100
#define SCF_RSP_136 0x0200
#define SCF_RSP_CRC 0x0400
#define SCF_RSP_IDX 0x0800
#define SCF_RSP_PRESENT 0x1000
/* response types */
#define SCF_RSP_R0 0 /*!< none */
#define SCF_RSP_R1 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX)
#define SCF_RSP_R1B (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX|SCF_RSP_BSY)
#define SCF_RSP_R2 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_136)
#define SCF_RSP_R3 (SCF_RSP_PRESENT)
#define SCF_RSP_R4 (SCF_RSP_PRESENT)
#define SCF_RSP_R5 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX)
#define SCF_RSP_R5B (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX|SCF_RSP_BSY)
#define SCF_RSP_R6 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX)
#define SCF_RSP_R7 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX)
esp_err_t error; /*!< error returned from transfer */
} sdmmc_command_t;
/**
* SD/MMC Host description
*
* This structure defines properties of SD/MMC host and functions
* of SD/MMC host which can be used by upper layers.
*/
typedef struct {
uint32_t flags; /*!< flags defining host properties */
#define SDMMC_HOST_FLAG_1BIT BIT(0) /*!< host supports 1-line SD and MMC protocol */
#define SDMMC_HOST_FLAG_4BIT BIT(1) /*!< host supports 4-line SD and MMC protocol */
#define SDMMC_HOST_FLAG_8BIT BIT(2) /*!< host supports 8-line MMC protocol */
#define SDMMC_HOST_FLAG_SPI BIT(3) /*!< host supports SPI protocol */
int slot; /*!< slot number, to be passed to host functions */
int max_freq_khz; /*!< max frequency supported by the host */
#define SDMMC_FREQ_DEFAULT 20000 /*!< SD/MMC Default speed (limited by clock divider) */
#define SDMMC_FREQ_HIGHSPEED 40000 /*!< SD High speed (limited by clock divider) */
#define SDMMC_FREQ_PROBING 4000 /*!< SD/MMC probing speed */
float io_voltage; /*!< I/O voltage used by the controller (voltage switching is not supported) */
esp_err_t (*init)(void); /*!< Host function to initialize the driver */
esp_err_t (*set_bus_width)(int slot, size_t width); /*!< host function to set bus width */
esp_err_t (*set_card_clk)(int slot, uint32_t freq_khz); /*!< host function to set card clock frequency */
esp_err_t (*do_transaction)(int slot, sdmmc_command_t* cmdinfo); /*!< host function to do a transaction */
esp_err_t (*deinit)(void); /*!< host function to deinitialize the driver */
} sdmmc_host_t;
/**
* SD/MMC card information structure
*/
typedef struct {
sdmmc_host_t host; /*!< Host with which the card is associated */
uint32_t ocr; /*!< OCR (Operation Conditions Register) value */
sdmmc_cid_t cid; /*!< decoded CID (Card IDentification) register value */
sdmmc_csd_t csd; /*!< decoded CSD (Card-Specific Data) register value */
sdmmc_scr_t scr; /*!< decoded SCR (SD card Configuration Register) value */
uint16_t rca; /*!< RCA (Relative Card Address) */
} sdmmc_card_t;
#endif // _SDMMC_TYPES_H_

View File

@ -0,0 +1,463 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdbool.h>
#include <stddef.h>
#include <sys/param.h>
#include "esp_log.h"
#include "esp_intr_alloc.h"
#include "soc/sdmmc_struct.h"
#include "soc/sdmmc_reg.h"
#include "soc/io_mux_reg.h"
#include "soc/gpio_sig_map.h"
#include "rom/gpio.h"
#include "driver/gpio.h"
#include "driver/sdmmc_host.h"
#include "sdmmc_private.h"
#define SDMMC_EVENT_QUEUE_LENGTH 32
typedef struct {
uint32_t clk;
uint32_t cmd;
uint32_t d0;
uint32_t d1;
uint32_t d2;
uint32_t d3;
uint32_t d4;
uint32_t d5;
uint32_t d6;
uint32_t d7;
uint8_t card_detect;
uint8_t write_protect;
uint8_t width;
} sdmmc_slot_info_t;
static void sdmmc_isr(void* arg);
static void sdmmc_host_dma_init();
static const sdmmc_slot_info_t s_slot_info[2] = {
{
.clk = PERIPHS_IO_MUX_SD_CLK_U,
.cmd = PERIPHS_IO_MUX_SD_CMD_U,
.d0 = PERIPHS_IO_MUX_SD_DATA0_U,
.d1 = PERIPHS_IO_MUX_SD_DATA1_U,
.d2 = PERIPHS_IO_MUX_SD_DATA2_U,
.d3 = PERIPHS_IO_MUX_SD_DATA3_U,
.d4 = PERIPHS_IO_MUX_GPIO16_U,
.d5 = PERIPHS_IO_MUX_GPIO17_U,
.d6 = PERIPHS_IO_MUX_GPIO5_U,
.d7 = PERIPHS_IO_MUX_GPIO18_U,
.card_detect = HOST_CARD_DETECT_N_1_IDX,
.write_protect = HOST_CARD_WRITE_PRT_1_IDX,
.width = 8
},
{
.clk = PERIPHS_IO_MUX_MTMS_U,
.cmd = PERIPHS_IO_MUX_MTDO_U,
.d0 = PERIPHS_IO_MUX_GPIO2_U,
.d1 = PERIPHS_IO_MUX_GPIO4_U,
.d2 = PERIPHS_IO_MUX_MTDI_U,
.d3 = PERIPHS_IO_MUX_MTCK_U,
.card_detect = HOST_CARD_DETECT_N_2_IDX,
.write_protect = HOST_CARD_WRITE_PRT_2_IDX,
.width = 4
}
};
static const char* TAG = "sdmmc_periph";
static intr_handle_t s_intr_handle;
static QueueHandle_t s_event_queue;
void sdmmc_host_reset()
{
// Set reset bits
SDMMC.ctrl.controller_reset = 1;
SDMMC.ctrl.dma_reset = 1;
SDMMC.ctrl.fifo_reset = 1;
// Wait for the reset bits to be cleared by hardware
while (SDMMC.ctrl.controller_reset || SDMMC.ctrl.fifo_reset || SDMMC.ctrl.dma_reset) {
;
}
}
/* We have two clock divider stages:
* - one is the clock generator which drives SDMMC peripheral,
* it can be configured using SDMMC.clock register. It can generate
* frequencies 160MHz/(N + 1), where 0 < N < 16, I.e. from 10 to 80 MHz.
* - 4 clock dividers inside SDMMC peripheral, which can divide clock
* from the first stage by 2 * M, where 0 < M < 255
* (they can also be bypassed).
*
* For cards which aren't UHS-1 or UHS-2 cards, which we don't support,
* maximum bus frequency in high speed (HS) mode is 50 MHz.
* Note: for non-UHS-1 cards, HS mode is optional.
* Default speed (DS) mode is mandatory, it works up to 25 MHz.
* Whether the card supports HS or not can be determined using TRAN_SPEED
* field of card's CSD register.
*
* 50 MHz can not be obtained exactly, closest we can get is 53 MHz.
* For now set the first stage divider to generate 40MHz, and then configure
* the second stage dividers to generate the frequency requested.
*
* Of the second stage dividers, div0 is used for card 0, and div1 is used
* for card 1.
*/
static void sdmmc_host_input_clk_enable()
{
// Set frequency to 160MHz / (p + 1) = 40MHz, duty cycle (h + 1)/(p + 1) = 1/2
SDMMC.clock.div_factor_p = 3;
SDMMC.clock.div_factor_h = 1;
SDMMC.clock.div_factor_m = 3;
// Set phases for in/out clocks
SDMMC.clock.phase_dout = 4;
SDMMC.clock.phase_din = 4;
SDMMC.clock.phase_core = 0;
// Wait for the clock to propagate
ets_delay_us(10);
}
static void sdmmc_host_input_clk_disable()
{
SDMMC.clock.val = 0;
}
static void sdmmc_host_clock_update_command(int slot)
{
// Clock update command (not a real command; just updates CIU registers)
sdmmc_hw_cmd_t cmd_val = {
.card_num = slot,
.update_clk_reg = 1,
.wait_complete = 1
};
bool repeat = true;
while(repeat) {
sdmmc_host_start_command(slot, cmd_val, 0);
while (true) {
// Sending clock update command to the CIU can generate HLE error.
// According to the manual, this is okay and we must retry the command.
if (SDMMC.rintsts.hle) {
SDMMC.rintsts.hle = 1;
repeat = true;
break;
}
// When the command is accepted by CIU, start_command bit will be
// cleared in SDMMC.cmd register.
if (SDMMC.cmd.start_command == 0) {
repeat = false;
break;
}
}
}
}
esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz)
{
if (!(slot == 0 || slot == 1)) {
return ESP_ERR_INVALID_ARG;
}
const int clk40m = 40000;
// Disable clock first
SDMMC.clkena.cclk_enable &= ~BIT(slot);
sdmmc_host_clock_update_command(slot);
// Calculate new dividers
int div = 0;
if (freq_khz < clk40m) {
// round up; extra *2 is because clock divider divides by 2*n
div = (clk40m + freq_khz * 2 - 1) / (freq_khz * 2);
}
ESP_LOGD(TAG, "slot=%d div=%d freq=%dkHz", slot, div,
(div == 0) ? clk40m : clk40m / (2 * div));
// Program CLKDIV and CLKSRC, send them to the CIU
switch(slot) {
case 0:
SDMMC.clksrc.card0 = 0;
SDMMC.clkdiv.div0 = div;
break;
case 1:
SDMMC.clksrc.card1 = 1;
SDMMC.clkdiv.div1 = div;
break;
}
sdmmc_host_clock_update_command(slot);
// Re-enable clocks
SDMMC.clkena.cclk_enable |= BIT(slot);
SDMMC.clkena.cclk_low_power |= BIT(slot);
sdmmc_host_clock_update_command(slot);
return ESP_OK;
}
esp_err_t sdmmc_host_start_command(int slot, sdmmc_hw_cmd_t cmd, uint32_t arg) {
if (!(slot == 0 || slot == 1)) {
return ESP_ERR_INVALID_ARG;
}
while (SDMMC.cmd.start_command == 1) {
;
}
SDMMC.cmdarg = arg;
cmd.card_num = slot;
cmd.start_command = 1;
SDMMC.cmd = cmd;
return ESP_OK;
}
esp_err_t sdmmc_host_init()
{
if (s_intr_handle) {
return ESP_ERR_INVALID_STATE;
}
// Enable clock to peripheral
sdmmc_host_input_clk_enable();
// Reset
sdmmc_host_reset();
ESP_LOGD(TAG, "peripheral version %x, hardware config %08x", SDMMC.verid, SDMMC.hcon);
// Clear interrupt status and set interrupt mask to known state
SDMMC.rintsts.val = 0xffffffff;
SDMMC.intmask.val = 0;
SDMMC.ctrl.int_enable = 0;
// Allocate event queue
s_event_queue = xQueueCreate(SDMMC_EVENT_QUEUE_LENGTH, sizeof(sdmmc_event_t));
if (!s_event_queue) {
return ESP_ERR_NO_MEM;
}
// Attach interrupt handler
esp_err_t ret = esp_intr_alloc(ETS_SDIO_HOST_INTR_SOURCE, 0, &sdmmc_isr, s_event_queue, &s_intr_handle);
if (ret != ESP_OK) {
vQueueDelete(s_event_queue);
s_event_queue = NULL;
return ret;
}
// Enable interrupts
SDMMC.intmask.val =
SDMMC_INTMASK_CD |
SDMMC_INTMASK_CMD_DONE |
SDMMC_INTMASK_DATA_OVER |
SDMMC_INTMASK_RCRC | SDMMC_INTMASK_DCRC |
SDMMC_INTMASK_RTO | SDMMC_INTMASK_DTO | SDMMC_INTMASK_HTO |
SDMMC_INTMASK_SBE | SDMMC_INTMASK_EBE |
SDMMC_INTMASK_RESP_ERR | SDMMC_INTMASK_HLE;
SDMMC.ctrl.int_enable = 1;
// Enable DMA
sdmmc_host_dma_init();
// Initialize transaction handler
ret = sdmmc_host_transaction_handler_init();
if (ret != ESP_OK) {
vQueueDelete(s_event_queue);
s_event_queue = NULL;
esp_intr_free(s_intr_handle);
s_intr_handle = NULL;
return ret;
}
return ESP_OK;
}
static inline void configure_pin(uint32_t io_mux_reg)
{
const int sdmmc_func = 3;
const int drive_strength = 3;
PIN_INPUT_ENABLE(io_mux_reg);
PIN_FUNC_SELECT(io_mux_reg, sdmmc_func);
PIN_SET_DRV(io_mux_reg, drive_strength);
}
esp_err_t sdmmc_host_init_slot(int slot, const sdmmc_slot_config_t* slot_config)
{
if (!s_intr_handle) {
return ESP_ERR_INVALID_STATE;
}
if (!(slot == 0 || slot == 1)) {
return ESP_ERR_INVALID_ARG;
}
if (slot_config == NULL) {
return ESP_ERR_INVALID_ARG;
}
int gpio_cd = slot_config->gpio_cd;
int gpio_wp = slot_config->gpio_wp;
// Configure pins
const sdmmc_slot_info_t* pslot = &s_slot_info[slot];
configure_pin(pslot->clk);
configure_pin(pslot->cmd);
configure_pin(pslot->d0);
configure_pin(pslot->d1);
configure_pin(pslot->d2);
configure_pin(pslot->d3);
if (pslot->width == 8) {
configure_pin(pslot->d4);
configure_pin(pslot->d5);
configure_pin(pslot->d6);
configure_pin(pslot->d7);
}
if (gpio_cd != -1) {
gpio_set_direction(gpio_cd, GPIO_MODE_INPUT);
gpio_matrix_in(gpio_cd, pslot->card_detect, 0);
}
if (gpio_wp != -1) {
gpio_set_direction(gpio_wp, GPIO_MODE_INPUT);
gpio_matrix_in(gpio_wp, pslot->write_protect, 0);
}
// By default, set probing frequency (400kHz) and 1-bit bus
esp_err_t ret = sdmmc_host_set_card_clk(slot, 400);
if (ret != ESP_OK) {
return ret;
}
ret = sdmmc_host_set_bus_width(slot, 1);
if (ret != ESP_OK) {
return ret;
}
return ESP_OK;
}
esp_err_t sdmmc_host_deinit()
{
if (!s_intr_handle) {
return ESP_ERR_INVALID_STATE;
}
esp_intr_free(s_intr_handle);
s_intr_handle = NULL;
vQueueDelete(s_event_queue);
s_event_queue = NULL;
sdmmc_host_input_clk_disable();
sdmmc_host_transaction_handler_deinit();
return ESP_OK;
}
esp_err_t sdmmc_host_wait_for_event(int tick_count, sdmmc_event_t* out_event)
{
if (!out_event) {
return ESP_ERR_INVALID_ARG;
}
if (!s_event_queue) {
return ESP_ERR_INVALID_STATE;
}
int ret = xQueueReceive(s_event_queue, out_event, tick_count);
if (ret == pdFALSE) {
return ESP_ERR_TIMEOUT;
}
return ESP_OK;
}
esp_err_t sdmmc_host_set_bus_width(int slot, size_t width)
{
if (!(slot == 0 || slot == 1)) {
return ESP_ERR_INVALID_ARG;
}
if (s_slot_info[slot].width < width) {
return ESP_ERR_INVALID_ARG;
}
const uint16_t mask = BIT(slot);
if (width == 1) {
SDMMC.ctype.card_width_8 &= ~mask;
SDMMC.ctype.card_width &= ~mask;
} else if (width == 4) {
SDMMC.ctype.card_width_8 &= ~mask;
SDMMC.ctype.card_width |= mask;
} else if (width == 8){
SDMMC.ctype.card_width_8 |= mask;
} else {
return ESP_ERR_INVALID_ARG;
}
ESP_LOGD(TAG, "slot=%d width=%d", slot, width);
return ESP_OK;
}
static void sdmmc_host_dma_init()
{
SDMMC.ctrl.dma_enable = 1;
SDMMC.bmod.val = 0;
SDMMC.bmod.sw_reset = 1;
SDMMC.idinten.ni = 1;
SDMMC.idinten.ri = 1;
SDMMC.idinten.ti = 1;
}
void sdmmc_host_dma_stop()
{
SDMMC.ctrl.use_internal_dma = 0;
SDMMC.ctrl.dma_reset = 1;
SDMMC.bmod.fb = 0;
SDMMC.bmod.enable = 0;
}
void sdmmc_host_dma_prepare(sdmmc_desc_t* desc, size_t block_size, size_t data_size)
{
// TODO: set timeout depending on data size
SDMMC.tmout.val = 0xffffffff;
// Set size of data and DMA descriptor pointer
SDMMC.bytcnt = data_size;
SDMMC.blksiz = block_size;
SDMMC.dbaddr = desc;
// Enable everything needed to use DMA
SDMMC.ctrl.dma_enable = 1;
SDMMC.ctrl.use_internal_dma = 1;
SDMMC.bmod.enable = 1;
SDMMC.bmod.fb = 1;
sdmmc_host_dma_resume();
}
void sdmmc_host_dma_resume()
{
SDMMC.pldmnd = 1;
}
/**
* @brief SDMMC interrupt handler
*
* Ignoring SDIO and streaming read/writes for now (and considering just SD memory cards),
* all communication is driven by the master, and the hardware handles things like stop
* commands automatically. So the interrupt handler doesn't need to do much, we just push
* interrupt status into a queue, clear interrupt flags, and let the task currently doing
* communication figure out what to do next.
*
* Card detect interrupts pose a small issue though, because if a card is plugged in and
* out a few times, while there is no task to process the events, event queue can become
* full and some card detect events may be dropped. We ignore this problem for now, since
* the there are no other interesting events which can get lost due to this.
*/
static void sdmmc_isr(void* arg) {
QueueHandle_t queue = (QueueHandle_t) arg;
sdmmc_event_t event;
uint32_t pending = SDMMC.mintsts.val;
SDMMC.rintsts.val = pending;
event.sdmmc_status = pending;
uint32_t dma_pending = SDMMC.idsts.val;
SDMMC.idsts.val = dma_pending;
event.dma_status = dma_pending & 0x1f;
int higher_priority_task_awoken = pdFALSE;
xQueueSendFromISR(queue, &event, &higher_priority_task_awoken);
if (higher_priority_task_awoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}

View File

@ -0,0 +1,44 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "soc/sdmmc_struct.h"
typedef struct {
uint32_t sdmmc_status; ///< masked SDMMC interrupt status
uint32_t dma_status; ///< masked DMA interrupt status
} sdmmc_event_t;
void sdmmc_host_reset();
esp_err_t sdmmc_host_start_command(int slot, sdmmc_hw_cmd_t cmd, uint32_t arg);
esp_err_t sdmmc_host_wait_for_event(int tick_count, sdmmc_event_t* out_event);
void sdmmc_host_dma_prepare(sdmmc_desc_t* desc, size_t block_size, size_t data_size);
void sdmmc_host_dma_stop();
void sdmmc_host_dma_resume();
esp_err_t sdmmc_host_transaction_handler_init();
void sdmmc_host_transaction_handler_deinit();

View File

@ -0,0 +1,372 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "soc/sdmmc_reg.h"
#include "soc/sdmmc_struct.h"
#include "driver/sdmmc_types.h"
#include "driver/sdmmc_defs.h"
#include "driver/sdmmc_host.h"
#include "sdmmc_private.h"
/* Number of DMA descriptors used for transfer.
* Increasing this value above 4 doesn't improve performance for the usual case
* of SD memory cards (most data transfers are multiples of 512 bytes).
*/
#define SDMMC_DMA_DESC_CNT 4
static const char* TAG = "sdmmc_req";
typedef enum {
SDMMC_IDLE,
SDMMC_SENDING_CMD,
SDMMC_SENDING_DATA,
SDMMC_BUSY,
} sdmmc_req_state_t;
typedef struct {
uint8_t* ptr;
size_t size_remaining;
size_t next_desc;
size_t desc_remaining;
} sdmmc_transfer_state_t;
const uint32_t SDMMC_DATA_ERR_MASK =
SDMMC_INTMASK_DTO | SDMMC_INTMASK_DCRC |
SDMMC_INTMASK_HTO | SDMMC_INTMASK_SBE |
SDMMC_INTMASK_EBE;
const uint32_t SDMMC_DMA_DONE_MASK =
SDMMC_IDMAC_INTMASK_RI | SDMMC_IDMAC_INTMASK_TI |
SDMMC_IDMAC_INTMASK_NI;
const uint32_t SDMMC_CMD_ERR_MASK =
SDMMC_INTMASK_RTO |
SDMMC_INTMASK_RCRC |
SDMMC_INTMASK_RESP_ERR;
static sdmmc_desc_t s_dma_desc[SDMMC_DMA_DESC_CNT];
static sdmmc_transfer_state_t s_cur_transfer = { 0 };
static QueueHandle_t s_request_mutex;
static esp_err_t handle_idle_state_events();
static sdmmc_hw_cmd_t make_hw_cmd(sdmmc_command_t* cmd);
static esp_err_t handle_event(sdmmc_command_t* cmd, sdmmc_req_state_t* pstate);
static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_req_state_t* pstate);
static void process_command_response(uint32_t status, sdmmc_command_t* cmd);
static void fill_dma_descriptors(size_t num_desc);
esp_err_t sdmmc_host_transaction_handler_init()
{
assert(s_request_mutex == NULL);
s_request_mutex = xSemaphoreCreateMutex();
if (!s_request_mutex) {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
void sdmmc_host_transaction_handler_deinit()
{
assert(s_request_mutex);
vSemaphoreDelete(s_request_mutex);
s_request_mutex = NULL;
}
esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo)
{
xSemaphoreTake(s_request_mutex, portMAX_DELAY);
// dispose of any events which happened asynchronously
handle_idle_state_events();
// convert cmdinfo to hardware register value
sdmmc_hw_cmd_t hw_cmd = make_hw_cmd(cmdinfo);
if (cmdinfo->data) {
// these constraints should be handled by upper layer
assert(cmdinfo->datalen >= 4);
assert(cmdinfo->blklen % 4 == 0);
// this clears "owned by IDMAC" bits
memset(s_dma_desc, 0, sizeof(s_dma_desc));
// initialize first descriptor
s_dma_desc[0].first_descriptor = 1;
// save transfer info
s_cur_transfer.ptr = (uint8_t*) cmdinfo->data;
s_cur_transfer.size_remaining = cmdinfo->datalen;
s_cur_transfer.next_desc = 0;
s_cur_transfer.desc_remaining = (cmdinfo->datalen + SDMMC_DMA_MAX_BUF_LEN - 1) / SDMMC_DMA_MAX_BUF_LEN;
// prepare descriptors
fill_dma_descriptors(SDMMC_DMA_DESC_CNT);
// write transfer info into hardware
sdmmc_host_dma_prepare(&s_dma_desc[0], cmdinfo->blklen, cmdinfo->datalen);
}
// write command into hardware, this also sends the command to the card
esp_err_t ret = sdmmc_host_start_command(slot, hw_cmd, cmdinfo->arg);
if (ret != ESP_OK) {
xSemaphoreGive(s_request_mutex);
return ret;
}
// process events until transfer is complete
cmdinfo->error = ESP_OK;
sdmmc_req_state_t state = SDMMC_SENDING_CMD;
while (state != SDMMC_IDLE) {
ret = handle_event(cmdinfo, &state);
if (ret != ESP_OK) {
break;
}
}
xSemaphoreGive(s_request_mutex);
return ret;
}
static void fill_dma_descriptors(size_t num_desc)
{
for (size_t i = 0; i < num_desc; ++i) {
if (s_cur_transfer.size_remaining == 0) {
return;
}
const size_t next = s_cur_transfer.next_desc;
sdmmc_desc_t* desc = &s_dma_desc[next];
assert(!desc->owned_by_idmac);
size_t size_to_fill =
(s_cur_transfer.size_remaining < SDMMC_DMA_MAX_BUF_LEN) ?
s_cur_transfer.size_remaining : SDMMC_DMA_MAX_BUF_LEN;
bool last = size_to_fill == s_cur_transfer.size_remaining;
desc->last_descriptor = last;
desc->second_address_chained = 1;
desc->owned_by_idmac = 1;
desc->buffer1_ptr = s_cur_transfer.ptr;
desc->next_desc_ptr = (last) ? NULL : &s_dma_desc[(next + 1) % SDMMC_DMA_DESC_CNT];
desc->buffer1_size = size_to_fill;
s_cur_transfer.size_remaining -= size_to_fill;
s_cur_transfer.ptr += size_to_fill;
s_cur_transfer.next_desc = (s_cur_transfer.next_desc + 1) % SDMMC_DMA_DESC_CNT;
ESP_LOGV(TAG, "fill %d desc=%d rem=%d next=%d last=%d sz=%d",
num_desc, next, s_cur_transfer.size_remaining,
s_cur_transfer.next_desc, desc->last_descriptor, desc->buffer1_size);
}
}
static esp_err_t handle_idle_state_events()
{
/* Handle any events which have happened in between transfers.
* Under current assumptions (no SDIO support) only card detect events
* can happen in the idle state.
*/
sdmmc_event_t evt;
while (sdmmc_host_wait_for_event(0, &evt) == ESP_OK) {
if (evt.sdmmc_status & SDMMC_INTMASK_CD) {
ESP_LOGV(TAG, "card detect event");
evt.sdmmc_status &= ~SDMMC_INTMASK_CD;
}
if (evt.sdmmc_status != 0 || evt.dma_status != 0) {
ESP_LOGE(TAG, "handle_idle_state_events unhandled: %08x %08x",
evt.sdmmc_status, evt.dma_status);
}
}
return ESP_OK;
}
static esp_err_t handle_event(sdmmc_command_t* cmd, sdmmc_req_state_t* state)
{
sdmmc_event_t evt;
esp_err_t err = sdmmc_host_wait_for_event(portMAX_DELAY, &evt);
if (err != ESP_OK) {
ESP_LOGE(TAG, "sdmmc_host_wait_for_event returned %d", err);
return err;
}
ESP_LOGV(TAG, "sdmmc_handle_event: evt %08x %08x", evt.sdmmc_status, evt.dma_status);
process_events(evt, cmd, state);
return ESP_OK;
}
static sdmmc_hw_cmd_t make_hw_cmd(sdmmc_command_t* cmd)
{
sdmmc_hw_cmd_t res = { 0 };
res.cmd_index = cmd->opcode;
if (cmd->opcode == MMC_STOP_TRANSMISSION) {
res.stop_abort_cmd = 1;
} else {
res.wait_complete = 1;
}
if (cmd->opcode == SD_APP_SET_BUS_WIDTH) {
res.send_auto_stop = 1;
res.data_expected = 1;
}
if (cmd->flags & SCF_RSP_PRESENT) {
res.response_expect = 1;
if (cmd->flags & SCF_RSP_136) {
res.response_long = 1;
}
}
if (cmd->flags & SCF_RSP_CRC) {
res.check_response_crc = 1;
}
res.use_hold_reg = 1;
if (cmd->data) {
res.data_expected = 1;
if ((cmd->flags & SCF_CMD_READ) == 0) {
res.rw = 1;
}
assert(cmd->datalen % cmd->blklen == 0);
if ((cmd->datalen / cmd->blklen) > 1) {
res.send_auto_stop = 1;
}
}
ESP_LOGV(TAG, "%s: opcode=%d, rexp=%d, crc=%d", __func__,
res.cmd_index, res.response_expect, res.check_response_crc);
return res;
}
static void process_command_response(uint32_t status, sdmmc_command_t* cmd)
{
if (cmd->flags & SCF_RSP_PRESENT) {
if (cmd->flags & SCF_RSP_136) {
cmd->response[3] = SDMMC.resp[0];
cmd->response[2] = SDMMC.resp[1];
cmd->response[1] = SDMMC.resp[2];
cmd->response[0] = SDMMC.resp[3];
} else {
cmd->response[0] = SDMMC.resp[0];
cmd->response[1] = 0;
cmd->response[2] = 0;
cmd->response[3] = 0;
}
}
if ((status & SDMMC_INTMASK_RTO) &&
cmd->opcode != MMC_ALL_SEND_CID &&
cmd->opcode != MMC_SELECT_CARD &&
cmd->opcode != MMC_STOP_TRANSMISSION) {
cmd->error = ESP_ERR_TIMEOUT;
} else if ((cmd->flags & SCF_RSP_CRC) && (status & SDMMC_INTMASK_RCRC)) {
cmd->error = ESP_ERR_INVALID_CRC;
} else if (status & SDMMC_INTMASK_RESP_ERR) {
cmd->error = ESP_ERR_INVALID_RESPONSE;
}
if (cmd->error != 0) {
if (cmd->data) {
sdmmc_host_dma_stop();
}
ESP_LOGD(TAG, "%s: error %d", __func__, cmd->error);
}
}
static void process_data_status(uint32_t status, sdmmc_command_t* cmd)
{
if (status & SDMMC_DATA_ERR_MASK) {
if (status & SDMMC_INTMASK_DTO) {
cmd->error = ESP_ERR_TIMEOUT;
} else if (status & SDMMC_INTMASK_DCRC) {
cmd->error = ESP_ERR_INVALID_CRC;
} else if ((status & SDMMC_INTMASK_EBE) &&
(cmd->flags & SCF_CMD_READ) == 0) {
cmd->error = ESP_ERR_TIMEOUT;
} else {
cmd->error = ESP_FAIL;
}
SDMMC.ctrl.fifo_reset = 1;
}
if (cmd->error != 0) {
if (cmd->data) {
sdmmc_host_dma_stop();
}
ESP_LOGD(TAG, "%s: error %d", __func__, cmd->error);
}
}
static inline bool mask_check_and_clear(uint32_t* state, uint32_t mask) {
bool ret = ((*state) & mask) != 0;
*state &= ~mask;
return ret;
}
static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_req_state_t* pstate)
{
const char* const s_state_names[] __attribute__((unused)) = {
"IDLE",
"SENDING_CMD",
"SENDIND_DATA",
"BUSY"
};
sdmmc_event_t orig_evt = evt;
ESP_LOGV(TAG, "%s: state=%s", __func__, s_state_names[*pstate]);
sdmmc_req_state_t next_state = *pstate;
sdmmc_req_state_t state = (sdmmc_req_state_t) -1;
while (next_state != state) {
state = next_state;
switch (state) {
case SDMMC_IDLE:
break;
case SDMMC_SENDING_CMD:
if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_CMD_ERR_MASK)) {
process_command_response(orig_evt.sdmmc_status, cmd);
break;
}
if (!mask_check_and_clear(&evt.sdmmc_status, SDMMC_INTMASK_CMD_DONE)) {
break;
}
process_command_response(orig_evt.sdmmc_status, cmd);
if (cmd->error != ESP_OK || cmd->data == NULL) {
next_state = SDMMC_IDLE;
break;
}
next_state = SDMMC_SENDING_DATA;
break;
case SDMMC_SENDING_DATA:
if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_DATA_ERR_MASK)) {
process_data_status(orig_evt.sdmmc_status, cmd);
sdmmc_host_dma_stop();
}
if (mask_check_and_clear(&evt.dma_status, SDMMC_DMA_DONE_MASK)) {
s_cur_transfer.desc_remaining--;
if (s_cur_transfer.size_remaining) {
fill_dma_descriptors(1);
sdmmc_host_dma_resume();
}
if (s_cur_transfer.desc_remaining == 0) {
next_state = SDMMC_BUSY;
}
}
break;
case SDMMC_BUSY:
if (!mask_check_and_clear(&evt.sdmmc_status, SDMMC_INTMASK_DATA_OVER)) {
break;
}
process_data_status(orig_evt.sdmmc_status, cmd);
next_state = SDMMC_IDLE;
break;
}
ESP_LOGV(TAG, "%s state=%s next_state=%s", __func__, s_state_names[state], s_state_names[next_state]);
}
*pstate = state;
return ESP_OK;
}

View File

@ -35,7 +35,8 @@ typedef int32_t esp_err_t;
#define ESP_ERR_NOT_FOUND 0x105
#define ESP_ERR_NOT_SUPPORTED 0x106
#define ESP_ERR_TIMEOUT 0x107
#define ESP_ERR_INVALID_RESPONSE 0x108
#define ESP_ERR_INVALID_CRC 0x109
#define ESP_ERR_WIFI_BASE 0x3000 /*!< Starting number of WiFi error codes */

View File

@ -0,0 +1,94 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _SOC_SDMMC_REG_H_
#define _SOC_SDMMC_REG_H_
#include "soc.h"
#define SDMMC_CTRL_REG (DR_REG_SDMMC_BASE + 0x00)
#define SDMMC_PWREN_REG (DR_REG_SDMMC_BASE + 0x04)
#define SDMMC_CLKDIV_REG (DR_REG_SDMMC_BASE + 0x08)
#define SDMMC_CLKSRC_REG (DR_REG_SDMMC_BASE + 0x0c)
#define SDMMC_CLKENA_REG (DR_REG_SDMMC_BASE + 0x10)
#define SDMMC_TMOUT_REG (DR_REG_SDMMC_BASE + 0x14)
#define SDMMC_CTYPE_REG (DR_REG_SDMMC_BASE + 0x18)
#define SDMMC_BLKSIZ_REG (DR_REG_SDMMC_BASE + 0x1c)
#define SDMMC_BYTCNT_REG (DR_REG_SDMMC_BASE + 0x20)
#define SDMMC_INTMASK_REG (DR_REG_SDMMC_BASE + 0x24)
#define SDMMC_CMDARG_REG (DR_REG_SDMMC_BASE + 0x28)
#define SDMMC_CMD_REG (DR_REG_SDMMC_BASE + 0x2c)
#define SDMMC_RESP0_REG (DR_REG_SDMMC_BASE + 0x30)
#define SDMMC_RESP1_REG (DR_REG_SDMMC_BASE + 0x34)
#define SDMMC_RESP2_REG (DR_REG_SDMMC_BASE + 0x38)
#define SDMMC_RESP3_REG (DR_REG_SDMMC_BASE + 0x3c)
#define SDMMC_MINTSTS_REG (DR_REG_SDMMC_BASE + 0x40)
#define SDMMC_RINTSTS_REG (DR_REG_SDMMC_BASE + 0x44)
#define SDMMC_STATUS_REG (DR_REG_SDMMC_BASE + 0x48)
#define SDMMC_FIFOTH_REG (DR_REG_SDMMC_BASE + 0x4c)
#define SDMMC_CDETECT_REG (DR_REG_SDMMC_BASE + 0x50)
#define SDMMC_WRTPRT_REG (DR_REG_SDMMC_BASE + 0x54)
#define SDMMC_GPIO_REG (DR_REG_SDMMC_BASE + 0x58)
#define SDMMC_TCBCNT_REG (DR_REG_SDMMC_BASE + 0x5c)
#define SDMMC_TBBCNT_REG (DR_REG_SDMMC_BASE + 0x60)
#define SDMMC_DEBNCE_REG (DR_REG_SDMMC_BASE + 0x64)
#define SDMMC_USRID_REG (DR_REG_SDMMC_BASE + 0x68)
#define SDMMC_VERID_REG (DR_REG_SDMMC_BASE + 0x6c)
#define SDMMC_HCON_REG (DR_REG_SDMMC_BASE + 0x70)
#define SDMMC_UHS_REG_REG (DR_REG_SDMMC_BASE + 0x74)
#define SDMMC_RST_N_REG (DR_REG_SDMMC_BASE + 0x78)
#define SDMMC_BMOD_REG (DR_REG_SDMMC_BASE + 0x80)
#define SDMMC_PLDMND_REG (DR_REG_SDMMC_BASE + 0x84)
#define SDMMC_DBADDR_REG (DR_REG_SDMMC_BASE + 0x88)
#define SDMMC_DBADDRU_REG (DR_REG_SDMMC_BASE + 0x8c)
#define SDMMC_IDSTS_REG (DR_REG_SDMMC_BASE + 0x8c)
#define SDMMC_IDINTEN_REG (DR_REG_SDMMC_BASE + 0x90)
#define SDMMC_DSCADDR_REG (DR_REG_SDMMC_BASE + 0x94)
#define SDMMC_DSCADDRL_REG (DR_REG_SDMMC_BASE + 0x98)
#define SDMMC_DSCADDRU_REG (DR_REG_SDMMC_BASE + 0x9c)
#define SDMMC_BUFADDRL_REG (DR_REG_SDMMC_BASE + 0xa0)
#define SDMMC_BUFADDRU_REG (DR_REG_SDMMC_BASE + 0xa4)
#define SDMMC_CARDTHRCTL_REG (DR_REG_SDMMC_BASE + 0x100)
#define SDMMC_BACK_END_POWER_REG (DR_REG_SDMMC_BASE + 0x104)
#define SDMMC_UHS_REG_EXT_REG (DR_REG_SDMMC_BASE + 0x108)
#define SDMMC_EMMC_DDR_REG_REG (DR_REG_SDMMC_BASE + 0x10c)
#define SDMMC_ENABLE_SHIFT_REG (DR_REG_SDMMC_BASE + 0x110)
#define SDMMC_CLOCK_REG (DR_REG_SDMMC_BASE + 0x800)
#define SDMMC_INTMASK_EBE BIT(15)
#define SDMMC_INTMASK_ACD BIT(14)
#define SDMMC_INTMASK_SBE BIT(13)
#define SDMMC_INTMASK_HLE BIT(12)
#define SDMMC_INTMASK_FRUN BIT(11)
#define SDMMC_INTMASK_HTO BIT(10)
#define SDMMC_INTMASK_DTO BIT(9)
#define SDMMC_INTMASK_RTO BIT(8)
#define SDMMC_INTMASK_DCRC BIT(7)
#define SDMMC_INTMASK_RCRC BIT(6)
#define SDMMC_INTMASK_RXDR BIT(5)
#define SDMMC_INTMASK_TXDR BIT(4)
#define SDMMC_INTMASK_DATA_OVER BIT(3)
#define SDMMC_INTMASK_CMD_DONE BIT(2)
#define SDMMC_INTMASK_RESP_ERR BIT(1)
#define SDMMC_INTMASK_CD BIT(0)
#define SDMMC_IDMAC_INTMASK_AI BIT(9)
#define SDMMC_IDMAC_INTMASK_NI BIT(8)
#define SDMMC_IDMAC_INTMASK_CES BIT(5)
#define SDMMC_IDMAC_INTMASK_DU BIT(4)
#define SDMMC_IDMAC_INTMASK_FBE BIT(2)
#define SDMMC_IDMAC_INTMASK_RI BIT(1)
#define SDMMC_IDMAC_INTMASK_TI BIT(0)
#endif /* _SOC_SDMMC_REG_H_ */

View File

@ -0,0 +1,371 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _SOC_SDMMC_STRUCT_H_
#define _SOC_SDMMC_STRUCT_H_
#include <stdint.h>
typedef struct {
uint32_t reserved1: 1;
uint32_t disable_int_on_completion: 1;
uint32_t last_descriptor: 1;
uint32_t first_descriptor: 1;
uint32_t second_address_chained: 1;
uint32_t end_of_ring: 1;
uint32_t reserved2: 24;
uint32_t card_error_summary: 1;
uint32_t owned_by_idmac: 1;
uint32_t buffer1_size: 13;
uint32_t buffer2_size: 13;
uint32_t reserved3: 6;
void* buffer1_ptr;
union {
void* buffer2_ptr;
void* next_desc_ptr;
};
} sdmmc_desc_t;
#define SDMMC_DMA_MAX_BUF_LEN 4096
_Static_assert(sizeof(sdmmc_desc_t) == 16, "invalid size of sdmmc_desc_t structure");
typedef struct {
uint32_t cmd_index: 6; ///< Command index
uint32_t response_expect: 1; ///< set if response is expected
uint32_t response_long: 1; ///< 0: short response expected, 1: long response expected
uint32_t check_response_crc: 1; ///< set if controller should check response CRC
uint32_t data_expected: 1; ///< 0: no data expected, 1: data expected
uint32_t rw: 1; ///< 0: read from card, 1: write to card (don't care if no data expected)
uint32_t stream_mode: 1; ///< 0: block transfer, 1: stream transfer (don't care if no data expected)
uint32_t send_auto_stop: 1; ///< set to send stop at the end of the transfer
uint32_t wait_complete: 1; ///< 0: send command at once, 1: wait for previous command to complete
uint32_t stop_abort_cmd: 1; ///< set if this is a stop or abort command intended to stop current transfer
uint32_t send_init: 1; ///< set to send init sequence (80 clocks of 1)
uint32_t card_num: 5; ///< card number
uint32_t update_clk_reg: 1; ///< 0: normal command, 1: don't send command, just update clock registers
uint32_t read_ceata: 1; ///< set if performing read from CE-ATA device
uint32_t ccs_expected: 1; ///< set if CCS is expected from CE-ATA device
uint32_t enable_boot: 1; ///< set for mandatory boot mode
uint32_t expect_boot_ack: 1; ///< when set along with enable_boot, controller expects boot ack pattern
uint32_t disable_boot: 1; ///< set to terminate boot operation (don't set along with enable_boot)
uint32_t boot_mode: 1; ///< 0: mandatory boot operation, 1: alternate boot operation
uint32_t volt_switch: 1; ///< set to enable voltage switching (for CMD11 only)
uint32_t use_hold_reg: 1; ///< clear to bypass HOLD register
uint32_t reserved: 1;
uint32_t start_command: 1; ///< Start command; once command is sent to the card, bit is cleared.
} sdmmc_hw_cmd_t; ///< command format used in cmd register; this structure is defined to make it easier to build command values
_Static_assert(sizeof(sdmmc_hw_cmd_t) == 4, "invalid size of sdmmc_cmd_t structure");
typedef volatile struct {
union {
struct {
uint32_t controller_reset: 1;
uint32_t fifo_reset: 1;
uint32_t dma_reset: 1;
uint32_t reserved1: 1;
uint32_t int_enable: 1;
uint32_t dma_enable: 1;
uint32_t read_wait: 1;
uint32_t send_irq_response: 1;
uint32_t abort_read_data: 1;
uint32_t send_ccsd: 1;
uint32_t send_auto_stop_ccsd: 1;
uint32_t ceata_device_interrupt_status: 1;
uint32_t reserved2: 4;
uint32_t card_voltage_a: 4;
uint32_t card_voltage_b: 4;
uint32_t enable_od_pullup: 1;
uint32_t use_internal_dma: 1;
uint32_t reserved3: 6;
};
uint32_t val;
} ctrl;
uint32_t pwren; ///< 1: enable power to card, 0: disable power to card
union {
struct {
uint32_t div0: 8; ///< 0: bypass, 1-255: divide clock by (2*div0).
uint32_t div1: 8; ///< 0: bypass, 1-255: divide clock by (2*div0).
uint32_t div2: 8; ///< 0: bypass, 1-255: divide clock by (2*div0).
uint32_t div3: 8; ///< 0: bypass, 1-255: divide clock by (2*div0).
};
uint32_t val;
} clkdiv;
union {
struct {
uint32_t card0: 2; ///< 0-3: select clock divider for card 0 among div0-div3
uint32_t card1: 2; ///< 0-3: select clock divider for card 1 among div0-div3
uint32_t reserved: 28;
};
uint32_t val;
} clksrc;
union {
struct {
uint32_t cclk_enable: 16; ///< 1: enable clock to card, 0: disable clock
uint32_t cclk_low_power: 16; ///< 1: enable clock gating when card is idle, 0: disable clock gating
};
uint32_t val;
} clkena;
union {
struct {
uint32_t response: 8; ///< response timeout, in card output clock cycles
uint32_t data: 24; ///< data read timeout, in card output clock cycles
};
uint32_t val;
} tmout;
union {
struct {
uint32_t card_width: 16; ///< one bit for each card: 0: 1-bit mode, 1: 4-bit mode
uint32_t card_width_8: 16; ///< one bit for each card: 0: not 8-bit mode (corresponding card_width bit is used), 1: 8-bit mode (card_width bit is ignored)
};
uint32_t val;
} ctype;
uint32_t blksiz: 16; ///< block size, default 0x200
uint32_t : 16;
uint32_t bytcnt; ///< number of bytes to be transferred
union {
struct {
uint32_t cd: 1; ///< Card detect interrupt enable
uint32_t re: 1; ///< Response error interrupt enable
uint32_t cmd_done: 1; ///< Command done interrupt enable
uint32_t dto: 1; ///< Data transfer over interrupt enable
uint32_t txdr: 1; ///< Transmit FIFO data request interrupt enable
uint32_t rxdr: 1; ///< Receive FIFO data request interrupt enable
uint32_t rcrc: 1; ///< Response CRC error interrupt enable
uint32_t dcrc: 1; ///< Data CRC error interrupt enable
uint32_t rto: 1; ///< Response timeout interrupt enable
uint32_t drto: 1; ///< Data read timeout interrupt enable
uint32_t hto: 1; ///< Data starvation-by-host timeout interrupt enable
uint32_t frun: 1; ///< FIFO underrun/overrun error interrupt enable
uint32_t hle: 1; ///< Hardware locked write error interrupt enable
uint32_t sbi_bci: 1; ///< Start bit error / busy clear interrupt enable
uint32_t acd: 1; ///< Auto command done interrupt enable
uint32_t ebe: 1; ///< End bit error / write no CRC interrupt enable
uint32_t sdio: 16; ///< SDIO interrupt enable
};
uint32_t val;
} intmask;
uint32_t cmdarg; ///< Command argument to be passed to card
sdmmc_hw_cmd_t cmd;
uint32_t resp[4]; ///< Response from card
union {
struct {
uint32_t cd: 1; ///< Card detect interrupt masked status
uint32_t re: 1; ///< Response error interrupt masked status
uint32_t cmd_done: 1; ///< Command done interrupt masked status
uint32_t dto: 1; ///< Data transfer over interrupt masked status
uint32_t txdr: 1; ///< Transmit FIFO data request interrupt masked status
uint32_t rxdr: 1; ///< Receive FIFO data request interrupt masked status
uint32_t rcrc: 1; ///< Response CRC error interrupt masked status
uint32_t dcrc: 1; ///< Data CRC error interrupt masked status
uint32_t rto: 1; ///< Response timeout interrupt masked status
uint32_t drto: 1; ///< Data read timeout interrupt masked status
uint32_t hto: 1; ///< Data starvation-by-host timeout interrupt masked status
uint32_t frun: 1; ///< FIFO underrun/overrun error interrupt masked status
uint32_t hle: 1; ///< Hardware locked write error interrupt masked status
uint32_t sbi_bci: 1; ///< Start bit error / busy clear interrupt masked status
uint32_t acd: 1; ///< Auto command done interrupt masked status
uint32_t ebe: 1; ///< End bit error / write no CRC interrupt masked status
uint32_t sdio: 16; ///< SDIO interrupt masked status
};
uint32_t val;
} mintsts;
union {
struct {
uint32_t cd: 1; ///< Card detect raw interrupt status
uint32_t re: 1; ///< Response error raw interrupt status
uint32_t cmd_done: 1; ///< Command done raw interrupt status
uint32_t dto: 1; ///< Data transfer over raw interrupt status
uint32_t txdr: 1; ///< Transmit FIFO data request raw interrupt status
uint32_t rxdr: 1; ///< Receive FIFO data request raw interrupt status
uint32_t rcrc: 1; ///< Response CRC error raw interrupt status
uint32_t dcrc: 1; ///< Data CRC error raw interrupt status
uint32_t rto: 1; ///< Response timeout raw interrupt status
uint32_t drto: 1; ///< Data read timeout raw interrupt status
uint32_t hto: 1; ///< Data starvation-by-host timeout raw interrupt status
uint32_t frun: 1; ///< FIFO underrun/overrun error raw interrupt status
uint32_t hle: 1; ///< Hardware locked write error raw interrupt status
uint32_t sbi_bci: 1; ///< Start bit error / busy clear raw interrupt status
uint32_t acd: 1; ///< Auto command done raw interrupt status
uint32_t ebe: 1; ///< End bit error / write no CRC raw interrupt status
uint32_t sdio: 16; ///< SDIO raw interrupt status
};
uint32_t val;
} rintsts; ///< interrupts can be cleared by writing this register
union {
struct {
uint32_t fifo_rx_watermark: 1; ///< FIFO reached receive watermark level
uint32_t fifo_tx_watermark: 1; ///< FIFO reached transmit watermark level
uint32_t fifo_empty: 1; ///< FIFO is empty
uint32_t fifo_full: 1; ///< FIFO is full
uint32_t cmd_fsm_state: 4; ///< command FSM state
uint32_t data3_status: 1; ///< this bit reads 1 if card is present
uint32_t data_busy: 1; ///< this bit reads 1 if card is busy
uint32_t data_fsm_busy: 1; ///< this bit reads 1 if transmit/receive FSM is busy
uint32_t response_index: 6; ///< index of the previous response
uint32_t fifo_count: 13; ///< number of filled locations in the FIFO
uint32_t dma_ack: 1; ///< DMA acknowledge signal
uint32_t dma_req: 1; ///< DMA request signal
};
uint32_t val;
} status;
union {
struct {
uint32_t tx_watermark: 12; ///< FIFO TX watermark level
uint32_t reserved1: 4;
uint32_t rx_watermark: 12; ///< FIFO RX watermark level
uint32_t dw_dma_mts: 3;
uint32_t reserved2: 1;
};
uint32_t val;
} fifoth;
union {
struct {
uint32_t cards: 2; ///< bit N reads 1 if card N is present
uint32_t reserved: 30;
};
uint32_t val;
} cdetect;
union {
struct {
uint32_t card0: 2; ///< bit N reads 1 if card N is write protected
uint32_t reserved: 30;
};
uint32_t val;
} wrtprt;
uint32_t gpio; ///< unused
uint32_t tcbcnt; ///< transferred (to card) byte count
uint32_t tbbcnt; ///< transferred from host to FIFO byte count
union {
struct {
uint32_t debounce_count: 24; ///< number of host cycles used by debounce filter, typical time should be 5-25ms
uint32_t reserved: 8;
};
} debnce;
uint32_t usrid; ///< user ID
uint32_t verid; ///< IP block version
uint32_t hcon; ///< compile-time IP configuration
uint32_t uhs; ///< TBD
union {
struct {
uint32_t cards: 2; ///< bit N resets card N, active low
uint32_t reserved: 30;
};
} rst_n;
uint32_t reserved_7c;
union {
struct {
uint32_t sw_reset: 1; ///< set to reset DMA controller
uint32_t fb: 1; ///< set if AHB master performs fixed burst transfers
uint32_t dsl: 5; ///< descriptor skip length: number of words to skip between two unchained descriptors
uint32_t enable: 1; ///< set to enable IDMAC
uint32_t pbl: 3; ///< programmable burst length
uint32_t reserved: 21;
};
uint32_t val;
} bmod;
uint32_t pldmnd; ///< set any bit to resume IDMAC FSM from suspended state
sdmmc_desc_t* dbaddr; ///< descriptor list base
union {
struct {
uint32_t ti: 1; ///< transmit interrupt status
uint32_t ri: 1; ///< receive interrupt status
uint32_t fbe: 1; ///< fatal bus error
uint32_t reserved1: 1;
uint32_t du: 1; ///< descriptor unavailable
uint32_t ces: 1; ///< card error summary
uint32_t reserved2: 2;
uint32_t nis: 1; ///< normal interrupt summary
uint32_t fbe_code: 3; ///< code of fatal bus error
uint32_t fsm: 4; ///< DMAC FSM state
uint32_t reserved3: 15;
};
uint32_t val;
} idsts;
union {
struct {
uint32_t ti: 1; ///< transmit interrupt enable
uint32_t ri: 1; ///< receive interrupt enable
uint32_t fbe: 1; ///< fatal bus error interrupt enable
uint32_t reserved1: 1;
uint32_t du: 1; ///< descriptor unavailable interrupt enable
uint32_t ces: 1; ///< card error interrupt enable
uint32_t reserved2: 2;
uint32_t ni: 1; ///< normal interrupt interrupt enable
uint32_t ai: 1; ///< abnormal interrupt enable
uint32_t reserved3: 22;
};
uint32_t val;
} idinten;
uint32_t dscaddr; ///< current host descriptor address
uint32_t dscaddrl; ///< unused
uint32_t dscaddru; ///< unused
uint32_t bufaddrl; ///< unused
uint32_t bufaddru; ///< unused
uint32_t reserved_a8[22];
uint32_t cardthrctl;
uint32_t back_end_power;
uint32_t uhs_reg_ext;
uint32_t emmc_ddr_reg;
uint32_t enable_shift;
uint32_t reserved_114[443];
union {
struct {
uint32_t phase_dout: 3; ///< phase of data output clock (0x0: 0, 0x1: 90, 0x4: 180, 0x6: 270)
uint32_t phase_din: 3; ///< phase of data input clock
uint32_t phase_core: 3; ///< phase of the clock to SDMMC peripheral
uint32_t div_factor_p: 4; ///< controls clock period; it will be (div_factor_p + 1) / 160MHz
uint32_t div_factor_h: 4; ///< controls length of high pulse; it will be (div_factor_h + 1) / 160MHz
uint32_t div_factor_m: 4; ///< should be equal to div_factor_p
};
uint32_t val;
} clock;
} sdmmc_dev_t;
extern sdmmc_dev_t SDMMC;
_Static_assert(sizeof(sdmmc_dev_t) == 0x804, "invalid size of sdmmc_dev_t structure");
#endif //_SOC_SDMMC_STRUCT_H_

View File

@ -19,3 +19,4 @@ PROVIDE ( SPI3 = 0x3ff65000 );
PROVIDE ( I2C1 = 0x3ff67000 );
PROVIDE ( I2S1 = 0x3ff6D000 );
PROVIDE ( UART2 = 0x3ff6E000 );
PROVIDE ( SDMMC = 0x3ff68000 );

View File

@ -0,0 +1,2 @@
COMPONENT_ADD_INCLUDEDIRS := src
COMPONENT_SRCDIRS := src/option src

View File

@ -0,0 +1,279 @@
----------------------------------------------------------------------------
Revision history of FatFs module
----------------------------------------------------------------------------
R0.00 (February 26, 2006)
Prototype.
R0.01 (April 29, 2006)
The first release.
R0.02 (June 01, 2006)
Added FAT12 support.
Removed unbuffered mode.
Fixed a problem on small (<32M) partition.
R0.02a (June 10, 2006)
Added a configuration option (_FS_MINIMUM).
R0.03 (September 22, 2006)
Added f_rename().
Changed option _FS_MINIMUM to _FS_MINIMIZE.
R0.03a (December 11, 2006)
Improved cluster scan algorithm to write files fast.
Fixed f_mkdir() creates incorrect directory on FAT32.
R0.04 (February 04, 2007)
Added f_mkfs().
Supported multiple drive system.
Changed some interfaces for multiple drive system.
Changed f_mountdrv() to f_mount().
R0.04a (April 01, 2007)
Supported multiple partitions on a physical drive.
Added a capability of extending file size to f_lseek().
Added minimization level 3.
Fixed an endian sensitive code in f_mkfs().
R0.04b (May 05, 2007)
Added a configuration option _USE_NTFLAG.
Added FSINFO support.
Fixed DBCS name can result FR_INVALID_NAME.
Fixed short seek (<= csize) collapses the file object.
R0.05 (August 25, 2007)
Changed arguments of f_read(), f_write() and f_mkfs().
Fixed f_mkfs() on FAT32 creates incorrect FSINFO.
Fixed f_mkdir() on FAT32 creates incorrect directory.
R0.05a (February 03, 2008)
Added f_truncate() and f_utime().
Fixed off by one error at FAT sub-type determination.
Fixed btr in f_read() can be mistruncated.
Fixed cached sector is not flushed when create and close without write.
R0.06 (April 01, 2008)
Added fputc(), fputs(), fprintf() and fgets().
Improved performance of f_lseek() on moving to the same or following cluster.
R0.07 (April 01, 2009)
Merged Tiny-FatFs as a configuration option. (_FS_TINY)
Added long file name feature. (_USE_LFN)
Added multiple code page feature. (_CODE_PAGE)
Added re-entrancy for multitask operation. (_FS_REENTRANT)
Added auto cluster size selection to f_mkfs().
Added rewind option to f_readdir().
Changed result code of critical errors.
Renamed string functions to avoid name collision.
R0.07a (April 14, 2009)
Septemberarated out OS dependent code on reentrant cfg.
Added multiple sector size feature.
R0.07c (June 21, 2009)
Fixed f_unlink() can return FR_OK on error.
Fixed wrong cache control in f_lseek().
Added relative path feature.
Added f_chdir() and f_chdrive().
Added proper case conversion to extended character.
R0.07e (November 03, 2009)
Septemberarated out configuration options from ff.h to ffconf.h.
Fixed f_unlink() fails to remove a sub-directory on _FS_RPATH.
Fixed name matching error on the 13 character boundary.
Added a configuration option, _LFN_UNICODE.
Changed f_readdir() to return the SFN with always upper case on non-LFN cfg.
R0.08 (May 15, 2010)
Added a memory configuration option. (_USE_LFN = 3)
Added file lock feature. (_FS_SHARE)
Added fast seek feature. (_USE_FASTSEEK)
Changed some types on the API, XCHAR->TCHAR.
Changed .fname in the FILINFO structure on Unicode cfg.
String functions support UTF-8 encoding files on Unicode cfg.
R0.08a (August 16, 2010)
Added f_getcwd(). (_FS_RPATH = 2)
Added sector erase feature. (_USE_ERASE)
Moved file lock semaphore table from fs object to the bss.
Fixed f_mkfs() creates wrong FAT32 volume.
R0.08b (January 15, 2011)
Fast seek feature is also applied to f_read() and f_write().
f_lseek() reports required table size on creating CLMP.
Extended format syntax of f_printf().
Ignores duplicated directory separators in given path name.
R0.09 (September 06, 2011)
f_mkfs() supports multiple partition to complete the multiple partition feature.
Added f_fdisk().
R0.09a (August 27, 2012)
Changed f_open() and f_opendir() reject null object pointer to avoid crash.
Changed option name _FS_SHARE to _FS_LOCK.
Fixed assertion failure due to OS/2 EA on FAT12/16 volume.
R0.09b (January 24, 2013)
Added f_setlabel() and f_getlabel().
R0.10 (October 02, 2013)
Added selection of character encoding on the file. (_STRF_ENCODE)
Added f_closedir().
Added forced full FAT scan for f_getfree(). (_FS_NOFSINFO)
Added forced mount feature with changes of f_mount().
Improved behavior of volume auto detection.
Improved write throughput of f_puts() and f_printf().
Changed argument of f_chdrive(), f_mkfs(), disk_read() and disk_write().
Fixed f_write() can be truncated when the file size is close to 4GB.
Fixed f_open(), f_mkdir() and f_setlabel() can return incorrect value on error.
R0.10a (January 15, 2014)
Added arbitrary strings as drive number in the path name. (_STR_VOLUME_ID)
Added a configuration option of minimum sector size. (_MIN_SS)
2nd argument of f_rename() can have a drive number and it will be ignored.
Fixed f_mount() with forced mount fails when drive number is >= 1. (appeared at R0.10)
Fixed f_close() invalidates the file object without volume lock.
Fixed f_closedir() returns but the volume lock is left acquired. (appeared at R0.10)
Fixed creation of an entry with LFN fails on too many SFN collisions. (appeared at R0.07)
R0.10b (May 19, 2014)
Fixed a hard error in the disk I/O layer can collapse the directory entry.
Fixed LFN entry is not deleted on delete/rename an object with lossy converted SFN. (appeared at R0.07)
R0.10c (November 09, 2014)
Added a configuration option for the platforms without RTC. (_FS_NORTC)
Changed option name _USE_ERASE to _USE_TRIM.
Fixed volume label created by Mac OS X cannot be retrieved with f_getlabel(). (appeared at R0.09b)
Fixed a potential problem of FAT access that can appear on disk error.
Fixed null pointer dereference on attempting to delete the root direcotry. (appeared at R0.08)
R0.11 (February 09, 2015)
Added f_findfirst(), f_findnext() and f_findclose(). (_USE_FIND)
Fixed f_unlink() does not remove cluster chain of the file. (appeared at R0.10c)
Fixed _FS_NORTC option does not work properly. (appeared at R0.10c)
R0.11a (September 05, 2015)
Fixed wrong media change can lead a deadlock at thread-safe configuration.
Added code page 771, 860, 861, 863, 864, 865 and 869. (_CODE_PAGE)
Removed some code pages actually not exist on the standard systems. (_CODE_PAGE)
Fixed errors in the case conversion teble of code page 437 and 850 (ff.c).
Fixed errors in the case conversion teble of Unicode (cc*.c).
R0.12 (April 12, 2016)
Added support for exFAT file system. (_FS_EXFAT)
Added f_expand(). (_USE_EXPAND)
Changed some members in FINFO structure and behavior of f_readdir().
Added an option _USE_CHMOD.
Removed an option _WORD_ACCESS.
Fixed errors in the case conversion table of Unicode (cc*.c).
R0.12a (July 10, 2016)
Added support for creating exFAT volume with some changes of f_mkfs().
Added a file open method FA_OPEN_APPEND. An f_lseek() following f_open() is no longer needed.
f_forward() is available regardless of _FS_TINY.
Fixed f_mkfs() creates wrong volume. (appeared at R0.12)
Fixed wrong memory read in create_name(). (appeared at R0.12)
Fixed compilation fails at some configurations, _USE_FASTSEEK and _USE_FORWARD.
R0.12b (September 04, 2016)
Improved f_rename() to be able to rename objects with the same name but case.
Fixed an error in the case conversion teble of code page 866. (ff.c)
Fixed writing data is truncated at the file offset 4GiB on the exFAT volume. (appeared at R0.12)
Fixed creating a file in the root directory of exFAT volume can fail. (appeared at R0.12)
Fixed f_mkfs() creating exFAT volume with too small cluster size can collapse unallocated memory. (appeared at R0.12)
Fixed wrong object name can be returned when read directory at Unicode cfg. (appeared at R0.12)
Fixed large file allocation/removing on the exFAT volume collapses allocation bitmap. (appeared at R0.12)
Fixed some internal errors in f_expand() and f_lseek(). (appeared at R0.12)

View File

@ -0,0 +1,21 @@
FatFs Module Source Files R0.12a
FILES
00readme.txt This file.
history.txt Revision history.
ffconf.h Configuration file for FatFs module.
ff.h Common include file for FatFs and application module.
ff.c FatFs module.
diskio.h Common include file for FatFs and disk I/O module.
diskio.c An example of glue function to attach existing disk I/O module to FatFs.
integer.h Integer type definitions for FatFs.
option Optional external functions.
Low level disk I/O module is not included in this archive because the FatFs
module is only a generic file system layer and not depend on any specific
storage device. You have to provide a low level disk I/O module that written
to control the target storage device.

View File

@ -0,0 +1,134 @@
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2016 */
/* ESP-IDF port Copyright 2016 Espressif Systems (Shanghai) PTE LTD */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
#include <string.h>
#include "diskio.h" /* FatFs lower layer API */
#include "ffconf.h"
#include "ff.h"
#include "sdmmc_cmd.h"
#include "esp_log.h"
#include <time.h>
#include <sys/time.h>
static const char* TAG = "ff_diskio";
static ff_diskio_impl_t s_impls[_VOLUMES] = { { 0 } };
static sdmmc_card_t* s_cards[_VOLUMES] = { NULL };
PARTITION VolToPart[] = {
{0, 1}, /* Logical drive 0 ==> Physical drive 0, 1st partition */
{1, 0} /* Logical drive 1 ==> Physical drive 1, auto detection */
};
void ff_diskio_register(BYTE pdrv, const ff_diskio_impl_t* discio_impl)
{
assert(pdrv < _VOLUMES);
memcpy(&s_impls[pdrv], discio_impl, sizeof(ff_diskio_impl_t));
}
DSTATUS ff_disk_initialize (BYTE pdrv)
{
return s_impls[pdrv].init(pdrv);
}
DSTATUS ff_disk_status (BYTE pdrv)
{
return s_impls[pdrv].status(pdrv);
}
DRESULT ff_disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count)
{
return s_impls[pdrv].read(pdrv, buff, sector, count);
}
DRESULT ff_disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count)
{
return s_impls[pdrv].write(pdrv, buff, sector, count);
}
DRESULT ff_disk_ioctl (BYTE pdrv, BYTE cmd, void* buff)
{
return s_impls[pdrv].ioctl(pdrv, cmd, buff);
}
DWORD get_fattime(void)
{
time_t t = time(NULL);
struct tm *tmr = gmtime(&t);
int year = tmr->tm_year < 80 ? 0 : tmr->tm_year - 80;
return ((DWORD)(year) << 25)
| ((DWORD)(tmr->tm_mon + 1) << 21)
| ((DWORD)tmr->tm_mday << 16)
| (WORD)(tmr->tm_hour << 11)
| (WORD)(tmr->tm_min << 5)
| (WORD)(tmr->tm_sec >> 1);
}
DSTATUS ff_sdmmc_initialize (BYTE pdrv)
{
return 0;
}
DSTATUS ff_sdmmc_status (BYTE pdrv)
{
return 0;
}
DRESULT ff_sdmmc_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count)
{
sdmmc_card_t* card = s_cards[pdrv];
assert(card);
esp_err_t err = sdmmc_read_sectors(card, buff, sector, count);
if (err != ESP_OK) {
ESP_LOGE(TAG, "sdmmc_read_blocks failed (%d)", err);
return RES_ERROR;
}
return RES_OK;
}
DRESULT ff_sdmmc_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count)
{
sdmmc_card_t* card = s_cards[pdrv];
assert(card);
esp_err_t err = sdmmc_write_sectors(card, buff, sector, count);
if (err != ESP_OK) {
ESP_LOGE(TAG, "sdmmc_write_blocks failed (%d)", err);
return RES_ERROR;
}
return RES_OK;
}
DRESULT ff_sdmmc_ioctl (BYTE pdrv, BYTE cmd, void* buff)
{
sdmmc_card_t* card = s_cards[pdrv];
assert(card);
switch(cmd) {
case CTRL_SYNC:
return RES_OK;
case GET_SECTOR_COUNT:
*((uint32_t*) buff) = card->csd.capacity;
return RES_OK;
case GET_SECTOR_SIZE:
*((uint32_t*) buff) = card->csd.sector_size;
return RES_OK;
case GET_BLOCK_SIZE:
return RES_ERROR;
}
return RES_ERROR;
}
void ff_diskio_register_sdmmc(BYTE pdrv, sdmmc_card_t* card)
{
static const ff_diskio_impl_t sdmmc_impl = {
.init = &ff_sdmmc_initialize,
.status = &ff_sdmmc_status,
.read = &ff_sdmmc_read,
.write = &ff_sdmmc_write,
.ioctl = &ff_sdmmc_ioctl
};
s_cards[pdrv] = card;
ff_diskio_register(pdrv, &sdmmc_impl);
}

View File

@ -0,0 +1,120 @@
/*-----------------------------------------------------------------------/
/ Low level disk interface modlue include file (C)ChaN, 2014 /
/-----------------------------------------------------------------------*/
#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED
#ifdef __cplusplus
extern "C" {
#endif
#include "integer.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
/* Status of Disk Functions */
typedef BYTE DSTATUS;
/* Results of Disk Functions */
typedef enum {
RES_OK = 0, /* 0: Successful */
RES_ERROR, /* 1: R/W Error */
RES_WRPRT, /* 2: Write Protected */
RES_NOTRDY, /* 3: Not Ready */
RES_PARERR /* 4: Invalid Parameter */
} DRESULT;
/*---------------------------------------*/
/* Prototypes for disk control functions */
/* Redefine names of disk IO functions to prevent name collisions */
#define disk_initialize ff_disk_initialize
#define disk_status ff_disk_status
#define disk_read ff_disk_read
#define disk_write ff_disk_write
#define disk_ioctl ff_disk_ioctl
DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
/**
* Structure of pointers to disk IO driver functions.
*
* See FatFs documentation for details about these functions
*/
typedef struct {
DSTATUS (*init) (BYTE pdrv); /*!< disk initialization function */
DSTATUS (*status) (BYTE pdrv); /*!< disk status check function */
DRESULT (*read) (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); /*!< sector read function */
DRESULT (*write) (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count); /*!< sector write function */
DRESULT (*ioctl) (BYTE pdrv, BYTE cmd, void* buff); /*!< function to get info about disk and do some misc operations */
} ff_diskio_impl_t;
/**
* Register diskio driver for given drive number.
*
* When FATFS library calls one of disk_xxx functions for driver number pdrv,
* corresponding function in discio_impl for given pdrv will be called.
*
* @param pdrv drive number
* @param discio_impl pointer to ff_diskio_impl_t structure with diskio functions
*/
void ff_diskio_register(BYTE pdrv, const ff_diskio_impl_t* discio_impl);
/**
* Register SD/MMC diskio driver
*
* @param pdrv drive number
* @param card pointer to sdmmc_card_t structure describing a card; card should be initialized before calling f_mount.
*/
void ff_diskio_register_sdmmc(BYTE pdrv, sdmmc_card_t* card);
/* Disk Status Bits (DSTATUS) */
#define STA_NOINIT 0x01 /* Drive not initialized */
#define STA_NODISK 0x02 /* No medium in the drive */
#define STA_PROTECT 0x04 /* Write protected */
/* Command code for disk_ioctrl fucntion */
/* Generic command (Used by FatFs) */
#define CTRL_SYNC 0 /* Complete pending write process (needed at _FS_READONLY == 0) */
#define GET_SECTOR_COUNT 1 /* Get media size (needed at _USE_MKFS == 1) */
#define GET_SECTOR_SIZE 2 /* Get sector size (needed at _MAX_SS != _MIN_SS) */
#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at _USE_MKFS == 1) */
#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at _USE_TRIM == 1) */
/* Generic command (Not used by FatFs) */
#define CTRL_POWER 5 /* Get/Set power status */
#define CTRL_LOCK 6 /* Lock/Unlock media removal */
#define CTRL_EJECT 7 /* Eject media */
#define CTRL_FORMAT 8 /* Create physical format on the media */
/* MMC/SDC specific ioctl command */
#define MMC_GET_TYPE 10 /* Get card type */
#define MMC_GET_CSD 11 /* Get CSD */
#define MMC_GET_CID 12 /* Get CID */
#define MMC_GET_OCR 13 /* Get OCR */
#define MMC_GET_SDSTAT 14 /* Get SD status */
#define ISDIO_READ 55 /* Read data form SD iSDIO register */
#define ISDIO_WRITE 56 /* Write data to SD iSDIO register */
#define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */
/* ATA/CF specific ioctl command */
#define ATA_GET_REV 20 /* Get F/W revision */
#define ATA_GET_MODEL 21 /* Get model name */
#define ATA_GET_SN 22 /* Get serial number */
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,107 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stddef.h>
#include "esp_err.h"
#include "driver/gpio.h"
#include "driver/sdmmc_types.h"
#include "driver/sdmmc_host.h"
#include "ff.h"
/**
* @brief Register FATFS with VFS component
*
* This function registers given FAT drive in VFS, at the specified base path.
* If only one drive is used, fat_drive argument can be an empty string.
* Refer to FATFS library documentation on how to specify FAT drive.
* This function also allocates FATFS structure which should be used for f_mount
* call.
*
* @note This function doesn't mount the drive into FATFS, it just connects
* POSIX and C standard library IO function with FATFS. You need to mount
* desired drive into FATFS separately.
*
* @param base_path path prefix where FATFS should be registered
* @param fat_drive FATFS drive specification; if only one drive is used, can be an empty string
* @param max_files maximum number of files which can be open at the same time
* @param[out] out_fs pointer to FATFS structure which can be used for FATFS f_mount call is returned via this argument.
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_register was already called
* - ESP_ERR_NO_MEM if not enough memory or too many VFSes already registered
*/
esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive,
size_t max_files, FATFS** out_fs);
/**
* @brief Un-register FATFS from VFS
*
* @note FATFS structure returned by esp_vfs_fat_register is destroyed after
* this call. Make sure to call f_mount function to unmount it before
* calling esp_vfs_fat_unregister.
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if FATFS is not registered in VFS
*/
esp_err_t esp_vfs_fat_unregister();
/**
* @brief Configuration arguments for esp_vfs_fat_sdmmc_mount function
*/
typedef struct {
bool format_if_mount_failed; ///< If FAT partition can not be mounted, and this parameter is true, create partition table and format the filesystem
int max_files; ///< Max number of open files
} esp_vfs_fat_sdmmc_mount_config_t;
/**
* @brief Convenience function to get FAT filesystem on SD card registered in VFS
*
* This is an all-in-one function which does the following:
* - initializes SD/MMC peripheral with configuration in host_config
* - initializes SD/MMC card with configuration in slot_config
* - mounts FAT partition on SD/MMC card using FATFS library, with configuration in mount_config
* - registers FATFS library with VFS, with prefix given by base_prefix variable
*
* This function is intended to make example code more compact.
* For real world applications, developers should implement the logic of
* probing SD card, locating and mounting partition, and registering FATFS in VFS,
* with proper error checking and handling of exceptional conditions.
*
* @param base_path path where partition should be registered (e.g. "/sdcard")
* @param host_config pointer to structure describing SDMMC host
* @param slot_config pointer to structure with extra SDMMC slot configuration
* @param mount_config pointer to structure with extra parameters for mounting FATFS
* @param[out] out_card if not NULL, pointer to the card information structure will be returned via this argument
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount was already called
* - ESP_ERR_NO_MEM if memory can not be allocated
* - ESP_FAIL if partition can not be mounted
* - other error codes from SDMMC host, SDMMC protocol, or FATFS drivers
*/
esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
const sdmmc_host_t* host_config,
const sdmmc_slot_config_t* slot_config,
const esp_vfs_fat_sdmmc_mount_config_t* mount_config,
sdmmc_card_t** out_card);
/**
* @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_sdmmc_mount
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount hasn't been called
*/
esp_err_t esp_vfs_fat_sdmmc_unmount();

6042
components/fatfs/src/ff.c Normal file

File diff suppressed because it is too large Load Diff

369
components/fatfs/src/ff.h Normal file
View File

@ -0,0 +1,369 @@
/*----------------------------------------------------------------------------/
/ FatFs - Generic FAT file system module R0.12b /
/-----------------------------------------------------------------------------/
/
/ Copyright (C) 2016, ChaN, all right reserved.
/
/ FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided
/ that the following condition is met:
/ 1. Redistributions of source code must retain the above copyright notice,
/ this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/----------------------------------------------------------------------------*/
#ifndef _FATFS
#define _FATFS 68020 /* Revision ID */
#ifdef __cplusplus
extern "C" {
#endif
#include "integer.h" /* Basic integer types */
#include "ffconf.h" /* FatFs configuration options */
#if _FATFS != _FFCONF
#error Wrong configuration file (ffconf.h).
#endif
#ifdef FF_DEFINE_DIR
#define FF_DIR DIR
#endif
/* Definitions of volume management */
#if _MULTI_PARTITION /* Multiple partition configuration */
typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition resolution table */
#define LD2PD(vol) (VolToPart[vol].pd) /* Get physical drive number */
#define LD2PT(vol) (VolToPart[vol].pt) /* Get partition index */
#else /* Single partition configuration */
#define LD2PD(vol) (BYTE)(vol) /* Each logical drive is bound to the same physical drive number */
#define LD2PT(vol) 0 /* Find first valid partition or in SFD */
#endif
/* Type of path name strings on FatFs API */
#if _LFN_UNICODE /* Unicode (UTF-16) string */
#if _USE_LFN == 0
#error _LFN_UNICODE must be 0 at non-LFN cfg.
#endif
#ifndef _INC_TCHAR
typedef WCHAR TCHAR;
#define _T(x) L ## x
#define _TEXT(x) L ## x
#endif
#else /* ANSI/OEM string */
#ifndef _INC_TCHAR
typedef char TCHAR;
#define _T(x) x
#define _TEXT(x) x
#endif
#endif
/* Type of file size variables */
#if _FS_EXFAT
#if _USE_LFN == 0
#error LFN must be enabled when enable exFAT
#endif
typedef QWORD FSIZE_t;
#else
typedef DWORD FSIZE_t;
#endif
/* File system object structure (FATFS) */
typedef struct {
BYTE fs_type; /* File system type (0:N/A) */
BYTE drv; /* Physical drive number */
BYTE n_fats; /* Number of FATs (1 or 2) */
BYTE wflag; /* win[] flag (b0:dirty) */
BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */
WORD id; /* File system mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
WORD csize; /* Cluster size [sectors] */
#if _MAX_SS != _MIN_SS
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
#endif
#if _USE_LFN != 0
WCHAR* lfnbuf; /* LFN working buffer */
#endif
#if _FS_EXFAT
BYTE* dirbuf; /* Directory entry block scratchpad buffer */
#endif
#if _FS_REENTRANT
_SYNC_t sobj; /* Identifier of sync object */
#endif
#if !_FS_READONLY
DWORD last_clst; /* Last allocated cluster */
DWORD free_clst; /* Number of free clusters */
#endif
#if _FS_RPATH != 0
DWORD cdir; /* Current directory start cluster (0:root) */
#if _FS_EXFAT
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
#endif
#endif
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
DWORD fsize; /* Size of an FAT [sectors] */
DWORD volbase; /* Volume base sector */
DWORD fatbase; /* FAT base sector */
DWORD dirbase; /* Root directory base sector/cluster */
DWORD database; /* Data base sector */
DWORD winsect; /* Current sector appearing in the win[] */
BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS;
/* Object ID and allocation information (_FDID) */
typedef struct {
FATFS* fs; /* Pointer to the owner file system object */
WORD id; /* Owner file system mount ID */
BYTE attr; /* Object attribute */
BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous (no data on FAT), =3:got flagmented, b2:sub-directory stretched) */
DWORD sclust; /* Object start cluster (0:no cluster or root directory) */
FSIZE_t objsize; /* Object size (valid when sclust != 0) */
#if _FS_EXFAT
DWORD n_cont; /* Size of coutiguous part, clusters - 1 (valid when stat == 3) */
DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
DWORD c_ofs; /* Offset in the containing directory (valid when sclust != 0) */
#endif
#if _FS_LOCK != 0
UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
#endif
} _FDID;
/* File object structure (FIL) */
typedef struct {
_FDID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
BYTE flag; /* File status flags */
BYTE err; /* Abort flag (error code) */
FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
DWORD clust; /* Current cluster of fpter (invalid when fprt is 0) */
DWORD sect; /* Sector number appearing in buf[] (0:invalid) */
#if !_FS_READONLY
DWORD dir_sect; /* Sector number containing the directory entry */
BYTE* dir_ptr; /* Pointer to the directory entry in the win[] */
#endif
#if _USE_FASTSEEK
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !_FS_TINY
BYTE buf[_MAX_SS]; /* File private data read/write window */
#endif
} FIL;
/* Directory object structure (FF_DIR) */
typedef struct {
_FDID obj; /* Object identifier */
DWORD dptr; /* Current read/write offset */
DWORD clust; /* Current cluster */
DWORD sect; /* Current sector */
BYTE* dir; /* Pointer to the directory item in the win[] */
BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
#if _USE_LFN != 0
DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
#endif
#if _USE_FIND
const TCHAR* pat; /* Pointer to the name matching pattern */
#endif
} FF_DIR;
/* File information structure (FILINFO) */
typedef struct {
FSIZE_t fsize; /* File size */
WORD fdate; /* Modified date */
WORD ftime; /* Modified time */
BYTE fattrib; /* File attribute */
#if _USE_LFN != 0
TCHAR altname[13]; /* Altenative file name */
TCHAR fname[_MAX_LFN + 1]; /* Primary file name */
#else
TCHAR fname[13]; /* File name */
#endif
} FILINFO;
/* File function return code (FRESULT) */
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;
/*--------------------------------------------------------------*/
/* FatFs module application interface */
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
FRESULT f_close (FIL* fp); /* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
FRESULT f_truncate (FIL* fp); /* Truncate the file */
FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */
FRESULT f_opendir (FF_DIR* dp, const TCHAR* path); /* Open a directory */
FRESULT f_closedir (FF_DIR* dp); /* Close an open directory */
FRESULT f_readdir (FF_DIR* dp, FILINFO* fno); /* Read a directory item */
FRESULT f_findfirst (FF_DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
FRESULT f_findnext (FF_DIR* dp, FILINFO* fno); /* Find next file */
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */
FRESULT f_chdir (const TCHAR* path); /* Change current directory */
FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
FRESULT f_expand (FIL* fp, FSIZE_t szf, BYTE opt); /* Allocate a contiguous block to the file */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
FRESULT f_mkfs (const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len); /* Create a FAT volume */
FRESULT f_fdisk (BYTE pdrv, const DWORD* szt, void* work); /* Divide a physical drive into some partitions */
int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
#define f_error(fp) ((fp)->err)
#define f_tell(fp) ((fp)->fptr)
#define f_size(fp) ((fp)->obj.objsize)
#define f_rewind(fp) f_lseek((fp), 0)
#define f_rewinddir(dp) f_readdir((dp), 0)
#ifndef EOF
#define EOF (-1)
#endif
/*--------------------------------------------------------------*/
/* Additional user defined functions */
/* RTC function */
#if !_FS_READONLY && !_FS_NORTC
DWORD get_fattime (void);
#endif
/* Unicode support functions */
#if _USE_LFN != 0 /* Unicode - OEM code conversion */
WCHAR ff_convert (WCHAR chr, UINT dir); /* OEM-Unicode bidirectional conversion */
WCHAR ff_wtoupper (WCHAR chr); /* Unicode upper-case conversion */
#if _USE_LFN == 3 /* Memory functions */
void* ff_memalloc (UINT msize); /* Allocate memory block */
void ff_memfree (void* mblock); /* Free memory block */
#endif
#endif
/* Sync functions */
#if _FS_REENTRANT
int ff_cre_syncobj (BYTE vol, _SYNC_t* sobj); /* Create a sync object */
int ff_req_grant (_SYNC_t sobj); /* Lock sync object */
void ff_rel_grant (_SYNC_t sobj); /* Unlock sync object */
int ff_del_syncobj (_SYNC_t sobj); /* Delete a sync object */
#endif
/*--------------------------------------------------------------*/
/* Flags and offset address */
/* File access mode and open method flags (3rd argument of f_open) */
#define FA_READ 0x01
#define FA_WRITE 0x02
#define FA_OPEN_EXISTING 0x00
#define FA_CREATE_NEW 0x04
#define FA_CREATE_ALWAYS 0x08
#define FA_OPEN_ALWAYS 0x10
#define FA_OPEN_APPEND 0x30
/* Fast seek controls (2nd argument of f_lseek) */
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
/* Format options (2nd argument of f_mkfs) */
#define FM_FAT 0x01
#define FM_FAT32 0x02
#define FM_EXFAT 0x04
#define FM_ANY 0x07
#define FM_SFD 0x08
/* Filesystem type (FATFS.fs_type) */
#define FS_FAT12 1
#define FS_FAT16 2
#define FS_FAT32 3
#define FS_EXFAT 4
/* File attribute bits for directory entry (FILINFO.fattrib) */
#define AM_RDO 0x01 /* Read only */
#define AM_HID 0x02 /* Hidden */
#define AM_SYS 0x04 /* System */
#define AM_DIR 0x10 /* Directory */
#define AM_ARC 0x20 /* Archive */
#ifdef __cplusplus
}
#endif
#endif /* _FATFS */

View File

@ -0,0 +1,267 @@
/*---------------------------------------------------------------------------/
/ FatFs - FAT file system module configuration file
/---------------------------------------------------------------------------*/
#define _FFCONF 68020 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/
#define _FS_READONLY 0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */
#define _FS_MINIMIZE 0
/* This option defines minimization level to remove some basic API functions.
/
/ 0: All basic functions are enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */
#define _USE_STRFUNC 0
/* This option switches string functions, f_gets(), f_putc(), f_puts() and
/ f_printf().
/
/ 0: Disable string functions.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion. */
#define _USE_FIND 0
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
#define _USE_MKFS 1
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define _USE_FASTSEEK 0
/* This option switches fast seek function. (0:Disable or 1:Enable) */
#define _USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define _USE_CHMOD 0
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also _FS_READONLY needs to be 0 to enable this option. */
#define _USE_LABEL 0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define _USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/
#define _CODE_PAGE 1
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect setting of the code page can cause a file open failure.
/
/ 1 - ASCII (No extended character. Non-LFN cfg. only)
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
*/
#define _USE_LFN 0
#define _MAX_LFN 255
/* The _USE_LFN switches the support of long file name (LFN).
/
/ 0: Disable support of LFN. _MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, Unicode handling functions (option/unicode.c) must be added
/ to the project. The working buffer occupies (_MAX_LFN + 1) * 2 bytes and
/ additional 608 bytes at exFAT enabled. _MAX_LFN can be in range from 12 to 255.
/ It should be set 255 to support full featured LFN operations.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree(), must be added to the project. */
#define _LFN_UNICODE 0
/* This option switches character encoding on the API. (0:ANSI/OEM or 1:UTF-16)
/ To use Unicode string for the path name, enable LFN and set _LFN_UNICODE = 1.
/ This option also affects behavior of string I/O functions. */
#define _STRF_ENCODE 3
/* When _LFN_UNICODE == 1, this option selects the character encoding ON THE FILE to
/ be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf().
/
/ 0: ANSI/OEM
/ 1: UTF-16LE
/ 2: UTF-16BE
/ 3: UTF-8
/
/ This option has no effect when _LFN_UNICODE == 0. */
#define _FS_RPATH 0
/* This option configures support of relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*/
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/
#define _VOLUMES 2
/* Number of volumes (logical drives) to be used. */
#define _STR_VOLUME_ID 0
#define _VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
/* _STR_VOLUME_ID switches string support of volume ID.
/ When _STR_VOLUME_ID is set to 1, also pre-defined strings can be used as drive
/ number in the path name. _VOLUME_STRS defines the drive ID strings for each
/ logical drives. Number of items must be equal to _VOLUMES. Valid characters for
/ the drive ID strings are: A-Z and 0-9. */
#define _MULTI_PARTITION 1
/* This option switches support of multi-partition on a physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When multi-partition is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ funciton will be available. */
#define _MIN_SS 512
#define _MAX_SS 512
/* These options configure the range of sector size to be supported. (512, 1024,
/ 2048 or 4096) Always set both 512 for most systems, all type of memory cards and
/ harddisk. But a larger value may be required for on-board flash memory and some
/ type of optical media. When _MAX_SS is larger than _MIN_SS, FatFs is configured
/ to variable sector size and GET_SECTOR_SIZE command must be implemented to the
/ disk_ioctl() function. */
#define _USE_TRIM 0
/* This option switches support of ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
#define _FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/
/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/
#define _FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is reduced _MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the file system object (FATFS) is used for the file data transfer. */
#define _FS_EXFAT 0
/* This option switches support of exFAT file system. (0:Disable or 1:Enable)
/ When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1)
/ Note that enabling exFAT discards C89 compatibility. */
#define _FS_NORTC 0
#define _NORTC_MON 1
#define _NORTC_MDAY 1
#define _NORTC_YEAR 2016
/* The option _FS_NORTC switches timestamp functiton. If the system does not have
/ any RTC function or valid timestamp is not needed, set _FS_NORTC = 1 to disable
/ the timestamp function. All objects modified by FatFs will have a fixed timestamp
/ defined by _NORTC_MON, _NORTC_MDAY and _NORTC_YEAR in local time.
/ To enable timestamp function (_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to get current time form real-time clock. _NORTC_MON,
/ _NORTC_MDAY and _NORTC_YEAR have no effect.
/ These options have no effect at read-only configuration (_FS_READONLY = 1). */
#define _FS_LOCK 0
/* The option _FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when _FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. */
#define _FS_REENTRANT 1
#define _FS_TIMEOUT 1000
#define _SYNC_t SemaphoreHandle_t
/* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this function.
/
/ 0: Disable re-entrancy. _FS_TIMEOUT and _SYNC_t have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
/ function, must be added to the project. Samples are available in
/ option/syscall.c.
/
/ The _FS_TIMEOUT defines timeout period in unit of time tick.
/ The _SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
/ SemaphoreHandle_t and etc.. A header file for O/S definitions needs to be
/ included somewhere in the scope of ff.h. */
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
/*--- End of configuration options ---*/

View File

@ -0,0 +1,38 @@
/*-------------------------------------------*/
/* Integer type definitions for FatFs module */
/*-------------------------------------------*/
#ifndef _FF_INTEGER
#define _FF_INTEGER
#ifdef _WIN32 /* FatFs development platform */
#include <windows.h>
#include <tchar.h>
typedef unsigned __int64 QWORD;
#else /* Embedded platform */
/* These types MUST be 16-bit or 32-bit */
typedef int INT;
typedef unsigned int UINT;
/* This type MUST be 8-bit */
typedef unsigned char BYTE;
/* These types MUST be 16-bit */
typedef short SHORT;
typedef unsigned short WORD;
typedef unsigned short WCHAR;
/* These types MUST be 32-bit */
typedef long LONG;
typedef unsigned long DWORD;
/* This type MUST be 64-bit (Remove this for C89 compatibility) */
typedef unsigned long long QWORD;
#endif
#endif

View File

@ -0,0 +1,108 @@
/*------------------------------------------------------------------------*/
/* Sample code of OS dependent controls for FatFs */
/* (C)ChaN, 2014 */
/*------------------------------------------------------------------------*/
#include "../ff.h"
#if _FS_REENTRANT
/*------------------------------------------------------------------------*/
/* Create a Synchronization Object */
/*------------------------------------------------------------------------*/
/* This function is called in f_mount() function to create a new
/ synchronization object, such as semaphore and mutex. When a 0 is returned,
/ the f_mount() function fails with FR_INT_ERR.
*/
int ff_cre_syncobj ( /* 1:Function succeeded, 0:Could not create the sync object */
BYTE vol, /* Corresponding volume (logical drive number) */
_SYNC_t *sobj /* Pointer to return the created sync object */
)
{
*sobj = xSemaphoreCreateMutex();
return (*sobj != NULL) ? 1 : 0;
}
/*------------------------------------------------------------------------*/
/* Delete a Synchronization Object */
/*------------------------------------------------------------------------*/
/* This function is called in f_mount() function to delete a synchronization
/ object that created with ff_cre_syncobj() function. When a 0 is returned,
/ the f_mount() function fails with FR_INT_ERR.
*/
int ff_del_syncobj ( /* 1:Function succeeded, 0:Could not delete due to any error */
_SYNC_t sobj /* Sync object tied to the logical drive to be deleted */
)
{
vSemaphoreDelete(sobj);
return 1;
}
/*------------------------------------------------------------------------*/
/* Request Grant to Access the Volume */
/*------------------------------------------------------------------------*/
/* This function is called on entering file functions to lock the volume.
/ When a 0 is returned, the file function fails with FR_TIMEOUT.
*/
int ff_req_grant ( /* 1:Got a grant to access the volume, 0:Could not get a grant */
_SYNC_t sobj /* Sync object to wait */
)
{
return (xSemaphoreTake(sobj, _FS_TIMEOUT) == pdTRUE) ? 1 : 0;
}
/*------------------------------------------------------------------------*/
/* Release Grant to Access the Volume */
/*------------------------------------------------------------------------*/
/* This function is called on leaving file functions to unlock the volume.
*/
void ff_rel_grant (
_SYNC_t sobj /* Sync object to be signaled */
)
{
xSemaphoreGive(sobj);
}
#endif
#if _USE_LFN == 3 /* LFN with a working buffer on the heap */
/*------------------------------------------------------------------------*/
/* Allocate a memory block */
/*------------------------------------------------------------------------*/
/* If a NULL is returned, the file function fails with FR_NOT_ENOUGH_CORE.
*/
void* ff_memalloc ( /* Returns pointer to the allocated memory block */
UINT msize /* Number of bytes to allocate */
)
{
return malloc(msize); /* Allocate a new memory block with POSIX API */
}
/*------------------------------------------------------------------------*/
/* Free a memory block */
/*------------------------------------------------------------------------*/
void ff_memfree (
void* mblock /* Pointer to the memory block to free */
)
{
free(mblock); /* Discard the memory block with POSIX API */
}
#endif

View File

@ -0,0 +1,17 @@
#include "../ff.h"
#if _USE_LFN != 0
#if _CODE_PAGE == 932 /* Japanese Shift_JIS */
#include "cc932.c"
#elif _CODE_PAGE == 936 /* Simplified Chinese GBK */
#include "cc936.c"
#elif _CODE_PAGE == 949 /* Korean */
#include "cc949.c"
#elif _CODE_PAGE == 950 /* Traditional Chinese Big5 */
#include "cc950.c"
#else /* Single Byte Character-Set */
#include "ccsbcs.c"
#endif
#endif

View File

@ -0,0 +1,538 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/lock.h>
#include "esp_vfs.h"
#include "esp_log.h"
#include "ff.h"
#include "diskio.h"
typedef struct {
char fat_drive[8];
size_t max_files;
FATFS fs;
FIL files[0];
_lock_t lock;
} vfs_fat_ctx_t;
typedef struct {
DIR dir;
long offset;
FF_DIR ffdir;
FILINFO filinfo;
struct dirent cur_dirent;
} vfs_fat_dir_t;
static const char* TAG = "vfs_fat";
static size_t vfs_fat_write(void* p, int fd, const void * data, size_t size);
static off_t vfs_fat_lseek(void* p, int fd, off_t size, int mode);
static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size);
static int vfs_fat_open(void* ctx, const char * path, int flags, int mode);
static int vfs_fat_close(void* ctx, int fd);
static int vfs_fat_fstat(void* ctx, int fd, struct stat * st);
static int vfs_fat_stat(void* ctx, const char * path, struct stat * st);
static int vfs_fat_link(void* ctx, const char* n1, const char* n2);
static int vfs_fat_unlink(void* ctx, const char *path);
static int vfs_fat_rename(void* ctx, const char *src, const char *dst);
static DIR* vfs_fat_opendir(void* ctx, const char* name);
static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir);
static int vfs_fat_readdir_r(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent);
static long vfs_fat_telldir(void* ctx, DIR* pdir);
static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset);
static int vfs_fat_closedir(void* ctx, DIR* pdir);
static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode);
static int vfs_fat_rmdir(void* ctx, const char* name);
static char s_base_path[ESP_VFS_PATH_MAX];
static vfs_fat_ctx_t* s_fat_ctx = NULL;
esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, size_t max_files, FATFS** out_fs)
{
if (s_fat_ctx) {
return ESP_ERR_INVALID_STATE;
}
const esp_vfs_t vfs = {
.flags = ESP_VFS_FLAG_CONTEXT_PTR,
.write_p = &vfs_fat_write,
.lseek_p = &vfs_fat_lseek,
.read_p = &vfs_fat_read,
.open_p = &vfs_fat_open,
.close_p = &vfs_fat_close,
.fstat_p = &vfs_fat_fstat,
.stat_p = &vfs_fat_stat,
.link_p = &vfs_fat_link,
.unlink_p = &vfs_fat_unlink,
.rename_p = &vfs_fat_rename,
.opendir_p = &vfs_fat_opendir,
.closedir_p = &vfs_fat_closedir,
.readdir_p = &vfs_fat_readdir,
.readdir_r_p = &vfs_fat_readdir_r,
.seekdir_p = &vfs_fat_seekdir,
.telldir_p = &vfs_fat_telldir,
.mkdir_p = &vfs_fat_mkdir,
.rmdir_p = &vfs_fat_rmdir
};
size_t ctx_size = sizeof(vfs_fat_ctx_t) + max_files * sizeof(FIL);
s_fat_ctx = (vfs_fat_ctx_t*) calloc(1, ctx_size);
if (s_fat_ctx == NULL) {
return ESP_ERR_NO_MEM;
}
s_fat_ctx->max_files = max_files;
strncpy(s_fat_ctx->fat_drive, fat_drive, sizeof(s_fat_ctx->fat_drive) - 1);
*out_fs = &s_fat_ctx->fs;
esp_err_t err = esp_vfs_register(base_path, &vfs, s_fat_ctx);
if (err != ESP_OK) {
free(s_fat_ctx);
s_fat_ctx = NULL;
return err;
}
_lock_init(&s_fat_ctx->lock);
strncpy(s_base_path, base_path, sizeof(s_base_path) - 1);
s_base_path[sizeof(s_base_path) - 1] = 0;
return ESP_OK;
}
esp_err_t esp_vfs_fat_unregister()
{
if (s_fat_ctx == NULL) {
return ESP_ERR_INVALID_STATE;
}
esp_err_t err = esp_vfs_unregister(s_base_path);
if (err != ESP_OK) {
return err;
}
_lock_close(&s_fat_ctx->lock);
free(s_fat_ctx);
s_fat_ctx = NULL;
return ESP_OK;
}
static int get_next_fd(vfs_fat_ctx_t* fat_ctx)
{
for (size_t i = 0; i < fat_ctx->max_files; ++i) {
if (fat_ctx->files[i].obj.fs == NULL) {
return (int) i;
}
}
return -1;
}
static int fat_mode_conv(int m)
{
int res = 0;
int acc_mode = m & O_ACCMODE;
if (acc_mode == O_RDONLY) {
res |= FA_READ;
} else if (acc_mode == O_WRONLY) {
res |= FA_WRITE;
} else if (acc_mode == O_RDWR) {
res |= FA_READ | FA_WRITE;
}
if ((m & O_CREAT) && (m & O_EXCL)) {
res |= FA_CREATE_NEW;
} else if (m & O_CREAT) {
res |= FA_CREATE_ALWAYS;
} else if (m & O_APPEND) {
res |= FA_OPEN_ALWAYS;
} else {
res |= FA_OPEN_EXISTING;
}
return res;
}
static int fresult_to_errno(FRESULT fr)
{
switch(fr) {
case FR_DISK_ERR: return EIO;
case FR_INT_ERR:
assert(0 && "fatfs internal error");
return EIO;
case FR_NOT_READY: return ENODEV;
case FR_NO_FILE: return ENOENT;
case FR_NO_PATH: return ENOENT;
case FR_INVALID_NAME: return EINVAL;
case FR_DENIED: return EACCES;
case FR_EXIST: return EEXIST;
case FR_INVALID_OBJECT: return EBADF;
case FR_WRITE_PROTECTED: return EACCES;
case FR_INVALID_DRIVE: return ENXIO;
case FR_NOT_ENABLED: return ENODEV;
case FR_NO_FILESYSTEM: return ENODEV;
case FR_MKFS_ABORTED: return EINTR;
case FR_TIMEOUT: return ETIMEDOUT;
case FR_LOCKED: return EACCES;
case FR_NOT_ENOUGH_CORE: return ENOMEM;
case FR_TOO_MANY_OPEN_FILES: return ENFILE;
case FR_INVALID_PARAMETER: return EINVAL;
case FR_OK: return 0;
}
assert(0 && "unhandled FRESULT");
return ENOTSUP;
}
static void file_cleanup(vfs_fat_ctx_t* ctx, int fd)
{
memset(&ctx->files[fd], 0, sizeof(FIL));
}
static int vfs_fat_open(void* ctx, const char * path, int flags, int mode)
{
ESP_LOGV(TAG, "%s: path=\"%s\", flags=%x, mode=%x", __func__, path, flags, mode);
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
_lock_acquire(&s_fat_ctx->lock);
int fd = get_next_fd(fat_ctx);
if (fd < 0) {
ESP_LOGE(TAG, "open: no free file descriptors");
errno = ENFILE;
fd = -1;
goto out;
}
FRESULT res = f_open(&fat_ctx->files[fd], path, fat_mode_conv(flags));
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
file_cleanup(fat_ctx, fd);
errno = fresult_to_errno(res);
fd = -1;
goto out;
}
out:
_lock_release(&s_fat_ctx->lock);
return fd;
}
static size_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size)
{
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
FIL* file = &fat_ctx->files[fd];
unsigned written = 0;
FRESULT res = f_write(file, data, size, &written);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
if (written == 0) {
return -1;
}
}
return written;
}
static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size)
{
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
FIL* file = &fat_ctx->files[fd];
unsigned read = 0;
FRESULT res = f_read(file, dst, size, &read);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
if (read == 0) {
return -1;
}
}
return read;
}
static int vfs_fat_close(void* ctx, int fd)
{
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
_lock_acquire(&s_fat_ctx->lock);
FIL* file = &fat_ctx->files[fd];
FRESULT res = f_close(file);
file_cleanup(fat_ctx, fd);
int rc = 0;
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
rc = -1;
}
_lock_release(&s_fat_ctx->lock);
return rc;
}
static off_t vfs_fat_lseek(void* ctx, int fd, off_t offset, int mode)
{
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
FIL* file = &fat_ctx->files[fd];
off_t new_pos;
if (mode == SEEK_SET) {
new_pos = offset;
} else if (mode == SEEK_CUR) {
off_t cur_pos = f_tell(file);
new_pos = cur_pos + offset;
} else if (mode == SEEK_END) {
off_t size = f_size(file);
new_pos = size + offset;
} else {
errno = EINVAL;
return -1;
}
FRESULT res = f_lseek(file, new_pos);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return -1;
}
return new_pos;
}
static int vfs_fat_fstat(void* ctx, int fd, struct stat * st)
{
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
FIL* file = &fat_ctx->files[fd];
st->st_size = f_size(file);
st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFREG;
return 0;
}
static int vfs_fat_stat(void* ctx, const char * path, struct stat * st)
{
FILINFO info;
FRESULT res = f_stat(path, &info);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return -1;
}
st->st_size = info.fsize;
st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO |
((info.fattrib & AM_DIR) ? S_IFDIR : S_IFREG);
struct tm tm;
uint16_t fdate = info.fdate;
tm.tm_mday = fdate & 0x1f;
fdate >>= 5;
tm.tm_mon = (fdate & 0xf) - 1;
fdate >>=4;
tm.tm_year = fdate + 80;
uint16_t ftime = info.ftime;
tm.tm_sec = (ftime & 0x1f) * 2;
ftime >>= 5;
tm.tm_min = (ftime & 0x3f);
ftime >>= 6;
tm.tm_hour = (ftime & 0x1f);
st->st_mtime = mktime(&tm);
return 0;
}
static int vfs_fat_unlink(void* ctx, const char *path)
{
FRESULT res = f_unlink(path);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return -1;
}
return 0;
}
static int vfs_fat_link(void* ctx, const char* n1, const char* n2)
{
const size_t copy_buf_size = 4096;
void* buf = malloc(copy_buf_size);
if (buf == NULL) {
errno = ENOMEM;
return -1;
}
FIL f1;
FRESULT res = f_open(&f1, n1, FA_READ | FA_OPEN_EXISTING);
if (res != FR_OK) {
goto fail1;
}
FIL f2;
res = f_open(&f2, n2, FA_WRITE | FA_CREATE_NEW);
if (res != FR_OK) {
goto fail2;
}
size_t size_left = f_size(&f1);
while (size_left > 0) {
size_t will_copy = (size_left < copy_buf_size) ? size_left : copy_buf_size;
size_t read;
res = f_read(&f1, buf, will_copy, &read);
if (res != FR_OK) {
goto fail3;
} else if (read != will_copy) {
res = FR_DISK_ERR;
goto fail3;
}
size_t written;
res = f_write(&f2, buf, will_copy, &written);
if (res != FR_OK) {
goto fail3;
} else if (written != will_copy) {
res = FR_DISK_ERR;
goto fail3;
}
size_left -= will_copy;
}
fail3:
f_close(&f2);
fail2:
f_close(&f1);
fail1:
free(buf);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return -1;
}
return 0;
}
static int vfs_fat_rename(void* ctx, const char *src, const char *dst)
{
FRESULT res = f_rename(src, dst);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return -1;
}
return 0;
}
static DIR* vfs_fat_opendir(void* ctx, const char* name)
{
vfs_fat_dir_t* fat_dir = calloc(1, sizeof(vfs_fat_dir_t));
if (!fat_dir) {
errno = ENOMEM;
return NULL;
}
FRESULT res = f_opendir(&fat_dir->ffdir, name);
if (res != FR_OK) {
free(fat_dir);
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return NULL;
}
return (DIR*) fat_dir;
}
static int vfs_fat_closedir(void* ctx, DIR* pdir)
{
assert(pdir);
vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
FRESULT res = f_closedir(&fat_dir->ffdir);
free(pdir);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return -1;
}
return 0;
}
static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir)
{
vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
struct dirent* out_dirent;
int err = vfs_fat_readdir_r(ctx, pdir, &fat_dir->cur_dirent, &out_dirent);
if (err != 0) {
errno = err;
return NULL;
}
return out_dirent;
}
static int vfs_fat_readdir_r(void* ctx, DIR* pdir,
struct dirent* entry, struct dirent** out_dirent)
{
assert(pdir);
vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
FRESULT res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
return fresult_to_errno(res);
}
if (fat_dir->filinfo.fname[0] == 0) {
// end of directory
*out_dirent = NULL;
return 0;
}
entry->d_ino = 0;
if (fat_dir->filinfo.fattrib & AM_DIR) {
entry->d_type = DT_DIR;
} else {
entry->d_type = DT_REG;
}
strlcpy(entry->d_name, fat_dir->filinfo.fname,
sizeof(entry->d_name));
fat_dir->offset++;
*out_dirent = entry;
return 0;
}
static long vfs_fat_telldir(void* ctx, DIR* pdir)
{
assert(pdir);
vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
return fat_dir->offset;
}
static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset)
{
assert(pdir);
vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
FRESULT res;
if (offset < fat_dir->offset) {
res = f_rewinddir(&fat_dir->ffdir);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: rewinddir fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return;
}
fat_dir->offset = 0;
}
while (fat_dir->offset < offset) {
res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: f_readdir fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return;
}
fat_dir->offset++;
}
}
static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode)
{
(void) mode;
FRESULT res = f_mkdir(name);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return -1;
}
return 0;
}
static int vfs_fat_rmdir(void* ctx, const char* name)
{
FRESULT res = f_unlink(name);
if (res != FR_OK) {
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
errno = fresult_to_errno(res);
return -1;
}
return 0;
}

View File

@ -0,0 +1,126 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include "esp_log.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "driver/sdmmc_host.h"
#include "sdmmc_cmd.h"
#include "diskio.h"
static const char* TAG = "vfs_fat_sdmmc";
static sdmmc_card_t* s_card = NULL;
esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
const sdmmc_host_t* host_config,
const sdmmc_slot_config_t* slot_config,
const esp_vfs_fat_sdmmc_mount_config_t* mount_config,
sdmmc_card_t** out_card)
{
const size_t workbuf_size = 4096;
void* workbuf = NULL;
if (s_card != NULL) {
return ESP_ERR_INVALID_STATE;
}
// enable SDMMC
sdmmc_host_init();
// enable card slot
sdmmc_host_init_slot(host_config->slot, slot_config);
s_card = malloc(sizeof(sdmmc_card_t));
if (s_card == NULL) {
return ESP_ERR_NO_MEM;
}
// probe and initialize card
esp_err_t err = sdmmc_card_init(host_config, s_card);
if (err != ESP_OK) {
ESP_LOGD(TAG, "sdmmc_card_init failed 0x(%x)", err);
goto fail;
}
if (out_card != NULL) {
*out_card = s_card;
}
// connect SDMMC driver to FATFS
ff_diskio_register_sdmmc(0, s_card);
// connect FATFS to VFS
FATFS* fs;
err = esp_vfs_fat_register(base_path, "", mount_config->max_files, &fs);
if (err == ESP_ERR_INVALID_STATE) {
// it's okay, already registered with VFS
} else if (err != ESP_OK) {
ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", err);
goto fail;
}
// Try to mount partition
FRESULT res = f_mount(fs, "", 1);
if (res != FR_OK) {
err = ESP_FAIL;
ESP_LOGW(TAG, "failed to mount card (%d)", res);
if (!(res == FR_NO_FILESYSTEM && mount_config->format_if_mount_failed)) {
goto fail;
}
ESP_LOGW(TAG, "partitioning card");
DWORD plist[] = {100, 0, 0, 0};
workbuf = malloc(workbuf_size);
res = f_fdisk(0, plist, workbuf);
if (res != FR_OK) {
err = ESP_FAIL;
ESP_LOGD(TAG, "f_fdisk failed (%d)", res);
goto fail;
}
ESP_LOGW(TAG, "formatting card");
res = f_mkfs("", FM_ANY, s_card->csd.sector_size, workbuf, workbuf_size);
if (res != FR_OK) {
err = ESP_FAIL;
ESP_LOGD(TAG, "f_mkfs failed (%d)", res);
goto fail;
}
free(workbuf);
ESP_LOGW(TAG, "mounting again");
res = f_mount(fs, "", 0);
if (res != FR_OK) {
err = ESP_FAIL;
ESP_LOGD(TAG, "f_mount failed after formatting (%d)", res);
goto fail;
}
}
return ESP_OK;
fail:
free(workbuf);
esp_vfs_unregister(base_path);
free(s_card);
s_card = NULL;
return err;
}
esp_err_t esp_vfs_fat_sdmmc_unmount()
{
if (s_card == NULL) {
return ESP_ERR_INVALID_STATE;
}
// unmount
f_mount(0, "", 0);
// release SD driver
free(s_card);
s_card = NULL;
sdmmc_host_deinit();
return esp_vfs_fat_unregister();
}

View File

@ -0,0 +1 @@
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View File

@ -0,0 +1,552 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/unistd.h>
#include "unity.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/sdmmc_host.h"
#include "driver/sdmmc_defs.h"
#include "sdmmc_cmd.h"
#include "diskio.h"
#include "ff.h"
static const char* hello_str = "Hello, World!\n";
#define HEAP_SIZE_CAPTURE() \
size_t heap_size = esp_get_free_heap_size();
#define HEAP_SIZE_CHECK(tolerance) \
do {\
size_t final_heap_size = esp_get_free_heap_size(); \
if (final_heap_size < heap_size - tolerance) { \
printf("Initial heap size: %d, final: %d, diff=%d\n", heap_size, final_heap_size, heap_size - final_heap_size); \
} \
} while(0)
static void create_file_with_text(const char* name, const char* text)
{
FILE* f = fopen(name, "wb");
TEST_ASSERT_NOT_NULL(f);
TEST_ASSERT_TRUE(fputs(text, f) != EOF);
TEST_ASSERT_EQUAL(0, fclose(f));
}
TEST_CASE("can create and write file on sd card", "[fatfs]")
{
HEAP_SIZE_CAPTURE();
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5
};
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL));
create_file_with_text("/sdcard/hello.txt", hello_str);
TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount());
HEAP_SIZE_CHECK(0);
}
TEST_CASE("can read file on sd card", "[fatfs]")
{
HEAP_SIZE_CAPTURE();
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5
};
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL));
FILE* f = fopen("/sdcard/hello.txt", "r");
TEST_ASSERT_NOT_NULL(f);
char buf[32];
int cb = fread(buf, 1, sizeof(buf), f);
TEST_ASSERT_EQUAL(strlen(hello_str), cb);
TEST_ASSERT_EQUAL(0, strcmp(hello_str, buf));
TEST_ASSERT_EQUAL(0, fclose(f));
TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount());
HEAP_SIZE_CHECK(0);
}
static void speed_test(void* buf, size_t buf_size, size_t file_size, bool write)
{
const size_t buf_count = file_size / buf_size;
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = write,
.max_files = 5
};
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL));
FILE* f = fopen("/sdcard/4mb.bin", (write) ? "wb" : "rb");
TEST_ASSERT_NOT_NULL(f);
struct timeval tv_start;
gettimeofday(&tv_start, NULL);
for (size_t n = 0; n < buf_count; ++n) {
if (write) {
TEST_ASSERT_EQUAL(1, fwrite(buf, buf_size, 1, f));
} else {
if (fread(buf, buf_size, 1, f) != 1) {
printf("reading at n=%d, eof=%d", n, feof(f));
TEST_FAIL();
}
}
}
struct timeval tv_end;
gettimeofday(&tv_end, NULL);
TEST_ASSERT_EQUAL(0, fclose(f));
TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount());
float t_s = tv_end.tv_sec - tv_start.tv_sec + 1e-6f * (tv_end.tv_usec - tv_start.tv_usec);
printf("%s %d bytes (block size %d) in %.3fms (%.3f MB/s)\n",
(write)?"Wrote":"Read", file_size, buf_size, t_s * 1e3,
(file_size / 1024 / 1024) / t_s);
}
TEST_CASE("read speed test", "[fatfs]")
{
HEAP_SIZE_CAPTURE();
const size_t buf_size = 16 * 1024;
uint32_t* buf = (uint32_t*) calloc(1, buf_size);
const size_t file_size = 4 * 1024 * 1024;
speed_test(buf, 4 * 1024, file_size, false);
HEAP_SIZE_CHECK(0);
speed_test(buf, 8 * 1024, file_size, false);
HEAP_SIZE_CHECK(0);
speed_test(buf, 16 * 1024, file_size, false);
HEAP_SIZE_CHECK(0);
free(buf);
HEAP_SIZE_CHECK(0);
}
TEST_CASE("write speed test", "[fatfs]")
{
HEAP_SIZE_CAPTURE();
const size_t buf_size = 16 * 1024;
uint32_t* buf = (uint32_t*) calloc(1, buf_size);
for (size_t i = 0; i < buf_size / 4; ++i) {
buf[i] = esp_random();
}
const size_t file_size = 4 * 1024 * 1024;
speed_test(buf, 4 * 1024, file_size, true);
speed_test(buf, 8 * 1024, file_size, true);
speed_test(buf, 16 * 1024, file_size, true);
free(buf);
HEAP_SIZE_CHECK(0);
}
TEST_CASE("can lseek", "[fatfs]")
{
HEAP_SIZE_CAPTURE();
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5
};
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL));
FILE* f = fopen("/sdcard/seek.txt", "wb+");
TEST_ASSERT_NOT_NULL(f);
TEST_ASSERT_EQUAL(11, fprintf(f, "0123456789\n"));
TEST_ASSERT_EQUAL(0, fseek(f, -2, SEEK_CUR));
TEST_ASSERT_EQUAL('9', fgetc(f));
TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_SET));
TEST_ASSERT_EQUAL('3', fgetc(f));
TEST_ASSERT_EQUAL(0, fseek(f, -3, SEEK_END));
TEST_ASSERT_EQUAL('8', fgetc(f));
TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_END));
TEST_ASSERT_EQUAL(14, ftell(f));
TEST_ASSERT_EQUAL(4, fprintf(f, "abc\n"));
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END));
TEST_ASSERT_EQUAL(18, ftell(f));
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET));
char buf[20];
TEST_ASSERT_EQUAL(18, fread(buf, 1, sizeof(buf), f));
const char ref_buf[] = "0123456789\n\0\0\0abc\n";
TEST_ASSERT_EQUAL_INT8_ARRAY(ref_buf, buf, sizeof(ref_buf) - 1);
TEST_ASSERT_EQUAL(0, fclose(f));
TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount());
HEAP_SIZE_CHECK(0);
}
TEST_CASE("stat returns correct values", "[fatfs]")
{
HEAP_SIZE_CAPTURE();
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5
};
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL));
struct tm tm;
tm.tm_year = 2016 - 1900;
tm.tm_mon = 0;
tm.tm_mday = 10;
tm.tm_hour = 16;
tm.tm_min = 30;
tm.tm_sec = 0;
time_t t = mktime(&tm);
printf("Setting time: %s", asctime(&tm));
struct timeval now = { .tv_sec = t };
settimeofday(&now, NULL);
create_file_with_text("/sdcard/stat.txt", "foo\n");
struct stat st;
TEST_ASSERT_EQUAL(0, stat("/sdcard/stat.txt", &st));
time_t mtime = st.st_mtime;
struct tm mtm;
localtime_r(&mtime, &mtm);
printf("File time: %s", asctime(&mtm));
TEST_ASSERT(abs(mtime - t) < 2); // fatfs library stores time with 2 second precision
TEST_ASSERT(st.st_mode & S_IFREG);
TEST_ASSERT_FALSE(st.st_mode & S_IFDIR);
TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount());
HEAP_SIZE_CHECK(0);
}
TEST_CASE("unlink removes a file", "[fatfs]")
{
HEAP_SIZE_CAPTURE();
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5
};
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL));
create_file_with_text("/sdcard/unlink.txt", "unlink\n");
TEST_ASSERT_EQUAL(0, unlink("/sdcard/unlink.txt"));
TEST_ASSERT_NULL(fopen("/sdcard/unlink.txt", "r"));
TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount());
HEAP_SIZE_CHECK(0);
}
TEST_CASE("link copies a file, rename moves a file", "[fatfs]")
{
HEAP_SIZE_CAPTURE();
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5
};
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL));
unlink("/sdcard/linkcopy.txt");
unlink("/sdcard/link_dst.txt");
unlink("/sdcard/link_src.txt");
FILE* f = fopen("/sdcard/link_src.txt", "w+");
TEST_ASSERT_NOT_NULL(f);
char* str = "0123456789";
for (int i = 0; i < 4000; ++i) {
TEST_ASSERT_NOT_EQUAL(EOF, fputs(str, f));
}
TEST_ASSERT_EQUAL(0, fclose(f));
TEST_ASSERT_EQUAL(0, link("/sdcard/link_src.txt", "/sdcard/linkcopy.txt"));
FILE* fcopy = fopen("/sdcard/linkcopy.txt", "r");
TEST_ASSERT_NOT_NULL(fcopy);
TEST_ASSERT_EQUAL(0, fseek(fcopy, 0, SEEK_END));
TEST_ASSERT_EQUAL(40000, ftell(fcopy));
TEST_ASSERT_EQUAL(0, fclose(fcopy));
TEST_ASSERT_EQUAL(0, rename("/sdcard/linkcopy.txt", "/sdcard/link_dst.txt"));
TEST_ASSERT_NULL(fopen("/sdcard/linkcopy.txt", "r"));
FILE* fdst = fopen("/sdcard/link_dst.txt", "r");
TEST_ASSERT_NOT_NULL(fdst);
TEST_ASSERT_EQUAL(0, fseek(fdst, 0, SEEK_END));
TEST_ASSERT_EQUAL(40000, ftell(fdst));
TEST_ASSERT_EQUAL(0, fclose(fdst));
TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount());
HEAP_SIZE_CHECK(0);
}
typedef struct {
const char* filename;
bool write;
size_t word_count;
int seed;
SemaphoreHandle_t done;
int result;
} read_write_test_arg_t;
#define READ_WRITE_TEST_ARG_INIT(name, seed_) \
{ \
.filename = name, \
.seed = seed_, \
.word_count = 8192, \
.write = true, \
.done = xSemaphoreCreateBinary() \
}
static void read_write_task(void* param)
{
read_write_test_arg_t* args = (read_write_test_arg_t*) param;
FILE* f = fopen(args->filename, args->write ? "wb" : "rb");
if (f == NULL) {
args->result = ESP_ERR_NOT_FOUND;
goto done;
}
srand(args->seed);
for (size_t i = 0; i < args->word_count; ++i) {
uint32_t val = rand();
if (args->write) {
int cnt = fwrite(&val, sizeof(val), 1, f);
if (cnt != 1) {
args->result = ESP_FAIL;
goto close;
}
} else {
uint32_t rval;
int cnt = fread(&rval, sizeof(rval), 1, f);
if (cnt != 1 || rval != val) {
ets_printf("E: i=%d, cnt=%d rval=%d val=%d\n\n", i, cnt, rval, val);
args->result = ESP_FAIL;
goto close;
}
}
}
args->result = ESP_OK;
close:
fclose(f);
done:
xSemaphoreGive(args->done);
vTaskDelay(1);
vTaskDelete(NULL);
}
TEST_CASE("multiple tasks can use same volume", "[fatfs]")
{
HEAP_SIZE_CAPTURE();
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5
};
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL));
read_write_test_arg_t args1 = READ_WRITE_TEST_ARG_INIT("/sdcard/f1", 1);
read_write_test_arg_t args2 = READ_WRITE_TEST_ARG_INIT("/sdcard/f2", 2);
printf("writing f1 and f2\n");
xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0);
xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 1);
xSemaphoreTake(args1.done, portMAX_DELAY);
printf("f1 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args1.result);
xSemaphoreTake(args2.done, portMAX_DELAY);
printf("f2 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args2.result);
args1.write = false;
args2.write = false;
read_write_test_arg_t args3 = READ_WRITE_TEST_ARG_INIT("/sdcard/f3", 3);
read_write_test_arg_t args4 = READ_WRITE_TEST_ARG_INIT("/sdcard/f4", 4);
printf("reading f1 and f2, writing f3 and f4\n");
xTaskCreatePinnedToCore(&read_write_task, "rw3", 2048, &args3, 3, NULL, 1);
xTaskCreatePinnedToCore(&read_write_task, "rw4", 2048, &args4, 3, NULL, 0);
xTaskCreatePinnedToCore(&read_write_task, "rw1", 2048, &args1, 3, NULL, 0);
xTaskCreatePinnedToCore(&read_write_task, "rw2", 2048, &args2, 3, NULL, 1);
xSemaphoreTake(args1.done, portMAX_DELAY);
printf("f1 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args1.result);
xSemaphoreTake(args2.done, portMAX_DELAY);
printf("f2 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args2.result);
xSemaphoreTake(args3.done, portMAX_DELAY);
printf("f3 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args3.result);
xSemaphoreTake(args4.done, portMAX_DELAY);
printf("f4 done\n");
TEST_ASSERT_EQUAL(ESP_OK, args4.result);
TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount());
vSemaphoreDelete(args1.done);
vSemaphoreDelete(args2.done);
vSemaphoreDelete(args3.done);
vSemaphoreDelete(args4.done);
vTaskDelay(10);
HEAP_SIZE_CHECK(0);
}
TEST_CASE("can create and remove directories", "[fatfs]")
{
HEAP_SIZE_CAPTURE();
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5
};
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL));
TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir1", 0755));
struct stat st;
TEST_ASSERT_EQUAL(0, stat("/sdcard/dir1", &st));
TEST_ASSERT_TRUE(st.st_mode & S_IFDIR);
TEST_ASSERT_FALSE(st.st_mode & S_IFREG);
TEST_ASSERT_EQUAL(0, rmdir("/sdcard/dir1"));
TEST_ASSERT_EQUAL(-1, stat("/sdcard/dir1", &st));
TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir2", 0755));
create_file_with_text("/sdcard/dir2/1.txt", "foo\n");
TEST_ASSERT_EQUAL(0, stat("/sdcard/dir2", &st));
TEST_ASSERT_TRUE(st.st_mode & S_IFDIR);
TEST_ASSERT_FALSE(st.st_mode & S_IFREG);
TEST_ASSERT_EQUAL(0, stat("/sdcard/dir2/1.txt", &st));
TEST_ASSERT_FALSE(st.st_mode & S_IFDIR);
TEST_ASSERT_TRUE(st.st_mode & S_IFREG);
TEST_ASSERT_EQUAL(-1, rmdir("/sdcard/dir2"));
TEST_ASSERT_EQUAL(0, unlink("/sdcard/dir2/1.txt"));
TEST_ASSERT_EQUAL(0, rmdir("/sdcard/dir2"));
TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount());
HEAP_SIZE_CHECK(0);
}
TEST_CASE("opendir, readdir, rewinddir, seekdir work as expected", "[fatfs]")
{
HEAP_SIZE_CAPTURE();
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5
};
TEST_ESP_OK(esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL));
unlink("/sdcard/dir/inner/3.txt");
rmdir("/sdcard/dir/inner");
unlink("/sdcard/dir/2.txt");
unlink("/sdcard/dir/1.txt");
unlink("/sdcard/dir/boo.bin");
rmdir("/sdcard/dir");
TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir", 0755));
create_file_with_text("/sdcard/dir/2.txt", "1\n");
create_file_with_text("/sdcard/dir/1.txt", "1\n");
create_file_with_text("/sdcard/dir/boo.bin", "\01\02\03");
TEST_ASSERT_EQUAL(0, mkdir("/sdcard/dir/inner", 0755));
create_file_with_text("/sdcard/dir/inner/3.txt", "3\n");
DIR* dir = opendir("/sdcard/dir");
TEST_ASSERT_NOT_NULL(dir);
int count = 0;
const char* names[4];
while(count < 4) {
struct dirent* de = readdir(dir);
if (!de) {
break;
}
printf("found '%s'\n", de->d_name);
if (strcasecmp(de->d_name, "1.txt") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_REG);
names[count] = "1.txt";
++count;
} else if (strcasecmp(de->d_name, "2.txt") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_REG);
names[count] = "2.txt";
++count;
} else if (strcasecmp(de->d_name, "inner") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_DIR);
names[count] = "inner";
++count;
} else if (strcasecmp(de->d_name, "boo.bin") == 0) {
TEST_ASSERT_TRUE(de->d_type == DT_REG);
names[count] = "boo.bin";
++count;
} else {
TEST_FAIL_MESSAGE("unexpected directory entry");
}
}
TEST_ASSERT_EQUAL(count, 4);
rewinddir(dir);
struct dirent* de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[0]));
seekdir(dir, 3);
de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[3]));
seekdir(dir, 1);
de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[1]));
seekdir(dir, 2);
de = readdir(dir);
TEST_ASSERT_NOT_NULL(de);
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[2]));
TEST_ASSERT_EQUAL(0, closedir(dir));
TEST_ESP_OK(esp_vfs_fat_sdmmc_unmount());
HEAP_SIZE_CHECK(0);
}

View File

@ -1,13 +0,0 @@
/* <dirent.h> includes <sys/dirent.h>, which is this file. On a
system which supports <dirent.h>, this file is overridden by
dirent.h in the libc/sys/.../sys directory. On a system which does
not support <dirent.h>, we will get this file which uses #error to force
an error. */
#ifdef __cplusplus
extern "C" {
#endif
#error "<dirent.h> not supported"
#ifdef __cplusplus
}
#endif

View File

@ -3,7 +3,9 @@
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "unity.h"
#include "sdkconfig.h"
@ -86,6 +88,33 @@ TEST_CASE("test time functions", "[newlib]")
}
TEST_CASE("test asctime", "[newlib]")
{
char buf[64];
struct tm tm = { 0 };
tm.tm_year = 2016 - 1900;
tm.tm_mon = 0;
tm.tm_mday = 10;
tm.tm_hour = 16;
tm.tm_min = 30;
tm.tm_sec = 0;
time_t t = mktime(&tm);
const char* time_str = asctime(&tm);
strlcpy(buf, time_str, sizeof(buf));
printf("Setting time: %s", time_str);
struct timeval now = { .tv_sec = t };
settimeofday(&now, NULL);
struct timeval tv;
gettimeofday(&tv, NULL);
time_t mtime = tv.tv_sec;
struct tm mtm;
localtime_r(&mtime, &mtm);
time_str = asctime(&mtm);
printf("Got time: %s", time_str);
TEST_ASSERT_EQUAL_STRING(buf, time_str);
}
static bool fn_in_rom(void *fn, char *name)
{
const int fnaddr = (int)fn;

0
components/sdmmc/component.mk Executable file
View File

View File

@ -0,0 +1,77 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdio.h>
#include "esp_err.h"
#include "driver/sdmmc_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Probe and initialize SD/MMC card using given host
*
* @note Only SD cards (SDSC and SDHC/SDXC) are supported now.
* Support for MMC/eMMC cards will be added later.
*
* @param host pointer to structure defining host controller
* @param out_card pointer to structure which will receive information about the card when the function completes
* @return
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_card_init(const sdmmc_host_t* host,
sdmmc_card_t* out_card);
/**
* @brief Print information about the card to a stream
* @param stream stream obtained using fopen or fdopen
* @param card card information structure initialized using sdmmc_card_init
*/
void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card);
/**
* Write given number of sectors to SD/MMC card
*
* @param card pointer to card information structure previously initialized using sdmmc_card_init
* @param src pointer to data buffer to read data from; data size must be equal to sector_count * card->csd.sector_size
* @param start_sector sector where to start writing
* @param sector_count number of sectors to write
* @return
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src,
size_t start_sector, size_t sector_count);
/**
* Write given number of sectors to SD/MMC card
*
* @param card pointer to card information structure previously initialized using sdmmc_card_init
* @param dst pointer to data buffer to write into; buffer size must be at least sector_count * card->csd.sector_size
* @param start_sector sector where to start reading
* @param sector_count number of sectors to read
* @return
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst,
size_t start_sector, size_t sector_count);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,571 @@
/*
* Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org>
* Adaptations to ESP-IDF Copyright (c) 2016 Espressif Systems (Shanghai) PTE LTD
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_heap_alloc_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/sdmmc_defs.h"
#include "driver/sdmmc_types.h"
#include "sdmmc_cmd.h"
#define MIN(a,b) (((a)<(b))?(a):(b))
static const char* TAG = "sdmmc_cmd";
static esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd);
static esp_err_t sdmmc_send_app_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd);
static esp_err_t sdmmc_send_cmd_go_idle_state(sdmmc_card_t* card);
static esp_err_t sdmmc_send_cmd_send_if_cond(sdmmc_card_t* card, uint32_t ocr);
static esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp);
static esp_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid);
static esp_err_t sddmc_send_cmd_all_send_cid(sdmmc_card_t* card, sdmmc_cid_t* out_cid);
static esp_err_t sdmmc_send_cmd_set_relative_addr(sdmmc_card_t* card, uint16_t* out_rca);
static esp_err_t sdmmc_send_cmd_set_blocklen(sdmmc_card_t* card, sdmmc_csd_t* csd);
static esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd);
static esp_err_t sdmmc_send_cmd_send_csd(sdmmc_card_t* card, sdmmc_csd_t* out_csd);
static esp_err_t sdmmc_send_cmd_select_card(sdmmc_card_t* card);
static esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr);
static esp_err_t sdmmc_send_cmd_send_scr(sdmmc_card_t* card, sdmmc_scr_t *out_scr);
static esp_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width);
static esp_err_t sdmmc_send_cmd_stop_transmission(sdmmc_card_t* card, uint32_t* status);
static esp_err_t sdmmc_send_cmd_send_status(sdmmc_card_t* card, uint32_t* out_status);
static uint32_t get_host_ocr(float voltage);
esp_err_t sdmmc_card_init(const sdmmc_host_t* config,
sdmmc_card_t* card)
{
ESP_LOGD(TAG, "%s", __func__);
memset(card, 0, sizeof(*card));
memcpy(&card->host, config, sizeof(*config));
esp_err_t err = sdmmc_send_cmd_go_idle_state(card);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: go_idle_state (1) returned 0x%x", __func__, err);
return err;
}
ets_delay_us(10000);
uint32_t host_ocr = get_host_ocr(config->io_voltage);
err = sdmmc_send_cmd_send_if_cond(card, host_ocr);
if (err == ESP_OK) {
ESP_LOGD(TAG, "SDHC/SDXC card");
host_ocr |= SD_OCR_SDHC_CAP;
} else if (err == ESP_ERR_TIMEOUT) {
ESP_LOGD(TAG, "CMD8 timeout; not an SDHC/SDXC card");
} else {
ESP_LOGE(TAG, "%s: send_if_cond (1) returned 0x%x", __func__, err);
return err;
}
err = sdmmc_send_cmd_send_op_cond(card, host_ocr, &card->ocr);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: send_op_cond (1) returned 0x%x", __func__, err);
return err;
}
host_ocr &= card->ocr;
ESP_LOGD(TAG, "sdmmc_card_init: host_ocr=%08x, card_ocr=%08x", host_ocr, card->ocr);
err = sddmc_send_cmd_all_send_cid(card, &card->cid);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: all_send_cid returned 0x%x", __func__, err);
return err;
}
err = sdmmc_send_cmd_set_relative_addr(card, &card->rca);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: set_relative_addr returned 0x%x", __func__, err);
return err;
}
err = sdmmc_send_cmd_send_csd(card, &card->csd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: send_csd returned 0x%x", __func__, err);
return err;
}
const size_t max_sdsc_capacity = UINT32_MAX / card->csd.sector_size + 1;
if (!(card->ocr & SD_OCR_SDHC_CAP) &&
card->csd.capacity > max_sdsc_capacity) {
ESP_LOGW(TAG, "%s: SDSC card reports capacity=%u. Limiting to %u.",
__func__, card->csd.capacity, max_sdsc_capacity);
card->csd.capacity = max_sdsc_capacity;
}
err = sdmmc_send_cmd_select_card(card);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: select_card returned 0x%x", __func__, err);
return err;
}
if ((card->ocr & SD_OCR_SDHC_CAP) == 0) {
err = sdmmc_send_cmd_set_blocklen(card, &card->csd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: set_blocklen returned 0x%x", __func__, err);
return err;
}
}
err = sdmmc_send_cmd_send_scr(card, &card->scr);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: send_scr returned 0x%x", __func__, err);
return err;
}
if ((config->flags & SDMMC_HOST_FLAG_4BIT) &&
(card->scr.bus_width & SCR_SD_BUS_WIDTHS_4BIT)) {
ESP_LOGD(TAG, "switching to 4-bit bus mode");
err = sdmmc_send_cmd_set_bus_width(card, 4);
if (err != ESP_OK) {
ESP_LOGE(TAG, "set_bus_width failed");
return err;
}
err = (*config->set_bus_width)(config->slot, 4);
if (err != ESP_OK) {
ESP_LOGE(TAG, "slot->set_bus_width failed");
return err;
}
uint32_t status;
err = sdmmc_send_cmd_stop_transmission(card, &status);
if (err != ESP_OK) {
ESP_LOGE(TAG, "stop_transmission failed (0x%x)", err);
return err;
}
}
uint32_t status = 0;
while (!(status & MMC_R1_READY_FOR_DATA)) {
// TODO: add some timeout here
uint32_t count = 0;
err = sdmmc_send_cmd_send_status(card, &status);
if (err != ESP_OK) {
return err;
}
if (++count % 10 == 0) {
ESP_LOGV(TAG, "waiting for card to become ready (%d)", count);
}
}
if (config->max_freq_khz >= SDMMC_FREQ_HIGHSPEED &&
card->csd.tr_speed / 1000 >= SDMMC_FREQ_HIGHSPEED) {
ESP_LOGD(TAG, "switching to HS bus mode");
err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_HIGHSPEED);
if (err != ESP_OK) {
ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode");
return err;
}
} else if (config->max_freq_khz >= SDMMC_FREQ_DEFAULT &&
card->csd.tr_speed / 1000 >= SDMMC_FREQ_DEFAULT) {
ESP_LOGD(TAG, "switching to DS bus mode");
err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_DEFAULT);
if (err != ESP_OK) {
ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode");
return err;
}
}
sdmmc_scr_t scr_tmp;
err = sdmmc_send_cmd_send_scr(card, &scr_tmp);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: send_scr returned 0x%x", __func__, err);
return err;
}
if (memcmp(&card->scr, &scr_tmp, sizeof(scr_tmp)) != 0) {
ESP_LOGE(TAG, "data check fail!");
return ESP_ERR_INVALID_RESPONSE;
}
return ESP_OK;
}
void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card)
{
fprintf(stream, "Name: %s\n", card->cid.name);
fprintf(stream, "Type: %s\n", (card->ocr & SD_OCR_SDHC_CAP)?"SDHC/SDXC":"SDSC");
fprintf(stream, "Speed: %s\n", (card->csd.tr_speed > 25000000)?"high speed":"default speed");
fprintf(stream, "Size: %lluMB\n", ((uint64_t) card->csd.capacity) * card->csd.sector_size / (1024 * 1024));
fprintf(stream, "CSD: ver=%d, sector_size=%d, capacity=%d read_bl_len=%d\n",
card->csd.csd_ver,
card->csd.sector_size, card->csd.capacity, card->csd.read_block_len);
fprintf(stream, "SCR: sd_spec=%d, bus_width=%d\n", card->scr.sd_spec, card->scr.bus_width);
}
static esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd)
{
int slot = card->host.slot;
ESP_LOGV(TAG, "sending cmd slot=%d op=%d arg=%x flags=%x data=%p blklen=%d datalen=%d",
slot, cmd->opcode, cmd->arg, cmd->flags, cmd->data, cmd->blklen, cmd->datalen);
esp_err_t err = (*card->host.do_transaction)(slot, cmd);
if (err != 0) {
ESP_LOGD(TAG, "sdmmc_req_run returned 0x%x", err);
return err;
}
int state = MMC_R1_CURRENT_STATE(cmd->response);
ESP_LOGV(TAG, "cmd response %08x %08x %08x %08x err=0x%x state=%d",
cmd->response[0],
cmd->response[1],
cmd->response[2],
cmd->response[3],
cmd->error,
state);
return cmd->error;
}
static esp_err_t sdmmc_send_app_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd)
{
sdmmc_command_t app_cmd = {
.opcode = MMC_APP_CMD,
.flags = SCF_CMD_AC | SCF_RSP_R1,
.arg = MMC_ARG_RCA(card->rca),
};
esp_err_t err = sdmmc_send_cmd(card, &app_cmd);
if (err != ESP_OK) {
return err;
}
if (!(MMC_R1(app_cmd.response) & MMC_R1_APP_CMD)) {
ESP_LOGW(TAG, "card doesn't support APP_CMD");
return ESP_ERR_NOT_SUPPORTED;
}
return sdmmc_send_cmd(card, cmd);
}
static esp_err_t sdmmc_send_cmd_go_idle_state(sdmmc_card_t* card)
{
sdmmc_command_t cmd = {
.opcode = MMC_GO_IDLE_STATE,
.flags = SCF_CMD_BC | SCF_RSP_R0,
};
return sdmmc_send_cmd(card, &cmd);
}
static esp_err_t sdmmc_send_cmd_send_if_cond(sdmmc_card_t* card, uint32_t ocr)
{
const uint8_t pattern = 0xaa; /* any pattern will do here */
sdmmc_command_t cmd = {
.opcode = SD_SEND_IF_COND,
.arg = (((ocr & SD_OCR_VOL_MASK) != 0) << 8) | pattern,
.flags = SCF_CMD_BCR | SCF_RSP_R7,
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
uint8_t response = cmd.response[0] & 0xff;
if (response != pattern) {
return ESP_ERR_INVALID_RESPONSE;
}
return ESP_OK;
}
static esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp)
{
sdmmc_command_t cmd = {
.arg = ocr,
.flags = SCF_CMD_BCR | SCF_RSP_R3,
.opcode = SD_APP_OP_COND
};
int nretries = 100; // arbitrary, BSD driver uses this value
for (; nretries != 0; --nretries) {
esp_err_t err = sdmmc_send_app_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
if ((MMC_R3(cmd.response) & MMC_OCR_MEM_READY) ||
ocr == 0) {
break;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
if (nretries == 0) {
return ESP_ERR_TIMEOUT;
}
if (ocrp) {
*ocrp = MMC_R3(cmd.response);
}
return ESP_OK;
}
static esp_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid)
{
out_cid->mfg_id = SD_CID_MID(resp);
out_cid->oem_id = SD_CID_OID(resp);
SD_CID_PNM_CPY(resp, out_cid->name);
out_cid->revision = SD_CID_REV(resp);
out_cid->serial = SD_CID_PSN(resp);
out_cid->date = SD_CID_MDT(resp);
return ESP_OK;
}
static esp_err_t sddmc_send_cmd_all_send_cid(sdmmc_card_t* card, sdmmc_cid_t* out_cid)
{
assert(out_cid);
sdmmc_command_t cmd = {
.opcode = MMC_ALL_SEND_CID,
.flags = SCF_CMD_BCR | SCF_RSP_R2
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
return sdmmc_decode_cid(cmd.response, out_cid);
}
static esp_err_t sdmmc_send_cmd_set_relative_addr(sdmmc_card_t* card, uint16_t* out_rca)
{
assert(out_rca);
sdmmc_command_t cmd = {
.opcode = SD_SEND_RELATIVE_ADDR,
.flags = SCF_CMD_BCR | SCF_RSP_R6
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
*out_rca = SD_R6_RCA(cmd.response);
return ESP_OK;
}
static esp_err_t sdmmc_send_cmd_set_blocklen(sdmmc_card_t* card, sdmmc_csd_t* csd)
{
sdmmc_command_t cmd = {
.opcode = MMC_SET_BLOCKLEN,
.arg = csd->sector_size,
.flags = SCF_CMD_AC | SCF_RSP_R1
};
return sdmmc_send_cmd(card, &cmd);
}
static esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd)
{
out_csd->csd_ver = SD_CSD_CSDVER(response);
switch (out_csd->csd_ver) {
case SD_CSD_CSDVER_2_0:
out_csd->capacity = SD_CSD_V2_CAPACITY(response);
out_csd->read_block_len = SD_CSD_V2_BL_LEN;
break;
case SD_CSD_CSDVER_1_0:
out_csd->capacity = SD_CSD_CAPACITY(response);
out_csd->read_block_len = SD_CSD_READ_BL_LEN(response);
break;
default:
ESP_LOGE(TAG, "unknown SD CSD structure version 0x%x", out_csd->csd_ver);
return ESP_ERR_NOT_SUPPORTED;
}
out_csd->card_command_class = SD_CSD_CCC(response);
int read_bl_size = 1 << out_csd->read_block_len;
out_csd->sector_size = MIN(read_bl_size, 512);
if (out_csd->sector_size < read_bl_size) {
out_csd->capacity *= read_bl_size / out_csd->sector_size;
}
int speed = SD_CSD_SPEED(response);
if (speed == SD_CSD_SPEED_50_MHZ) {
out_csd->tr_speed = 50000000;
} else {
out_csd->tr_speed = 25000000;
}
return ESP_OK;
}
static esp_err_t sdmmc_send_cmd_send_csd(sdmmc_card_t* card, sdmmc_csd_t* out_csd)
{
sdmmc_command_t cmd = {
.opcode = MMC_SEND_CSD,
.arg = MMC_ARG_RCA(card->rca),
.flags = SCF_CMD_AC | SCF_RSP_R2
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
return sdmmc_decode_csd(cmd.response, out_csd);
}
static esp_err_t sdmmc_send_cmd_select_card(sdmmc_card_t* card)
{
sdmmc_command_t cmd = {
.opcode = MMC_SELECT_CARD,
.arg = MMC_ARG_RCA(card->rca),
.flags = SCF_CMD_AC | SCF_RSP_R1
};
return sdmmc_send_cmd(card, &cmd);
}
static esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr)
{
sdmmc_response_t resp = {0xabababab, 0xabababab, 0x12345678, 0x09abcdef};
resp[2] = __builtin_bswap32(raw_scr[0]);
resp[3] = __builtin_bswap32(raw_scr[1]);
int ver = SCR_STRUCTURE(resp);
if (ver != 0) {
return ESP_ERR_NOT_SUPPORTED;
}
out_scr->sd_spec = SCR_SD_SPEC(resp);
out_scr->bus_width = SCR_SD_BUS_WIDTHS(resp);
return ESP_OK;
}
static esp_err_t sdmmc_send_cmd_send_scr(sdmmc_card_t* card, sdmmc_scr_t *out_scr)
{
size_t datalen = 8;
uint32_t* buf = (uint32_t*) pvPortMallocCaps(datalen, MALLOC_CAP_DMA);
if (buf == NULL) {
return ESP_ERR_NO_MEM;
}
sdmmc_command_t cmd = {
.data = buf,
.datalen = datalen,
.blklen = datalen,
.flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1,
.opcode = SD_APP_SEND_SCR
};
esp_err_t err = sdmmc_send_app_cmd(card, &cmd);
if (err == ESP_OK) {
buf[0] = (buf[0]);
buf[1] = (buf[1]);
err = sdmmc_decode_scr(buf, out_scr);
}
free(buf);
return err;
}
static esp_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width)
{
sdmmc_command_t cmd = {
.opcode = SD_APP_SET_BUS_WIDTH,
.flags = SCF_RSP_R1 | SCF_CMD_AC,
.arg = (width == 4) ? SD_ARG_BUS_WIDTH_4 : SD_ARG_BUS_WIDTH_1
};
return sdmmc_send_app_cmd(card, &cmd);
}
static esp_err_t sdmmc_send_cmd_stop_transmission(sdmmc_card_t* card, uint32_t* status)
{
sdmmc_command_t cmd = {
.opcode = MMC_STOP_TRANSMISSION,
.arg = 0,
.flags = SCF_RSP_R1B | SCF_CMD_AC
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err == 0) {
*status = MMC_R1(cmd.response);
}
return err;
}
static uint32_t get_host_ocr(float voltage)
{
// TODO: report exact voltage to the card
// For now tell that the host has 2.8-3.6V voltage range
(void) voltage;
return SD_OCR_VOL_MASK;
}
static esp_err_t sdmmc_send_cmd_send_status(sdmmc_card_t* card, uint32_t* out_status)
{
sdmmc_command_t cmd = {
.opcode = MMC_SEND_STATUS,
.arg = MMC_ARG_RCA(card->rca),
.flags = SCF_CMD_AC | SCF_RSP_R1
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
if (out_status) {
*out_status = MMC_R1(cmd.response);
}
return ESP_OK;
}
esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src,
size_t start_block, size_t block_count)
{
if (start_block + block_count > card->csd.capacity) {
return ESP_ERR_INVALID_SIZE;
}
size_t block_size = card->csd.sector_size;
sdmmc_command_t cmd = {
.flags = SCF_CMD_ADTC | SCF_RSP_R1,
.blklen = block_size,
.data = (void*) src,
.datalen = block_count * block_size
};
if (block_count == 1) {
cmd.opcode = MMC_WRITE_BLOCK_SINGLE;
} else {
cmd.opcode = MMC_WRITE_BLOCK_MULTIPLE;
}
if (card->ocr & SD_OCR_SDHC_CAP) {
cmd.arg = start_block;
} else {
cmd.arg = start_block * block_size;
}
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
return err;
}
uint32_t status = 0;
size_t count = 0;
while (!(status & MMC_R1_READY_FOR_DATA)) {
// TODO: add some timeout here
err = sdmmc_send_cmd_send_status(card, &status);
if (err != ESP_OK) {
return err;
}
if (++count % 10 == 0) {
ESP_LOGV(TAG, "waiting for card to become ready (%d)", count);
}
}
return ESP_OK;
}
esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst,
size_t start_block, size_t block_count)
{
if (start_block + block_count > card->csd.capacity) {
return ESP_ERR_INVALID_SIZE;
}
size_t block_size = card->csd.sector_size;
sdmmc_command_t cmd = {
.flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1,
.blklen = block_size,
.data = (void*) dst,
.datalen = block_count * block_size
};
if (block_count == 1) {
cmd.opcode = MMC_READ_BLOCK_SINGLE;
} else {
cmd.opcode = MMC_READ_BLOCK_MULTIPLE;
}
if (card->ocr & SD_OCR_SDHC_CAP) {
cmd.arg = start_block;
} else {
cmd.arg = start_block * block_size;
}
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
return err;
}
uint32_t status = 0;
size_t count = 0;
while (!(status & MMC_R1_READY_FOR_DATA)) {
// TODO: add some timeout here
err = sdmmc_send_cmd_send_status(card, &status);
if (err != ESP_OK) {
return err;
}
if (++count % 10 == 0) {
ESP_LOGV(TAG, "waiting for card to become ready (%d)", count);
}
}
return ESP_OK;
}

View File

@ -0,0 +1 @@
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View File

@ -0,0 +1,109 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "unity.h"
#include "driver/gpio.h"
#include "driver/sdmmc_host.h"
#include "driver/sdmmc_defs.h"
#include "sdmmc_cmd.h"
#include "esp_log.h"
#include "esp_heap_alloc_caps.h"
#include <time.h>
#include <sys/time.h>
TEST_CASE("can probe SD", "[sd]")
{
sdmmc_host_t config = SDMMC_HOST_DEFAULT();
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
sdmmc_host_init();
sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config);
sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t));
TEST_ASSERT_NOT_NULL(card);
TEST_ESP_OK(sdmmc_card_init(&config, card));
sdmmc_card_print_info(stdout, card);
sdmmc_host_deinit();
free(card);
}
static void do_single_write_read_test(sdmmc_card_t* card,
size_t start_block, size_t block_count)
{
size_t block_size = card->csd.sector_size;
size_t total_size = block_size * block_count;
printf(" %8d | %3d | %4.1f ", start_block, block_count, total_size / 1024.0f);
uint32_t* buffer = pvPortMallocCaps(total_size, MALLOC_CAP_DMA);
srand(start_block);
for (size_t i = 0; i < total_size / sizeof(buffer[0]); ++i) {
buffer[i] = rand();
}
struct timeval t_start_wr;
gettimeofday(&t_start_wr, NULL);
TEST_ESP_OK(sdmmc_write_sectors(card, buffer, start_block, block_count));
struct timeval t_stop_wr;
gettimeofday(&t_stop_wr, NULL);
float time_wr = 1e3f * (t_stop_wr.tv_sec - t_start_wr.tv_sec) + 1e-3f * (t_stop_wr.tv_usec - t_start_wr.tv_usec);
memset(buffer, 0xbb, total_size);
struct timeval t_start_rd;
gettimeofday(&t_start_rd, NULL);
TEST_ESP_OK(sdmmc_read_sectors(card, buffer, start_block, block_count));
struct timeval t_stop_rd;
gettimeofday(&t_stop_rd, NULL);
float time_rd = 1e3f * (t_stop_rd.tv_sec - t_start_rd.tv_sec) + 1e-3f * (t_stop_rd.tv_usec - t_start_rd.tv_usec);
printf(" | %6.2f | %.2f | %.2fs | %.2f\n",
time_wr, total_size / (time_wr / 1000) / (1024 * 1024),
time_rd, total_size / (time_rd / 1000) / (1024 * 1024));
srand(start_block);
for (size_t i = 0; i < total_size / sizeof(buffer[0]); ++i) {
TEST_ASSERT_EQUAL_HEX32(rand(), buffer[i]);
}
free(buffer);
}
TEST_CASE("can write and read back blocks", "[sd]")
{
sdmmc_host_t config = SDMMC_HOST_DEFAULT();
config.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
sdmmc_host_init();
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config);
sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t));
TEST_ASSERT_NOT_NULL(card);
TEST_ESP_OK(sdmmc_card_init(&config, card));
sdmmc_card_print_info(stdout, card);
printf(" sector | count | size(kB) | wr_time(ms) | wr_speed(MB/s) | rd_time(ms) | rd_speed(MB/s)\n");
do_single_write_read_test(card, 0, 1);
do_single_write_read_test(card, 0, 4);
do_single_write_read_test(card, 1, 16);
do_single_write_read_test(card, 16, 32);
do_single_write_read_test(card, 48, 64);
do_single_write_read_test(card, 128, 128);
do_single_write_read_test(card, card->csd.capacity - 64, 32);
do_single_write_read_test(card, card->csd.capacity - 64, 64);
do_single_write_read_test(card, card->csd.capacity - 8, 1);
do_single_write_read_test(card, card->csd.capacity/2, 1);
do_single_write_read_test(card, card->csd.capacity/2, 4);
do_single_write_read_test(card, card->csd.capacity/2, 8);
do_single_write_read_test(card, card->csd.capacity/2, 16);
do_single_write_read_test(card, card->csd.capacity/2, 32);
do_single_write_read_test(card, card->csd.capacity/2, 64);
do_single_write_read_test(card, card->csd.capacity/2, 128);
free(card);
sdmmc_host_deinit();
}

View File

@ -21,6 +21,8 @@
#include <sys/types.h>
#include <sys/reent.h>
#include <sys/stat.h>
#include <dirent.h>
#ifdef __cplusplus
extern "C" {
#endif
@ -106,6 +108,38 @@ typedef struct
int (*rename_p)(void* ctx, const char *src, const char *dst);
int (*rename)(const char *src, const char *dst);
};
union {
DIR* (*opendir_p)(void* ctx, const char* name);
DIR* (*opendir)(const char* name);
};
union {
struct dirent* (*readdir_p)(void* ctx, DIR* pdir);
struct dirent* (*readdir)(DIR* pdir);
};
union {
int (*readdir_r_p)(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent);
int (*readdir_r)(DIR* pdir, struct dirent* entry, struct dirent** out_dirent);
};
union {
long (*telldir_p)(void* ctx, DIR* pdir);
long (*telldir)(DIR* pdir);
};
union {
void (*seekdir_p)(void* ctx, DIR* pdir, long offset);
void (*seekdir)(DIR* pdir, long offset);
};
union {
int (*closedir_p)(void* ctx, DIR* pdir);
int (*closedir)(DIR* pdir);
};
union {
int (*mkdir_p)(void* ctx, const char* name, mode_t mode);
int (*mkdir)(const char* name, mode_t mode);
};
union {
int (*rmdir_p)(void* ctx, const char* name);
int (*rmdir)(const char* name);
};
} esp_vfs_t;
@ -131,6 +165,15 @@ typedef struct
esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx);
/**
* Unregister a virtual filesystem for given path prefix
*
* @param base_path file prefix previously used in esp_vfs_register call
* @return ESP_OK if successful, ESP_ERR_INVALID_STATE if VFS for given prefix
* hasn't been registered
*/
esp_err_t esp_vfs_unregister(const char* base_path);
/**
* These functions are to be used in newlib syscall table. They will be called by
* newlib when it needs to use any of the syscalls.

View File

@ -0,0 +1,55 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stddef.h>
#include <stdint.h>
/**
* This header file provides POSIX-compatible definitions of directory
* access functions and related data types.
* See http://pubs.opengroup.org/onlinepubs/7908799/xsh/dirent.h.html
* for reference.
*/
/**
* @brief Opaque directory structure
*/
typedef struct {
uint16_t dd_vfs_idx; /*!< VFS index, not to be used by applications */
uint16_t dd_rsv; /*!< field reserved for future extension */
/* remaining fields are defined by VFS implementation */
} DIR;
/**
* @brief Directory entry structure
*/
struct dirent {
int d_ino; /*!< file number */
uint8_t d_type; /*!< not defined in POSIX, but present in BSD and Linux */
#define DT_UNKNOWN 0
#define DT_REG 1
#define DT_DIR 2
char d_name[256]; /*!< zero-terminated file name */
};
DIR* opendir(const char* name);
struct dirent* readdir(DIR* pdir);
long telldir(DIR* pdir);
void seekdir(DIR* pdir, long loc);
void rewinddir(DIR* pdir);
int closedir(DIR* pdir);
int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent);

View File

@ -54,9 +54,6 @@ static size_t s_vfs_count = 0;
esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ctx)
{
if (s_vfs_count >= VFS_MAX_COUNT) {
return ESP_ERR_NO_MEM;
}
size_t len = strlen(base_path);
if (len < 2 || len > ESP_VFS_PATH_MAX) {
return ESP_ERR_INVALID_ARG;
@ -68,16 +65,41 @@ esp_err_t esp_vfs_register(const char* base_path, const esp_vfs_t* vfs, void* ct
if (entry == NULL) {
return ESP_ERR_NO_MEM;
}
size_t index;
for (index = 0; index < s_vfs_count; ++index) {
if (s_vfs[index] == NULL) {
break;
}
}
if (index == s_vfs_count) {
if (s_vfs_count >= VFS_MAX_COUNT) {
free(entry);
return ESP_ERR_NO_MEM;
}
++s_vfs_count;
}
s_vfs[index] = entry;
strcpy(entry->path_prefix, base_path); // we have already verified argument length
memcpy(&entry->vfs, vfs, sizeof(esp_vfs_t));
entry->path_prefix_len = len;
entry->ctx = ctx;
entry->offset = s_vfs_count;
s_vfs[s_vfs_count] = entry;
++s_vfs_count;
entry->offset = index;
return ESP_OK;
}
esp_err_t esp_vfs_unregister(const char* base_path)
{
for (size_t i = 0; i < s_vfs_count; ++i) {
vfs_entry_t* vfs = s_vfs[i];
if (memcmp(base_path, vfs->path_prefix, vfs->path_prefix_len) == 0) {
free(vfs);
s_vfs[i] = NULL;
return ESP_OK;
}
}
return ESP_ERR_INVALID_STATE;
}
static const vfs_entry_t* get_vfs_for_fd(int fd)
{
int index = ((fd & VFS_INDEX_MASK) >> VFS_INDEX_S);
@ -141,6 +163,28 @@ static const vfs_entry_t* get_vfs_for_path(const char* path)
}
#define CHECK_AND_CALLV(r, pvfs, func, ...) \
if (pvfs->vfs.func == NULL) { \
__errno_r(r) = ENOSYS; \
return; \
} \
if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \
(*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \
} else { \
(*pvfs->vfs.func)(__VA_ARGS__);\
}
#define CHECK_AND_CALLP(ret, r, pvfs, func, ...) \
if (pvfs->vfs.func == NULL) { \
__errno_r(r) = ENOSYS; \
return NULL; \
} \
if (pvfs->vfs.flags & ESP_VFS_FLAG_CONTEXT_PTR) { \
ret = (*pvfs->vfs.func ## _p)(pvfs->ctx, __VA_ARGS__); \
} else { \
ret = (*pvfs->vfs.func)(__VA_ARGS__);\
}
int esp_vfs_open(struct _reent *r, const char * path, int flags, int mode)
{
const vfs_entry_t* vfs = get_vfs_for_path(path);
@ -287,3 +331,116 @@ int esp_vfs_rename(struct _reent *r, const char *src, const char *dst)
CHECK_AND_CALL(ret, r, vfs, rename, src_within_vfs, dst_within_vfs);
return ret;
}
DIR* opendir(const char* name)
{
const vfs_entry_t* vfs = get_vfs_for_path(name);
struct _reent* r = __getreent();
if (vfs == NULL) {
__errno_r(r) = ENOENT;
return NULL;
}
const char* path_within_vfs = translate_path(vfs, name);
DIR* ret;
CHECK_AND_CALLP(ret, r, vfs, opendir, path_within_vfs);
if (ret != NULL) {
ret->dd_vfs_idx = vfs->offset << VFS_INDEX_S;
}
return ret;
}
struct dirent* readdir(DIR* pdir)
{
const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx);
struct _reent* r = __getreent();
if (vfs == NULL) {
__errno_r(r) = EBADF;
return NULL;
}
struct dirent* ret;
CHECK_AND_CALLP(ret, r, vfs, readdir, pdir);
return ret;
}
int readdir_r(DIR* pdir, struct dirent* entry, struct dirent** out_dirent)
{
const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx);
struct _reent* r = __getreent();
if (vfs == NULL) {
errno = EBADF;
return -1;
}
int ret;
CHECK_AND_CALL(ret, r, vfs, readdir_r, pdir, entry, out_dirent);
return ret;
}
long telldir(DIR* pdir)
{
const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx);
struct _reent* r = __getreent();
if (vfs == NULL) {
errno = EBADF;
return -1;
}
long ret;
CHECK_AND_CALL(ret, r, vfs, telldir, pdir);
return ret;
}
void seekdir(DIR* pdir, long loc)
{
const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx);
struct _reent* r = __getreent();
if (vfs == NULL) {
errno = EBADF;
return;
}
CHECK_AND_CALLV(r, vfs, seekdir, pdir, loc);
}
void rewinddir(DIR* pdir)
{
seekdir(pdir, 0);
}
int closedir(DIR* pdir)
{
const vfs_entry_t* vfs = get_vfs_for_fd(pdir->dd_vfs_idx);
struct _reent* r = __getreent();
if (vfs == NULL) {
errno = EBADF;
return -1;
}
int ret;
CHECK_AND_CALL(ret, r, vfs, closedir, pdir);
return ret;
}
int mkdir(const char* name, mode_t mode)
{
const vfs_entry_t* vfs = get_vfs_for_path(name);
struct _reent* r = __getreent();
if (vfs == NULL) {
__errno_r(r) = ENOENT;
return -1;
}
const char* path_within_vfs = translate_path(vfs, name);
int ret;
CHECK_AND_CALL(ret, r, vfs, mkdir, path_within_vfs, mode);
return ret;
}
int rmdir(const char* name)
{
const vfs_entry_t* vfs = get_vfs_for_path(name);
struct _reent* r = __getreent();
if (vfs == NULL) {
__errno_r(r) = ENOENT;
return -1;
}
const char* path_within_vfs = translate_path(vfs, name);
int ret;
CHECK_AND_CALL(ret, r, vfs, rmdir, path_within_vfs);
return ret;
}

View File

@ -32,7 +32,10 @@ INPUT = ../components/esp32/include/esp_wifi.h \
../components/esp32/include/esp_heap_alloc_caps.h \
../components/freertos/include/freertos/heap_regions.h \
../components/esp32/include/esp_smartconfig.h \
../components/esp32/include/esp_deep_sleep.h
../components/esp32/include/esp_deep_sleep.h \
../components/sdmmc/include/sdmmc_cmd.h \
../components/fatfs/src/esp_vfs_fat.h \
../components/fatfs/src/diskio.h
## Get warnings for functions that have no documentation for their parameters or return value
##

64
docs/api/fatfs.rst Normal file
View File

@ -0,0 +1,64 @@
FAT Filesystem Support
======================
ESP-IDF uses `FatFs <http://elm-chan.org/fsw/ff/00index_e.html>`_ library to work with FAT filesystems. FatFs library resides in ``fatfs`` component. Although it can be used directly, many of its features can be accessed via VFS using C standard library and POSIX APIs.
Additionally, FatFs has been modified to support run-time pluggable disk IO layer. This allows mapping of FatFs drives to physical disks at run-time.
Using FatFs with VFS
--------------------
``esp_vfs_fat.h`` header file defines functions to connect FatFs with VFS. ``esp_vfs_fat_register`` function allocates a ``FATFS`` structure, and registers a given path prefix in VFS. Subsequent operations on files starting with this prefix are forwarded to FatFs APIs. ``esp_vfs_fat_unregister`` function deletes the registration with VFS, and frees the ``FATFS`` structure.
Most applications will use the following flow when working with ``esp_vfs_fat_`` functions:
1. Call ``esp_vfs_fat_register``, specifying path prefix where the filesystem has to be mounted (e.g. ``"/sdcard"``), FatFs drive number, and a variable which will receive a pointer to ``FATFS`` structure.
2. Call ``ff_diskio_register`` function to register disk IO driver for the drive number used in step 1.
3. Call ``f_mount`` function (and optionally ``f_fdisk``, ``f_mkfs``) to mount the filesystem using the same drive number which was passed to ``esp_vfs_fat_register``. See FatFs documentation for more details.
4. Call POSIX and C standard library functions to open, read, write, erase, copy files, etc. Use paths starting with the prefix passed to ``esp_vfs_register`` (such as ``"/sdcard/hello.txt"``).
5. Optionally, call FatFs library functions directly. Use paths without a VFS prefix in this case (``"/hello.txt"``).
6. Close all open files.
7. Call ``f_mount`` function for the same drive number, with NULL ``FATFS*`` argument, to unmount the filesystem.
8. Call ``ff_diskio_register`` with NULL ``ff_diskio_impl_t*`` argument and the same drive number.
9. Call ``esp_vfs_fat_unregister`` to remove FatFs from VFS, and free the ``FATFS`` structure allocated on step 1.
Convenience functions, ``esp_vfs_fat_sdmmc_mount`` and ``esp_vfs_fat_sdmmc_unmount``, which wrap these steps and also handle SD card initialization, are described in the next section.
.. doxygenfunction:: esp_vfs_fat_register
.. doxygenfunction:: esp_vfs_fat_unregister
Using FatFs with VFS and SD cards
---------------------------------
``esp_vfs_fat.h`` header file also provides a convenience function to perform steps 13 and 79, and also handle SD card initialization: ``esp_vfs_fat_sdmmc_mount``. This function does only limited error handling. Developers are encouraged to look at its source code and incorporate more advanced versions into production applications. ``esp_vfs_fat_sdmmc_unmount`` function unmounts the filesystem and releases resources acquired by ``esp_vfs_fat_sdmmc_mount``.
.. doxygenfunction:: esp_vfs_fat_sdmmc_mount
.. doxygenstruct:: esp_vfs_fat_sdmmc_mount_config_t
:members:
.. doxygenfunction:: esp_vfs_fat_sdmmc_unmount
FatFS disk IO layer
-------------------
FatFs has been extended with an API to register disk IO driver at runtime.
Implementation of disk IO functions for SD/MMC cards is provided. It can be registered for the given FatFs drive number using ``ff_diskio_register_sdmmc`` function.
.. doxygenfunction:: ff_diskio_register
.. doxygenstruct:: ff_diskio_impl_t
:members:
.. doxygenfunction:: ff_diskio_register_sdmmc

95
docs/api/sdmmc.rst Normal file
View File

@ -0,0 +1,95 @@
SDMMC Host Peripheral
=====================
Overview
--------
SDMMC peripheral supports SD and MMC memory cards and SDIO cards. SDMMC software builds on top of SDMMC driver and consists of the following parts:
1. SDMMC host driver (``driver/sdmmc_host.h``) — this driver provides APIs to send commands to the slave device(s), send and receive data, and handling error conditions on the bus.
2. SDMMC protocol layer (``sdmmc_cmd.h``) — this component handles specifics of SD protocol such as card initialization and data transfer commands. Despite the name, only SD (SDSC/SDHC/SDXC) cards are supported at the moment. Support for MCC/eMMC cards can be added in the future.
Protocol layer works with the host via ``sdmmc_host_t`` structure. This structure contains pointers to various functions of the host. This design makes it possible to implement an SD host using SPI interface later.
Application Example
-------------------
An example which combines SDMMC driver with FATFS library is provided in ``examples/24_sd_card`` directory. This example initializes the card, writes and reads data from it using POSIX and C library APIs. See README.md file in the example directory for more information.
Protocol layer APIs
-------------------
Protocol layer is given ``sdmmc_host_t`` structure which describes the SD/MMC host driver, lists its capabilites, and provides pointers to functions of the driver. Protocol layer stores card-specific information in ``sdmmc_card_t`` structure. When sending commands to the SD/MMC host driver, protocol layer uses ``sdmmc_command_t`` structure to describe the command, argument, expected return value, and data to transfer, if any.
Normal usage of the protocol layer is as follows:
1. Call the host driver functions to initialize the host (e.g. ``sdmmc_host_init``, ``sdmmc_host_init_slot``).
2. Call ``sdmmc_card_init`` to initialize the card, passing it host driver information (``host``) and a pointer to ``sdmmc_card_t`` structure which will be filled in (``card``).
3. To read and write sectors of the card, use ``sdmmc_read_sectors`` and ``sdmmc_write_sectors``, passing the pointer to card information structure (``card``).
4. When card is not used anymore, call the host driver function to disable SDMMC host peripheral and free resources allocated by the driver (e.g. ``sdmmc_host_deinit``).
Most applications need to use the protocol layer only in one task; therefore the protocol layer doesn't implement any kind of locking on the ``sdmmc_card_t`` structure, or when accessing SDMMC host driver. Such locking has to be implemented in the higher layer, if necessary (e.g. in the filesystem driver).
.. doxygenstruct:: sdmmc_host_t
:members:
.. doxygendefine:: SDMMC_HOST_FLAG_1BIT
.. doxygendefine:: SDMMC_HOST_FLAG_4BIT
.. doxygendefine:: SDMMC_HOST_FLAG_8BIT
.. doxygendefine:: SDMMC_HOST_FLAG_SPI
.. doxygendefine:: SDMMC_FREQ_DEFAULT
.. doxygendefine:: SDMMC_FREQ_HIGHSPEED
.. doxygendefine:: SDMMC_FREQ_PROBING
.. doxygenstruct:: sdmmc_command_t
:members:
.. doxygenstruct:: sdmmc_card_t
:members:
.. doxygenstruct:: sdmmc_csd_t
:members:
.. doxygenstruct:: sdmmc_cid_t
:members:
.. doxygenstruct:: sdmmc_scr_t
:members:
.. doxygenfunction:: sdmmc_card_init
.. doxygenfunction:: sdmmc_write_sectors
.. doxygenfunction:: sdmmc_read_sectors
SDMMC host driver APIs
----------------------
On the ESP32, SDMMC host peripheral has two slots:
- Slot 0 (``SDMMC_HOST_SLOT_0``) is an 8-bit slot. It uses ``HS1_*`` signals in the PIN MUX.
- Slot 1 (``SDMMC_HOST_SLOT_1``) is a 4-bit slot. It uses ``HS2_*`` signals in the PIN MUX.
Card Detect and Write Protect signals can be routed to arbitrary pins using GPIO matrix. To use these pins, set ``gpio_cd`` and ``gpio_wp`` members of ``sdmmc_slot_config_t`` structure when calling ``sdmmc_host_init_slot``.
Of all the funtions listed below, only ``sdmmc_host_init``, ``sdmmc_host_init_slot``, and ``sdmmc_host_deinit`` will be used directly by most applications. Other functions, such as ``sdmmc_host_set_bus_width``, ``sdmmc_host_set_card_clk``, and ``sdmmc_host_do_transaction`` will be called by the SD/MMC protocol layer via function pointers in ``sdmmc_host_t`` structure.
.. doxygenfunction:: sdmmc_host_init
.. doxygendefine:: SDMMC_HOST_SLOT_0
.. doxygendefine:: SDMMC_HOST_SLOT_1
.. doxygendefine:: SDMMC_HOST_DEFAULT
.. doxygenfunction:: sdmmc_host_init_slot
.. doxygenstruct:: sdmmc_slot_config_t
:members:
.. doxygendefine:: SDMMC_SLOT_NO_CD
.. doxygendefine:: SDMMC_SLOT_NO_WP
.. doxygendefine:: SDMMC_SLOT_CONFIG_DEFAULT
.. doxygenfunction:: sdmmc_host_set_bus_width
.. doxygenfunction:: sdmmc_host_set_card_clk
.. doxygenfunction:: sdmmc_host_do_transaction
.. doxygenfunction:: sdmmc_host_deinit

View File

@ -31,6 +31,7 @@ Functions
^^^^^^^^^
.. doxygenfunction:: esp_vfs_register
.. doxygenfunction:: esp_vfs_unregister
.. doxygenfunction:: esp_vfs_write
.. doxygenfunction:: esp_vfs_lseek
.. doxygenfunction:: esp_vfs_read

View File

@ -110,11 +110,13 @@ Contents:
I2C <api/i2c>
Pulse Counter <api/pcnt>
Sigma-delta Modulation <api/sigmadelta>
SD/MMC <api/sdmmc>
SPI Flash and Partition APIs <api/spi_flash>
SPI Master API <api/spi_master>
Logging <api/log>
Non-Volatile Storage <api/nvs_flash>
Virtual Filesystem <api/vfs>
FAT Filesystem <api/fatfs>
Ethernet <api/esp_eth>
Interrupt Allocation <api/intr_alloc>
Memory Allocation <api/mem_alloc>

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := sd_card
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,81 @@
# SD Card example
This example demonstrates how to use an SD card with ESP32. Example does the following steps:
1. Use an "all-in-one" `esp_vfs_fat_sdmmc_mount` function to:
- initialize SDMMC peripheral,
- probe and initialize the card connected to SD/MMC slot 1 (HS2_CMD, HS2_CLK, HS2_D0, HS2_D1, HS2_D2, HS2_D3 lines),
- mount FAT filesystem using FATFS library (and format card, if the filesystem can not be mounted),
- register FAT filesystem in VFS, enabling C standard library and POSIX functions to be used.
2. Print information about the card, such as name, type, capacity, and maximum supported frequency.
3. Create a file using `fopen` and write to it using `fprintf`.
4. Rename the file. Before renaming, check if destination file already exists using `stat` function, and remove it using `unlink` function.
5. Open renamed file for reading, read back the line, and print it to the terminal.
*Note:* despite the name, `sdmmc` component doesn't support MMC/eMMC cards yet. It is also possible to extend `sdmmc` component to support SPI mode with SD cards via SPI peripheral.
## Hardware
To run this example, ESP32 development board needs to be connected to SD card as follows:
ESP32 pin | SD card pin | Notes
--------------|-------------|------------
GPIO14 (MTMS) | CLK | 10k pullup
GPIO15 (MTDO) | CMD | 10k pullup
GPIO2 | D0 | 10k pullup, pull low to go into download mode
GPIO4 | D1 | 10k pullup; not used in 1-line mode
GPIO12 (MTDI) | D2 | otherwise 10k pullup (see note below!); not used in 1-line mode
GPIO13 (MTCK) | D3 | 10k pullup needed at card side, even in 1-line mode
N/C | CD |
N/C | WP |
This example doesn't utilize card detect (CD) and write protect (WP) signals from SD card slot.
### Note about GPIO2
GPIO2 pin is used as a bootstrapping pin, and should be low to enter UART download mode. One way to do this is to connect GPIO0 and GPIO2 using a jumper, and then the auto-reset circuit on most development boards will pull GPIO2 low along with GPIO2, when entering download mode.
### Note about GPIO12
GPIO12 is used as a bootstrapping pin to select output voltage of an internal regulator which powers the flash chip.
- For boards which don't use the internal regulator, GPIO12 can be pulled high.
- On boards which use the internal regulator and a 3.3V flash chip, GPIO12 should be pulled up high, which is compatible with SD card operation.
- For boards which use 1.8V flash chip, GPIO12 needs to be low at reset.
* In this case, internal pullup can be enabled using a `gpio_pullup_en(GPIO_NUM_12);` call. Most SD cards work fine when an internal pullup on GPIO12 line is enabled. Note that if ESP32 experiences a power-on reset while the SD card is sending data, high level on GPIO12 can be latched into the bootstrapping register, and ESP32 will enter a boot loop until external reset with correct GPIO12 level is applied.
* Another option is to program flash voltage selection efuses: set `SDIO_TIEH=0` and `SDIO_FORCE=1`. This will permanently select 1.8v output voltage for the internal regulator, and GPIO12 will not be used as a bootstrapping pin anymore. Then it is safe to connect a pullup resistor to GPIO12. This option is suggested for production use.
## 4-line and 1-line modes
By default, example code uses the following initializer for SDMMC host peripheral configuration:
```c++
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
```
Among other things, this sets `host.flags` to `SDMMC_HOST_FLAG_4BIT`, which means that SD/MMC driver will switch to 4-line mode when initializing the card (initial communication always happens in 1-line mode). If some of D1, D2, D3 pins are not connected to the card, set `host.flags` to `SDMMC_HOST_FLAG_1BIT` — then the SD/MMC driver will not attempt to switch to 4-line mode.
Note that even if D3 line is not connected to the ESP32, it still has to be pulled up at card side, otherwise the card will go into SPI protocol mode.
## Example output
Here is an example console output. In this case a 128MB SDSC card was connected, and `format_if_mount_failed` parameter was set to `true` in the source code. Card was unformatted, so the initial mount has failed. Card was then partitioned, formatted, and mounted again.
```
I (1776) example: Initializing SD card
W (1856) vfs_fat_sdmmc: failed to mount card (13)
W (1856) vfs_fat_sdmmc: partitioning card
W (1856) vfs_fat_sdmmc: formatting card
W (2726) vfs_fat_sdmmc: mounting again
I (2736) example: Card info:
I (2736) example: Name: SU128
I (2736) example: Type: SDSC
I (2736) example: Capacity: 120 MB
I (2736) example: Max clock speed: 25 MHz
I (2736) example: Opening file
I (2756) example: File written
I (2756) example: Renaming file
I (2756) example: Reading file
I (2756) example: Read from file: 'Hello SU128!'
I (2756) example: Card unmounted
```

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,107 @@
/* SD card and FAT filesystem example.
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_vfs_fat.h"
#include "driver/sdmmc_host.h"
#include "driver/sdmmc_defs.h"
#include "sdmmc_cmd.h"
static const char* TAG = "example";
void app_main(void)
{
ESP_LOGI(TAG, "Initializing SD card");
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
// To use 1-line SD mode, uncomment the following line:
// host.flags = SDMMC_HOST_FLAG_1BIT;
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
// Options for mounting the filesystem.
// If format_if_mount_failed is set to true, SD card will be partitioned and formatted
// in case when mounting fails.
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5
};
// Use settings defined above to initialize SD card and mount FAT filesystem.
// Note: esp_vfs_fat_sdmmc_mount is an all-in-one convenience function.
// Please check its source code and implement error recovery when developing
// production applications.
sdmmc_card_t* card;
esp_err_t ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem. If you want the card to be formatted, set format_if_mount_failed = true.");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%d). Make sure SD card lines have pull-up resistors in place.", ret);
}
return;
}
// Card has been initialized, print its properties
sdmmc_card_print_info(stdout, card);
// Use POSIX and C standard library functions to work with files.
// First create a file.
ESP_LOGI(TAG, "Opening file");
FILE* f = fopen("/sdcard/hello.txt", "w");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for writing");
return;
}
fprintf(f, "Hello %s!\n", card->cid.name);
fclose(f);
ESP_LOGI(TAG, "File written");
// Check if destination file exists before renaming
struct stat st;
if (stat("/sdcard/foo.txt", &st) == 0) {
// Delete it if it exists
unlink("/sdcard/foo.txt");
}
// Rename original file
ESP_LOGI(TAG, "Renaming file");
if (rename("/sdcard/hello.txt", "/sdcard/foo.txt") != 0) {
ESP_LOGE(TAG, "Rename failed");
return;
}
// Open renamed file for reading
ESP_LOGI(TAG, "Reading file");
f = fopen("/sdcard/foo.txt", "r");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for reading");
return;
}
char line[64];
fgets(line, sizeof(line), f);
fclose(f);
// strip newline
char* pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGI(TAG, "Read from file: '%s'", line);
// All done, unmount partition and disable SDMMC host peripheral
esp_vfs_fat_sdmmc_unmount();
ESP_LOGI(TAG, "Card unmounted");
}