Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for the `safe` option in `erlang:binary_to_term/2`
- Added xtensa JIT backend for esp32 platform
- Added support for configuring pins and width for sdmmc on ESP32
- Added `esp:sdcard_open/2`, `esp:sdcard_read/2`, `esp:sdcard_write/3`, `esp:sdcard_info/1` and
`esp:sdcard_close/1` to the ESP32 `esp` module for low level SD card block (sector) access
- Added support for map comprehensions
- Added USB CDC port drivers for ESP32, RP2, and STM32 platforms

Expand Down
46 changes: 46 additions & 0 deletions doc/src/programmers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,52 @@ These functions allow you to work with external storage devices or partitions on

```{important}
Remember to properly unmount any mounted filesystems before powering off or resetting the device to prevent data corruption.

#### Raw SD card block access

In addition to mounting a FAT filesystem, AtomVM can access an SD card at the block (sector)
level, without mounting any filesystem. This is useful for implementing custom filesystems,
inspecting partition tables, or reading and writing raw images.

Open the card with [`esp:sdcard_open/2`](./apidocs/erlang/eavmlib/esp.md#sdcard_open-2). The
source string (`"sdmmc"` or `"sdspi"`) and the options are the same as for `esp:mount/4`:

```erlang
{ok, SDCard} = esp:sdcard_open("sdmmc", []),
```

For an SPI attached card, first create a SPI instance, then pass the `spi_host` and `cs`
options, exactly as with `esp:mount/4`:

```erlang
SPI = spi:open(SPIConfig),
{ok, SDCard} = esp:sdcard_open("sdspi", [{spi_host, SPI}, {cs, 5}]),
```

Use [`esp:sdcard_info/1`](./apidocs/erlang/eavmlib/esp.md#sdcard_info-1) to obtain the card
geometry, then read and write individual sectors with
[`esp:sdcard_read/2`](./apidocs/erlang/eavmlib/esp.md#sdcard_read-2) and
[`esp:sdcard_write/3`](./apidocs/erlang/eavmlib/esp.md#sdcard_write-3). The data passed to
`esp:sdcard_write/3` must be exactly one sector in size:

```erlang
{ok, #{sector_size := SectorSize, sector_count := SectorCount}} = esp:sdcard_info(SDCard),
{ok, Sector0} = esp:sdcard_read(SDCard, 0),
ok = esp:sdcard_write(SDCard, SectorCount - 1, <<0:(SectorSize * 8)>>),
```

When you are done, close the card with
[`esp:sdcard_close/1`](./apidocs/erlang/eavmlib/esp.md#sdcard_close-1):

```erlang
ok = esp:sdcard_close(SDCard),
```

```{warning}
A given physical card should not be accessed through `esp:sdcard_open/2` and `esp:mount/4` at
the same time. Writing raw sectors bypasses any filesystem, so be careful not to corrupt a
filesystem you intend to keep using.
```
```

### Restart and Sleep
Expand Down
85 changes: 85 additions & 0 deletions libs/avm_esp32/src/esp.erl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
sleep_enable_timer_wakeup/1,
mount/4,
umount/1,
sdcard_open/2,
sdcard_read/2,
sdcard_write/3,
sdcard_info/1,
sdcard_close/1,
nvs_fetch_binary/2,
nvs_get_binary/1, nvs_get_binary/2, nvs_get_binary/3,
nvs_set_binary/2, nvs_set_binary/3,
Expand Down Expand Up @@ -148,6 +153,8 @@
| {cd, non_neg_integer()}.
-type mount_options() :: [sdmmc_mount_option() | sdspi_mount_option()] | #{atom() => term()}.

-opaque sdcard() :: binary().

-export_type(
[
esp_reset_reason/0,
Expand All @@ -160,6 +167,7 @@
esp_partition_props/0,
mounted_fs/0,
mount_options/0,
sdcard/0,
task_wdt_config/0,
task_wdt_user_handle/0
]
Expand Down Expand Up @@ -376,6 +384,83 @@ mount(_Source, _Target, _FS, _Opts) ->
umount(_Target) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param Source the device to open, either `"sdmmc"' or `"sdspi"'
%% @param Opts options for the device
%% @returns either a tuple having `ok' and the SD card resource, or an error tuple
%% @doc Open an SD card for raw block (sector) access, without mounting any
%% filesystem on it. The returned resource is used with
%% {@link sdcard_read/2}, {@link sdcard_write/3}, {@link sdcard_info/1}
%% and {@link sdcard_close/1}. The card is automatically closed when the
%% resource is garbage collected, but {@link sdcard_close/1} should be
%% preferred.
%%
%% The same options as {@link mount/4} are accepted.
%%
%% A given physical card should not be used through `sdcard_open/2' and
%% {@link mount/4} at the same time.
%% @end
%%-----------------------------------------------------------------------------
-spec sdcard_open(
Source :: unicode:chardata(),
Opts :: mount_options()
) -> {ok, sdcard()} | {error, term()}.
sdcard_open(_Source, _Opts) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param SDCard the SD card resource returned by {@link sdcard_open/2}
%% @param Sector the sector (block) number to read
%% @returns a tuple with `ok' and the sector data as a binary, or an error tuple
%% @doc Read a single sector from the SD card. The size of the returned
%% binary is the sector size reported by {@link sdcard_info/1} (usually
%% 512 bytes).
%% @end
%%-----------------------------------------------------------------------------
-spec sdcard_read(SDCard :: sdcard(), Sector :: non_neg_integer()) ->
{ok, binary()} | {error, term()}.
sdcard_read(_SDCard, _Sector) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param SDCard the SD card resource returned by {@link sdcard_open/2}
%% @param Sector the sector (block) number to write
%% @param Data the sector data, its size must be exactly the sector size
%% @returns `ok' or an error tuple
%% @doc Write a single sector to the SD card. `Data' must be a binary whose
%% size is exactly the sector size reported by {@link sdcard_info/1}.
%% @end
%%-----------------------------------------------------------------------------
-spec sdcard_write(SDCard :: sdcard(), Sector :: non_neg_integer(), Data :: binary()) ->
ok | {error, term()}.
sdcard_write(_SDCard, _Sector, _Data) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param SDCard the SD card resource returned by {@link sdcard_open/2}
%% @returns a tuple with `ok' and a map describing the card, or an error tuple
%% @doc Return information about the SD card. The map contains the
%% `sector_size' (in bytes) and `sector_count' (the number of sectors)
%% keys.
%% @end
%%-----------------------------------------------------------------------------
-spec sdcard_info(SDCard :: sdcard()) ->
{ok, #{sector_size := pos_integer(), sector_count := non_neg_integer()}}
| {error, term()}.
sdcard_info(_SDCard) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param SDCard the SD card resource returned by {@link sdcard_open/2}
%% @returns `ok' or an error tuple
%% @doc Close a previously opened SD card and release the underlying
%% peripheral. The resource must not be used after it has been closed.
%% @end
%%-----------------------------------------------------------------------------
-spec sdcard_close(SDCard :: sdcard()) -> ok | {error, term()}.
sdcard_close(_SDCard) ->
erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param Namespace NVS namespace
%% @param Key NVS key
Expand Down
4 changes: 4 additions & 0 deletions src/platforms/esp32/components/avm_builtins/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ config AVM_ENABLE_STORAGE_NIFS
bool "Enable Storage NIFs"
default y

config AVM_ENABLE_RAW_SDCARD_NIFS
bool "Enable raw SD card block device NIFs"
default y

config AVM_ENABLE_GPIO_PORT_DRIVER
bool "Enable GPIO port driver"
default y
Expand Down
Loading
Loading