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
13 changes: 12 additions & 1 deletion .github/workflows/esp32-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ jobs:
idf-version: 'v5.5.3'
usb-serial: 'ON'
jit: false
- esp-idf-target: "esp32s3"
idf-version: 'v5.5.3'
usb-cdc: 'ON'
jit: false
- esp-idf-target: "esp32"
idf-version: 'v5.4.3'
libsodium: 'ON'
Expand Down Expand Up @@ -104,7 +108,7 @@ jobs:
idf.py add-dependency "espressif/libsodium^1.0.20~4"

- name: Add ESP TinyUSB dependency
if: matrix.usb-serial == 'ON'
if: matrix.usb-serial == 'ON' || matrix.usb-cdc == 'ON'
working-directory: ./src/platforms/esp32/
run: |
. $IDF_PATH/export.sh
Expand All @@ -115,11 +119,18 @@ jobs:
working-directory: ./src/platforms/esp32/
env:
USB_SERIAL: ${{ matrix.usb-serial || 'OFF' }}
USB_CDC: ${{ matrix.usb-cdc || 'OFF' }}
run: |
. $IDF_PATH/export.sh
if [[ "${USB_SERIAL}" == "ON" ]]; then
echo 'CONFIG_USE_USB_SERIAL=y' >> sdkconfig.defaults.in
fi
if [[ "${USB_CDC}" == "ON" ]]; then
printf '%s\n' \
'CONFIG_TINYUSB_CDC_ENABLED=y' \
'CONFIG_AVM_ENABLE_USB_CDC_PORT_DRIVER=y' \
>> sdkconfig.defaults.in
fi
export IDF_TARGET=${{matrix.esp-idf-target}}
if [ "${{ matrix.jit }}" = "true" ]; then
SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.jit" idf.py set-target ${{matrix.esp-idf-target}}
Expand Down
25 changes: 15 additions & 10 deletions .github/workflows/pico-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ jobs:
platform: "-DPICO_PLATFORM=rp2350-riscv"
jit: "-DAVM_DISABLE_JIT=OFF"

- board: "pico"
platform: ""
jit: ""
usb-cdc: "ON"

steps:
- name: Checkout repo
uses: actions/checkout@v4
Expand Down Expand Up @@ -172,7 +177,7 @@ jobs:
set -euo pipefail
mkdir build
cd build
cmake .. -G Ninja -DPICO_BOARD=${{ matrix.board }} ${{ matrix.platform }} ${{ matrix.jit }}
cmake .. -G Ninja -DPICO_BOARD=${{ matrix.board }} ${{ matrix.platform }} ${{ matrix.jit }} ${{ matrix.usb-cdc == 'ON' && '-DAVM_USB_CDC_PORT_DRIVER_ENABLED=ON' || '' }}
cmake --build . --target=AtomVM

- name: "Perform CodeQL Analysis"
Expand All @@ -187,7 +192,7 @@ jobs:
nvm install 24

- name: Build tests (without SMP)
if: matrix.board != 'pico2' && matrix.board != 'pico2_w'
if: matrix.board != 'pico2' && matrix.board != 'pico2_w' && matrix.usb-cdc != 'ON'
shell: bash
working-directory: ./src/platforms/rp2/
run: |
Expand All @@ -199,7 +204,7 @@ jobs:
cmake --build . --target=rp2_tests

- name: Run tests with rp2040js
if: matrix.board != 'pico2' && matrix.board != 'pico2_w'
if: matrix.board != 'pico2' && matrix.board != 'pico2_w' && matrix.usb-cdc != 'ON'
shell: bash
working-directory: ./src/platforms/rp2/tests
run: |
Expand All @@ -210,7 +215,7 @@ jobs:
npx tsx run-tests.ts ../build.nosmp/tests/rp2_tests.uf2 ../build.nosmp/tests/test_erl_sources/rp2_test_modules.uf2

- name: Rename AtomVM and write sha256sum
if: matrix.platform == '' && matrix.jit == ''
if: matrix.platform == '' && matrix.jit == '' && matrix.usb-cdc != 'ON'
shell: bash
run: |
pushd src/platforms/rp2/build
Expand All @@ -220,14 +225,14 @@ jobs:
popd

- name: Upload AtomVM artifact
if: matrix.platform == '' && matrix.jit == ''
if: matrix.platform == '' && matrix.jit == '' && matrix.usb-cdc != 'ON'
uses: actions/upload-artifact@v4
with:
name: AtomVM-${{ matrix.board }}-${{env.AVM_REF_NAME}}.uf2
path: src/platforms/rp2/build/src/AtomVM-${{ matrix.board }}-*.uf2

