From e6e1c62ef4cc22b78cceef466964e2edd8ba397b Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Tue, 17 Feb 2026 17:35:26 +0100 Subject: [PATCH] Extract platform-specific modules from eavmlib Create new avm_emscripten, avm_esp32, avm_network, avm_rp2 and avm_stm32 libraries and build atomvmlib--.avm files for each platform and jit combination. Create new `{gpio,i2c,spi,uart}_hal` behavior module that serve as abstractions for all platforms. Signed-off-by: Paul Guyot --- .github/workflows/build-libraries.yaml | 16 +- .github/workflows/pico-build.yaml | 16 +- .github/workflows/run-tests-with-beam.yaml | 2 +- .github/workflows/wasm-build.yaml | 2 +- CMakeModules/BuildErlang.cmake | 136 ++++--- examples/elixir/esp32/CMakeLists.txt | 6 +- examples/elixir/stm32/CMakeLists.txt | 2 +- examples/emscripten/CMakeLists.txt | 10 +- examples/erlang/CMakeLists.txt | 6 +- examples/erlang/esp32/CMakeLists.txt | 32 +- examples/erlang/stm32/CMakeLists.txt | 14 +- libs/CMakeLists.txt | 106 ++++-- libs/avm_emscripten/src/CMakeLists.txt | 51 +++ .../src/emscripten.erl | 0 .../src/websocket.erl | 0 libs/avm_esp32/src/CMakeLists.txt | 57 +++ libs/{eavmlib => avm_esp32}/src/esp.erl | 0 libs/{eavmlib => avm_esp32}/src/esp_adc.erl | 0 libs/{eavmlib => avm_esp32}/src/esp_dac.erl | 0 libs/{eavmlib => avm_esp32}/src/gpio.erl | 139 +------- libs/{eavmlib => avm_esp32}/src/i2c.erl | 3 + libs/{eavmlib => avm_esp32}/src/ledc.erl | 0 libs/{eavmlib => avm_esp32}/src/spi.erl | 2 + libs/{eavmlib => avm_esp32}/src/uart.erl | 3 + libs/avm_network/src/CMakeLists.txt | 55 +++ .../src/ahttp_client.erl | 0 libs/{eavmlib => avm_network}/src/epmd.erl | 0 .../src/http_server.erl | 0 libs/{eavmlib => avm_network}/src/mdns.erl | 0 libs/{eavmlib => avm_network}/src/network.erl | 0 .../src/network_fsm.erl | 0 libs/avm_rp2/src/CMakeLists.txt | 51 +++ libs/avm_rp2/src/gpio.erl | 309 ++++++++++++++++ libs/{eavmlib => avm_rp2}/src/pico.erl | 0 libs/avm_stm32/src/CMakeLists.txt | 50 +++ libs/avm_stm32/src/gpio.erl | 336 ++++++++++++++++++ libs/eavmlib/src/CMakeLists.txt | 21 +- libs/eavmlib/src/gpio_hal.erl | 94 +++++ libs/eavmlib/src/i2c_hal.erl | 75 ++++ libs/eavmlib/src/spi_hal.erl | 76 ++++ libs/eavmlib/src/uart_hal.erl | 53 +++ libs/esp32boot/CMakeLists.txt | 4 +- .../emscripten/tests/src/test_atomvm.html | 1 + .../emscripten/tests/src/test_call.html | 2 +- .../emscripten/tests/src/test_html5.html | 2 +- .../emscripten/tests/src/test_websockets.html | 2 +- .../test/main/test_erl_sources/CMakeLists.txt | 4 +- .../rp2/tests/test_erl_sources/CMakeLists.txt | 6 +- .../tests/test_erl_sources/CMakeLists.txt | 2 +- tests/libs/eavmlib/CMakeLists.txt | 2 +- tests/libs/estdlib/CMakeLists.txt | 2 +- 51 files changed, 1462 insertions(+), 288 deletions(-) create mode 100644 libs/avm_emscripten/src/CMakeLists.txt rename libs/{eavmlib => avm_emscripten}/src/emscripten.erl (100%) rename libs/{eavmlib => avm_emscripten}/src/websocket.erl (100%) create mode 100644 libs/avm_esp32/src/CMakeLists.txt rename libs/{eavmlib => avm_esp32}/src/esp.erl (100%) rename libs/{eavmlib => avm_esp32}/src/esp_adc.erl (100%) rename libs/{eavmlib => avm_esp32}/src/esp_dac.erl (100%) rename libs/{eavmlib => avm_esp32}/src/gpio.erl (72%) rename libs/{eavmlib => avm_esp32}/src/i2c.erl (99%) rename libs/{eavmlib => avm_esp32}/src/ledc.erl (100%) rename libs/{eavmlib => avm_esp32}/src/spi.erl (99%) rename libs/{eavmlib => avm_esp32}/src/uart.erl (99%) create mode 100644 libs/avm_network/src/CMakeLists.txt rename libs/{eavmlib => avm_network}/src/ahttp_client.erl (100%) rename libs/{eavmlib => avm_network}/src/epmd.erl (100%) rename libs/{eavmlib => avm_network}/src/http_server.erl (100%) rename libs/{eavmlib => avm_network}/src/mdns.erl (100%) rename libs/{eavmlib => avm_network}/src/network.erl (100%) rename libs/{eavmlib => avm_network}/src/network_fsm.erl (100%) create mode 100644 libs/avm_rp2/src/CMakeLists.txt create mode 100644 libs/avm_rp2/src/gpio.erl rename libs/{eavmlib => avm_rp2}/src/pico.erl (100%) create mode 100644 libs/avm_stm32/src/CMakeLists.txt create mode 100644 libs/avm_stm32/src/gpio.erl create mode 100644 libs/eavmlib/src/gpio_hal.erl create mode 100644 libs/eavmlib/src/i2c_hal.erl create mode 100644 libs/eavmlib/src/spi_hal.erl create mode 100644 libs/eavmlib/src/uart_hal.erl diff --git a/.github/workflows/build-libraries.yaml b/.github/workflows/build-libraries.yaml index 7c7d9eaaee..bb8894f83b 100644 --- a/.github/workflows/build-libraries.yaml +++ b/.github/workflows/build-libraries.yaml @@ -113,9 +113,11 @@ jobs: - name: "Rename and write sha256sum" working-directory: build run: | - ATOMVMLIB_FILE=atomvmlib-${{ github.ref_name }}.avm - mv libs/atomvmlib.avm "libs/${ATOMVMLIB_FILE}" && - sha256sum "libs/${ATOMVMLIB_FILE}" > "libs/${ATOMVMLIB_FILE}.sha256" + for variant in atomvmlib atomvmlib-esp32 atomvmlib-rp2 atomvmlib-stm32 atomvmlib-emscripten; do + VARIANT_FILE="${variant}-${{ github.ref_name }}.avm" + mv "libs/${variant}.avm" "libs/${VARIANT_FILE}" && + sha256sum "libs/${VARIANT_FILE}" > "libs/${VARIANT_FILE}.sha256" + done HELLO_WORLD_FILE=hello_world-${{ github.ref_name }}.avm mv examples/erlang/hello_world.avm "examples/erlang/${HELLO_WORLD_FILE}" sha256sum "examples/erlang/${HELLO_WORLD_FILE}" > "examples/erlang/${HELLO_WORLD_FILE}.sha256" @@ -129,5 +131,13 @@ jobs: files: | build/libs/atomvmlib-${{ github.ref_name }}.avm build/libs/atomvmlib-${{ github.ref_name }}.avm.sha256 + build/libs/atomvmlib-esp32-${{ github.ref_name }}.avm + build/libs/atomvmlib-esp32-${{ github.ref_name }}.avm.sha256 + build/libs/atomvmlib-rp2-${{ github.ref_name }}.avm + build/libs/atomvmlib-rp2-${{ github.ref_name }}.avm.sha256 + build/libs/atomvmlib-stm32-${{ github.ref_name }}.avm + build/libs/atomvmlib-stm32-${{ github.ref_name }}.avm.sha256 + build/libs/atomvmlib-emscripten-${{ github.ref_name }}.avm + build/libs/atomvmlib-emscripten-${{ github.ref_name }}.avm.sha256 build/examples/erlang/hello_world-${{ github.ref_name }}.avm build/examples/erlang/hello_world-${{ github.ref_name }}.avm.sha256 diff --git a/.github/workflows/pico-build.yaml b/.github/workflows/pico-build.yaml index 379add078f..41a0d0f7ab 100644 --- a/.github/workflows/pico-build.yaml +++ b/.github/workflows/pico-build.yaml @@ -59,14 +59,14 @@ jobs: https://repo.hex.pm https://cdn.jsdelivr.net/hex - - name: Build atomvmlib-pico.uf2/atomvmlib-pico2.uf2 + - name: Build atomvmlib-rp2-pico.uf2/atomvmlib-rp2-pico2.uf2 shell: bash run: | set -euo pipefail mkdir build cd build cmake .. -G Ninja - cmake --build . -t atomvmlib + cmake --build . -t atomvmlib-rp2 - name: Upload atomvmlib artifacts uses: actions/upload-artifact@v4 @@ -225,13 +225,13 @@ jobs: name: AtomVM-${{ matrix.board }}-${{env.AVM_REF_NAME}}.uf2 path: src/platforms/rp2/build/src/AtomVM-${{ matrix.board }}-*.uf2 - - name: Rename atomvmlib and write sha256sum + - name: Rename atomvmlib-rp2 and write sha256sum if: matrix.platform == '' && matrix.jit == '' shell: bash run: | pushd build/libs - ATOMVMLIB_FILE=atomvmlib-${{ matrix.board }}-${{env.AVM_REF_NAME}}.uf2 - mv atomvmlib-${{matrix.board == 'pico_w' && 'pico' || matrix.board == 'pico2_w' && 'pico2' || matrix.board}}.uf2 "${ATOMVMLIB_FILE}" + ATOMVMLIB_FILE=atomvmlib-rp2-${{ matrix.board }}-${{env.AVM_REF_NAME}}.uf2 + mv atomvmlib-rp2-${{matrix.board == 'pico_w' && 'pico' || matrix.board == 'pico2_w' && 'pico2' || matrix.board}}.uf2 "${ATOMVMLIB_FILE}" sha256sum "${ATOMVMLIB_FILE}" > "${ATOMVMLIB_FILE}.sha256" popd @@ -240,7 +240,7 @@ jobs: shell: bash run: | ATOMVM_COMBINED_FILE=AtomVM-combined-${{ matrix.board }}-${{env.AVM_REF_NAME}}.uf2 - ./uf2tool join -o "${ATOMVM_COMBINED_FILE}" src/platforms/rp2/build/src/AtomVM-${{ matrix.board }}-${{env.AVM_REF_NAME}}.uf2 build/libs/atomvmlib-${{ matrix.board }}-${{env.AVM_REF_NAME}}.uf2 + ./uf2tool join -o "${ATOMVM_COMBINED_FILE}" src/platforms/rp2/build/src/AtomVM-${{ matrix.board }}-${{env.AVM_REF_NAME}}.uf2 build/libs/atomvmlib-rp2-${{ matrix.board }}-${{env.AVM_REF_NAME}}.uf2 sha256sum "${ATOMVM_COMBINED_FILE}" > "${ATOMVM_COMBINED_FILE}.sha256" echo "ATOMVM_COMBINED_FILE=${ATOMVM_COMBINED_FILE}" >> $GITHUB_ENV @@ -260,8 +260,8 @@ jobs: files: | src/platforms/rp2/build/src/AtomVM-${{ matrix.board }}-${{ github.ref_name }}.uf2 src/platforms/rp2/build/src/AtomVM-${{ matrix.board }}-${{ github.ref_name }}.uf2.sha256 - build/libs/atomvmlib-${{ matrix.board }}-${{ github.ref_name }}.uf2 - build/libs/atomvmlib-${{ matrix.board }}-${{ github.ref_name }}.uf2.sha256 + build/libs/atomvmlib-rp2-${{ matrix.board }}-${{ github.ref_name }}.uf2 + build/libs/atomvmlib-rp2-${{ matrix.board }}-${{ github.ref_name }}.uf2.sha256 ${{ env.ATOMVM_COMBINED_FILE }} ${{ env.ATOMVM_COMBINED_FILE }}.sha256 diff --git a/.github/workflows/run-tests-with-beam.yaml b/.github/workflows/run-tests-with-beam.yaml index 40f206c05c..b75125b823 100644 --- a/.github/workflows/run-tests-with-beam.yaml +++ b/.github/workflows/run-tests-with-beam.yaml @@ -186,7 +186,7 @@ jobs: working-directory: build run: | export PATH="${{ matrix.path_prefix }}$PATH" - erl -pa tests/libs/estdlib/ -pa tests/libs/estdlib/beams/ -pa libs/etest/src/beams -pa libs/eavmlib/src/beams -s tests -s init stop -noshell + erl -pa tests/libs/estdlib/ -pa tests/libs/estdlib/beams/ -pa libs/etest/src/beams -pa libs/eavmlib/src/beams -pa libs/avm_network/src/beams -s tests -s init stop -noshell # Test - name: "Run tests/libs/etest/test_eunit with OTP eunit" diff --git a/.github/workflows/wasm-build.yaml b/.github/workflows/wasm-build.yaml index 321b669f26..f0add5d2da 100644 --- a/.github/workflows/wasm-build.yaml +++ b/.github/workflows/wasm-build.yaml @@ -71,7 +71,7 @@ jobs: cd build cmake .. -G Ninja -DAVM_WARNINGS_ARE_ERRORS=ON # test_eavmlib does not work with wasm due to http + ssl test - ninja AtomVM atomvmlib erlang_test_modules test_etest test_alisp test_estdlib hello_world run_script call_cast html5_events wasm_webserver + ninja AtomVM atomvmlib atomvmlib-emscripten erlang_test_modules test_etest test_alisp test_estdlib hello_world run_script call_cast html5_events wasm_webserver - name: "Perform CodeQL Analysis" uses: github/codeql-action/analyze@v4 diff --git a/CMakeModules/BuildErlang.cmake b/CMakeModules/BuildErlang.cmake index 509cc8bd70..f034411aaa 100644 --- a/CMakeModules/BuildErlang.cmake +++ b/CMakeModules/BuildErlang.cmake @@ -20,9 +20,18 @@ macro(pack_archive avm_name) - set(multiValueArgs ERLC_FLAGS MODULES) + set(multiValueArgs ERLC_FLAGS MODULES DEPENDS_ON) cmake_parse_arguments(PACK_ARCHIVE "" "" "${multiValueArgs}" ${ARGN}) list(JOIN PACK_ARCHIVE_ERLC_FLAGS " " PACK_ARCHIVE_ERLC_FLAGS) + + # Build -pa flags and file dependencies from DEPENDS_ON + set(_pack_archive_pa_flags "") + set(_pack_archive_extra_deps "") + foreach(_dep_name IN LISTS PACK_ARCHIVE_DEPENDS_ON) + list(APPEND _pack_archive_pa_flags -pa ${CMAKE_BINARY_DIR}/libs/${_dep_name}/src/beams) + list(APPEND _pack_archive_extra_deps ${CMAKE_BINARY_DIR}/libs/${_dep_name}/src/${_dep_name}.avm) + endforeach() + foreach(module_name IN LISTS ${PACK_ARCHIVE_MODULES} PACK_ARCHIVE_MODULES PACK_ARCHIVE_UNPARSED_ARGUMENTS) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/beams/${module_name}.beam @@ -32,8 +41,9 @@ macro(pack_archive avm_name) -I ${CMAKE_SOURCE_DIR}/libs/include -I ${CMAKE_SOURCE_DIR}/libs -I ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${_pack_archive_pa_flags} ${CMAKE_CURRENT_SOURCE_DIR}/${module_name}.erl - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${module_name}.erl + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${module_name}.erl ${_pack_archive_extra_deps} COMMENT "Compiling ${module_name}.erl" VERBATIM ) @@ -47,7 +57,7 @@ macro(pack_archive avm_name) endif() add_custom_command( - OUTPUT ${avm_name}.avm + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${avm_name}.avm DEPENDS ${pack_archive_${avm_name}_beams} PackBEAM COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam create --lib ${INCLUDE_LINES} ${avm_name}.avm ${pack_archive_${avm_name}_beams} COMMENT "Packing archive ${avm_name}.avm" @@ -63,6 +73,11 @@ macro(pack_archive avm_name) ${avm_name} ALL DEPENDS ${avm_name}_emu ) + # Add target-level dependencies from DEPENDS_ON + foreach(_dep_name IN LISTS PACK_ARCHIVE_DEPENDS_ON) + add_dependencies(${avm_name} ${_dep_name}) + add_dependencies(${avm_name}_emu ${_dep_name}) + endforeach() endmacro() macro(pack_precompiled_archive avm_name) @@ -127,12 +142,17 @@ macro(pack_precompiled_archive avm_name) endmacro() macro(pack_lib avm_name) + set(options UF2) + cmake_parse_arguments(PACK_LIB "${options}" "" "" ${ARGN}) + set(pack_lib_${avm_name}_archive_targets "") + set(pack_lib_${avm_name}_archives "") + set(pack_lib_${avm_name}_emu_archives "") if(NOT AVM_DISABLE_JIT) set(pack_lib_${avm_name}_archive_targets jit) endif() - foreach(archive_name ${ARGN}) + foreach(archive_name ${PACK_LIB_UNPARSED_ARGUMENTS}) if(${archive_name} STREQUAL "exavmlib") set(pack_lib_${avm_name}_archives ${pack_lib_${avm_name}_archives} ${CMAKE_BINARY_DIR}/libs/${archive_name}/lib/${archive_name}.avm) elseif(${archive_name} STREQUAL "estdlib") @@ -164,7 +184,7 @@ macro(pack_lib avm_name) foreach(jit_target_arch_variant ${AVM_PRECOMPILED_TARGETS}) # Build JIT archives list for this specific target architecture set(pack_lib_${avm_name}_jit_archives_${jit_target_arch_variant} ${CMAKE_BINARY_DIR}/libs/jit/src/jit-${jit_target_arch_variant}.avm) - foreach(archive_name ${ARGN}) + foreach(archive_name ${PACK_LIB_UNPARSED_ARGUMENTS}) if(${archive_name} STREQUAL "estdlib") set(pack_lib_${avm_name}_jit_archives_${jit_target_arch_variant} ${pack_lib_${avm_name}_jit_archives_${jit_target_arch_variant}} ${CMAKE_BINARY_DIR}/libs/${archive_name}/src/${archive_name}-${jit_target_arch_variant}.avm) endif() @@ -180,56 +200,59 @@ macro(pack_lib avm_name) set(target_deps ${target_deps} ${avm_name}-${jit_target_arch_variant}.avm) endforeach() endif() - add_custom_command( - OUTPUT ${avm_name}-pico.uf2 - DEPENDS ${avm_name}.avm UF2Tool - COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-pico.uf2 -s 0x10100000 ${avm_name}.avm - COMMENT "Creating UF2 file ${avm_name}.uf2" - VERBATIM - ) - add_custom_command( - OUTPUT ${avm_name}-pico2.uf2 - DEPENDS ${avm_name}.avm UF2Tool - COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-pico2.uf2 -f data -s 0x10100000 ${avm_name}.avm - COMMENT "Creating UF2 file ${avm_name}.uf2" - VERBATIM - ) - set(target_deps ${target_deps} ${avm_name}-pico.uf2 ${avm_name}-pico2.uf2) - if((NOT AVM_DISABLE_JIT OR AVM_ENABLE_PRECOMPILED) AND ("armv6m" IN_LIST AVM_PRECOMPILED_TARGETS)) + if(PACK_LIB_UF2) add_custom_command( - OUTPUT ${avm_name}-armv6m-pico.uf2 - DEPENDS ${avm_name}-armv6m.avm UF2Tool - COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-armv6m-pico.uf2 -s 0x10100000 ${avm_name}-armv6m.avm - COMMENT "Creating UF2 file ${avm_name}-armv6m.uf2" + OUTPUT ${avm_name}-pico.uf2 + DEPENDS ${avm_name}.avm UF2Tool + COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-pico.uf2 -s 0x10100000 ${avm_name}.avm + COMMENT "Creating UF2 file ${avm_name}-pico.uf2" VERBATIM ) add_custom_command( - OUTPUT ${avm_name}-armv6m-pico2.uf2 - DEPENDS ${avm_name}-armv6m.avm UF2Tool - COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-armv6m-pico2.uf2 -f data -s 0x10100000 ${avm_name}-armv6m.avm - COMMENT "Creating UF2 file ${avm_name}-armv6m.uf2" + OUTPUT ${avm_name}-pico2.uf2 + DEPENDS ${avm_name}.avm UF2Tool + COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-pico2.uf2 -f data -s 0x10100000 ${avm_name}.avm + COMMENT "Creating UF2 file ${avm_name}-pico2.uf2" VERBATIM ) - set(target_deps ${target_deps} ${avm_name}-armv6m-pico.uf2 ${avm_name}-armv6m-pico2.uf2) - endif() + set(target_deps ${target_deps} ${avm_name}-pico.uf2 ${avm_name}-pico2.uf2) - if((NOT AVM_DISABLE_JIT OR AVM_ENABLE_PRECOMPILED) AND ("armv6m+float32" IN_LIST AVM_PRECOMPILED_TARGETS)) - add_custom_command( - OUTPUT ${avm_name}-armv6m+float32-pico.uf2 - DEPENDS ${avm_name}-armv6m+float32.avm UF2Tool - COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-armv6m+float32-pico.uf2 -s 0x10100000 ${avm_name}-armv6m+float32.avm - COMMENT "Creating UF2 file ${avm_name}-armv6m+float32.uf2" - VERBATIM - ) - add_custom_command( - OUTPUT ${avm_name}-armv6m+float32-pico2.uf2 - DEPENDS ${avm_name}-armv6m+float32.avm UF2Tool - COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-armv6m+float32-pico2.uf2 -f data -s 0x10100000 ${avm_name}-armv6m+float32.avm - COMMENT "Creating UF2 file ${avm_name}-armv6m+float32.uf2" - VERBATIM - ) - set(target_deps ${target_deps} ${avm_name}-armv6m+float32-pico.uf2 ${avm_name}-armv6m+float32-pico2.uf2) + if((NOT AVM_DISABLE_JIT OR AVM_ENABLE_PRECOMPILED) AND ("armv6m" IN_LIST AVM_PRECOMPILED_TARGETS)) + add_custom_command( + OUTPUT ${avm_name}-armv6m-pico.uf2 + DEPENDS ${avm_name}-armv6m.avm UF2Tool + COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-armv6m-pico.uf2 -s 0x10100000 ${avm_name}-armv6m.avm + COMMENT "Creating UF2 file ${avm_name}-armv6m-pico.uf2" + VERBATIM + ) + add_custom_command( + OUTPUT ${avm_name}-armv6m-pico2.uf2 + DEPENDS ${avm_name}-armv6m.avm UF2Tool + COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-armv6m-pico2.uf2 -f data -s 0x10100000 ${avm_name}-armv6m.avm + COMMENT "Creating UF2 file ${avm_name}-armv6m-pico2.uf2" + VERBATIM + ) + set(target_deps ${target_deps} ${avm_name}-armv6m-pico.uf2 ${avm_name}-armv6m-pico2.uf2) + endif() + + if((NOT AVM_DISABLE_JIT OR AVM_ENABLE_PRECOMPILED) AND ("armv6m+float32" IN_LIST AVM_PRECOMPILED_TARGETS)) + add_custom_command( + OUTPUT ${avm_name}-armv6m+float32-pico.uf2 + DEPENDS ${avm_name}-armv6m+float32.avm UF2Tool + COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-armv6m+float32-pico.uf2 -s 0x10100000 ${avm_name}-armv6m+float32.avm + COMMENT "Creating UF2 file ${avm_name}-armv6m+float32-pico.uf2" + VERBATIM + ) + add_custom_command( + OUTPUT ${avm_name}-armv6m+float32-pico2.uf2 + DEPENDS ${avm_name}-armv6m+float32.avm UF2Tool + COMMAND ${CMAKE_BINARY_DIR}/tools/uf2tool/uf2tool create -o ${avm_name}-armv6m+float32-pico2.uf2 -f data -s 0x10100000 ${avm_name}-armv6m+float32.avm + COMMENT "Creating UF2 file ${avm_name}-armv6m+float32-pico2.uf2" + VERBATIM + ) + set(target_deps ${target_deps} ${avm_name}-armv6m+float32-pico.uf2 ${avm_name}-armv6m+float32-pico2.uf2) + endif() endif() add_custom_target( @@ -258,25 +281,38 @@ macro(pack_runnable avm_name main) DEPENDS ${main}.beam ) + # Select the right PLT based on platform-specific dependencies + set(pack_runnable_${avm_name}_plt_name "atomvmlib") + foreach(archive_name ${ARGN}) if(NOT ${archive_name} STREQUAL "exavmlib") set(pack_runnable_${avm_name}_archives ${pack_runnable_${avm_name}_archives} ${CMAKE_BINARY_DIR}/libs/${archive_name}/src/${archive_name}.avm) - if(NOT ${archive_name} MATCHES "^eavmlib|estdlib|alisp$") + if(NOT ${archive_name} MATCHES "^eavmlib|estdlib|alisp|avm_network|avm_esp32|avm_rp2|avm_stm32|avm_emscripten$") set(${avm_name}_dialyzer_beams_opt ${${avm_name}_dialyzer_beams_opt} "-r" ${CMAKE_BINARY_DIR}/libs/${archive_name}/src/beams/) endif() else() set(pack_runnable_${avm_name}_archives ${pack_runnable_${avm_name}_archives} ${CMAKE_BINARY_DIR}/libs/${archive_name}/lib/${archive_name}.avm) endif() set(pack_runnable_${avm_name}_archive_targets ${pack_runnable_${avm_name}_archive_targets} ${archive_name}) + # Pick the platform-specific PLT if a platform library is in the dependencies + if(${archive_name} STREQUAL "avm_esp32") + set(pack_runnable_${avm_name}_plt_name "atomvmlib-esp32") + elseif(${archive_name} STREQUAL "avm_rp2") + set(pack_runnable_${avm_name}_plt_name "atomvmlib-rp2") + elseif(${archive_name} STREQUAL "avm_stm32") + set(pack_runnable_${avm_name}_plt_name "atomvmlib-stm32") + elseif(${archive_name} STREQUAL "avm_emscripten") + set(pack_runnable_${avm_name}_plt_name "atomvmlib-emscripten") + endif() endforeach() if (Dialyzer_FOUND) add_custom_target( ${avm_name}_dialyzer DEPENDS ${avm_name}_main - COMMAND dialyzer --plt ${CMAKE_BINARY_DIR}/libs/atomvmlib.plt -c ${main}.beam ${${avm_name}_dialyzer_beams_opt} + COMMAND dialyzer --plt ${CMAKE_BINARY_DIR}/libs/${pack_runnable_${avm_name}_plt_name}.plt -c ${main}.beam ${${avm_name}_dialyzer_beams_opt} ) - add_dependencies(${avm_name}_dialyzer atomvmlib_plt ${pack_runnable_${avm_name}_archive_targets}) + add_dependencies(${avm_name}_dialyzer ${pack_runnable_${avm_name}_plt_name}_plt ${pack_runnable_${avm_name}_archive_targets}) add_dependencies(dialyzer ${avm_name}_dialyzer) endif() diff --git a/examples/elixir/esp32/CMakeLists.txt b/examples/elixir/esp32/CMakeLists.txt index 9a15d37cd9..95c2f674f1 100644 --- a/examples/elixir/esp32/CMakeLists.txt +++ b/examples/elixir/esp32/CMakeLists.txt @@ -22,8 +22,8 @@ project(examples_elixir_esp32) include(BuildElixir) -pack_runnable(Blink Blink estdlib eavmlib exavmlib) -pack_runnable(Ledc_x4 Ledc_x4 estdlib eavmlib exavmlib) +pack_runnable(Blink Blink estdlib eavmlib exavmlib avm_esp32) +pack_runnable(Ledc_x4 Ledc_x4 estdlib eavmlib exavmlib avm_esp32) if(NOT (AVM_DISABLE_FP)) - pack_runnable(SHT31 SHT31 estdlib eavmlib exavmlib) + pack_runnable(SHT31 SHT31 estdlib eavmlib exavmlib avm_esp32) endif() diff --git a/examples/elixir/stm32/CMakeLists.txt b/examples/elixir/stm32/CMakeLists.txt index fe94a28c12..91db883aca 100644 --- a/examples/elixir/stm32/CMakeLists.txt +++ b/examples/elixir/stm32/CMakeLists.txt @@ -25,4 +25,4 @@ include(BuildElixir) # STM32 examples bundle runtime libs in the same AVM and targets are flash-constrained. set(AVM_PRUNE_RUNNABLES ON) -pack_runnable(MultiBlink MultiBlink estdlib eavmlib exavmlib) +pack_runnable(MultiBlink MultiBlink estdlib eavmlib exavmlib avm_stm32) diff --git a/examples/emscripten/CMakeLists.txt b/examples/emscripten/CMakeLists.txt index 51ce812beb..349702471a 100644 --- a/examples/emscripten/CMakeLists.txt +++ b/examples/emscripten/CMakeLists.txt @@ -22,8 +22,8 @@ project(examples_emscripten) include(BuildErlang) -pack_runnable(run_script run_script estdlib eavmlib) -pack_runnable(call_cast call_cast eavmlib) -pack_runnable(html5_events html5_events estdlib eavmlib) -pack_runnable(echo_websocket echo_websocket estdlib eavmlib) -pack_runnable(wasm_webserver wasm_webserver estdlib eavmlib) +pack_runnable(run_script run_script estdlib eavmlib avm_emscripten) +pack_runnable(call_cast call_cast eavmlib avm_emscripten) +pack_runnable(html5_events html5_events estdlib eavmlib avm_emscripten) +pack_runnable(echo_websocket echo_websocket estdlib eavmlib avm_emscripten) +pack_runnable(wasm_webserver wasm_webserver estdlib eavmlib avm_network avm_emscripten) diff --git a/examples/erlang/CMakeLists.txt b/examples/erlang/CMakeLists.txt index fd6fd85e62..4920832fd3 100644 --- a/examples/erlang/CMakeLists.txt +++ b/examples/erlang/CMakeLists.txt @@ -35,11 +35,11 @@ pack_runnable(tcp_socket_client tcp_socket_client estdlib eavmlib) pack_runnable(tcp_socket_server tcp_socket_server estdlib eavmlib) pack_runnable(udp_socket_server udp_socket_server estdlib eavmlib) pack_runnable(udp_socket_client udp_socket_client estdlib eavmlib) -pack_runnable(hello_world_server hello_world_server estdlib eavmlib) -pack_runnable(system_info_server system_info_server estdlib eavmlib) +pack_runnable(hello_world_server hello_world_server estdlib eavmlib avm_network) +pack_runnable(system_info_server system_info_server estdlib eavmlib avm_network) pack_runnable(code_lock code_lock estdlib eavmlib) pack_runnable(mqtt_client mqtt_client estdlib eavmlib) pack_runnable(network_console network_console estdlib eavmlib alisp) pack_runnable(logging_example logging_example estdlib eavmlib) -pack_runnable(http_client http_client estdlib eavmlib) +pack_runnable(http_client http_client estdlib eavmlib avm_network) pack_runnable(disterl disterl estdlib) diff --git a/examples/erlang/esp32/CMakeLists.txt b/examples/erlang/esp32/CMakeLists.txt index f28afeb8fe..39e497bd55 100644 --- a/examples/erlang/esp32/CMakeLists.txt +++ b/examples/erlang/esp32/CMakeLists.txt @@ -22,19 +22,19 @@ project(examples_erlang_esp32) include(BuildErlang) -pack_runnable(blink blink eavmlib estdlib) -pack_runnable(deep_sleep deep_sleep eavmlib estdlib) -pack_runnable(morse_server morse_server estdlib eavmlib) -pack_runnable(ap_sta_network ap_sta_network eavmlib estdlib) -pack_runnable(set_network_config set_network_config eavmlib estdlib) -pack_runnable(udp_server_blink udp_server_blink eavmlib estdlib) -pack_runnable(tcp_client_esp32 tcp_client_esp32 eavmlib estdlib) -pack_runnable(tcp_server_blink tcp_server_blink eavmlib estdlib) -pack_runnable(esp_random esp_random eavmlib estdlib) -pack_runnable(esp_nvs esp_nvs eavmlib) -pack_runnable(sht31 sht31 eavmlib estdlib) -pack_runnable(sx127x sx127x eavmlib estdlib) -pack_runnable(reformat_nvs reformat_nvs eavmlib) -pack_runnable(uartecho uartecho eavmlib estdlib) -pack_runnable(ledc_example ledc_example eavmlib estdlib) -pack_runnable(epmd_disterl epmd_disterl eavmlib estdlib) +pack_runnable(blink blink eavmlib estdlib avm_esp32) +pack_runnable(deep_sleep deep_sleep eavmlib estdlib avm_esp32) +pack_runnable(morse_server morse_server estdlib eavmlib avm_network avm_esp32) +pack_runnable(ap_sta_network ap_sta_network eavmlib estdlib avm_network avm_esp32) +pack_runnable(set_network_config set_network_config eavmlib estdlib avm_esp32) +pack_runnable(udp_server_blink udp_server_blink eavmlib estdlib avm_network avm_esp32) +pack_runnable(tcp_client_esp32 tcp_client_esp32 eavmlib estdlib avm_network avm_esp32) +pack_runnable(tcp_server_blink tcp_server_blink eavmlib estdlib avm_network avm_esp32) +pack_runnable(esp_random esp_random eavmlib estdlib avm_esp32) +pack_runnable(esp_nvs esp_nvs eavmlib avm_esp32) +pack_runnable(sht31 sht31 eavmlib estdlib avm_esp32) +pack_runnable(sx127x sx127x eavmlib estdlib avm_esp32) +pack_runnable(reformat_nvs reformat_nvs eavmlib avm_esp32) +pack_runnable(uartecho uartecho eavmlib estdlib avm_esp32) +pack_runnable(ledc_example ledc_example eavmlib estdlib avm_esp32) +pack_runnable(epmd_disterl epmd_disterl eavmlib estdlib avm_network avm_esp32) diff --git a/examples/erlang/stm32/CMakeLists.txt b/examples/erlang/stm32/CMakeLists.txt index fb46e75f27..0cc91e19c8 100644 --- a/examples/erlang/stm32/CMakeLists.txt +++ b/examples/erlang/stm32/CMakeLists.txt @@ -25,10 +25,10 @@ include(BuildErlang) # STM32 examples bundle runtime libs in the same AVM and targets are flash-constrained. set(AVM_PRUNE_RUNNABLES ON) -pack_runnable(blink_weact_studio_blackpill blink_weact_studio_blackpill eavmlib) -pack_runnable(blink_weact_studio_h562 blink_weact_studio_h562 eavmlib) -pack_runnable(blink_weact_studio_h743 blink_weact_studio_h743 eavmlib) -pack_runnable(blink_weact_studio_u585 blink_weact_studio_u585 eavmlib) -pack_runnable(blink_weact_studio_wb55 blink_weact_studio_wb55 eavmlib) -pack_runnable(blink_nucleo64 blink_nucleo64 eavmlib) -pack_runnable(blink_nucleo144 blink_nucleo144 eavmlib) +pack_runnable(blink_weact_studio_blackpill blink_weact_studio_blackpill eavmlib avm_stm32) +pack_runnable(blink_weact_studio_h562 blink_weact_studio_h562 eavmlib avm_stm32) +pack_runnable(blink_weact_studio_h743 blink_weact_studio_h743 eavmlib avm_stm32) +pack_runnable(blink_weact_studio_u585 blink_weact_studio_u585 eavmlib avm_stm32) +pack_runnable(blink_weact_studio_wb55 blink_weact_studio_wb55 eavmlib avm_stm32) +pack_runnable(blink_nucleo64 blink_nucleo64 eavmlib avm_stm32) +pack_runnable(blink_nucleo144 blink_nucleo144 eavmlib avm_stm32) diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index a7173df44d..3f3e3f6cce 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -25,6 +25,11 @@ add_subdirectory(estdlib/src) add_subdirectory(eavmlib/src) add_subdirectory(alisp/src) add_subdirectory(etest/src) +add_subdirectory(avm_network/src) +add_subdirectory(avm_esp32/src) +add_subdirectory(avm_rp2/src) +add_subdirectory(avm_stm32/src) +add_subdirectory(avm_emscripten/src) add_subdirectory(esp32boot) add_subdirectory(esp32devmode/src) # JIT compiler doesn't compile with OTP < 23 @@ -33,69 +38,96 @@ if(Erlang_VERSION VERSION_GREATER_EQUAL "23") endif() -set(ATOMVM_LIBS eavmlib estdlib alisp) +set(ATOMVM_COMMON_LIBS eavmlib estdlib alisp) find_package(Elixir) find_package(Gleam) if (Elixir_FOUND) add_subdirectory(exavmlib/lib) - list(APPEND ATOMVM_LIBS exavmlib) + list(APPEND ATOMVM_COMMON_LIBS exavmlib) else() message(WARNING "Unable to find elixirc -- skipping Elixir libs") endif() if (Gleam_FOUND) add_subdirectory(gleam_avm) - list(APPEND ATOMVM_LIBS gleam_avm) + list(APPEND ATOMVM_COMMON_LIBS gleam_avm) else() message(WARNING "Unable to find gleam -- skipping Gleam libs") endif() -pack_lib(atomvmlib ${ATOMVM_LIBS}) +# Base (generic_unix): common + network +pack_lib(atomvmlib ${ATOMVM_COMMON_LIBS} avm_network) + +# Platform-specific variants +pack_lib(atomvmlib-esp32 ${ATOMVM_COMMON_LIBS} avm_network avm_esp32) +pack_lib(atomvmlib-rp2 UF2 ${ATOMVM_COMMON_LIBS} avm_network avm_rp2) +pack_lib(atomvmlib-stm32 ${ATOMVM_COMMON_LIBS} avm_stm32) +pack_lib(atomvmlib-emscripten ${ATOMVM_COMMON_LIBS} avm_network avm_emscripten) if (Dialyzer_FOUND) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/estdlib_beams.txt - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/estdlib/src/estdlib.avm PackBEAM - COMMAND - ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam list -f bare ${CMAKE_CURRENT_BINARY_DIR}/estdlib/src/estdlib.avm | sed -e 's|^|${CMAKE_CURRENT_BINARY_DIR}/estdlib/src/beams/|g' > ${CMAKE_CURRENT_BINARY_DIR}/estdlib_beams.txt - ) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/eavmlib_beams.txt - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/eavmlib/src/eavmlib.avm PackBEAM - COMMAND - ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam list -f bare ${CMAKE_CURRENT_BINARY_DIR}/eavmlib/src/eavmlib.avm | sed -e 's|^|${CMAKE_CURRENT_BINARY_DIR}/eavmlib/src/beams/|g' > ${CMAKE_CURRENT_BINARY_DIR}/eavmlib_beams.txt - ) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/alisp_beams.txt - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/alisp/src/alisp.avm PackBEAM - COMMAND - ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam list -f bare ${CMAKE_CURRENT_BINARY_DIR}/alisp/src/alisp.avm | sed -e 's|^|${CMAKE_CURRENT_BINARY_DIR}/alisp/src/beams/|g' > ${CMAKE_CURRENT_BINARY_DIR}/alisp_beams.txt - ) + # Helper macro to generate a beams list file from an archive + macro(dialyzer_beams_list lib_name lib_path) + get_filename_component(_beams_dir ${lib_path} DIRECTORY) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${lib_name}_beams.txt + DEPENDS ${lib_path} PackBEAM + COMMAND + ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam list -f bare ${lib_path} | sed -e 's|^|${_beams_dir}/beams/|g' > ${CMAKE_CURRENT_BINARY_DIR}/${lib_name}_beams.txt + ) + endmacro() + + dialyzer_beams_list(estdlib ${CMAKE_CURRENT_BINARY_DIR}/estdlib/src/estdlib.avm) + dialyzer_beams_list(eavmlib ${CMAKE_CURRENT_BINARY_DIR}/eavmlib/src/eavmlib.avm) + dialyzer_beams_list(alisp ${CMAKE_CURRENT_BINARY_DIR}/alisp/src/alisp.avm) + dialyzer_beams_list(avm_network ${CMAKE_CURRENT_BINARY_DIR}/avm_network/src/avm_network.avm) + dialyzer_beams_list(avm_esp32 ${CMAKE_CURRENT_BINARY_DIR}/avm_esp32/src/avm_esp32.avm) + dialyzer_beams_list(avm_rp2 ${CMAKE_CURRENT_BINARY_DIR}/avm_rp2/src/avm_rp2.avm) + dialyzer_beams_list(avm_stm32 ${CMAKE_CURRENT_BINARY_DIR}/avm_stm32/src/avm_stm32.avm) + dialyzer_beams_list(avm_emscripten ${CMAKE_CURRENT_BINARY_DIR}/avm_emscripten/src/avm_emscripten.avm) + add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/jit_beams.txt DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/jit/src/jit.avm PackBEAM COMMAND ${CMAKE_BINARY_DIR}/tools/packbeam/packbeam list -f bare ${CMAKE_CURRENT_BINARY_DIR}/jit/src/jit.avm | sed -e 's|^|${CMAKE_CURRENT_BINARY_DIR}/jit/src/beams/|g' | grep -v jit_precompile > ${CMAKE_CURRENT_BINARY_DIR}/jit_beams.txt ) - set(dialyzer_lists + + # Base beam lists shared by all PLTs + set(dialyzer_base_lists ${CMAKE_CURRENT_BINARY_DIR}/estdlib_beams.txt ${CMAKE_CURRENT_BINARY_DIR}/eavmlib_beams.txt ${CMAKE_CURRENT_BINARY_DIR}/alisp_beams.txt + ${CMAKE_CURRENT_BINARY_DIR}/avm_network_beams.txt ) if(Erlang_VERSION VERSION_GREATER_EQUAL "23") - set(dialyzer_lists ${dialyzer_lists} ${CMAKE_CURRENT_BINARY_DIR}/jit_beams.txt) + set(dialyzer_base_lists ${dialyzer_base_lists} ${CMAKE_CURRENT_BINARY_DIR}/jit_beams.txt) endif() - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/atomvmlib.plt - DEPENDS ${dialyzer_lists} - COMMAND cat ${dialyzer_lists} - | xargs dialyzer --build_plt --output_plt ${CMAKE_CURRENT_BINARY_DIR}/atomvmlib.plt - ) - add_custom_target(atomvmlib_plt - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/atomvmlib.plt - ) + + # Helper macro to build a PLT from base lists + platform-specific lists + macro(build_plt plt_name) + set(plt_extra_lists ${ARGN}) + set(plt_all_lists ${dialyzer_base_lists} ${plt_extra_lists}) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${plt_name}.plt + DEPENDS ${plt_all_lists} + COMMAND cat ${plt_all_lists} + | xargs dialyzer --build_plt --output_plt ${CMAKE_CURRENT_BINARY_DIR}/${plt_name}.plt + ) + add_custom_target(${plt_name}_plt + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${plt_name}.plt + ) + endmacro() + + # Base PLT (no platform-specific modules) + build_plt(atomvmlib) + + # Per-platform PLTs + build_plt(atomvmlib-esp32 ${CMAKE_CURRENT_BINARY_DIR}/avm_esp32_beams.txt) + build_plt(atomvmlib-rp2 ${CMAKE_CURRENT_BINARY_DIR}/avm_rp2_beams.txt) + build_plt(atomvmlib-stm32 ${CMAKE_CURRENT_BINARY_DIR}/avm_stm32_beams.txt) + build_plt(atomvmlib-emscripten ${CMAKE_CURRENT_BINARY_DIR}/avm_emscripten_beams.txt) else() message("Dialyzer was not found -- skipping PLT build") endif() @@ -104,6 +136,14 @@ install( FILES ${CMAKE_CURRENT_BINARY_DIR}/atomvmlib.avm DESTINATION lib/atomvm ) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/atomvmlib-esp32.avm + ${CMAKE_CURRENT_BINARY_DIR}/atomvmlib-rp2.avm + ${CMAKE_CURRENT_BINARY_DIR}/atomvmlib-stm32.avm + ${CMAKE_CURRENT_BINARY_DIR}/atomvmlib-emscripten.avm + DESTINATION lib/atomvm +) if(NOT AVM_DISABLE_JIT) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/atomvmlib-${AVM_JIT_TARGET_ARCH}.avm diff --git a/libs/avm_emscripten/src/CMakeLists.txt b/libs/avm_emscripten/src/CMakeLists.txt new file mode 100644 index 0000000000..65bbfee9c7 --- /dev/null +++ b/libs/avm_emscripten/src/CMakeLists.txt @@ -0,0 +1,51 @@ +# +# This file is part of AtomVM. +# +# Copyright 2026 Paul Guyot +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +# + +project(avm_emscripten) + +include(BuildErlang) + +set(ERLANG_MODULES + emscripten + websocket +) + +pack_archive(avm_emscripten ${ERLANG_MODULES}) + +include(../../../version.cmake) + +set(AVM_EMSCRIPTEN_VERSION ${ATOMVM_BASE_VERSION}) + +install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/beams/ + DESTINATION lib/atomvm/lib/avm_emscripten-${AVM_EMSCRIPTEN_VERSION}/ebin + FILES_MATCHING PATTERN "*.beam" +) + +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/avm_emscripten.avm + DESTINATION lib/atomvm/lib/avm_emscripten-${AVM_EMSCRIPTEN_VERSION}/ebin/ +) + +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ + DESTINATION lib/atomvm/lib/avm_emscripten-${AVM_EMSCRIPTEN_VERSION}/src + FILES_MATCHING PATTERN "*.erl" +) diff --git a/libs/eavmlib/src/emscripten.erl b/libs/avm_emscripten/src/emscripten.erl similarity index 100% rename from libs/eavmlib/src/emscripten.erl rename to libs/avm_emscripten/src/emscripten.erl diff --git a/libs/eavmlib/src/websocket.erl b/libs/avm_emscripten/src/websocket.erl similarity index 100% rename from libs/eavmlib/src/websocket.erl rename to libs/avm_emscripten/src/websocket.erl diff --git a/libs/avm_esp32/src/CMakeLists.txt b/libs/avm_esp32/src/CMakeLists.txt new file mode 100644 index 0000000000..07afa6d387 --- /dev/null +++ b/libs/avm_esp32/src/CMakeLists.txt @@ -0,0 +1,57 @@ +# +# This file is part of AtomVM. +# +# Copyright 2026 Paul Guyot +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +# + +project(avm_esp32) + +include(BuildErlang) + +set(ERLANG_MODULES + esp + esp_adc + esp_dac + gpio + i2c + ledc + spi + uart +) + +pack_archive(avm_esp32 DEPENDS_ON eavmlib ERLC_FLAGS +warnings_as_errors MODULES ${ERLANG_MODULES}) + +include(../../../version.cmake) + +set(AVM_ESP32_VERSION ${ATOMVM_BASE_VERSION}) + +install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/beams/ + DESTINATION lib/atomvm/lib/avm_esp32-${AVM_ESP32_VERSION}/ebin + FILES_MATCHING PATTERN "*.beam" +) + +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/avm_esp32.avm + DESTINATION lib/atomvm/lib/avm_esp32-${AVM_ESP32_VERSION}/ebin/ +) + +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ + DESTINATION lib/atomvm/lib/avm_esp32-${AVM_ESP32_VERSION}/src + FILES_MATCHING PATTERN "*.erl" +) diff --git a/libs/eavmlib/src/esp.erl b/libs/avm_esp32/src/esp.erl similarity index 100% rename from libs/eavmlib/src/esp.erl rename to libs/avm_esp32/src/esp.erl diff --git a/libs/eavmlib/src/esp_adc.erl b/libs/avm_esp32/src/esp_adc.erl similarity index 100% rename from libs/eavmlib/src/esp_adc.erl rename to libs/avm_esp32/src/esp_adc.erl diff --git a/libs/eavmlib/src/esp_dac.erl b/libs/avm_esp32/src/esp_dac.erl similarity index 100% rename from libs/eavmlib/src/esp_dac.erl rename to libs/avm_esp32/src/esp_dac.erl diff --git a/libs/eavmlib/src/gpio.erl b/libs/avm_esp32/src/gpio.erl similarity index 72% rename from libs/eavmlib/src/gpio.erl rename to libs/avm_esp32/src/gpio.erl index d1f42082af..26c508ed39 100644 --- a/libs/eavmlib/src/gpio.erl +++ b/libs/avm_esp32/src/gpio.erl @@ -19,18 +19,16 @@ % %%----------------------------------------------------------------------------- -%% @doc GPIO driver module +%% @doc GPIO driver module for ESP32 %% %% This module provides functions for interacting with micro-controller GPIO -%% (General Purpose Input and Output) pins. -%% -%% Note: `-type pin()' used in this driver refers to a pin number on Espressif -%% chips and normal Raspberry Pi Pico pins, or a tuple {GPIO_BANK, PIN} for STM32 -%% chips and the "extra" GPIOs available on the Pico-W. +%% (General Purpose Input and Output) pins on the ESP32 platform. %% @end %%----------------------------------------------------------------------------- -module(gpio). +-behaviour(gpio_hal). + -export([ start/0, open/0, @@ -60,27 +58,18 @@ -type gpio() :: port(). %% This is the port returned by `gpio:start/0'. --type pin() :: non_neg_integer() | pin_tuple(). -%% The pin definition for ESP32 and PR2040 is a non-negative integer. A tuple is used on the STM32 platform and for the extra "WL" pins on the Pico-W. --type pin_tuple() :: {gpio_bank(), 0..15 | all | [0..15]}. -%% A pin parameter on STM32 is a tuple consisting of a GPIO bank and pin number, also used on the Pico-W for the extra "WL" pins `0..2'. --type gpio_bank() :: a | b | c | d | e | f | g | h | i | j | k | wl. -%% STM32 gpio banks vary by board, some only break out `a' thru `h'. The extra "WL" pins on Pico-W use bank `wl'. --type direction() :: input | output | output_od | mode_config(). +-type pin() :: non_neg_integer(). +%% The pin definition for ESP32 is a non-negative integer. +-type direction() :: input | output | output_od. %% The direction is used to set the mode of operation for a GPIO pin, either as an input, an output, or output with open drain. -%% On the STM32 platform pull mode and output_speed must be set at the same time as direction. See @type mode_config() --type mode_config() :: {direction(), pull()} | {output, pull(), output_speed()}. -%% Extended mode configuration options on STM32. Default pull() is `floating', default output_speed() is `mhz_2' if options are omitted. -type pull() :: up | down | up_down | floating. -%% Internal resistor pull mode. STM32 does not support `up_down'. --type output_speed() :: mhz_2 | mhz_25 | mhz_50 | mhz_100. -%% Output clock speed. Only available on STM32, default is `mhz_2'. +%% Internal resistor pull mode. -type low_level() :: low | 0. -type high_level() :: high | 1. -type level() :: low_level() | high_level(). %% Valid pin levels can be atom or binary representation. -type trigger() :: none | rising | falling | both | low | high. -%% Event type that will trigger a `gpio_interrupt'. STM32 only supports `rising', `falling', or `both'. +%% Event type that will trigger a `gpio_interrupt'. %%----------------------------------------------------------------------------- %% @returns Port | error | {error, Reason} @@ -90,8 +79,6 @@ %% port driver will be stared and registered as `gpio'. The use of %% `gpio:open/0' or `gpio:start/0' is required before using any functions %% that require a GPIO port as a parameter. -%% -%% Not currently available on rp2040 (Pico) port, use nif functions. %% @end %%----------------------------------------------------------------------------- -spec start() -> gpio() | {error, Reason :: atom()} | error. @@ -112,8 +99,6 @@ start() -> %% `gpio:start/0' the command will fail. The use of `gpio:open/0' or %% `gpio:start/0' is required before using any functions that require a %% GPIO port as a parameter. -%% -%% Not currently available on rp2040 (Pico) port, use nif functions. %% @end %%----------------------------------------------------------------------------- -spec open() -> gpio() | {error, Reason :: atom()} | error. @@ -127,8 +112,6 @@ open() -> %% %% This function disables any interrupts that are set, stops %% the listening port, and frees all of its resources. -%% -%% Not currently available on rp2040 (Pico) port, use nif functions. %% @end %%----------------------------------------------------------------------------- -spec close(GPIO :: gpio()) -> ok | {error, Reason :: atom()} | error. @@ -141,8 +124,6 @@ close(GPIO) -> %% %% This function disables any interrupts that are set, stops %% the listening port, and frees all of its resources. -%% -%% Not currently available on rp2040 (Pico) port, use nif functions. %% @end %%----------------------------------------------------------------------------- -spec stop() -> ok | {error, Reason :: atom()} | error. @@ -163,8 +144,6 @@ stop() -> %% Read if an input pin state is `high' or `low'. %% Warning: if the pin was not previously configured as an input using %% `gpio:set_direction/3' it will always read as low. -%% -%% Not supported on rp2040 (Pico), use `gpio:digital_read/1' instead. %% @end %%----------------------------------------------------------------------------- -spec read(GPIO :: gpio(), Pin :: pin()) -> high | low | {error, Reason :: atom()} | error. @@ -179,21 +158,6 @@ read(GPIO, Pin) -> %% @doc Set the operational mode of a pin %% %% Pins can be used for input, output, or output with open drain. -%% -%% The STM32 platform has extended direction mode configuration options. -%% See @type mode_config() for details. All configuration must be set using -%% `set_direction/3', including pull() mode, unlike the ESP32 which has a -%% separate function (`set_pin_pull/2'). If you are configuring multiple pins -%% on the same GPIO `bank' with the same options the pins may be configured all -%% at the same time by giving a list of pin numbers in the pin tuple. -%% -%% Example to configure all of the leds on a Nucleo board: -%% -%%
-%%    gpio:set_direction({b, [0,7,14], output)
-%% 
-%% -%% Not supported on rp2040 (Pico), use `gpio:set_pin_mode/2' instead. %% @end %%----------------------------------------------------------------------------- -spec set_direction(GPIO :: gpio(), Pin :: pin(), Direction :: direction()) -> @@ -209,25 +173,6 @@ set_direction(GPIO, Pin, Direction) -> %% @doc Set GPIO digital output level %% %% Set a pin to `high' (1) or `low' (0). -%% -%% The STM32 is capable of setting the state for any, or all of the output pins -%% on a single bank at the same time, this is done by passing a list of pins numbers -%% in the pin tuple. -%% -%% For example, setting all of the even numbered pins to a `high' state, -%% and all of the odd numbered pins to a `low' state can be accomplished in two lines: -%% -%%
-%%    gpio:digital_write({c, [0,2,4,6,8,10,12,14]}, high}),
-%%    gpio:digital_write({c, [1,3,5,7,9,11,13,15]}, low}).
-%% 
-%% -%% To set the same state for all of the pins that have been previously configured as outputs -%% on a specific bank the atom `all' may be used, this will have no effect on any pins on the -%% same bank that have been configured as inputs, so it is safe to use with mixed direction -%% modes on a bank. -%% -%% Not supported on rp2040 (Pico), use `gpio:digital_write/2' instead. %% @end %%----------------------------------------------------------------------------- -spec set_level(GPIO :: gpio(), Pin :: pin(), Level :: level()) -> @@ -247,10 +192,6 @@ set_level(GPIO, Pin, Level) -> %% and `high'. When the interrupt is triggered it will send a tuple: %% `{gpio_interrupt, Pin}' to the process that set the interrupt. `Pin' %% will be the number of the pin that triggered the interrupt. -%% -%% The STM32 port only supports `rising', `falling', or `both'. -%% -%% The rp2040 (Pico) port does not support gpio interrupts at this time. %% @end %%----------------------------------------------------------------------------- -spec set_int(GPIO :: gpio(), Pin :: pin(), Trigger :: trigger()) -> @@ -272,10 +213,6 @@ set_int(GPIO, Pin, Trigger) -> %% `{gpio_interrupt, Pin}' %% to the process that set the interrupt. Pin will be the number %% of the pin that triggered the interrupt. -%% -%% The STM32 port only supports `rising', `falling', or `both'. -%% -%% The rp2040 (Pico) port does not support gpio interrupts at this time. %% @end %%----------------------------------------------------------------------------- -spec set_int(GPIO :: gpio(), Pin :: pin(), Trigger :: trigger(), Pid :: pid()) -> @@ -290,8 +227,6 @@ set_int(GPIO, Pin, Trigger, Pid) -> %% @doc Remove a GPIO interrupt %% %% Removes an interrupt from the specified pin. -%% -%% The rp2040 (Pico) port does not support gpio interrupts at this time. %% @end %%----------------------------------------------------------------------------- -spec remove_int(GPIO :: gpio(), Pin :: pin()) -> ok | {error, Reason :: atom()} | error. @@ -302,7 +237,7 @@ remove_int(GPIO, Pin) -> %% @param Pin number to initialize %% @returns ok %% @doc Initialize a pin to be used as GPIO. -%% This is required on RP2040 and for some pins on ESP32. +%% This may be required for some pins on ESP32. %% @end %%----------------------------------------------------------------------------- -spec init(Pin :: pin()) -> ok. @@ -313,7 +248,6 @@ init(_Pin) -> %% @param Pin number to deinitialize %% @returns ok %% @doc Reset a pin back to the NULL function. -%% Currently only implemented for RP2040 (Pico). %% @end %%----------------------------------------------------------------------------- -spec deinit(Pin :: pin()) -> ok. @@ -327,18 +261,6 @@ deinit(_Pin) -> %% @doc Set the operational mode of a pin %% %% Pins can be used for input, output, or output with open drain. -%% -%% The STM32 platform has extended direction mode configuration options. -%% See @type mode_config() for details. All configuration must be set using -%% `set_direction/3', including pull() mode, unlike the ESP32 which has a -%% separate function (`set_pin_pull/2'). If you are configuring multiple pins -%% on the same GPIO `bank' with the same options the pins may be configured all -%% at the same time by giving a list of pin numbers in the pin tuple. -%% Example to configure all of the leds on a Nucleo board: -%% -%%
-%%    gpio:set_direction({b, [0,7,14], output)
-%% 
%% @end %%----------------------------------------------------------------------------- -spec set_pin_mode(Pin :: pin(), Direction :: direction()) -> @@ -354,10 +276,6 @@ set_pin_mode(_Pin, _Mode) -> %% %% Pins can be internally pulled `up', `down', `up_down' (pulled in %% both directions), or left `floating'. -%% -%% This function is not supported on STM32, the internal resistor must -%% be configured when setting the direction mode, see `set_direction/3' -%% or `set_pin_mode/2'. %% @end %%----------------------------------------------------------------------------- -spec set_pin_pull(Pin :: pin(), Pull :: pull()) -> ok | error. @@ -382,8 +300,6 @@ set_pin_pull(_Pin, _Pull) -> %% will resume the hold function when the chip wakes up from %% Deep-sleep. If the digital gpio also needs to be held during %% Deep-sleep `gpio:deep_sleep_hold_en' should also be called. -%% -%% This function is only supported on ESP32. %% @end %%----------------------------------------------------------------------------- -spec hold_en(Pin :: pin()) -> ok | error. @@ -397,15 +313,13 @@ hold_en(_Pin) -> %% %% When the chip is woken up from Deep-sleep, the gpio will be set to %% the default mode, so, the gpio will output the default level if -%% this function is called. If you don’t want the level changes, the +%% this function is called. If you don't want the level changes, the %% gpio should be configured to a known state before this function is %% called. e.g. If you hold gpio18 high during Deep-sleep, after the %% chip is woken up and `gpio:hold_dis' is called, gpio18 will output -%% low level(because gpio18 is input mode by default). If you don’t +%% low level(because gpio18 is input mode by default). If you don't %% want this behavior, you should configure gpio18 as output mode and %% set it to hight level before calling `gpio:hold_dis'. -%% -%% This function is only supported on ESP32. %% @end %%----------------------------------------------------------------------------- -spec hold_dis(Pin :: pin()) -> ok | error. @@ -429,8 +343,6 @@ hold_dis(_Pin) -> %% Power down or call `gpio_hold_dis' will disable this function, %% otherwise, the digital gpio hold feature works as long as the chip %% enters Deep-sleep. -%% -%% This function is only supported on ESP32. %% @end %%----------------------------------------------------------------------------- -spec deep_sleep_hold_en() -> ok. @@ -440,8 +352,6 @@ deep_sleep_hold_en() -> %%----------------------------------------------------------------------------- %% @returns ok %% @doc Disable all gpio pad functions during Deep-sleep. -%% -%% This function is only supported on ESP32. %% @end %%----------------------------------------------------------------------------- -spec deep_sleep_hold_dis() -> ok. @@ -455,24 +365,6 @@ deep_sleep_hold_dis() -> %% @doc Set GPIO digital output level %% %% Set a pin to `high' (1) or `low' (0). -%% -%% The STM32 is capable of setting the state for any, or all of the output pins -%% on a single bank at the same time, this is done by passing a list of pins numbers -%% in the pin tuple. For example, setting all of the even numbered pins to a `high' state, -%% and all of the odd numbered pins to a `low' state can be accomplished in two lines: -%% -%%
-%%    gpio:digital_write({c, [0,2,4,6,8,10,12,14]}, high}),
-%%    gpio:digital_write({c, [1,3,5,7,9,11,13,15]}, low}).
-%% 
-%% -%% To set the same state for all of the pins that have been previously configured as outputs -%% on a specific bank the atom `all' may be used, this will have no effect on any pins on the -%% same bank that have been configured as inputs, so it is safe to use with mixed direction -%% modes on a bank. -%% -%% The LED pin on the Pico-W can be controlled on the extended pin `{wl, 0}', and does not -%% require or accept `set_pin_mode' or `set_pin_pull' before use. %% @end %%----------------------------------------------------------------------------- -spec digital_write(Pin :: pin(), Level :: level()) -> ok | {error, Reason :: atom()} | error. @@ -487,9 +379,6 @@ digital_write(_Pin, _Level) -> %% Read if an input pin state is high or low. %% Warning: if the pin was not previously configured as an input using %% `gpio:set_pin_mode/2' it will always read as low. -%% -%% The VBUS detect pin on the Pico-W can be read on the extended pin `{wl, 2}', -%% and does not require or accept `set_pin_mode' or `set_pin_pull' before use. %% @end %%----------------------------------------------------------------------------- -spec digital_read(Pin :: pin()) -> high | low | {error, Reason :: atom()} | error. @@ -510,8 +399,6 @@ digital_read(_Pin) -> %% used in an application. If multiple pins are being configured with %% interrupt triggers gpio:set_int/3 should be used otherwise there is %% a race condition when start() is called internally by this function. -%% -%% The rp2040 (Pico) port does not support gpio interrupts at this time. %% @end %%----------------------------------------------------------------------------- -spec attach_interrupt(Pin :: pin(), Trigger :: trigger()) -> @@ -529,8 +416,6 @@ attach_interrupt(Pin, Trigger) -> %% %% Unlike `gpio:attach_interrupt/2' this function can be safely used %% regardless of the number of interrupt pins used in the application. -%% -%% The rp2040 (Pico) port does not support gpio interrupts at this time. %% @end %%----------------------------------------------------------------------------- -spec detach_interrupt(Pin :: pin()) -> ok | {error, Reason :: atom()} | error. diff --git a/libs/eavmlib/src/i2c.erl b/libs/avm_esp32/src/i2c.erl similarity index 99% rename from libs/eavmlib/src/i2c.erl rename to libs/avm_esp32/src/i2c.erl index 76260edd1c..7833eadf08 100644 --- a/libs/eavmlib/src/i2c.erl +++ b/libs/avm_esp32/src/i2c.erl @@ -32,6 +32,9 @@ %% @end %%----------------------------------------------------------------------------- -module(i2c). + +-behaviour(i2c_hal). + -export([ open/1, close/1, diff --git a/libs/eavmlib/src/ledc.erl b/libs/avm_esp32/src/ledc.erl similarity index 100% rename from libs/eavmlib/src/ledc.erl rename to libs/avm_esp32/src/ledc.erl diff --git a/libs/eavmlib/src/spi.erl b/libs/avm_esp32/src/spi.erl similarity index 99% rename from libs/eavmlib/src/spi.erl rename to libs/avm_esp32/src/spi.erl index 337388c672..ae9d009e09 100644 --- a/libs/eavmlib/src/spi.erl +++ b/libs/avm_esp32/src/spi.erl @@ -40,6 +40,8 @@ %% -module(spi). +-behaviour(spi_hal). + -export([open/1, close/1, read_at/4, write_at/5, write/3, write_read/3]). %% TODO remove deprecated hspi and vspi diff --git a/libs/eavmlib/src/uart.erl b/libs/avm_esp32/src/uart.erl similarity index 99% rename from libs/eavmlib/src/uart.erl rename to libs/avm_esp32/src/uart.erl index 34a3daf06f..76a51bb85e 100644 --- a/libs/eavmlib/src/uart.erl +++ b/libs/avm_esp32/src/uart.erl @@ -19,6 +19,9 @@ % -module(uart). + +-behaviour(uart_hal). + -export([open/1, open/2, close/1, read/1, read/2, write/2]). -type peripheral() :: string() | binary(). diff --git a/libs/avm_network/src/CMakeLists.txt b/libs/avm_network/src/CMakeLists.txt new file mode 100644 index 0000000000..59afdc8659 --- /dev/null +++ b/libs/avm_network/src/CMakeLists.txt @@ -0,0 +1,55 @@ +# +# This file is part of AtomVM. +# +# Copyright 2026 Paul Guyot +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +# + +project(avm_network) + +include(BuildErlang) + +set(ERLANG_MODULES + ahttp_client + epmd + http_server + mdns + network + network_fsm +) + +pack_archive(avm_network ${ERLANG_MODULES}) + +include(../../../version.cmake) + +set(AVM_NETWORK_VERSION ${ATOMVM_BASE_VERSION}) + +install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/beams/ + DESTINATION lib/atomvm/lib/avm_network-${AVM_NETWORK_VERSION}/ebin + FILES_MATCHING PATTERN "*.beam" +) + +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/avm_network.avm + DESTINATION lib/atomvm/lib/avm_network-${AVM_NETWORK_VERSION}/ebin/ +) + +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ + DESTINATION lib/atomvm/lib/avm_network-${AVM_NETWORK_VERSION}/src + FILES_MATCHING PATTERN "*.erl" +) diff --git a/libs/eavmlib/src/ahttp_client.erl b/libs/avm_network/src/ahttp_client.erl similarity index 100% rename from libs/eavmlib/src/ahttp_client.erl rename to libs/avm_network/src/ahttp_client.erl diff --git a/libs/eavmlib/src/epmd.erl b/libs/avm_network/src/epmd.erl similarity index 100% rename from libs/eavmlib/src/epmd.erl rename to libs/avm_network/src/epmd.erl diff --git a/libs/eavmlib/src/http_server.erl b/libs/avm_network/src/http_server.erl similarity index 100% rename from libs/eavmlib/src/http_server.erl rename to libs/avm_network/src/http_server.erl diff --git a/libs/eavmlib/src/mdns.erl b/libs/avm_network/src/mdns.erl similarity index 100% rename from libs/eavmlib/src/mdns.erl rename to libs/avm_network/src/mdns.erl diff --git a/libs/eavmlib/src/network.erl b/libs/avm_network/src/network.erl similarity index 100% rename from libs/eavmlib/src/network.erl rename to libs/avm_network/src/network.erl diff --git a/libs/eavmlib/src/network_fsm.erl b/libs/avm_network/src/network_fsm.erl similarity index 100% rename from libs/eavmlib/src/network_fsm.erl rename to libs/avm_network/src/network_fsm.erl diff --git a/libs/avm_rp2/src/CMakeLists.txt b/libs/avm_rp2/src/CMakeLists.txt new file mode 100644 index 0000000000..10b1e64ec7 --- /dev/null +++ b/libs/avm_rp2/src/CMakeLists.txt @@ -0,0 +1,51 @@ +# +# This file is part of AtomVM. +# +# Copyright 2026 Paul Guyot +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +# + +project(avm_rp2) + +include(BuildErlang) + +set(ERLANG_MODULES + gpio + pico +) + +pack_archive(avm_rp2 DEPENDS_ON eavmlib ERLC_FLAGS +warnings_as_errors MODULES ${ERLANG_MODULES}) + +include(../../../version.cmake) + +set(AVM_RP2_VERSION ${ATOMVM_BASE_VERSION}) + +install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/beams/ + DESTINATION lib/atomvm/lib/avm_rp2-${AVM_RP2_VERSION}/ebin + FILES_MATCHING PATTERN "*.beam" +) + +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/avm_rp2.avm + DESTINATION lib/atomvm/lib/avm_rp2-${AVM_RP2_VERSION}/ebin/ +) + +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ + DESTINATION lib/atomvm/lib/avm_rp2-${AVM_RP2_VERSION}/src + FILES_MATCHING PATTERN "*.erl" +) diff --git a/libs/avm_rp2/src/gpio.erl b/libs/avm_rp2/src/gpio.erl new file mode 100644 index 0000000000..0d8b4e4e16 --- /dev/null +++ b/libs/avm_rp2/src/gpio.erl @@ -0,0 +1,309 @@ +% +% This file is part of AtomVM. +% +% Copyright 2018-2023 Davide Bettio +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +%%----------------------------------------------------------------------------- +%% @doc GPIO driver module for RP2 (Pico) +%% +%% This module provides functions for interacting with micro-controller GPIO +%% (General Purpose Input and Output) pins on the RP2 platform. +%% +%% The port-based API (start/0, open/0, read/2, set_direction/3, set_level/3) +%% is implemented as a wrapper around NIFs. Interrupt functions (set_int/3,4 +%% and remove_int/2) are not supported on this platform and return +%% `{error, not_supported}'. +%% @end +%%----------------------------------------------------------------------------- +-module(gpio). + +-behaviour(gpio_hal). + +-export([ + start/0, + open/0, + read/2, + set_direction/3, + set_level/3, + set_int/3, set_int/4, + remove_int/2, + stop/0, + close/1 +]). +-export([ + init/1, + deinit/1, + set_pin_mode/2, + set_pin_pull/2, + digital_write/2, + digital_read/1 +]). + +-type pin() :: non_neg_integer() | pin_tuple(). +%% The pin definition for RP2040 is a non-negative integer. A tuple is used for the extra "WL" pins on the Pico-W. +-type pin_tuple() :: {wl, 0..2}. +%% The extra "WL" pins on Pico-W use bank `wl'. +-type direction() :: input | output | output_od. +%% The direction is used to set the mode of operation for a GPIO pin, either as an input, an output, or output with open drain. +-type pull() :: up | down | up_down | floating. +%% Internal resistor pull mode. +-type low_level() :: low | 0. +-type high_level() :: high | 1. +-type level() :: low_level() | high_level(). +%% Valid pin levels can be atom or binary representation. +-type gpio() :: pid(). +%% This is the pid returned by `gpio:start/0'. Unlike ESP32 and STM32, this +%% is not a real port but a process wrapping NIF calls. +-type trigger() :: none | rising | falling | both | low | high. +%% Event type that will trigger a `gpio_interrupt'. + +%%----------------------------------------------------------------------------- +%% @returns Pid | error | {error, Reason} +%% @doc Start the GPIO driver +%% +%% Returns the pid of the active GPIO driver process, otherwise the GPIO +%% driver process will be started and registered as `gpio'. The use of +%% `gpio:open/0' or `gpio:start/0' is required before using any functions +%% that require a GPIO pid as a parameter. +%% @end +%%----------------------------------------------------------------------------- +-spec start() -> gpio() | {error, Reason :: atom()} | error. +start() -> + case whereis(gpio) of + undefined -> + open(); + GPIO -> + GPIO + end. + +%%----------------------------------------------------------------------------- +%% @returns Pid | error | {error, Reason} +%% @doc Start the GPIO driver +%% +%% The GPIO driver process will be started and registered as `gpio'. If the +%% process has already been started through `gpio:open/0' or +%% `gpio:start/0' the command will fail. +%% @end +%%----------------------------------------------------------------------------- +-spec open() -> gpio() | {error, Reason :: atom()} | error. +open() -> + Pid = spawn(fun gpio_loop/0), + register(gpio, Pid), + Pid. + +%%----------------------------------------------------------------------------- +%% @param GPIO pid that was returned from gpio:start/0 +%% @returns ok | error | {error, Reason} +%% @doc Stop the GPIO driver +%% @end +%%----------------------------------------------------------------------------- +-spec close(GPIO :: gpio()) -> ok | {error, Reason :: atom()} | error. +close(GPIO) -> + Ref = make_ref(), + GPIO ! {'$call', {self(), Ref}, {close}}, + receive + {Ref, Result} -> Result + end. + +%%----------------------------------------------------------------------------- +%% @returns ok | error | {error, Reason} +%% @doc Stop the GPIO driver +%% @end +%%----------------------------------------------------------------------------- +-spec stop() -> ok | {error, Reason :: atom()} | error. +stop() -> + case whereis(gpio) of + undefined -> + ok; + Pid when is_pid(Pid) -> + close(Pid) + end. + +%%----------------------------------------------------------------------------- +%% @param GPIO pid that was returned from gpio:start/0 +%% @param Pin number of the pin to read +%% @returns high | low | error | {error, Reason} +%% @doc Read the digital state of a GPIO pin +%% +%% Read if an input pin state is `high' or `low'. +%% Warning: if the pin was not previously configured as an input using +%% `gpio:set_direction/3' it will always read as low. +%% @end +%%----------------------------------------------------------------------------- +-spec read(GPIO :: gpio(), Pin :: pin()) -> high | low | {error, Reason :: atom()} | error. +read(_GPIO, Pin) -> + digital_read(Pin). + +%%----------------------------------------------------------------------------- +%% @param GPIO pid that was returned from `gpio:start/0' +%% @param Pin number of the pin to configure +%% @param Direction is `input', `output', or `output_od' +%% @returns ok | error | {error, Reason} +%% @doc Set the operational mode of a pin +%% +%% Pins can be used for input, output, or output with open drain. +%% @end +%%----------------------------------------------------------------------------- +-spec set_direction(GPIO :: gpio(), Pin :: pin(), Direction :: direction()) -> + ok | {error, Reason :: atom()} | error. +set_direction(_GPIO, Pin, Direction) -> + init(Pin), + set_pin_mode(Pin, Direction). + +%%----------------------------------------------------------------------------- +%% @param GPIO pid that was returned from `gpio:start/0' +%% @param Pin number of the pin to write +%% @param Level the desired output level to set +%% @returns ok | error | {error, Reason} +%% @doc Set GPIO digital output level +%% +%% Set a pin to `high' (1) or `low' (0). +%% @end +%%----------------------------------------------------------------------------- +-spec set_level(GPIO :: gpio(), Pin :: pin(), Level :: level()) -> + ok | {error, Reason :: atom()} | error. +set_level(_GPIO, Pin, Level) -> + digital_write(Pin, Level). + +%%----------------------------------------------------------------------------- +%% @param GPIO pid that was returned from `gpio:start/0' +%% @param Pin number of the pin to set the interrupt on +%% @param Trigger is the state that will trigger an interrupt +%% @returns {error, not_supported} +%% @doc Not supported on RP2. +%% @end +%%----------------------------------------------------------------------------- +-spec set_int(GPIO :: gpio(), Pin :: pin(), Trigger :: trigger()) -> + {error, not_supported}. +set_int(_GPIO, _Pin, _Trigger) -> + {error, not_supported}. + +%%----------------------------------------------------------------------------- +%% @param GPIO pid that was returned from `gpio:start/0' +%% @param Pin number of the pin to set the interrupt on +%% @param Trigger is the state that will trigger an interrupt +%% @param Pid is the process that will receive the interrupt message +%% @returns {error, not_supported} +%% @doc Not supported on RP2. +%% @end +%%----------------------------------------------------------------------------- +-spec set_int(GPIO :: gpio(), Pin :: pin(), Trigger :: trigger(), Pid :: pid()) -> + {error, not_supported}. +set_int(_GPIO, _Pin, _Trigger, _Pid) -> + {error, not_supported}. + +%%----------------------------------------------------------------------------- +%% @param GPIO pid that was returned from `gpio:start/0' +%% @param Pin number of the pin to remove the interrupt +%% @returns {error, not_supported} +%% @doc Not supported on RP2. +%% @end +%%----------------------------------------------------------------------------- +-spec remove_int(GPIO :: gpio(), Pin :: pin()) -> {error, not_supported}. +remove_int(_GPIO, _Pin) -> + {error, not_supported}. + +%%----------------------------------------------------------------------------- +%% @param Pin number to initialize +%% @returns ok +%% @doc Initialize a pin to be used as GPIO. +%% This is required on RP2040. +%% @end +%%----------------------------------------------------------------------------- +-spec init(Pin :: pin()) -> ok. +init(_Pin) -> + ok. + +%%----------------------------------------------------------------------------- +%% @param Pin number to deinitialize +%% @returns ok +%% @doc Reset a pin back to the NULL function. +%% @end +%%----------------------------------------------------------------------------- +-spec deinit(Pin :: pin()) -> ok. +deinit(_Pin) -> + ok. + +%%----------------------------------------------------------------------------- +%% @param Pin number to set operational mode +%% @param Direction is `input', `output', or `output_od' +%% @returns ok | error | {error, Reason} +%% @doc Set the operational mode of a pin +%% +%% Pins can be used for input, output, or output with open drain. +%% @end +%%----------------------------------------------------------------------------- +-spec set_pin_mode(Pin :: pin(), Direction :: direction()) -> + ok | {error, Reason :: atom()} | error. +set_pin_mode(_Pin, _Mode) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Pin number to set internal resistor direction +%% @param Pull is the internal resistor state +%% @returns ok | error +%% @doc Set the internal resistor of a pin +%% +%% Pins can be internally pulled `up', `down', `up_down' (pulled in +%% both directions), or left `floating'. +%% @end +%%----------------------------------------------------------------------------- +-spec set_pin_pull(Pin :: pin(), Pull :: pull()) -> ok | error. +set_pin_pull(_Pin, _Pull) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Pin number of the pin to write +%% @param Level the desired output level to set +%% @returns ok | error | {error, Reason} +%% @doc Set GPIO digital output level +%% +%% Set a pin to `high' (1) or `low' (0). +%% +%% The LED pin on the Pico-W can be controlled on the extended pin `{wl, 0}', and does not +%% require or accept `set_pin_mode' or `set_pin_pull' before use. +%% @end +%%----------------------------------------------------------------------------- +-spec digital_write(Pin :: pin(), Level :: level()) -> ok | {error, Reason :: atom()} | error. +digital_write(_Pin, _Level) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Pin number of the pin to read +%% @returns high | low | error | {error, Reason} +%% @doc Read the digital state of a GPIO pin +%% +%% Read if an input pin state is high or low. +%% Warning: if the pin was not previously configured as an input using +%% `gpio:set_pin_mode/2' it will always read as low. +%% +%% The VBUS detect pin on the Pico-W can be read on the extended pin `{wl, 2}', +%% and does not require or accept `set_pin_mode' or `set_pin_pull' before use. +%% @end +%%----------------------------------------------------------------------------- +-spec digital_read(Pin :: pin()) -> high | low | {error, Reason :: atom()} | error. +digital_read(_Pin) -> + erlang:nif_error(undefined). + +%% @private +gpio_loop() -> + receive + {'$call', {Pid, Ref}, {close}} -> + unregister(gpio), + Pid ! {Ref, ok} + end. diff --git a/libs/eavmlib/src/pico.erl b/libs/avm_rp2/src/pico.erl similarity index 100% rename from libs/eavmlib/src/pico.erl rename to libs/avm_rp2/src/pico.erl diff --git a/libs/avm_stm32/src/CMakeLists.txt b/libs/avm_stm32/src/CMakeLists.txt new file mode 100644 index 0000000000..d85f481d4c --- /dev/null +++ b/libs/avm_stm32/src/CMakeLists.txt @@ -0,0 +1,50 @@ +# +# This file is part of AtomVM. +# +# Copyright 2026 Paul Guyot +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +# + +project(avm_stm32) + +include(BuildErlang) + +set(ERLANG_MODULES + gpio +) + +pack_archive(avm_stm32 DEPENDS_ON eavmlib ERLC_FLAGS +warnings_as_errors MODULES ${ERLANG_MODULES}) + +include(../../../version.cmake) + +set(AVM_STM32_VERSION ${ATOMVM_BASE_VERSION}) + +install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/beams/ + DESTINATION lib/atomvm/lib/avm_stm32-${AVM_STM32_VERSION}/ebin + FILES_MATCHING PATTERN "*.beam" +) + +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/avm_stm32.avm + DESTINATION lib/atomvm/lib/avm_stm32-${AVM_STM32_VERSION}/ebin/ +) + +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ + DESTINATION lib/atomvm/lib/avm_stm32-${AVM_STM32_VERSION}/src + FILES_MATCHING PATTERN "*.erl" +) diff --git a/libs/avm_stm32/src/gpio.erl b/libs/avm_stm32/src/gpio.erl new file mode 100644 index 0000000000..bf9550a44b --- /dev/null +++ b/libs/avm_stm32/src/gpio.erl @@ -0,0 +1,336 @@ +% +% This file is part of AtomVM. +% +% Copyright 2018-2023 Davide Bettio +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +%%----------------------------------------------------------------------------- +%% @doc GPIO driver module for STM32 +%% +%% This module provides functions for interacting with micro-controller GPIO +%% (General Purpose Input and Output) pins on the STM32 platform. +%% +%% Note: `-type pin()' used in this driver refers to a tuple {GPIO_BANK, PIN}. +%% @end +%%----------------------------------------------------------------------------- +-module(gpio). + +-behaviour(gpio_hal). + +-export([ + start/0, + open/0, + read/2, + set_direction/3, + set_level/3, + set_int/3, set_int/4, + remove_int/2, + stop/0, + close/1 +]). +-export([ + init/1, + deinit/1, + set_pin_mode/2, + set_pin_pull/2, + digital_write/2, + digital_read/1 +]). + +-type pin() :: {gpio_bank(), 0..15 | [0..15] | all}. +%% A pin parameter on STM32 is a tuple consisting of a GPIO bank and a pin number, +%% a list of pin numbers, or the atom `all'. +-type gpio_bank() :: a | b | c | d | e | f | g | h | i | j | k. +%% STM32 gpio banks vary by board, some only break out `a' thru `h'. +-type direction() :: input | output | output_od | mode_config(). +%% The direction is used to set the mode of operation for a GPIO pin, either as an input, an output, or output with open drain. +%% Pull mode and output_speed must be set at the same time as direction. See @type mode_config() +-type mode_config() :: {direction(), pull()} | {output, pull(), output_speed()}. +%% Extended mode configuration options. Default pull() is `floating', default output_speed() is `mhz_2' if options are omitted. +-type pull() :: up | down | floating. +%% Internal resistor pull mode. STM32 does not support `up_down'. +-type output_speed() :: mhz_2 | mhz_25 | mhz_50 | mhz_100. +%% Output clock speed. Default is `mhz_2'. +-type low_level() :: low | 0. +-type high_level() :: high | 1. +-type level() :: low_level() | high_level(). +%% Valid pin levels can be atom or binary representation. +-type gpio() :: port(). +%% This is the port returned by `gpio:start/0'. +-type trigger() :: none | rising | falling | both | low | high. +%% Event type that will trigger a `gpio_interrupt'. + +%%----------------------------------------------------------------------------- +%% @returns Port | error | {error, Reason} +%% @doc Start the GPIO driver port +%% +%% Returns the port of the active GPIO port driver, otherwise the GPIO +%% port driver will be stared and registered as `gpio'. The use of +%% `gpio:open/0' or `gpio:start/0' is required before using any functions +%% that require a GPIO port as a parameter. +%% @end +%%----------------------------------------------------------------------------- +-spec start() -> gpio() | {error, Reason :: atom()} | error. +start() -> + case whereis(gpio) of + undefined -> + open(); + GPIO -> + GPIO + end. + +%%----------------------------------------------------------------------------- +%% @returns Port | error | {error, Reason} +%% @doc Start the GPIO driver port +%% +%% The GPIO port driver will be stared and registered as `gpio'. If the +%% port has already been started through the `gpio:open/0' or +%% `gpio:start/0' the command will fail. The use of `gpio:open/0' or +%% `gpio:start/0' is required before using any functions that require a +%% GPIO port as a parameter. +%% @end +%%----------------------------------------------------------------------------- +-spec open() -> gpio() | {error, Reason :: atom()} | error. +open() -> + open_port({spawn, "gpio"}, []). + +%%----------------------------------------------------------------------------- +%% @param GPIO port that was returned from gpio:start/0 +%% @returns ok | error | {error, Reason} +%% @doc Stop the GPIO interrupt port +%% +%% This function disables any interrupts that are set, stops +%% the listening port, and frees all of its resources. +%% @end +%%----------------------------------------------------------------------------- +-spec close(GPIO :: gpio()) -> ok | {error, Reason :: atom()} | error. +close(GPIO) -> + port:call(GPIO, {close}). + +%%----------------------------------------------------------------------------- +%% @returns ok | error | {error, Reason} +%% @doc Stop the GPIO interrupt port +%% +%% This function disables any interrupts that are set, stops +%% the listening port, and frees all of its resources. +%% @end +%%----------------------------------------------------------------------------- +-spec stop() -> ok | {error, Reason :: atom()} | error. +stop() -> + case whereis(gpio) of + undefined -> + ok; + Port when is_port(Port) -> + close(Port) + end. + +%%----------------------------------------------------------------------------- +%% @param GPIO port that was returned from gpio:start/0 +%% @param Pin number of the pin to read +%% @returns high | low | error | {error, Reason} +%% @doc Read the digital state of a GPIO pin +%% +%% Read if an input pin state is `high' or `low'. +%% Warning: if the pin was not previously configured as an input using +%% `gpio:set_direction/3' it will always read as low. +%% @end +%%----------------------------------------------------------------------------- +-spec read(GPIO :: gpio(), Pin :: pin()) -> high | low | {error, Reason :: atom()} | error. +read(GPIO, Pin) -> + port:call(GPIO, {read, Pin}). + +%%----------------------------------------------------------------------------- +%% @param GPIO port that was returned from `gpio:start/0' +%% @param Pin number of the pin to configure +%% @param Direction is `input', `output', or `output_od' +%% @returns ok | error | {error, Reason} +%% @doc Set the operational mode of a pin +%% +%% Pins can be used for input, output, or output with open drain. +%% @end +%%----------------------------------------------------------------------------- +-spec set_direction(GPIO :: gpio(), Pin :: pin(), Direction :: direction()) -> + ok | {error, Reason :: atom()} | error. +set_direction(GPIO, Pin, Direction) -> + port:call(GPIO, {set_direction, Pin, Direction}). + +%%----------------------------------------------------------------------------- +%% @param GPIO port that was returned from `gpio:start/0' +%% @param Pin number of the pin to write +%% @param Level the desired output level to set +%% @returns ok | error | {error, Reason} +%% @doc Set GPIO digital output level +%% +%% Set a pin to `high' (1) or `low' (0). +%% @end +%%----------------------------------------------------------------------------- +-spec set_level(GPIO :: gpio(), Pin :: pin(), Level :: level()) -> + ok | {error, Reason :: atom()} | error. +set_level(GPIO, Pin, Level) -> + port:call(GPIO, {set_level, Pin, Level}). + +%%----------------------------------------------------------------------------- +%% @param GPIO port that was returned from `gpio:start/0' +%% @param Pin number of the pin to set the interrupt on +%% @param Trigger is the state that will trigger an interrupt +%% @returns ok | error | {error, Reason} +%% @doc Set a GPIO interrupt +%% +%% Available triggers are `none' (which is the same as disabling an +%% interrupt), `rising', `falling', `both' (rising or falling), `low', +%% and `high'. When the interrupt is triggered it will send a tuple: +%% `{gpio_interrupt, {Bank, Pin}}' to the process that set the interrupt. +%% @end +%%----------------------------------------------------------------------------- +-spec set_int(GPIO :: gpio(), Pin :: pin(), Trigger :: trigger()) -> + ok | {error, Reason :: atom()} | error. +set_int(GPIO, Pin, Trigger) -> + port:call(GPIO, {set_int, Pin, Trigger}). + +%%----------------------------------------------------------------------------- +%% @param GPIO port that was returned from `gpio:start/0' +%% @param Pin number of the pin to set the interrupt on +%% @param Trigger is the state that will trigger an interrupt +%% @param Pid is the process that will receive the interrupt message +%% @returns ok | error | {error, Reason} +%% @doc Set a GPIO interrupt +%% +%% Available triggers are `none' (which is the same as disabling an +%% interrupt), `rising', `falling', `both' (rising or falling), `low', and +%% `high'. When the interrupt is triggered it will send a tuple: +%% `{gpio_interrupt, {Bank, Pin}}' +%% to the specified process. +%% @end +%%----------------------------------------------------------------------------- +-spec set_int(GPIO :: gpio(), Pin :: pin(), Trigger :: trigger(), Pid :: pid()) -> + ok | {error, Reason :: atom()} | error. +set_int(GPIO, Pin, Trigger, Pid) -> + port:call(GPIO, {set_int, Pin, Trigger, Pid}). + +%%----------------------------------------------------------------------------- +%% @param GPIO port that was returned from `gpio:start/0' +%% @param Pin number of the pin to remove the interrupt +%% @returns ok | error | {error, Reason} +%% @doc Remove a GPIO interrupt +%% +%% Removes an interrupt from the specified pin. +%% @end +%%----------------------------------------------------------------------------- +-spec remove_int(GPIO :: gpio(), Pin :: pin()) -> ok | {error, Reason :: atom()} | error. +remove_int(GPIO, Pin) -> + port:call(GPIO, {remove_int, Pin}). + +%%----------------------------------------------------------------------------- +%% @param Pin number to initialize +%% @returns ok +%% @doc Initialize a pin to be used as GPIO. +%% @end +%%----------------------------------------------------------------------------- +-spec init(Pin :: pin()) -> ok. +init(_Pin) -> + ok. + +%%----------------------------------------------------------------------------- +%% @param Pin number to deinitialize +%% @returns ok +%% @doc Reset a pin back to the NULL function. +%% @end +%%----------------------------------------------------------------------------- +-spec deinit(Pin :: pin()) -> ok. +deinit(_Pin) -> + ok. + +%%----------------------------------------------------------------------------- +%% @param Pin number to set operational mode +%% @param Direction is `input', `output', or `output_od' +%% @returns ok | error | {error, Reason} +%% @doc Set the operational mode of a pin +%% +%% Pins can be used for input, output, or output with open drain. +%% +%% All configuration must be set using `set_pin_mode/2', including +%% pull() mode. If you are configuring multiple pins on the same GPIO +%% `bank' with the same options the pins may be configured all at the +%% same time by giving a list of pin numbers in the pin tuple. +%% +%% Example to configure all of the leds on a Nucleo board: +%% +%%
+%%    gpio:set_pin_mode({b, [0,7,14]}, output)
+%% 
+%% @end +%%----------------------------------------------------------------------------- +-spec set_pin_mode(Pin :: pin(), Direction :: direction()) -> + ok | {error, Reason :: atom()} | error. +set_pin_mode(_Pin, _Mode) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Pin number to set internal resistor direction +%% @param Pull is the internal resistor state +%% @returns ok | error +%% @doc Set the internal resistor of a pin +%% +%% Pins can be internally pulled `up', `down', or left `floating'. +%% STM32 does not support `up_down'. +%% @end +%%----------------------------------------------------------------------------- +-spec set_pin_pull(Pin :: pin(), Pull :: pull()) -> ok | error. +set_pin_pull(_Pin, _Pull) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Pin number of the pin to write +%% @param Level the desired output level to set +%% @returns ok | error | {error, Reason} +%% @doc Set GPIO digital output level +%% +%% Set a pin to `high' (1) or `low' (0). +%% +%% STM32 is capable of setting the state for any, or all of the output pins +%% on a single bank at the same time, this is done by passing a list of pins numbers +%% in the pin tuple. For example, setting all of the even numbered pins to a `high' state, +%% and all of the odd numbered pins to a `low' state can be accomplished in two lines: +%% +%%
+%%    gpio:digital_write({c, [0,2,4,6,8,10,12,14]}, high),
+%%    gpio:digital_write({c, [1,3,5,7,9,11,13,15]}, low).
+%% 
+%% +%% To set the same state for all of the pins that have been previously configured as outputs +%% on a specific bank the atom `all' may be used, this will have no effect on any pins on the +%% same bank that have been configured as inputs, so it is safe to use with mixed direction +%% modes on a bank. +%% @end +%%----------------------------------------------------------------------------- +-spec digital_write(Pin :: pin(), Level :: level()) -> ok | {error, Reason :: atom()} | error. +digital_write(_Pin, _Level) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Pin number of the pin to read +%% @returns high | low | error | {error, Reason} +%% @doc Read the digital state of a GPIO pin +%% +%% Read if an input pin state is high or low. +%% Warning: if the pin was not previously configured as an input using +%% `gpio:set_pin_mode/2' it will always read as low. +%% @end +%%----------------------------------------------------------------------------- +-spec digital_read(Pin :: pin()) -> high | low | {error, Reason :: atom()} | error. +digital_read(_Pin) -> + erlang:nif_error(undefined). diff --git a/libs/eavmlib/src/CMakeLists.txt b/libs/eavmlib/src/CMakeLists.txt index c3abbef24b..548511680c 100644 --- a/libs/eavmlib/src/CMakeLists.txt +++ b/libs/eavmlib/src/CMakeLists.txt @@ -23,31 +23,18 @@ project(eavmlib) include(BuildErlang) set(ERLANG_MODULES - ahttp_client atomvm avm_pubsub console - emscripten - epmd - esp - esp_dac - esp_adc - gpio - i2c - http_server + gpio_hal + i2c_hal json_encoder - ledc logger_manager - mdns - network - network_fsm - pico port - spi + spi_hal timer_manager timestamp_util - uart - websocket + uart_hal ) pack_archive(eavmlib ${ERLANG_MODULES}) diff --git a/libs/eavmlib/src/gpio_hal.erl b/libs/eavmlib/src/gpio_hal.erl new file mode 100644 index 0000000000..703df71789 --- /dev/null +++ b/libs/eavmlib/src/gpio_hal.erl @@ -0,0 +1,94 @@ +% +% This file is part of AtomVM. +% +% Copyright 2026 Paul Guyot +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +%%----------------------------------------------------------------------------- +%% @doc GPIO Hardware Abstraction Layer behavior +%% +%% This module defines the behavior that platform-specific GPIO modules +%% must implement. It provides a common interface for basic GPIO operations +%% across all supported platforms (ESP32, RP2, STM32). +%% @end +%%----------------------------------------------------------------------------- +-module(gpio_hal). + +-type direction() :: input | output | output_od. +%% The direction is used to set the mode of operation for a GPIO pin, +%% either as an input, an output, or output with open drain. + +-type pull() :: up | down | up_down | floating. +%% Internal resistor pull mode. + +-type low_level() :: low | 0. +-type high_level() :: high | 1. +-type level() :: low_level() | high_level(). +%% Valid pin levels can be atom or integer representation. + +-type trigger() :: none | rising | falling | both | low | high. +%% Event type that will trigger a `gpio_interrupt'. + +-type gpio() :: port() | pid(). +%% Handle returned by `start/0' or `open/0'. + +-export_type([direction/0, pull/0, level/0, trigger/0, gpio/0]). + +%% NIF-based API + +-callback init(Pin :: term()) -> ok. + +-callback deinit(Pin :: term()) -> ok. + +-callback set_pin_mode(Pin :: term(), Direction :: direction()) -> + ok | {error, Reason :: atom()} | error. + +-callback set_pin_pull(Pin :: term(), Pull :: pull()) -> ok | error. + +-callback digital_write(Pin :: term(), Level :: level()) -> + ok | {error, Reason :: atom()} | error. + +-callback digital_read(Pin :: term()) -> + high | low | {error, Reason :: atom()} | error. + +%% Port-based API + +-callback start() -> gpio() | {error, Reason :: atom()} | error. + +-callback open() -> gpio() | {error, Reason :: atom()} | error. + +-callback close(GPIO :: gpio()) -> ok | {error, Reason :: atom()} | error. + +-callback stop() -> ok | {error, Reason :: atom()} | error. + +-callback read(GPIO :: gpio(), Pin :: term()) -> + high | low | {error, Reason :: atom()} | error. + +-callback set_direction(GPIO :: gpio(), Pin :: term(), Direction :: direction()) -> + ok | {error, Reason :: atom()} | error. + +-callback set_level(GPIO :: gpio(), Pin :: term(), Level :: level()) -> + ok | {error, Reason :: atom()} | error. + +-callback set_int(GPIO :: gpio(), Pin :: term(), Trigger :: trigger()) -> + ok | {error, Reason :: atom()} | error. + +-callback set_int(GPIO :: gpio(), Pin :: term(), Trigger :: trigger(), Pid :: pid()) -> + ok | {error, Reason :: atom()} | error. + +-callback remove_int(GPIO :: gpio(), Pin :: term()) -> + ok | {error, Reason :: atom()} | error. diff --git a/libs/eavmlib/src/i2c_hal.erl b/libs/eavmlib/src/i2c_hal.erl new file mode 100644 index 0000000000..2f5c1c86de --- /dev/null +++ b/libs/eavmlib/src/i2c_hal.erl @@ -0,0 +1,75 @@ +% +% This file is part of AtomVM. +% +% Copyright 2026 Paul Guyot +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +%%----------------------------------------------------------------------------- +%% @doc I2C Hardware Abstraction Layer behavior +%% +%% This module defines the behavior that platform-specific I2C modules +%% must implement. It provides a common interface for I2C operations +%% across all supported platforms. +%% @end +%%----------------------------------------------------------------------------- +-module(i2c_hal). + +-type i2c() :: port() | pid() | term(). +%% Handle returned by `open/1'. + +-type address() :: non_neg_integer(). +%% I2C device address. + +-type register() :: non_neg_integer(). +%% Register address within an I2C device. + +-type params() :: [term()]. +%% Initialization parameters for the I2C bus. + +-export_type([i2c/0, address/0, register/0, params/0]). + +-callback open(Params :: params()) -> i2c(). + +-callback close(I2C :: i2c()) -> ok | {error, Reason :: term()}. + +-callback begin_transmission(I2C :: i2c(), Address :: address()) -> + ok | {error, Reason :: term()}. + +-callback write_byte(I2C :: i2c(), Byte :: byte()) -> + ok | {error, Reason :: term()}. + +-callback write_bytes(I2C :: i2c(), Bytes :: binary()) -> + ok | {error, Reason :: term()}. + +-callback end_transmission(I2C :: i2c()) -> + ok | {error, Reason :: term()}. + +-callback read_bytes(I2C :: i2c(), Address :: address(), Count :: non_neg_integer()) -> + {ok, Data :: binary()} | {error, Reason :: term()}. + +-callback read_bytes( + I2C :: i2c(), Address :: address(), Register :: register(), Count :: non_neg_integer() +) -> + {ok, Data :: binary()} | {error, Reason :: term()}. + +-callback write_bytes(I2C :: i2c(), Address :: address(), BinOrInt :: binary() | byte()) -> + ok | {error, Reason :: term()}. + +-callback write_bytes( + I2C :: i2c(), Address :: address(), Register :: register(), BinOrInt :: binary() | integer() +) -> + ok | {error, Reason :: term()}. diff --git a/libs/eavmlib/src/spi_hal.erl b/libs/eavmlib/src/spi_hal.erl new file mode 100644 index 0000000000..274962de9b --- /dev/null +++ b/libs/eavmlib/src/spi_hal.erl @@ -0,0 +1,76 @@ +% +% This file is part of AtomVM. +% +% Copyright 2026 Paul Guyot +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +%%----------------------------------------------------------------------------- +%% @doc SPI Hardware Abstraction Layer behavior +%% +%% This module defines the behavior that platform-specific SPI modules +%% must implement. It provides a common interface for SPI operations +%% across all supported platforms. +%% @end +%%----------------------------------------------------------------------------- +-module(spi_hal). + +-type spi() :: port() | pid() | term(). +%% Handle returned by `open/1'. + +-type device_name() :: atom(). +%% Name identifying an SPI device, as specified in the device configuration. + +-type address() :: non_neg_integer(). +%% SPI device address. + +-type params() :: [term()] | map(). +%% Initialization parameters for the SPI bus. + +-type transaction() :: #{ + command => integer(), + address => non_neg_integer(), + write_data => binary(), + write_bits => non_neg_integer(), + read_bits => non_neg_integer() +}. +%% SPI transaction map. + +-export_type([spi/0, device_name/0, address/0, params/0, transaction/0]). + +-callback open(Params :: params()) -> spi(). + +-callback close(SPI :: spi()) -> ok. + +-callback read_at( + SPI :: spi(), DeviceName :: device_name(), Address :: address(), Len :: non_neg_integer() +) -> + {ok, integer()} | {error, Reason :: term()}. + +-callback write_at( + SPI :: spi(), + DeviceName :: device_name(), + Address :: address(), + Len :: non_neg_integer(), + Data :: integer() +) -> + {ok, integer()} | {error, Reason :: term()}. + +-callback write(SPI :: spi(), DeviceName :: device_name(), Transaction :: transaction()) -> + ok | {error, Reason :: term()}. + +-callback write_read(SPI :: spi(), DeviceName :: device_name(), Transaction :: transaction()) -> + {ok, ReadData :: binary()} | {error, Reason :: term()}. diff --git a/libs/eavmlib/src/uart_hal.erl b/libs/eavmlib/src/uart_hal.erl new file mode 100644 index 0000000000..63a40b0d5c --- /dev/null +++ b/libs/eavmlib/src/uart_hal.erl @@ -0,0 +1,53 @@ +% +% This file is part of AtomVM. +% +% Copyright 2026 Paul Guyot +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +%%----------------------------------------------------------------------------- +%% @doc UART Hardware Abstraction Layer behavior +%% +%% This module defines the behavior that platform-specific UART modules +%% must implement. It provides a common interface for UART operations +%% across all supported platforms. +%% @end +%%----------------------------------------------------------------------------- +-module(uart_hal). + +-type uart() :: port() | pid() | term(). +%% Handle returned by `open/1' or `open/2'. + +-type peripheral() :: string() | binary(). +%% UART peripheral name. + +-type params() :: [term()]. +%% Initialization parameters for the UART bus. + +-export_type([uart/0, peripheral/0, params/0]). + +-callback open(Params :: params()) -> uart() | {error, Reason :: term()}. + +-callback open(Name :: peripheral(), Params :: params()) -> uart() | {error, Reason :: term()}. + +-callback close(UART :: uart()) -> ok | {error, Reason :: term()}. + +-callback read(UART :: uart()) -> {ok, Data :: iodata()} | {error, Reason :: term()}. + +-callback read(UART :: uart(), Timeout :: pos_integer()) -> + {ok, Data :: iodata()} | {error, Reason :: term()}. + +-callback write(UART :: uart(), Data :: iodata()) -> ok | {error, Reason :: term()}. diff --git a/libs/esp32boot/CMakeLists.txt b/libs/esp32boot/CMakeLists.txt index 23cdc8795c..316b42136e 100644 --- a/libs/esp32boot/CMakeLists.txt +++ b/libs/esp32boot/CMakeLists.txt @@ -23,7 +23,7 @@ project(esp32boot) include(BuildErlang) if (Elixir_FOUND) - pack_runnable(elixir_esp32boot esp32init esp32devmode eavmlib estdlib alisp exavmlib) + pack_runnable(elixir_esp32boot esp32init esp32devmode eavmlib estdlib alisp avm_network avm_esp32 exavmlib) endif() -pack_runnable(esp32boot esp32init esp32devmode eavmlib estdlib alisp) +pack_runnable(esp32boot esp32init esp32devmode eavmlib estdlib alisp avm_network avm_esp32) diff --git a/src/platforms/emscripten/tests/src/test_atomvm.html b/src/platforms/emscripten/tests/src/test_atomvm.html index dfc8e4d9bf..cde06286ff 100644 --- a/src/platforms/emscripten/tests/src/test_atomvm.html +++ b/src/platforms/emscripten/tests/src/test_atomvm.html @@ -36,6 +36,7 @@ "/tests/build/test_atomvm.beam", "/build/libs/estdlib/src/estdlib.avm", "/build/libs/eavmlib/src/eavmlib.avm", + "/build/libs/avm_emscripten/src/avm_emscripten.avm", ], }; diff --git a/src/platforms/emscripten/tests/src/test_call.html b/src/platforms/emscripten/tests/src/test_call.html index d497a6fa9f..59ae9db66d 100644 --- a/src/platforms/emscripten/tests/src/test_call.html +++ b/src/platforms/emscripten/tests/src/test_call.html @@ -33,7 +33,7 @@ // wasm_webserver serves under /tests/build/ files in src/platform/escripten/build/tests/src subdirectory // and under /build/ files in build/ subdirectory. var Module = { - arguments: ['/tests/build/test_call.beam', '/build/libs/eavmlib/src/eavmlib.avm'] + arguments: ['/tests/build/test_call.beam', '/build/libs/eavmlib/src/eavmlib.avm', '/build/libs/avm_emscripten/src/avm_emscripten.avm'] } diff --git a/src/platforms/emscripten/tests/src/test_html5.html b/src/platforms/emscripten/tests/src/test_html5.html index 407af6dd5b..9b7ed24166 100644 --- a/src/platforms/emscripten/tests/src/test_html5.html +++ b/src/platforms/emscripten/tests/src/test_html5.html @@ -34,7 +34,7 @@ // wasm_webserver serves under /tests/build/ files in src/platform/escripten/build/tests/src subdirectory // and under /build/ files in build/ subdirectory. var Module = { - arguments: ['/tests/build/test_html5.beam', '/build/libs/estdlib/src/estdlib.avm', '/build/libs/eavmlib/src/eavmlib.avm'] + arguments: ['/tests/build/test_html5.beam', '/build/libs/estdlib/src/estdlib.avm', '/build/libs/eavmlib/src/eavmlib.avm', '/build/libs/avm_emscripten/src/avm_emscripten.avm'] } diff --git a/src/platforms/emscripten/tests/src/test_websockets.html b/src/platforms/emscripten/tests/src/test_websockets.html index 8b852ee0cf..c8853b4d3a 100644 --- a/src/platforms/emscripten/tests/src/test_websockets.html +++ b/src/platforms/emscripten/tests/src/test_websockets.html @@ -35,7 +35,7 @@ const echoServer = hashParams.get('echo_server'); var Module = { - arguments: ['/tests/build/test_websockets.beam', '/build/libs/eavmlib/src/eavmlib.avm', '/build/libs/estdlib/src/estdlib.avm'], + arguments: ['/tests/build/test_websockets.beam', '/build/libs/eavmlib/src/eavmlib.avm', '/build/libs/avm_emscripten/src/avm_emscripten.avm', '/build/libs/estdlib/src/estdlib.avm'], preRun: [function() { if (echoServer) { // Set environment variable for the Erlang code to read diff --git a/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt b/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt index 544ddd2054..ce8d9f62e5 100644 --- a/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt +++ b/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt @@ -29,7 +29,7 @@ endif() ExternalProject_Add(HostAtomVM SOURCE_DIR ../../../../../../../../ INSTALL_COMMAND cmake -E echo "Skipping install step." - BUILD_COMMAND cmake --build . --target=atomvmlib ${host_atomvm_jit_target} --target=PackBEAM + BUILD_COMMAND cmake --build . --target=atomvmlib-esp32 ${host_atomvm_jit_target} --target=PackBEAM ) macro(jit_precompile module_name) @@ -115,7 +115,7 @@ endif() add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/esp32_test_modules.avm" COMMAND HostAtomVM-prefix/src/HostAtomVM-build/tools/packbeam/packbeam create esp32_test_modules.avm - HostAtomVM-prefix/src/HostAtomVM-build/libs/atomvmlib.avm + HostAtomVM-prefix/src/HostAtomVM-build/libs/atomvmlib-esp32.avm ${erlang_test_beams_to_package} DEPENDS HostAtomVM diff --git a/src/platforms/rp2/tests/test_erl_sources/CMakeLists.txt b/src/platforms/rp2/tests/test_erl_sources/CMakeLists.txt index 2918a0396d..4b764cd392 100644 --- a/src/platforms/rp2/tests/test_erl_sources/CMakeLists.txt +++ b/src/platforms/rp2/tests/test_erl_sources/CMakeLists.txt @@ -21,16 +21,16 @@ include(ExternalProject) if(NOT AVM_DISABLE_JIT) set(host_atomvm_jit_target "--target=jit") -set(atomvlib_name "atomvmlib-${AVM_JIT_TARGET_ARCH}.avm") +set(atomvlib_name "atomvmlib-rp2-${AVM_JIT_TARGET_ARCH}.avm") else() set(host_atomvm_jit_target "") -set(atomvlib_name "atomvmlib.avm") +set(atomvlib_name "atomvmlib-rp2.avm") endif() ExternalProject_Add(HostAtomVM SOURCE_DIR ../../../../../../ INSTALL_COMMAND cmake -E echo "Skipping install step." CMAKE_ARGS -DAVM_DISABLE_JIT=${AVM_DISABLE_JIT} - BUILD_COMMAND cmake --build . --target=atomvmlib ${host_atomvm_jit_target} --target=PackBEAM --target=UF2Tool + BUILD_COMMAND cmake --build . --target=atomvmlib-rp2 ${host_atomvm_jit_target} --target=PackBEAM --target=UF2Tool ) macro(jit_precompile module_name) diff --git a/src/platforms/stm32/tests/test_erl_sources/CMakeLists.txt b/src/platforms/stm32/tests/test_erl_sources/CMakeLists.txt index 21932080b9..aff32393c9 100644 --- a/src/platforms/stm32/tests/test_erl_sources/CMakeLists.txt +++ b/src/platforms/stm32/tests/test_erl_sources/CMakeLists.txt @@ -21,4 +21,4 @@ include(BuildErlang) pack_runnable(stm32_boot_test test_boot) -pack_runnable(stm32_gpio_test test_gpio eavmlib) +pack_runnable(stm32_gpio_test test_gpio eavmlib avm_stm32) diff --git a/tests/libs/eavmlib/CMakeLists.txt b/tests/libs/eavmlib/CMakeLists.txt index 98de68e338..92484b196d 100644 --- a/tests/libs/eavmlib/CMakeLists.txt +++ b/tests/libs/eavmlib/CMakeLists.txt @@ -33,4 +33,4 @@ set(ERLANG_MODULES ) pack_archive(test_eavmlib_lib ${ERLANG_MODULES}) -pack_test(test_eavmlib eavmlib estdlib etest) +pack_test(test_eavmlib eavmlib avm_network estdlib etest) diff --git a/tests/libs/estdlib/CMakeLists.txt b/tests/libs/estdlib/CMakeLists.txt index 8a1d87fabf..7fa1cae580 100644 --- a/tests/libs/estdlib/CMakeLists.txt +++ b/tests/libs/estdlib/CMakeLists.txt @@ -60,4 +60,4 @@ set(ERLANG_MODULES ) pack_archive(test_estdlib_lib ${ERLANG_MODULES}) -pack_test(test_estdlib estdlib eavmlib etest) +pack_test(test_estdlib estdlib eavmlib avm_network etest)