| id | peripheral-sd |
| name | SD Card Storage |
| description | TDL + filesystem API for Micro SD card over SPI. 用 tkl_fs_mount() + tkl_fopen/fwrite/fread 读写 SD 卡。 |
| installPayload | ["src/**/*.c","src/**/*.h"] |
| command | null |
| surfaces | ["embedded"] |
SD Card Storage (Micro SD / SPI)
Accessing SD cards via the TuyaOpen filesystem abstraction layer.
The SD card is typically driven over SPI interface and mounted as a filesystem device.
Hardware Prerequisites
- SD card interface: SPI (Master mode)
- Kconfig enable flag: Board-dependent (e.g.,
ENABLE_DNESP32S3_SDCARD for ESP32-S3)
Hardware Prerequisites
- SD card interface: SPI or SDIO (platform-dependent)
- SPI mode (ESP32-S3): Master mode, pins configurable per board
- SDIO mode (T5AI, etc.): 4-line data mode, fixed pins per platform
- Kconfig enable flag: Board-dependent (e.g.,
ENABLE_DNESP32S3_SDCARD)
- Mount path: conventional path
/sdcard or user-defined
- Device class:
DEV_SDCARD (used in tkl_fs_mount())
- Board registration:
board_register_hardware() handles pinmux configuration
- Power management (power domain enable, if needed) is handled separately by board code
- Application code does NOT need to manage power; only mount and use the filesystem
Initialization Flow
1. Mount the SD card device
#include "tal_api.h"
#include "tkl_fs.h"
static const char *SDCARD_MOUNT_PATH = "/sdcard";
OPERATE_RET rt = tkl_fs_mount(SDCARD_MOUNT_PATH, DEV_SDCARD);
if (rt != OPRT_OK) {
PR_ERR("SD card mount failed: %d", rt);
return;
}
PR_NOTICE("SD card mounted at %s", SDCARD_MOUNT_PATH);
Notes:
DEV_SDCARD is the device class identifier for SD cards
- If mounting fails, typically due to card not inserted or filesystem corruption
- Consider retrying with backoff (e.g.,
tal_system_sleep(3 * 1000)) for robustness
- Do NOT call this from interrupt handlers — run in a separate thread/task
2. Open, read, write files
Use standard POSIX-like file operations:
#include "tkl_fs.h"
const char *file_path = "/sdcard/data.txt";
const char *content = "Hello SD Card";
TUYA_FILE file_hdl = tkl_fopen(file_path, "w");
if (NULL == file_hdl) {
PR_ERR("Failed to open %s for writing", file_path);
return;
}
uint32_t write_len = strlen(content);
uint32_t written = tkl_fwrite(content, write_len, file_hdl);
if (written != write_len) {
PR_ERR("Write mismatch: requested %d, wrote %d", write_len, written);
}
tkl_fclose(file_hdl);
file_hdl = tkl_fopen(file_path, "r");
if (NULL == file_hdl) {
PR_ERR("Failed to open %s for reading", file_path);
return;
}
char read_buf[256] = {0};
uint32_t read_len = tkl_fread(read_buf, sizeof(read_buf), file_hdl);
if (read_len > 0) {
PR_NOTICE("Read %d bytes: %.*s", read_len, read_len, read_buf);
} else {
PR_ERR("Read failed");
}
tkl_fclose(file_hdl);
Key APIs:
tkl_fopen(path, mode) — open file; modes: "r" (read), "w" (write), "a" (append)
tkl_fwrite(buf, len, file_hdl) — write len bytes; returns bytes actually written
tkl_fread(buf, max_len, file_hdl) — read up to max_len bytes; returns bytes read (0 at EOF)
tkl_fclose(file_hdl) — close file and flush
Common Patterns
Pattern 1: Log to file with rotation
#define LOG_FILE "/sdcard/app.log"
#define MAX_LOG_SIZE (100 * 1024)
static void append_log(const char *msg)
{
struct stat st;
if (tkl_stat(LOG_FILE, &st) == 0 && st.st_size > MAX_LOG_SIZE) {
tkl_remove(LOG_FILE);
}
TUYA_FILE file = tkl_fopen(LOG_FILE, "a");
if (!file) return;
tkl_fwrite(msg, strlen(msg), file);
tkl_fwrite("\n", 1, file);
tkl_fclose(file);
}
Pattern 2: Save binary data (e.g., camera JPEG)
#define IMG_DIR "/sdcard/images"
static void save_jpeg_image(const uint8_t *data, uint32_t len)
{
static uint32_t img_counter = 0;
char filepath[64];
snprintf(filepath, sizeof(filepath), "%s/IMG_%06d.jpg", IMG_DIR, img_counter++);
TUYA_FILE file = tkl_fopen(filepath, "w");
if (!file) {
PR_ERR("Failed to open %s", filepath);
return;
}
uint32_t written = tkl_fwrite(data, len, file);
tkl_fclose(file);
if (written == len) {
PR_NOTICE("Saved %s (%d bytes)", filepath, len);
} else {
PR_ERR("Write truncated: %d/%d bytes", written, len);
}
}
Pattern 3: JSON configuration read/write
#include "cJSON.h"
#define CONFIG_FILE "/sdcard/config.json"
static OPERATE_RET save_config(const char *key, const char *value)
{
cJSON *config = cJSON_CreateObject();
TUYA_FILE file = tkl_fopen(CONFIG_FILE, "r");
if (file) {
tkl_fclose(file);
}
cJSON_AddStringToObject(config, key, value);
char *json_str = cJSON_Print(config);
file = tkl_fopen(CONFIG_FILE, "w");
if (!file) {
PR_ERR("Failed to write config");
cJSON_Delete(config);
free(json_str);
return OPRT_COM_ERROR;
}
tkl_fwrite(json_str, strlen(json_str), file);
tkl_fclose(file);
cJSON_Delete(config);
free(json_str);
return OPRT_OK;
}
Recommended Task Structure
#define SD_TASK_PRIORITY THREAD_PRIO_2
#define SD_TASK_STACK 4096
static THREAD_HANDLE g_sd_task_hdl;
static void sd_worker_task(void *arg)
{
OPERATE_RET rt = tkl_fs_mount("/sdcard", DEV_SDCARD);
if (rt != OPRT_OK) {
PR_ERR("Mount failed, retrying...");
tal_thread_delete(g_sd_task_hdl);
return;
}
while (1) {
tal_system_sleep(1000);
}
}
THREAD_CFG_T thrd_cfg = {0};
thrd_cfg.stackDepth = SD_TASK_STACK;
thrd_cfg.priority = SD_TASK_PRIORITY;
thrd_cfg.thrdname = "sd_worker";
tal_thread_create_and_start(&g_sd_task_hdl, NULL, NULL, sd_worker_task, NULL, &thrd_cfg);
Error Handling
Common error codes from tkl_fs_* functions:
| Return Value | Meaning |
|---|
OPRT_OK (0) | Success |
OPRT_COM_ERROR | General filesystem error |
OPRT_INVALID_PARM | Invalid parameter (e.g., bad path) |
File open returns NULL | Cannot open (file not found, permissions, etc.) |
tkl_fwrite() returns 0 | Write failed or disk full |
tkl_fread() returns 0 | EOF or read error |
Always check return values and handle failures explicitly:
if (tkl_fwrite(data, len, file) != len) {
PR_ERR("Partial write; may indicate disk full");
}
Platform-Specific Notes
ESP32-S3 (DNESP32S3, DNESP32S3-BOX, Bread Compact)
- Interface: SPI (Master mode)
- Kconfig flag:
ENABLE_DNESP32S3_SDCARD
- SPI port: TUYA_SPI1 (GPIO12 sck, GPIO11 mosi, GPIO13 miso, GPIO2 cs)
- Mounting: May take a few seconds; retry with backoff if first attempt fails
- Notes: SPI interface may be shared with LCD on some boards (check hardware context for conflicts)
T5AI (TUYA_T5AI_BOARD, TUYA_T5AI_POCKET, TUYA_T5AI_EINK_NFC)
- Interface: SDIO (4-line mode)
- Kconfig flag: typically none (SDIO enabled by default on T5AI platform)
- SDIO pins (fixed):
- CLK=GPIO14, CMD=GPIO15
- DATA0=GPIO16, DATA1=GPIO17, DATA2=GPIO18, DATA3=GPIO19
- Mounting: Typically reliable; pinmux handled by board registration
- Notes: SDIO is generally more reliable than SPI for SD cards
Other platforms
- Refer to
platform/<chip>/ and boards/<chip>/<board>/ documentation for SD card interface type (SPI vs SDIO) and pin assignments
Best Practices
- Mount early, once — Perform
tkl_fs_mount() once during initialization, not per file operation
- Check return values — SD operations can fail (ejected card, corruption, disk full)
- Run in a task — Never call file operations from ISRs or critical sections
- Batch operations — Group multiple file accesses into single file open/close cycles
- Validate paths — Sanitize filenames to prevent path traversal or invalid characters
- Use relative paths — Paths are relative to mount point; always prefix with
/sdcard or mount path
- Watchdog awareness — Call
tal_system_sleep() periodically in long operations to avoid watchdog reset
Testing Checklist
- ✅ SD card mounts successfully at startup
- ✅ Can write files and verify content on card
- ✅ Can read files back and compare
- ✅ Handles missing card gracefully (retry/error message)
- ✅ Handles disk full condition (write returns partial length)
- ✅ Multiple files can coexist in
/sdcard directory
- ✅ File operations don't block other tasks (use separate thread)