- name: Rename atomvmlib-rp2 and write sha256sum
if: matrix.platform == '' && matrix.jit == ''
if: matrix.platform == '' && matrix.jit == '' && matrix.usb-cdc != 'ON'
shell: bash
run: |
pushd build/libs
Expand All @@ -237,7 +242,7 @@ jobs:
popd

- name: Combine uf2 using uf2tool
if: matrix.platform == '' && matrix.jit == ''
if: matrix.platform == '' && matrix.jit == '' && matrix.usb-cdc != 'ON'
shell: bash
run: |
ATOMVM_COMBINED_FILE=AtomVM-${{ matrix.board }}-combined-${{env.AVM_REF_NAME}}.uf2
Expand All @@ -246,15 +251,15 @@ jobs:
echo "ATOMVM_COMBINED_FILE=${ATOMVM_COMBINED_FILE}" >> $GITHUB_ENV

- name: Upload combined AtomVM artifact
if: matrix.platform == '' && matrix.jit == ''
if: matrix.platform == '' && matrix.jit == '' && matrix.usb-cdc != 'ON'
uses: actions/upload-artifact@v4
with:
name: ${{ env.ATOMVM_COMBINED_FILE }}
path: ${{ env.ATOMVM_COMBINED_FILE }}

- name: Release (Pico & Pico2)
uses: softprops/action-gh-release@v3.0.0
if: startsWith(github.ref, 'refs/tags/') && matrix.board != 'pico_w' && matrix.board != 'pico2_w' && matrix.platform == '' && matrix.jit == ''
if: startsWith(github.ref, 'refs/tags/') && matrix.board != 'pico_w' && matrix.board != 'pico2_w' && matrix.platform == '' && matrix.jit == '' && matrix.usb-cdc != 'ON'
with:
draft: true
fail_on_unmatched_files: true
Expand All @@ -268,7 +273,7 @@ jobs:

- name: Release (PicoW & Pico2W)
uses: softprops/action-gh-release@v3.0.0
if: startsWith(github.ref, 'refs/tags/') && (matrix.board == 'pico_w' || matrix.board == 'pico2_w') && matrix.platform == '' && matrix.jit == ''
if: startsWith(github.ref, 'refs/tags/') && (matrix.board == 'pico_w' || matrix.board == 'pico2_w') && matrix.platform == '' && matrix.jit == '' && matrix.usb-cdc != 'ON'
with:
draft: true
fail_on_unmatched_files: true
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/stm32-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ jobs:
max_size: 524288
- device: stm32h562rgt6
max_size: 524288
usb-cdc: "OFF"
- device: stm32h562rgt6
max_size: 524288
usb-cdc: "ON"
Comment thread
pguyot marked this conversation as resolved.
- device: stm32f746zgt6
max_size: 524288
renode_platform: stm32f746.repl
Expand Down Expand Up @@ -162,7 +166,7 @@ jobs:
set -euo pipefail
mkdir build
cd build
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/arm-toolchain.cmake -DDEVICE=${{ matrix.device }}
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/arm-toolchain.cmake -DDEVICE=${{ matrix.device }} ${{ matrix.usb-cdc == 'ON' && '-DAVM_USB_CDC_PORT_DRIVER_ENABLED=ON' || '' }}
cmake --build .

- name: "Perform CodeQL Analysis"
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ 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 USB CDC port drivers for ESP32, RP2, and STM32 platforms

