mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
feat(storage): Add generic partition BDL example
Add an example under examples/storage/generic_partition_bdl that partitions a raw block device at run time using only the Block Device Layer (BDL) interface. The example obtains a whole-disk BDL (SPI flash data partition by default, or an SD/eMMC card via menuconfig), writes an MBR partition table onto it with the esp_ext_part_tables managed component (fetched via idf_component.yml), then creates a generic-partition BDL for each MBR entry with esp_blockdev_generic_partition_get() and mounts FATFS on the FAT slice and LittleFS on the LittleFS slice.
This commit is contained in:
@@ -18,6 +18,21 @@ examples/storage/emmc:
|
||||
- if: IDF_TARGET in ["esp32s3", "esp32p4"]
|
||||
reason: only support on esp32s3 and esp32p4
|
||||
|
||||
examples/storage/generic_partition_bdl:
|
||||
depends_components:
|
||||
- esp_blockdev
|
||||
- esp_blockdev_util
|
||||
- esp_partition
|
||||
- fatfs
|
||||
- vfs
|
||||
- sdmmc
|
||||
- esp_driver_sdmmc
|
||||
- esp_driver_sdspi
|
||||
- esp_driver_spi
|
||||
disable_test:
|
||||
- if: IDF_TARGET != "esp32"
|
||||
reason: only one target needed
|
||||
|
||||
examples/storage/partition_api/partition_find:
|
||||
depends_components:
|
||||
- esp_partition
|
||||
|
||||
9
examples/storage/generic_partition_bdl/CMakeLists.txt
Normal file
9
examples/storage/generic_partition_bdl/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
set(COMPONENTS main)
|
||||
|
||||
project(generic_partition_bdl)
|
||||
228
examples/storage/generic_partition_bdl/README.md
Normal file
228
examples/storage/generic_partition_bdl/README.md
Normal file
@@ -0,0 +1,228 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
|
||||
|
||||
# Generic Partition over the Block Device Layer (BDL)
|
||||
|
||||
This example shows how to partition a raw block device **at run time** and mount
|
||||
two different filesystems on the resulting slices, using only the generic
|
||||
Block Device Layer (BDL) interface (`esp_blockdev`).
|
||||
|
||||
It combines three building blocks:
|
||||
|
||||
* a **whole-disk BDL** — a flash data partition (`esp_partition_get_blockdev()`)
|
||||
or an SD/eMMC card over SDMMC or SDSPI (`sdmmc_get_blockdev()`);
|
||||
* the [`esp_ext_part_tables`](https://components.espressif.com/components/espressif/esp_ext_part_tables)
|
||||
managed component — to generate/write and read/parse an [**MBR**](https://en.wikipedia.org/wiki/Master_boot_record) partition table;
|
||||
* the **generic-partition BDL** (`esp_blockdev_generic_partition_get()`) — to expose
|
||||
each MBR partition entry as its own BDL that maps a slice of the whole disk.
|
||||
|
||||
> Note: Putting an MBR partition table on a **SPI flash** data partition (the
|
||||
> default medium) is mainly for demonstration — it needs no extra hardware and
|
||||
> runs in QEMU/CI. In real projects flash is normally partitioned with the ESP-IDF
|
||||
> partition table instead. MBR partitioning at run time makes practical sense for
|
||||
> **removable/large media such as SD cards and eMMC**, where a standard, host-PC
|
||||
> readable partition table is expected.
|
||||
|
||||
## When to use this example
|
||||
|
||||
- You need to partition a raw block device **at run time**, with a standard MBR
|
||||
table that a host PC can also read.
|
||||
- You are working with removable or large media (SD card, eMMC) that is expected
|
||||
to carry its own partition table.
|
||||
- You want several filesystems (FATFS and LittleFS here) on slices of one device,
|
||||
sharing the same `esp_blockdev` interface.
|
||||
|
||||
Use a different example when you need:
|
||||
|
||||
- FATFS over the Block Device Layer on a single wear-levelled flash partition →
|
||||
[fatfs/bdl_wl](../fatfs/bdl_wl/)
|
||||
- the classic wear levelling + FATFS flow on SPI flash →
|
||||
[wear_levelling](../wear_levelling/)
|
||||
- a plain FATFS SD card mount without run-time partitioning →
|
||||
[sd_card/sdmmc](../sd_card/sdmmc/) or [sd_card/sdspi](../sd_card/sdspi/)
|
||||
- LittleFS on a single ESP-IDF partition → [littlefs](../littlefs/)
|
||||
|
||||
## BDL stack
|
||||
|
||||
```
|
||||
+--------------+ +--------------+
|
||||
| LittleFS | | FATFS | file systems (VFS)
|
||||
+--------------+ +--------------+ +-----------------------+
|
||||
| | | <-- | WL BDL (if SPI flash) |
|
||||
| generic-part | +- - - - - - - + +-----------------------+
|
||||
| BDL | | generic-part | esp_blockdev_generic_partition_get()
|
||||
| | | BDL | (one slice per MBR partition entry)
|
||||
+------+--------------+--+--------------+
|
||||
| MBR | whole-disk BDL | esp_partition_get_blockdev() /
|
||||
+------+ | sdmmc_get_blockdev()
|
||||
+---------------------------------------+
|
||||
| SPI flash partition / SD card | physical storage
|
||||
+---------------------------------------+
|
||||
```
|
||||
|
||||
The whole disk is laid out as an MBR followed by the two data slices. Partition
|
||||
starts use 4 KiB alignment on SPI flash and 1 MiB alignment on SD/eMMC, matching
|
||||
the usual alignment used by PC partitioning tools.
|
||||
|
||||
For the 2 MiB SPI flash `storage` partition, the layout is:
|
||||
|
||||
```
|
||||
0x0 0x200 0x1000 0x101000 disk end
|
||||
+-------+-----------------+-----------------------------+---------------------+-----+
|
||||
| MBR | pad (align gap) | LittleFS slice (1 MiB, | FAT slice |[tail]
|
||||
| 512 B | ~3.5 KiB | MBR entry 0, type 0xC3) | (rest of disk, | gap
|
||||
| | (unused) | | MBR entry 1, |
|
||||
| | | | type 0x0C) |
|
||||
+-------+-----------------+-----------------------------+---------------------+-----+
|
||||
^-- reserved MBR block ---^ ^
|
||||
(align_up(512, 4 KiB)) align_down() may drop a few KiB here --+
|
||||
```
|
||||
|
||||
On SD/eMMC, FAT has a fixed size of 16 MiB to keep formatting time bounded.
|
||||
The card must have at least 18 MiB for the alignment gap and both partitions:
|
||||
|
||||
```
|
||||
0x0 0x200 0x100000 0x200000 0x1200000 disk end
|
||||
+-------+-----------------+----------------------------+---------------------+----------------+
|
||||
| MBR | pad (align gap) | LittleFS slice (1 MiB, | FAT slice | Unallocated |
|
||||
| 512 B | ~1 MiB - 512 B | MBR entry 0, type 0xC3) | (16 MiB, | remainder |
|
||||
| | (unused) | | MBR entry 1, | |
|
||||
| | | | type 0x0C) | |
|
||||
+-------+-----------------+----------------------------+---------------------+----------------+
|
||||
^-- reserved MBR block ---^
|
||||
(align_up(512, 1 MiB))
|
||||
```
|
||||
|
||||
Notes on the gaps:
|
||||
|
||||
* **MBR padding**: the MBR is only 512 B, but the first block is reserved up to
|
||||
the selected alignment (`align_up(MBR_SIZE, align)`). This leaves ~3.5 KiB
|
||||
unused on SPI flash (`0x200`..`0x1000`) or ~1 MiB - 512 B on SD/eMMC
|
||||
(`0x200`..`0x100000`).
|
||||
* **Between slices**: none — LittleFS is a whole number of aligned blocks, so the
|
||||
FAT slice starts immediately after it (`fat_start = lfs_start + lfs_size`).
|
||||
* **SPI flash tail** (`[tail] gap`): `fat_size` is rounded *down* to the alignment, so if the
|
||||
disk size is not a multiple of the alignment, less than one alignment unit at
|
||||
the very end stays unused. For the 2 MiB flash partition the numbers divide
|
||||
evenly, so this gap is zero.
|
||||
* **SD/eMMC remainder**: space after the fixed FAT slice (offset 18 MiB) is left
|
||||
unallocated. The example still overwrites the card's partition table and data;
|
||||
the smaller FAT slice does not make it safe to use a card containing valuable data.
|
||||
|
||||
Because every layer speaks the same BDL interface, the same
|
||||
`esp_blockdev_generic_partition_get()` slice works with any bottom device, and the
|
||||
FATFS / LittleFS integration code does not depend on the storage driver.
|
||||
|
||||
## What the example does
|
||||
|
||||
1. Creates a whole-disk BDL from the selected storage medium.
|
||||
2. Computes a layout that reserves space for the MBR up to the selected partition
|
||||
alignment (4 KiB on SPI flash, 1 MiB on SD/eMMC), places a fixed 1 MiB LittleFS
|
||||
slice next, then a FAT slice of 16 MiB on SD/eMMC or the remaining space on SPI flash.
|
||||
3. Builds an in-memory partition list, generates an MBR, and writes it to the
|
||||
whole disk with `esp_ext_part_list_bdl_write()`.
|
||||
4. Reads the MBR back with `esp_ext_part_list_bdl_read()` and, for each entry,
|
||||
creates a generic-partition BDL and mounts the matching filesystem
|
||||
(formatting it on first run).
|
||||
5. Writes and reads back a small file on each filesystem, then unmounts and
|
||||
releases every BDL handle.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Choose the storage medium
|
||||
|
||||
Run `idf.py menuconfig` and open **Example Configuration → Whole-disk block device**:
|
||||
|
||||
* **SPI flash 'storage' data partition** (default): no extra hardware needed, also
|
||||
works in QEMU/CI.
|
||||
* **SD/eMMC card**: uses a real card and overwrites its existing partition table and
|
||||
data. Pick the peripheral under **SD card host peripheral**:
|
||||
* **SDMMC host**: uses the default SDMMC slot. You can select the bus width (1 or
|
||||
4 lines) in the same menu.
|
||||
* **SD SPI**: accesses the card over the generic SPI bus. Set the MOSI/MISO/CLK/CS
|
||||
GPIOs in the same menu; the data bus is always 1-line.
|
||||
|
||||
**SD power supply comes from internal LDO IO** defaults to enabled on supported
|
||||
targets, with LDO ID 4 on ESP32-P4 and 1 on ESP32-S31. Check your board schematic
|
||||
and adjust the ID, or disable this option if the card uses an external supply.
|
||||
> Warning: this overwrites the card's existing partition table and data.
|
||||
|
||||
### Build and flash
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type `Ctrl-]`.)
|
||||
|
||||
The managed components (`esp_ext_part_tables`, `joltwallet/littlefs`) are fetched
|
||||
automatically by the IDF Component Manager from
|
||||
[`main/idf_component.yml`](main/idf_component.yml).
|
||||
|
||||
## Example output
|
||||
|
||||
```
|
||||
I (321) example: Whole disk: SPI flash data partition 'storage'
|
||||
I (331) example: Whole disk BDL: disk_size=2097152, read_size=1, write_size=1, erase_size=4096
|
||||
I (341) example: Layout: LittleFS = 1024 KiB (fixed), FAT = remainder of the disk
|
||||
I (361) example: Writing MBR partition table to the whole disk
|
||||
I (401) example: Reading MBR partition table back
|
||||
I (411) example: Mountable partition 0: type=4, address=0x00001000, size=0x00100000
|
||||
I (421) example: Mounting LittleFS on the LittleFS partition BDL
|
||||
I (521) example: Writing '/littlefs/hello.txt'
|
||||
I (611) example: Read back from /littlefs/hello.txt: 'Hello from LittleFS over a generic-partition BDL!'
|
||||
I (621) example: Mountable partition 1: type=3, address=0x00101000, size=0x000ff000
|
||||
I (631) example: Mounting FATFS on the FAT partition BDL
|
||||
I (811) example: Writing '/fat/hello.txt'
|
||||
I (951) example: Read back from /fat/hello.txt: 'Hello from FATFS over a generic-partition BDL!'
|
||||
I (961) example: Unmounting FATFS
|
||||
I (971) example: Unmounting LittleFS
|
||||
I (981) example: Releasing whole-disk BDL
|
||||
I (991) example: Done
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
* An MBR holds at most **4 primary partition entries**, so this scheme supports up
|
||||
to 4 slices; this example uses 2 (FAT + LittleFS). `esp_mbr_generate()` keeps only
|
||||
the first 4 entries and logs a warning if the partition list is longer.
|
||||
* When SPI flash is selected, the FAT partition BDL is wrapped in a wear-levelling
|
||||
BDL before FATFS is mounted. This layer handles flash erases and distributes
|
||||
writes across the partition. LittleFS is mounted directly on its partition BDL
|
||||
because it handles erase-before-write and wear levelling internally.
|
||||
* `esp_ext_part_list_bdl_write()` writes the raw MBR sector without erasing first,
|
||||
so on flash-like devices (`erase_before_write` flag set) the example erases the
|
||||
MBR block beforehand.
|
||||
* The MBR partition **type** byte drives the filesystem choice: FAT entries are
|
||||
mounted with FATFS and the LittleFS entry with LittleFS. The generator writes
|
||||
the raw MBR type bytes `0x0C` (FAT32 with LBA) and `0xC3` (LittleFS); the
|
||||
`type=` values printed on read-back (`3` for FAT32, `4` for LittleFS) are the
|
||||
`esp_ext_part_tables` enum (`esp_ext_part_type_known_t`), not the raw bytes.
|
||||
* LittleFS has no standard MBR type, so `esp_ext_part_tables` uses a **custom
|
||||
`0xC3` "hack"**: `0xC3` = `0x83` (Linux-style filesystem) `| 0x40` (a flag meaning
|
||||
"the CHS field carries the LittleFS block size") `| 0x10` (hidden). Because the
|
||||
block size is smuggled into the entry's otherwise-unused CHS field, the example
|
||||
must supply it via the `extra` field together with the `ESP_EXT_PART_FLAG_EXTRA`
|
||||
flag (it passes the BDL `erase_size`, which LittleFS uses as its block size in
|
||||
classic mode). This is a non-standard convention, not something a PC OS will
|
||||
interpret as LittleFS.
|
||||
* The example lets `esp_ext_part_tables` place the partitions automatically:
|
||||
each entry sets `ESP_EXT_PART_FLAG_AUTO_ADDRESS` (so the generator assigns an
|
||||
aligned start address after the previous entry and past the MBR sector).
|
||||
On SPI flash, the FAT entry additionally sets `ESP_EXT_PART_FLAG_FILL` with
|
||||
`size == 0` to fill the rest of the disk. On SD/eMMC, it sets a fixed 16 MiB size
|
||||
without `ESP_EXT_PART_FLAG_FILL`. The example explicitly sets `total_size`
|
||||
from the device geometry, enabling the generator's built-in overlap and
|
||||
"fits within the disk" checks. If `total_size` is left at zero,
|
||||
`esp_ext_part_list_bdl_write()` supplies it from the device geometry instead.
|
||||
* The example requests `ESP_EXT_PART_ALIGN_4KiB` for SPI flash and
|
||||
`ESP_EXT_PART_ALIGN_1MiB` for SD/eMMC when generating the MBR. Both alignments
|
||||
satisfy the underlying media's BDL erase-alignment requirement.
|
||||
* On the **first** run the data area is empty, so both filesystems fail to mount
|
||||
and are formatted automatically (`format_if_mount_failed`). You will therefore
|
||||
see a few `W`/`E` log lines the first time, for example
|
||||
`esp_littlefs: ... Corrupted dir pair` followed by `mount failed ... formatting`.
|
||||
Subsequent runs mount the existing filesystems directly.
|
||||
* FATFS probes the block device with an `ioctl` that the flash whole-disk BDL does
|
||||
not implement, so a harmless `esp_blockdev/generic_partition: ... Parent device
|
||||
does not implement ioctl` error may be logged; the mount still succeeds.
|
||||
@@ -0,0 +1,9 @@
|
||||
set(srcs "generic_partition_bdl_main.c")
|
||||
if(CONFIG_EXAMPLE_STORAGE_MEDIA_SDCARD)
|
||||
list(APPEND srcs "sd_card_bdl.c")
|
||||
endif()
|
||||
|
||||
# fatfs already provides the SDMMC/SDSPI driver dependencies on hardware targets.
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_REQUIRES vfs fatfs esp_partition esp_blockdev esp_blockdev_util
|
||||
INCLUDE_DIRS ".")
|
||||
188
examples/storage/generic_partition_bdl/main/Kconfig.projbuild
Normal file
188
examples/storage/generic_partition_bdl/main/Kconfig.projbuild
Normal file
@@ -0,0 +1,188 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice EXAMPLE_STORAGE_MEDIA
|
||||
prompt "Whole-disk block device"
|
||||
default EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
||||
help
|
||||
Select the underlying "whole-disk" block device that gets partitioned
|
||||
with an MBR and split into a FAT and a LittleFS partition using the
|
||||
generic-partition Block Device Layer (BDL).
|
||||
|
||||
config EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
||||
bool "SPI flash 'storage' data partition"
|
||||
help
|
||||
Use a SPI flash data partition (labelled "storage") as the whole disk.
|
||||
No extra hardware is required, so this option also works in QEMU/CI.
|
||||
|
||||
config EXAMPLE_STORAGE_MEDIA_SDCARD
|
||||
bool "SD/eMMC card"
|
||||
help
|
||||
Use an SD/eMMC card as the whole disk. The card can be accessed
|
||||
through the SDMMC host or over the generic SPI bus (SDSPI); pick
|
||||
the peripheral below.
|
||||
WARNING: running the example overwrites the card's partition table.
|
||||
endchoice
|
||||
|
||||
choice EXAMPLE_SD_HOST
|
||||
prompt "SD card host peripheral"
|
||||
depends on EXAMPLE_STORAGE_MEDIA_SDCARD
|
||||
default EXAMPLE_SD_HOST_SDMMC if SOC_SDMMC_HOST_SUPPORTED
|
||||
default EXAMPLE_SD_HOST_SDSPI
|
||||
help
|
||||
Select the peripheral used to talk to the SD/eMMC card.
|
||||
|
||||
config EXAMPLE_SD_HOST_SDMMC
|
||||
bool "SDMMC host"
|
||||
depends on SOC_SDMMC_HOST_SUPPORTED
|
||||
help
|
||||
Access the card through the dedicated SDMMC host controller.
|
||||
|
||||
config EXAMPLE_SD_HOST_SDSPI
|
||||
bool "SD SPI (SD card over the SPI bus)"
|
||||
help
|
||||
Access the card over the generic SPI bus using the SDSPI protocol.
|
||||
Works on any target with a free SPI host, but is slower and always
|
||||
uses a 1-line data bus.
|
||||
endchoice
|
||||
|
||||
choice EXAMPLE_SDMMC_BUS_WIDTH
|
||||
prompt "SDMMC bus width"
|
||||
depends on EXAMPLE_SD_HOST_SDMMC
|
||||
default EXAMPLE_SDMMC_BUS_WIDTH_4
|
||||
|
||||
config EXAMPLE_SDMMC_BUS_WIDTH_4
|
||||
bool "4 lines (D0 - D3)"
|
||||
|
||||
config EXAMPLE_SDMMC_BUS_WIDTH_1
|
||||
bool "1 line (D0)"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_SDMMC_INTERNAL_PULLUP
|
||||
bool "Enable internal pull-ups on the SDMMC lines"
|
||||
depends on EXAMPLE_SD_HOST_SDMMC
|
||||
default n
|
||||
help
|
||||
Enable the internal pull-ups on the SDMMC CMD/DATA lines. The SD
|
||||
specification requires external 10 kOhm pull-ups; the internal ones are
|
||||
weak and only adequate for boards that do not provide external pull-ups
|
||||
(for example the ESP32-S3-USB-OTG devkit). Prefer external pull-ups when
|
||||
the board has them.
|
||||
|
||||
# On SoCs whose SDMMC controller routes through the GPIO matrix (e.g. ESP32-S3,
|
||||
# ESP32-P4) the slot pins are freely assignable, so expose them here. On SoCs
|
||||
# with a fixed SDMMC IO_MUX (e.g. ESP32) the slot default pins are used instead.
|
||||
config EXAMPLE_SDMMC_PIN_CLK
|
||||
int "SDMMC CLK GPIO number"
|
||||
depends on EXAMPLE_SD_HOST_SDMMC && SOC_SDMMC_USE_GPIO_MATRIX
|
||||
default 36 if IDF_TARGET_ESP32S3
|
||||
default 43 if IDF_TARGET_ESP32P4
|
||||
default -1
|
||||
help
|
||||
GPIO for the SDMMC CLK line. The ESP32-S3-USB-OTG devkit uses GPIO 36.
|
||||
|
||||
config EXAMPLE_SDMMC_PIN_CMD
|
||||
int "SDMMC CMD GPIO number"
|
||||
depends on EXAMPLE_SD_HOST_SDMMC && SOC_SDMMC_USE_GPIO_MATRIX
|
||||
default 35 if IDF_TARGET_ESP32S3
|
||||
default 44 if IDF_TARGET_ESP32P4
|
||||
default -1
|
||||
help
|
||||
GPIO for the SDMMC CMD line. The ESP32-S3-USB-OTG devkit uses GPIO 35.
|
||||
|
||||
config EXAMPLE_SDMMC_PIN_D0
|
||||
int "SDMMC D0 GPIO number"
|
||||
depends on EXAMPLE_SD_HOST_SDMMC && SOC_SDMMC_USE_GPIO_MATRIX
|
||||
default 37 if IDF_TARGET_ESP32S3
|
||||
default 39 if IDF_TARGET_ESP32P4
|
||||
default -1
|
||||
help
|
||||
GPIO for the SDMMC D0 line. The ESP32-S3-USB-OTG devkit uses GPIO 37.
|
||||
|
||||
config EXAMPLE_SDMMC_PIN_D1
|
||||
int "SDMMC D1 GPIO number"
|
||||
depends on EXAMPLE_SD_HOST_SDMMC && SOC_SDMMC_USE_GPIO_MATRIX && EXAMPLE_SDMMC_BUS_WIDTH_4
|
||||
default 38 if IDF_TARGET_ESP32S3
|
||||
default 40 if IDF_TARGET_ESP32P4
|
||||
default -1
|
||||
help
|
||||
GPIO for the SDMMC D1 line (4-line mode only). The ESP32-S3-USB-OTG
|
||||
devkit uses GPIO 38.
|
||||
|
||||
config EXAMPLE_SDMMC_PIN_D2
|
||||
int "SDMMC D2 GPIO number"
|
||||
depends on EXAMPLE_SD_HOST_SDMMC && SOC_SDMMC_USE_GPIO_MATRIX && EXAMPLE_SDMMC_BUS_WIDTH_4
|
||||
default 33 if IDF_TARGET_ESP32S3
|
||||
default 41 if IDF_TARGET_ESP32P4
|
||||
default -1
|
||||
help
|
||||
GPIO for the SDMMC D2 line (4-line mode only). The ESP32-S3-USB-OTG
|
||||
devkit uses GPIO 33.
|
||||
|
||||
config EXAMPLE_SDMMC_PIN_D3
|
||||
int "SDMMC D3 GPIO number"
|
||||
depends on EXAMPLE_SD_HOST_SDMMC && SOC_SDMMC_USE_GPIO_MATRIX && EXAMPLE_SDMMC_BUS_WIDTH_4
|
||||
default 34 if IDF_TARGET_ESP32S3
|
||||
default 42 if IDF_TARGET_ESP32P4
|
||||
default -1
|
||||
help
|
||||
GPIO for the SDMMC D3 line (4-line mode only). The ESP32-S3-USB-OTG
|
||||
devkit uses GPIO 34.
|
||||
|
||||
config EXAMPLE_SDSPI_PIN_MOSI
|
||||
int "SDSPI MOSI GPIO number"
|
||||
depends on EXAMPLE_SD_HOST_SDSPI
|
||||
default 15 if IDF_TARGET_ESP32
|
||||
default 35 if IDF_TARGET_ESP32S2
|
||||
default 4 if IDF_TARGET_ESP32S3
|
||||
default 5 if IDF_TARGET_ESP32H2
|
||||
default 36 if IDF_TARGET_ESP32P4
|
||||
default 4
|
||||
|
||||
config EXAMPLE_SDSPI_PIN_MISO
|
||||
int "SDSPI MISO GPIO number"
|
||||
depends on EXAMPLE_SD_HOST_SDSPI
|
||||
default 2 if IDF_TARGET_ESP32
|
||||
default 37 if IDF_TARGET_ESP32S2
|
||||
default 5 if IDF_TARGET_ESP32S3
|
||||
default 0 if IDF_TARGET_ESP32H2
|
||||
default 47 if IDF_TARGET_ESP32P4
|
||||
default 6
|
||||
|
||||
config EXAMPLE_SDSPI_PIN_CLK
|
||||
int "SDSPI CLK GPIO number"
|
||||
depends on EXAMPLE_SD_HOST_SDSPI
|
||||
default 14 if IDF_TARGET_ESP32
|
||||
default 36 if IDF_TARGET_ESP32S2
|
||||
default 2 if IDF_TARGET_ESP32S3
|
||||
default 4 if IDF_TARGET_ESP32H2
|
||||
default 53 if IDF_TARGET_ESP32P4
|
||||
default 5
|
||||
|
||||
config EXAMPLE_SDSPI_PIN_CS
|
||||
int "SDSPI CS GPIO number"
|
||||
depends on EXAMPLE_SD_HOST_SDSPI
|
||||
default 13 if IDF_TARGET_ESP32
|
||||
default 34 if IDF_TARGET_ESP32S2
|
||||
default 8 if IDF_TARGET_ESP32S3
|
||||
default 33 if IDF_TARGET_ESP32P4
|
||||
default 1
|
||||
|
||||
config EXAMPLE_SD_PWR_CTRL_LDO_INTERNAL_IO
|
||||
depends on EXAMPLE_STORAGE_MEDIA_SDCARD
|
||||
depends on SOC_SDMMC_IO_POWER_EXTERNAL || SOC_SDMMC_IO_UHS_POWER_EXTERNAL
|
||||
bool "SD power supply comes from internal LDO IO (READ HELP!)"
|
||||
default y
|
||||
help
|
||||
Only needed when the SD card is connected to specific IO pins which can be used for high-speed SDMMC.
|
||||
Please read the schematic first and check if the SD VDD is connected to any internal LDO output.
|
||||
Unselect this option if the SD card is powered by an external power supply.
|
||||
|
||||
config EXAMPLE_SD_PWR_CTRL_LDO_IO_ID
|
||||
depends on EXAMPLE_SD_PWR_CTRL_LDO_INTERNAL_IO
|
||||
int "LDO ID"
|
||||
default 4 if IDF_TARGET_ESP32P4
|
||||
default 1 if IDF_TARGET_ESP32S31
|
||||
help
|
||||
Please read the schematic first and input your LDO ID.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Generic-partition Block Device Layer (BDL) example
|
||||
*
|
||||
* Demonstrates partitioning a raw block device at run time and mounting two
|
||||
* different filesystems on the resulting sub-partitions:
|
||||
*
|
||||
* +--------------+ +--------------+
|
||||
* | LittleFS | | FATFS | file systems (VFS)
|
||||
* +--------------+ +--------------+ +-----------------------+
|
||||
* | | | <-- | WL BDL (if SPI flash) |
|
||||
* | generic-part | +- - - - - - - + +-----------------------+
|
||||
* | BDL | | generic-part | esp_blockdev_generic_partition_get()
|
||||
* | | | BDL | (one slice per MBR partition entry)
|
||||
* +------+--------------+--+--------------+
|
||||
* | MBR | whole-disk BDL | esp_partition_get_blockdev() /
|
||||
* +------+ | sdmmc_get_blockdev()
|
||||
* +---------------------------------------+
|
||||
* | SPI flash partition / SD card | physical storage
|
||||
* +---------------------------------------+
|
||||
*
|
||||
* Steps performed:
|
||||
* 1. Obtain a "whole-disk" BDL from either a SPI flash data partition or an
|
||||
* SD/eMMC card accessed over SDMMC or SDSPI (selectable in menuconfig).
|
||||
* 2. Lay out and write an MBR partition table onto the whole disk using the
|
||||
* `esp_ext_part_tables` managed component (fetched via idf_component.yml).
|
||||
* 3. Read the partition table back and, for each entry, create a
|
||||
* generic-partition BDL that maps only that slice of the whole disk.
|
||||
* 4. Format (if needed) and mount FATFS on the FAT entry and LittleFS on the
|
||||
* LittleFS entry, then perform a simple write/read on each.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "esp_blockdev.h"
|
||||
#include "esp_blockdev/generic_partition.h"
|
||||
|
||||
#include "esp_ext_part_tables.h"
|
||||
#include "esp_mbr.h"
|
||||
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "esp_littlefs.h"
|
||||
|
||||
#if CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
||||
#include "esp_partition.h"
|
||||
#include "wear_levelling.h"
|
||||
#else
|
||||
#include "sd_card_bdl.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
#define WHOLE_DISK_PARTITION_LABEL "storage"
|
||||
#define FAT_MOUNT_POINT "/fat"
|
||||
#define LITTLEFS_MOUNT_POINT "/littlefs"
|
||||
|
||||
/* Keep SD/eMMC formatting bounded; SPI flash FAT uses the remaining space. */
|
||||
#define LITTLEFS_PARTITION_SIZE (1 * 1024 * 1024)
|
||||
#define FAT_PARTITION_SIZE (16 * 1024 * 1024)
|
||||
|
||||
/* The example lays out two data partitions on the whole disk. Their start
|
||||
* addresses (and the SPI flash FAT size) are computed by esp_ext_part_tables
|
||||
* during MBR generation - see build_and_write_partition_table(). */
|
||||
enum {
|
||||
EXAMPLE_PART_LITTLEFS, /* first slice, right after the MBR block */
|
||||
EXAMPLE_PART_FAT,
|
||||
EXAMPLE_PART_COUNT,
|
||||
};
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Whole-disk BDL creation / release (media specific) */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
#if CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
||||
|
||||
static esp_err_t obtain_whole_disk_bdl(esp_blockdev_handle_t *out)
|
||||
{
|
||||
ESP_LOGI(TAG, "Whole disk: SPI flash data partition '%s'", WHOLE_DISK_PARTITION_LABEL);
|
||||
return esp_partition_get_blockdev(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY,
|
||||
WHOLE_DISK_PARTITION_LABEL, out);
|
||||
}
|
||||
|
||||
static esp_err_t release_whole_disk_bdl(esp_blockdev_handle_t disk)
|
||||
{
|
||||
return disk->ops->release(disk);
|
||||
}
|
||||
|
||||
#else /* CONFIG_EXAMPLE_STORAGE_MEDIA_SDCARD */
|
||||
|
||||
static esp_err_t obtain_whole_disk_bdl(esp_blockdev_handle_t *out)
|
||||
{
|
||||
/* Card setup (SDMMC or SDSPI, selectable in menuconfig) lives in sd_card_bdl.c. */
|
||||
return example_sd_card_bdl_create(out);
|
||||
}
|
||||
|
||||
static esp_err_t release_whole_disk_bdl(esp_blockdev_handle_t disk)
|
||||
{
|
||||
return example_sd_card_bdl_release(disk);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Partitioning */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static esp_err_t build_and_write_partition_table(esp_blockdev_handle_t disk)
|
||||
{
|
||||
esp_ext_part_list_t part_list = {0};
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
/* Describe the two partitions and let esp_ext_part_tables place them:
|
||||
* ESP_EXT_PART_FLAG_AUTO_ADDRESS makes the generator compute each start
|
||||
* address (after the previous entry, aligned to the requested alignment and
|
||||
* past the MBR sector), so `.address` is left unset. LittleFS gets a fixed
|
||||
* size; FATFS gets 16 MiB on SD/eMMC. On SPI flash, FATFS instead uses
|
||||
* ESP_EXT_PART_FLAG_FILL with `.size == 0` to take the remaining space. */
|
||||
esp_ext_part_list_item_t partitions[EXAMPLE_PART_COUNT] = {
|
||||
/* The LittleFS MBR entry (type 0xC3) stores the filesystem block size in
|
||||
* the CHS "hack" field, passed via `.extra` + ESP_EXT_PART_FLAG_EXTRA.
|
||||
* LittleFS in classic mode uses the BDL erase size as its block size. */
|
||||
[EXAMPLE_PART_LITTLEFS] = {
|
||||
.info = {
|
||||
.size = LITTLEFS_PARTITION_SIZE,
|
||||
.type = ESP_EXT_PART_TYPE_LITTLEFS,
|
||||
.extra = disk->geometry.erase_size,
|
||||
.flags = ESP_EXT_PART_FLAG_EXTRA | ESP_EXT_PART_FLAG_AUTO_ADDRESS,
|
||||
.label = NULL,
|
||||
},
|
||||
},
|
||||
[EXAMPLE_PART_FAT] = {
|
||||
.info = {
|
||||
.type = ESP_EXT_PART_TYPE_FAT32,
|
||||
#if CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
||||
.size = 0, /* FILL: sized to the rest of the disk by the generator */
|
||||
.flags = ESP_EXT_PART_FLAG_AUTO_ADDRESS | ESP_EXT_PART_FLAG_FILL,
|
||||
#else
|
||||
.size = FAT_PARTITION_SIZE,
|
||||
.flags = ESP_EXT_PART_FLAG_AUTO_ADDRESS,
|
||||
#endif
|
||||
.label = NULL,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/* Choose the partition-start alignment based on the storage medium:
|
||||
* - SPI flash: 4 KiB, matching the flash erase (sector) size. A larger
|
||||
* alignment would waste a big chunk of the small (few-MiB) flash disk.
|
||||
* - SD/eMMC card: 1 MiB, matching how PC tools (fdisk/parted) align
|
||||
* partitions to the card's erase/allocation unit for best performance.
|
||||
* Either value is >= the BDL erase size, so it also satisfies the
|
||||
* generic-partition BDL erase-alignment requirement.
|
||||
*
|
||||
* `total_size` is the whole-disk size: the generator needs it to size the
|
||||
* SPI flash FILL (FAT) partition and to run its overlap / off-disk checks.
|
||||
* (esp_ext_part_list_bdl_write() would auto-fill it from the device geometry
|
||||
* when left 0, but the example sets it explicitly for clarity.) */
|
||||
esp_mbr_generate_extra_args_t gen_args = {
|
||||
.total_size = disk->geometry.disk_size,
|
||||
.sector_size = ESP_EXT_PART_SECTOR_SIZE_512B,
|
||||
#if CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
||||
.alignment = ESP_EXT_PART_ALIGN_4KiB,
|
||||
#else
|
||||
.alignment = ESP_EXT_PART_ALIGN_1MiB,
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Insert in on-disk order so the MBR entries match the physical layout. */
|
||||
for (int i = 0; i < EXAMPLE_PART_COUNT; i++) {
|
||||
ESP_GOTO_ON_ERROR(esp_ext_part_list_insert(&part_list, &partitions[i]), cleanup, TAG, "insert partition entry %d", i);
|
||||
}
|
||||
|
||||
/* esp_ext_part_list_bdl_write() writes the raw 512-byte MBR without erasing
|
||||
* first. On flash-like devices the target block must be erased beforehand. */
|
||||
if (disk->device_flags.erase_before_write) {
|
||||
ESP_GOTO_ON_ERROR(disk->ops->erase(disk, 0, disk->geometry.erase_size), cleanup, TAG, "erase MBR area");
|
||||
}
|
||||
|
||||
ESP_GOTO_ON_ERROR(esp_ext_part_list_bdl_write(disk, &part_list, ESP_EXT_PART_LIST_SIGNATURE_MBR, &gen_args),
|
||||
cleanup, TAG, "write MBR");
|
||||
|
||||
cleanup:
|
||||
esp_ext_part_list_deinit(&part_list);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Filesystem usage */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static void write_and_read_back(const char *path, const char *content);
|
||||
|
||||
static esp_err_t mount_fat(esp_blockdev_handle_t part)
|
||||
{
|
||||
ESP_LOGI(TAG, "Mounting FATFS on the FAT partition BDL");
|
||||
const esp_vfs_fat_mount_config_t mount_config = {
|
||||
.max_files = 4,
|
||||
.format_if_mount_failed = true,
|
||||
/* Use larger clusters to keep the FAT tables small. */
|
||||
.allocation_unit_size = 16 * 1024,
|
||||
.use_one_fat = false,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(esp_vfs_fat_bdl_mount(FAT_MOUNT_POINT, part, &mount_config), TAG, "FAT mount failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t mount_littlefs(esp_blockdev_handle_t part)
|
||||
{
|
||||
ESP_LOGI(TAG, "Mounting LittleFS on the LittleFS partition BDL");
|
||||
const esp_vfs_littlefs_conf_t conf = {
|
||||
.base_path = LITTLEFS_MOUNT_POINT,
|
||||
.blockdev = part,
|
||||
.format_if_mount_failed = true,
|
||||
.dont_mount = false,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(esp_vfs_littlefs_register(&conf), TAG, "LittleFS mount failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Step 1: obtain the whole-disk block device. */
|
||||
esp_blockdev_handle_t disk = NULL;
|
||||
ESP_ERROR_CHECK(obtain_whole_disk_bdl(&disk));
|
||||
ESP_LOGI(TAG, "Whole disk BDL: disk_size=%" PRIu64 ", read_size=%zu, write_size=%zu, erase_size=%zu",
|
||||
disk->geometry.disk_size, disk->geometry.read_size, disk->geometry.write_size, disk->geometry.erase_size);
|
||||
|
||||
/* Step 2: lay out and write the MBR partition table. The partition start
|
||||
* addresses (and the SPI flash FAT size) are assigned by esp_ext_part_tables during
|
||||
* generation; the actual offsets are logged from the read-back below. */
|
||||
#if CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
||||
ESP_LOGI(TAG, "Layout: LittleFS = %u KiB (fixed), FAT = remainder of the disk",
|
||||
(unsigned)(LITTLEFS_PARTITION_SIZE / 1024));
|
||||
#else
|
||||
ESP_LOGI(TAG, "Layout: LittleFS = %u KiB (fixed), FAT = %u KiB (fixed)",
|
||||
(unsigned)(LITTLEFS_PARTITION_SIZE / 1024), (unsigned)(FAT_PARTITION_SIZE / 1024));
|
||||
#endif
|
||||
ESP_LOGI(TAG, "Writing MBR partition table to the whole disk");
|
||||
ESP_ERROR_CHECK(build_and_write_partition_table(disk));
|
||||
|
||||
/* Step 3: read the partition table back and create a generic-partition BDL
|
||||
* for each entry, mounting the matching filesystem. */
|
||||
ESP_LOGI(TAG, "Reading MBR partition table back");
|
||||
esp_ext_part_list_t part_list = {0};
|
||||
esp_mbr_parse_extra_args_t parse_args = {
|
||||
.sector_size = ESP_EXT_PART_SECTOR_SIZE_512B,
|
||||
/* .match left zero (fn == NULL): keep every recognized partition (the two
|
||||
* we wrote). To have the parser drop non-mountable entries up front, set
|
||||
* .match = esp_ext_part_match_mountable() instead. */
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_ext_part_list_bdl_read(disk, &part_list, ESP_EXT_PART_LIST_SIGNATURE_MBR, &parse_args));
|
||||
|
||||
/* A LOSSY list means the parser could not represent every on-disk partition
|
||||
* (e.g. it was filtered, or the table held more than the list can hold). */
|
||||
if (part_list.flags & ESP_EXT_PART_LIST_FLAG_LOSSY) {
|
||||
ESP_LOGW(TAG, "Parsed partition list is LOSSY (some entries were dropped)");
|
||||
}
|
||||
|
||||
#if CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
||||
esp_blockdev_handle_t wl_part = NULL;
|
||||
#endif
|
||||
esp_blockdev_handle_t fat_part = NULL;
|
||||
esp_blockdev_handle_t lfs_part = NULL;
|
||||
|
||||
/* esp_ext_part_match_mountable() reports whether a partition holds a
|
||||
* filesystem this build can mount: FAT (always part of ESP-IDF) and LittleFS
|
||||
* when the LittleFS component is linked. esp_ext_part_tables detects LittleFS
|
||||
* via its build system (it defines ESP_EXT_PART_HAS_LITTLEFS when the
|
||||
* component is present), so the stock predicate works here without a custom
|
||||
* one. esp_ext_part_list_next_matching() then walks only the matching
|
||||
* (mountable) entries; any other partition types are skipped automatically. */
|
||||
esp_ext_part_match_t mountable = esp_ext_part_match_mountable();
|
||||
|
||||
int index = 0;
|
||||
for (esp_ext_part_list_item_t *it = esp_ext_part_list_next_matching(NULL, &part_list, &mountable);
|
||||
it != NULL;
|
||||
it = esp_ext_part_list_next_matching(it, &part_list, &mountable), index++) {
|
||||
ESP_LOGI(TAG, "Mountable partition %d: type=%u, address=0x%08llx, size=0x%08llx",
|
||||
index, (unsigned)it->info.type,
|
||||
(unsigned long long)it->info.address, (unsigned long long)it->info.size);
|
||||
|
||||
/* FAT and LittleFS need different mount calls. */
|
||||
switch (it->info.type) {
|
||||
case ESP_EXT_PART_TYPE_FAT12:
|
||||
case ESP_EXT_PART_TYPE_FAT16:
|
||||
case ESP_EXT_PART_TYPE_FAT32:
|
||||
#if CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
||||
ESP_ERROR_CHECK(esp_blockdev_generic_partition_get(disk, it->info.address, it->info.size, &wl_part));
|
||||
ESP_ERROR_CHECK(wl_get_blockdev(wl_part, &fat_part));
|
||||
#else
|
||||
ESP_ERROR_CHECK(esp_blockdev_generic_partition_get(disk, it->info.address, it->info.size, &fat_part));
|
||||
#endif
|
||||
ESP_ERROR_CHECK(mount_fat(fat_part));
|
||||
write_and_read_back(FAT_MOUNT_POINT "/hello.txt", "Hello from FATFS over a generic-partition BDL!");
|
||||
break;
|
||||
case ESP_EXT_PART_TYPE_LITTLEFS:
|
||||
ESP_ERROR_CHECK(esp_blockdev_generic_partition_get(disk, it->info.address, it->info.size, &lfs_part));
|
||||
ESP_ERROR_CHECK(mount_littlefs(lfs_part));
|
||||
write_and_read_back(LITTLEFS_MOUNT_POINT "/hello.txt", "Hello from LittleFS over a generic-partition BDL!");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "No mount handler for mountable type %u", (unsigned)it->info.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Step 4: tear everything down. */
|
||||
|
||||
esp_ext_part_list_deinit(&part_list);
|
||||
if (fat_part != NULL) {
|
||||
ESP_LOGI(TAG, "Unmounting FATFS");
|
||||
ESP_ERROR_CHECK(esp_vfs_fat_bdl_unmount(FAT_MOUNT_POINT, fat_part));
|
||||
/* FAT unmount does NOT release the BDL handle - the caller owns it. */
|
||||
ESP_ERROR_CHECK(fat_part->ops->release(fat_part));
|
||||
#if CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH
|
||||
ESP_LOGI(TAG, "Releasing WL BDL");
|
||||
ESP_ERROR_CHECK(wl_part->ops->release(wl_part));
|
||||
#endif
|
||||
}
|
||||
if (lfs_part != NULL) {
|
||||
ESP_LOGI(TAG, "Unmounting LittleFS");
|
||||
/* LittleFS unregister releases the BDL handle for us. */
|
||||
ESP_ERROR_CHECK(esp_vfs_littlefs_unregister_blockdev(lfs_part));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Releasing whole-disk BDL");
|
||||
ESP_ERROR_CHECK(release_whole_disk_bdl(disk));
|
||||
|
||||
ESP_LOGI(TAG, "Done");
|
||||
}
|
||||
|
||||
static void write_and_read_back(const char *path, const char *content)
|
||||
{
|
||||
ESP_LOGI(TAG, "Writing '%s'", path);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (f == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open %s for writing", path);
|
||||
return;
|
||||
}
|
||||
fprintf(f, "%s\n", content);
|
||||
fclose(f);
|
||||
|
||||
FILE *fr = fopen(path, "r");
|
||||
if (fr == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open %s for reading", path);
|
||||
return;
|
||||
}
|
||||
char line[128] = {0};
|
||||
if (fgets(line, sizeof(line), fr) != NULL) {
|
||||
char *nl = strchr(line, '\n');
|
||||
if (nl) {
|
||||
*nl = '\0';
|
||||
}
|
||||
ESP_LOGI(TAG, "Read back from %s: '%s'", path, line);
|
||||
}
|
||||
fclose(fr);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
idf: ">=6.0"
|
||||
# Provides MBR parsing/generation and the BDL read/write helpers.
|
||||
espressif/esp_ext_part_tables: "^0.4.0"
|
||||
# LittleFS with Block Device Layer (BDL) support (esp_vfs_littlefs_conf_t.blockdev).
|
||||
joltwallet/littlefs: "^1.22.2"
|
||||
211
examples/storage/generic_partition_bdl/main/sd_card_bdl.c
Normal file
211
examples/storage/generic_partition_bdl/main/sd_card_bdl.c
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* SD/eMMC whole-disk block device helpers for the generic-partition BDL example.
|
||||
*
|
||||
* Both the SDMMC and the SDSPI host paths initialize an `sdmmc_card_t` and then
|
||||
* hand it to `sdmmc_get_blockdev()`, so the rest of the example works with a
|
||||
* plain BDL handle and does not care which peripheral is used underneath.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "sdmmc_cmd.h"
|
||||
|
||||
#if CONFIG_EXAMPLE_SD_HOST_SDMMC
|
||||
#include "driver/sdmmc_host.h"
|
||||
#elif CONFIG_EXAMPLE_SD_HOST_SDSPI
|
||||
#include "driver/sdspi_host.h"
|
||||
#include "driver/spi_common.h"
|
||||
#endif
|
||||
|
||||
#if SOC_SDMMC_IO_POWER_EXTERNAL || SOC_SDMMC_IO_UHS_POWER_EXTERNAL
|
||||
#include "sd_pwr_ctrl_by_on_chip_ldo.h"
|
||||
#endif
|
||||
|
||||
#include "sd_card_bdl.h"
|
||||
|
||||
#if CONFIG_EXAMPLE_STORAGE_MEDIA_SDCARD
|
||||
|
||||
static const char *TAG = "example_sd";
|
||||
|
||||
#if CONFIG_EXAMPLE_SD_HOST_SDMMC
|
||||
static sdmmc_host_t s_host = SDMMC_HOST_DEFAULT();
|
||||
#elif CONFIG_EXAMPLE_SD_HOST_SDSPI
|
||||
static sdmmc_host_t s_host = SDSPI_HOST_DEFAULT();
|
||||
static spi_host_device_t s_spi_host_id; /* SPI bus id, saved before host.slot is reused for the device handle */
|
||||
#endif
|
||||
|
||||
static sdmmc_card_t *s_card;
|
||||
|
||||
#if SOC_SDMMC_IO_POWER_EXTERNAL || SOC_SDMMC_IO_UHS_POWER_EXTERNAL
|
||||
static sd_pwr_ctrl_handle_t s_pwr_ctrl_handle;
|
||||
|
||||
static esp_err_t init_power_control(sdmmc_host_t *host)
|
||||
{
|
||||
#if CONFIG_EXAMPLE_SD_PWR_CTRL_LDO_INTERNAL_IO
|
||||
sd_pwr_ctrl_ldo_config_t ldo_config = {
|
||||
.ldo_chan_id = CONFIG_EXAMPLE_SD_PWR_CTRL_LDO_IO_ID,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &s_pwr_ctrl_handle), TAG,
|
||||
"failed to create on-chip LDO power control driver");
|
||||
host->pwr_ctrl_handle = s_pwr_ctrl_handle;
|
||||
#else
|
||||
(void)host;
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void deinit_power_control(void)
|
||||
{
|
||||
if (s_pwr_ctrl_handle != NULL) {
|
||||
esp_err_t err = sd_pwr_ctrl_del_on_chip_ldo(s_pwr_ctrl_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "failed to delete on-chip LDO power control driver: %s", esp_err_to_name(err));
|
||||
}
|
||||
s_pwr_ctrl_handle = NULL;
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline esp_err_t init_power_control(sdmmc_host_t *host)
|
||||
{
|
||||
(void)host;
|
||||
return ESP_OK;
|
||||
}
|
||||
static inline void deinit_power_control(void) {}
|
||||
#endif
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
#if CONFIG_EXAMPLE_SD_HOST_SDMMC
|
||||
|
||||
static esp_err_t init_card(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Whole disk: SD/eMMC card via SDMMC (slot %d)", s_host.slot);
|
||||
|
||||
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
|
||||
#if CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_1
|
||||
slot_config.width = 1;
|
||||
#else
|
||||
slot_config.width = 4;
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_SDMMC_INTERNAL_PULLUP
|
||||
/* Boards without external pull-ups on the SD lines (e.g. ESP32-S3-USB-OTG)
|
||||
* need the internal ones enabled. */
|
||||
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
|
||||
#endif
|
||||
|
||||
#if SOC_SDMMC_USE_GPIO_MATRIX
|
||||
/* On SoCs that route the SDMMC controller through the GPIO matrix the slot
|
||||
* pins are assignable, so apply the configured GPIOs. */
|
||||
slot_config.clk = CONFIG_EXAMPLE_SDMMC_PIN_CLK;
|
||||
slot_config.cmd = CONFIG_EXAMPLE_SDMMC_PIN_CMD;
|
||||
slot_config.d0 = CONFIG_EXAMPLE_SDMMC_PIN_D0;
|
||||
#if CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4
|
||||
slot_config.d1 = CONFIG_EXAMPLE_SDMMC_PIN_D1;
|
||||
slot_config.d2 = CONFIG_EXAMPLE_SDMMC_PIN_D2;
|
||||
slot_config.d3 = CONFIG_EXAMPLE_SDMMC_PIN_D3;
|
||||
#endif
|
||||
#endif // SOC_SDMMC_USE_GPIO_MATRIX
|
||||
|
||||
ESP_RETURN_ON_ERROR(init_power_control(&s_host), TAG, "power control init failed");
|
||||
ESP_RETURN_ON_ERROR(sdmmc_host_init(), TAG, "SDMMC host init failed");
|
||||
ESP_RETURN_ON_ERROR(sdmmc_host_init_slot(s_host.slot, &slot_config), TAG, "SDMMC slot init failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void deinit_card(void)
|
||||
{
|
||||
sdmmc_host_deinit();
|
||||
deinit_power_control();
|
||||
}
|
||||
|
||||
#elif CONFIG_EXAMPLE_SD_HOST_SDSPI
|
||||
|
||||
static esp_err_t init_card(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Whole disk: SD card via SPI (SDSPI)");
|
||||
|
||||
/* SDSPI_HOST_DEFAULT() puts the SPI host id in host.slot; keep it for later
|
||||
* as host.slot is overwritten with the SDSPI device handle below. */
|
||||
s_spi_host_id = s_host.slot;
|
||||
|
||||
spi_bus_config_t bus_cfg = {
|
||||
.mosi_io_num = CONFIG_EXAMPLE_SDSPI_PIN_MOSI,
|
||||
.miso_io_num = CONFIG_EXAMPLE_SDSPI_PIN_MISO,
|
||||
.sclk_io_num = CONFIG_EXAMPLE_SDSPI_PIN_CLK,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = 4000,
|
||||
};
|
||||
|
||||
ESP_RETURN_ON_ERROR(init_power_control(&s_host), TAG, "power control init failed");
|
||||
ESP_RETURN_ON_ERROR(spi_bus_initialize(s_spi_host_id, &bus_cfg, SDSPI_DEFAULT_DMA), TAG,
|
||||
"failed to initialize SPI bus");
|
||||
|
||||
sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
||||
dev_config.gpio_cs = CONFIG_EXAMPLE_SDSPI_PIN_CS;
|
||||
dev_config.host_id = s_spi_host_id;
|
||||
|
||||
sdspi_dev_handle_t dev_handle;
|
||||
ESP_RETURN_ON_ERROR(sdspi_host_init(), TAG, "SDSPI host init failed");
|
||||
ESP_RETURN_ON_ERROR(sdspi_host_init_device(&dev_config, &dev_handle), TAG, "SDSPI device init failed");
|
||||
|
||||
/* Route the card commands through the freshly created SDSPI device. */
|
||||
s_host.slot = dev_handle;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void deinit_card(void)
|
||||
{
|
||||
sdspi_host_deinit();
|
||||
spi_bus_free(s_spi_host_id);
|
||||
deinit_power_control();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
esp_err_t example_sd_card_bdl_create(esp_blockdev_handle_t *out)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_LOGW(TAG, "This will overwrite the card's existing partition table!");
|
||||
|
||||
ESP_RETURN_ON_ERROR(init_card(), TAG, "card host init failed");
|
||||
|
||||
s_card = calloc(1, sizeof(sdmmc_card_t));
|
||||
ESP_GOTO_ON_FALSE(s_card != NULL, ESP_ERR_NO_MEM, cleanup, TAG, "no mem for card");
|
||||
|
||||
ESP_GOTO_ON_ERROR(sdmmc_card_init(&s_host, s_card), cleanup, TAG, "SD/eMMC card init failed");
|
||||
sdmmc_card_print_info(stdout, s_card);
|
||||
|
||||
ESP_GOTO_ON_ERROR(sdmmc_get_blockdev(s_card, out), cleanup, TAG, "SD/eMMC block device creation failed");
|
||||
return ESP_OK;
|
||||
|
||||
cleanup:
|
||||
free(s_card);
|
||||
s_card = NULL;
|
||||
deinit_card();
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t example_sd_card_bdl_release(esp_blockdev_handle_t disk)
|
||||
{
|
||||
esp_err_t err = disk->ops->release(disk);
|
||||
deinit_card();
|
||||
free(s_card);
|
||||
s_card = NULL;
|
||||
return err;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_EXAMPLE_STORAGE_MEDIA_SDCARD */
|
||||
38
examples/storage/generic_partition_bdl/main/sd_card_bdl.h
Normal file
38
examples/storage/generic_partition_bdl/main/sd_card_bdl.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_blockdev.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Create a whole-disk BDL backed by an SD/eMMC card.
|
||||
*
|
||||
* Initializes the card over the host peripheral selected in menuconfig
|
||||
* (SDMMC or SDSPI) and returns a block device that maps the whole card.
|
||||
*
|
||||
* @param[out] out Handle of the created whole-disk block device.
|
||||
* @return ESP_OK on success, an error code otherwise.
|
||||
*/
|
||||
esp_err_t example_sd_card_bdl_create(esp_blockdev_handle_t *out);
|
||||
|
||||
/**
|
||||
* @brief Release a whole-disk BDL previously created with
|
||||
* example_sd_card_bdl_create() and de-initialize the card host.
|
||||
*
|
||||
* @param disk Handle returned by example_sd_card_bdl_create().
|
||||
* @return ESP_OK on success, an error code otherwise.
|
||||
*/
|
||||
esp_err_t example_sd_card_bdl_release(esp_blockdev_handle_t disk);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
# Whole-disk data partition. The example writes an MBR onto it at run time and
|
||||
# splits it into a FAT and a LittleFS sub-partition using generic-partition BDLs.
|
||||
storage, data, fat, , 2M,
|
||||
|
@@ -0,0 +1,51 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
def _expect_partition_bdl_flow(dut: Dut) -> None:
|
||||
# The partitions are enumerated in on-disk order: LittleFS (MBR entry 0) comes
|
||||
# before FATFS (MBR entry 1), so the mount/read-back logs appear in that order.
|
||||
dut.expect('example: Writing MBR partition table to the whole disk', timeout=90)
|
||||
dut.expect('example: Reading MBR partition table back', timeout=90)
|
||||
dut.expect('example: Mounting LittleFS on the LittleFS partition BDL', timeout=90)
|
||||
dut.expect(
|
||||
"example: Read back from /littlefs/hello.txt: 'Hello from LittleFS over a generic-partition BDL!'",
|
||||
timeout=90,
|
||||
)
|
||||
dut.expect('example: Mounting FATFS on the FAT partition BDL', timeout=90)
|
||||
dut.expect("example: Read back from /fat/hello.txt: 'Hello from FATFS over a generic-partition BDL!'", timeout=90)
|
||||
dut.expect('example: Done', timeout=90)
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize('config', ['spiflash'], indirect=True)
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_examples_generic_partition_bdl_spiflash(dut: Dut) -> None:
|
||||
_expect_partition_bdl_flow(dut)
|
||||
|
||||
|
||||
# The SPI-flash configuration needs no external peripherals, so it can also run
|
||||
# under QEMU (which emulates SPI flash but has no SD host). The SD card configs
|
||||
# below require real hardware and therefore have no QEMU variant.
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.parametrize('config', ['spiflash'], indirect=True)
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_examples_generic_partition_bdl_spiflash_qemu(dut: Dut) -> None:
|
||||
_expect_partition_bdl_flow(dut)
|
||||
|
||||
|
||||
@pytest.mark.sdcard_sdmode
|
||||
@pytest.mark.parametrize('config', ['sdmmc'], indirect=True)
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_examples_generic_partition_bdl_sdmmc(dut: Dut) -> None:
|
||||
_expect_partition_bdl_flow(dut)
|
||||
|
||||
|
||||
@pytest.mark.sdcard_spimode
|
||||
@pytest.mark.parametrize('config', ['sdspi'], indirect=True)
|
||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
||||
def test_examples_generic_partition_bdl_sdspi(dut: Dut) -> None:
|
||||
_expect_partition_bdl_flow(dut)
|
||||
@@ -0,0 +1,9 @@
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
|
||||
# Whole-disk block device: SD/eMMC card over the SDMMC host
|
||||
CONFIG_EXAMPLE_STORAGE_MEDIA_SDCARD=y
|
||||
CONFIG_EXAMPLE_SD_HOST_SDMMC=y
|
||||
|
||||
# FATFS
|
||||
# Runtime FAT sector size is derived from the SD card BDL geometry (512 bytes).
|
||||
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=4096
|
||||
@@ -0,0 +1,9 @@
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
|
||||
# Whole-disk block device: SD/eMMC card over the SPI bus (SDSPI)
|
||||
CONFIG_EXAMPLE_STORAGE_MEDIA_SDCARD=y
|
||||
CONFIG_EXAMPLE_SD_HOST_SDSPI=y
|
||||
|
||||
# FATFS
|
||||
# Runtime FAT sector size is derived from the SD card BDL geometry (512 bytes).
|
||||
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=4096
|
||||
11
examples/storage/generic_partition_bdl/sdkconfig.ci.spiflash
Normal file
11
examples/storage/generic_partition_bdl/sdkconfig.ci.spiflash
Normal file
@@ -0,0 +1,11 @@
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
|
||||
# Whole-disk block device: SPI flash 'storage' data partition
|
||||
CONFIG_EXAMPLE_STORAGE_MEDIA_SPIFLASH=y
|
||||
|
||||
# FATFS
|
||||
# CONFIG_FATFS_SECTOR_4096=y # Does nothing in this case as the partitions are generated at run-time
|
||||
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=4096
|
||||
@@ -0,0 +1,8 @@
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
# FATFS
|
||||
# CONFIG_FATFS_SECTOR_* only selects the sector size for host-generated FAT images.
|
||||
# This example formats at run time using the sector size derived from BDL geometry.
|
||||
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=4096
|
||||
Reference in New Issue
Block a user