mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'feat/sdspi_polling_spi' into 'master'
sdspi: allow using sdspi with other devices on the same bus See merge request espressif/esp-idf!3177
This commit is contained in:
commit
8d8337e80c
@ -130,6 +130,7 @@ typedef struct {
|
||||
#define SDMMC_HOST_FLAG_8BIT BIT(2) /*!< host supports 8-line MMC protocol */
|
||||
#define SDMMC_HOST_FLAG_SPI BIT(3) /*!< host supports SPI protocol */
|
||||
#define SDMMC_HOST_FLAG_DDR BIT(4) /*!< host supports DDR mode for SD/MMC */
|
||||
#define SDMMC_HOST_FLAG_DEINIT_ARG BIT(5) /*!< host `deinit` function called with the slot argument */
|
||||
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) */
|
||||
@ -144,7 +145,10 @@ typedef struct {
|
||||
esp_err_t (*set_bus_ddr_mode)(int slot, bool ddr_enable); /*!< host function to set DDR mode */
|
||||
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 */
|
||||
union {
|
||||
esp_err_t (*deinit)(void); /*!< host function to deinitialize the driver */
|
||||
esp_err_t (*deinit_p)(int slot); /*!< host function to deinitialize the driver, called with the `slot` */
|
||||
};
|
||||
esp_err_t (*io_int_enable)(int slot); /*!< Host function to enable SDIO interrupt line */
|
||||
esp_err_t (*io_int_wait)(int slot, TickType_t timeout_ticks); /*!< Host function to wait for SDIO interrupt line to be active */
|
||||
int command_timeout_ms; /*!< timeout, in milliseconds, of a single command. Set to 0 to use the default value. */
|
||||
|
@ -25,15 +25,18 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Handle representing an SD SPI device
|
||||
typedef int sdspi_dev_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Default sdmmc_host_t structure initializer for SD over SPI driver
|
||||
*
|
||||
* Uses SPI mode and max frequency set to 20MHz
|
||||
*
|
||||
* 'slot' can be set to one of HSPI_HOST, VSPI_HOST.
|
||||
* 'slot' should be set to an sdspi device initialized by `sdspi_host_init_device()`.
|
||||
*/
|
||||
#define SDSPI_HOST_DEFAULT() {\
|
||||
.flags = SDMMC_HOST_FLAG_SPI, \
|
||||
.flags = SDMMC_HOST_FLAG_SPI | SDMMC_HOST_FLAG_DEINIT_ARG, \
|
||||
.slot = HSPI_HOST, \
|
||||
.max_freq_khz = SDMMC_FREQ_DEFAULT, \
|
||||
.io_voltage = 3.3f, \
|
||||
@ -43,42 +46,36 @@ extern "C" {
|
||||
.set_bus_ddr_mode = NULL, \
|
||||
.set_card_clk = &sdspi_host_set_card_clk, \
|
||||
.do_transaction = &sdspi_host_do_transaction, \
|
||||
.deinit = &sdspi_host_deinit, \
|
||||
.deinit_p = &sdspi_host_remove_device, \
|
||||
.io_int_enable = &sdspi_host_io_int_enable, \
|
||||
.io_int_wait = &sdspi_host_io_int_wait, \
|
||||
.command_timeout_ms = 0, \
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra configuration for SPI host
|
||||
* Extra configuration for SD SPI device.
|
||||
*/
|
||||
typedef struct {
|
||||
gpio_num_t gpio_miso; ///< GPIO number of MISO signal
|
||||
gpio_num_t gpio_mosi; ///< GPIO number of MOSI signal
|
||||
gpio_num_t gpio_sck; ///< GPIO number of SCK signal
|
||||
spi_host_device_t host_id; ///< SPI host to use, SPIx_HOST (see spi_types.h).
|
||||
gpio_num_t gpio_cs; ///< GPIO number of CS signal
|
||||
gpio_num_t gpio_cd; ///< GPIO number of card detect signal
|
||||
gpio_num_t gpio_wp; ///< GPIO number of write protect signal
|
||||
gpio_num_t gpio_int; ///< GPIO number of interrupt line (input) for SDIO card.
|
||||
int dma_channel; ///< DMA channel to be used by SPI driver (1 or 2)
|
||||
} sdspi_slot_config_t;
|
||||
} sdspi_device_config_t;
|
||||
|
||||
#define SDSPI_SLOT_NO_CD GPIO_NUM_NC ///< indicates that card detect line is not used
|
||||
#define SDSPI_SLOT_NO_WP GPIO_NUM_NC ///< indicates that write protect line is not used
|
||||
#define SDSPI_SLOT_NO_INT GPIO_NUM_NC ///< indicates that interrupt line is not used
|
||||
|
||||
/**
|
||||
* Macro defining default configuration of SPI host
|
||||
* Macro defining default configuration of SD SPI device.
|
||||
*/
|
||||
#define SDSPI_SLOT_CONFIG_DEFAULT() {\
|
||||
.gpio_miso = GPIO_NUM_2, \
|
||||
.gpio_mosi = GPIO_NUM_15, \
|
||||
.gpio_sck = GPIO_NUM_14, \
|
||||
#define SDSPI_DEVICE_CONFIG_DEFAULT() {\
|
||||
.host_id = HSPI_HOST, \
|
||||
.gpio_cs = GPIO_NUM_13, \
|
||||
.gpio_cd = SDSPI_SLOT_NO_CD, \
|
||||
.gpio_wp = SDSPI_SLOT_NO_WP, \
|
||||
.gpio_int = GPIO_NUM_NC, \
|
||||
.dma_channel = 1 \
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,22 +90,32 @@ typedef struct {
|
||||
esp_err_t sdspi_host_init(void);
|
||||
|
||||
/**
|
||||
* @brief Initialize SD SPI driver for the specific SPI controller
|
||||
* @brief Attach and initialize an SD SPI device on the specific SPI bus
|
||||
*
|
||||
* @note This function is not thread safe
|
||||
*
|
||||
* @note Initialize the SPI bus by `spi_bus_initialize()` before calling this function.
|
||||
*
|
||||
* @note The SDIO over sdspi needs an extra interrupt line. Call ``gpio_install_isr_service()`` before this function.
|
||||
*
|
||||
* @param slot SPI controller to use (HSPI_HOST or VSPI_HOST)
|
||||
* @param slot_config pointer to slot configuration structure
|
||||
*
|
||||
* @param dev_config pointer to device configuration structure
|
||||
* @param out_handle Output of the handle to the sdspi device.
|
||||
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if sdspi_init_slot has invalid arguments
|
||||
* - ESP_ERR_INVALID_ARG if sdspi_host_init_device has invalid arguments
|
||||
* - ESP_ERR_NO_MEM if memory can not be allocated
|
||||
* - other errors from the underlying spi_master and gpio drivers
|
||||
*/
|
||||
esp_err_t sdspi_host_init_slot(int slot, const sdspi_slot_config_t* slot_config);
|
||||
esp_err_t sdspi_host_init_device(const sdspi_device_config_t* dev_config, sdspi_dev_handle_t* out_handle);
|
||||
|
||||
/**
|
||||
* @brief Remove an SD SPI device
|
||||
*
|
||||
* @param handle Handle of the SD SPI device
|
||||
* @return Always ESP_OK
|
||||
*/
|
||||
esp_err_t sdspi_host_remove_device(sdspi_dev_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Send command to the card and get response
|
||||
@ -121,7 +128,7 @@ esp_err_t sdspi_host_init_slot(int slot, const sdspi_slot_config_t* slot_config)
|
||||
* can call sdspi_host_do_transaction as long as other sdspi_host_*
|
||||
* functions are not called.
|
||||
*
|
||||
* @param slot SPI controller (HSPI_HOST or VSPI_HOST)
|
||||
* @param handle Handle of the sdspi device
|
||||
* @param cmdinfo pointer to structure describing command and data to transfer
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
@ -129,7 +136,7 @@ esp_err_t sdspi_host_init_slot(int slot, const sdspi_slot_config_t* slot_config)
|
||||
* - 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 sdspi_host_do_transaction(int slot, sdmmc_command_t *cmdinfo);
|
||||
esp_err_t sdspi_host_do_transaction(sdspi_dev_handle_t handle, sdmmc_command_t *cmdinfo);
|
||||
|
||||
/**
|
||||
* @brief Set card clock frequency
|
||||
@ -140,14 +147,13 @@ esp_err_t sdspi_host_do_transaction(int slot, sdmmc_command_t *cmdinfo);
|
||||
*
|
||||
* @note This function is not thread safe
|
||||
*
|
||||
* @param slot SPI controller (HSPI_HOST or VSPI_HOST)
|
||||
* @param host Handle of the sdspi device
|
||||
* @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 sdspi_host_set_card_clk(int slot, uint32_t freq_khz);
|
||||
|
||||
esp_err_t sdspi_host_set_card_clk(sdspi_dev_handle_t host, uint32_t freq_khz);
|
||||
|
||||
/**
|
||||
* @brief Release resources allocated using sdspi_host_init
|
||||
@ -163,23 +169,77 @@ esp_err_t sdspi_host_deinit(void);
|
||||
/**
|
||||
* @brief Enable SDIO interrupt.
|
||||
*
|
||||
* @param slot SPI controller to use (HSPI_HOST or VSPI_HOST)
|
||||
* @param handle Handle of the sdspi device
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
*/
|
||||
esp_err_t sdspi_host_io_int_enable(int slot);
|
||||
esp_err_t sdspi_host_io_int_enable(sdspi_dev_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Wait for SDIO interrupt until timeout.
|
||||
*
|
||||
* @param slot SPI controller to use (HSPI_HOST or VSPI_HOST)
|
||||
* @param handle Handle of the sdspi device
|
||||
* @param timeout_ticks Ticks to wait before timeout.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
*/
|
||||
esp_err_t sdspi_host_io_int_wait(int slot, TickType_t timeout_ticks);
|
||||
esp_err_t sdspi_host_io_int_wait(sdspi_dev_handle_t handle, TickType_t timeout_ticks);
|
||||
|
||||
/*******************************************************************************
|
||||
* Deprecated APIs
|
||||
******************************************************************************/
|
||||
|
||||
/**
|
||||
* Extra configuration for SPI host.
|
||||
*
|
||||
* @deprecated Use `sdspi_device_config_t` and corresponding `sdspi_host_init_device()` instead.
|
||||
*/
|
||||
typedef struct {
|
||||
gpio_num_t gpio_cs; ///< GPIO number of CS signal
|
||||
gpio_num_t gpio_cd; ///< GPIO number of card detect signal
|
||||
gpio_num_t gpio_wp; ///< GPIO number of write protect signal
|
||||
gpio_num_t gpio_int; ///< GPIO number of interrupt line (input) for SDIO card.
|
||||
gpio_num_t gpio_miso; ///< GPIO number of MISO signal.
|
||||
gpio_num_t gpio_mosi; ///< GPIO number of MOSI signal.
|
||||
gpio_num_t gpio_sck; ///< GPIO number of SCK signal.
|
||||
int dma_channel; ///< DMA channel to be used by SPI driver (1 or 2).
|
||||
} sdspi_slot_config_t;
|
||||
|
||||
/**
|
||||
* Macro defining default configuration of SPI host
|
||||
*/
|
||||
#define SDSPI_SLOT_CONFIG_DEFAULT() {\
|
||||
.gpio_cs = GPIO_NUM_13, \
|
||||
.gpio_cd = SDSPI_SLOT_NO_CD, \
|
||||
.gpio_wp = SDSPI_SLOT_NO_WP, \
|
||||
.gpio_int = GPIO_NUM_NC, \
|
||||
.gpio_miso = GPIO_NUM_2, \
|
||||
.gpio_mosi = GPIO_NUM_15, \
|
||||
.gpio_sck = GPIO_NUM_14, \
|
||||
.dma_channel = 1, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize SD SPI driver for the specific SPI controller
|
||||
*
|
||||
* @note This function is not thread safe
|
||||
*
|
||||
* @note The SDIO over sdspi needs an extra interrupt line. Call ``gpio_install_isr_service()`` before this function.
|
||||
*
|
||||
* @param slot SPI controller to use (HSPI_HOST or VSPI_HOST)
|
||||
* @param slot_config pointer to slot configuration structure
|
||||
|
||||
* @deprecated Use `sdspi_host_init_device` instead.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if sdspi_init_slot has invalid arguments
|
||||
* - ESP_ERR_NO_MEM if memory can not be allocated
|
||||
* - other errors from the underlying spi_master and gpio drivers
|
||||
*/
|
||||
esp_err_t sdspi_host_init_slot(int slot, const sdspi_slot_config_t* slot_config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -13,8 +13,15 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp32/rom/crc.h"
|
||||
#include "sdspi_crc.h"
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#include "esp32/rom/crc.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
// CRC16 not implemented in the ROM, will implement it below
|
||||
#else
|
||||
#error CRC16 not implemented yet! Choose from ROM or the impl below
|
||||
#endif
|
||||
|
||||
static const uint8_t crc7_table[256] =
|
||||
{
|
||||
@ -46,8 +53,39 @@ uint8_t sdspi_crc7(const uint8_t *data, size_t size)
|
||||
return result;
|
||||
}
|
||||
|
||||
#if !ESP_ROM_HAS_CRC16BE
|
||||
static uint16_t crc16_be_table[256] = {
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129,0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
|
||||
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318,0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
|
||||
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b,0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
|
||||
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a,0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
|
||||
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed,0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
|
||||
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
|
||||
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f,0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
|
||||
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe,0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
|
||||
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1,0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290,0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3,0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2,0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865,0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
|
||||
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54,0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
|
||||
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07,0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
|
||||
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36,0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
|
||||
};
|
||||
|
||||
uint16_t crc16_be(uint16_t crc, uint8_t const * buf,uint32_t len)
|
||||
{
|
||||
uint32_t i;
|
||||
crc = ~crc;
|
||||
for(i=0;i<len;i++){
|
||||
crc = crc16_be_table[(crc>>8)^buf[i]]^(crc<<8);
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
#endif //!ESP_ROM_HAS_CRC16BE
|
||||
|
||||
/// Return CRC16 of data, in the on-the-wire format used by SD protocol
|
||||
uint16_t sdspi_crc16(const uint8_t* data, size_t size)
|
||||
{
|
||||
return __builtin_bswap16(crc16_be(UINT16_MAX, data, size) ^ UINT16_MAX);
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "soc/soc_memory_layout.h"
|
||||
|
||||
|
||||
/// Max number of transactions in flight (used in start_command_write_blocks)
|
||||
@ -39,148 +40,148 @@
|
||||
/// Maximum number of dummy bytes between the request and response (minimum is 1)
|
||||
#define SDSPI_RESPONSE_MAX_DELAY 8
|
||||
|
||||
|
||||
/// Structure containing run time configuration for a single SD slot
|
||||
/**
|
||||
* @brief Structure containing run time configuration for a single SD slot
|
||||
*
|
||||
* The slot info is referenced to by an sdspi_dev_handle_t (alias int). The handle may be the raw
|
||||
* pointer to the slot info itself (force converted to, new API in IDFv4.2), or the index of the
|
||||
* s_slot array (deprecated API). Returning the raw pointer to the caller instead of storing it
|
||||
* locally can save some static memory.
|
||||
*/
|
||||
typedef struct {
|
||||
spi_device_handle_t handle; //!< SPI device handle, used for transactions
|
||||
spi_host_device_t host_id; //!< SPI host id.
|
||||
spi_device_handle_t spi_handle; //!< SPI device handle, used for transactions
|
||||
uint8_t gpio_cs; //!< CS GPIO
|
||||
uint8_t gpio_cd; //!< Card detect GPIO, or GPIO_UNUSED
|
||||
uint8_t gpio_wp; //!< Write protect GPIO, or GPIO_UNUSED
|
||||
uint8_t gpio_int; //!< Write protect GPIO, or GPIO_UNUSED
|
||||
/// Set to 1 if the higher layer has asked the card to enable CRC checks
|
||||
uint8_t data_crc_enabled : 1;
|
||||
/// Number of transactions in 'transactions' array which are in use
|
||||
uint8_t used_transaction_count: 3;
|
||||
/// Intermediate buffer used when application buffer is not in DMA memory;
|
||||
/// allocated on demand, SDSPI_BLOCK_BUF_SIZE bytes long. May be zero.
|
||||
uint8_t* block_buf;
|
||||
/// array with SDSPI_TRANSACTION_COUNT transaction structures
|
||||
spi_transaction_t* transactions;
|
||||
/// semaphore of gpio interrupt
|
||||
SemaphoreHandle_t semphr_int;
|
||||
} slot_info_t;
|
||||
|
||||
static slot_info_t s_slots[3];
|
||||
// Reserved for old API to be back-compatible
|
||||
static slot_info_t *s_slots[SOC_SPI_PERIPH_NUM] = {};
|
||||
static const char *TAG = "sdspi_host";
|
||||
|
||||
static const bool use_polling = true;
|
||||
static const bool no_use_polling = true;
|
||||
|
||||
|
||||
/// Functions to send out different kinds of commands
|
||||
static esp_err_t start_command_read_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
static esp_err_t start_command_read_blocks(slot_info_t *slot, sdspi_hw_cmd_t *cmd,
|
||||
uint8_t *data, uint32_t rx_length, bool need_stop_command);
|
||||
|
||||
static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
static esp_err_t start_command_write_blocks(slot_info_t *slot, sdspi_hw_cmd_t *cmd,
|
||||
const uint8_t *data, uint32_t tx_length, bool multi_block, bool stop_trans);
|
||||
|
||||
static esp_err_t start_command_default(int slot, int flags, sdspi_hw_cmd_t *cmd);
|
||||
static esp_err_t start_command_default(slot_info_t *slot, int flags, sdspi_hw_cmd_t *cmd);
|
||||
|
||||
static esp_err_t shift_cmd_response(sdspi_hw_cmd_t *cmd, int sent_bytes);
|
||||
|
||||
/// A few helper functions
|
||||
|
||||
/// Set CS high for given slot
|
||||
static void cs_high(int slot)
|
||||
/// Map handle to pointer of slot information
|
||||
static slot_info_t* get_slot_info(sdspi_dev_handle_t handle)
|
||||
{
|
||||
gpio_set_level(s_slots[slot].gpio_cs, 1);
|
||||
if ((uint32_t) handle < SOC_SPI_PERIPH_NUM) {
|
||||
return s_slots[handle];
|
||||
} else {
|
||||
return (slot_info_t *) handle;
|
||||
}
|
||||
}
|
||||
|
||||
/// Store slot information (if possible) and return corresponding handle
|
||||
static sdspi_dev_handle_t store_slot_info(slot_info_t *slot)
|
||||
{
|
||||
/*
|
||||
* To be back-compatible, the first device of each bus will always be stored locally, and
|
||||
* referenced to by the handle `host_id`, otherwise the new API return the raw pointer to the
|
||||
* slot info as the handle, to save some static memory.
|
||||
*/
|
||||
if (s_slots[slot->host_id] == NULL) {
|
||||
s_slots[slot->host_id] = slot;
|
||||
return slot->host_id;
|
||||
} else {
|
||||
return (sdspi_dev_handle_t)slot;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the slot info for a specific handle, and remove the local reference (if exist).
|
||||
static slot_info_t* remove_slot_info(sdspi_dev_handle_t handle)
|
||||
{
|
||||
if ((uint32_t) handle < SOC_SPI_PERIPH_NUM) {
|
||||
slot_info_t* slot = s_slots[handle];
|
||||
s_slots[handle] = NULL;
|
||||
return slot;
|
||||
} else {
|
||||
return (slot_info_t *) handle;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set CS high for given slot
|
||||
static void cs_high(slot_info_t *slot)
|
||||
{
|
||||
gpio_set_level(slot->gpio_cs, 1);
|
||||
}
|
||||
|
||||
/// Set CS low for given slot
|
||||
static void cs_low(int slot)
|
||||
static void cs_low(slot_info_t *slot)
|
||||
{
|
||||
gpio_set_level(s_slots[slot].gpio_cs, 0);
|
||||
gpio_set_level(slot->gpio_cs, 0);
|
||||
}
|
||||
|
||||
/// Return true if WP pin is configured and is low
|
||||
static bool card_write_protected(int slot)
|
||||
static bool card_write_protected(slot_info_t *slot)
|
||||
{
|
||||
if (s_slots[slot].gpio_wp == GPIO_UNUSED) {
|
||||
if (slot->gpio_wp == GPIO_UNUSED) {
|
||||
return false;
|
||||
}
|
||||
return gpio_get_level(s_slots[slot].gpio_wp) == 0;
|
||||
return gpio_get_level(slot->gpio_wp) == 0;
|
||||
}
|
||||
|
||||
/// Return true if CD pin is configured and is high
|
||||
static bool card_missing(int slot)
|
||||
static bool card_missing(slot_info_t *slot)
|
||||
{
|
||||
if (s_slots[slot].gpio_cd == GPIO_UNUSED) {
|
||||
if (slot->gpio_cd == GPIO_UNUSED) {
|
||||
return false;
|
||||
}
|
||||
return gpio_get_level(s_slots[slot].gpio_cd) == 1;
|
||||
}
|
||||
|
||||
/// Check if slot number is within bounds
|
||||
static bool is_valid_slot(int slot)
|
||||
{
|
||||
//SPI1 is not supported yet
|
||||
return slot == SPI2_HOST || slot == SPI3_HOST;
|
||||
}
|
||||
|
||||
static spi_device_handle_t spi_handle(int slot)
|
||||
{
|
||||
return s_slots[slot].handle;
|
||||
}
|
||||
|
||||
static bool is_slot_initialized(int slot)
|
||||
{
|
||||
return spi_handle(slot) != NULL;
|
||||
}
|
||||
|
||||
static bool data_crc_enabled(int slot)
|
||||
{
|
||||
return s_slots[slot].data_crc_enabled;
|
||||
return gpio_get_level(slot->gpio_cd) == 1;
|
||||
}
|
||||
|
||||
/// Get pointer to a block of DMA memory, allocate if necessary.
|
||||
/// This is used if the application provided buffer is not in DMA capable memory.
|
||||
static esp_err_t get_block_buf(int slot, uint8_t** out_buf)
|
||||
static esp_err_t get_block_buf(slot_info_t *slot, uint8_t **out_buf)
|
||||
{
|
||||
if (s_slots[slot].block_buf == NULL) {
|
||||
s_slots[slot].block_buf = heap_caps_malloc(SDSPI_BLOCK_BUF_SIZE, MALLOC_CAP_DMA);
|
||||
if (s_slots[slot].block_buf == NULL) {
|
||||
if (slot->block_buf == NULL) {
|
||||
slot->block_buf = heap_caps_malloc(SDSPI_BLOCK_BUF_SIZE, MALLOC_CAP_DMA);
|
||||
if (slot->block_buf == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
*out_buf = s_slots[slot].block_buf;
|
||||
*out_buf = slot->block_buf;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static spi_transaction_t* get_transaction(int slot)
|
||||
{
|
||||
size_t used_transaction_count = s_slots[slot].used_transaction_count;
|
||||
assert(used_transaction_count < SDSPI_TRANSACTION_COUNT);
|
||||
spi_transaction_t* ret = &s_slots[slot].transactions[used_transaction_count];
|
||||
++s_slots[slot].used_transaction_count;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void release_transaction(int slot)
|
||||
{
|
||||
--s_slots[slot].used_transaction_count;
|
||||
}
|
||||
|
||||
static void wait_for_transactions(int slot)
|
||||
{
|
||||
size_t used_transaction_count = s_slots[slot].used_transaction_count;
|
||||
for (size_t i = 0; i < used_transaction_count; ++i) {
|
||||
spi_transaction_t* t_out;
|
||||
spi_device_get_trans_result(spi_handle(slot), &t_out, portMAX_DELAY);
|
||||
release_transaction(slot);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clock out one byte (CS has to be high) to make the card release MISO
|
||||
/// (clocking one bit would work as well, but that triggers a bug in SPI DMA)
|
||||
static void release_bus(int slot)
|
||||
static void release_bus(slot_info_t *slot)
|
||||
{
|
||||
spi_transaction_t t = {
|
||||
.flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA,
|
||||
.length = 8,
|
||||
.tx_data = {0xff}
|
||||
};
|
||||
spi_device_transmit(spi_handle(slot), &t);
|
||||
spi_device_polling_transmit(slot->spi_handle, &t);
|
||||
// don't care if this failed
|
||||
}
|
||||
|
||||
/// Clock out 80 cycles (10 bytes) before GO_IDLE command
|
||||
static void go_idle_clockout(int slot)
|
||||
static void go_idle_clockout(slot_info_t *slot)
|
||||
{
|
||||
//actually we need 10, declare 12 to meet requirement of RXDMA
|
||||
uint8_t data[12];
|
||||
@ -190,30 +191,22 @@ static void go_idle_clockout(int slot)
|
||||
.tx_buffer = data,
|
||||
.rx_buffer = data,
|
||||
};
|
||||
spi_device_transmit(spi_handle(slot), &t);
|
||||
spi_device_polling_transmit(slot->spi_handle, &t);
|
||||
// don't care if this failed
|
||||
}
|
||||
|
||||
|
||||
/// Return true if the pointer can be used for DMA
|
||||
static bool ptr_dma_compatible(const void* ptr)
|
||||
{
|
||||
return (uintptr_t) ptr >= 0x3FFAE000 &&
|
||||
(uintptr_t) ptr < 0x40000000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize SPI device. Used to change clock speed.
|
||||
* @param slot SPI host number
|
||||
* (Re)Configure SPI device. Used to change clock speed.
|
||||
* @param slot Pointer to the slot to be configured
|
||||
* @param clock_speed_hz clock speed, Hz
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
static esp_err_t init_spi_dev(int slot, int clock_speed_hz)
|
||||
static esp_err_t configure_spi_dev(slot_info_t *slot, int clock_speed_hz)
|
||||
{
|
||||
if (spi_handle(slot)) {
|
||||
if (slot->spi_handle) {
|
||||
// Reinitializing
|
||||
spi_bus_remove_device(spi_handle(slot));
|
||||
s_slots[slot].handle = NULL;
|
||||
spi_bus_remove_device(slot->spi_handle);
|
||||
slot->spi_handle = NULL;
|
||||
}
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.clock_speed_hz = clock_speed_hz,
|
||||
@ -223,7 +216,7 @@ static esp_err_t init_spi_dev(int slot, int clock_speed_hz)
|
||||
.spics_io_num = GPIO_NUM_NC,
|
||||
.queue_size = SDSPI_TRANSACTION_COUNT,
|
||||
};
|
||||
return spi_bus_add_device((spi_host_device_t) slot, &devcfg, &s_slots[slot].handle);
|
||||
return spi_bus_add_device(slot->host_id, &devcfg, &slot->spi_handle);
|
||||
}
|
||||
|
||||
esp_err_t sdspi_host_init(void)
|
||||
@ -231,53 +224,79 @@ esp_err_t sdspi_host_init(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t deinit_slot(slot_info_t *slot)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
if (slot->spi_handle) {
|
||||
spi_bus_remove_device(slot->spi_handle);
|
||||
slot->spi_handle = NULL;
|
||||
free(slot->block_buf);
|
||||
slot->block_buf = NULL;
|
||||
}
|
||||
|
||||
uint64_t pin_bit_mask = 0;
|
||||
if (slot->gpio_cs != GPIO_UNUSED) {
|
||||
pin_bit_mask |= BIT64(slot->gpio_cs);
|
||||
}
|
||||
if (slot->gpio_cd != GPIO_UNUSED) {
|
||||
pin_bit_mask |= BIT64(slot->gpio_cd);
|
||||
}
|
||||
if (slot->gpio_wp != GPIO_UNUSED) {
|
||||
pin_bit_mask |= BIT64(slot->gpio_wp);
|
||||
}
|
||||
if (slot->gpio_int != GPIO_UNUSED) {
|
||||
pin_bit_mask |= BIT64(slot->gpio_int);
|
||||
gpio_intr_disable(slot->gpio_int);
|
||||
gpio_isr_handler_remove(slot->gpio_int);
|
||||
}
|
||||
gpio_config_t config = {
|
||||
.pin_bit_mask = pin_bit_mask,
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.intr_type = GPIO_PIN_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&config);
|
||||
|
||||
if (slot->semphr_int) {
|
||||
vSemaphoreDelete(slot->semphr_int);
|
||||
slot->semphr_int = NULL;
|
||||
}
|
||||
free(slot);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t sdspi_host_remove_device(sdspi_dev_handle_t handle)
|
||||
{
|
||||
//Get the slot info and remove the reference in the static memory (if used)
|
||||
slot_info_t* slot = remove_slot_info(handle);
|
||||
if (slot == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
deinit_slot(slot);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
//only the slots locally stored can be deinit in this function.
|
||||
esp_err_t sdspi_host_deinit(void)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(s_slots)/sizeof(s_slots[0]); ++i) {
|
||||
if (s_slots[i].handle) {
|
||||
spi_bus_remove_device(s_slots[i].handle);
|
||||
free(s_slots[i].block_buf);
|
||||
s_slots[i].block_buf = NULL;
|
||||
free(s_slots[i].transactions);
|
||||
s_slots[i].transactions = NULL;
|
||||
spi_bus_free((spi_host_device_t) i);
|
||||
s_slots[i].handle = NULL;
|
||||
slot_info_t* slot = remove_slot_info(i);
|
||||
//slot isn't used, skip
|
||||
if (slot == NULL) continue;
|
||||
|
||||
uint64_t pin_bit_mask = BIT64(s_slots[i].gpio_cs);
|
||||
if (s_slots[i].gpio_cd != GPIO_UNUSED) {
|
||||
pin_bit_mask |= BIT64(s_slots[i].gpio_cd);
|
||||
}
|
||||
if (s_slots[i].gpio_wp != GPIO_UNUSED) {
|
||||
pin_bit_mask |= BIT64(s_slots[i].gpio_wp);
|
||||
}
|
||||
if (s_slots[i].gpio_int != GPIO_UNUSED) {
|
||||
pin_bit_mask |= BIT64(s_slots[i].gpio_int);
|
||||
}
|
||||
|
||||
gpio_config_t config = {
|
||||
.pin_bit_mask = pin_bit_mask,
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
};
|
||||
gpio_config(&config);
|
||||
}
|
||||
if (s_slots[i].semphr_int) {
|
||||
vSemaphoreDelete(s_slots[i].semphr_int);
|
||||
s_slots[i].semphr_int = NULL;
|
||||
}
|
||||
deinit_slot(slot);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t sdspi_host_set_card_clk(int slot, uint32_t freq_khz)
|
||||
esp_err_t sdspi_host_set_card_clk(sdspi_dev_handle_t handle, uint32_t freq_khz)
|
||||
{
|
||||
if (!is_valid_slot(slot)) {
|
||||
slot_info_t *slot = get_slot_info(handle);
|
||||
if (slot == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!is_slot_initialized(slot)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
ESP_LOGD(TAG, "Setting card clock to %d kHz", freq_khz);
|
||||
return init_spi_dev(slot, freq_khz * 1000);
|
||||
return configure_spi_dev(slot, freq_khz * 1000);
|
||||
}
|
||||
|
||||
static void gpio_intr(void* arg)
|
||||
@ -291,45 +310,29 @@ static void gpio_intr(void* arg)
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t sdspi_host_init_slot(int slot, const sdspi_slot_config_t* slot_config)
|
||||
esp_err_t sdspi_host_init_device(const sdspi_device_config_t* slot_config, sdspi_dev_handle_t* out_handle)
|
||||
{
|
||||
ESP_LOGD(TAG, "%s: SPI%d miso=%d mosi=%d sck=%d cs=%d cd=%d wp=%d, dma_ch=%d",
|
||||
__func__, slot + 1,
|
||||
slot_config->gpio_miso, slot_config->gpio_mosi,
|
||||
slot_config->gpio_sck, slot_config->gpio_cs,
|
||||
slot_config->gpio_cd, slot_config->gpio_wp,
|
||||
slot_config->dma_channel);
|
||||
ESP_LOGD(TAG, "%s: SPI%d cs=%d cd=%d wp=%d",
|
||||
__func__, slot_config->host_id + 1, slot_config->gpio_cs,
|
||||
slot_config->gpio_cd, slot_config->gpio_wp);
|
||||
|
||||
spi_host_device_t host = (spi_host_device_t) slot;
|
||||
if (!is_valid_slot(slot)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
slot_info_t* slot = (slot_info_t*)malloc(sizeof(slot_info_t));
|
||||
if (slot == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.miso_io_num = slot_config->gpio_miso,
|
||||
.mosi_io_num = slot_config->gpio_mosi,
|
||||
.sclk_io_num = slot_config->gpio_sck,
|
||||
.quadwp_io_num = GPIO_NUM_NC,
|
||||
.quadhd_io_num = GPIO_NUM_NC
|
||||
*slot = (slot_info_t) {
|
||||
.host_id = slot_config->host_id,
|
||||
.gpio_cs = slot_config->gpio_cs,
|
||||
};
|
||||
|
||||
// Initialize SPI bus
|
||||
esp_err_t ret = spi_bus_initialize((spi_host_device_t)slot, &buscfg,
|
||||
slot_config->dma_channel);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGD(TAG, "spi_bus_initialize failed with rc=0x%x", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Attach the SD card to the SPI bus
|
||||
ret = init_spi_dev(slot, SDMMC_FREQ_PROBING * 1000);
|
||||
esp_err_t ret = configure_spi_dev(slot, SDMMC_FREQ_PROBING * 1000);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGD(TAG, "spi_bus_add_device failed with rc=0x%x", ret);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Configure CS pin
|
||||
s_slots[slot].gpio_cs = (uint8_t) slot_config->gpio_cs;
|
||||
gpio_config_t io_conf = {
|
||||
.intr_type = GPIO_PIN_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
@ -352,16 +355,16 @@ esp_err_t sdspi_host_init_slot(int slot, const sdspi_slot_config_t* slot_config)
|
||||
};
|
||||
if (slot_config->gpio_cd != SDSPI_SLOT_NO_CD) {
|
||||
io_conf.pin_bit_mask |= (1ULL << slot_config->gpio_cd);
|
||||
s_slots[slot].gpio_cd = slot_config->gpio_cd;
|
||||
slot->gpio_cd = slot_config->gpio_cd;
|
||||
} else {
|
||||
s_slots[slot].gpio_cd = GPIO_UNUSED;
|
||||
slot->gpio_cd = GPIO_UNUSED;
|
||||
}
|
||||
|
||||
if (slot_config->gpio_wp != SDSPI_SLOT_NO_WP) {
|
||||
io_conf.pin_bit_mask |= (1ULL << slot_config->gpio_wp);
|
||||
s_slots[slot].gpio_wp = slot_config->gpio_wp;
|
||||
slot->gpio_wp = slot_config->gpio_wp;
|
||||
} else {
|
||||
s_slots[slot].gpio_wp = GPIO_UNUSED;
|
||||
slot->gpio_wp = GPIO_UNUSED;
|
||||
}
|
||||
|
||||
if (io_conf.pin_bit_mask != 0) {
|
||||
@ -373,7 +376,7 @@ esp_err_t sdspi_host_init_slot(int slot, const sdspi_slot_config_t* slot_config)
|
||||
}
|
||||
|
||||
if (slot_config->gpio_int != SDSPI_SLOT_NO_INT) {
|
||||
s_slots[slot].gpio_int = slot_config->gpio_int;
|
||||
slot->gpio_int = slot_config->gpio_int;
|
||||
io_conf = (gpio_config_t) {
|
||||
.intr_type = GPIO_INTR_LOW_LEVEL,
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
@ -386,56 +389,49 @@ esp_err_t sdspi_host_init_slot(int slot, const sdspi_slot_config_t* slot_config)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
gpio_intr_disable(slot_config->gpio_int);
|
||||
|
||||
s_slots[slot].semphr_int = xSemaphoreCreateBinary();
|
||||
if (s_slots[slot].semphr_int == NULL) {
|
||||
slot->semphr_int = xSemaphoreCreateBinary();
|
||||
if (slot->semphr_int == NULL) {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
gpio_intr_disable(slot->gpio_int);
|
||||
// 1. the interrupt is better to be disabled before the ISR is registered
|
||||
// 2. the semaphore MUST be initialized before the ISR is registered
|
||||
// 3. the gpio_int member should be filled before the ISR is registered
|
||||
ret = gpio_isr_handler_add(slot_config->gpio_int, &gpio_intr, &s_slots[slot]);
|
||||
ret = gpio_isr_handler_add(slot->gpio_int, &gpio_intr, slot);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "gpio_isr_handle_add failed with rc=0x%x", ret);
|
||||
goto cleanup;
|
||||
}
|
||||
} else {
|
||||
s_slots[slot].gpio_int = GPIO_UNUSED;
|
||||
slot->gpio_int = GPIO_UNUSED;
|
||||
}
|
||||
|
||||
s_slots[slot].transactions = calloc(SDSPI_TRANSACTION_COUNT, sizeof(spi_transaction_t));
|
||||
if (s_slots[slot].transactions == NULL) {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
//Initialization finished, store the store information if possible
|
||||
//Then return corresponding handle
|
||||
*out_handle = store_slot_info(slot);
|
||||
return ESP_OK;
|
||||
cleanup:
|
||||
if (s_slots[slot].semphr_int) {
|
||||
vSemaphoreDelete(s_slots[slot].semphr_int);
|
||||
s_slots[slot].semphr_int = NULL;
|
||||
if (slot->semphr_int) {
|
||||
vSemaphoreDelete(slot->semphr_int);
|
||||
slot->semphr_int = NULL;
|
||||
}
|
||||
if (s_slots[slot].handle) {
|
||||
spi_bus_remove_device(spi_handle(slot));
|
||||
s_slots[slot].handle = NULL;
|
||||
if (slot->spi_handle) {
|
||||
spi_bus_remove_device(slot->spi_handle);
|
||||
slot->spi_handle = NULL;
|
||||
}
|
||||
spi_bus_free(host);
|
||||
free(slot);
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
|
||||
esp_err_t sdspi_host_start_command(int slot, sdspi_hw_cmd_t *cmd, void *data,
|
||||
esp_err_t sdspi_host_start_command(sdspi_dev_handle_t handle, sdspi_hw_cmd_t *cmd, void *data,
|
||||
uint32_t data_size, int flags)
|
||||
{
|
||||
if (!is_valid_slot(slot)) {
|
||||
slot_info_t *slot = get_slot_info(handle);
|
||||
if (slot == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!is_slot_initialized(slot)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (card_missing(slot)) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
@ -445,7 +441,7 @@ esp_err_t sdspi_host_start_command(int slot, sdspi_hw_cmd_t *cmd, void *data,
|
||||
memcpy(&cmd_arg, cmd->arguments, sizeof(cmd_arg));
|
||||
cmd_arg = __builtin_bswap32(cmd_arg);
|
||||
ESP_LOGV(TAG, "%s: slot=%i, CMD%d, arg=0x%08x flags=0x%x, data=%p, data_size=%i crc=0x%02x",
|
||||
__func__, slot, cmd_index, cmd_arg, flags, data, data_size, cmd->crc7);
|
||||
__func__, handle, cmd_index, cmd_arg, flags, data, data_size, cmd->crc7);
|
||||
|
||||
|
||||
// For CMD0, clock out 80 cycles to help the card enter idle state,
|
||||
@ -455,6 +451,8 @@ esp_err_t sdspi_host_start_command(int slot, sdspi_hw_cmd_t *cmd, void *data,
|
||||
}
|
||||
// actual transaction
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
spi_device_acquire_bus(slot->spi_handle, portMAX_DELAY);
|
||||
cs_low(slot);
|
||||
if (flags & SDSPI_CMD_FLAG_DATA) {
|
||||
const bool multi_block = flags & SDSPI_CMD_FLAG_MULTI_BLK;
|
||||
@ -471,20 +469,21 @@ esp_err_t sdspi_host_start_command(int slot, sdspi_hw_cmd_t *cmd, void *data,
|
||||
cs_high(slot);
|
||||
|
||||
release_bus(slot);
|
||||
spi_device_release_bus(slot->spi_handle);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGD(TAG, "%s: cmd=%d error=0x%x", __func__, cmd_index, ret);
|
||||
} else {
|
||||
// Update internal state when some commands are sent successfully
|
||||
if (cmd_index == SD_CRC_ON_OFF) {
|
||||
s_slots[slot].data_crc_enabled = (uint8_t) cmd_arg;
|
||||
ESP_LOGD(TAG, "data CRC set=%d", s_slots[slot].data_crc_enabled);
|
||||
slot->data_crc_enabled = (uint8_t) cmd_arg;
|
||||
ESP_LOGD(TAG, "data CRC set=%d", slot->data_crc_enabled);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t start_command_default(int slot, int flags, sdspi_hw_cmd_t *cmd)
|
||||
static esp_err_t start_command_default(slot_info_t *slot, int flags, sdspi_hw_cmd_t *cmd)
|
||||
{
|
||||
size_t cmd_size = SDSPI_CMD_R1_SIZE;
|
||||
if ((flags & SDSPI_CMD_FLAG_RSP_R1) ||
|
||||
@ -509,13 +508,13 @@ static esp_err_t start_command_default(int slot, int flags, sdspi_hw_cmd_t *cmd)
|
||||
.tx_buffer = cmd,
|
||||
.rx_buffer = cmd,
|
||||
};
|
||||
esp_err_t ret = spi_device_transmit(spi_handle(slot), &t);
|
||||
esp_err_t ret = spi_device_polling_transmit(slot->spi_handle, &t);
|
||||
if (cmd->cmd_index == MMC_STOP_TRANSMISSION) {
|
||||
/* response is a stuff byte from previous transfer, ignore it */
|
||||
cmd->r1 = 0xff;
|
||||
}
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGD(TAG, "%s: spi_device_transmit returned 0x%x", __func__, ret);
|
||||
ESP_LOGD(TAG, "%s: spi_device_polling_transmit returned 0x%x", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
if (flags & SDSPI_CMD_FLAG_NORSP) {
|
||||
@ -532,10 +531,10 @@ static esp_err_t start_command_default(int slot, int flags, sdspi_hw_cmd_t *cmd)
|
||||
}
|
||||
|
||||
// Wait until MISO goes high
|
||||
static esp_err_t poll_busy(int slot, spi_transaction_t* t, int timeout_ms)
|
||||
static esp_err_t poll_busy(slot_info_t *slot, int timeout_ms, bool polling)
|
||||
{
|
||||
uint8_t t_rx;
|
||||
*t = (spi_transaction_t) {
|
||||
spi_transaction_t t = {
|
||||
.tx_buffer = &t_rx,
|
||||
.flags = SPI_TRANS_USE_RXDATA, //data stored in rx_data
|
||||
.length = 8,
|
||||
@ -546,12 +545,16 @@ static esp_err_t poll_busy(int slot, spi_transaction_t* t, int timeout_ms)
|
||||
int nonzero_count = 0;
|
||||
do {
|
||||
t_rx = SDSPI_MOSI_IDLE_VAL;
|
||||
t->rx_data[0] = 0;
|
||||
ret = spi_device_transmit(spi_handle(slot), t);
|
||||
t.rx_data[0] = 0;
|
||||
if (polling) {
|
||||
ret = spi_device_polling_transmit(slot->spi_handle, &t);
|
||||
} else {
|
||||
ret = spi_device_transmit(slot->spi_handle, &t);
|
||||
}
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
if (t->rx_data[0] != 0) {
|
||||
if (t.rx_data[0] != 0) {
|
||||
if (++nonzero_count == 2) {
|
||||
return ESP_OK;
|
||||
}
|
||||
@ -564,11 +567,10 @@ static esp_err_t poll_busy(int slot, spi_transaction_t* t, int timeout_ms)
|
||||
// Wait for data token, reading 8 bytes at a time.
|
||||
// If the token is found, write all subsequent bytes to extra_ptr,
|
||||
// and store the number of bytes written to extra_size.
|
||||
static esp_err_t poll_data_token(int slot, spi_transaction_t* t,
|
||||
uint8_t* extra_ptr, size_t* extra_size, int timeout_ms)
|
||||
static esp_err_t poll_data_token(slot_info_t *slot, uint8_t *extra_ptr, size_t *extra_size, int timeout_ms)
|
||||
{
|
||||
uint8_t t_rx[8];
|
||||
*t = (spi_transaction_t) {
|
||||
spi_transaction_t t = {
|
||||
.tx_buffer = &t_rx,
|
||||
.rx_buffer = &t_rx,
|
||||
.length = sizeof(t_rx) * 8,
|
||||
@ -577,7 +579,7 @@ static esp_err_t poll_data_token(int slot, spi_transaction_t* t,
|
||||
uint64_t t_end = esp_timer_get_time() + timeout_ms * 1000;
|
||||
do {
|
||||
memset(t_rx, SDSPI_MOSI_IDLE_VAL, sizeof(t_rx));
|
||||
ret = spi_device_transmit(spi_handle(slot), t);
|
||||
ret = spi_device_polling_transmit(slot->spi_handle, &t);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
@ -667,20 +669,18 @@ static esp_err_t shift_cmd_response(sdspi_hw_cmd_t* cmd, int sent_bytes)
|
||||
* Further speedup is possible by pipelining transfers and CRC checks, at an
|
||||
* expense of one extra temporary buffer.
|
||||
*/
|
||||
static esp_err_t start_command_read_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
static esp_err_t start_command_read_blocks(slot_info_t *slot, sdspi_hw_cmd_t *cmd,
|
||||
uint8_t *data, uint32_t rx_length, bool need_stop_command)
|
||||
{
|
||||
spi_transaction_t* t_command = get_transaction(slot);
|
||||
*t_command = (spi_transaction_t) {
|
||||
spi_transaction_t t_command = {
|
||||
.length = (SDSPI_CMD_R1_SIZE + SDSPI_RESPONSE_MAX_DELAY) * 8,
|
||||
.tx_buffer = cmd,
|
||||
.rx_buffer = cmd,
|
||||
};
|
||||
esp_err_t ret = spi_device_transmit(spi_handle(slot), t_command);
|
||||
esp_err_t ret = spi_device_polling_transmit(slot->spi_handle, &t_command);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
release_transaction(slot);
|
||||
|
||||
uint8_t* cmd_u8 = (uint8_t*) cmd;
|
||||
size_t pre_scan_data_size = SDSPI_RESPONSE_MAX_DELAY;
|
||||
@ -715,9 +715,7 @@ static esp_err_t start_command_read_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
|
||||
if (need_poll) {
|
||||
// Wait for data to be ready
|
||||
spi_transaction_t* t_poll = get_transaction(slot);
|
||||
ret = poll_data_token(slot, t_poll, cmd_u8 + SDSPI_CMD_R1_SIZE, &extra_data_size, cmd->timeout_ms);
|
||||
release_transaction(slot);
|
||||
ret = poll_data_token(slot, cmd_u8 + SDSPI_CMD_R1_SIZE, &extra_data_size, cmd->timeout_ms);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
@ -737,18 +735,16 @@ static esp_err_t start_command_read_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
// receive actual data
|
||||
const size_t receive_extra_bytes = (rx_length > SDSPI_MAX_DATA_LEN) ? 4 : 2;
|
||||
memset(rx_data, 0xff, will_receive + receive_extra_bytes);
|
||||
spi_transaction_t* t_data = get_transaction(slot);
|
||||
*t_data = (spi_transaction_t) {
|
||||
spi_transaction_t t_data = {
|
||||
.length = (will_receive + receive_extra_bytes) * 8,
|
||||
.rx_buffer = rx_data,
|
||||
.tx_buffer = rx_data
|
||||
};
|
||||
|
||||
ret = spi_device_transmit(spi_handle(slot), t_data);
|
||||
ret = spi_device_transmit(slot->spi_handle, &t_data);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
release_transaction(slot);
|
||||
|
||||
// CRC bytes need to be received even if CRC is not enabled
|
||||
uint16_t crc = UINT16_MAX;
|
||||
@ -766,7 +762,7 @@ static esp_err_t start_command_read_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
|
||||
// compute CRC of the received data
|
||||
uint16_t crc_of_data = 0;
|
||||
if (data_crc_enabled(slot)) {
|
||||
if (slot->data_crc_enabled) {
|
||||
crc_of_data = sdspi_crc16(data, will_receive + extra_data_size);
|
||||
if (crc_of_data != crc) {
|
||||
ESP_LOGE(TAG, "data CRC failed, got=0x%04x expected=0x%04x", crc_of_data, crc);
|
||||
@ -793,9 +789,7 @@ static esp_err_t start_command_read_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
if (stop_cmd.r1 != 0) {
|
||||
ESP_LOGD(TAG, "%s: STOP_TRANSMISSION response 0x%02x", __func__, stop_cmd.r1);
|
||||
}
|
||||
spi_transaction_t* t_poll = get_transaction(slot);
|
||||
ret = poll_busy(slot, t_poll, cmd->timeout_ms);
|
||||
release_transaction(slot);
|
||||
ret = poll_busy(slot, cmd->timeout_ms, use_polling);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
@ -808,7 +802,7 @@ static esp_err_t start_command_read_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
* That's why we need ``multi_block``.
|
||||
* It's also different that stop transmission token is not needed in the SDIO mode.
|
||||
*/
|
||||
static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
static esp_err_t start_command_write_blocks(slot_info_t *slot, sdspi_hw_cmd_t *cmd,
|
||||
const uint8_t *data, uint32_t tx_length, bool multi_block, bool stop_trans)
|
||||
{
|
||||
if (card_write_protected(slot)) {
|
||||
@ -819,17 +813,15 @@ static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
// SD cards always return R1 (1bytes), SDIO returns R5 (2 bytes)
|
||||
const int send_bytes = SDSPI_CMD_R5_SIZE+SDSPI_NCR_MAX_SIZE-SDSPI_NCR_MIN_SIZE;
|
||||
|
||||
spi_transaction_t* t_command = get_transaction(slot);
|
||||
*t_command = (spi_transaction_t) {
|
||||
spi_transaction_t t_command = {
|
||||
.length = send_bytes * 8,
|
||||
.tx_buffer = cmd,
|
||||
.rx_buffer = cmd,
|
||||
};
|
||||
esp_err_t ret = spi_device_queue_trans(spi_handle(slot), t_command, 0);
|
||||
esp_err_t ret = spi_device_polling_transmit(slot->spi_handle, &t_command);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
wait_for_transactions(slot);
|
||||
|
||||
// check if command response valid
|
||||
ret = shift_cmd_response(cmd, send_bytes);
|
||||
@ -843,12 +835,11 @@ static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
|
||||
while (tx_length > 0) {
|
||||
// Write block start token
|
||||
spi_transaction_t* t_start_token = get_transaction(slot);
|
||||
*t_start_token = (spi_transaction_t) {
|
||||
spi_transaction_t t_start_token = {
|
||||
.length = sizeof(start_token) * 8,
|
||||
.tx_buffer = &start_token
|
||||
};
|
||||
ret = spi_device_queue_trans(spi_handle(slot), t_start_token, 0);
|
||||
ret = spi_device_polling_transmit(slot->spi_handle, &t_start_token);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
@ -856,7 +847,7 @@ static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
// Prepare data to be sent
|
||||
size_t will_send = MIN(tx_length, SDSPI_MAX_DATA_LEN);
|
||||
const uint8_t* tx_data = data;
|
||||
if (!ptr_dma_compatible(tx_data)) {
|
||||
if (!esp_ptr_in_dram(tx_data)) {
|
||||
// If the pointer can't be used with DMA, copy data into a new buffer
|
||||
uint8_t* tmp;
|
||||
ret = get_block_buf(slot, &tmp);
|
||||
@ -868,12 +859,11 @@ static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
}
|
||||
|
||||
// Write data
|
||||
spi_transaction_t* t_data = get_transaction(slot);
|
||||
*t_data = (spi_transaction_t) {
|
||||
spi_transaction_t t_data = {
|
||||
.length = will_send * 8,
|
||||
.tx_buffer = tx_data,
|
||||
};
|
||||
ret = spi_device_queue_trans(spi_handle(slot), t_data, 0);
|
||||
ret = spi_device_transmit(slot->spi_handle, &t_data);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
@ -882,23 +872,19 @@ static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
uint16_t crc = sdspi_crc16(data, will_send);
|
||||
const int size_crc_response = sizeof(crc) + 1;
|
||||
|
||||
spi_transaction_t* t_crc_rsp = get_transaction(slot);
|
||||
*t_crc_rsp = (spi_transaction_t) {
|
||||
spi_transaction_t t_crc_rsp = {
|
||||
.length = size_crc_response * 8,
|
||||
.flags = SPI_TRANS_USE_TXDATA|SPI_TRANS_USE_RXDATA,
|
||||
};
|
||||
memset(t_crc_rsp->tx_data, 0xff, 4);
|
||||
memcpy(t_crc_rsp->tx_data, &crc, sizeof(crc));
|
||||
memset(t_crc_rsp.tx_data, 0xff, 4);
|
||||
memcpy(t_crc_rsp.tx_data, &crc, sizeof(crc));
|
||||
|
||||
ret = spi_device_queue_trans(spi_handle(slot), t_crc_rsp, 0);
|
||||
ret = spi_device_polling_transmit(slot->spi_handle, &t_crc_rsp);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Wait for data to be sent
|
||||
wait_for_transactions(slot);
|
||||
|
||||
uint8_t data_rsp = t_crc_rsp->rx_data[2];
|
||||
uint8_t data_rsp = t_crc_rsp.rx_data[2];
|
||||
if (!SD_SPI_DATA_RSP_VALID(data_rsp)) return ESP_ERR_INVALID_RESPONSE;
|
||||
switch (SD_SPI_DATA_RSP(data_rsp)) {
|
||||
case SD_SPI_DATA_ACCEPTED:
|
||||
@ -912,9 +898,7 @@ static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
}
|
||||
|
||||
// Wait for the card to finish writing data
|
||||
spi_transaction_t* t_poll = get_transaction(slot);
|
||||
ret = poll_busy(slot, t_poll, cmd->timeout_ms);
|
||||
release_transaction(slot);
|
||||
ret = poll_busy(slot, cmd->timeout_ms, no_use_polling);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
@ -928,20 +912,16 @@ static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
TOKEN_BLOCK_STOP_WRITE_MULTI,
|
||||
SDSPI_MOSI_IDLE_VAL
|
||||
};
|
||||
spi_transaction_t *t_stop_token = get_transaction(slot);
|
||||
*t_stop_token = (spi_transaction_t) {
|
||||
spi_transaction_t t_stop_token = {
|
||||
.length = sizeof(stop_token) * 8,
|
||||
.tx_buffer = &stop_token,
|
||||
};
|
||||
ret = spi_device_queue_trans(spi_handle(slot), t_stop_token, 0);
|
||||
ret = spi_device_polling_transmit(slot->spi_handle, &t_stop_token);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
wait_for_transactions(slot);
|
||||
|
||||
spi_transaction_t *t_poll = get_transaction(slot);
|
||||
ret = poll_busy(slot, t_poll, cmd->timeout_ms);
|
||||
release_transaction(slot);
|
||||
ret = poll_busy(slot, cmd->timeout_ms, use_polling);
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
@ -950,28 +930,77 @@ static esp_err_t start_command_write_blocks(int slot, sdspi_hw_cmd_t *cmd,
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t sdspi_host_io_int_enable(int slot)
|
||||
esp_err_t sdspi_host_io_int_enable(sdspi_dev_handle_t handle)
|
||||
{
|
||||
//the pin and its interrupt is already initialized, nothing to do here.
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
//the interrupt will give the semaphore and then disable itself
|
||||
esp_err_t sdspi_host_io_int_wait(int slot, TickType_t timeout_ticks)
|
||||
esp_err_t sdspi_host_io_int_wait(sdspi_dev_handle_t handle, TickType_t timeout_ticks)
|
||||
{
|
||||
slot_info_t* pslot = &s_slots[slot];
|
||||
slot_info_t* slot = get_slot_info(handle);
|
||||
//skip the interrupt and semaphore if the gpio is already low.
|
||||
if (gpio_get_level(pslot->gpio_int)==0) return ESP_OK;
|
||||
if (gpio_get_level(slot->gpio_int)==0) return ESP_OK;
|
||||
|
||||
//clear the semaphore before wait
|
||||
xSemaphoreTake(pslot->semphr_int, 0);
|
||||
xSemaphoreTake(slot->semphr_int, 0);
|
||||
//enable the interrupt and wait for the semaphore
|
||||
gpio_intr_enable(pslot->gpio_int);
|
||||
BaseType_t ret = xSemaphoreTake(pslot->semphr_int, timeout_ticks);
|
||||
gpio_intr_enable(slot->gpio_int);
|
||||
BaseType_t ret = xSemaphoreTake(slot->semphr_int, timeout_ticks);
|
||||
if (ret == pdFALSE) {
|
||||
gpio_intr_disable(pslot->gpio_int);
|
||||
gpio_intr_disable(slot->gpio_int);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
//Deprecated, make use of new sdspi_host_init_device
|
||||
esp_err_t sdspi_host_init_slot(int slot, const sdspi_slot_config_t* slot_config)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
if (get_slot_info(slot) != NULL) {
|
||||
ESP_LOGE(TAG, "Bus already initialized. Call `sdspi_host_init_dev` to attach an sdspi device to an initialized bus.");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
//Assume the slot number equals to the host id.
|
||||
spi_host_device_t host_id = slot;
|
||||
// Initialize SPI bus
|
||||
spi_bus_config_t buscfg = {
|
||||
.miso_io_num = slot_config->gpio_miso,
|
||||
.mosi_io_num = slot_config->gpio_mosi,
|
||||
.sclk_io_num = slot_config->gpio_sck,
|
||||
.quadwp_io_num = GPIO_NUM_NC,
|
||||
.quadhd_io_num = GPIO_NUM_NC
|
||||
};
|
||||
ret = spi_bus_initialize(host_id, &buscfg,
|
||||
slot_config->dma_channel);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "spi_bus_initialize failed with rc=0x%x", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
sdspi_dev_handle_t sdspi_handle;
|
||||
sdspi_device_config_t dev_config = {
|
||||
.host_id = host_id,
|
||||
.gpio_cs = slot_config->gpio_cs,
|
||||
.gpio_cd = slot_config->gpio_cd,
|
||||
.gpio_wp = slot_config->gpio_wp,
|
||||
.gpio_int = slot_config->gpio_int,
|
||||
};
|
||||
ret = sdspi_host_init_device(&dev_config, &sdspi_handle);
|
||||
if (ret != ESP_OK) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (sdspi_handle != host_id) {
|
||||
ESP_LOGE(TAG, "The deprecated sdspi_host_init_slot should be called before all other devices on the specified bus.");
|
||||
sdspi_host_remove_device(sdspi_handle);
|
||||
ret = ESP_ERR_INVALID_STATE;
|
||||
goto cleanup;
|
||||
}
|
||||
return ESP_OK;
|
||||
cleanup:
|
||||
spi_bus_free(slot);
|
||||
return ret;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "driver/sdspi_host.h"
|
||||
|
||||
/// Control tokens used to frame data transfers
|
||||
/// (see section 7.3.3 of SD simplified spec)
|
||||
@ -105,5 +105,5 @@ typedef struct {
|
||||
|
||||
void make_hw_cmd(uint32_t opcode, uint32_t arg, int timeout_ms, sdspi_hw_cmd_t *hw_cmd);
|
||||
|
||||
esp_err_t sdspi_host_start_command(int slot, sdspi_hw_cmd_t *cmd,
|
||||
esp_err_t sdspi_host_start_command(sdspi_dev_handle_t handle, sdspi_hw_cmd_t *cmd,
|
||||
void *data, uint32_t data_size, int flags);
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "test_utils.h"
|
||||
#include "param_test.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/spi_common.h"
|
||||
|
||||
#if defined(SOC_SDMMC_HOST_SUPPORTED) && defined(SOC_SDIO_SLAVE_SUPPORTED)
|
||||
#include "driver/sdio_slave.h"
|
||||
@ -34,6 +35,9 @@
|
||||
//TEST_CNT > 512
|
||||
#define TEST_CNT 10000
|
||||
|
||||
#define TEST_SDSPI_HOST HSPI_HOST
|
||||
#define TEST_SDSPI_DMACHAN 1
|
||||
|
||||
#define TEST_RESET_DATA_LEN 10
|
||||
|
||||
#ifndef MIN
|
||||
@ -154,6 +158,7 @@ static void init_essl(essl_handle_t *out_handle, const sdio_test_config_t *conf)
|
||||
{
|
||||
sdmmc_host_t config;
|
||||
esp_err_t err;
|
||||
spi_bus_config_t bus_config;
|
||||
/* Probe */
|
||||
|
||||
switch (conf->sdio_mode) {
|
||||
@ -172,24 +177,34 @@ static void init_essl(essl_handle_t *out_handle, const sdio_test_config_t *conf)
|
||||
init_sdmmc_host();
|
||||
break;
|
||||
case SDIO_SPI:
|
||||
config = (sdmmc_host_t)SDSPI_HOST_DEFAULT();
|
||||
bus_config = (spi_bus_config_t) {
|
||||
.mosi_io_num = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CMD,
|
||||
.miso_io_num = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D0,
|
||||
.sclk_io_num = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CLK,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
};
|
||||
err = spi_bus_initialize(TEST_SDSPI_HOST, &bus_config, TEST_SDSPI_DMACHAN);
|
||||
TEST_ESP_OK(err);
|
||||
|
||||
sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
|
||||
slot_config.gpio_miso = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D0;
|
||||
slot_config.gpio_mosi = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CMD;
|
||||
slot_config.gpio_sck = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CLK;
|
||||
slot_config.gpio_cs = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D3;
|
||||
slot_config.gpio_int = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D1;
|
||||
sdspi_device_config_t device_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||
device_config.host_id = TEST_SDSPI_HOST;
|
||||
device_config.gpio_cs = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D3;
|
||||
device_config.gpio_int= SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D1;
|
||||
|
||||
err = gpio_install_isr_service(0);
|
||||
TEST_ASSERT(err == ESP_OK || err == ESP_ERR_INVALID_STATE);
|
||||
|
||||
sdspi_dev_handle_t sdspi_handle;
|
||||
err = sdspi_host_init();
|
||||
TEST_ESP_OK(err);
|
||||
|
||||
err = sdspi_host_init_slot(HSPI_HOST, &slot_config);
|
||||
err = sdspi_host_init_device(&device_config, &sdspi_handle);
|
||||
TEST_ESP_OK(err);
|
||||
|
||||
ESP_LOGI(MASTER_TAG, "Probe using SPI...\n");
|
||||
|
||||
config = (sdmmc_host_t)SDSPI_HOST_DEFAULT();
|
||||
config.slot = sdspi_handle;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -224,6 +239,9 @@ static void deinit_essl(essl_handle_t handle, const sdio_test_config_t *conf)
|
||||
|
||||
err = sdspi_host_deinit();
|
||||
TEST_ESP_OK(err);
|
||||
|
||||
err = spi_bus_free(TEST_SDSPI_HOST);
|
||||
TEST_ESP_OK(err);
|
||||
} else {
|
||||
err = sdmmc_host_deinit();
|
||||
TEST_ESP_OK(err);
|
||||
|
@ -24,4 +24,6 @@ static void test_initializers()
|
||||
(void) sdspi_host;
|
||||
sdspi_slot_config_t sdspi_slot = SDSPI_SLOT_CONFIG_DEFAULT();
|
||||
(void) sdspi_slot;
|
||||
sdspi_device_config_t sdspi_dev = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||
(void) sdspi_dev;
|
||||
}
|
||||
|
@ -17,6 +17,13 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define ESP_ROM_HAS_CRC8LE 1
|
||||
#define ESP_ROM_HAS_CRC16LE 1
|
||||
#define ESP_ROM_HAS_CRC32LE 1
|
||||
#define ESP_ROM_HAS_CRC8BE 1
|
||||
#define ESP_ROM_HAS_CRC16BE 1
|
||||
#define ESP_ROM_HAS_CRC32BE 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -37,7 +44,7 @@ extern "C" {
|
||||
* CRC-8 x8+x2+x1+1 0x07
|
||||
* CRC16-CCITT x16+x12+x5+1 0x1021
|
||||
* CRC32 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x1+1 0x04c11db7
|
||||
*
|
||||
*
|
||||
* These group of CRC APIs are designed to calculate the data in buffers either continuous or not.
|
||||
* To make it easy, we had added a `~` at the beginning and the end of the functions.
|
||||
* To calculate non-continuous buffers, we can write the code like this:
|
||||
@ -57,7 +64,7 @@ extern "C" {
|
||||
* Here are some examples for CRC16:
|
||||
* CRC-16/CCITT, poly = 0x1021, init = 0x0000, refin = true, refout = true, xorout = 0x0000
|
||||
* crc = ~crc16_le((uint16_t)~0x0000, buf, length);
|
||||
*
|
||||
*
|
||||
* CRC-16/CCITT-FALSE, poly = 0x1021, init = 0xffff, refin = false, refout = false, xorout = 0x0000
|
||||
* crc = ~crc16_be((uint16_t)~0xffff, buf, length);
|
||||
*
|
||||
@ -67,7 +74,7 @@ extern "C" {
|
||||
* CRC-16/XMODEM, poly= 0x1021, init = 0x0000, refin = false, refout = false, xorout = 0x0000
|
||||
* crc = ~crc16_be((uint16_t)~0x0000, buf, length);
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define ESP_ROM_HAS_CRC8LE 1
|
||||
#define ESP_ROM_HAS_CRC16LE 1
|
||||
#define ESP_ROM_HAS_CRC32LE 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -50,19 +54,6 @@ extern "C" {
|
||||
*/
|
||||
uint32_t crc32_le(uint32_t crc, uint8_t const *buf, uint32_t len);
|
||||
|
||||
/**
|
||||
* @brief Crc32 value that is in big endian.
|
||||
*
|
||||
* @param uint32_t crc : init crc value, use 0 at the first use.
|
||||
*
|
||||
* @param uint8_t const *buf : buffer to start calculate crc.
|
||||
*
|
||||
* @param uint32_t len : buffer length in byte.
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
uint32_t crc32_be(uint32_t crc, uint8_t const *buf, uint32_t len);
|
||||
|
||||
/**
|
||||
* @brief Crc16 value that is in little endian.
|
||||
*
|
||||
@ -76,19 +67,6 @@ uint32_t crc32_be(uint32_t crc, uint8_t const *buf, uint32_t len);
|
||||
*/
|
||||
uint16_t crc16_le(uint16_t crc, uint8_t const *buf, uint32_t len);
|
||||
|
||||
/**
|
||||
* @brief Crc16 value that is in big endian.
|
||||
*
|
||||
* @param uint16_t crc : init crc value, use 0 at the first use.
|
||||
*
|
||||
* @param uint8_t const *buf : buffer to start calculate crc.
|
||||
*
|
||||
* @param uint32_t len : buffer length in byte.
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
uint16_t crc16_be(uint16_t crc, uint8_t const *buf, uint32_t len);
|
||||
|
||||
/**
|
||||
* @brief Crc8 value that is in little endian.
|
||||
*
|
||||
@ -102,19 +80,6 @@ uint16_t crc16_be(uint16_t crc, uint8_t const *buf, uint32_t len);
|
||||
*/
|
||||
uint8_t crc8_le(uint8_t crc, uint8_t const *buf, uint32_t len);
|
||||
|
||||
/**
|
||||
* @brief Crc8 value that is in big endian.
|
||||
*
|
||||
* @param uint32_t crc : init crc value, use 0 at the first use.
|
||||
*
|
||||
* @param uint8_t const *buf : buffer to start calculate crc.
|
||||
*
|
||||
* @param uint32_t len : buffer length in byte.
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
uint8_t crc8_be(uint8_t crc, uint8_t const *buf, uint32_t len);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
@ -6,12 +6,9 @@ set(srcs "diskio/diskio.c"
|
||||
"port/freertos/ffsystem.c"
|
||||
"src/ffunicode.c"
|
||||
"vfs/vfs_fat.c"
|
||||
"vfs/vfs_fat_sdmmc.c"
|
||||
"vfs/vfs_fat_spiflash.c")
|
||||
|
||||
if(IDF_TARGET STREQUAL "esp32")
|
||||
list(APPEND srcs "vfs/vfs_fat_sdmmc.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS diskio vfs src
|
||||
REQUIRES wear_levelling sdmmc
|
||||
|
@ -23,6 +23,8 @@ typedef unsigned int UINT;
|
||||
typedef unsigned char BYTE;
|
||||
typedef uint32_t DWORD;
|
||||
|
||||
#define FF_DRV_NOT_USED 0xFF
|
||||
|
||||
#include "diskio.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
|
@ -89,3 +89,12 @@ void ff_diskio_register_sdmmc(BYTE pdrv, sdmmc_card_t* card)
|
||||
ff_diskio_register(pdrv, &sdmmc_impl);
|
||||
}
|
||||
|
||||
BYTE ff_diskio_get_pdrv_card(const sdmmc_card_t* card)
|
||||
{
|
||||
for (int i = 0; i < FF_VOLUMES; i++) {
|
||||
if (card == s_cards[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include "driver/sdmmc_defs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -29,6 +29,14 @@ extern "C" {
|
||||
*/
|
||||
void ff_diskio_register_sdmmc(unsigned char pdrv, sdmmc_card_t* card);
|
||||
|
||||
/**
|
||||
* @brief Get the driver number corresponding to a card
|
||||
*
|
||||
* @param card The card to get its driver
|
||||
* @return Driver number to the card
|
||||
*/
|
||||
BYTE ff_diskio_get_pdrv_card(const sdmmc_card_t* card);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -31,9 +31,17 @@
|
||||
#include "test_fatfs_common.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#define SDSPI_MOSI_PIN 15
|
||||
#define SDSPI_MISO_PIN 2
|
||||
#define SDSPI_CS_PIN 13
|
||||
#define SDSPI_CLK_PIN 14
|
||||
#define SDSPI_HOST_ID HSPI_HOST
|
||||
|
||||
|
||||
#ifdef SOC_SDMMC_HOST_SUPPORTED
|
||||
#include "driver/sdmmc_host.h"
|
||||
|
||||
|
||||
static void test_setup(void)
|
||||
{
|
||||
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
||||
@ -180,7 +188,7 @@ TEST_CASE("(SD) multiple tasks can use same volume", "[fatfs][test_env=UT_T1_SDM
|
||||
test_teardown();
|
||||
}
|
||||
|
||||
static void speed_test(void* buf, size_t buf_size, size_t file_size, bool write);
|
||||
static void sdmmc_speed_test(void *buf, size_t buf_size, size_t file_size, bool write);
|
||||
|
||||
TEST_CASE("(SD) write/read speed test", "[fatfs][sd][test_env=UT_T1_SDMODE][timeout=60]")
|
||||
{
|
||||
@ -192,20 +200,20 @@ TEST_CASE("(SD) write/read speed test", "[fatfs][sd][test_env=UT_T1_SDMODE][time
|
||||
esp_fill_random(buf, buf_size);
|
||||
const size_t file_size = 1 * 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);
|
||||
sdmmc_speed_test(buf, 4 * 1024, file_size, true);
|
||||
sdmmc_speed_test(buf, 8 * 1024, file_size, true);
|
||||
sdmmc_speed_test(buf, 16 * 1024, file_size, true);
|
||||
|
||||
speed_test(buf, 4 * 1024, file_size, false);
|
||||
speed_test(buf, 8 * 1024, file_size, false);
|
||||
speed_test(buf, 16 * 1024, file_size, false);
|
||||
sdmmc_speed_test(buf, 4 * 1024, file_size, false);
|
||||
sdmmc_speed_test(buf, 8 * 1024, file_size, false);
|
||||
sdmmc_speed_test(buf, 16 * 1024, file_size, false);
|
||||
|
||||
free(buf);
|
||||
|
||||
HEAP_SIZE_CHECK(heap_size, 0);
|
||||
}
|
||||
|
||||
static void speed_test(void* buf, size_t buf_size, size_t file_size, bool write)
|
||||
static void sdmmc_speed_test(void *buf, size_t buf_size, size_t file_size, bool write)
|
||||
{
|
||||
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
||||
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
|
||||
@ -297,4 +305,72 @@ TEST_CASE("(SD) opendir, readdir, rewinddir, seekdir work as expected using UTF-
|
||||
}
|
||||
#endif // CONFIG_FATFS_API_ENCODING_UTF_8 && CONFIG_FATFS_CODEPAGE == 936
|
||||
|
||||
#endif //SDMMC HOST SUPPORTED
|
||||
|
||||
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2BETA)
|
||||
|
||||
static void sdspi_speed_test(void *buf, size_t buf_size, size_t file_size, bool write);
|
||||
|
||||
TEST_CASE("(SDSPI) write/read speed test", "[fatfs][sd][test_env=UT_T1_SPIMODE][timeout=60]")
|
||||
{
|
||||
size_t heap_size;
|
||||
HEAP_SIZE_CAPTURE(heap_size);
|
||||
|
||||
const size_t buf_size = 16 * 1024;
|
||||
uint32_t* buf = (uint32_t*) calloc(1, buf_size);
|
||||
esp_fill_random(buf, buf_size);
|
||||
const size_t file_size = 1 * 1024 * 1024;
|
||||
|
||||
spi_bus_config_t bus_cfg = {
|
||||
.mosi_io_num = SDSPI_MOSI_PIN,
|
||||
.miso_io_num = SDSPI_MISO_PIN,
|
||||
.sclk_io_num = SDSPI_CLK_PIN,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = 4000,
|
||||
};
|
||||
esp_err_t err = spi_bus_initialize(SDSPI_HOST_ID, &bus_cfg, 1);
|
||||
TEST_ESP_OK(err);
|
||||
|
||||
sdspi_speed_test(buf, 4 * 1024, file_size, true);
|
||||
sdspi_speed_test(buf, 8 * 1024, file_size, true);
|
||||
sdspi_speed_test(buf, 16 * 1024, file_size, true);
|
||||
|
||||
sdspi_speed_test(buf, 4 * 1024, file_size, false);
|
||||
sdspi_speed_test(buf, 8 * 1024, file_size, false);
|
||||
sdspi_speed_test(buf, 16 * 1024, file_size, false);
|
||||
|
||||
free(buf);
|
||||
spi_bus_free(SDSPI_HOST_ID);
|
||||
|
||||
HEAP_SIZE_CHECK(heap_size, 0);
|
||||
}
|
||||
|
||||
static void sdspi_speed_test(void *buf, size_t buf_size, size_t file_size, bool write)
|
||||
{
|
||||
const char path[] = "/sdcard";
|
||||
sdmmc_card_t *card;
|
||||
card = NULL;
|
||||
sdspi_device_config_t device_cfg = {
|
||||
.gpio_cs = SDSPI_CS_PIN,
|
||||
.host_id = SDSPI_HOST_ID,
|
||||
.gpio_cd = SDSPI_SLOT_NO_CD,
|
||||
.gpio_wp = SDSPI_SLOT_NO_WP,
|
||||
.gpio_int = SDSPI_SLOT_NO_INT,
|
||||
};
|
||||
|
||||
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
|
||||
host.slot = SDSPI_HOST_ID;
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = write,
|
||||
.max_files = 5,
|
||||
.allocation_unit_size = 64 * 1024
|
||||
};
|
||||
TEST_ESP_OK(esp_vfs_fat_sdspi_mount(path, &host, &device_cfg, &mount_config, &card));
|
||||
|
||||
test_fatfs_rw_speed("/sdcard/4mb.bin", buf, buf_size, file_size, write);
|
||||
|
||||
TEST_ESP_OK(esp_vfs_fat_sdcard_unmount(path, card));
|
||||
}
|
||||
|
||||
#endif
|
@ -112,6 +112,9 @@ typedef esp_vfs_fat_mount_config_t esp_vfs_fat_sdmmc_mount_config_t;
|
||||
* probing SD card, locating and mounting partition, and registering FATFS in VFS,
|
||||
* with proper error checking and handling of exceptional conditions.
|
||||
*
|
||||
* @note Use this API to mount a card through SDSPI is deprecated. Please call
|
||||
* `esp_vfs_fat_sdspi_mount()` instead for that case.
|
||||
*
|
||||
* @param base_path path where partition should be registered (e.g. "/sdcard")
|
||||
* @param host_config Pointer to structure describing SDMMC host. When using
|
||||
* SDMMC peripheral, this structure can be initialized using
|
||||
@ -121,8 +124,8 @@ typedef esp_vfs_fat_mount_config_t esp_vfs_fat_sdmmc_mount_config_t;
|
||||
* @param slot_config Pointer to structure with slot configuration.
|
||||
* For SDMMC peripheral, pass a pointer to sdmmc_slot_config_t
|
||||
* structure initialized using SDMMC_SLOT_CONFIG_DEFAULT.
|
||||
* For SPI peripheral, pass a pointer to sdspi_slot_config_t
|
||||
* structure initialized using SDSPI_SLOT_CONFIG_DEFAULT.
|
||||
* (Deprecated) For SPI peripheral, pass a pointer to sdspi_slot_config_t
|
||||
* structure initialized using SDSPI_SLOT_CONFIG_DEFAULT().
|
||||
* @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
|
||||
@ -138,15 +141,71 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
sdmmc_card_t** out_card);
|
||||
|
||||
/**
|
||||
* @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 an SPI Master device based on the SPI Master driver with configuration in
|
||||
* slot_config, and attach it to an initialized SPI bus.
|
||||
* - initializes SD card with configuration in host_config_input
|
||||
* - mounts FAT partition on SD 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.
|
||||
*
|
||||
* @note This function try to attach the new SD SPI device to the bus specified in host_config.
|
||||
* Make sure the SPI bus specified in `host_config->slot` have been initialized by
|
||||
* `spi_bus_initialize()` before.
|
||||
*
|
||||
* @param base_path path where partition should be registered (e.g. "/sdcard")
|
||||
* @param host_config_input Pointer to structure describing SDMMC host. This structure can be
|
||||
* initialized using SDSPI_HOST_DEFAULT() macro.
|
||||
* @param slot_config Pointer to structure with slot configuration.
|
||||
* For SPI peripheral, pass a pointer to sdspi_device_config_t
|
||||
* structure initialized using SDSPI_DEVICE_CONFIG_DEFAULT().
|
||||
* @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. It is suggested to hold this handle and use it to unmount the card later if
|
||||
* needed. Otherwise it's not suggested to use more than one card at the same time and unmount one
|
||||
* of them in your application.
|
||||
* @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 or SPI drivers, SDMMC protocol, or FATFS drivers
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path,
|
||||
const sdmmc_host_t* host_config_input,
|
||||
const sdspi_device_config_t* slot_config,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
sdmmc_card_t** out_card);
|
||||
|
||||
/**
|
||||
* @brief Unmount FAT filesystem and release resources acquired using esp_vfs_fat_sdmmc_mount
|
||||
*
|
||||
* @deprecated Use `esp_vfs_fat_sdcard_unmount()` instead.
|
||||
*
|
||||
* @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(void);
|
||||
|
||||
/**
|
||||
* @brief Unmount an SD card from the FAT filesystem and release resources acquired using
|
||||
* `esp_vfs_fat_sdmmc_mount()` or `esp_vfs_fat_sdspi_mount()`
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if the card argument is unregistered
|
||||
* - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount hasn't been called
|
||||
*/
|
||||
esp_err_t esp_vfs_fat_sdcard_unmount(const char *base_path, sdmmc_card_t *card);
|
||||
|
||||
/**
|
||||
* @brief Convenience function to initialize FAT filesystem in SPI flash and register it in VFS
|
||||
*
|
||||
|
@ -18,82 +18,81 @@
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "vfs_fat_internal.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include "driver/sdspi_host.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "diskio_impl.h"
|
||||
#include "diskio_sdmmc.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "driver/sdmmc_defs.h"
|
||||
|
||||
#ifdef SOC_SDMMC_HOST_SUPPORTED
|
||||
#include "driver/sdmmc_host.h"
|
||||
#endif
|
||||
|
||||
static const char* TAG = "vfs_fat_sdmmc";
|
||||
static sdmmc_card_t* s_card = NULL;
|
||||
static uint8_t s_pdrv = 0;
|
||||
static uint8_t s_pdrv = FF_DRV_NOT_USED;
|
||||
static char * s_base_path = NULL;
|
||||
|
||||
esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
|
||||
const sdmmc_host_t* host_config,
|
||||
const void* slot_config,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
sdmmc_card_t** out_card)
|
||||
{
|
||||
const size_t workbuf_size = 4096;
|
||||
void* workbuf = NULL;
|
||||
FATFS* fs = NULL;
|
||||
#define CHECK_EXECUTE_RESULT(err, str) do { \
|
||||
if ((err) !=ESP_OK) { \
|
||||
ESP_LOGE(TAG, str" (0x%x).", err); \
|
||||
goto cleanup; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
if (s_card != NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
static void call_host_deinit(const sdmmc_host_t *host_config);
|
||||
static esp_err_t partition_card(const esp_vfs_fat_mount_config_t *mount_config,
|
||||
const char *drv, sdmmc_card_t *card, BYTE pdrv);
|
||||
|
||||
static esp_err_t mount_prepare_mem(const char *base_path,
|
||||
BYTE *out_pdrv,
|
||||
char **out_dup_path,
|
||||
sdmmc_card_t** out_card)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
char* dup_path = NULL;
|
||||
sdmmc_card_t* card = NULL;
|
||||
|
||||
// connect SDMMC driver to FATFS
|
||||
BYTE pdrv = 0xFF;
|
||||
if (ff_diskio_get_drive(&pdrv) != ESP_OK || pdrv == 0xFF) {
|
||||
BYTE pdrv = FF_DRV_NOT_USED;
|
||||
if (ff_diskio_get_drive(&pdrv) != ESP_OK || pdrv == FF_DRV_NOT_USED) {
|
||||
ESP_LOGD(TAG, "the maximum count of volumes is already mounted");
|
||||
return ESP_ERR_NO_MEM;
|
||||
|
||||
}
|
||||
|
||||
s_base_path = strdup(base_path);
|
||||
if(!s_base_path){
|
||||
ESP_LOGD(TAG, "could not copy base_path");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
esp_err_t err = ESP_OK;
|
||||
// not using ff_memalloc here, as allocation in internal RAM is preferred
|
||||
s_card = malloc(sizeof(sdmmc_card_t));
|
||||
if (s_card == NULL) {
|
||||
card = (sdmmc_card_t*)malloc(sizeof(sdmmc_card_t));
|
||||
if (card == NULL) {
|
||||
ESP_LOGD(TAG, "could not locate new sdmmc_card_t");
|
||||
err = ESP_ERR_NO_MEM;
|
||||
goto fail;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
err = (*host_config->init)();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGD(TAG, "host init returned rc=0x%x", err);
|
||||
goto fail;
|
||||
dup_path = strdup(base_path);
|
||||
if(!dup_path){
|
||||
ESP_LOGD(TAG, "could not copy base_path");
|
||||
err = ESP_ERR_NO_MEM;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// configure SD slot
|
||||
if (host_config->flags == SDMMC_HOST_FLAG_SPI) {
|
||||
err = sdspi_host_init_slot(host_config->slot,
|
||||
(const sdspi_slot_config_t*) slot_config);
|
||||
} else {
|
||||
err = sdmmc_host_init_slot(host_config->slot,
|
||||
(const sdmmc_slot_config_t*) slot_config);
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGD(TAG, "slot_config returned rc=0x%x", err);
|
||||
goto fail;
|
||||
}
|
||||
*out_card = card;
|
||||
*out_pdrv = pdrv;
|
||||
*out_dup_path = dup_path;
|
||||
return ESP_OK;
|
||||
cleanup:
|
||||
free(card);
|
||||
free(dup_path);
|
||||
return err;
|
||||
}
|
||||
|
||||
// probe and initialize card
|
||||
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;
|
||||
}
|
||||
|
||||
ff_diskio_register_sdmmc(pdrv, s_card);
|
||||
s_pdrv = pdrv;
|
||||
static esp_err_t mount_to_vfs_fat(const esp_vfs_fat_mount_config_t *mount_config, sdmmc_card_t *card, uint8_t pdrv,
|
||||
const char *base_path)
|
||||
{
|
||||
FATFS* fs = NULL;
|
||||
esp_err_t err;
|
||||
ff_diskio_register_sdmmc(pdrv, card);
|
||||
ESP_LOGD(TAG, "using pdrv=%i", pdrv);
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
|
||||
@ -115,31 +114,12 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
|
||||
&& mount_config->format_if_mount_failed)) {
|
||||
goto fail;
|
||||
}
|
||||
ESP_LOGW(TAG, "partitioning card");
|
||||
workbuf = ff_memalloc(workbuf_size);
|
||||
if (workbuf == NULL) {
|
||||
err = ESP_ERR_NO_MEM;
|
||||
|
||||
err = partition_card(mount_config, drv, card, pdrv);
|
||||
if (err != ESP_OK) {
|
||||
goto fail;
|
||||
}
|
||||
DWORD plist[] = {100, 0, 0, 0};
|
||||
res = f_fdisk(s_pdrv, plist, workbuf);
|
||||
if (res != FR_OK) {
|
||||
err = ESP_FAIL;
|
||||
ESP_LOGD(TAG, "f_fdisk failed (%d)", res);
|
||||
goto fail;
|
||||
}
|
||||
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
|
||||
s_card->csd.sector_size,
|
||||
mount_config->allocation_unit_size);
|
||||
ESP_LOGW(TAG, "formatting card, allocation unit size=%d", alloc_unit_size);
|
||||
res = f_mkfs(drv, FM_ANY, alloc_unit_size, workbuf, workbuf_size);
|
||||
if (res != FR_OK) {
|
||||
err = ESP_FAIL;
|
||||
ESP_LOGD(TAG, "f_mkfs failed (%d)", res);
|
||||
goto fail;
|
||||
}
|
||||
free(workbuf);
|
||||
workbuf = NULL;
|
||||
|
||||
ESP_LOGW(TAG, "mounting again");
|
||||
res = f_mount(fs, drv, 0);
|
||||
if (res != FR_OK) {
|
||||
@ -151,36 +131,270 @@ esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
|
||||
return ESP_OK;
|
||||
|
||||
fail:
|
||||
host_config->deinit();
|
||||
free(workbuf);
|
||||
if (fs) {
|
||||
f_mount(NULL, drv, 0);
|
||||
}
|
||||
esp_vfs_fat_unregister_path(base_path);
|
||||
ff_diskio_unregister(pdrv);
|
||||
free(s_card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t partition_card(const esp_vfs_fat_mount_config_t *mount_config,
|
||||
const char *drv, sdmmc_card_t *card, BYTE pdrv)
|
||||
{
|
||||
FRESULT res = FR_OK;
|
||||
esp_err_t err;
|
||||
const size_t workbuf_size = 4096;
|
||||
void* workbuf = NULL;
|
||||
ESP_LOGW(TAG, "partitioning card");
|
||||
|
||||
workbuf = ff_memalloc(workbuf_size);
|
||||
if (workbuf == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
DWORD plist[] = {100, 0, 0, 0};
|
||||
res = f_fdisk(pdrv, plist, workbuf);
|
||||
if (res != FR_OK) {
|
||||
err = ESP_FAIL;
|
||||
ESP_LOGD(TAG, "f_fdisk failed (%d)", res);
|
||||
goto fail;
|
||||
}
|
||||
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
|
||||
card->csd.sector_size,
|
||||
mount_config->allocation_unit_size);
|
||||
ESP_LOGW(TAG, "formatting card, allocation unit size=%d", alloc_unit_size);
|
||||
res = f_mkfs(drv, FM_ANY, alloc_unit_size, workbuf, workbuf_size);
|
||||
if (res != FR_OK) {
|
||||
err = ESP_FAIL;
|
||||
ESP_LOGD(TAG, "f_mkfs failed (%d)", res);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
free(workbuf);
|
||||
return ESP_OK;
|
||||
fail:
|
||||
free(workbuf);
|
||||
return err;
|
||||
}
|
||||
|
||||
#if SOC_SDMMC_HOST_SUPPORTED
|
||||
static esp_err_t init_sdmmc_host(int slot, const void *slot_config, int *out_slot)
|
||||
{
|
||||
*out_slot = slot;
|
||||
return sdmmc_host_init_slot(slot, (const sdmmc_slot_config_t*) slot_config);
|
||||
}
|
||||
|
||||
static esp_err_t init_sdspi_host_deprecated(int slot, const void *slot_config, int *out_slot)
|
||||
{
|
||||
*out_slot = slot;
|
||||
return sdspi_host_init_slot(slot, (const sdspi_slot_config_t*) slot_config);
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
|
||||
const sdmmc_host_t* host_config,
|
||||
const void* slot_config,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
sdmmc_card_t** out_card)
|
||||
{
|
||||
esp_err_t err;
|
||||
int card_handle = -1; //uninitialized
|
||||
sdmmc_card_t* card = NULL;
|
||||
BYTE pdrv = FF_DRV_NOT_USED;
|
||||
char* dup_path = NULL;
|
||||
bool host_inited = false;
|
||||
|
||||
err = mount_prepare_mem(base_path, &pdrv, &dup_path, &card);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "mount_prepare failed");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (host_config->flags == SDMMC_HOST_FLAG_SPI) {
|
||||
//Deprecated API
|
||||
//the init() function is usually empty, doesn't require any deinit to revert it
|
||||
err = (*host_config->init)();
|
||||
CHECK_EXECUTE_RESULT(err, "host init failed");
|
||||
err = init_sdspi_host_deprecated(host_config->slot, slot_config, &card_handle);
|
||||
CHECK_EXECUTE_RESULT(err, "slot init failed");
|
||||
//Set `host_inited` to true to indicate that host_config->deinit() needs
|
||||
//to be called to revert `init_sdspi_host_deprecated`; set `card_handle`
|
||||
//to -1 to indicate that no other deinit is required.
|
||||
host_inited = true;
|
||||
card_handle = -1;
|
||||
} else {
|
||||
err = (*host_config->init)();
|
||||
CHECK_EXECUTE_RESULT(err, "host init failed");
|
||||
//deinit() needs to be called to revert the init
|
||||
host_inited = true;
|
||||
//If this failed (indicated by card_handle != -1), slot deinit needs to called()
|
||||
//leave card_handle as is to indicate that (though slot deinit not implemented yet.
|
||||
err = init_sdmmc_host(host_config->slot, slot_config, &card_handle);
|
||||
CHECK_EXECUTE_RESULT(err, "slot init failed");
|
||||
}
|
||||
|
||||
// probe and initialize card
|
||||
err = sdmmc_card_init(host_config, card);
|
||||
CHECK_EXECUTE_RESULT(err, "sdmmc_card_init failed");
|
||||
|
||||
err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path);
|
||||
CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed");
|
||||
|
||||
if (out_card != NULL) {
|
||||
*out_card = card;
|
||||
}
|
||||
if (s_card == NULL) {
|
||||
//store the ctx locally to be back-compatible
|
||||
s_card = card;
|
||||
s_pdrv = pdrv;
|
||||
s_base_path = dup_path;
|
||||
} else {
|
||||
free(dup_path);
|
||||
}
|
||||
return ESP_OK;
|
||||
cleanup:
|
||||
if (host_inited) {
|
||||
call_host_deinit(host_config);
|
||||
}
|
||||
free(card);
|
||||
free(dup_path);
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static esp_err_t init_sdspi_host(int slot, const void *slot_config, int *out_slot)
|
||||
{
|
||||
esp_err_t err = sdspi_host_init_device((const sdspi_device_config_t*)slot_config, out_slot);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG,
|
||||
"Failed to attach sdspi device onto an SPI bus (rc=0x%x), please initialize the \
|
||||
bus first and check the device parameters."
|
||||
, err);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_sdspi_mount(const char* base_path,
|
||||
const sdmmc_host_t* host_config_input,
|
||||
const sdspi_device_config_t* slot_config,
|
||||
const esp_vfs_fat_mount_config_t* mount_config,
|
||||
sdmmc_card_t** out_card)
|
||||
{
|
||||
const sdmmc_host_t* host_config = host_config_input;
|
||||
esp_err_t err;
|
||||
int card_handle = -1; //uninitialized
|
||||
bool host_inited = false;
|
||||
BYTE pdrv = FF_DRV_NOT_USED;
|
||||
sdmmc_card_t* card = NULL;
|
||||
char* dup_path = NULL;
|
||||
|
||||
err = mount_prepare_mem(base_path, &pdrv, &dup_path, &card);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "mount_prepare failed");
|
||||
return err;
|
||||
}
|
||||
|
||||
//the init() function is usually empty, doesn't require any deinit to revert it
|
||||
err = (*host_config->init)();
|
||||
CHECK_EXECUTE_RESULT(err, "host init failed");
|
||||
|
||||
err = init_sdspi_host(host_config->slot, slot_config, &card_handle);
|
||||
CHECK_EXECUTE_RESULT(err, "slot init failed");
|
||||
//Set `host_inited` to true to indicate that host_config->deinit() needs
|
||||
//to be called to revert `init_sdspi_host`
|
||||
host_inited = true;
|
||||
|
||||
/*
|
||||
* The `slot` argument inside host_config should be replaced by the SD SPI handled returned
|
||||
* above. But the input pointer is const, so create a new variable.
|
||||
*/
|
||||
sdmmc_host_t new_config;
|
||||
if (card_handle != host_config->slot) {
|
||||
new_config = *host_config_input;
|
||||
host_config = &new_config;
|
||||
new_config.slot = card_handle;
|
||||
}
|
||||
|
||||
// probe and initialize card
|
||||
err = sdmmc_card_init(host_config, card);
|
||||
CHECK_EXECUTE_RESULT(err, "sdmmc_card_init failed");
|
||||
|
||||
err = mount_to_vfs_fat(mount_config, card, pdrv, dup_path);
|
||||
CHECK_EXECUTE_RESULT(err, "mount_to_vfs failed");
|
||||
|
||||
if (out_card != NULL) {
|
||||
*out_card = card;
|
||||
}
|
||||
if (s_card == NULL) {
|
||||
//store the ctx locally to be back-compatible
|
||||
s_card = card;
|
||||
s_pdrv = pdrv;
|
||||
s_base_path = dup_path;
|
||||
} else {
|
||||
free(dup_path);
|
||||
}
|
||||
return ESP_OK;
|
||||
|
||||
cleanup:
|
||||
if (host_inited) {
|
||||
call_host_deinit(host_config);
|
||||
}
|
||||
free(card);
|
||||
free(dup_path);
|
||||
return err;
|
||||
|
||||
}
|
||||
|
||||
static void local_card_remove(void)
|
||||
{
|
||||
s_card = NULL;
|
||||
free(s_base_path);
|
||||
s_base_path = NULL;
|
||||
s_pdrv = FF_DRV_NOT_USED;
|
||||
}
|
||||
|
||||
static void call_host_deinit(const sdmmc_host_t *host_config)
|
||||
{
|
||||
if (host_config->flags & SDMMC_HOST_FLAG_DEINIT_ARG) {
|
||||
host_config->deinit_p(host_config->slot);
|
||||
} else {
|
||||
host_config->deinit();
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t unmount_card_core(const char *base_path, sdmmc_card_t *card)
|
||||
{
|
||||
BYTE pdrv = ff_diskio_get_pdrv_card(card);
|
||||
if (pdrv == 0xff) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// unmount
|
||||
char drv[3] = {(char)('0' + pdrv), ':', 0};
|
||||
f_mount(0, drv, 0);
|
||||
// release SD driver
|
||||
ff_diskio_unregister(pdrv);
|
||||
|
||||
call_host_deinit(&card->host);
|
||||
free(card);
|
||||
|
||||
esp_err_t err = esp_vfs_fat_unregister_path(base_path);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_sdmmc_unmount(void)
|
||||
{
|
||||
if (s_card == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
// unmount
|
||||
char drv[3] = {(char)('0' + s_pdrv), ':', 0};
|
||||
f_mount(0, drv, 0);
|
||||
// release SD driver
|
||||
esp_err_t (*host_deinit)(void) = s_card->host.deinit;
|
||||
ff_diskio_unregister(s_pdrv);
|
||||
free(s_card);
|
||||
s_card = NULL;
|
||||
(*host_deinit)();
|
||||
esp_err_t err = esp_vfs_fat_unregister_path(s_base_path);
|
||||
free(s_base_path);
|
||||
s_base_path = NULL;
|
||||
sdmmc_card_t* card = s_card;
|
||||
esp_err_t err = unmount_card_core(s_base_path, card);
|
||||
local_card_remove();
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_fat_sdcard_unmount(const char *base_path, sdmmc_card_t *card)
|
||||
{
|
||||
esp_err_t err = unmount_card_core(base_path, card);
|
||||
if (s_card == card) {
|
||||
local_card_remove();
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
@ -43,6 +43,8 @@
|
||||
#define SD_TEST_BOARD_VSEL_3V3 1
|
||||
#define SD_TEST_BOARD_VSEL_1V8 0
|
||||
|
||||
#define TEST_SDSPI_DMACHAN 1
|
||||
|
||||
/* time to wait for reset / power-on */
|
||||
#define SD_TEST_BOARD_PWR_RST_DELAY_MS 5
|
||||
#define SD_TEST_BOARD_PWR_ON_DELAY_MS 50
|
||||
@ -161,7 +163,57 @@ TEST_CASE("probe SD, slot 0, 1-bit", "[sd][test_env=UT_T1_SDCARD][ignore]")
|
||||
|
||||
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2)
|
||||
//No runners
|
||||
static void test_sdspi_init_bus(spi_host_device_t host, int mosi_pin, int miso_pin, int clk_pin, int dma_chan)
|
||||
{
|
||||
spi_bus_config_t bus_config = {
|
||||
.mosi_io_num = mosi_pin,
|
||||
.miso_io_num = miso_pin,
|
||||
.sclk_io_num = clk_pin,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
};
|
||||
esp_err_t err = spi_bus_initialize(host, &bus_config, dma_chan);
|
||||
TEST_ESP_OK(err);
|
||||
}
|
||||
|
||||
static void test_sdspi_deinit_bus(spi_host_device_t host)
|
||||
{
|
||||
esp_err_t err = spi_bus_free(host);
|
||||
TEST_ESP_OK(err);
|
||||
}
|
||||
|
||||
static void probe_core(int slot)
|
||||
{
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
config.slot = slot;
|
||||
|
||||
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);
|
||||
free(card);
|
||||
}
|
||||
|
||||
static void probe_spi(int freq_khz, int pin_miso, int pin_mosi, int pin_sck, int pin_cs)
|
||||
{
|
||||
sd_test_board_power_on();
|
||||
|
||||
sdspi_dev_handle_t handle;
|
||||
sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||
dev_config.gpio_cs = pin_cs;
|
||||
test_sdspi_init_bus(dev_config.host_id, pin_mosi, pin_miso, pin_sck, TEST_SDSPI_DMACHAN);
|
||||
TEST_ESP_OK(sdspi_host_init());
|
||||
TEST_ESP_OK(sdspi_host_init_device(&dev_config, &handle));
|
||||
|
||||
probe_core(handle);
|
||||
|
||||
TEST_ESP_OK(sdspi_host_deinit());
|
||||
test_sdspi_deinit_bus(dev_config.host_id);
|
||||
sd_test_board_power_off();
|
||||
}
|
||||
|
||||
static void probe_spi_legacy(int freq_khz, int pin_miso, int pin_mosi, int pin_sck, int pin_cs)
|
||||
{
|
||||
sd_test_board_power_on();
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
@ -173,23 +225,23 @@ static void probe_spi(int freq_khz, int pin_miso, int pin_mosi, int pin_sck, int
|
||||
|
||||
TEST_ESP_OK(sdspi_host_init());
|
||||
TEST_ESP_OK(sdspi_host_init_slot(config.slot, &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);
|
||||
|
||||
probe_core(config.slot);
|
||||
|
||||
TEST_ESP_OK(sdspi_host_deinit());
|
||||
free(card);
|
||||
sd_test_board_power_off();
|
||||
}
|
||||
|
||||
TEST_CASE("probe SD in SPI mode, slot 1", "[sd][test_env=UT_T1_SPIMODE]")
|
||||
{
|
||||
probe_spi(SDMMC_FREQ_DEFAULT, 2, 15, 14, 13);
|
||||
probe_spi_legacy(SDMMC_FREQ_DEFAULT, 2, 15, 14, 13);
|
||||
}
|
||||
|
||||
TEST_CASE("probe SD in SPI mode, slot 0", "[sd][test_env=UT_T1_SDCARD][ignore]")
|
||||
{
|
||||
probe_spi(SDMMC_FREQ_DEFAULT, 7, 11, 6, 10);
|
||||
probe_spi_legacy(SDMMC_FREQ_DEFAULT, 7, 11, 6, 10);
|
||||
}
|
||||
|
||||
#endif //DISABLED(ESP32S2)
|
||||
@ -327,16 +379,23 @@ TEST_CASE("SDMMC read/write test (eMMC slot 0, 8 line)", "[sd][test_env=EMMC]")
|
||||
TEST_CASE("SDMMC read/write test (SD slot 1, in SPI mode)", "[sdspi][test_env=UT_T1_SPIMODE]")
|
||||
{
|
||||
sd_test_board_power_on();
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
|
||||
|
||||
sdspi_dev_handle_t handle;
|
||||
sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||
test_sdspi_init_bus(dev_config.host_id, GPIO_NUM_15, GPIO_NUM_2, GPIO_NUM_14, TEST_SDSPI_DMACHAN);
|
||||
TEST_ESP_OK(sdspi_host_init());
|
||||
TEST_ESP_OK(sdspi_host_init_slot(config.slot, &slot_config));
|
||||
TEST_ESP_OK(sdspi_host_init_device(&dev_config, &handle));
|
||||
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
config.slot = handle;
|
||||
|
||||
sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t));
|
||||
TEST_ASSERT_NOT_NULL(card);
|
||||
TEST_ESP_OK(sdmmc_card_init(&config, card));
|
||||
read_write_test(card);
|
||||
free(card);
|
||||
TEST_ESP_OK(sdspi_host_deinit());
|
||||
free(card);
|
||||
test_sdspi_deinit_bus(dev_config.host_id);
|
||||
sd_test_board_power_off();
|
||||
}
|
||||
#endif //DISABLED_FOR_TARGETS(ESP32S2)
|
||||
@ -427,15 +486,21 @@ TEST_CASE("CD input works in SD mode", "[sd][test_env=UT_T1_SDMODE]")
|
||||
TEST_CASE("CD input works in SPI mode", "[sd][test_env=UT_T1_SPIMODE]")
|
||||
{
|
||||
sd_test_board_power_on();
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
|
||||
slot_config.gpio_cd = CD_WP_TEST_GPIO;
|
||||
|
||||
sdspi_dev_handle_t handle;
|
||||
sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||
dev_config.gpio_cd = CD_WP_TEST_GPIO;
|
||||
test_sdspi_init_bus(dev_config.host_id, GPIO_NUM_15, GPIO_NUM_2, GPIO_NUM_14, TEST_SDSPI_DMACHAN);
|
||||
TEST_ESP_OK(sdspi_host_init());
|
||||
TEST_ESP_OK(sdspi_host_init_slot(config.slot, &slot_config));
|
||||
TEST_ESP_OK(sdspi_host_init_device(&dev_config, &handle));
|
||||
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
config.slot = handle;
|
||||
|
||||
test_cd_input(CD_WP_TEST_GPIO, &config);
|
||||
|
||||
TEST_ESP_OK(sdspi_host_deinit());
|
||||
test_sdspi_deinit_bus(dev_config.host_id);
|
||||
sd_test_board_power_off();
|
||||
}
|
||||
#endif //DISABLED_FOR_TARGETS(ESP32S2)
|
||||
@ -495,15 +560,22 @@ TEST_CASE("WP input works in SD mode", "[sd][test_env=UT_T1_SDMODE]")
|
||||
TEST_CASE("WP input works in SPI mode", "[sd][test_env=UT_T1_SPIMODE]")
|
||||
{
|
||||
sd_test_board_power_on();
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
|
||||
slot_config.gpio_wp = CD_WP_TEST_GPIO;
|
||||
|
||||
sdspi_dev_handle_t handle;
|
||||
sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||
dev_config.gpio_wp = CD_WP_TEST_GPIO;
|
||||
test_sdspi_init_bus(dev_config.host_id, GPIO_NUM_15, GPIO_NUM_2, GPIO_NUM_14, TEST_SDSPI_DMACHAN);
|
||||
|
||||
TEST_ESP_OK(sdspi_host_init());
|
||||
TEST_ESP_OK(sdspi_host_init_slot(config.slot, &slot_config));
|
||||
TEST_ESP_OK(sdspi_host_init_device(&dev_config, &handle));
|
||||
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
config.slot = handle;
|
||||
|
||||
test_wp_input(CD_WP_TEST_GPIO, &config);
|
||||
|
||||
TEST_ESP_OK(sdspi_host_deinit());
|
||||
test_sdspi_deinit_bus(dev_config.host_id);
|
||||
sd_test_board_power_off();
|
||||
}
|
||||
#endif //DISABLED_FOR_TARGETS(ESP32S2)
|
@ -4,33 +4,35 @@ SD SPI Host Driver
|
||||
Overview
|
||||
--------
|
||||
|
||||
The SD SPI host driver allows using the SPI2 (HSPI) or SPI3 (VSPI) controller for communication with SD cards.
|
||||
The SD SPI host driver allows communicating with one or more SD cards by the SPI Master driver which makes use of the SPI host. Each card is accessed through an SD SPI device represented by an `sdspi_dev_handle_t` spi_handle returned when attaching the device to an SPI bus by calling `sdspi_host_init_device`. The bus should be already initialized before (by `spi_bus_initialize`).
|
||||
|
||||
.. only:: esp32
|
||||
|
||||
This driver's naming pattern was adopted from the :doc:`SDMMC Host <sdmmc_host>` driver due to their similarity. Likewise, the APIs of both drivers are also very similar.
|
||||
|
||||
The SD SPI host driver has the following modes:
|
||||
SD SPI driver (access the SD card in SPI mode) offers lower throughput but makes pin selection more flexible. With the help of the GPIO matrix, an SPI peripheral's signals can be routed to any {IDF_TARGET_NAME} pin. Otherwise, if SDMMC host driver is used (See :doc:`SDMMC Host <sdmmc_host>`) to access the card in SD 1-bit/4-bit mode, higher throughput can be reached but it requires routing the signals through their dedicated IO_MUX pins only.
|
||||
|
||||
- **SPI mode**: offers lower throughput but makes pin selection more flexible. With the help of the GPIO matrix, an SPI peripheral's signals can be routed to any {IDF_TARGET_NAME} pin.
|
||||
- **1-bit SD mode**: offers higher throughput but requires routing the signals through their dedicated IO_MUX pins only.
|
||||
With the help of :doc:`SPI Master driver <spi_master>` based on, the SPI bus can be shared among SD cards and other SPI devices. The SPI Master driver will handle exclusive access from different tasks.
|
||||
|
||||
The SD SPI driver uses software-controlled CS signal.
|
||||
|
||||
Currently, the SD SPI driver cannot handle multi-threaded environments as does not support time-division multiplexing on the same SPI bus. It means that if your application needs to communicate with an SD card and other devices on the same SPI bus, the application itself must ensure that its different tasks do not try to access the SPI slaves at the same time.
|
||||
|
||||
|
||||
How to Use
|
||||
----------
|
||||
|
||||
The state and configurations of the SD SPI host driver are stored in a :cpp:type:`sdmmc_host_t` structure. This structure can be initialized using the :c:macro:`SDSPI_HOST_DEFAULT` macro.
|
||||
Firstly, use the macro :c:macro:`SDSPI_DEVICE_CONFIG_DEFAULT` to initialize a structure :cpp:type:`sdmmc_slot_config_t`, which is used to initialize an SD SPI device. This macro will also fill in the default pin mappings, which is same as the pin mappings of SDMMC host driver. Modify the host and pins of the structure to desired value. Then call `sdspi_host_init_device` to initialize the SD SPI device and attach to its bus.
|
||||
|
||||
The state and configurations of the SD slot are stored in a :cpp:type:`sdmmc_slot_config_t` structure. Use the macro :c:macro:`SDSPI_SLOT_CONFIG_DEFAULT` to initialize the structure and to fill in the default pin mappings (SD mode pin mappings).
|
||||
Then use :c:macro:`SDSPI_HOST_DEFAULT` macro to initialize a :cpp:type:`sdmmc_host_t` structure, which is used to store the state and configurations of upper layer (SD/SDIO/MMC driver). Modify the `slot` parameter of the structure to the SD SPI device spi_handle just returned from `sdspi_host_init_device`. Call `sdmmc_card_init` with the :cpp:type:`sdmmc_host_t` to probe and initialize the SD card.
|
||||
|
||||
Now you can use SD/SDIO/MMC driver functions to access your card!
|
||||
|
||||
Other Details
|
||||
-------------
|
||||
|
||||
Only the following driver's API functions are normally used by most applications:
|
||||
|
||||
- :cpp:func:`sdspi_host_init`
|
||||
- :cpp:func:`sdspi_host_init_slot`
|
||||
- :cpp:func:`sdspi_host_init_device`
|
||||
- :cpp:func:`sdspi_host_remove_device`
|
||||
- :cpp:func:`sdspi_host_deinit`
|
||||
|
||||
Other functions are mostly used by the protocol level SD/SDIO/MMC driver via function pointers in the :cpp:type:`sdmmc_host_t` structure. For more details, see :doc:`the SD/SDIO/MMC Driver <../storage/sdmmc>`.
|
||||
|
@ -39,7 +39,7 @@ Most applications use the following workflow when working with ``esp_vfs_fat_``
|
||||
|
||||
9. Call :cpp:func:`esp_vfs_fat_unregister_path` with the path where the file system is mounted to remove FatFs from VFS, and free the ``FATFS`` structure allocated in Step 1.
|
||||
|
||||
The convenience functions ``esp_vfs_fat_sdmmc_mount`` and ``esp_vfs_fat_sdmmc_unmount`` wrap the steps described above and also handle SD card initialization. These two functions are described in the next section.
|
||||
The convenience functions ``esp_vfs_fat_sdmmc_mount``, ``esp_vfs_fat_sdspi_mount`` and ``esp_vfs_fat_sdcard_unmount`` wrap the steps described above and also handle SD card initialization. These two functions are described in the next section.
|
||||
|
||||
.. doxygenfunction:: esp_vfs_fat_register
|
||||
.. doxygenfunction:: esp_vfs_fat_unregister_path
|
||||
@ -48,14 +48,15 @@ The convenience functions ``esp_vfs_fat_sdmmc_mount`` and ``esp_vfs_fat_sdmmc_un
|
||||
Using FatFs with VFS and SD cards
|
||||
---------------------------------
|
||||
|
||||
The header file :component_file:`fatfs/vfs/esp_vfs_fat.h` defines convenience functions :cpp:func:`esp_vfs_fat_sdmmc_mount` and :cpp:func:`esp_vfs_fat_sdmmc_unmount`. These function perform Steps 1–3 and 7–9 respectively and handle SD card initialization, but provide only limited error handling. Developers are encouraged to check its source code and incorporate more advanced features into production applications.
|
||||
The header file :component_file:`fatfs/vfs/esp_vfs_fat.h` defines convenience functions :cpp:func:`esp_vfs_fat_sdmmc_mount`, :cpp:func:`esp_vfs_fat_sdspi_mount` and :cpp:func:`esp_vfs_fat_sdcard_unmount`. These function perform Steps 1–3 and 7–9 respectively and handle SD card initialization, but provide only limited error handling. Developers are encouraged to check its source code and incorporate more advanced features into production applications.
|
||||
|
||||
The convenience function :cpp:func:`esp_vfs_fat_sdmmc_unmount` unmounts the filesystem and releases the resources acquired by :cpp:func:`esp_vfs_fat_sdmmc_mount`.
|
||||
|
||||
.. doxygenfunction:: esp_vfs_fat_sdmmc_mount
|
||||
.. doxygenfunction:: esp_vfs_fat_sdspi_mount
|
||||
.. doxygenstruct:: esp_vfs_fat_mount_config_t
|
||||
:members:
|
||||
.. doxygenfunction:: esp_vfs_fat_sdmmc_unmount
|
||||
.. doxygenfunction:: esp_vfs_fat_sdcard_unmount
|
||||
|
||||
|
||||
Using FatFs with VFS in read-only mode
|
||||
|
@ -193,24 +193,33 @@ esp_err_t slave_init(essl_handle_t* handle)
|
||||
err = sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config);
|
||||
ESP_ERROR_CHECK(err);
|
||||
#else //over SPI
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
|
||||
sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
|
||||
slot_config.gpio_miso = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D0;
|
||||
slot_config.gpio_mosi = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CMD;
|
||||
slot_config.gpio_sck = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CLK;
|
||||
slot_config.gpio_cs = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D3;
|
||||
slot_config.gpio_int = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D1;
|
||||
sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||
dev_config.gpio_cs = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D3;
|
||||
dev_config.gpio_int = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D1;
|
||||
|
||||
err = gpio_install_isr_service(0);
|
||||
ESP_ERROR_CHECK(err);
|
||||
err = sdspi_host_init();
|
||||
|
||||
spi_bus_config_t bus_config = {
|
||||
.mosi_io_num = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CMD,
|
||||
.miso_io_num = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_D0,
|
||||
.sclk_io_num = SDIO_SLAVE_SLOT1_IOMUX_PIN_NUM_CLK,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = 4000,
|
||||
};
|
||||
err = spi_bus_initialize(dev_config.host_id, &bus_config, 1);
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
err = sdspi_host_init_slot(HSPI_HOST, &slot_config);
|
||||
sdspi_device_handle_t sdspi_handle;
|
||||
err = sdspi_host_init();
|
||||
ESP_ERROR_CHECK(err);
|
||||
err = sdspi_host_init_device(&slot_config, &sdspi_handle);
|
||||
ESP_ERROR_CHECK(err);
|
||||
ESP_LOGI(TAG, "Probe using SPI...\n");
|
||||
|
||||
sdmmc_host_t config = SDSPI_HOST_DEFAULT();
|
||||
config.slot = sdspi_handle;
|
||||
//we have to pull up all the slave pins even when the pin is not used
|
||||
gpio_d2_set_high();
|
||||
#endif //over SPI
|
||||
|
@ -15,16 +15,21 @@
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "driver/sdmmc_host.h"
|
||||
#include "driver/sdspi_host.h"
|
||||
#include "driver/spi_common.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
#define MOUNT_POINT "/sdcard"
|
||||
|
||||
// This example can use SDMMC and SPI peripherals to communicate with SD card.
|
||||
// By default, SDMMC peripheral is used.
|
||||
// To enable SPI mode, uncomment the following line:
|
||||
|
||||
// #define USE_SPI_MODE
|
||||
|
||||
#define SPI_DMA_CHAN 1
|
||||
|
||||
// When testing SD and SPI modes, keep in mind that once the card has been
|
||||
// initialized in SPI mode, it can not be reinitialized in SD mode without
|
||||
// toggling power to the card.
|
||||
@ -41,8 +46,23 @@ static const char *TAG = "example";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
// 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,
|
||||
.allocation_unit_size = 16 * 1024
|
||||
};
|
||||
sdmmc_card_t* card;
|
||||
const char mount_point[] = MOUNT_POINT;
|
||||
ESP_LOGI(TAG, "Initializing SD card");
|
||||
|
||||
// Use settings defined above to initialize SD card and mount FAT filesystem.
|
||||
// Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions.
|
||||
// Please check its source code and implement error recovery when developing
|
||||
// production applications.
|
||||
#ifndef USE_SPI_MODE
|
||||
ESP_LOGI(TAG, "Using SDMMC peripheral");
|
||||
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
||||
@ -63,35 +83,33 @@ void app_main(void)
|
||||
gpio_set_pull_mode(12, GPIO_PULLUP_ONLY); // D2, needed in 4-line mode only
|
||||
gpio_set_pull_mode(13, GPIO_PULLUP_ONLY); // D3, needed in 4- and 1-line modes
|
||||
|
||||
ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card);
|
||||
#else
|
||||
ESP_LOGI(TAG, "Using SPI peripheral");
|
||||
|
||||
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
|
||||
sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT();
|
||||
slot_config.gpio_miso = PIN_NUM_MISO;
|
||||
slot_config.gpio_mosi = PIN_NUM_MOSI;
|
||||
slot_config.gpio_sck = PIN_NUM_CLK;
|
||||
slot_config.gpio_cs = PIN_NUM_CS;
|
||||
spi_bus_config_t bus_cfg = {
|
||||
.mosi_io_num = PIN_NUM_MOSI,
|
||||
.miso_io_num = PIN_NUM_MISO,
|
||||
.sclk_io_num = PIN_NUM_CLK,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = 4000,
|
||||
};
|
||||
ret = spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_CHAN);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize bus.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||
slot_config.gpio_cs = PIN_NUM_CS;
|
||||
|
||||
ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);
|
||||
#endif //USE_SPI_MODE
|
||||
|
||||
// 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,
|
||||
.allocation_unit_size = 16 * 1024
|
||||
};
|
||||
|
||||
// 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. "
|
||||
@ -109,7 +127,7 @@ void app_main(void)
|
||||
// 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");
|
||||
FILE* f = fopen(MOUNT_POINT"/hello.txt", "w");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open file for writing");
|
||||
return;
|
||||
@ -120,21 +138,21 @@ void app_main(void)
|
||||
|
||||
// Check if destination file exists before renaming
|
||||
struct stat st;
|
||||
if (stat("/sdcard/foo.txt", &st) == 0) {
|
||||
if (stat(MOUNT_POINT"/foo.txt", &st) == 0) {
|
||||
// Delete it if it exists
|
||||
unlink("/sdcard/foo.txt");
|
||||
unlink(MOUNT_POINT"/foo.txt");
|
||||
}
|
||||
|
||||
// Rename original file
|
||||
ESP_LOGI(TAG, "Renaming file");
|
||||
if (rename("/sdcard/hello.txt", "/sdcard/foo.txt") != 0) {
|
||||
if (rename(MOUNT_POINT"/hello.txt", MOUNT_POINT"/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");
|
||||
f = fopen(MOUNT_POINT"/foo.txt", "r");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open file for reading");
|
||||
return;
|
||||
@ -150,6 +168,10 @@ void app_main(void)
|
||||
ESP_LOGI(TAG, "Read from file: '%s'", line);
|
||||
|
||||
// All done, unmount partition and disable SDMMC or SPI peripheral
|
||||
esp_vfs_fat_sdmmc_unmount();
|
||||
esp_vfs_fat_sdcard_unmount(mount_point, card);
|
||||
ESP_LOGI(TAG, "Card unmounted");
|
||||
#ifdef USE_SPI_MODE
|
||||
//deinitialize the bus after all devices are removed
|
||||
spi_bus_free(host.slot);
|
||||
#endif
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user