### Changed
- Updated network type db() to dbm() to reflect the actual representation of the type
Expand Down
74 changes: 73 additions & 1 deletion doc/src/distributed-erlang.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ Distribution over serial (UART) is also available for point-to-point
connections between any two nodes, including microcontrollers without
networking (e.g. STM32). See [Serial distribution](#serial-distribution).

Three examples are provided:
Distribution over USB CDC is also supported on ESP32-S2/S3, RP2040/RP2350,
and STM32, using the same `serial_dist` protocol. On Unix hosts, USB CDC
devices appear as standard serial ports. See [USB distribution](#usb-distribution).

Four examples are provided:

- disterl in `examples/erlang/disterl.erl`: distribution on Unix systems
- epmd\_disterl in `examples/erlang/esp32/epmd_disterl.erl`: distribution on ESP32 devices
- serial\_disterl in `examples/erlang/serial_disterl.erl`: distribution over serial (ESP32 and Unix)
- usb\_disterl in `examples/erlang/usb_disterl.erl`: distribution over USB CDC (all platforms)

## Starting and stopping distribution

Expand Down Expand Up @@ -240,6 +245,73 @@ This creates two pseudo-terminal devices (e.g. `/dev/ttys003` and
{some_registered_name, 'a@serial.local'} ! {self(), hello}.
```

## USB distribution

AtomVM supports distribution over USB CDC (Communications Device Class)
connections. USB CDC makes the device appear as a virtual serial port,
so it reuses the `serial_dist` module with a USB-specific HAL module.

See the [USB CDC](./programmers-guide.md#usb-cdc) section of the Programmer's
Guide for how to enable the port driver on each platform

### Platform support

| Platform | Module | Notes |
|----------|--------|-------|
| ESP32 (S2/S3) | `usb_cdc` | Requires TinyUSB CDC (`CONFIG_AVM_ENABLE_USB_CDC_PORT_DRIVER`) |
| RP2040/RP2350 | `usb_cdc` | Requires `AVM_USB_CDC_PORT_DRIVER_ENABLED`; disable `pico_enable_stdio_usb` |
| STM32 | `usb_cdc` | Requires TinyUSB integration and `AVM_USB_CDC_PORT_DRIVER_ENABLED` |
| Unix | `uart` | USB CDC devices appear as `/dev/ttyACMx` (Linux) or `/dev/cu.usbmodemXXXX` (macOS) |

### Quick start, MCU side (ESP32-S3 example)

```erlang
{ok, _} = net_kernel:start('sensor@serial.local', #{
name_domain => longnames,
proto_dist => serial_dist,
avm_dist_opts => #{
uart_opts => [{peripheral, "CDC0"}],
uart_module => usb_cdc
}
}).
```

### Quick start, Unix host side

On Unix, USB CDC devices are standard serial ports. Use the regular
`uart` module:

```erlang
{ok, _} = net_kernel:start('host@serial.local', #{
name_domain => longnames,
proto_dist => serial_dist,
avm_dist_opts => #{
uart_opts => [{peripheral, "/dev/ttyACM0"}, {speed, 115200}],
uart_module => uart
}
}).
```

### Multi-device topology

USB uses a star topology: one host connects to multiple devices through
a USB hub. Each device appears as a separate `/dev/ttyACMx` on the host.
Use `uart_ports` (list of proplists) to connect to multiple devices:

```erlang
{ok, _} = net_kernel:start('host@serial.local', #{
name_domain => longnames,
proto_dist => serial_dist,
avm_dist_opts => #{
uart_ports => [
[{peripheral, "/dev/ttyACM0"}, {speed, 115200}],
[{peripheral, "/dev/ttyACM1"}, {speed, 115200}]
],
uart_module => uart
}
}).
```

## Distribution features

Distribution implementation is (very) partial. The most basic features are available:
Expand Down
32 changes: 32 additions & 0 deletions doc/src/programmers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -2012,6 +2012,38 @@ ok = uart:close(UART)

Once the UART driver is closed, any calls to `uart` functions using a reference to the UART driver instance should return with the value `{error, noproc}`.

### USB CDC

On platforms with a USB device controller, AtomVM can expose a USB CDC (Communications Device Class) ACM interface, which appears on the host as a virtual serial port (`/dev/ttyACMx` on Linux, `/dev/cu.usbmodemXXXX` on macOS, a COM port on Windows). The same byte-stream interface can be used as a UART replacement (logs, REPLs, custom serial protocols) or as the transport for [distribution over USB CDC](./distributed-erlang.md#usb-distribution).

The Erlang-side API is the platform-specific `usb_cdc` module ([`libs/avm_esp32/src/usb_cdc.erl`](https://github.com/atomvm/AtomVM/blob/main/libs/avm_esp32/src/usb_cdc.erl), [`libs/avm_rp2/src/usb_cdc.erl`](https://github.com/atomvm/AtomVM/blob/main/libs/avm_rp2/src/usb_cdc.erl), [`libs/avm_stm32/src/usb_cdc.erl`](https://github.com/atomvm/AtomVM/blob/main/libs/avm_stm32/src/usb_cdc.erl)). It mirrors the `uart` API: `open/1,2`, `read/1,2`, `write/2`, `close/1`.

#### Platform support and build configuration

| Platform | Notes |
|----------|-------|
| ESP32 | Enable `CONFIG_USE_USB_SERIAL` and `CONFIG_AVM_ENABLE_USB_CDC_PORT_DRIVER` in `menuconfig`. The ESP-IDF `esp_tinyusb` component must be installed. |
| RP2040/RP2350 | Set `-DAVM_USB_CDC_PORT_DRIVER_ENABLED=ON` in CMake. You must also disable stdio over USB (`pico_enable_stdio_usb(AtomVM 0)`) so that the CDC interface is available for the port driver. |
| STM32 | Set `-DAVM_USB_CDC_PORT_DRIVER_ENABLED=ON` in CMake. TinyUSB is fetched automatically by default; set the `TINYUSB_PATH` environment variable to use a local checkout. |

#### USB VID/PID and string descriptors

USB CDC ACM is a standard device class, so on chip vendors that ship their own VID with a blessed "standard CDC" PID arrangement, AtomVM uses that pair by default and no override is required for the device class to be correctly identified by the host:

- **RP2**: `0x2E8A:0x0009` Raspberry Pi's registered "Pico SDK CDC UART" PID, chip-agnostic (covers RP2040 and RP2350), per the [raspberrypi/usb-pid registry](https://github.com/raspberrypi/usb-pid). The same pair is used by pico-sdk's `stdio_usb` and by any other firmware exposing standard CDC under Pi's VID, so host-side tooling cannot distinguish AtomVM-Pico from other CDC firmwares on this pair. Note that bootrom PIDs (`0x0003` on RP2040, `0x000F` on RP2350) must NOT be reused.
- **ESP32**: `0x303A` (Espressif VID) + TinyUSB-derived class-encoded PID, via the `esp_tinyusb` defaults (`CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=y`, `CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=y`). [Espressif's USB VID/PID guidance](https://docs.espressif.com/projects/esp-iot-solution/en/latest/usb/usb_overview/usb_vid_pid.html) explicitly states that USB standard-class devices built on TinyUSB do not need a separate PID under Espressif's VID.
- **STM32**: `0xCAFE:0x4001` TinyUSB example placeholder, **not** a vendor-issued identity. Production firmware should override this with a real VID/PID. It is possible to apply to ST for a PID.

If you want AtomVM to be distinguishable from other standard-CDC firmwares on the same chip (so host-side udev rules, drivers, or tooling can target it specifically), override the defaults:

- **RP2 / STM32**: `-DAVM_USB_CDC_VID=0xXXXX -DAVM_USB_CDC_PID=0xXXXX` in CMake. RP2 builds may register a project-specific PID for free under Pi's VID via the [usb-pid registry](https://github.com/raspberrypi/usb-pid).
- **ESP32**: set `CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=n` / `CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=n` and provide `CONFIG_TINYUSB_DESC_CUSTOM_VID` / `CONFIG_TINYUSB_DESC_CUSTOM_PID` in `menuconfig`.

The string descriptors can be overridden the same way:

- **RP2 / STM32** (CMake): `-DAVM_USB_CDC_MANUFACTURER="Your Company"`, `-DAVM_USB_CDC_PRODUCT="Your Product"`, `-DAVM_USB_CDC_INTERFACE_NAME="Your Product CDC ACM"`.
- **ESP32** (sdkconfig): `CONFIG_TINYUSB_DESC_MANUFACTURER_STRING`, `CONFIG_TINYUSB_DESC_PRODUCT_STRING`, `CONFIG_TINYUSB_DESC_CDC_STRING`.

### LED Control

The LED Control API can be used to drive LEDs, as well as generate PWM signals on GPIO pins.
Expand Down
1 change: 1 addition & 0 deletions examples/erlang/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pack_runnable(logging_example logging_example estdlib eavmlib)
pack_runnable(http_client http_client estdlib eavmlib avm_network)
pack_runnable(disterl disterl estdlib)
pack_runnable(serial_disterl serial_disterl eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_unix)
pack_runnable(usb_disterl usb_disterl eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2 avm_stm32 avm_unix)
pack_runnable(i2c_scanner i2c_scanner eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2 avm_stm32)
pack_runnable(i2c_lis3dh i2c_lis3dh eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2 avm_stm32)
pack_runnable(spi_flash spi_flash eavmlib estdlib DIALYZE_AGAINST avm_esp32 avm_rp2 avm_stm32)
Expand Down
Loading
Loading