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
5 changes: 5 additions & 0 deletions .github/workflows/build-and-test-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ jobs:
run: |
./tests/test-structs

- name: "Test: test-term"
working-directory: build
run: |
./tests/test-term

- name: "Test: test_etest.avm"
timeout-minutes: 5
working-directory: build
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/build-and-test-on-freebsd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ jobs:
cd build
./tests/test-structs

- name: "Test: test-term"
shell: freebsd {0}
run: |
cd $GITHUB_WORKSPACE;
cd build
./tests/test-term

- name: "Test: test_etest.avm"
shell: freebsd {0}
run: |
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,19 @@ jobs:
ulimit -c unlimited
./tests/test-structs

- name: "Test: test-term with valgrind"
if: matrix.library-arch == ''
working-directory: build
run: |
ulimit -c unlimited
valgrind --error-exitcode=1 ./tests/test-term

- name: "Test: test-term"
working-directory: build
run: |
ulimit -c unlimited
./tests/test-term

- name: "Test: test_etest.avm with valgrind"
if: matrix.library-arch == ''
timeout-minutes: 5
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/build-linux-artifacts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ jobs:
make test-heap &&
make test-mailbox &&
make test-structs &&
make test-term &&
file ./tests/test-erlang &&
./tests/test-erlang -s prime_smp &&
file ./tests/test-enif &&
Expand All @@ -246,6 +247,8 @@ jobs:
./tests/test-mailbox &&
file ./tests/test-structs &&
./tests/test-structs &&
file ./tests/test-term &&
./tests/test-term &&
file ./src/AtomVM &&
./src/AtomVM tests/libs/etest/test_etest.avm &&
./src/AtomVM tests/libs/estdlib/test_estdlib.avm &&
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
sending to an alias reference
- 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
- Added a Linux `gpio` driver for the generic_unix port (in `avm_unix`) using sysfs
Expand Down Expand Up @@ -90,6 +92,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed a bug where negative or oversized segment sizes were not rejected in binary matching
- Fixed the `network` mdns configuration to read the documented `host` key; the previously
required, undocumented `hostname` key is still accepted
- Fixed `term_is_uint32` accepting big integers whose low 64 bits are within range on 32-bit
builds, which made `erlang:crc32/2`, `erlang:crc32_combine/3` and `crypto:pbkdf2_hmac/5`
silently truncate huge integer arguments instead of raising `badarg`

## [0.7.0-alpha.1] - 2026-04-06

Expand Down
52 changes: 52 additions & 0 deletions doc/src/programmers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1394,6 +1394,58 @@ These functions allow you to work with external storage devices or partitions on
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),
```

All `esp:sdcard_*` functions are synchronous: like `esp:mount/4`, they run on the scheduler
that executes the calling process and block it for the duration of the card transaction
(typically milliseconds).

```{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. A card opened with `"sdspi"` must be closed with
`esp:sdcard_close/1` before the SPI bus is closed with `spi:close/1`: closing the bus first
leaves the SPI host unusable until the device is restarted.
```

### Restart and Sleep

You can use the [`esp:restart/0`](./apidocs/erlang/eavmlib/esp.md#restart0) function to immediately restart the ESP32 device. This function does not return a value.
Expand Down
88 changes: 88 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() :: reference().

-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,86 @@ 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.
%%
%% A card opened with `"sdspi"' must be closed before the SPI bus is
%% closed with `spi:close/1'.
%% @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
2 changes: 1 addition & 1 deletion src/libAtomVM/term.h
Original file line number Diff line number Diff line change
Expand Up @@ -1746,7 +1746,7 @@ static inline bool term_is_uint32(term t)
#endif

#if BOXED_TERMS_REQUIRED_FOR_INT != BOXED_TERMS_REQUIRED_FOR_INT64
} else {
} else if (term_boxed_size(t) == BOXED_TERMS_REQUIRED_FOR_INT64) {
avm_int64_t unboxed64 = term_unbox_int64(t);
return unboxed64 <= UINT32_MAX;
#endif
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