├── .github └── workflows │ ├── build-c-esp-idf-hello-world.yaml │ ├── build-python-circuitpython-hello-world.yaml │ ├── build-python-micropython-hello-world.yaml │ ├── build-rust-no_std-hello-world.yaml │ ├── build-rust-std-hello-world.yaml │ └── build-zig-esp-idf-hello-world.yaml ├── .gitignore ├── LICENSE ├── docs └── README.md ├── examples ├── arduino │ └── hello_world │ │ └── hello_world.ino ├── c │ └── esp-idf-v5 │ │ └── get-started │ │ └── hello_world │ │ ├── CMakeLists.txt │ │ └── main │ │ ├── CMakeLists.txt │ │ └── hello_world_main.c ├── python │ ├── circuitpython │ │ ├── README.md │ │ └── hello_world │ │ │ └── code.py │ └── micropython │ │ ├── README.md │ │ └── hello_world │ │ └── main.py ├── rust │ ├── no_std │ │ ├── README.md │ │ └── hello-world │ │ │ ├── .cargo │ │ │ └── config.toml │ │ │ ├── Cargo.toml │ │ │ ├── diagram.json │ │ │ ├── rust-toolchain.toml │ │ │ ├── src │ │ │ └── main.rs │ │ │ └── wokwi.toml │ └── std │ │ ├── README.md │ │ └── hello-world │ │ ├── .cargo │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── rust-toolchain.toml │ │ └── src │ │ └── main.rs ├── toit │ ├── README.md │ └── hello_world │ │ └── hello_world.toit └── zig │ └── esp-idf-v5 │ └── get-started │ └── hello_world │ ├── CMakeLists.txt │ ├── build.zig │ ├── build.zig.zon │ └── main │ ├── CMakeLists.txt │ ├── hello.zig │ └── placeholder.c ├── support ├── python │ ├── gen_esp32part.py │ └── littlefs_generate.py ├── rust │ ├── rust-toolchain-esp32.toml │ ├── rust-toolchain-esp32c3.toml │ ├── rust-toolchain-esp32c6.toml │ ├── rust-toolchain-esp32h2.toml │ ├── rust-toolchain-esp32s2.toml │ └── rust-toolchain-esp32s3.toml └── wokwi │ ├── diagram-esp32.json │ ├── diagram-esp32c3.json │ ├── diagram-esp32c6.json │ ├── diagram-esp32h2.json │ ├── diagram-esp32s2.json │ ├── diagram-esp32s3.json │ ├── wokwi-esp32.toml │ ├── wokwi-esp32c3.toml │ ├── wokwi-esp32c6.toml │ ├── wokwi-esp32h2.toml │ ├── wokwi-esp32s2.toml │ └── wokwi-esp32s3.toml └── tests └── python └── circuitpython └── test-circuitpython.sh /.github/workflows/build-c-esp-idf-hello-world.yaml: -------------------------------------------------------------------------------- 1 | name: Build ESP32 ESP-IDF binaries and upload to GitHub Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | description: "Upload to specific release" 8 | required: true 9 | default: 'v0.1.0' 10 | skip_projects: 11 | description: "Skip projects during build (e.g. esp32-c3-devkit-rust)" 12 | required: false 13 | default: '' 14 | 15 | jobs: 16 | get_release: 17 | name: Get release 18 | runs-on: ubuntu-latest 19 | outputs: 20 | upload_url: ${{ steps.get_upload_url.outputs.url }} 21 | steps: 22 | - uses: octokit/request-action@v2.x 23 | id: get_release 24 | with: 25 | route: GET /repos/{owner}/{repo}/releases/tags/${{ github.event.inputs.release_tag }} 26 | owner: georgik 27 | repo: esp32-lang-lab 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | - name: get upload url 31 | id: get_upload_url 32 | run: | 33 | url=$(echo "$response" | jq -r '.upload_url' | sed 's/{?name,label}//') 34 | echo "url=$url" >> $GITHUB_OUTPUT 35 | env: 36 | response: ${{ steps.get_release.outputs.data }} 37 | 38 | build: 39 | runs-on: ubuntu-22.04 40 | container: 41 | image: espressif/idf:release-v5.1 42 | needs: get_release 43 | steps: 44 | - name: Clone repository with specific branch 45 | shell: bash 46 | run: | 47 | export HOME=/home/esp 48 | mkdir -p /home/esp 49 | cd /home/esp 50 | 51 | pwd 52 | git clone --depth 1 --branch ${{ github.ref_name }} https://github.com/georgik/esp32-lang-lab.git esp32-lang-lab 53 | 54 | set +e # Workaround for Exit code 2 exit code installation 55 | curl -L https://wokwi.com/ci/install.sh | sh 56 | exit_code=$? 57 | if [ $exit_code -eq 2 ]; then 58 | echo "Received exit code 2 from install script, overriding to 0" 59 | exit_code=0 60 | else 61 | exit $exit_code 62 | fi 63 | set -e 64 | 65 | - name: Build and upload binaries 66 | env: 67 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 68 | WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }} 69 | shell: bash 70 | run: | 71 | source /opt/esp/idf/export.sh 72 | 73 | # Workaround GitHub issue with setting HOME in container https://github.com/actions/runner/issues/863 74 | export HOME=/home/esp 75 | export SUPPORT_DIR="${HOME}/esp32-lang-lab/support" 76 | export PROJECT="${HOME}/esp32-lang-lab/examples/c/esp-idf-v5/get-started/hello_world/" 77 | cd /home/esp 78 | source /opt/esp/idf/export.sh 79 | 80 | # Install jq - workaround when running in the image without jq 81 | apt-get update && apt-get install -y jq 82 | 83 | echo "Project path: ${PROJECT}" 84 | cd ${PROJECT} 85 | 86 | # Prepare report 87 | echo '[]' > results.json 88 | 89 | for TARGET in esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2; do 90 | 91 | echo "Building $TARGET" 92 | OUTPUT_PREFIX="c-esp-idf-v5-hello-world-${TARGET}-${{ github.event.inputs.release_tag }}" 93 | OUTPUT_BIN="build/hello_world.bin" 94 | OUTPUT_TXT="${OUTPUT_PREFIX}.txt" 95 | 96 | # If TARGET is a substring in SKIP_PROJECTS, skip it 97 | #if echo "${{ github.event.inputs.skip_projects }}" | grep -q "${TARGET}"; then 98 | # echo "Skipping $TARGET" 99 | # continue 100 | #fi 101 | 102 | idf.py set-target "${TARGET}" 103 | idf.py build 104 | 105 | # Prepare Wokwi test 106 | cp ${SUPPORT_DIR}/wokwi/diagram-${TARGET}.json diagram.json 107 | 108 | echo '[wokwi]' > wokwi.toml 109 | echo 'version = 1' >> wokwi.toml 110 | echo 'elf = "build/hello_world.elf"' >> wokwi.toml 111 | echo 'firmware = "build/hello_world.bin"' >> wokwi.toml 112 | 113 | # Run Wokwi test 114 | /home/esp/bin/wokwi-cli --timeout 5000 \ 115 | --timeout-exit-code 0 \ 116 | --serial-log-file ${PROJECT}/${OUTPUT_TXT} \ 117 | --elf ${OUTPUT_BIN} \ 118 | "." 119 | 120 | # Upload binary 121 | asset_path="${PROJECT}/${OUTPUT_BIN}" 122 | asset_name="${OUTPUT_PREFIX}.bin" 123 | curl \ 124 | --request POST \ 125 | --header "authorization: Bearer $GITHUB_TOKEN" \ 126 | --header "Content-Type: application/octet-stream" \ 127 | --data-binary "@$asset_path" \ 128 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 129 | 130 | # Upload log 131 | asset_path="${PROJECT}/${OUTPUT_TXT}" 132 | asset_name="${OUTPUT_TXT}" 133 | curl \ 134 | --request POST \ 135 | --header "authorization: Bearer $GITHUB_TOKEN" \ 136 | --header "Content-Type: application/octet-stream" \ 137 | --data-binary "@$asset_path" \ 138 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 139 | 140 | # Extract heap size (you might need to adjust the parsing command) 141 | HEAP_SIZE=$(grep 'Minimum free heap size' ${OUTPUT_TXT} | sed -e 's/Minimum free heap size: //' -e 's/ bytes//') 142 | 143 | # Add a new record to the JSON database 144 | jq --arg target "$TARGET" \ 145 | --arg language "C" \ 146 | --arg flavor "ESP-IDF" \ 147 | --arg example "hello-world" \ 148 | --arg property "heap" \ 149 | --arg value "$HEAP_SIZE" \ 150 | --arg unit "bytes" \ 151 | --arg note "" \ 152 | --arg version "4.4.6" \ 153 | --arg timestamp "$(date -Iseconds)" \ 154 | '. += [{ 155 | target: $target, 156 | language: $language, 157 | flavor: $flavor, 158 | example: $example, 159 | property: $property, 160 | value: $value, 161 | unit: $unit, 162 | note: $note, 163 | version: $version, 164 | timestamp: $timestamp 165 | }]' results.json > temp.json && mv temp.json results.json 166 | 167 | # If skip-wokwi-test.toml exists, skip Wokwi test 168 | #if [ ! -f "skip-wokwi-test.toml" ]; then 169 | # asset_path="/home/esp/project/${TARGET}/screenshot.png" 170 | # asset_name="spooky-maze-${TARGET}-${{ github.event.inputs.release_tag }}.png" 171 | # curl \ 172 | # --request POST \ 173 | # --header "authorization: Bearer $GITHUB_TOKEN" \ 174 | # --header "Content-Type: application/octet-stream" \ 175 | # --data-binary "@$asset_path" \ 176 | # --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 177 | #fi 178 | done 179 | 180 | # Generate report 181 | echo "| Target | Language | Flavor | Example | Property | Value | Unit | Note | Version | Timestamp |" > report.md 182 | echo "|--------|----------|--------|---------|----------|-------|------|------|---------|-----------|" >> report.md 183 | jq -r '.[] | "| \(.target) | \(.language) | \(.flavor) | \(.example) | \(.property) | \(.value) | \(.unit) | \(.note) | \(.version) | \(.timestamp) |"' results.json >> report.md 184 | 185 | asset_path="report.md" 186 | asset_name="c-esp-idf-hello-world-report.md" 187 | curl \ 188 | --request POST \ 189 | --header "authorization: Bearer $GITHUB_TOKEN" \ 190 | --header "Content-Type: application/octet-stream" \ 191 | --data-binary "@$asset_path" \ 192 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 193 | 194 | asset_path="results.json" 195 | asset_name="c-esp-idf-hello-world-results.json" 196 | curl \ 197 | --request POST \ 198 | --header "authorization: Bearer $GITHUB_TOKEN" \ 199 | --header "Content-Type: application/octet-stream" \ 200 | --data-binary "@$asset_path" \ 201 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 202 | -------------------------------------------------------------------------------- /.github/workflows/build-python-circuitpython-hello-world.yaml: -------------------------------------------------------------------------------- 1 | name: Run ESP32 CircuitPython examples and upload results to GitHub Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | description: "Upload to specific release" 8 | required: true 9 | default: 'v0.1.0' 10 | skip_projects: 11 | description: "Skip projects during build (e.g. esp32-c3-devkit-rust)" 12 | required: false 13 | default: '' 14 | 15 | jobs: 16 | get_release: 17 | name: Get release 18 | runs-on: ubuntu-latest 19 | outputs: 20 | upload_url: ${{ steps.get_upload_url.outputs.url }} 21 | steps: 22 | - uses: octokit/request-action@v2.x 23 | id: get_release 24 | with: 25 | route: GET /repos/{owner}/{repo}/releases/tags/${{ github.event.inputs.release_tag }} 26 | owner: georgik 27 | repo: esp32-lang-lab 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | - name: get upload url 31 | id: get_upload_url 32 | run: | 33 | url=$(echo "$response" | jq -r '.upload_url' | sed 's/{?name,label}//') 34 | echo "url=$url" >> $GITHUB_OUTPUT 35 | env: 36 | response: ${{ steps.get_release.outputs.data }} 37 | 38 | build: 39 | runs-on: ubuntu-22.04 40 | # container: 41 | # image: espressif/idf:release-v5.1 42 | needs: get_release 43 | steps: 44 | - name: Checkout repository 45 | uses: actions/checkout@v3 46 | - name: Install Wokwi CLI 47 | shell: bash 48 | run: | 49 | curl -L https://wokwi.com/ci/install.sh | sh 50 | - name: Install littlefs-python and esptool 51 | shell: bash 52 | run: | 53 | pip3 install littlefs-python esptool 54 | - name: Run tests and upload results 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }} 58 | shell: bash 59 | run: | 60 | for TARGET in esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2; do 61 | ../tests/python/circuitpython/test-circuitpython.sh esp32c3 "`pwd`" "${{ github.event.inputs.release_tag }}" > "${PROJECT}/${OUTPUT_TXT}" 62 | 63 | # Upload log 64 | asset_path="${PROJECT}/${OUTPUT_TXT}" 65 | asset_name="${OUTPUT_TXT}" 66 | curl \ 67 | --request POST \ 68 | --header "authorization: Bearer $GITHUB_TOKEN" \ 69 | --header "Content-Type: application/octet-stream" \ 70 | --data-binary "@$asset_path" \ 71 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 72 | 73 | # Extract heap size (you might need to adjust the parsing command) 74 | HEAP_SIZE=$(grep 'Minimum free heap size' ${OUTPUT_TXT} | tr -d '\n' | sed -e 's/Minimum free heap size: //' -e 's/ bytes//') 75 | 76 | # Add a new record to the JSON database 77 | jq --arg target "$TARGET" \ 78 | --arg language "Python" \ 79 | --arg flavor "CircuitPython" \ 80 | --arg example "hello-world" \ 81 | --arg property "heap" \ 82 | --arg value "$HEAP_SIZE" \ 83 | --arg unit "bytes" \ 84 | --arg note "" \ 85 | --arg version "8.2.9" \ 86 | --arg timestamp "$(date -Iseconds)" \ 87 | '. += [{ 88 | target: $target, 89 | language: $language, 90 | flavor: $flavor, 91 | example: $example, 92 | property: $property, 93 | value: $value, 94 | unit: $unit, 95 | note: $note, 96 | version: $version, 97 | timestamp: $timestamp 98 | }]' results.json > temp.json && mv temp.json results.json 99 | 100 | done 101 | 102 | # Generate report 103 | echo "| Target | Language | Flavor | Example | Property | Value | Unit | Note | Version | Timestamp |" > report.md 104 | echo "|--------|----------|--------|---------|----------|-------|------|------|---------|-----------|" >> report.md 105 | jq -r '.[] | "| \(.target) | \(.language) | \(.flavor) | \(.example) | \(.property) | \(.value) | \(.unit) | \(.note) | \(.version) | \(.timestamp) |"' results.json >> report.md 106 | 107 | asset_path="report.md" 108 | asset_name="python-circuitpython-hello-world-report.md" 109 | curl \ 110 | --request POST \ 111 | --header "authorization: Bearer $GITHUB_TOKEN" \ 112 | --header "Content-Type: application/octet-stream" \ 113 | --data-binary "@$asset_path" \ 114 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 115 | 116 | asset_path="results.json" 117 | asset_name="python-circuitpython-hello-world-results.json" 118 | curl \ 119 | --request POST \ 120 | --header "authorization: Bearer $GITHUB_TOKEN" \ 121 | --header "Content-Type: application/octet-stream" \ 122 | --data-binary "@$asset_path" \ 123 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 124 | -------------------------------------------------------------------------------- /.github/workflows/build-python-micropython-hello-world.yaml: -------------------------------------------------------------------------------- 1 | name: Run ESP32 MicroPython examples and upload results to GitHub Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | description: "Upload to specific release" 8 | required: true 9 | default: 'v0.1.0' 10 | skip_projects: 11 | description: "Skip projects during build (e.g. esp32-c3-devkit-rust)" 12 | required: false 13 | default: '' 14 | 15 | jobs: 16 | get_release: 17 | name: Get release 18 | runs-on: ubuntu-latest 19 | outputs: 20 | upload_url: ${{ steps.get_upload_url.outputs.url }} 21 | steps: 22 | - uses: octokit/request-action@v2.x 23 | id: get_release 24 | with: 25 | route: GET /repos/{owner}/{repo}/releases/tags/${{ github.event.inputs.release_tag }} 26 | owner: georgik 27 | repo: esp32-lang-lab 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | - name: get upload url 31 | id: get_upload_url 32 | run: | 33 | url=$(echo "$response" | jq -r '.upload_url' | sed 's/{?name,label}//') 34 | echo "url=$url" >> $GITHUB_OUTPUT 35 | env: 36 | response: ${{ steps.get_release.outputs.data }} 37 | 38 | build: 39 | runs-on: ubuntu-22.04 40 | # container: 41 | # image: espressif/idf:release-v5.1 42 | needs: get_release 43 | steps: 44 | - name: Checkout repository 45 | uses: actions/checkout@v3 46 | - name: Install Wokwi CLI 47 | shell: bash 48 | run: | 49 | curl -L https://wokwi.com/ci/install.sh | sh 50 | - name: Install littlefs-python and esptool 51 | shell: bash 52 | run: | 53 | pip3 install littlefs-python esptool 54 | - name: Run tests and upload results 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }} 58 | shell: bash 59 | run: | 60 | # source path to wokwi-cli 61 | export PATH=$PATH:${HOME}/bin 62 | 63 | export SUPPORT_DIR="`pwd`/support" 64 | export PROJECT="`pwd`/examples/python/micropython/hello_world/" 65 | 66 | # Install jq - workaround when running in the image without jq 67 | #apt-get update && apt-get install -y jq 68 | 69 | echo "Project path: ${PROJECT}" 70 | cd ${PROJECT} 71 | 72 | # Prepare report 73 | echo '[]' > results.json 74 | 75 | #for TARGET in esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2; do 76 | for TARGET in esp32; do 77 | 78 | echo "Downloading MicroPython for $TARGET" 79 | RUNTIME_BIN="ESP32_GENERIC-20240105-v1.22.1.bin" 80 | curl -L https://micropython.org/resources/firmware/${RUNTIME_BIN} -o ${RUNTIME_BIN} 81 | 82 | echo "Building $TARGET" 83 | OUTPUT_PREFIX="python-micropython-hello-world-${TARGET}-${{ github.event.inputs.release_tag }}" 84 | OUTPUT_BIN="out.bin" 85 | OUTPUT_TXT="${OUTPUT_PREFIX}.txt" 86 | 87 | # If TARGET is a substring in SKIP_PROJECTS, skip it 88 | #if echo "${{ github.event.inputs.skip_projects }}" | grep -q "${TARGET}"; then 89 | # echo "Skipping $TARGET" 90 | # continue 91 | #fi 92 | 93 | # Merge filesystem with MicroPython binary 94 | python3 ${SUPPORT_DIR}/python/littlefs_generate.py 95 | esptool.py --chip ${TARGET} merge_bin -o ${OUTPUT_BIN} --flash_mode dio --flash_size 4MB 0x1000 ${RUNTIME_BIN} 0x200000 littlefs.img 96 | 97 | # Prepare Wokwi test 98 | cp ${SUPPORT_DIR}/wokwi/diagram-${TARGET}.json diagram.json 99 | 100 | echo '[wokwi]' > wokwi.toml 101 | echo 'version = 1' >> wokwi.toml 102 | echo "elf = \"${OUTPUT_BIN}\"" >> wokwi.toml 103 | echo "firmware = \"${OUTPUT_BIN}\"" >> wokwi.toml 104 | 105 | # Run Wokwi test 106 | wokwi-cli --timeout 5000 \ 107 | --timeout-exit-code 0 \ 108 | --serial-log-file ${PROJECT}/${OUTPUT_TXT} \ 109 | --elf ${OUTPUT_BIN} \ 110 | "." 111 | 112 | # Upload binary 113 | # asset_path="${PROJECT}/${OUTPUT_BIN}" 114 | # asset_name="${OUTPUT_PREFIX}.bin" 115 | # curl \ 116 | # --request POST \ 117 | # --header "authorization: Bearer $GITHUB_TOKEN" \ 118 | # --header "Content-Type: application/octet-stream" \ 119 | # --data-binary "@$asset_path" \ 120 | # --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 121 | 122 | # Upload log 123 | asset_path="${PROJECT}/${OUTPUT_TXT}" 124 | asset_name="${OUTPUT_TXT}" 125 | curl \ 126 | --request POST \ 127 | --header "authorization: Bearer $GITHUB_TOKEN" \ 128 | --header "Content-Type: application/octet-stream" \ 129 | --data-binary "@$asset_path" \ 130 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 131 | 132 | # Extract heap size (you might need to adjust the parsing command) 133 | HEAP_SIZE=$(grep 'Minimum free heap size' ${OUTPUT_TXT} | sed -e 's/Minimum free heap size: //' -e 's/ bytes//') 134 | 135 | # Add a new record to the JSON database 136 | jq --arg target "$TARGET" \ 137 | --arg language "Python" \ 138 | --arg flavor "MicroPython" \ 139 | --arg example "hello-world" \ 140 | --arg property "heap" \ 141 | --arg value "$HEAP_SIZE" \ 142 | --arg unit "bytes" \ 143 | --arg note "" \ 144 | --arg version "ESP32_GENERIC-20240105-v1.22.1" \ 145 | --arg timestamp "$(date -Iseconds)" \ 146 | '. += [{ 147 | target: $target, 148 | language: $language, 149 | flavor: $flavor, 150 | example: $example, 151 | property: $property, 152 | value: $value, 153 | unit: $unit, 154 | note: $note, 155 | version: $version, 156 | timestamp: $timestamp 157 | }]' results.json > temp.json && mv temp.json results.json 158 | 159 | # If skip-wokwi-test.toml exists, skip Wokwi test 160 | #if [ ! -f "skip-wokwi-test.toml" ]; then 161 | # asset_path="/home/esp/project/${TARGET}/screenshot.png" 162 | # asset_name="spooky-maze-${TARGET}-${{ github.event.inputs.release_tag }}.png" 163 | # curl \ 164 | # --request POST \ 165 | # --header "authorization: Bearer $GITHUB_TOKEN" \ 166 | # --header "Content-Type: application/octet-stream" \ 167 | # --data-binary "@$asset_path" \ 168 | # --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 169 | #fi 170 | done 171 | 172 | # Generate report 173 | echo "| Target | Language | Flavor | Example | Property | Value | Unit | Note | Version | Timestamp |" > report.md 174 | echo "|--------|----------|--------|---------|----------|-------|------|------|---------|-----------|" >> report.md 175 | jq -r '.[] | "| \(.target) | \(.language) | \(.flavor) | \(.example) | \(.property) | \(.value) | \(.unit) | \(.note) | \(.version) | \(.timestamp) |"' results.json >> report.md 176 | 177 | asset_path="report.md" 178 | asset_name="python-micropython-hello-world-report.md" 179 | curl \ 180 | --request POST \ 181 | --header "authorization: Bearer $GITHUB_TOKEN" \ 182 | --header "Content-Type: application/octet-stream" \ 183 | --data-binary "@$asset_path" \ 184 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 185 | 186 | asset_path="results.json" 187 | asset_name="python-micropython-hello-world-results.json" 188 | curl \ 189 | --request POST \ 190 | --header "authorization: Bearer $GITHUB_TOKEN" \ 191 | --header "Content-Type: application/octet-stream" \ 192 | --data-binary "@$asset_path" \ 193 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 194 | -------------------------------------------------------------------------------- /.github/workflows/build-rust-no_std-hello-world.yaml: -------------------------------------------------------------------------------- 1 | name: Build ESP32 Rust no_std binaries and upload to GitHub Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | description: "Upload to specific release" 8 | required: true 9 | default: 'v0.1.0' 10 | skip_projects: 11 | description: "Skip projects during build (e.g. esp32-c3-devkit-rust)" 12 | required: false 13 | default: '' 14 | 15 | jobs: 16 | get_release: 17 | name: Get release 18 | runs-on: ubuntu-latest 19 | outputs: 20 | upload_url: ${{ steps.get_upload_url.outputs.url }} 21 | steps: 22 | - uses: octokit/request-action@v2.x 23 | id: get_release 24 | with: 25 | route: GET /repos/{owner}/{repo}/releases/tags/${{ github.event.inputs.release_tag }} 26 | owner: georgik 27 | repo: esp32-lang-lab 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | - name: get upload url 31 | id: get_upload_url 32 | run: | 33 | url=$(echo "$response" | jq -r '.upload_url' | sed 's/{?name,label}//') 34 | echo "url=$url" >> $GITHUB_OUTPUT 35 | env: 36 | response: ${{ steps.get_release.outputs.data }} 37 | 38 | build: 39 | runs-on: ubuntu-22.04 40 | container: 41 | image: espressif/idf-rust:all_1.75.0.0 42 | options: --user esp --workdir /home/esp 43 | needs: get_release 44 | steps: 45 | - name: Clone repository with specific branch 46 | run: | 47 | export HOME=/home/esp 48 | cd /home/esp 49 | 50 | set +e # Workaround for Exit code 2 exit code installation 51 | curl -L https://wokwi.com/ci/install.sh | sh 52 | exit_code=$? 53 | if [ $exit_code -eq 2 ]; then 54 | echo "Received exit code 2 from install script, overriding to 0" 55 | exit_code=0 56 | else 57 | exit $exit_code 58 | fi 59 | set -e 60 | 61 | pwd 62 | git clone --depth 1 --branch ${{ github.ref_name }} https://github.com/georgik/esp32-lang-lab.git esp32-lang-lab 63 | - name: Build and upload binaries 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }} 67 | run: | 68 | # Workaround GitHub issue with setting HOME in container https://github.com/actions/runner/issues/863 69 | export HOME=/home/esp 70 | export SUPPORT_DIR="${HOME}/esp32-lang-lab/support" 71 | export PROJECT="${HOME}/esp32-lang-lab/examples/rust/no_std/hello-world" 72 | cd /home/esp 73 | . /home/esp/.bashrc 74 | . /home/esp/export-esp.sh 75 | 76 | # Install jq - workaround when running in the image without jq 77 | mkdir -p $HOME/bin 78 | curl -L -o $HOME/bin/jq "https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64" 79 | chmod +x $HOME/bin/jq 80 | export PATH="$HOME/bin:$PATH" 81 | # echo "$HOME/bin" >> $GITHUB_PATH 82 | 83 | # Upload loop for each binary 84 | cd ${PROJECT} 85 | 86 | # Prepare report 87 | echo '[]' > results.json 88 | 89 | for TARGET in esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2; do 90 | 91 | echo "Building $TARGET" 92 | OUTPUT_PREFIX="rust-no_std-hello-world-${TARGET}-${{ github.event.inputs.release_tag }}" 93 | OUTPUT_BIN="${OUTPUT_PREFIX}.bin" 94 | OUTPUT_TXT="${OUTPUT_PREFIX}.txt" 95 | 96 | # If TARGET is a substring in SKIP_PROJECTS, skip it 97 | #if echo "${{ github.event.inputs.skip_projects }}" | grep -q "${TARGET}"; then 98 | # echo "Skipping $TARGET" 99 | # continue 100 | #fi 101 | 102 | # Prepare Rust build 103 | cp ${SUPPORT_DIR}/rust/rust-toolchain-${TARGET}.toml rust-toolchain.toml 104 | echo "Toolchain: $(cat rust-toolchain.toml)" 105 | 106 | # Using alias from .cargo/config.toml 107 | cargo "save-image-${TARGET}" --release --merge --skip-padding ${OUTPUT_BIN} 108 | 109 | # Prepare Wokwi test 110 | cp ${SUPPORT_DIR}/wokwi/diagram-${TARGET}.json diagram.json 111 | cp ${SUPPORT_DIR}/wokwi/wokwi-${TARGET}.toml wokwi.toml 112 | 113 | # Run Wokwi test 114 | /home/esp/bin/wokwi-cli --timeout 5000 \ 115 | --timeout-exit-code 0 \ 116 | --serial-log-file ${PROJECT}/${OUTPUT_TXT} \ 117 | --elf ${OUTPUT_BIN} \ 118 | "." 119 | 120 | # Upload binary 121 | asset_path="${PROJECT}/${OUTPUT_BIN}" 122 | asset_name="${OUTPUT_BIN}" 123 | curl \ 124 | --request POST \ 125 | --header "authorization: Bearer $GITHUB_TOKEN" \ 126 | --header "Content-Type: application/octet-stream" \ 127 | --data-binary "@$asset_path" \ 128 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 129 | 130 | # Upload log 131 | asset_path="${PROJECT}/${OUTPUT_TXT}" 132 | asset_name="${OUTPUT_TXT}" 133 | curl \ 134 | --request POST \ 135 | --header "authorization: Bearer $GITHUB_TOKEN" \ 136 | --header "Content-Type: application/octet-stream" \ 137 | --data-binary "@$asset_path" \ 138 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 139 | 140 | # Extract heap size (you might need to adjust the parsing command) 141 | HEAP_SIZE=$(grep 'Minimum free heap size' ${OUTPUT_TXT} | sed -e 's/Minimum free heap size: //' -e 's/ bytes//') 142 | 143 | # Add a new record to the JSON database 144 | jq --arg target "$TARGET" \ 145 | --arg language "Rust" \ 146 | --arg flavor "no_std" \ 147 | --arg example "hello-world" \ 148 | --arg property "heap" \ 149 | --arg value "$HEAP_SIZE" \ 150 | --arg unit "bytes" \ 151 | --arg note "" \ 152 | --arg version "1.75" \ 153 | --arg timestamp "$(date -Iseconds)" \ 154 | '. += [{ 155 | target: $target, 156 | language: $language, 157 | flavor: $flavor, 158 | example: $example, 159 | property: $property, 160 | value: $value, 161 | unit: $unit, 162 | note: $note, 163 | version: $version, 164 | timestamp: $timestamp 165 | }]' results.json > temp.json && mv temp.json results.json 166 | 167 | # If skip-wokwi-test.toml exists, skip Wokwi test 168 | #if [ ! -f "skip-wokwi-test.toml" ]; then 169 | # asset_path="/home/esp/project/${TARGET}/screenshot.png" 170 | # asset_name="spooky-maze-${TARGET}-${{ github.event.inputs.release_tag }}.png" 171 | # curl \ 172 | # --request POST \ 173 | # --header "authorization: Bearer $GITHUB_TOKEN" \ 174 | # --header "Content-Type: application/octet-stream" \ 175 | # --data-binary "@$asset_path" \ 176 | # --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 177 | #fi 178 | done 179 | 180 | # Generate report 181 | echo "| Target | Language | Flavor | Example | Property | Value | Unit | Note | Version | Timestamp |" > report.md 182 | echo "|--------|----------|--------|---------|----------|-------|------|------|---------|-----------|" >> report.md 183 | jq -r '.[] | "| \(.target) | \(.language) | \(.flavor) | \(.example) | \(.property) | \(.value) | \(.unit) | \(.note) | \(.version) | \(.timestamp) |"' results.json >> report.md 184 | 185 | asset_path="${PROJECT}/report.md" 186 | asset_name="report.md" 187 | curl \ 188 | --request POST \ 189 | --header "authorization: Bearer $GITHUB_TOKEN" \ 190 | --header "Content-Type: application/octet-stream" \ 191 | --data-binary "@$asset_path" \ 192 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 193 | 194 | asset_path="${PROJECT}/results.json" 195 | asset_name="results.json" 196 | curl \ 197 | --request POST \ 198 | --header "authorization: Bearer $GITHUB_TOKEN" \ 199 | --header "Content-Type: application/octet-stream" \ 200 | --data-binary "@$asset_path" \ 201 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 202 | -------------------------------------------------------------------------------- /.github/workflows/build-rust-std-hello-world.yaml: -------------------------------------------------------------------------------- 1 | name: Build ESP32 Rust std binaries and upload to GitHub Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | description: "Upload to specific release" 8 | required: true 9 | default: 'v0.1.0' 10 | skip_projects: 11 | description: "Skip projects during build (e.g. esp32-c3-devkit-rust)" 12 | required: false 13 | default: '' 14 | 15 | jobs: 16 | get_release: 17 | name: Get release 18 | runs-on: ubuntu-latest 19 | outputs: 20 | upload_url: ${{ steps.get_upload_url.outputs.url }} 21 | steps: 22 | - uses: octokit/request-action@v2.x 23 | id: get_release 24 | with: 25 | route: GET /repos/{owner}/{repo}/releases/tags/${{ github.event.inputs.release_tag }} 26 | owner: georgik 27 | repo: esp32-lang-lab 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | - name: get upload url 31 | id: get_upload_url 32 | run: | 33 | url=$(echo "$response" | jq -r '.upload_url' | sed 's/{?name,label}//') 34 | echo "url=$url" >> $GITHUB_OUTPUT 35 | env: 36 | response: ${{ steps.get_release.outputs.data }} 37 | 38 | build: 39 | runs-on: ubuntu-22.04 40 | container: 41 | image: espressif/idf-rust:all_1.75.0.0 42 | options: --user esp --workdir /home/esp 43 | needs: get_release 44 | steps: 45 | - name: Clone repository with specific branch 46 | run: | 47 | export HOME=/home/esp 48 | cd /home/esp 49 | 50 | pwd 51 | git clone --depth 1 --branch ${{ github.ref_name }} https://github.com/georgik/esp32-lang-lab.git esp32-lang-lab 52 | 53 | set +e # Workaround for Exit code 2 exit code installation 54 | curl -L https://wokwi.com/ci/install.sh | sh 55 | exit_code=$? 56 | if [ $exit_code -eq 2 ]; then 57 | echo "Received exit code 2 from install script, overriding to 0" 58 | exit_code=0 59 | else 60 | exit $exit_code 61 | fi 62 | set -e 63 | 64 | - name: Build and upload binaries 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }} 68 | run: | 69 | # Workaround GitHub issue with setting HOME in container https://github.com/actions/runner/issues/863 70 | export HOME=/home/esp 71 | export SUPPORT_DIR="${HOME}/esp32-lang-lab/support" 72 | export PROJECT="${HOME}/esp32-lang-lab/examples/rust/std/hello-world" 73 | cd /home/esp 74 | . /home/esp/.bashrc 75 | . /home/esp/export-esp.sh 76 | 77 | # Install jq - workaround when running in the image without jq 78 | mkdir -p $HOME/bin 79 | curl -L -o $HOME/bin/jq "https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64" 80 | chmod +x $HOME/bin/jq 81 | export PATH="$HOME/bin:$PATH" 82 | # echo "$HOME/bin" >> $GITHUB_PATH 83 | 84 | echo "Project path: ${PROJECT}" 85 | cd ${PROJECT} 86 | 87 | # Prepare report 88 | echo '[]' > results.json 89 | 90 | for TARGET in esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2; do 91 | 92 | echo "Building $TARGET" 93 | export MCU=${TARGET} 94 | OUTPUT_PREFIX="rust-std-hello-world-${TARGET}-${{ github.event.inputs.release_tag }}" 95 | OUTPUT_BIN="${OUTPUT_PREFIX}.bin" 96 | OUTPUT_TXT="${OUTPUT_PREFIX}.txt" 97 | 98 | # If TARGET is a substring in SKIP_PROJECTS, skip it 99 | #if echo "${{ github.event.inputs.skip_projects }}" | grep -q "${TARGET}"; then 100 | # echo "Skipping $TARGET" 101 | # continue 102 | #fi 103 | 104 | # Prepare Rust build 105 | cp ${SUPPORT_DIR}/rust/rust-toolchain-${TARGET}.toml rust-toolchain.toml 106 | echo "Toolchain: $(cat rust-toolchain.toml)" 107 | 108 | # Using alias from .cargo/config.toml 109 | cargo "save-image-${TARGET}" --release --merge --skip-padding ${OUTPUT_BIN} 110 | 111 | # Prepare Wokwi test 112 | cp ${SUPPORT_DIR}/wokwi/diagram-${TARGET}.json diagram.json 113 | cp ${SUPPORT_DIR}/wokwi/wokwi-${TARGET}.toml wokwi.toml 114 | 115 | # Run Wokwi test 116 | /home/esp/bin/wokwi-cli --timeout 5000 \ 117 | --timeout-exit-code 0 \ 118 | --serial-log-file ${PROJECT}/${OUTPUT_TXT} \ 119 | --elf ${OUTPUT_BIN} \ 120 | "." 121 | 122 | # Upload binary 123 | asset_path="${PROJECT}/${OUTPUT_BIN}" 124 | asset_name="${OUTPUT_BIN}" 125 | curl \ 126 | --request POST \ 127 | --header "authorization: Bearer $GITHUB_TOKEN" \ 128 | --header "Content-Type: application/octet-stream" \ 129 | --data-binary "@$asset_path" \ 130 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 131 | 132 | # Upload log 133 | asset_path="${PROJECT}/${OUTPUT_TXT}" 134 | asset_name="${OUTPUT_TXT}" 135 | curl \ 136 | --request POST \ 137 | --header "authorization: Bearer $GITHUB_TOKEN" \ 138 | --header "Content-Type: application/octet-stream" \ 139 | --data-binary "@$asset_path" \ 140 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 141 | 142 | # Extract heap size (you might need to adjust the parsing command) 143 | HEAP_SIZE=$(grep 'Minimum free heap size' ${OUTPUT_TXT} | sed -e 's/Minimum free heap size: //' -e 's/ bytes//') 144 | 145 | # Add a new record to the JSON database 146 | jq --arg target "$TARGET" \ 147 | --arg language "Rust" \ 148 | --arg flavor "std" \ 149 | --arg example "hello-world" \ 150 | --arg property "heap" \ 151 | --arg value "$HEAP_SIZE" \ 152 | --arg unit "bytes" \ 153 | --arg note "" \ 154 | --arg version "4.4.6" \ 155 | --arg timestamp "$(date -Iseconds)" \ 156 | '. += [{ 157 | target: $target, 158 | language: $language, 159 | flavor: $flavor, 160 | example: $example, 161 | property: $property, 162 | value: $value, 163 | unit: $unit, 164 | note: $note, 165 | version: $version, 166 | timestamp: $timestamp 167 | }]' results.json > temp.json && mv temp.json results.json 168 | 169 | # If skip-wokwi-test.toml exists, skip Wokwi test 170 | #if [ ! -f "skip-wokwi-test.toml" ]; then 171 | # asset_path="/home/esp/project/${TARGET}/screenshot.png" 172 | # asset_name="spooky-maze-${TARGET}-${{ github.event.inputs.release_tag }}.png" 173 | # curl \ 174 | # --request POST \ 175 | # --header "authorization: Bearer $GITHUB_TOKEN" \ 176 | # --header "Content-Type: application/octet-stream" \ 177 | # --data-binary "@$asset_path" \ 178 | # --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 179 | #fi 180 | done 181 | 182 | # Generate report 183 | echo "| Target | Language | Flavor | Example | Property | Value | Unit | Note | Version | Timestamp |" > report.md 184 | echo "|--------|----------|--------|---------|----------|-------|------|------|---------|-----------|" >> report.md 185 | jq -r '.[] | "| \(.target) | \(.language) | \(.flavor) | \(.example) | \(.property) | \(.value) | \(.unit) | \(.note) | \(.version) | \(.timestamp) |"' results.json >> report.md 186 | 187 | asset_path="${PROJECT}/report.md" 188 | asset_name="rust-std-hello-world-report.md" 189 | curl \ 190 | --request POST \ 191 | --header "authorization: Bearer $GITHUB_TOKEN" \ 192 | --header "Content-Type: application/octet-stream" \ 193 | --data-binary "@$asset_path" \ 194 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 195 | 196 | asset_path="${PROJECT}/results.json" 197 | asset_name="rust-std-hello-world-results.json" 198 | curl \ 199 | --request POST \ 200 | --header "authorization: Bearer $GITHUB_TOKEN" \ 201 | --header "Content-Type: application/octet-stream" \ 202 | --data-binary "@$asset_path" \ 203 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 204 | -------------------------------------------------------------------------------- /.github/workflows/build-zig-esp-idf-hello-world.yaml: -------------------------------------------------------------------------------- 1 | name: Build ESP32 (ZIG) ESP-IDF binaries and upload to GitHub Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | description: "Upload to specific release" 8 | required: true 9 | default: 'v0.1.0' 10 | skip_projects: 11 | description: "Skip projects during build (e.g. esp32-c3-devkit-rust)" 12 | required: false 13 | default: '' 14 | 15 | jobs: 16 | get_release: 17 | name: Get release 18 | runs-on: ubuntu-latest 19 | outputs: 20 | upload_url: ${{ steps.get_upload_url.outputs.url }} 21 | steps: 22 | - uses: octokit/request-action@v2.x 23 | id: get_release 24 | with: 25 | route: GET /repos/{owner}/{repo}/releases/tags/${{ github.event.inputs.release_tag }} 26 | owner: georgik 27 | repo: esp32-lang-lab 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | - name: get upload url 31 | id: get_upload_url 32 | run: | 33 | url=$(echo "$response" | jq -r '.upload_url' | sed 's/{?name,label}//') 34 | echo "url=$url" >> $GITHUB_OUTPUT 35 | env: 36 | response: ${{ steps.get_release.outputs.data }} 37 | 38 | build: 39 | runs-on: ubuntu-22.04 40 | container: 41 | image: espressif/idf:release-v5.1 42 | needs: get_release 43 | steps: 44 | - name: Clone repository with specific branch 45 | shell: bash 46 | run: | 47 | export HOME=/home/esp 48 | mkdir -p /home/esp 49 | cd /home/esp 50 | 51 | pwd 52 | git clone --depth 1 --branch ${{ github.ref_name }} https://github.com/georgik/esp32-lang-lab.git esp32-lang-lab 53 | 54 | set +e # Workaround for Exit code 2 exit code installation 55 | curl -L https://wokwi.com/ci/install.sh | sh 56 | exit_code=$? 57 | if [ $exit_code -eq 2 ]; then 58 | echo "Received exit code 2 from install script, overriding to 0" 59 | exit_code=0 60 | else 61 | exit $exit_code 62 | fi 63 | set -e 64 | 65 | - name: Build and upload binaries 66 | env: 67 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 68 | WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }} 69 | shell: bash 70 | run: | 71 | source /opt/esp/idf/export.sh 72 | 73 | # Workaround GitHub issue with setting HOME in container https://github.com/actions/runner/issues/863 74 | export HOME=/home/esp 75 | export SUPPORT_DIR="${HOME}/esp32-lang-lab/support" 76 | export PROJECT="${HOME}/esp32-lang-lab/examples/zig/esp-idf-v5/get-started/hello_world/" 77 | cd /home/esp 78 | source /opt/esp/idf/export.sh 79 | 80 | # Install jq - workaround when running in the image without jq 81 | apt-get update && apt-get install -y jq 82 | 83 | echo "Project path: ${PROJECT}" 84 | cd ${PROJECT} 85 | 86 | # Prepare report 87 | echo '[]' > results.json 88 | 89 | for TARGET in esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2; do 90 | 91 | echo "Building $TARGET" 92 | OUTPUT_PREFIX="zig-esp-idf-v5-hello-world-${TARGET}-${{ github.event.inputs.release_tag }}" 93 | OUTPUT_BIN="build/hello_world_zig.bin" 94 | OUTPUT_TXT="${OUTPUT_PREFIX}.txt" 95 | 96 | # If TARGET is a substring in SKIP_PROJECTS, skip it 97 | #if echo "${{ github.event.inputs.skip_projects }}" | grep -q "${TARGET}"; then 98 | # echo "Skipping $TARGET" 99 | # continue 100 | #fi 101 | 102 | idf.py set-target "${TARGET}" 103 | idf.py build 104 | 105 | # Prepare Wokwi test 106 | cp ${SUPPORT_DIR}/wokwi/diagram-${TARGET}.json diagram.json 107 | 108 | echo '[wokwi]' > wokwi.toml 109 | echo 'version = 1' >> wokwi.toml 110 | echo 'elf = "build/hello_world_zig.elf"' >> wokwi.toml 111 | echo 'firmware = "build/hello_world_zig.bin"' >> wokwi.toml 112 | 113 | # Run Wokwi test 114 | /home/esp/bin/wokwi-cli --timeout 5000 \ 115 | --timeout-exit-code 0 \ 116 | --serial-log-file ${PROJECT}/${OUTPUT_TXT} \ 117 | --elf ${OUTPUT_BIN} \ 118 | "." 119 | 120 | # Upload binary 121 | asset_path="${PROJECT}/${OUTPUT_BIN}" 122 | asset_name="${OUTPUT_PREFIX}.bin" 123 | echo "Uploading $asset_path as $asset_name" 124 | curl \ 125 | --request POST \ 126 | --header "authorization: Bearer $GITHUB_TOKEN" \ 127 | --header "Content-Type: application/octet-stream" \ 128 | --data-binary "@$asset_path" \ 129 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" || true 130 | 131 | # Upload log 132 | asset_path="${PROJECT}/${OUTPUT_TXT}" 133 | asset_name="${OUTPUT_TXT}" 134 | echo "Uploading $asset_path as $asset_name" 135 | curl \ 136 | --request POST \ 137 | --header "authorization: Bearer $GITHUB_TOKEN" \ 138 | --header "Content-Type: application/octet-stream" \ 139 | --data-binary "@$asset_path" \ 140 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" || true 141 | 142 | # Extract heap size (you might need to adjust the parsing command) 143 | HEAP_SIZE=$(grep 'Minimum free heap size' ${OUTPUT_TXT} | sed -e 's/Minimum free heap size: //' -e 's/ bytes//') 144 | 145 | echo "Heap size: $HEAP_SIZE bytes" 146 | 147 | # Add a new record to the JSON database 148 | jq --arg target "$TARGET" \ 149 | --arg language "ZIG" \ 150 | --arg flavor "ESP-IDF" \ 151 | --arg example "hello-world" \ 152 | --arg property "heap" \ 153 | --arg value "$HEAP_SIZE" \ 154 | --arg unit "bytes" \ 155 | --arg note "" \ 156 | --arg version "4.4.6" \ 157 | --arg timestamp "$(date -Iseconds)" \ 158 | '. += [{ 159 | target: $target, 160 | language: $language, 161 | flavor: $flavor, 162 | example: $example, 163 | property: $property, 164 | value: $value, 165 | unit: $unit, 166 | note: $note, 167 | version: $version, 168 | timestamp: $timestamp 169 | }]' results.json > temp.json && mv temp.json results.json || echo "Failed to generate jq" 170 | 171 | # If skip-wokwi-test.toml exists, skip Wokwi test 172 | #if [ ! -f "skip-wokwi-test.toml" ]; then 173 | # asset_path="/home/esp/project/${TARGET}/screenshot.png" 174 | # asset_name="spooky-maze-${TARGET}-${{ github.event.inputs.release_tag }}.png" 175 | # curl \ 176 | # --request POST \ 177 | # --header "authorization: Bearer $GITHUB_TOKEN" \ 178 | # --header "Content-Type: application/octet-stream" \ 179 | # --data-binary "@$asset_path" \ 180 | # --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 181 | #fi 182 | done 183 | 184 | # Generate report 185 | echo "| Target | Language | Flavor | Example | Property | Value | Unit | Note | Version | Timestamp |" > report.md 186 | echo "|--------|----------|--------|---------|----------|-------|------|------|---------|-----------|" >> report.md 187 | jq -r '.[] | "| \(.target) | \(.language) | \(.flavor) | \(.example) | \(.property) | \(.value) | \(.unit) | \(.note) | \(.version) | \(.timestamp) |"' results.json >> report.md 188 | 189 | asset_path="report.md" 190 | asset_name="zig-esp-idf-hello-world-report.md" 191 | curl \ 192 | --request POST \ 193 | --header "authorization: Bearer $GITHUB_TOKEN" \ 194 | --header "Content-Type: application/octet-stream" \ 195 | --data-binary "@$asset_path" \ 196 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 197 | 198 | asset_path="results.json" 199 | asset_name="zig-esp-idf-hello-world-results.json" 200 | curl \ 201 | --request POST \ 202 | --header "authorization: Bearer $GITHUB_TOKEN" \ 203 | --header "Content-Type: application/octet-stream" \ 204 | --data-binary "@$asset_path" \ 205 | --url "${{ needs.get_release.outputs.upload_url }}?name=${asset_name}" 206 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | managed_components/ 3 | target/ 4 | zig-*/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Juraj Michálek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Language Lab 2 | 3 | The project is comparing different languages/technologies for ESP32 4 | 5 | ## hello_world 6 | 7 | The application outputs "Hello world!". Then it prints information about chip, flash and heap size. 8 | Then the application waits for 10 seconds before rebooting. 9 | 10 | ### Results for ESP32: 11 | 12 | | | ESP-IDF C | Arduino | CircuitPython | MicroPython | Rust no_std | Rust std | Toit |ESP-IDF ZIG | 13 | |-------------|------------|------------|---------------|-------------|-------------|----------|------|------------| 14 | | Chip Target | ESP32 | n/a | n/a | n/a | n/a | | n/a | | 15 | | CPU Cores | 2 | 2 | n/a | n/a | n/a | | n/a | | 16 | | Features | WiFi/BTBLE | WiFi/BTBLE | n/a | n/a | n/a | | n/a | | 17 | | Flash size | 2MB [^1] | 4MB | 8192 (?)[^4] | 4 MB | n/a | | n/a | | 18 | | Free heap | 300892 | 237568 | 113424 | 164064 | 179200 [^2] | 296028 | n/a | 300476 | 19 | 20 | ### Results for ESP32-S2: 21 | 22 | | | ESP-IDF C | Arduino | CircuitPython | MicroPython | Rust no_std | Rust std | Toit |ESP-IDF ZIG | 23 | |-------------|------------|------------|---------------|-------------|-------------|----------|------|------------| 24 | | Chip Target | esp32s2 | n/a | | | | | | | 25 | | CPU Cores | 1 | 1 | | | | | | | 26 | | Features | WiFi | WiFi | | | | | | | 27 | | Flash size | 2MB [^1] | 4MB | | | | | | | 28 | | Free heap | 246696 | 229688 | 70848 | 2059520 | 178176 [^3] | 246844 | | 248180 | 29 | 30 | 31 | ### Results for ESP32-S3: 32 | 33 | | | ESP-IDF C | Arduino | CircuitPython | MicroPython | Rust no_std | Rust std | Toit |ESP-IDF ZIG | 34 | |-------------|------------|------------|---------------|-------------|-------------|----------|------|------------| 35 | | Chip Target | esp32s3 | n/a | | | | | | | 36 | | CPU Cores | 2 | 2 | | | | | | | 37 | | Features | WiFi/BLE | WiFi/BLE | | | | | | | 38 | | Flash size | 2MB [^1] | 8MB external | | | | | | | 39 | | Free heap | 386744 | 36992 | 150432 | | 332800 | 388016 | | 389976 | 40 | 41 | ### Results for ESP32-C3: 42 | 43 | | | ESP-IDF C | Arduino | CircuitPython | MicroPython | Rust no_std | Rust std | Toit |ESP-IDF ZIG | 44 | |-------------|------------|------------|---------------|-------------|-------------|----------|------|------------| 45 | | Chip Target | esp32c3 | | | | | | | | 46 | | CPU Cores | 1 | | | | | | | | 47 | | Features | WiFi/BLE | | | | | | | | 48 | | Flash size | 2MB [^1] | | | | | | | | 49 | | Free heap | 327840 | | 129808 | | 322556 | 327124 | | 329700 | 50 | 51 | ### Results for ESP32-C6: 52 | 53 | | | ESP-IDF C | Arduino | CircuitPython | MicroPython | Rust no_std | Rust std | Toit |ESP-IDF ZIG | 54 | |-------------|------------|------------|---------------|-------------|-------------|----------|------|------------| 55 | | Chip Target | esp32c6 | Not supported | | | | | | | 56 | | CPU Cores | 1 | | | | | | | | 57 | | Features | WiFi/BLE 802.15.4 (Zigbee/Thread) | | | | | | | | 58 | | Flash size | 2MB (1.) | | | | | | | | 59 | | Free heap | 468852 | | | | 440316 | 471068 | | 471208 | 60 | 61 | ### Results for ESP32-H2: 62 | 63 | | | ESP-IDF C | Arduino | CircuitPython | MicroPython | Rust no_std | Rust std | Toit |ESP-IDF ZIG | 64 | |-------------|------------|------------|---------------|-------------|-------------|----------|------|------------| 65 | | Chip Target | esp32h2 | Not supported | | | | | | | 66 | | CPU Cores | 1 | | | | | | | | 67 | | Features | BLE, 802.15.4 (Zigbee/Thread) | | | | | | | | 68 | | Flash size | 2MB [^1] | | | | | | | | 69 | | Free heap | 262644 | | | | 252924 | 265060 | | 264824 | 70 | 71 | ### Results for ESP32-P4: 72 | 73 | | | ESP-IDF C | Arduino | CircuitPython | MicroPython | Rust no_std | Rust std | Toit |ESP-IDF ZIG | 74 | |-------------|------------|------------|---------------|-------------|-------------|----------|------|------------| 75 | | Chip Target | esp32p4 | Not supported | | | | | | | 76 | | CPU Cores | 2 | | | | | | | | 77 | | Features | none | | | | | | | | 78 | | Flash size | 2MB [^1] | | | | | | | | 79 | | Free heap | 618680 | | | | | | | | 80 | 81 | 82 | ## Notes 83 | 84 | [^1]: Compile time value, not auto-detected. 85 | [^2]: Rust no_std does not support non-continuous memory. ESP32 memory contains WiFi part in the middle. 86 | [^3]: Rust no_std does not support non-continuous memory. The issue is caused by the [bootloader on ESP32-S2](https://github.com/espressif/esp-idf/blob/5b1189570025ba027f2ff6c2d91f6ffff3809cc2/components/esp_system/ld/esp32s2/memory.ld.in#L41C27-L43). 87 | [^4]: Results marked with (?) does not seems to be correct, requires further validation. 88 | 89 | 90 | -------------------------------------------------------------------------------- /examples/arduino/hello_world/hello_world.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void setup() { 4 | Serial.begin(115200); 5 | delay(1000); // Give time for serial monitor to start 6 | 7 | Serial.println("Hello world!"); 8 | 9 | // Print chip information 10 | esp_chip_info_t chip_info; 11 | esp_chip_info(&chip_info); 12 | Serial.print("This is ESP32 chip with "); 13 | Serial.print(chip_info.cores); 14 | Serial.print(" CPU core(s), "); 15 | 16 | if (chip_info.features & CHIP_FEATURE_WIFI_BGN) Serial.print("WiFi/"); 17 | if (chip_info.features & CHIP_FEATURE_BT) Serial.print("BT"); 18 | if (chip_info.features & CHIP_FEATURE_BLE) Serial.print("BLE"); 19 | if (chip_info.features & CHIP_FEATURE_IEEE802154) Serial.print(", 802.15.4 (Zigbee/Thread)"); 20 | 21 | unsigned major_rev = chip_info.revision / 100; 22 | unsigned minor_rev = chip_info.revision % 100; 23 | Serial.print(" silicon revision v"); 24 | Serial.print(major_rev); 25 | Serial.print("."); 26 | Serial.println(minor_rev); 27 | 28 | uint32_t flash_size; 29 | if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) { 30 | Serial.println("Get flash size failed"); 31 | return; 32 | } 33 | Serial.print(flash_size / (uint32_t)(1024 * 1024)); 34 | Serial.print("MB "); 35 | Serial.print((chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); 36 | Serial.println(" flash"); 37 | 38 | Serial.print("Minimum free heap size: "); 39 | Serial.print(esp_get_minimum_free_heap_size()); 40 | Serial.println(" bytes"); 41 | } 42 | 43 | void loop() { 44 | // Countdown and restart 45 | for (int i = 10; i >= 0; i--) { 46 | Serial.print("Restarting in "); 47 | Serial.print(i); 48 | Serial.println(" seconds..."); 49 | delay(1000); 50 | } 51 | Serial.println("Restarting now."); 52 | ESP.restart(); 53 | } 54 | -------------------------------------------------------------------------------- /examples/c/esp-idf-v5/get-started/hello_world/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.16) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(hello_world) 7 | -------------------------------------------------------------------------------- /examples/c/esp-idf-v5/get-started/hello_world/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "hello_world_main.c" 2 | INCLUDE_DIRS "") 3 | -------------------------------------------------------------------------------- /examples/c/esp-idf-v5/get-started/hello_world/main/hello_world_main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: CC0-1.0 5 | */ 6 | 7 | #include 8 | #include 9 | #include "sdkconfig.h" 10 | #include "freertos/FreeRTOS.h" 11 | #include "freertos/task.h" 12 | #include "esp_chip_info.h" 13 | #include "esp_flash.h" 14 | #include "esp_system.h" 15 | 16 | void app_main(void) 17 | { 18 | printf("Hello world!\n"); 19 | 20 | /* Print chip information */ 21 | esp_chip_info_t chip_info; 22 | uint32_t flash_size; 23 | esp_chip_info(&chip_info); 24 | printf("This is %s chip with %d CPU core(s), %s%s%s%s, ", 25 | CONFIG_IDF_TARGET, 26 | chip_info.cores, 27 | (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "", 28 | (chip_info.features & CHIP_FEATURE_BT) ? "BT" : "", 29 | (chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "", 30 | (chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : ""); 31 | 32 | unsigned major_rev = chip_info.revision / 100; 33 | unsigned minor_rev = chip_info.revision % 100; 34 | printf("silicon revision v%d.%d, ", major_rev, minor_rev); 35 | if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) { 36 | printf("Get flash size failed"); 37 | return; 38 | } 39 | 40 | printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024), 41 | (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); 42 | 43 | printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size()); 44 | 45 | for (int i = 10; i >= 0; i--) { 46 | printf("starting in %d seconds...\n", i); 47 | vTaskDelay(1000 / portTICK_PERIOD_MS); 48 | } 49 | printf("Restarting now.\n"); 50 | fflush(stdout); 51 | esp_restart(); 52 | } 53 | -------------------------------------------------------------------------------- /examples/python/circuitpython/README.md: -------------------------------------------------------------------------------- 1 | # CircuitPython 2 | 3 | Binary of runtime: https://circuitpython.org/board/doit_esp32_devkit_v1/ 4 | 5 | ## Installation of prerequisites 6 | 7 | ``` 8 | cargo install espflash 9 | pip install adafruit-ampy 10 | ``` 11 | 12 | ## Deployment of runtime 13 | 14 | ``` 15 | curl -o circuitpython.bin https://downloads.circuitpython.org/bin/doit_esp32_devkit_v1/en_US/adafruit-circuitpython-doit_esp32_devkit_v1-en_US-8.2.9.bin 16 | espflash write-bin 0x1000 circuitpython.bin 17 | ``` 18 | 19 | ### Deployment of MicroPython code 20 | 21 | ``` 22 | ampy --port /dev/tty.usbserial-0001 put main.py 23 | espflash monitor # Press CTRL+R to reset the chip 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/python/circuitpython/hello_world/code.py: -------------------------------------------------------------------------------- 1 | import board 2 | import gc 3 | import microcontroller 4 | import time 5 | 6 | # Boot in Wokwi takes time, because of USB console redirection 7 | # Wait for a little bit, to get the output for the beginning 8 | time.sleep(5) 9 | 10 | print("Hello world!") 11 | 12 | 13 | # Flash size 14 | flash_size = microcontroller.nvm 15 | print("Flash size:", len(flash_size), "bytes") 16 | 17 | print("Minimum free heap size: {} bytes".format(gc.mem_free())) 18 | 19 | # Countdown and reset 20 | for i in range(10, -1, -1): 21 | print("Restarting in {} seconds...".format(i)) 22 | time.sleep(1) 23 | 24 | print("Restarting now.") 25 | microcontroller.reset() 26 | -------------------------------------------------------------------------------- /examples/python/micropython/README.md: -------------------------------------------------------------------------------- 1 | # MicroPython 2 | 3 | Binary of runtime: https://micropython.org/download/ESP32_GENERIC/ 4 | 5 | ## Installation of prerequisites 6 | 7 | ``` 8 | cargo install espflash 9 | pip install adafruit-ampy 10 | ``` 11 | 12 | ## Deployment of runtime 13 | 14 | ``` 15 | curl -o ESP32_GENERIC.bin https://micropython.org/resources/firmware/ESP32_GENERIC-20240105-v1.22.1.bin 16 | espflash write-bin 0x1000 ESP32_GENERIC.bin 17 | ``` 18 | 19 | ### Deployment of MicroPython code 20 | 21 | ``` 22 | ampy --port /dev/tty.usbserial-0001 put main.py 23 | espflash monitor # Press CTRL+R to reset the chip 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/python/micropython/hello_world/main.py: -------------------------------------------------------------------------------- 1 | import machine 2 | import esp 3 | import gc 4 | import time 5 | import os 6 | 7 | print("Hello world!") 8 | 9 | # Flash size 10 | flash_size = esp.flash_size() 11 | print("Flash size: {} MB".format(flash_size // (1024 * 1024))) 12 | 13 | # Minimum free heap size 14 | print("Minimum free heap size: {} bytes".format(gc.mem_free())) 15 | 16 | # Countdown and restart 17 | for i in range(10, -1, -1): 18 | print("Restarting in {} seconds...".format(i)) 19 | time.sleep(1) 20 | 21 | print("Restarting now.") 22 | machine.reset() 23 | -------------------------------------------------------------------------------- /examples/rust/no_std/README.md: -------------------------------------------------------------------------------- 1 | ## Build Rust no_std example 2 | 3 | The project is using .cargo/config.toml alias mechanism. 4 | 5 | Instead of `cargo run`, use alias: 6 | 7 | ``` 8 | cargo esp32 --release 9 | cargo esp32s2 --release 10 | cargo esp32s3 --release 11 | cargo esp32c3 --release 12 | cargo esp32c6 --release 13 | cargo esp32h2 --release 14 | ``` -------------------------------------------------------------------------------- /examples/rust/no_std/hello-world/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | esp32 = "run --features esp32 --target xtensa-esp32-none-elf --features esp32-hal/default" 3 | esp32s2 = "run --features esp32s2 --target xtensa-esp32s2-none-elf --features esp32s2-hal/default" 4 | esp32s3 = "run --features esp32s3 --target xtensa-esp32s3-none-elf --features esp32s3-hal/default" 5 | esp32c2 = "run --features esp32c2 --target riscv32imc-unknown-none-elf --features esp32c2-hal/default" 6 | esp32c3 = "run --features esp32c3 --target riscv32imc-unknown-none-elf --features esp32c3-hal/default" 7 | esp32c6 = "run --features esp32c6 --target riscv32imac-unknown-none-elf --features esp32c6-hal/default" 8 | esp32h2 = "run --features esp32h2 --target riscv32imac-unknown-none-elf --features esp32h2-hal/default" 9 | 10 | build-esp32 = "build --features esp32 --target xtensa-esp32-none-elf --features esp32-hal/default" 11 | build-esp32s2 = "build --features esp32s2 --target xtensa-esp32s2-none-elf --features esp32s2-hal/default" 12 | build-esp32s3 = "build --features esp32s3 --target xtensa-esp32s3-none-elf --features esp32s3-hal/default" 13 | build-esp32c2 = "build --features esp32c2 --target riscv32imc-unknown-none-elf --features esp32c2-hal/default" 14 | build-esp32c3 = "build --features esp32c3 --target riscv32imc-unknown-none-elf --features esp32c3-hal/default" 15 | build-esp32c6 = "build --features esp32c6 --target riscv32imac-unknown-none-elf --features esp32c6-hal/default" 16 | build-esp32h2 = "build --features esp32h2 --target riscv32imac-unknown-none-elf --features esp32h2-hal/default" 17 | 18 | save-image-esp32 = "espflash save-image --chip esp32 --features esp32 --target xtensa-esp32-none-elf --features esp32-hal/default" 19 | save-image-esp32s2 = "espflash save-image --chip esp32s2 --features esp32s2 --target xtensa-esp32s2-none-elf --features esp32s2-hal/default" 20 | save-image-esp32s3 = "espflash save-image --chip esp32s3 --features esp32s3 --target xtensa-esp32s3-none-elf --features esp32s3-hal/default" 21 | save-image-esp32c2 = "espflash save-image --chip esp32c2 --features esp32c2 --target riscv32imc-unknown-none-elf --features esp32c2-hal/default" 22 | save-image-esp32c3 = "espflash save-image --chip esp32c3 --features esp32c3 --target riscv32imc-unknown-none-elf --features esp32c3-hal/default" 23 | save-image-esp32c6 = "espflash save-image --chip esp32c6 --features esp32c6 --target riscv32imac-unknown-none-elf --features esp32c6-hal/default" 24 | save-image-esp32h2 = "espflash save-image --chip esp32h2 --features esp32h2 --target riscv32imac-unknown-none-elf --features esp32h2-hal/default" 25 | 26 | [target.riscv32imc-unknown-none-elf] 27 | runner = "espflash flash --monitor" 28 | rustflags = [ 29 | "-C", "link-arg=-Tlinkall.x", 30 | "-C", "force-frame-pointers", 31 | ] 32 | 33 | [target.riscv32imac-unknown-none-elf] 34 | runner = "espflash flash --monitor" 35 | rustflags = [ 36 | "-C", "link-arg=-Tlinkall.x", 37 | "-C", "force-frame-pointers", 38 | ] 39 | 40 | [target.xtensa-esp32-none-elf] 41 | runner = "espflash flash --monitor" 42 | rustflags = [ 43 | "-C", "link-arg=-Tlinkall.x", 44 | ] 45 | 46 | [target.xtensa-esp32s3-none-elf] 47 | runner = "espflash flash --monitor" 48 | rustflags = [ 49 | "-C", "link-arg=-Tlinkall.x", 50 | ] 51 | 52 | [target.xtensa-esp32s2-none-elf] 53 | runner = "espflash flash --monitor" 54 | rustflags = [ 55 | "-C", "link-arg=-Tlinkall.x", 56 | "-C", "force-frame-pointers", 57 | ] 58 | # [build] 59 | #target = "xtensa-esp32-none-elf" 60 | # target = "xtensa-esp32s2-none-elf" 61 | # target = "xtensa-esp32s3-none-elf" 62 | #target = "riscv32imc-unknown-none-elf" 63 | # target = "riscv32imac-unknown-none-elf" 64 | 65 | [unstable] 66 | build-std = [ "core", "alloc" ] 67 | -------------------------------------------------------------------------------- /examples/rust/no_std/hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-world" 3 | version = "0.1.0" 4 | authors = ["Juraj Michálek "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | esp32-hal = { version = "0.18.0", optional = true, default-features = false } 10 | esp32s2-hal = { version = "0.15.0", optional = true, default-features = false } 11 | esp32s3-hal = { version = "0.15.0", optional = true, default-features = false } 12 | esp32c3-hal = { version = "0.15.0", optional = true, default-features = false } 13 | esp32c6-hal = { version = "0.8.0", optional = true, default-features = false } 14 | esp32h2-hal = { version = "0.6.0", optional = true, default-features = false } 15 | 16 | esp-alloc = { version = "0.3.0" } 17 | esp-backtrace = { version = "0.10.0", features = [ "panic-handler", "exception-handler", "print-uart"], optional = true} 18 | esp-println = { version = "0.8.0", features = [ ], optional = true} 19 | 20 | 21 | [features] 22 | # default = [ "esp32" ] 23 | esp32 = [ "esp32-hal", "esp-backtrace/esp32", "esp-println/esp32" ] 24 | esp32s2 = [ "esp32s2-hal", "esp-backtrace/esp32s2", "esp-println/esp32s2" ] 25 | esp32s3 = [ "esp32s3-hal", "esp-backtrace/esp32s3", "esp-println/esp32s3" ] 26 | esp32c3 = [ "esp32c3-hal", "esp-backtrace/esp32c3", "esp-println/esp32c3" ] 27 | esp32c6 = [ "esp32c6-hal", "esp-backtrace/esp32c6", "esp-println/esp32c6" ] 28 | esp32h2 = [ "esp32h2-hal", "esp-backtrace/esp32h2", "esp-println/esp32h2" ] -------------------------------------------------------------------------------- /examples/rust/no_std/hello-world/diagram.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "author": "Juraj Michálek", 4 | "editor": "wokwi", 5 | "parts": [ { "type": "board-esp32-devkit-c-v4", "id": "esp", "top": 0, "left": 0, "attrs": {} } ], 6 | "connections": [ [ "esp:TX", "$serialMonitor:RX", "", [] ], [ "esp:RX", "$serialMonitor:TX", "", [] ] ], 7 | "dependencies": {} 8 | } -------------------------------------------------------------------------------- /examples/rust/no_std/hello-world/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "esp" 3 | # channel = "nightly" 4 | -------------------------------------------------------------------------------- /examples/rust/no_std/hello-world/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | #[cfg(feature = "esp32")] 5 | pub use esp32_hal as hal; 6 | #[cfg(feature = "esp32c2")] 7 | pub use esp32c2_hal as hal; 8 | #[cfg(feature = "esp32c3")] 9 | pub use esp32c3_hal as hal; 10 | #[cfg(feature = "esp32c6")] 11 | pub use esp32c6_hal as hal; 12 | #[cfg(feature = "esp32h2")] 13 | pub use esp32h2_hal as hal; 14 | #[cfg(feature = "esp32s2")] 15 | pub use esp32s2_hal as hal; 16 | #[cfg(feature = "esp32s3")] 17 | pub use esp32s3_hal as hal; 18 | 19 | extern crate alloc; 20 | use core::mem::MaybeUninit; 21 | use esp_backtrace as _; 22 | use esp_println::println; 23 | use hal::{clock::ClockControl, peripherals::Peripherals, prelude::*, Delay}; 24 | 25 | #[global_allocator] 26 | static ALLOCATOR: esp_alloc::EspHeap = esp_alloc::EspHeap::empty(); 27 | 28 | fn init_heap() { 29 | #[cfg(feature = "esp32")] 30 | const HEAP_SIZE: usize = 175 * 1024; 31 | #[cfg(feature = "esp32s2")] 32 | const HEAP_SIZE: usize = 174 * 1024; 33 | #[cfg(feature = "esp32s3")] 34 | const HEAP_SIZE: usize = 325 * 1024; 35 | #[cfg(feature = "esp32c3")] 36 | const HEAP_SIZE: usize = 315 * 1024; 37 | #[cfg(feature = "esp32c6")] 38 | const HEAP_SIZE: usize = 430 * 1024; 39 | #[cfg(feature = "esp32h2")] 40 | const HEAP_SIZE: usize = 247 * 1024; 41 | 42 | 43 | static mut HEAP: MaybeUninit<[u8; HEAP_SIZE]> = MaybeUninit::uninit(); 44 | 45 | unsafe { 46 | ALLOCATOR.init(HEAP.as_mut_ptr() as *mut u8, HEAP_SIZE); 47 | } 48 | } 49 | 50 | #[entry] 51 | fn main() -> ! { 52 | 53 | init_heap(); 54 | 55 | let peripherals = Peripherals::take(); 56 | let system = peripherals.SYSTEM.split(); 57 | 58 | let clocks = ClockControl::max(system.clock_control).freeze(); 59 | let mut delay = Delay::new(&clocks); 60 | 61 | println!("Hello world!"); 62 | 63 | // println!("This is ESP32 chip with ... CPU core(s), ..."); 64 | 65 | println!("Minimum free heap size: {} bytes", ALLOCATOR.free()); 66 | 67 | // Countdown from 10 to 0 with a 1-second delay 68 | for i in (0..=10).rev() { 69 | println!("starting in {} seconds...", i); 70 | delay.delay_ms(1000u32); 71 | } 72 | 73 | println!("Restarting now."); 74 | 75 | loop { 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /examples/rust/no_std/hello-world/wokwi.toml: -------------------------------------------------------------------------------- 1 | [wokwi] 2 | version = 1 3 | elf = "target/xtensa-esp32-none-elf/release/hello-world" 4 | firmware = "target/xtensa-esp32-none-elf/release/hello-world" 5 | -------------------------------------------------------------------------------- /examples/rust/std/README.md: -------------------------------------------------------------------------------- 1 | # Rust std 2 | 3 | Using ESP-IDF as OS. 4 | 5 | ## Prerequisites 6 | 7 | - ESP-IDF 5.1 (explicit version) 8 | ``` 9 | cargo install ldproxy espflash 10 | ``` 11 | -------------------------------------------------------------------------------- /examples/rust/std/hello-world/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | esp32 = "run --target xtensa-esp32-none-elf " 3 | esp32s2 = "run --target xtensa-esp32s2-none-elf " 4 | esp32s3 = "run --target xtensa-esp32s3-none-elf " 5 | esp32c2 = "run --target riscv32imc-unknown-none-elf " 6 | esp32c3 = "run --target riscv32imc-unknown-none-elf " 7 | esp32c6 = "run --target riscv32imac-unknown-none-elf" 8 | esp32h2 = "run --target riscv32imac-unknown-none-elf" 9 | 10 | build-esp32 = "build --target xtensa-esp32-none-elf " 11 | build-esp32s2 = "build --target xtensa-esp32s2-none-elf " 12 | build-esp32s3 = "build --target xtensa-esp32s3-none-elf " 13 | build-esp32c2 = "build --target riscv32imc-unknown-none-elf " 14 | build-esp32c3 = "build --target riscv32imc-unknown-none-elf " 15 | build-esp32c6 = "build --target riscv32imac-unknown-none-elf" 16 | build-esp32h2 = "build --target riscv32imac-unknown-none-elf" 17 | 18 | save-image-esp32 = "espflash save-image --chip esp32 --target xtensa-esp32-none-elf " 19 | save-image-esp32s2 = "espflash save-image --chip esp32s2 --target xtensa-esp32s2-none-elf " 20 | save-image-esp32s3 = "espflash save-image --chip esp32s3 --target xtensa-esp32s3-none-elf " 21 | save-image-esp32c2 = "espflash save-image --chip esp32c2 --target riscv32imc-unknown-none-elf " 22 | save-image-esp32c3 = "espflash save-image --chip esp32c3 --target riscv32imc-unknown-none-elf " 23 | save-image-esp32c6 = "espflash save-image --chip esp32c6 --target riscv32imac-unknown-none-elf" 24 | save-image-esp32h2 = "espflash save-image --chip esp32h2 --target riscv32imac-unknown-none-elf" 25 | 26 | 27 | [target.xtensa-esp32-espidf] 28 | linker = "ldproxy" 29 | runner = "espflash flash --monitor" 30 | rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 31 | 32 | [target.xtensa-esp32s2-espidf] 33 | linker = "ldproxy" 34 | runner = "espflash flash --monitor" 35 | rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 36 | 37 | [target.xtensa-esp32s3-espidf] 38 | linker = "ldproxy" 39 | runner = "espflash flash --monitor" 40 | rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 41 | 42 | [target.riscv32imc-esp-espidf] 43 | linker = "ldproxy" 44 | runner = "espflash flash --monitor" 45 | rustflags = ["--cfg", "espidf_time64", "-C", "default-linker-libraries"] 46 | 47 | [target.riscv32imac-esp-espidf] 48 | linker = "ldproxy" 49 | runner = "espflash flash --monitor" 50 | rustflags = ["--cfg", "espidf_time64", "-C", "default-linker-libraries"] 51 | 52 | [target.riscv32imafc-esp-espidf] 53 | linker = "ldproxy" 54 | runner = "espflash flash --monitor" 55 | rustflags = ["--cfg", "espidf_time64", "-C", "default-linker-libraries"] 56 | 57 | [unstable] 58 | build-std = ["std", "panic_abort"] 59 | 60 | [env] 61 | # MCU="esp32" 62 | # MCU="esp32s2" 63 | # MCU="esp32s3" 64 | # MCU="esp32c3" 65 | # MCU="esp32c6" 66 | #MCU="esp32h2" 67 | # MCU="esp32p4" 68 | 69 | # Note: this variable is not used by the pio builder (`cargo build --features pio`) 70 | ESP_IDF_VERSION = "v4.4.6" 71 | 72 | -------------------------------------------------------------------------------- /examples/rust/std/hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-world" 3 | version = "0.1.0" 4 | authors = ["Juraj Michálek "] 5 | edition = "2021" 6 | resolver = "2" 7 | rust-version = "1.71" 8 | 9 | [profile.release] 10 | opt-level = "s" 11 | 12 | [profile.dev] 13 | debug = true # Symbols are nice and they don't increase the size on Flash 14 | opt-level = "z" 15 | 16 | [features] 17 | default = ["std", "embassy", "esp-idf-svc/native"] 18 | 19 | pio = ["esp-idf-svc/pio"] 20 | std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"] 21 | alloc = ["esp-idf-svc/alloc"] 22 | nightly = ["esp-idf-svc/nightly"] 23 | experimental = ["esp-idf-svc/experimental"] 24 | embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"] 25 | 26 | [dependencies] 27 | log = { version = "0.4", default-features = false } 28 | esp-idf-svc = { version = "0.47.3", default-features = false } 29 | 30 | [build-dependencies] 31 | embuild = "0.31.3" 32 | -------------------------------------------------------------------------------- /examples/rust/std/hello-world/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | embuild::espidf::sysenv::output(); 3 | } 4 | -------------------------------------------------------------------------------- /examples/rust/std/hello-world/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "esp" 3 | -------------------------------------------------------------------------------- /examples/rust/std/hello-world/src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | use esp_idf_svc::{ 3 | hal::{ 4 | delay::Ets, 5 | reset::restart, 6 | }, 7 | sys::*, 8 | }; 9 | // use esp_idf_svc::sys::esp_get_free_heap_size; 10 | fn main() { 11 | esp_idf_svc::sys::link_patches(); 12 | esp_idf_svc::log::EspLogger::initialize_default(); 13 | 14 | log::info!("Hello, world!"); 15 | 16 | // Access chip information 17 | // let chip_info = esp_chip_info(&chip_info); 18 | // let mut features = String::new(); 19 | // if chip_info.features & esp_idf_sys::CHIP_FEATURE_WIFI_BGN != 0 { 20 | // features.push_str("WiFi/"); 21 | // } 22 | // if chip_info.features & esp_idf_sys::CHIP_FEATURE_BLE != 0 { 23 | // features.push_str("BLE/"); 24 | // } 25 | 26 | // log::info!("This is ESP32 chip with {} CPU core(s), {}", chip_info.cores, features); 27 | 28 | // Get flash size 29 | // let mut flash_size: u32 = 0; 30 | // unsafe { 31 | // esp_flash_get_size(0, &mut flash_size); 32 | // } 33 | // log::info!("Flash size: {} MB", flash_size / (1024 * 1024)); 34 | 35 | // Get free heap size 36 | let free_heap = unsafe { esp_get_free_heap_size() }; 37 | log::info!("Free heap size: {} bytes", free_heap); 38 | 39 | // // Countdown 40 | for i in (0..=10).rev() { 41 | Ets::delay_ms(1000_u32); 42 | log::info!("Restarting in {} seconds...", i); 43 | } 44 | 45 | log::info!("Restarting now."); 46 | restart(); 47 | } 48 | -------------------------------------------------------------------------------- /examples/toit/README.md: -------------------------------------------------------------------------------- 1 | # Toit 2 | 3 | Install Toit following the instructions: https://docs.toit.io/getstarted/device 4 | 5 | ### Monitoring of the console 6 | 7 | ``` 8 | jag monitor 9 | ``` 10 | 11 | ### Deployment of Toit code 12 | 13 | ``` 14 | jag run -d IP_ADDR hello_world.toit 15 | ``` 16 | -------------------------------------------------------------------------------- /examples/toit/hello_world/hello_world.toit: -------------------------------------------------------------------------------- 1 | 2 | main: 3 | print "Hello world!" 4 | 5 | // Countdown 6 | for i := 10; i >= 0; i--: 7 | print "Restarting in $i seconds..." 8 | sleep --ms=1000 9 | 10 | print "Restarting now." 11 | -------------------------------------------------------------------------------- /examples/zig/esp-idf-v5/get-started/hello_world/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.16) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | set(BUILD_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 7 | project(hello_world_zig) 8 | -------------------------------------------------------------------------------- /examples/zig/esp-idf-v5/get-started/hello_world/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const idf = @import("idf"); 3 | 4 | pub fn build(b: *std.Build) !void { 5 | const target = b.standardTargetOptions(.{ 6 | .whitelist = idf.espressif_targets, 7 | .default_target = idf.espressif_targets[0], 8 | }); 9 | const optimize = b.standardOptimizeOption(.{}); 10 | 11 | const lib = b.addStaticLibrary(.{ 12 | .name = "hello", 13 | .root_source_file = .{ .path = "main/hello.zig" }, 14 | .target = target, 15 | .optimize = optimize, 16 | }); 17 | lib.root_module.addImport("esp_idf", idf.idf_wrapped_modules(b)); 18 | 19 | const idf_path = std.process.getEnvVarOwned(b.allocator, "IDF_PATH") catch ""; 20 | if (!std.mem.eql(u8, idf_path, "")) { 21 | try idf.searched_idf_include(b, lib, idf_path); 22 | try searched_idf_libs(b, lib); 23 | } 24 | lib.linkLibC(); 25 | b.installArtifact(lib); 26 | } 27 | 28 | fn searched_idf_libs(b: *std.Build, lib: *std.Build.Step.Compile) !void { 29 | var dir = try std.fs.cwd().openDir("../build", .{ 30 | .iterate = true, 31 | }); 32 | defer dir.close(); 33 | var walker = try dir.walk(b.allocator); 34 | defer walker.deinit(); 35 | 36 | while (try walker.next()) |entry| { 37 | const ext = std.fs.path.extension(entry.basename); 38 | const lib_ext = inline for (&.{".obj"}) |e| { 39 | if (std.mem.eql(u8, ext, e)) 40 | break true; 41 | } else false; 42 | if (lib_ext) { 43 | const src_path = std.fs.path.dirname(@src().file).?; 44 | const cwd_path = b.pathJoin(&.{ src_path, "build", b.dupe(entry.path) }); 45 | const lib_file: std.Build.LazyPath = .{ .path = cwd_path }; 46 | lib.addObjectFile(lib_file); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/zig/esp-idf-v5/get-started/hello_world/build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = "hello World!", 3 | .version = "0.1.0", 4 | .dependencies = .{ 5 | .idf = .{ 6 | .url = "git+https://github.com/kassane/zig-esp-idf-sample#71c342b77863f56ded6748f49a10ecab5f67bbc0", 7 | .hash = "1220e30e27fd2c1e09684c7eb9584584ed43bd4df1b5127ba149fea3fd9a17ea5045", 8 | }, 9 | }, 10 | .paths = .{""}, 11 | } 12 | -------------------------------------------------------------------------------- /examples/zig/esp-idf-v5/get-started/hello_world/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "placeholder.c" 2 | INCLUDE_DIRS "." 3 | ) 4 | set(include_dirs $ ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}) 5 | 6 | # Detect target architecture and platform 7 | if(CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "x86_64") 8 | set(TARGET_ARCH "x86_64") 9 | elseif(CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64") 10 | set(TARGET_ARCH "aarch64") 11 | else() 12 | message(FATAL_ERROR "Unsupported architecture") 13 | endif() 14 | 15 | set(EXT "tar.xz") 16 | if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") 17 | set(TARGET_PLATFORM "linux-musl") 18 | elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") 19 | set(TARGET_PLATFORM "windows-gnu") 20 | set(EXT "zip") 21 | elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") 22 | set(TARGET_PLATFORM "macos-none") 23 | else() 24 | message(FATAL_ERROR "Unsupported platform") 25 | endif() 26 | 27 | if(NOT EXISTS "${CMAKE_BINARY_DIR}/zig-${TARGET_ARCH}-relsafe-espressif-${TARGET_PLATFORM}-baseline") 28 | file(DOWNLOAD "https://github.com/kassane/zig-espressif-bootstrap/releases/download/0.12.0-dev/zig-${TARGET_ARCH}-relsafe-espressif-${TARGET_PLATFORM}-baseline.${EXT}" 29 | "${CMAKE_BINARY_DIR}/zig.${EXT}") 30 | 31 | if(CMAKE_SYSTEM_NAME STREQUAL "Windows") 32 | execute_process( 33 | COMMAND powershell -Command "Expand-Archive -Path ${CMAKE_BINARY_DIR}/zig.${EXT} -DestinationPath ${CMAKE_BINARY_DIR}" 34 | ) 35 | else() 36 | execute_process( 37 | COMMAND ${CMAKE_COMMAND} -E tar xf ${CMAKE_BINARY_DIR}/zig.${EXT} 38 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 39 | ) 40 | endif() 41 | else() 42 | message(STATUS "Zig already downloaded. Skipping zig install.") 43 | endif() 44 | set(ZIG_INSTALL ${CMAKE_BINARY_DIR}/zig-${TARGET_ARCH}-relsafe-espressif-${TARGET_PLATFORM}-baseline) 45 | 46 | if(CONFIG_IDF_TARGET_ARCH_RISCV) 47 | set(ZIG_TARGET "riscv32-freestanding-none") 48 | if(CONFIG_IDF_TARGET_ESP32C6 OR CONFIG_IDF_TARGET_ESP32C5 OR CONFIG_IDF_TARGET_ESP32H2) 49 | set(TARGET_CPU_MODEL "generic_rv32+m+a+c") 50 | elseif(CONFIG_IDF_TARGET_ESP32P4) 51 | string(REGEX REPLACE "-none" "-eabihf" ZIG_TARGET ${ZIG_TARGET}) 52 | set(TARGET_CPU_MODEL "generic_rv32+m+a+c+f") 53 | else() 54 | set(TARGET_CPU_MODEL "generic_rv32+m+c") 55 | endif() 56 | elseif(CONFIG_IDF_TARGET_ARCH_XTENSA) 57 | set(ZIG_TARGET "xtensa-freestanding-none") 58 | if(CONFIG_IDF_TARGET_ESP32) 59 | set(TARGET_CPU_MODEL "esp32") 60 | elseif(CONFIG_IDF_TARGET_ESP32S2) 61 | set(TARGET_CPU_MODEL "esp32s2") 62 | else(CONFIG_IDF_TARGET_ESP32S3) 63 | set(TARGET_CPU_MODEL "esp32s3") 64 | endif() 65 | else() 66 | message(FATAL_ERROR "Unsupported target ${CONFIG_IDF_TARGET}") 67 | endif() 68 | 69 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 70 | set(ZIG_BUILD_TYPE "Debug") 71 | else() 72 | set(ZIG_BUILD_TYPE "ReleaseSafe") 73 | endif() 74 | 75 | add_custom_target(zig_build 76 | COMMAND ${CMAKE_COMMAND} -E env 77 | "INCLUDE_DIRS=${include_dirs}" 78 | ${ZIG_INSTALL}/zig build 79 | --build-file ${BUILD_PATH}/build.zig 80 | -Doptimize=${ZIG_BUILD_TYPE} 81 | -Dtarget=${ZIG_TARGET} 82 | -Dcpu=${TARGET_CPU_MODEL} 83 | -freference-trace 84 | --prominent-compile-errors 85 | --cache-dir ${CMAKE_BINARY_DIR}/zig-cache 86 | --prefix ${CMAKE_BINARY_DIR} 87 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 88 | BYPRODUCTS ${CMAKE_BINARY_DIR}/lib/libhello.a 89 | VERBATIM) 90 | 91 | add_prebuilt_library(zig ${CMAKE_BINARY_DIR}/lib/libhello.a) 92 | add_dependencies(zig zig_build) 93 | target_link_libraries(${COMPONENT_LIB} PRIVATE ${CMAKE_BINARY_DIR}/lib/libhello.a) 94 | -------------------------------------------------------------------------------- /examples/zig/esp-idf-v5/get-started/hello_world/main/hello.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const idf = @import("esp_idf"); 4 | 5 | export fn app_main() callconv(.C) void { 6 | var heap = idf.heap.HeapCapsAllocator.init(.MALLOC_CAP_DEFAULT); 7 | var arena = std.heap.ArenaAllocator.init(heap.allocator()); 8 | defer arena.deinit(); 9 | const allocator = arena.allocator(); 10 | 11 | log.info("Hello, world from Zig!", .{}); 12 | 13 | log.info( 14 | \\[Zig Info] 15 | \\* Version: {s} 16 | \\* Compiler Backend: {s} 17 | \\ 18 | , .{ builtin.zig_version_string, @tagName(builtin.zig_backend) }); 19 | 20 | idf.ESP_LOG( 21 | allocator, 22 | tag, 23 | \\[Memory Info] 24 | \\* Total: {d} 25 | \\* Free: {d} 26 | \\* Minimum free heap size: {d} 27 | \\ 28 | , 29 | .{ 30 | heap.totalSize(), 31 | heap.freeSize(), 32 | heap.minimumFreeSize(), 33 | }, 34 | ); 35 | 36 | idf.ESP_LOG(allocator, tag, "Let's have a look at your shiny {s} - {s} system! :)\n\n", .{ 37 | @tagName(builtin.cpu.arch), 38 | builtin.cpu.model.name, 39 | }); 40 | 41 | if (builtin.mode == .Debug) 42 | heap.dump(); 43 | } 44 | 45 | // override the std panic function with idf.panic 46 | pub const panic = idf.panic; 47 | const log = std.log.scoped(.@"esp-idf"); 48 | pub const std_options = .{ 49 | .log_level = switch (builtin.mode) { 50 | .Debug => .debug, 51 | else => .info, 52 | }, 53 | // Define logFn to override the std implementation 54 | .logFn = idf.espLogFn, 55 | }; 56 | 57 | const tag = "zig-hello"; 58 | -------------------------------------------------------------------------------- /examples/zig/esp-idf-v5/get-started/hello_world/main/placeholder.c: -------------------------------------------------------------------------------- 1 | // empty (app) file -------------------------------------------------------------------------------- /support/python/gen_esp32part.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Source: https://github.com/espressif/esp-idf/blob/master/components/partition_table/gen_esp32part.py 4 | # 5 | # ESP32 partition table generation tool 6 | # 7 | # Converts partition tables to/from CSV and binary formats. 8 | # 9 | # See https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/partition-tables.html 10 | # for explanation of partition table structure and uses. 11 | # 12 | # SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD 13 | # SPDX-License-Identifier: Apache-2.0 14 | 15 | from __future__ import division, print_function, unicode_literals 16 | 17 | import argparse 18 | import binascii 19 | import errno 20 | import hashlib 21 | import os 22 | import re 23 | import struct 24 | import sys 25 | 26 | MAX_PARTITION_LENGTH = 0xC00 # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature 27 | MD5_PARTITION_BEGIN = b'\xEB\xEB' + b'\xFF' * 14 # The first 2 bytes are like magic numbers for MD5 sum 28 | PARTITION_TABLE_SIZE = 0x1000 # Size of partition table 29 | 30 | MIN_PARTITION_SUBTYPE_APP_OTA = 0x10 31 | NUM_PARTITION_SUBTYPE_APP_OTA = 16 32 | 33 | SECURE_NONE = None 34 | SECURE_V1 = 'v1' 35 | SECURE_V2 = 'v2' 36 | 37 | __version__ = '1.3' 38 | 39 | APP_TYPE = 0x00 40 | DATA_TYPE = 0x01 41 | 42 | TYPES = { 43 | 'app': APP_TYPE, 44 | 'data': DATA_TYPE, 45 | } 46 | 47 | 48 | def get_ptype_as_int(ptype): 49 | """ Convert a string which might be numeric or the name of a partition type to an integer """ 50 | try: 51 | return TYPES[ptype] 52 | except KeyError: 53 | try: 54 | return int(ptype, 0) 55 | except TypeError: 56 | return ptype 57 | 58 | 59 | # Keep this map in sync with esp_partition_subtype_t enum in esp_partition.h 60 | SUBTYPES = { 61 | APP_TYPE: { 62 | 'factory': 0x00, 63 | 'test': 0x20, 64 | }, 65 | DATA_TYPE: { 66 | 'ota': 0x00, 67 | 'phy': 0x01, 68 | 'nvs': 0x02, 69 | 'coredump': 0x03, 70 | 'nvs_keys': 0x04, 71 | 'efuse': 0x05, 72 | 'undefined': 0x06, 73 | 'esphttpd': 0x80, 74 | 'fat': 0x81, 75 | 'spiffs': 0x82, 76 | 'littlefs': 0x83, 77 | }, 78 | } 79 | 80 | 81 | def get_subtype_as_int(ptype, subtype): 82 | """ Convert a string which might be numeric or the name of a partition subtype to an integer """ 83 | try: 84 | return SUBTYPES[get_ptype_as_int(ptype)][subtype] 85 | except KeyError: 86 | try: 87 | return int(subtype, 0) 88 | except TypeError: 89 | return subtype 90 | 91 | 92 | ALIGNMENT = { 93 | APP_TYPE: 0x10000, 94 | DATA_TYPE: 0x1000, 95 | } 96 | 97 | 98 | def get_alignment_offset_for_type(ptype): 99 | return ALIGNMENT.get(ptype, ALIGNMENT[DATA_TYPE]) 100 | 101 | 102 | def get_alignment_size_for_type(ptype): 103 | if ptype == APP_TYPE: 104 | if secure == SECURE_V1: 105 | # For secure boot v1 case, app partition must be 64K aligned 106 | # signature block (68 bytes) lies at the very end of 64K block 107 | return 0x10000 108 | elif secure == SECURE_V2: 109 | # For secure boot v2 case, app partition must be 4K aligned 110 | # signature block (4K) is kept after padding the unsigned image to 64K boundary 111 | return 0x1000 112 | else: 113 | # For no secure boot enabled case, app partition must be 4K aligned (min. flash erase size) 114 | return 0x1000 115 | # No specific size alignement requirement as such 116 | return 0x1 117 | 118 | 119 | def get_partition_type(ptype): 120 | if ptype == 'app': 121 | return APP_TYPE 122 | if ptype == 'data': 123 | return DATA_TYPE 124 | raise InputError('Invalid partition type') 125 | 126 | 127 | def add_extra_subtypes(csv): 128 | for line_no in csv: 129 | try: 130 | fields = [line.strip() for line in line_no.split(',')] 131 | for subtype, subtype_values in SUBTYPES.items(): 132 | if (int(fields[2], 16) in subtype_values.values() and subtype == get_partition_type(fields[0])): 133 | raise ValueError('Found duplicate value in partition subtype') 134 | SUBTYPES[TYPES[fields[0]]][fields[1]] = int(fields[2], 16) 135 | except InputError as err: 136 | raise InputError('Error parsing custom subtypes: %s' % err) 137 | 138 | 139 | quiet = False 140 | md5sum = True 141 | secure = SECURE_NONE 142 | offset_part_table = 0 143 | 144 | 145 | def status(msg): 146 | """ Print status message to stderr """ 147 | if not quiet: 148 | critical(msg) 149 | 150 | 151 | def critical(msg): 152 | """ Print critical message to stderr """ 153 | sys.stderr.write(msg) 154 | sys.stderr.write('\n') 155 | 156 | 157 | class PartitionTable(list): 158 | def __init__(self): 159 | super(PartitionTable, self).__init__(self) 160 | 161 | @classmethod 162 | def from_file(cls, f): 163 | data = f.read() 164 | data_is_binary = data[0:2] == PartitionDefinition.MAGIC_BYTES 165 | if data_is_binary: 166 | status('Parsing binary partition input...') 167 | return cls.from_binary(data), True 168 | 169 | data = data.decode() 170 | status('Parsing CSV input...') 171 | return cls.from_csv(data), False 172 | 173 | @classmethod 174 | def from_csv(cls, csv_contents): 175 | res = PartitionTable() 176 | lines = csv_contents.splitlines() 177 | 178 | def expand_vars(f): 179 | f = os.path.expandvars(f) 180 | m = re.match(r'(? 1) 257 | 258 | # print sorted duplicate partitions by name 259 | if len(duplicates) != 0: 260 | critical('A list of partitions that have the same name:') 261 | for p in sorted(self, key=lambda x:x.name): 262 | if len(duplicates.intersection([p.name])) != 0: 263 | critical('%s' % (p.to_csv())) 264 | raise InputError('Partition names must be unique') 265 | 266 | # check for overlaps 267 | last = None 268 | for p in sorted(self, key=lambda x:x.offset): 269 | if p.offset < offset_part_table + PARTITION_TABLE_SIZE: 270 | raise InputError('Partition offset 0x%x is below 0x%x' % (p.offset, offset_part_table + PARTITION_TABLE_SIZE)) 271 | if last is not None and p.offset < last.offset + last.size: 272 | raise InputError('Partition at 0x%x overlaps 0x%x-0x%x' % (p.offset, last.offset, last.offset + last.size - 1)) 273 | last = p 274 | 275 | # check that otadata should be unique 276 | otadata_duplicates = [p for p in self if p.type == TYPES['data'] and p.subtype == SUBTYPES[DATA_TYPE]['ota']] 277 | if len(otadata_duplicates) > 1: 278 | for p in otadata_duplicates: 279 | critical('%s' % (p.to_csv())) 280 | raise InputError('Found multiple otadata partitions. Only one partition can be defined with type="data"(1) and subtype="ota"(0).') 281 | 282 | if len(otadata_duplicates) == 1 and otadata_duplicates[0].size != 0x2000: 283 | p = otadata_duplicates[0] 284 | critical('%s' % (p.to_csv())) 285 | raise InputError('otadata partition must have size = 0x2000') 286 | 287 | def flash_size(self): 288 | """ Return the size that partitions will occupy in flash 289 | (ie the offset the last partition ends at) 290 | """ 291 | try: 292 | last = sorted(self, reverse=True)[0] 293 | except IndexError: 294 | return 0 # empty table! 295 | return last.offset + last.size 296 | 297 | def verify_size_fits(self, flash_size_bytes: int) -> None: 298 | """ Check that partition table fits into the given flash size. 299 | Raises InputError otherwise. 300 | """ 301 | table_size = self.flash_size() 302 | if flash_size_bytes < table_size: 303 | mb = 1024 * 1024 304 | raise InputError('Partitions tables occupies %.1fMB of flash (%d bytes) which does not fit in configured ' 305 | "flash size %dMB. Change the flash size in menuconfig under the 'Serial Flasher Config' menu." % 306 | (table_size / mb, table_size, flash_size_bytes / mb)) 307 | 308 | @classmethod 309 | def from_binary(cls, b): 310 | md5 = hashlib.md5() 311 | result = cls() 312 | for o in range(0,len(b),32): 313 | data = b[o:o + 32] 314 | if len(data) != 32: 315 | raise InputError('Partition table length must be a multiple of 32 bytes') 316 | if data == b'\xFF' * 32: 317 | return result # got end marker 318 | if md5sum and data[:2] == MD5_PARTITION_BEGIN[:2]: # check only the magic number part 319 | if data[16:] == md5.digest(): 320 | continue # the next iteration will check for the end marker 321 | else: 322 | raise InputError("MD5 checksums don't match! (computed: 0x%s, parsed: 0x%s)" % (md5.hexdigest(), binascii.hexlify(data[16:]))) 323 | else: 324 | md5.update(data) 325 | result.append(PartitionDefinition.from_binary(data)) 326 | raise InputError('Partition table is missing an end-of-table marker') 327 | 328 | def to_binary(self): 329 | result = b''.join(e.to_binary() for e in self) 330 | if md5sum: 331 | result += MD5_PARTITION_BEGIN + hashlib.md5(result).digest() 332 | if len(result) >= MAX_PARTITION_LENGTH: 333 | raise InputError('Binary partition table length (%d) longer than max' % len(result)) 334 | result += b'\xFF' * (MAX_PARTITION_LENGTH - len(result)) # pad the sector, for signing 335 | return result 336 | 337 | def to_csv(self, simple_formatting=False): 338 | rows = ['# ESP-IDF Partition Table', 339 | '# Name, Type, SubType, Offset, Size, Flags'] 340 | rows += [x.to_csv(simple_formatting) for x in self] 341 | return '\n'.join(rows) + '\n' 342 | 343 | 344 | class PartitionDefinition(object): 345 | MAGIC_BYTES = b'\xAA\x50' 346 | 347 | # dictionary maps flag name (as used in CSV flags list, property name) 348 | # to bit set in flags words in binary format 349 | FLAGS = { 350 | 'encrypted': 0, 351 | 'readonly': 1 352 | } 353 | 354 | # add subtypes for the 16 OTA slot values ("ota_XX, etc.") 355 | for ota_slot in range(NUM_PARTITION_SUBTYPE_APP_OTA): 356 | SUBTYPES[TYPES['app']]['ota_%d' % ota_slot] = MIN_PARTITION_SUBTYPE_APP_OTA + ota_slot 357 | 358 | def __init__(self): 359 | self.name = '' 360 | self.type = None 361 | self.subtype = None 362 | self.offset = None 363 | self.size = None 364 | self.encrypted = False 365 | self.readonly = False 366 | 367 | @classmethod 368 | def from_csv(cls, line, line_no): 369 | """ Parse a line from the CSV """ 370 | line_w_defaults = line + ',,,,' # lazy way to support default fields 371 | fields = [f.strip() for f in line_w_defaults.split(',')] 372 | 373 | res = PartitionDefinition() 374 | res.line_no = line_no 375 | res.name = fields[0] 376 | res.type = res.parse_type(fields[1]) 377 | res.subtype = res.parse_subtype(fields[2]) 378 | res.offset = res.parse_address(fields[3]) 379 | res.size = res.parse_address(fields[4]) 380 | if res.size is None: 381 | raise InputError("Size field can't be empty") 382 | 383 | flags = fields[5].split(':') 384 | for flag in flags: 385 | if flag in cls.FLAGS: 386 | setattr(res, flag, True) 387 | elif len(flag) > 0: 388 | raise InputError("CSV flag column contains unknown flag '%s'" % (flag)) 389 | 390 | return res 391 | 392 | def __eq__(self, other): 393 | return self.name == other.name and self.type == other.type \ 394 | and self.subtype == other.subtype and self.offset == other.offset \ 395 | and self.size == other.size 396 | 397 | def __repr__(self): 398 | def maybe_hex(x): 399 | return '0x%x' % x if x is not None else 'None' 400 | return "PartitionDefinition('%s', 0x%x, 0x%x, %s, %s)" % (self.name, self.type, self.subtype or 0, 401 | maybe_hex(self.offset), maybe_hex(self.size)) 402 | 403 | def __str__(self): 404 | return "Part '%s' %d/%d @ 0x%x size 0x%x" % (self.name, self.type, self.subtype, self.offset or -1, self.size or -1) 405 | 406 | def __cmp__(self, other): 407 | return self.offset - other.offset 408 | 409 | def __lt__(self, other): 410 | return self.offset < other.offset 411 | 412 | def __gt__(self, other): 413 | return self.offset > other.offset 414 | 415 | def __le__(self, other): 416 | return self.offset <= other.offset 417 | 418 | def __ge__(self, other): 419 | return self.offset >= other.offset 420 | 421 | def parse_type(self, strval): 422 | if strval == '': 423 | raise InputError("Field 'type' can't be left empty.") 424 | return parse_int(strval, TYPES) 425 | 426 | def parse_subtype(self, strval): 427 | if strval == '': 428 | if self.type == TYPES['app']: 429 | raise InputError('App partition cannot have an empty subtype') 430 | return SUBTYPES[DATA_TYPE]['undefined'] 431 | return parse_int(strval, SUBTYPES.get(self.type, {})) 432 | 433 | def parse_address(self, strval): 434 | if strval == '': 435 | return None # PartitionTable will fill in default 436 | return parse_int(strval) 437 | 438 | def verify(self): 439 | if self.type is None: 440 | raise ValidationError(self, 'Type field is not set') 441 | if self.subtype is None: 442 | raise ValidationError(self, 'Subtype field is not set') 443 | if self.offset is None: 444 | raise ValidationError(self, 'Offset field is not set') 445 | if self.size is None: 446 | raise ValidationError(self, 'Size field is not set') 447 | offset_align = get_alignment_offset_for_type(self.type) 448 | if self.offset % offset_align: 449 | raise ValidationError(self, 'Offset 0x%x is not aligned to 0x%x' % (self.offset, offset_align)) 450 | if self.type == APP_TYPE: 451 | size_align = get_alignment_size_for_type(self.type) 452 | if self.size % size_align: 453 | raise ValidationError(self, 'Size 0x%x is not aligned to 0x%x' % (self.size, size_align)) 454 | 455 | if self.name in TYPES and TYPES.get(self.name, '') != self.type: 456 | critical("WARNING: Partition has name '%s' which is a partition type, but does not match this partition's " 457 | 'type (0x%x). Mistake in partition table?' % (self.name, self.type)) 458 | all_subtype_names = [] 459 | for names in (t.keys() for t in SUBTYPES.values()): 460 | all_subtype_names += names 461 | if self.name in all_subtype_names and SUBTYPES.get(self.type, {}).get(self.name, '') != self.subtype: 462 | critical("WARNING: Partition has name '%s' which is a partition subtype, but this partition has " 463 | 'non-matching type 0x%x and subtype 0x%x. Mistake in partition table?' % (self.name, self.type, self.subtype)) 464 | 465 | always_rw_data_subtypes = [SUBTYPES[DATA_TYPE]['ota'], SUBTYPES[DATA_TYPE]['coredump']] 466 | if self.type == TYPES['data'] and self.subtype in always_rw_data_subtypes and self.readonly is True: 467 | raise ValidationError(self, "'%s' partition of type %s and subtype %s is always read-write and cannot be read-only" % 468 | (self.name, self.type, self.subtype)) 469 | 470 | STRUCT_FORMAT = b'<2sBBLL16sL' 471 | 472 | @classmethod 473 | def from_binary(cls, b): 474 | if len(b) != 32: 475 | raise InputError('Partition definition length must be exactly 32 bytes. Got %d bytes.' % len(b)) 476 | res = cls() 477 | (magic, res.type, res.subtype, res.offset, 478 | res.size, res.name, flags) = struct.unpack(cls.STRUCT_FORMAT, b) 479 | if b'\x00' in res.name: # strip null byte padding from name string 480 | res.name = res.name[:res.name.index(b'\x00')] 481 | res.name = res.name.decode() 482 | if magic != cls.MAGIC_BYTES: 483 | raise InputError('Invalid magic bytes (%r) for partition definition' % magic) 484 | for flag,bit in cls.FLAGS.items(): 485 | if flags & (1 << bit): 486 | setattr(res, flag, True) 487 | flags &= ~(1 << bit) 488 | if flags != 0: 489 | critical('WARNING: Partition definition had unknown flag(s) 0x%08x. Newer binary format?' % flags) 490 | return res 491 | 492 | def get_flags_list(self): 493 | return [flag for flag in self.FLAGS.keys() if getattr(self, flag)] 494 | 495 | def to_binary(self): 496 | flags = sum((1 << self.FLAGS[flag]) for flag in self.get_flags_list()) 497 | return struct.pack(self.STRUCT_FORMAT, 498 | self.MAGIC_BYTES, 499 | self.type, self.subtype, 500 | self.offset, self.size, 501 | self.name.encode(), 502 | flags) 503 | 504 | def to_csv(self, simple_formatting=False): 505 | def addr_format(a, include_sizes): 506 | if not simple_formatting and include_sizes: 507 | for (val, suffix) in [(0x100000, 'M'), (0x400, 'K')]: 508 | if a % val == 0: 509 | return '%d%s' % (a // val, suffix) 510 | return '0x%x' % a 511 | 512 | def lookup_keyword(t, keywords): 513 | for k,v in keywords.items(): 514 | if simple_formatting is False and t == v: 515 | return k 516 | return '%d' % t 517 | 518 | def generate_text_flags(): 519 | """ colon-delimited list of flags """ 520 | return ':'.join(self.get_flags_list()) 521 | 522 | return ','.join([self.name, 523 | lookup_keyword(self.type, TYPES), 524 | lookup_keyword(self.subtype, SUBTYPES.get(self.type, {})), 525 | addr_format(self.offset, False), 526 | addr_format(self.size, True), 527 | generate_text_flags()]) 528 | 529 | 530 | def parse_int(v, keywords={}): 531 | """Generic parser for integer fields - int(x,0) with provision for 532 | k/m/K/M suffixes and 'keyword' value lookup. 533 | """ 534 | try: 535 | for letter, multiplier in [('k', 1024), ('m', 1024 * 1024)]: 536 | if v.lower().endswith(letter): 537 | return parse_int(v[:-1], keywords) * multiplier 538 | return int(v, 0) 539 | except ValueError: 540 | if len(keywords) == 0: 541 | raise InputError('Invalid field value %s' % v) 542 | try: 543 | return keywords[v.lower()] 544 | except KeyError: 545 | raise InputError("Value '%s' is not valid. Known keywords: %s" % (v, ', '.join(keywords))) 546 | 547 | 548 | def main(): 549 | global quiet 550 | global md5sum 551 | global offset_part_table 552 | global secure 553 | parser = argparse.ArgumentParser(description='ESP32 partition table utility') 554 | 555 | parser.add_argument('--flash-size', help='Optional flash size limit, checks partition table fits in flash', 556 | nargs='?', choices=['1MB', '2MB', '4MB', '8MB', '16MB', '32MB', '64MB', '128MB']) 557 | parser.add_argument('--disable-md5sum', help='Disable md5 checksum for the partition table', default=False, action='store_true') 558 | parser.add_argument('--no-verify', help="Don't verify partition table fields", action='store_true') 559 | parser.add_argument('--verify', '-v', help='Verify partition table fields (deprecated, this behaviour is ' 560 | 'enabled by default and this flag does nothing.', action='store_true') 561 | parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true') 562 | parser.add_argument('--offset', '-o', help='Set offset partition table', default='0x8000') 563 | parser.add_argument('--secure', help='Require app partitions to be suitable for secure boot', nargs='?', const=SECURE_V1, choices=[SECURE_V1, SECURE_V2]) 564 | parser.add_argument('--extra-partition-subtypes', help='Extra partition subtype entries', nargs='*') 565 | parser.add_argument('input', help='Path to CSV or binary file to parse.', type=argparse.FileType('rb')) 566 | parser.add_argument('output', help='Path to output converted binary or CSV file. Will use stdout if omitted.', 567 | nargs='?', default='-') 568 | 569 | args = parser.parse_args() 570 | 571 | quiet = args.quiet 572 | md5sum = not args.disable_md5sum 573 | secure = args.secure 574 | offset_part_table = int(args.offset, 0) 575 | if args.extra_partition_subtypes: 576 | add_extra_subtypes(args.extra_partition_subtypes) 577 | 578 | table, input_is_binary = PartitionTable.from_file(args.input) 579 | 580 | if not args.no_verify: 581 | status('Verifying table...') 582 | table.verify() 583 | 584 | if args.flash_size: 585 | size_mb = int(args.flash_size.replace('MB', '')) 586 | table.verify_size_fits(size_mb * 1024 * 1024) 587 | 588 | # Make sure that the output directory is created 589 | output_dir = os.path.abspath(os.path.dirname(args.output)) 590 | 591 | if not os.path.exists(output_dir): 592 | try: 593 | os.makedirs(output_dir) 594 | except OSError as exc: 595 | if exc.errno != errno.EEXIST: 596 | raise 597 | 598 | if input_is_binary: 599 | output = table.to_csv() 600 | with sys.stdout if args.output == '-' else open(args.output, 'w') as f: 601 | f.write(output) 602 | else: 603 | output = table.to_binary() 604 | try: 605 | stdout_binary = sys.stdout.buffer # Python 3 606 | except AttributeError: 607 | stdout_binary = sys.stdout 608 | with stdout_binary if args.output == '-' else open(args.output, 'wb') as f: 609 | f.write(output) 610 | 611 | 612 | class InputError(RuntimeError): 613 | def __init__(self, e): 614 | super(InputError, self).__init__(e) 615 | 616 | 617 | class ValidationError(InputError): 618 | def __init__(self, partition, message): 619 | super(ValidationError, self).__init__( 620 | 'Partition %s invalid: %s' % (partition.name, message)) 621 | 622 | 623 | if __name__ == '__main__': 624 | try: 625 | main() 626 | except InputError as e: 627 | print(e, file=sys.stderr) 628 | sys.exit(2) 629 | -------------------------------------------------------------------------------- /support/python/littlefs_generate.py: -------------------------------------------------------------------------------- 1 | 2 | # Source: https://github.com/minasouliman/wokwi_esp32_micropython/blob/main/tools/filesystem_generate.py 3 | 4 | import os 5 | from littlefs import LittleFS 6 | import subprocess 7 | 8 | workspace_dir = './' 9 | 10 | #files = os.listdir(f"{workspace_dir}/src") 11 | 12 | 13 | output_image = f"{workspace_dir}/littlefs.img" 14 | 15 | lfs = LittleFS(block_size=4096, block_count=512, prog_size=256) 16 | 17 | #for filename in files: 18 | with open(f"{workspace_dir}/main.py", 'rb') as src_file: 19 | with lfs.open("main.py", 'wb') as lfs_file: 20 | lfs_file.write(src_file.read()) 21 | 22 | with open(output_image, 'wb') as fh: 23 | fh.write(lfs.context.buffer) 24 | 25 | 26 | #subprocess.run(f"esptool.py --chip esp32 merge_bin -o {workspace_dir}/out.bin --flash_mode dio --flash_size 4MB 0x1000 {workspace_dir}/ESP32_GENERIC-20231227-v1.22.0.bin 0x200000 {workspace_dir}/littlefs.img", shell=True) 27 | 28 | -------------------------------------------------------------------------------- /support/rust/rust-toolchain-esp32.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "esp" 3 | # channel = "nightly" -------------------------------------------------------------------------------- /support/rust/rust-toolchain-esp32c3.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | #channel = "esp" 3 | channel = "nightly" -------------------------------------------------------------------------------- /support/rust/rust-toolchain-esp32c6.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | #channel = "esp" 3 | channel = "nightly" -------------------------------------------------------------------------------- /support/rust/rust-toolchain-esp32h2.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | #channel = "esp" 3 | channel = "nightly" -------------------------------------------------------------------------------- /support/rust/rust-toolchain-esp32s2.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "esp" 3 | # channel = "nightly" -------------------------------------------------------------------------------- /support/rust/rust-toolchain-esp32s3.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "esp" 3 | # channel = "nightly" -------------------------------------------------------------------------------- /support/wokwi/diagram-esp32.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "author": "Juraj Michálek", 4 | "editor": "wokwi", 5 | "parts": [ { "type": "board-esp32-devkit-c-v4", "id": "esp", "top": 0, "left": 0, "attrs": {} } ], 6 | "connections": [ [ "esp:TX", "$serialMonitor:RX", "", [] ], [ "esp:RX", "$serialMonitor:TX", "", [] ] ], 7 | "dependencies": {} 8 | } -------------------------------------------------------------------------------- /support/wokwi/diagram-esp32c3.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "author": "Juraj Michálek", 4 | "editor": "wokwi", 5 | "parts": [ 6 | { 7 | "type": "board-esp32-c3-devkitm-1", 8 | "id": "esp", 9 | "top": 0, 10 | "left": 0, 11 | "attrs": { "builder": "esp-idf" } 12 | } 13 | ], 14 | "connections": [ [ "esp:TX", "$serialMonitor:RX", "", [] ], [ "esp:RX", "$serialMonitor:TX", "", [] ] ], 15 | "dependencies": {} 16 | } -------------------------------------------------------------------------------- /support/wokwi/diagram-esp32c6.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "author": "Juraj Michálek", 4 | "editor": "wokwi", 5 | "parts": [ 6 | { 7 | "type": "board-esp32-c6-devkitc-1", 8 | "id": "esp", 9 | "top": 0, 10 | "left": 0, 11 | "attrs": { } 12 | } 13 | ], 14 | "connections": [ [ "esp:TX", "$serialMonitor:RX", "", [] ], [ "esp:RX", "$serialMonitor:TX", "", [] ] ], 15 | "dependencies": {} 16 | } -------------------------------------------------------------------------------- /support/wokwi/diagram-esp32h2.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "author": "Juraj Michálek", 4 | "editor": "wokwi", 5 | "parts": [ 6 | { 7 | "type": "board-esp32-h2-devkitm-1", 8 | "id": "esp", 9 | "top": 0, 10 | "left": 0, 11 | "attrs": { "builder": "esp-idf" } 12 | } 13 | ], 14 | "connections": [ [ "esp:TX", "$serialMonitor:RX", "", [] ], [ "esp:RX", "$serialMonitor:TX", "", [] ] ], 15 | "dependencies": {} 16 | } -------------------------------------------------------------------------------- /support/wokwi/diagram-esp32s2.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "author": "Juraj Michálek", 4 | "editor": "wokwi", 5 | "parts": [ { "type": "board-esp32-s2-devkitm-1", "id": "esp", "top": 0, "left": 0, "attrs": {} } ], 6 | "connections": [ [ "esp:TX", "$serialMonitor:RX", "", [] ], [ "esp:RX", "$serialMonitor:TX", "", [] ] ], 7 | "dependencies": {} 8 | } 9 | -------------------------------------------------------------------------------- /support/wokwi/diagram-esp32s3.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "author": "Juraj Michálek", 4 | "editor": "wokwi", 5 | "parts": [ { "type": "board-esp32-s3-devkitc-1", "id": "esp", "top": 0, "left": 0, "attrs": { "flashSize":"8"} } ], 6 | "connections": [ [ "esp:TX", "$serialMonitor:RX", "", [] ], [ "esp:RX", "$serialMonitor:TX", "", [] ] ], 7 | "dependencies": {} 8 | } -------------------------------------------------------------------------------- /support/wokwi/wokwi-esp32.toml: -------------------------------------------------------------------------------- 1 | [wokwi] 2 | version = 1 3 | elf = "target/xtensa-esp32-none-elf/release/hello-world" 4 | firmware = "target/xtensa-esp32-none-elf/release/hello-world" 5 | -------------------------------------------------------------------------------- /support/wokwi/wokwi-esp32c3.toml: -------------------------------------------------------------------------------- 1 | [wokwi] 2 | version = 1 3 | elf = "target/xtensa-esp32c3-none-elf/release/hello-world" 4 | firmware = "target/xtensa-esp32c3-none-elf/release/hello-world" 5 | -------------------------------------------------------------------------------- /support/wokwi/wokwi-esp32c6.toml: -------------------------------------------------------------------------------- 1 | [wokwi] 2 | version = 1 3 | elf = "target/xtensa-esp32c6-none-elf/release/hello-world" 4 | firmware = "target/xtensa-esp32c6-none-elf/release/hello-world" 5 | -------------------------------------------------------------------------------- /support/wokwi/wokwi-esp32h2.toml: -------------------------------------------------------------------------------- 1 | [wokwi] 2 | version = 1 3 | elf = "target/xtensa-esp32h2-none-elf/release/hello-world" 4 | firmware = "target/xtensa-esp32h2-none-elf/release/hello-world" 5 | -------------------------------------------------------------------------------- /support/wokwi/wokwi-esp32s2.toml: -------------------------------------------------------------------------------- 1 | [wokwi] 2 | version = 1 3 | elf = "target/xtensa-esp32s2-none-elf/release/hello-world" 4 | firmware = "target/xtensa-esp32s2-none-elf/release/hello-world" 5 | -------------------------------------------------------------------------------- /support/wokwi/wokwi-esp32s3.toml: -------------------------------------------------------------------------------- 1 | [wokwi] 2 | version = 1 3 | elf = "target/xtensa-esp32s3-none-elf/release/hello-world" 4 | firmware = "target/xtensa-esp32s3-none-elf/release/hello-world" 5 | -------------------------------------------------------------------------------- /tests/python/circuitpython/test-circuitpython.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | TARGET="$1" 6 | BASE_DIR="$2" 7 | RELEASE_TAG="$3" 8 | 9 | # Define allowed targets 10 | declare -A ALLOWED_TARGETS=( 11 | ["esp32"]=1 12 | ["esp32s2"]=1 13 | ["esp32s3"]=1 14 | ["esp32c3"]=1 15 | ["esp32c6"]=1 16 | ["esp32h2"]=1 17 | ) 18 | 19 | TARGET="$1" 20 | 21 | # Check if the provided target is allowed 22 | if [[ -z "${ALLOWED_TARGETS[$TARGET]}" ]]; then 23 | echo "Error: Invalid target '${TARGET}'. Allowed targets are: ${!ALLOWED_TARGETS[@]}" 24 | exit 1 25 | fi 26 | 27 | 28 | # source path to wokwi-cli 29 | export PATH=$PATH:${HOME}/bin 30 | 31 | export SUPPORT_DIR="${BASE_DIR}/support" 32 | export PROJECT="${BASE_DIR}/examples/python/circuitpython/hello_world/" 33 | 34 | echo "Project path: ${PROJECT}" 35 | cd ${PROJECT} 36 | 37 | # Prepare report 38 | echo '[]' > results.json 39 | 40 | 41 | # Define URL prefixes and binary names for each target 42 | declare -A DEVKIT 43 | declare -A CIRCUITPYTHON_VERSION 44 | 45 | CIRCUITPYTHON_VERSION_DEFAULT="8.2.9" 46 | BASE_URL="https://downloads.circuitpython.org/bin" 47 | VENDOR="espressif" 48 | 49 | DEVKIT[esp32]="lyrat" 50 | DEVKIT[esp32s2]="devkitc_1_n4" 51 | DEVKIT[esp32s3]="devkitm_1_n8" 52 | DEVKIT[esp32c3]="devkitm_1_n4" 53 | DEVKIT[esp32c6]="devkitc_1_n8" 54 | DEVKIT[esp32h2]="devkitm_1_n4" 55 | 56 | CIRCUITPYTHON_VERSION[esp32]=${CIRCUITPYTHON_VERSION_DEFAULT} 57 | CIRCUITPYTHON_VERSION[esp32s2]=${CIRCUITPYTHON_VERSION_DEFAULT} 58 | CIRCUITPYTHON_VERSION[esp32s3]=${CIRCUITPYTHON_VERSION_DEFAULT} 59 | CIRCUITPYTHON_VERSION[esp32c3]=${CIRCUITPYTHON_VERSION_DEFAULT} 60 | CIRCUITPYTHON_VERSION[esp32c6]="9.0.0-beta.0" 61 | CIRCUITPYTHON_VERSION[esp32h2]="9.0.0-beta.0" 62 | 63 | SELECTED_DEVKIT=${DEVKIT[$TARGET]} 64 | SELECTED_VERSION=${CIRCUITPYTHON_VERSION[$TARGET]} 65 | 66 | DOWNLOAD_PREFIX="${BASE_URL}/${VENDOR}_${TARGET}_${SELECTED_DEVKIT}/en_US" 67 | RUNTIME_BIN="adafruit-circuitpython-${VENDOR}_${TARGET}_${SELECTED_DEVKIT}-en_US-${SELECTED_VERSION}.bin" 68 | 69 | echo "Runtime: ${RUNTIME_BIN}" 70 | if [ ! -e ${RUNTIME_BIN} ]; then 71 | echo "Downloading CircuitPython for ${TARGET} from ${DOWNLOAD_PREFIX}/${RUNTIME_BIN}" 72 | curl -L ${DOWNLOAD_PREFIX}/${RUNTIME_BIN} -o ${RUNTIME_BIN} 73 | fi 74 | 75 | echo "Building ${TARGET}" 76 | OUTPUT_PREFIX="python-circuitpython-hello-world-${TARGET}-${RELEASE_TAG}" 77 | OUTPUT_BIN="out.bin" 78 | OUTPUT_TXT="${OUTPUT_PREFIX}.txt" 79 | 80 | # Extract information from partition table for creating custom FS 81 | ls -l ${RUNTIME_BIN} 82 | echo dd if=${RUNTIME_BIN} of=partitions.bin bs=4096 count=1 skip=8 # 0x8000 83 | dd if=${RUNTIME_BIN} of=partitions.bin bs=4096 count=1 skip=8 # 0x8000 84 | 85 | echo "Partition table:" 86 | python3 ${SUPPORT_DIR}/python/gen_esp32part.py partitions.bin | tee partitions.txt 87 | 88 | USER_FS_OFFSET=$(grep user_fs partitions.txt | \ 89 | sed -e 's/user_fs,data,fat,//' \ 90 | -e 's/,.*//' | \ 91 | tr -d '\n') 92 | 93 | USER_FS_SIZE_KB=$(grep user_fs partitions.txt | \ 94 | sed -e 's/K,.*$//' \ 95 | -e 's/.*,//' | \ 96 | tr -d '\n') 97 | USER_FS_SIZE=$((USER_FS_SIZE_KB * 1024)) 98 | echo "User FS offset: ${USER_FS_OFFSET}; size: ${USER_FS_SIZE}" 99 | 100 | # Create an empty FAT filesystem image 101 | echo dd if=/dev/zero of=circuitpython_fs.img bs=${USER_FS_SIZE} count=1 102 | dd if=/dev/zero of=circuitpython_fs.img bs=${USER_FS_SIZE} count=1 103 | mkfs.fat -S 512 circuitpython_fs.img 104 | 105 | # Mount the image and copy the code.py file into it 106 | mkdir -p mountpoint 107 | sudo mount -o loop circuitpython_fs.img mountpoint 108 | sudo cp code.py mountpoint/ 109 | sudo umount mountpoint 110 | 111 | # cp ${RUNTIME_BIN} ${OUTPUT_BIN} 112 | 113 | echo esptool.py --chip ${TARGET} merge_bin -o ${OUTPUT_BIN} --flash_mode dio --flash_size 8MB 0x00 ${RUNTIME_BIN} ${USER_FS_OFFSET} circuitpython_fs.img 114 | file ${OUTPUT_BIN} 115 | esptool.py --chip ${TARGET} merge_bin -o ${OUTPUT_BIN} --flash_mode dio --flash_size 8MB 0x00 ${RUNTIME_BIN} ${USER_FS_OFFSET} circuitpython_fs.img 116 | 117 | # Prepare Wokwi test 118 | cp ${SUPPORT_DIR}/wokwi/diagram-${TARGET}.json diagram.json 119 | 120 | echo '[wokwi]' > wokwi.toml 121 | echo 'version = 1' >> wokwi.toml 122 | echo "elf = \"${OUTPUT_BIN}\"" >> wokwi.toml 123 | echo "firmware = \"${OUTPUT_BIN}\"" >> wokwi.toml 124 | 125 | # Run Wokwi test 126 | wokwi-cli --timeout 15000 \ 127 | --timeout-exit-code 0 \ 128 | --serial-log-file ${PROJECT}/${OUTPUT_TXT} \ 129 | --elf ${OUTPUT_BIN} \ 130 | "." 131 | 132 | 133 | # Extract heap size (you might need to adjust the parsing command) 134 | HEAP_SIZE=$(grep 'Minimum free heap size' ${OUTPUT_TXT} | tr -d '\n' | sed -e 's/Minimum free heap size: //' -e 's/ bytes//') 135 | 136 | # Add a new record to the JSON database 137 | jq --arg target "$TARGET" \ 138 | --arg language "Python" \ 139 | --arg flavor "CircuitPython" \ 140 | --arg example "hello-world" \ 141 | --arg property "heap" \ 142 | --arg value "$HEAP_SIZE" \ 143 | --arg unit "bytes" \ 144 | --arg note "" \ 145 | --arg version "8.2.9" \ 146 | --arg timestamp "$(date -Iseconds)" \ 147 | '. += [{ 148 | target: $target, 149 | language: $language, 150 | flavor: $flavor, 151 | example: $example, 152 | property: $property, 153 | value: $value, 154 | unit: $unit, 155 | note: $note, 156 | version: $version, 157 | timestamp: $timestamp 158 | }]' results.json > temp.json && mv temp.json results.json 159 | 160 | cat result.json --------------------------------------------------------------------------------