diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 1ff0be8def..23d41a61c7 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -31,7 +31,7 @@ jobs: run: | HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true - name: Extract TinyGo version @@ -40,13 +40,13 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Restore LLVM source cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: cache-llvm-source with: - key: llvm-source-20-${{ matrix.os }}-v1 + key: llvm-source-20-${{ matrix.os }}-v2 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -57,7 +57,7 @@ jobs: if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} @@ -68,10 +68,10 @@ jobs: llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: cache-llvm-build with: - key: llvm-build-20-${{ matrix.os }}-v2 + key: llvm-build-20-${{ matrix.os }}-v3 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -85,7 +85,7 @@ jobs: make llvm-build find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} @@ -107,7 +107,7 @@ jobs: # - have a double-zipped artifact when downloaded from the UI # - have a very slow artifact upload # We're doing the former here, to keep artifact uploads fast. - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: darwin-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }} path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz @@ -131,11 +131,11 @@ jobs: run: | brew install llvm@${{ matrix.version }} - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Build TinyGo (LLVM ${{ matrix.version }}) run: go install -tags=llvm${{ matrix.version }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 18fd5127d4..f1e8be695b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -31,14 +31,14 @@ jobs: sudo rm -rf /usr/local/share/boost df -h - name: Check out the repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | tinygo/tinygo-dev @@ -47,18 +47,18 @@ jobs: type=sha,format=long type=raw,value=latest - name: Log in to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Log in to Github Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . push: true diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d5a6619fbc..37a3fbc0c8 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -23,7 +23,7 @@ jobs: version: ${{ steps.version.outputs.version }} steps: - name: Install apk dependencies - # tar: needed for actions/cache@v4 + # tar: needed for actions/cache@v5 # git+openssh: needed for checkout (I think?) # ruby: needed to install fpm run: apk add tar git openssh make g++ ruby-dev mold @@ -31,24 +31,24 @@ jobs: # We're not on a multi-user machine, so this is safe. run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true - name: Extract TinyGo version id: version run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" - name: Cache Go - uses: actions/cache@v4 + uses: actions/cache@v5 with: key: go-cache-linux-alpine-v1-${{ hashFiles('go.mod') }} path: | ~/.cache/go-build ~/go/pkg/mod - name: Restore LLVM source cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: cache-llvm-source with: - key: llvm-source-20-linux-alpine-v1 + key: llvm-source-20-linux-alpine-v2 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -59,7 +59,7 @@ jobs: if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} @@ -70,10 +70,10 @@ jobs: llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: cache-llvm-build with: - key: llvm-build-20-linux-alpine-v1 + key: llvm-build-20-linux-alpine-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -88,13 +88,13 @@ jobs: # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache Binaryen - uses: actions/cache@v4 + uses: actions/cache@v5 id: cache-binaryen with: key: binaryen-linux-alpine-v1 @@ -119,7 +119,7 @@ jobs: cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb - name: Publish release artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: linux-amd64-double-zipped-${{ steps.version.outputs.version }} path: | @@ -131,13 +131,13 @@ jobs: needs: build-linux steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Install wasmtime uses: bytecodealliance/actions/wasmtime/setup@v1 @@ -164,7 +164,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true - name: Install apt dependencies @@ -181,10 +181,10 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: '18' - name: Install wasmtime @@ -194,10 +194,10 @@ jobs: - name: Setup `wasm-tools` uses: bytecodealliance/actions/wasm-tools/setup@v1 - name: Restore LLVM source cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: cache-llvm-source with: - key: llvm-source-20-linux-asserts-v1 + key: llvm-source-20-linux-asserts-v2 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -208,7 +208,7 @@ jobs: if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} @@ -219,10 +219,10 @@ jobs: llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: cache-llvm-build with: - key: llvm-build-20-linux-asserts-v1 + key: llvm-build-20-linux-asserts-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -235,13 +235,13 @@ jobs: # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache Binaryen - uses: actions/cache@v4 + uses: actions/cache@v5 id: cache-binaryen with: key: binaryen-linux-asserts-v1 @@ -284,7 +284,7 @@ jobs: needs: build-linux steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Get TinyGo version id: version run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" @@ -298,13 +298,13 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Restore LLVM source cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: cache-llvm-source with: - key: llvm-source-20-linux-v1 + key: llvm-source-20-linux-v2 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -315,7 +315,7 @@ jobs: if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} @@ -326,10 +326,10 @@ jobs: llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: cache-llvm-build with: - key: llvm-build-20-linux-${{ matrix.goarch }}-v1 + key: llvm-build-20-linux-${{ matrix.goarch }}-v2 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -344,13 +344,13 @@ jobs: # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache Binaryen - uses: actions/cache@v4 + uses: actions/cache@v5 id: cache-binaryen with: key: binaryen-linux-${{ matrix.goarch }}-v4 @@ -387,7 +387,7 @@ jobs: cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb - name: Publish release artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: linux-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }} path: | diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index ec45d587e4..fac80561ed 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -25,14 +25,14 @@ jobs: contents: read steps: - name: Check out the repo - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: recursive - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | tinygo/llvm-20 @@ -46,13 +46,13 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Log in to Github Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: target: tinygo-llvm-build context: . diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index e65ae3193a..ca8f16500e 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -21,28 +21,28 @@ jobs: # See: https://github.com/tinygo-org/tinygo/pull/4516#issuecomment-2416363668 run: sudo apt-get remove llvm-18 - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Pull musl, bdwgc run: | git submodule update --init lib/musl lib/bdwgc - name: Restore LLVM source cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: cache-llvm-source with: - key: llvm-source-20-linux-nix-v1 + key: llvm-source-20-linux-nix-v2 path: | llvm-project/compiler-rt - name: Download LLVM source if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} path: | llvm-project/compiler-rt - - uses: cachix/install-nix-action@v22 + - uses: cachix/install-nix-action@v31 - name: Test run: | nix develop --ignore-environment --keep HOME --command bash -c "go install && ~/go/bin/tinygo version && ~/go/bin/tinygo build -o test ./testdata/cgo" diff --git a/.github/workflows/sizediff.yml b/.github/workflows/sizediff.yml index 08130a78c9..155208dfc9 100644 --- a/.github/workflows/sizediff.yml +++ b/.github/workflows/sizediff.yml @@ -20,24 +20,24 @@ jobs: run: | echo "$HOME/go/bin" >> $GITHUB_PATH - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 # fetch all history (no sparse checkout) submodules: true - name: Install apt dependencies run: ./.github/workflows/sizediff-install-pkgs.sh - name: Restore LLVM source cache - uses: actions/cache@v4 + uses: actions/cache@v5 id: cache-llvm-source with: - key: llvm-source-20-sizediff-v1 + key: llvm-source-20-sizediff-v2 path: | llvm-project/compiler-rt - name: Download LLVM source if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Cache Go - uses: actions/cache@v4 + uses: actions/cache@v5 with: key: go-cache-linux-sizediff-v2-${{ hashFiles('go.mod') }} path: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 60b0d8cb5d..06eccd9fa2 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -24,14 +24,13 @@ jobs: maximum-size: 24GB disk-root: "C:" - uses: MinoruSekine/setup-scoop@v4 - with: - scoop_update: 'false' - name: Install Dependencies shell: bash run: | + scoop config use_external_7zip true scoop install ninja binaryen - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: submodules: true - name: Extract TinyGo version @@ -41,13 +40,13 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Restore cached LLVM source - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: cache-llvm-source with: - key: llvm-source-20-windows-v1 + key: llvm-source-20-windows-v2 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -58,7 +57,7 @@ jobs: if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save cached LLVM source - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} @@ -69,10 +68,10 @@ jobs: llvm-project/lld/include llvm-project/llvm/include - name: Restore cached LLVM build - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: cache-llvm-build with: - key: llvm-build-20-windows-v2 + key: llvm-build-20-windows-v3 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -86,13 +85,13 @@ jobs: # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save cached LLVM build - uses: actions/cache/save@v4 + uses: actions/cache/save@v5 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache Go cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: key: go-cache-windows-v1-${{ hashFiles('go.mod') }} path: | @@ -100,6 +99,7 @@ jobs: C:/Users/runneradmin/go/pkg/mod - name: Install wasmtime run: | + scoop config use_external_7zip true scoop install wasmtime@29.0.1 - name: make gen-device run: make -j3 gen-device @@ -120,7 +120,7 @@ jobs: # - have a dobule-zipped artifact when downloaded from the UI # - have a very slow artifact upload # We're doing the former here, to keep artifact uploads fast. - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: windows-amd64-double-zipped-${{ steps.version.outputs.version }} path: build/release/tinygo${{ steps.version.outputs.version }}.windows-amd64.zip @@ -136,18 +136,17 @@ jobs: maximum-size: 24GB disk-root: "C:" - uses: MinoruSekine/setup-scoop@v4 - with: - scoop_update: 'false' - name: Install Dependencies shell: bash run: | + scoop config use_external_7zip true scoop install binaryen - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 @@ -173,11 +172,11 @@ jobs: maximum-size: 24GB disk-root: "C:" - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 @@ -202,18 +201,17 @@ jobs: maximum-size: 24GB disk-root: "C:" - uses: MinoruSekine/setup-scoop@v4 - with: - scoop_update: 'false' - name: Install Dependencies shell: bash run: | - scoop install binaryen && scoop install wasmtime@29.0.1 + scoop config use_external_7zip true + scoop install binaryen wasmtime@29.0.1 - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 diff --git a/GNUmakefile b/GNUmakefile index 99a654ca7f..f3961a65d7 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -263,7 +263,7 @@ gen-device-renesas: build/gen-device-svd GO111MODULE=off $(GO) fmt ./src/device/renesas $(LLVM_PROJECTDIR)/llvm: - git clone -b tinygo_20.x --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR) + git clone -b tinygo_20.x.1 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR) llvm-source: $(LLVM_PROJECTDIR)/llvm ## Get LLVM sources # Configure LLVM. @@ -814,6 +814,10 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=waveshare-rp2040-tiny examples/echo @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=vicharak_shrike-lite examples/echo + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=xiao-rp2350 examples/blinky1 + @$(MD5SUM) test.hex # test pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm @$(MD5SUM) test.hex @@ -873,6 +877,12 @@ ifneq ($(STM32), 0) @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=stm32l0x1 examples/serial @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/blinky1 + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/serial + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/blinkm + @$(MD5SUM) test.hex endif $(TINYGO) build -size short -o test.hex -target=atmega328pb examples/blinkm @$(MD5SUM) test.hex @@ -896,6 +906,10 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=digispark examples/pwm + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=digispark examples/mcp3008 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1 @$(MD5SUM) test.hex ifneq ($(XTENSA), 0) @@ -903,6 +917,8 @@ ifneq ($(XTENSA), 0) @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1 @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinkm + @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/machinetest @@ -915,9 +931,48 @@ ifneq ($(XTENSA), 0) @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest @$(MD5SUM) test.bin + # xiao-esp32s3 $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1 @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinkm + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/mcp3008 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/pwm + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/adc + @$(MD5SUM) test.bin + # esp32s3-supermini + $(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/blinky1 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/blinkm + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/mcp3008 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/adc + @$(MD5SUM) test.bin + # esp32s3-wroom1 + $(TINYGO) build -size short -o test.bin -target=esp32s3-wroom1 examples/blinkm + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32s3-wroom1 examples/mcp3008 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32s3-wroom1 examples/pwm + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/adc + @$(MD5SUM) test.bin endif + # esp32c3-supermini + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinkm + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/mcp3008 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/pwm + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/adc + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=qtpy-esp32c3 examples/machinetest @@ -930,6 +985,7 @@ endif @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=esp32c3-12f examples/blinky1 @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=makerfabs-esp32c3spi35 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 diff --git a/README.md b/README.md index 518dcdad18..86955a2d9d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ TinyGo is a Go compiler intended for use in small places such as microcontroller It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language. +> [!IMPORTANT] +> You can help TinyGo with a financial contribution using OpenCollective. Please see https://opencollective.com/tinygo for more information. Thank you! + ## Embedded Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED: @@ -63,7 +66,7 @@ tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.go You can also use the same syntax as Go 1.24+: ```shell -GOARCH=wasip1 GOOS=wasm tinygo build -buildmode=c-shared -o add.wasm add.go +GOOS=wasip1 GOARCH=wasm tinygo build -buildmode=c-shared -o add.wasm add.go ``` ## Installation diff --git a/builder/build.go b/builder/build.go index a598f01965..44f41eb233 100644 --- a/builder/build.go +++ b/builder/build.go @@ -19,6 +19,7 @@ import ( "os/exec" "path/filepath" "runtime" + "slices" "sort" "strconv" "strings" @@ -281,9 +282,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe allFiles[file.Name] = append(allFiles[file.Name], file) } } - for name, files := range allFiles { - name := name - files := files + // Sort embedded files by name to maintain output determinism. + embedNames := make([]string, 0, len(allFiles)) + for _, files := range allFiles { + embedNames = append(embedNames, files[0].Name) + } + slices.Sort(embedNames) + for _, name := range embedNames { job := &compileJob{ description: "make object file for " + name, run: func(job *compileJob) error { @@ -298,7 +303,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe sum := sha256.Sum256(data) hexSum := hex.EncodeToString(sum[:16]) - for _, file := range files { + for _, file := range allFiles[name] { file.Size = uint64(len(data)) file.Hash = hexSum if file.NeedsData { diff --git a/builder/sizes.go b/builder/sizes.go index 57fb36df67..0b407f96f7 100644 --- a/builder/sizes.go +++ b/builder/sizes.go @@ -954,7 +954,11 @@ func findPackagePath(path string, packagePathMap map[string]string) (packagePath libPath := strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")+string(os.PathSeparator)) parts := strings.SplitN(libPath, string(os.PathSeparator), 2) packagePath = "C " + parts[0] - filename = parts[1] + if len(parts) > 1 { + filename = parts[1] + } else { + filename = parts[0] + } } else if prefix := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project", "compiler-rt"); strings.HasPrefix(path, prefix) { packagePath = "C compiler-rt" filename = strings.TrimPrefix(path, prefix+string(os.PathSeparator)) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 0f52f3c6be..fd985a8977 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 3668, 280, 0, 2244}, + {"hifive1b", "examples/echo", 3680, 280, 0, 2252}, {"microbit", "examples/serial", 2694, 342, 8, 2248}, - {"wioterminal", "examples/pininterrupt", 6837, 1491, 120, 6888}, + {"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/compileopts/target.go b/compileopts/target.go index 16d21f46c5..29b3733bd9 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -24,6 +24,7 @@ import ( // https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json type TargetSpec struct { Inherits []string `json:"inherits,omitempty"` + InheritableOnly bool `json:"inheritable-only"` // this target is only meant to be inherited from, not used directly Triple string `json:"llvm-target,omitempty"` CPU string `json:"cpu,omitempty"` ABI string `json:"target-abi,omitempty"` // roughly equivalent to -mabi= flag @@ -148,6 +149,11 @@ func (spec *TargetSpec) loadFromGivenStr(str string) error { // resolveInherits loads inherited targets, recursively. func (spec *TargetSpec) resolveInherits() error { + // Save InheritableOnly before resolving, since it must not propagate + // from parent to child (a board target should not become inheritable-only + // just because its parent processor target is). + inheritableOnly := spec.InheritableOnly + // First create a new spec with all the inherited properties. newSpec := &TargetSpec{} for _, name := range spec.Inherits { @@ -173,6 +179,9 @@ func (spec *TargetSpec) resolveInherits() error { } *spec = *newSpec + // Restore InheritableOnly from the original spec, not from parents. + spec.InheritableOnly = inheritableOnly + return nil } @@ -239,10 +248,17 @@ func GetTargetSpecs() (map[string]*TargetSpec, error) { continue } path := filepath.Join(dir, entry.Name()) + spec, err := LoadTarget(&Options{Target: path}) if err != nil { return nil, fmt.Errorf("could not list target: %w", err) } + + if spec.InheritableOnly { + // Skip targets that are only meant to be inherited from, not used directly. + continue + } + if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == "" { // This doesn't look like a regular target file, but rather like // a parent target (such as targets/cortex-m.json). diff --git a/compileopts/target_test.go b/compileopts/target_test.go index 579c54f67b..d8a17a5e34 100644 --- a/compileopts/target_test.go +++ b/compileopts/target_test.go @@ -23,6 +23,37 @@ func TestLoadTarget(t *testing.T) { } } +func TestGetTargetSpecs_InheritableOnlyTargetsExcluded(t *testing.T) { + specs, err := GetTargetSpecs() + if err != nil { + t.Fatal("GetTargetSpecs failed:", err) + } + + // Inheritable-only processor-level targets should not appear in the listing. + inheritableOnlyTargets := []string{"esp32", "esp32c3", "esp32s3", "esp8266", "rp2040", "rp2350", "rp2350b"} + for _, name := range inheritableOnlyTargets { + if _, ok := specs[name]; ok { + t.Errorf("inheritable-only target %q should not appear in GetTargetSpecs", name) + } + } + + // Board targets that inherit from inheritable-only targets should still appear. + boardTargets := []string{"esp32-coreboard-v2", "pico"} + for _, name := range boardTargets { + if _, ok := specs[name]; !ok { + t.Errorf("board target %q should appear in GetTargetSpecs", name) + } + } +} + +func TestLoadTarget_InheritableOnlyTargetStillLoadable(t *testing.T) { + // Inheritable-only targets should still be loadable directly (for building). + _, err := LoadTarget(&Options{Target: "esp32"}) + if err != nil { + t.Errorf("LoadTarget should still load inheritable-only target esp32: %v", err) + } +} + func TestOverrideProperties(t *testing.T) { baseAutoStackSize := true base := &TargetSpec{ diff --git a/compiler/gc.go b/compiler/gc.go index fc0e6e687f..5ca79b91ba 100644 --- a/compiler/gc.go +++ b/compiler/gc.go @@ -99,6 +99,9 @@ func typeHasPointers(t llvm.Type) bool { } return false case llvm.ArrayTypeKind: + if t.ArrayLength() == 0 { + return false + } if typeHasPointers(t.ElementType()) { return true } diff --git a/compiler/llvm.go b/compiler/llvm.go index de387b39c0..7ce6c7d615 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -1,10 +1,10 @@ package compiler import ( + "encoding/binary" "fmt" "go/token" "go/types" - "math/big" "strings" "github.com/tinygo-org/tinygo/compileopts" @@ -231,6 +231,12 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l // // For details on what's in this value, see src/runtime/gc_precise.go. func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value { + if !typeHasPointers(t) { + // There are no pointers in this type, so we can simplify the layout. + layout := (uint64(1) << 1) | 1 + return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) + } + // Use the element type for arrays. This works even for nested arrays. for { kind := t.TypeKind() @@ -248,54 +254,29 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va break } - // Do a few checks to see whether we need to generate any object layout - // information at all. + // Create the pointer bitmap. objectSizeBytes := c.targetData.TypeAllocSize(t) - pointerSize := c.targetData.TypeAllocSize(c.dataPtrType) - pointerAlignment := c.targetData.PrefTypeAlignment(c.dataPtrType) - if objectSizeBytes < pointerSize { - // Too small to contain a pointer. - layout := (uint64(1) << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) - } - bitmap := c.getPointerBitmap(t, pos) - if bitmap.BitLen() == 0 { - // There are no pointers in this type, so we can simplify the layout. - // TODO: this can be done in many other cases, e.g. when allocating an - // array (like [4][]byte, which repeats a slice 4 times). - layout := (uint64(1) << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) - } - if objectSizeBytes%uint64(pointerAlignment) != 0 { - // This shouldn't happen except for packed structs, which aren't - // currently used. - c.addError(pos, "internal error: unexpected object size for object with pointer field") - return llvm.ConstNull(c.dataPtrType) - } - objectSizeWords := objectSizeBytes / uint64(pointerAlignment) + pointerAlignment := uint64(c.targetData.PrefTypeAlignment(c.dataPtrType)) + bitmapLen := objectSizeBytes / pointerAlignment + bitmapBytes := (bitmapLen + 7) / 8 + bitmap := make([]byte, bitmapBytes, max(bitmapBytes, 8)) + c.buildPointerBitmap(bitmap, pointerAlignment, pos, t, 0) + // Try to encode the layout inline. + pointerSize := c.targetData.TypeAllocSize(c.dataPtrType) pointerBits := pointerSize * 8 - var sizeFieldBits uint64 - switch pointerBits { - case 16: - sizeFieldBits = 4 - case 32: - sizeFieldBits = 5 - case 64: - sizeFieldBits = 6 - default: - panic("unknown pointer size") - } - layoutFieldBits := pointerBits - 1 - sizeFieldBits - - // Try to emit the value as an inline integer. This is possible in most - // cases. - if objectSizeWords < layoutFieldBits { - // If it can be stored directly in the pointer value, do so. - // The runtime knows that if the least significant bit of the pointer is - // set, the pointer contains the value itself. - layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) + if bitmapLen < pointerBits { + rawMask := binary.LittleEndian.Uint64(bitmap[0:8]) + layout := rawMask*pointerBits + bitmapLen + layout <<= 1 + layout |= 1 + + // Check if the layout fits. + layout &= 1<>1)/pointerBits == rawMask { + // No set bits were shifted off. + return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) + } } // Unfortunately, the object layout is too big to fit in a pointer-sized @@ -303,25 +284,24 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va // Try first whether the global already exists. All objects with a // particular name have the same type, so this is possible. - globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap) + globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", bitmapLen, (bitmapLen+15)/16, bitmap) global := c.mod.NamedGlobal(globalName) if !global.IsNil() { return global } // Create the global initializer. - bitmapBytes := make([]byte, int(objectSizeWords+7)/8) - bitmap.FillBytes(bitmapBytes) - reverseBytes(bitmapBytes) // big-endian to little-endian - var bitmapByteValues []llvm.Value - for _, b := range bitmapBytes { - bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)) + bitmapByteValues := make([]llvm.Value, bitmapBytes) + i8 := c.ctx.Int8Type() + for i, b := range bitmap { + bitmapByteValues[i] = llvm.ConstInt(i8, uint64(b), false) } initializer := c.ctx.ConstStruct([]llvm.Value{ - llvm.ConstInt(c.uintptrType, objectSizeWords, false), - llvm.ConstArray(c.ctx.Int8Type(), bitmapByteValues), + llvm.ConstInt(c.uintptrType, bitmapLen, false), + llvm.ConstArray(i8, bitmapByteValues), }, false) + // Create the actual global. global = llvm.AddGlobal(c.mod, initializer.Type(), globalName) global.SetInitializer(initializer) global.SetUnnamedAddr(true) @@ -329,6 +309,7 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va global.SetLinkage(llvm.LinkOnceODRLinkage) if c.targetData.PrefTypeAlignment(c.uintptrType) < 2 { // AVR doesn't have alignment by default. + // The lowest bit must be unset to distinguish this from an inline layout. global.SetAlignment(2) } if c.Debug && pos != token.NoPos { @@ -360,52 +341,71 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va return global } -// getPointerBitmap scans the given LLVM type for pointers and sets bits in a -// bigint at the word offset that contains a pointer. This scan is recursive. -func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int { - alignment := c.targetData.PrefTypeAlignment(c.dataPtrType) - switch typ.TypeKind() { +// buildPointerBitmap scans the given LLVM type for pointers and sets bits in a +// bitmap at the word offset that contains a pointer. This scan is recursive. +func (c *compilerContext) buildPointerBitmap( + dst []byte, + ptrAlign uint64, + pos token.Pos, + t llvm.Type, + offset uint64, +) { + switch t.TypeKind() { case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind: - return big.NewInt(0) + // These types do not contain pointers. + case llvm.PointerTypeKind: - return big.NewInt(1) + // Set the corresponding position in the bitmap. + dst[offset/8] |= 1 << (offset % 8) + case llvm.StructTypeKind: - ptrs := big.NewInt(0) - for i, subtyp := range typ.StructElementTypes() { - subptrs := c.getPointerBitmap(subtyp, pos) - if subptrs.BitLen() == 0 { - continue - } - offset := c.targetData.ElementOffset(typ, i) - if offset%uint64(alignment) != 0 { - // This error will let the compilation fail, but by continuing - // the error can still easily be shown. - c.addError(pos, "internal error: allocated struct contains unaligned pointer") + // Recurse over struct elements. + for i, et := range t.StructElementTypes() { + eo := c.targetData.ElementOffset(t, i) + if eo%uint64(ptrAlign) != 0 { + if typeHasPointers(et) { + // This error will let the compilation fail, but by continuing + // the error can still easily be shown. + c.addError(pos, "internal error: allocated struct contains unaligned pointer") + } continue } - subptrs.Lsh(subptrs, uint(offset)/uint(alignment)) - ptrs.Or(ptrs, subptrs) + c.buildPointerBitmap( + dst, + ptrAlign, + pos, + et, + offset+(eo/ptrAlign), + ) } - return ptrs + case llvm.ArrayTypeKind: - subtyp := typ.ElementType() - subptrs := c.getPointerBitmap(subtyp, pos) - ptrs := big.NewInt(0) - if subptrs.BitLen() == 0 { - return ptrs + // Recurse over array elements. + len := t.ArrayLength() + if len <= 0 { + return } - elementSize := c.targetData.TypeAllocSize(subtyp) - if elementSize%uint64(alignment) != 0 { - // This error will let the compilation fail (but continues so that - // other errors can be shown). - c.addError(pos, "internal error: allocated array contains unaligned pointer") - return ptrs + et := t.ElementType() + elementSize := c.targetData.TypeAllocSize(et) + if elementSize%ptrAlign != 0 { + if typeHasPointers(et) { + // This error will let the compilation fail (but continues so that + // other errors can be shown). + c.addError(pos, "internal error: allocated array contains unaligned pointer") + } + return } - for i := 0; i < typ.ArrayLength(); i++ { - ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment)) - ptrs.Or(ptrs, subptrs) + elementSize /= ptrAlign + for i := 0; i < len; i++ { + c.buildPointerBitmap( + dst, + ptrAlign, + pos, + et, + offset+uint64(i)*elementSize, + ) } - return ptrs + default: // Should not happen. panic("unknown LLVM type") diff --git a/compiler/testdata/gc.go b/compiler/testdata/gc.go index 20e5967028..9aa00a4c6f 100644 --- a/compiler/testdata/gc.go +++ b/compiler/testdata/gc.go @@ -24,6 +24,10 @@ var ( x *byte y [61]uintptr } + struct5 *struct { + x *byte + y [30]uintptr + } slice1 []byte slice2 []*int @@ -58,6 +62,10 @@ func newStruct() { x *byte y [61]uintptr }) + struct5 = new(struct { + x *byte + y [30]uintptr + }) } func newFuncValue() *func() { diff --git a/compiler/testdata/gc.ll b/compiler/testdata/gc.ll index d2be74cbcf..42a278b66e 100644 --- a/compiler/testdata/gc.ll +++ b/compiler/testdata/gc.ll @@ -16,11 +16,12 @@ target triple = "wasm32-unknown-wasi" @main.struct2 = hidden global ptr null, align 4 @main.struct3 = hidden global ptr null, align 4 @main.struct4 = hidden global ptr null, align 4 +@main.struct5 = hidden global ptr null, align 4 @main.slice1 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.slice2 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 -@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } -@"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" } +@"runtime/gc.layout:62-0100000000000020" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } +@"runtime/gc.layout:62-0100000000000000" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" } @"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 80, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4 @"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:complex128" }, align 4 @@ -80,12 +81,15 @@ entry: %new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.struct2, align 4 - %new2 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-2000000000000001", ptr undef) #3 + %new2 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0100000000000020", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.struct3, align 4 - %new3 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0001", ptr undef) #3 + %new3 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0100000000000000", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new3, ptr @main.struct4, align 4 + %new4 = call align 4 dereferenceable(124) ptr @runtime.alloc(i32 124, ptr nonnull inttoptr (i32 127 to ptr), ptr undef) #3 + call void @runtime.trackPointer(ptr nonnull %new4, ptr nonnull %stackalloc, ptr undef) #3 + store ptr %new4, ptr @main.struct5, align 4 ret void } diff --git a/flake.lock b/flake.lock index 877c18b461..9ff701357d 100644 --- a/flake.lock +++ b/flake.lock @@ -20,16 +20,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1747953325, - "narHash": "sha256-y2ZtlIlNTuVJUZCqzZAhIw5rrKP4DOSklev6c8PyCkQ=", + "lastModified": 1770136044, + "narHash": "sha256-tlFqNG/uzz2++aAmn4v8J0vAkV3z7XngeIIB3rM3650=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "55d1f923c480dadce40f5231feb472e81b0bab48", + "rev": "e576e3c9cf9bad747afcddd9e34f51d18c855b4e", "type": "github" }, "original": { "id": "nixpkgs", - "ref": "nixos-25.05", + "ref": "nixos-25.11", "type": "indirect" } }, diff --git a/flake.nix b/flake.nix index 4feea0a4b8..85ab404940 100644 --- a/flake.nix +++ b/flake.nix @@ -34,7 +34,7 @@ inputs = { # Use a recent stable release, but fix the version to make it reproducible. # This version should be updated from time to time. - nixpkgs.url = "nixpkgs/nixos-25.05"; + nixpkgs.url = "nixpkgs/nixos-25.11"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: diff --git a/go.mod b/go.mod index 7d4ea5f189..035527189e 100644 --- a/go.mod +++ b/go.mod @@ -13,17 +13,17 @@ require ( github.com/mattn/go-tty v0.0.4 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 github.com/tetratelabs/wazero v1.6.0 - go.bug.st/serial v1.6.0 + go.bug.st/serial v1.6.4 golang.org/x/net v0.35.0 golang.org/x/sys v0.30.0 golang.org/x/tools v0.30.0 gopkg.in/yaml.v2 v2.4.0 + tinygo.org/x/espflasher v0.4.0 tinygo.org/x/go-llvm v0.0.0-20250422114502-b8f170971e74 ) require ( github.com/creack/goselect v0.1.2 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/stretchr/testify v1.8.4 // indirect golang.org/x/text v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index 8c2330c3c5..a3b398191d 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= -go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A= -go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= +go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= +go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= @@ -58,5 +58,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +tinygo.org/x/espflasher v0.4.0 h1:0N+Ei+0qT/wGkGIoMaY2g+oI519VA5G4kUVUYHedTv8= +tinygo.org/x/espflasher v0.4.0/go.mod h1:a3hRV9EETPUkfPE6P14p4A6jKKth+oD5gtQz3nmij+8= tinygo.org/x/go-llvm v0.0.0-20250422114502-b8f170971e74 h1:ovavgTdIBWCH8YWlcfq9gkpoyT1+IxMKSn+Df27QwE8= tinygo.org/x/go-llvm v0.0.0-20250422114502-b8f170971e74/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= diff --git a/goenv/version.go b/goenv/version.go index 423f95906e..9ade0e0b79 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -10,7 +10,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.40.1" +const version = "0.41.0-dev" // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012). diff --git a/lib/stm32-svd b/lib/stm32-svd index e6db8e32d5..bfed180b63 160000 --- a/lib/stm32-svd +++ b/lib/stm32-svd @@ -1 +1 @@ -Subproject commit e6db8e32d5d42293a528434ec12e7f88479a8649 +Subproject commit bfed180b639863db414221bdb6aa77e198f464f5 diff --git a/main.go b/main.go index e2736fd17b..a798b9fb4c 100644 --- a/main.go +++ b/main.go @@ -32,12 +32,15 @@ import ( "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/loader" "golang.org/x/tools/go/buildutil" + "tinygo.org/x/espflasher/pkg/espflasher" "tinygo.org/x/go-llvm" "go.bug.st/serial" "go.bug.st/serial/enumerator" ) +var errInheritableOnly = errors.New("target is inheritable-only, which means it cannot be used directly for building or flashing") + // commandError is an error type to wrap os/exec.Command errors. This provides // some more information regarding what went wrong while running a command. type commandError struct { @@ -139,6 +142,10 @@ func printCommand(cmd string, args ...string) { // Build compiles and links the given package and writes it to outpath. func Build(pkgName, outpath string, config *compileopts.Config) error { + if config.Target != nil && config.Target.InheritableOnly { + return errInheritableOnly + } + // Create a temporary directory for intermediary files. tmpdir, err := os.MkdirTemp("", "tinygo") if err != nil { @@ -356,6 +363,10 @@ func Flash(pkgName, port, outpath string, options *compileopts.Options) error { return err } + if config.Target != nil && config.Target.InheritableOnly { + return errInheritableOnly + } + // determine the type of file to compile var fileExt string @@ -385,6 +396,8 @@ func Flash(pkgName, port, outpath string, options *compileopts.Options) error { fileExt = ".hex" case "bmp": fileExt = ".elf" + case "esp32flash", "esp32jtag": + fileExt = ".bin" case "native": return errors.New("unknown flash method \"native\" - did you miss a -target flag?") default: @@ -519,6 +532,24 @@ func Flash(pkgName, port, outpath string, options *compileopts.Options) error { if err != nil { return &commandError{"failed to flash", result.Binary, err} } + case "esp32flash": + port, err := getDefaultPort(port, config.Target.SerialPort) + if err != nil { + return &commandError{"failed to find port", port, err} + } + + if err := flashBinUsingEsp32(port, classicReset, result.Binary, config.Options); err != nil { + return &commandError{"failed to flash", result.Binary, err} + } + case "esp32jtag": + port, err := getDefaultPort(port, config.Target.SerialPort) + if err != nil { + return &commandError{"failed to find port", port, err} + } + + if err := flashBinUsingEsp32(port, jtagReset, result.Binary, config.Options); err != nil { + return &commandError{"failed to flash", result.Binary, err} + } default: return fmt.Errorf("unknown flash method: %s", flashMethod) } @@ -765,6 +796,10 @@ func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error { return err } + if config.Target != nil && config.Target.InheritableOnly { + return errInheritableOnly + } + _, err = buildAndRun(pkgName, config, os.Stdout, cmdArgs, nil, 0, func(cmd *exec.Cmd, result builder.BuildResult) error { return cmd.Run() }) @@ -1019,6 +1054,57 @@ func flashHexUsingMSD(volumes []string, tmppath string, options *compileopts.Opt return errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]") } +const ( + classicReset = "classic" + jtagReset = "jtag" +) + +func flashBinUsingEsp32(port, resetMode, tmppath string, options *compileopts.Options) error { + var opts *espflasher.FlasherOptions + // On Windows, we have to explicitly specify the reset mode to use USB JTAG. + if runtime.GOOS == "windows" && resetMode == jtagReset { + opts = espflasher.DefaultOptions() + opts.ResetMode = espflasher.ResetUSBJTAG + } + + flasher, err := espflasher.New(port, opts) + if err != nil { + return err + } + defer flasher.Close() + + chipName := flasher.ChipName() + fmt.Printf("Connected to %s\n", chipName) + + offset := uint32(0x0) + if chipName == "ESP32" { + offset = 0x1000 + } + + // Read the firmware binary + data, err := os.ReadFile(tmppath) + if err != nil { + return err + } + + // Flash with progress reporting + err = flasher.FlashImage(data, offset, func(current, total int) { + fmt.Printf("\rFlashing: %d/%d bytes (%.0f%%)", current, total, + float64(current)/float64(total)*100) + }) + if err != nil { + return err + } + fmt.Println() + + time.Sleep(time.Second) + + // Reset the device to run the new firmware + flasher.Reset() + + return nil +} + type mountPoint struct { name string path string diff --git a/src/crypto/rand/rand_baremetal.go b/src/crypto/rand/rand_baremetal.go index 5711f23eb0..b39eee17e7 100644 --- a/src/crypto/rand/rand_baremetal.go +++ b/src/crypto/rand/rand_baremetal.go @@ -1,4 +1,4 @@ -//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) +//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt) // If you update the above build constraint, you'll probably also need to update // src/runtime/rand_hwrng.go. diff --git a/src/device/riscv/handleinterrupt.S b/src/device/riscv/handleinterrupt.S index c206c01595..79031423fb 100644 --- a/src/device/riscv/handleinterrupt.S +++ b/src/device/riscv/handleinterrupt.S @@ -75,6 +75,10 @@ handleInterruptASM: SFREG f30,(30 + 16)*REGSIZE(sp) SFREG f31,(31 + 16)*REGSIZE(sp) #endif + // Save ra to a global so handleException can print the caller of the NULL + // function pointer. + la t0, tinygo_saved_ra + SREG ra, 0(t0) call handleInterrupt #ifdef __riscv_flen LFREG f0, (31 + 16)*REGSIZE(sp) @@ -128,3 +132,10 @@ handleInterruptASM: LREG ra, 0*REGSIZE(sp) addi sp, sp, NREG*REGSIZE mret + +.section .bss.tinygo_saved_ra +.global tinygo_saved_ra +.type tinygo_saved_ra,@object +.align 2 +tinygo_saved_ra: + .space REGSIZE diff --git a/src/examples/adc/adc.go b/src/examples/adc/adc.go index de8a7f8859..3ec213f41a 100644 --- a/src/examples/adc/adc.go +++ b/src/examples/adc/adc.go @@ -5,25 +5,15 @@ import ( "time" ) -// This example assumes that an analog sensor such as a rotary dial is connected to pin ADC0. -// When the dial is turned past the midway point, the built-in LED will light up. - func main() { machine.InitADC() - led := machine.LED - led.Configure(machine.PinConfig{Mode: machine.PinOutput}) - sensor := machine.ADC{machine.ADC2} sensor.Configure(machine.ADCConfig{}) for { val := sensor.Get() - if val < 0x8000 { - led.Low() - } else { - led.High() - } - time.Sleep(time.Millisecond * 100) + println(val) + time.Sleep(time.Millisecond * 500) } } diff --git a/src/examples/pwm/arduino-uno-q.go b/src/examples/pwm/arduino-uno-q.go new file mode 100644 index 0000000000..e0c2c53a52 --- /dev/null +++ b/src/examples/pwm/arduino-uno-q.go @@ -0,0 +1,11 @@ +//go:build arduino_uno_q + +package main + +import "machine" + +var ( + pwm = &machine.TIM3 + pinA = machine.D3 // PB0 = TIM3_CH3 + pinB = machine.D6 // PB1 = TIM3_CH4 +) diff --git a/src/examples/pwm/digispark.go b/src/examples/pwm/digispark.go new file mode 100644 index 0000000000..848d518546 --- /dev/null +++ b/src/examples/pwm/digispark.go @@ -0,0 +1,12 @@ +//go:build digispark + +package main + +import "machine" + +var ( + // Use Timer1 for PWM (recommended for ATtiny85) + pwm = machine.Timer1 + pinA = machine.P1 // PB1, Timer1 channel A (LED pin) + pinB = machine.P4 // PB4, Timer1 channel B +) diff --git a/src/examples/pwm/esp32c3-supermini.go b/src/examples/pwm/esp32c3-supermini.go new file mode 100644 index 0000000000..edca88b97e --- /dev/null +++ b/src/examples/pwm/esp32c3-supermini.go @@ -0,0 +1,11 @@ +//go:build esp32c3_supermini + +package main + +import "machine" + +var ( + pwm = machine.PWM0 + pinA = machine.GPIO5 + pinB = machine.GPIO6 +) diff --git a/src/examples/pwm/esp32s3-wroom1.go b/src/examples/pwm/esp32s3-wroom1.go new file mode 100644 index 0000000000..b3e5dcb584 --- /dev/null +++ b/src/examples/pwm/esp32s3-wroom1.go @@ -0,0 +1,11 @@ +//go:build esp32s3_wroom1 + +package main + +import "machine" + +var ( + pwm = machine.PWM0 + pinA = machine.GPIO17 + pinB = machine.GPIO18 +) diff --git a/src/examples/pwm/xiao_esp32s3.go b/src/examples/pwm/xiao_esp32s3.go new file mode 100644 index 0000000000..c614abc861 --- /dev/null +++ b/src/examples/pwm/xiao_esp32s3.go @@ -0,0 +1,11 @@ +//go:build xiao_esp32s3 + +package main + +import "machine" + +var ( + pwm = machine.PWM0 + pinA = machine.D0 + pinB = machine.D1 +) diff --git a/src/examples/serial-stress/main.go b/src/examples/serial-stress/main.go new file mode 100644 index 0000000000..cdda2cfa7c --- /dev/null +++ b/src/examples/serial-stress/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "machine" + "strconv" + "time" +) + +func main() { + time.Sleep(2 * time.Second) // connect via serial + buf1 := makeBuffer('|', 600) + buf2 := makeBuffer('/', 600) + println("start") + serialWrite(buf1) + serialWrite(buf2) +} + +func makeBuffer(sep byte, size int) []byte { + buf := make([]byte, size) + for i := 0; i < size-5; i += 5 { + buf[i] = sep + strconv.AppendInt(buf[i+1:i+1:i+5], int64(i), 10) + } + return buf +} + +func serialWrite(b []byte) { + machine.Serial.Write(b) +} diff --git a/src/internal/reflectlite/value.go b/src/internal/reflectlite/value.go index b8ec96d55f..3c2af94f72 100644 --- a/src/internal/reflectlite/value.go +++ b/src/internal/reflectlite/value.go @@ -138,7 +138,7 @@ func TypeAssert[T any](v Value) (T, bool) { var zero T return zero, false } - if !v.isIndirect() { + if !v.isIndirect() && v.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { return *(*T)(unsafe.Pointer(&v.value)), true } return *(*T)(v.value), true diff --git a/src/internal/task/task_stack_tinygoriscv.go b/src/internal/task/task_stack_tinygoriscv.go index 541dc96a4c..27e6542474 100644 --- a/src/internal/task/task_stack_tinygoriscv.go +++ b/src/internal/task/task_stack_tinygoriscv.go @@ -71,3 +71,8 @@ func (s *state) pause() { func SystemStack() uintptr { return *runtime_systemStackPtr() } + +//export tinygo_task_current +func tinygo_task_current() unsafe.Pointer { + return unsafe.Pointer(Current()) +} diff --git a/src/machine/board_arduino_uno_q.go b/src/machine/board_arduino_uno_q.go new file mode 100644 index 0000000000..9c2296bc89 --- /dev/null +++ b/src/machine/board_arduino_uno_q.go @@ -0,0 +1,124 @@ +//go:build arduino_uno_q + +// Arduino UNO Q board with STM32U585 processor. + +package machine + +import ( + "device/stm32" + "runtime/interrupt" + "unsafe" +) + +const ( + // Arduino Pins + A0 = PA4 + A1 = PA5 + A2 = PA6 + A3 = PA7 + A4 = PC1 + A5 = PC0 + + // ADC pin aliases + ADC0 = A0 + ADC1 = A1 + ADC2 = A2 + ADC3 = A3 + ADC4 = A4 + ADC5 = A5 + + D0 = PB7 // USART1 RX, PWM TIM4_CH2 + D1 = PB6 // USART1 TX, PWM TIM4_CH1 + D2 = PB3 // PWM TIM2_CH2 + D3 = PB0 // PWM TIM3_CH3 + D4 = PA12 + D5 = PA11 // PWM TIM1_CH4 + D6 = PB1 // PWM TIM3_CH4 + D7 = PB2 + D8 = PB4 // PWM TIM3_CH1 + D9 = PB8 // PWM TIM4_CH3 / TIM16_CH1 + D10 = PB9 // PWM TIM4_CH4 / TIM17_CH1 + D11 = PB15 // PWM TIM15_CH2 + D12 = PB14 // PWM TIM15_CH1 + D13 = PB13 + D18 = PC1 + D19 = PC0 + D20 = PB10 // I2C2 SCL, PWM TIM2_CH3 + D21 = PB11 // I2C2 SDA, PWM TIM2_CH4 +) + +const ( + LED = LED3_R + LED3_R = PH10 + LED3_G = PH11 + LED3_B = PH12 + LED4_R = PH13 + LED4_G = PH14 + LED4_B = PH15 +) + +const ( + // Default UART pins (LPUART1 active via ST-LINK virtual COM port) + UART_TX_PIN = PG7 + UART_RX_PIN = PG8 + + // USART1 pins (Arduino header D1/D0) + UART1_TX_PIN = D1 + UART1_RX_PIN = D0 + + // LPUART1 pins (active via ST-LINK virtual COM port) + UART2_TX_PIN = PG7 + UART2_RX_PIN = PG8 + + // I2C pins + I2C0_SCL_PIN = D20 + I2C0_SDA_PIN = D21 + + // SPI pins + SPI1_SCK_PIN = PA5 + SPI1_SDI_PIN = PA6 + SPI1_SDO_PIN = PA7 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN +) + +var ( + // USART1 on PB6/PB7 (Arduino header D1/D0). + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART1, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + + // LPUART1 on PG7/PG8 (active via ST-LINK virtual COM port). + UART2 = &_UART2 + _UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: (*stm32.USART_Type)(unsafe.Pointer(stm32.LPUART1)), + TxAltFuncSelector: AF8_UART4_5_LPUART1_SDMMC1, + RxAltFuncSelector: AF8_UART4_5_LPUART1_SDMMC1, + } + DefaultUART = UART2 + + // I2C2 is documented, alias to I2C0 as well + I2C2 = &I2C{ + Bus: stm32.I2C2, + AltFuncSelector: AF4_I2C1_2_3_4, + } + I2C0 = I2C2 + + // SPI1 is documented, alias to SPI0 as well + SPI1 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF5_SPI1_2_3_OCTOSPI1_OCTOSPI2, + } + SPI0 = SPI1 +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART1, _UART1.handleInterrupt) + UART2.Interrupt = interrupt.New(stm32.IRQ_LPUART1, _UART2.handleInterrupt) +} diff --git a/src/machine/board_digispark.go b/src/machine/board_digispark.go index f380aae85c..d7106a5544 100644 --- a/src/machine/board_digispark.go +++ b/src/machine/board_digispark.go @@ -2,17 +2,26 @@ package machine +// Digispark is a tiny ATtiny85-based board with 6 I/O pins. +// +// PWM is available on the following pins: +// - P0 (PB0): Timer0 channel A +// - P1 (PB1): Timer0 channel B or Timer1 channel A (LED pin) +// - P4 (PB4): Timer1 channel B +// +// Timer1 is recommended for PWM as it provides more flexible frequency control. + // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 16000000 } const ( - P0 Pin = PB0 - P1 Pin = PB1 + P0 Pin = PB0 // PWM available (Timer0 OC0A) + P1 Pin = PB1 // PWM available (Timer0 OC0B or Timer1 OC1A) P2 Pin = PB2 P3 Pin = PB3 - P4 Pin = PB4 + P4 Pin = PB4 // PWM available (Timer1 OC1B) P5 Pin = PB5 LED = P1 diff --git a/src/machine/board_esp32c3-12f.go b/src/machine/board_esp32c3-12f.go index f023bb9d61..0988c7adcb 100644 --- a/src/machine/board_esp32c3-12f.go +++ b/src/machine/board_esp32c3-12f.go @@ -30,9 +30,6 @@ const ( // ADC pins const ( - ADC0 Pin = ADC1_0 - ADC1 Pin = ADC2_0 - ADC1_0 Pin = IO0 ADC1_1 Pin = IO1 ADC1_2 Pin = IO2 diff --git a/src/machine/board_esp32c3-supermini.go b/src/machine/board_esp32c3-supermini.go index c180ff0e3e..51fee55c36 100644 --- a/src/machine/board_esp32c3-supermini.go +++ b/src/machine/board_esp32c3-supermini.go @@ -1,6 +1,6 @@ //go:build esp32c3_supermini -// This file contains the pin mappings for the ESP32 supermini boards. +// This file contains the pin mappings for the ESP32C3 supermini board. // // - https://web.archive.org/web/20240805232453/https://dl.artronshop.co.th/ESP32-C3%20SuperMini%20datasheet.pdf diff --git a/src/machine/board_esp32s3-supermini.go b/src/machine/board_esp32s3-supermini.go new file mode 100644 index 0000000000..c377dda75f --- /dev/null +++ b/src/machine/board_esp32s3-supermini.go @@ -0,0 +1,88 @@ +//go:build esp32s3_supermini + +// This file contains the pin mappings for the ESP32S3 supermini board. +// +// - https://www.nologo.tech/product/esp32/esp32s3/esp32s3supermini/esp32S3SuperMini.html + +package machine + +// Digital Pins +const ( + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO10 = GPIO10 + IO11 = GPIO11 + IO12 = GPIO12 + IO13 = GPIO13 + IO14 = GPIO14 + IO15 = GPIO15 + IO16 = GPIO16 + IO17 = GPIO17 + IO18 = GPIO18 + IO21 = GPIO21 + IO33 = GPIO33 + IO34 = GPIO34 + IO35 = GPIO35 + IO36 = GPIO36 + IO37 = GPIO37 + IO38 = GPIO38 + IO39 = GPIO39 + IO40 = GPIO40 + IO41 = GPIO41 + IO42 = GPIO42 + IO43 = GPIO43 + IO44 = GPIO44 + IO45 = GPIO45 + IO46 = GPIO46 + IO47 = GPIO47 + IO48 = GPIO48 +) + +// Built-in LED +const LED = GPIO48 + +// Analog pins +const ( + A1 = GPIO1 + A2 = GPIO2 + A3 = GPIO3 + A4 = GPIO4 + A5 = GPIO5 + A6 = GPIO6 + A7 = GPIO7 + A8 = GPIO8 + A9 = GPIO9 + A10 = GPIO10 + A11 = GPIO11 + A12 = GPIO12 + A13 = GPIO13 + A14 = GPIO14 + A15 = GPIO15 + A16 = GPIO16 +) + +// I2C pins +const ( + SDA_PIN = GPIO8 + SCL_PIN = GPIO9 +) + +// SPI pins +const ( + SPI1_SCK_PIN = GPIO4 + SPI1_MISO_PIN = GPIO5 + SPI1_MOSI_PIN = GPIO6 + SPI1_CS_PIN = GPIO7 + + SPI2_SCK_PIN = NoPin + SPI2_MOSI_PIN = NoPin + SPI2_MISO_PIN = NoPin + SPI2_CS_PIN = NoPin +) diff --git a/src/machine/board_esp32s3-wroom1.go b/src/machine/board_esp32s3-wroom1.go new file mode 100644 index 0000000000..20c8883183 --- /dev/null +++ b/src/machine/board_esp32s3-wroom1.go @@ -0,0 +1,18 @@ +//go:build esp32s3_wroom1 + +package machine + +const ( + SCL_PIN = GPIO17 + SDA_PIN = GPIO18 + + SPI1_SCK_PIN = GPIO12 // SCK + SPI1_MOSI_PIN = GPIO11 // SDO (MOSI) + SPI1_MISO_PIN = GPIO13 // SDI (MISO) + SPI1_CS_PIN = GPIO10 // CS + + SPI2_SCK_PIN = GPIO36 // SCK + SPI2_MOSI_PIN = GPIO35 // SDO (MOSI) + SPI2_MISO_PIN = GPIO37 // SDI (MISO) + SPI2_CS_PIN = GPIO34 // CS +) diff --git a/src/machine/board_feather-m0.go b/src/machine/board_feather-m0.go index f38d8ec889..15ec77d37a 100644 --- a/src/machine/board_feather-m0.go +++ b/src/machine/board_feather-m0.go @@ -43,6 +43,15 @@ const ( USBCDC_DP_PIN = PA25 ) +// UART0 pins +const ( + UART0_TX_PIN = D1 + UART0_RX_PIN = D0 +) + +// UART0 on the Feather M0. +var UART0 = &sercomUSART0 + // UART1 pins const ( UART_TX_PIN = D10 diff --git a/src/machine/board_vicharak_shrike-lite.go b/src/machine/board_vicharak_shrike-lite.go new file mode 100644 index 0000000000..8899e7125c --- /dev/null +++ b/src/machine/board_vicharak_shrike-lite.go @@ -0,0 +1,118 @@ +//go:build vicharak_shrike_lite + +// Pin mappings for Vicharak Shrike-Lite. +// +// Reference: https://vicharak-in.github.io/shrike/shrike_pinouts.html + +package machine + +// Digital +const ( + IO0 Pin = GPIO0 + IO1 Pin = GPIO1 + IO2 Pin = GPIO2 + IO3 Pin = GPIO3 + IO4 Pin = GPIO4 + IO5 Pin = GPIO5 + IO6 Pin = GPIO6 + IO7 Pin = GPIO7 + IO8 Pin = GPIO8 + IO9 Pin = GPIO9 + IO10 Pin = GPIO10 + IO11 Pin = GPIO11 + IO12 Pin = GPIO12 + IO13 Pin = GPIO13 + IO14 Pin = GPIO14 + IO15 Pin = GPIO15 + IO16 Pin = GPIO16 + IO17 Pin = GPIO17 + IO18 Pin = GPIO18 + IO19 Pin = GPIO19 + IO20 Pin = GPIO20 + IO21 Pin = GPIO21 + IO22 Pin = GPIO22 + IO23 Pin = GPIO23 + IO24 Pin = GPIO24 + IO25 Pin = GPIO25 + IO26 Pin = GPIO26 + IO27 Pin = GPIO27 + IO28 Pin = GPIO28 + IO29 Pin = GPIO29 +) + +// FPGA Pins +const ( + FPGA_EN Pin = IO13 + FPGA_PWR Pin = IO12 + // SPI_SCLK + F3 Pin = IO2 + // SPI_SS + F4 Pin = IO1 + // SPI_SI (MOSI) + F5 Pin = IO3 + // SPI_SO (MISO) / CONFIG + F6 Pin = IO0 + F18 Pin = IO14 + F17 Pin = IO15 +) + +// Analog pins +const ( + A0 Pin = IO26 + A1 Pin = IO27 + A2 Pin = IO28 + A3 Pin = IO29 +) + +// LED +const ( + LED = IO4 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = IO24 + I2C0_SCL_PIN Pin = IO25 + + I2C1_SDA_PIN Pin = IO6 + I2C1_SCL_PIN Pin = IO7 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = IO18 + SPI0_SDO_PIN Pin = IO19 + SPI0_SDI_PIN Pin = IO20 + + SPI1_SCK_PIN Pin = IO10 + SPI1_SDO_PIN Pin = IO11 + SPI1_SDI_PIN Pin = IO8 +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// UART pins +const ( + UART0_TX_PIN = IO28 + UART0_RX_PIN = IO29 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN + UART1_TX_PIN = IO24 + UART1_RX_PIN = IO25 +) + +var DefaultUART = UART0 + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Shrike-Lite" + usb_STRING_MANUFACTURER = "Vicharak" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) diff --git a/src/machine/board_xiao-esp32s3.go b/src/machine/board_xiao-esp32s3.go index 9181bffc2e..6e1e67b832 100644 --- a/src/machine/board_xiao-esp32s3.go +++ b/src/machine/board_xiao-esp32s3.go @@ -47,9 +47,15 @@ const ( // SPI pins const ( - SPI_SCK_PIN = GPIO7 - SPI_SDI_PIN = GPIO9 - SPI_SDO_PIN = GPIO8 + SPI1_SCK_PIN = GPIO7 // D8 + SPI1_MISO_PIN = GPIO8 // D9 + SPI1_MOSI_PIN = GPIO9 // D10 + SPI1_CS_PIN = NoPin + + SPI2_SCK_PIN = NoPin + SPI2_MOSI_PIN = NoPin + SPI2_MISO_PIN = NoPin + SPI2_CS_PIN = NoPin ) // Onboard LEDs diff --git a/src/machine/board_xiao-rp2350.go b/src/machine/board_xiao-rp2350.go new file mode 100644 index 0000000000..30003d4600 --- /dev/null +++ b/src/machine/board_xiao-rp2350.go @@ -0,0 +1,93 @@ +//go:build xiao_rp2350 + +// This file contains the pin mappings for the Seeed XIAO RP2350 boards. +// +// XIAO RP2350 is a microcontroller using the Raspberry Pi RP2350 chip. +// +// - https://wiki.seeedstudio.com/XIAO-RP2350/ +package machine + +// Digital Pins +const ( + D0 Pin = GPIO26 + D1 Pin = GPIO27 + D2 Pin = GPIO28 + D3 Pin = GPIO5 + D4 Pin = GPIO6 + D5 Pin = GPIO7 + D6 Pin = GPIO0 + D7 Pin = GPIO1 + D8 Pin = GPIO2 + D9 Pin = GPIO4 + D10 Pin = GPIO3 + D11 Pin = GPIO21 + D12 Pin = GPIO20 + D13 Pin = GPIO17 + D14 Pin = GPIO16 + D15 Pin = GPIO11 + D16 Pin = GPIO12 + D17 Pin = GPIO10 +) + +// Analog pins +const ( + A0 Pin = D0 + A1 Pin = D1 + A2 Pin = D2 +) + +// Onboard LEDs +const ( + NEOPIXEL = GPIO22 + WS2812 = GPIO22 + NEO_PWR = GPIO23 + NEOPIXEL_POWER = GPIO23 + + LED = GPIO25 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = D14 + I2C0_SCL_PIN Pin = D13 + + I2C1_SDA_PIN Pin = D4 + I2C1_SCL_PIN Pin = D5 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = D8 + SPI0_SDO_PIN Pin = D10 + SPI0_SDI_PIN Pin = D9 + + SPI1_SCK_PIN Pin = D17 + SPI1_SDO_PIN Pin = D15 + SPI1_SDI_PIN Pin = D16 +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "XIAO RP2350" + usb_STRING_MANUFACTURER = "Seeed" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x000a +) diff --git a/src/machine/flash.go b/src/machine/flash.go index bfbb28794a..bcbc64e12e 100644 --- a/src/machine/flash.go +++ b/src/machine/flash.go @@ -1,4 +1,4 @@ -//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l0 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350 +//go:build esp32c3 || nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l0 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350 package machine diff --git a/src/machine/i2c.go b/src/machine/i2c.go index 016f1a969b..ff6813856e 100644 --- a/src/machine/i2c.go +++ b/src/machine/i2c.go @@ -1,4 +1,4 @@ -//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 +//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 || esp32s3 package machine diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 20dc41b8de..158be4911b 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -11,6 +11,7 @@ import ( "device/sam" "errors" "internal/binary" + "runtime/interrupt" "unsafe" ) diff --git a/src/machine/machine_attiny85.go b/src/machine/machine_attiny85.go index 33424c6052..6d31846b5a 100644 --- a/src/machine/machine_attiny85.go +++ b/src/machine/machine_attiny85.go @@ -21,3 +21,524 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { // Very simple for the attiny85, which only has a single port. return avr.PORTB, 1 << uint8(p) } + +// PWM is one PWM peripheral, which consists of a counter and two output +// channels (that can be connected to two fixed pins). You can set the frequency +// using SetPeriod, but only for all the channels in this PWM peripheral at +// once. +type PWM struct { + num uint8 +} + +var ( + Timer0 = PWM{0} // 8 bit timer for PB0 and PB1 + Timer1 = PWM{1} // 8 bit high-speed timer for PB1 and PB4 +) + +// GTCCR bits for Timer1 that are not defined in the device file +const ( + gtccrPWM1B = 0x40 // Pulse Width Modulator B Enable + gtccrCOM1B0 = 0x10 // Comparator B Output Mode bit 0 + gtccrCOM1B1 = 0x20 // Comparator B Output Mode bit 1 +) + +// Configure enables and configures this PWM. +// +// For Timer0, there is only a limited number of periods available, namely the +// CPU frequency divided by 256 and again divided by 1, 8, 64, 256, or 1024. +// For a MCU running at 8MHz, this would be a period of 32µs, 256µs, 2048µs, +// 8192µs, or 32768µs. +// +// For Timer1, the period is more flexible as it uses OCR1C as the top value. +// Timer1 also supports more prescaler values (1 to 16384). +func (pwm PWM) Configure(config PWMConfig) error { + switch pwm.num { + case 0: // Timer/Counter 0 (8-bit) + // Calculate the timer prescaler. + var prescaler uint8 + switch config.Period { + case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()): + prescaler = 1 + case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()): + prescaler = 2 + case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()): + prescaler = 3 + case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()): + prescaler = 4 + case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()): + prescaler = 5 + default: + return ErrPWMPeriodTooLong + } + + avr.TCCR0B.Set(prescaler) + // Set the PWM mode to fast PWM (mode = 3). + avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) + + case 1: // Timer/Counter 1 (8-bit high-speed) + // Timer1 on ATtiny85 is different from ATmega328: + // - It's 8-bit with configurable top (OCR1C) + // - Has more prescaler options (1-16384) + // - PWM mode is enabled per-channel via PWM1A/PWM1B bits + var top uint64 + if config.Period == 0 { + // Use a top appropriate for LEDs. + top = 0xff + } else { + // Calculate top value: top = period * (CPUFrequency / 1e9) + top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + // Timer1 prescaler values: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384 + const maxTop = 256 + var prescaler uint8 + switch { + case top <= maxTop: + prescaler = 1 // prescaler 1 + case top/2 <= maxTop: + prescaler = 2 // prescaler 2 + top /= 2 + case top/4 <= maxTop: + prescaler = 3 // prescaler 4 + top /= 4 + case top/8 <= maxTop: + prescaler = 4 // prescaler 8 + top /= 8 + case top/16 <= maxTop: + prescaler = 5 // prescaler 16 + top /= 16 + case top/32 <= maxTop: + prescaler = 6 // prescaler 32 + top /= 32 + case top/64 <= maxTop: + prescaler = 7 // prescaler 64 + top /= 64 + case top/128 <= maxTop: + prescaler = 8 // prescaler 128 + top /= 128 + case top/256 <= maxTop: + prescaler = 9 // prescaler 256 + top /= 256 + case top/512 <= maxTop: + prescaler = 10 // prescaler 512 + top /= 512 + case top/1024 <= maxTop: + prescaler = 11 // prescaler 1024 + top /= 1024 + case top/2048 <= maxTop: + prescaler = 12 // prescaler 2048 + top /= 2048 + case top/4096 <= maxTop: + prescaler = 13 // prescaler 4096 + top /= 4096 + case top/8192 <= maxTop: + prescaler = 14 // prescaler 8192 + top /= 8192 + case top/16384 <= maxTop: + prescaler = 15 // prescaler 16384 + top /= 16384 + default: + return ErrPWMPeriodTooLong + } + + // Set prescaler (CS1[3:0] bits) + avr.TCCR1.Set(prescaler) + // Set top value + avr.OCR1C.Set(uint8(top - 1)) + } + return nil +} + +// SetPeriod updates the period of this PWM peripheral. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// If you use a period of 0, a period that works well for LEDs will be picked. +// +// SetPeriod will not change the prescaler, but also won't change the current +// value in any of the channels. This means that you may need to update the +// value for the particular channel. +// +// Note that you cannot pick any arbitrary period after the PWM peripheral has +// been configured. If you want to switch between frequencies, pick the lowest +// frequency (longest period) once when calling Configure and adjust the +// frequency here as needed. +func (pwm PWM) SetPeriod(period uint64) error { + if pwm.num == 0 { + return ErrPWMPeriodTooLong // Timer0 doesn't support dynamic period + } + + // Timer1 can adjust period via OCR1C + var top uint64 + if period == 0 { + top = 0xff + } else { + top = period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + // Get current prescaler + prescaler := avr.TCCR1.Get() & 0x0f + // Timer1 prescaler values follow a power-of-2 pattern: + // prescaler n maps to divisor 2^(n-1), so we can use a simple shift + if prescaler > 0 && prescaler <= 15 { + top >>= (prescaler - 1) + } + + if top > 256 { + return ErrPWMPeriodTooLong + } + + avr.OCR1C.Set(uint8(top - 1)) + avr.TCNT1.Set(0) + + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. It +// will only change with a call to Configure or SetPeriod, otherwise it is +// constant. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to Set +// (see Set documentation for more information). +func (pwm PWM) Top() uint32 { + if pwm.num == 1 { + // Timer1 has configurable top via OCR1C + return uint32(avr.OCR1C.Get()) + 1 + } + // Timer0 goes from 0 to 0xff (256 in total) + return 256 +} + +// Counter returns the current counter value of the timer in this PWM +// peripheral. It may be useful for debugging. +func (pwm PWM) Counter() uint32 { + switch pwm.num { + case 0: + return uint32(avr.TCNT0.Get()) + case 1: + return uint32(avr.TCNT1.Get()) + } + return 0 +} + +// Prescaler lookup tables using uint16 (more efficient than uint64 on AVR) +// Timer0 prescaler lookup table (index 0-7 maps to prescaler bits) +var timer0Prescalers = [8]uint16{0, 1, 8, 64, 256, 1024, 0, 0} + +// Timer1 prescaler lookup table (index 0-15 maps to prescaler bits) +var timer1Prescalers = [16]uint16{0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384} + +// Period returns the used PWM period in nanoseconds. It might deviate slightly +// from the configured period due to rounding. +func (pwm PWM) Period() uint64 { + var prescaler uint64 + switch pwm.num { + case 0: + prescalerBits := avr.TCCR0B.Get() & 0x7 + prescaler = uint64(timer0Prescalers[prescalerBits]) + if prescaler == 0 { + return 0 + } + case 1: + prescalerBits := avr.TCCR1.Get() & 0x0f + prescaler = uint64(timer1Prescalers[prescalerBits]) + if prescaler == 0 { + return 0 + } + } + top := uint64(pwm.Top()) + return prescaler * top * 1000 / uint64(CPUFrequency()/1e6) +} + +// Channel returns a PWM channel for the given pin. +func (pwm PWM) Channel(pin Pin) (uint8, error) { + pin.Configure(PinConfig{Mode: PinOutput}) + pin.Low() + switch pwm.num { + case 0: + switch pin { + case PB0: // OC0A + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + return 0, nil + case PB1: // OC0B + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + return 1, nil + } + case 1: + switch pin { + case PB1: // OC1A + // Enable PWM on channel A + avr.TCCR1.SetBits(avr.TCCR1_PWM1A | avr.TCCR1_COM1A1) + return 0, nil + case PB4: // OC1B + // Enable PWM on channel B (controlled via GTCCR) + avr.GTCCR.SetBits(gtccrPWM1B | gtccrCOM1B1) + return 1, nil + } + } + return 0, ErrInvalidOutputPin +} + +// SetInverting sets whether to invert the output of this channel. +// Without inverting, a 25% duty cycle would mean the output is high for 25% of +// the time and low for the rest. Inverting flips the output as if a NOT gate +// was placed at the output, meaning that the output would be 25% low and 75% +// high with a duty cycle of 25%. +func (pwm PWM) SetInverting(channel uint8, inverting bool) { + switch pwm.num { + case 0: + switch channel { + case 0: // channel A, PB0 + if inverting { + avr.PORTB.SetBits(1 << 0) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0) + } else { + avr.PORTB.ClearBits(1 << 0) + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0) + } + case 1: // channel B, PB1 + if inverting { + avr.PORTB.SetBits(1 << 1) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0) + } else { + avr.PORTB.ClearBits(1 << 1) + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0) + } + } + case 1: + switch channel { + case 0: // channel A, PB1 + if inverting { + avr.PORTB.SetBits(1 << 1) + avr.TCCR1.SetBits(avr.TCCR1_COM1A0) + } else { + avr.PORTB.ClearBits(1 << 1) + avr.TCCR1.ClearBits(avr.TCCR1_COM1A0) + } + case 1: // channel B, PB4 + if inverting { + avr.PORTB.SetBits(1 << 4) + avr.GTCCR.SetBits(gtccrCOM1B0) + } else { + avr.PORTB.ClearBits(1 << 4) + avr.GTCCR.ClearBits(gtccrCOM1B0) + } + } + } +} + +// Set updates the channel value. This is used to control the channel duty +// cycle, in other words the fraction of time the channel output is high (or low +// when inverted). For example, to set it to a 25% duty cycle, use: +// +// pwm.Set(channel, pwm.Top() / 4) +// +// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, +// pwm.Top()) will set the output to high, assuming the output isn't inverted. +func (pwm PWM) Set(channel uint8, value uint32) { + switch pwm.num { + case 0: + switch channel { + case 0: // channel A, PB0 + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1) + } else { + avr.OCR0A.Set(uint8(value - 1)) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + } + case 1: // channel B, PB1 + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1) + } else { + avr.OCR0B.Set(uint8(value - 1)) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + } + } + case 1: + switch channel { + case 0: // channel A, PB1 + if value == 0 { + avr.TCCR1.ClearBits(avr.TCCR1_COM1A1) + } else { + avr.OCR1A.Set(uint8(value - 1)) + avr.TCCR1.SetBits(avr.TCCR1_COM1A1) + } + case 1: // channel B, PB4 + if value == 0 { + avr.GTCCR.ClearBits(gtccrCOM1B1) + } else { + avr.OCR1B.Set(uint8(value - 1)) + avr.GTCCR.SetBits(gtccrCOM1B1) + } + } + } +} + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + LSBFirst bool + Mode uint8 +} + +// SPI is the USI-based SPI implementation for ATTiny85. +// The ATTiny85 doesn't have dedicated SPI hardware, but uses the USI +// (Universal Serial Interface) in three-wire mode. +// +// Fixed pin mapping (directly controlled by USI hardware): +// - PB2: SCK (clock) +// - PB1: DO/MOSI (data out) +// - PB0: DI/MISO (data in) +// +// Note: CS pin must be managed by the user. +type SPI struct { + // Delay cycles for frequency control (0 = max speed) + delayCycles uint16 + + // USICR value configured for the selected SPI mode + usicrValue uint8 + + // LSB-first mode (requires software bit reversal) + lsbFirst bool +} + +// SPI0 is the USI-based SPI interface on the ATTiny85 +var SPI0 = SPI{} + +// Configure sets up the USI for SPI communication. +// Note: The user must configure and control the CS pin separately. +func (s *SPI) Configure(config SPIConfig) error { + // Configure USI pins (fixed by hardware) + // PB1 (DO/MOSI) -> OUTPUT + // PB2 (USCK/SCK) -> OUTPUT + // PB0 (DI/MISO) -> INPUT + PB1.Configure(PinConfig{Mode: PinOutput}) + PB2.Configure(PinConfig{Mode: PinOutput}) + PB0.Configure(PinConfig{Mode: PinInput}) + + // Reset USI registers + avr.USIDR.Set(0) + avr.USISR.Set(0) + + // Configure USI for SPI mode: + // - USIWM0: Three-wire mode (SPI) + // - USICS1: External clock source (software controlled via USITC) + // - USICLK: Clock strobe - enables counter increment on USITC toggle + // - USICS0: Controls clock phase (CPHA) + // + // SPI Modes: + // Mode 0 (CPOL=0, CPHA=0): Clock idle low, sample on rising edge + // Mode 1 (CPOL=0, CPHA=1): Clock idle low, sample on falling edge + // Mode 2 (CPOL=1, CPHA=0): Clock idle high, sample on falling edge + // Mode 3 (CPOL=1, CPHA=1): Clock idle high, sample on rising edge + // + // For USI, USICS0 controls the sampling edge when USICS1=1: + // USICS0=0: Positive edge (rising) + // USICS0=1: Negative edge (falling) + switch config.Mode { + case Mode0: // CPOL=0, CPHA=0: idle low, sample rising + PB2.Low() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICLK + case Mode1: // CPOL=0, CPHA=1: idle low, sample falling + PB2.Low() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICS0 | avr.USICR_USICLK + case Mode2: // CPOL=1, CPHA=0: idle high, sample falling + PB2.High() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICS0 | avr.USICR_USICLK + case Mode3: // CPOL=1, CPHA=1: idle high, sample rising + PB2.High() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICLK + default: // Default to Mode 0 + PB2.Low() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICLK + } + avr.USICR.Set(s.usicrValue) + + // Calculate delay cycles for frequency control + // Each bit transfer requires 2 clock toggles (rising + falling edge) + // The loop overhead is approximately 10-15 cycles per toggle on AVR + // We calculate additional delay cycles needed to achieve the target frequency + if config.Frequency > 0 && config.Frequency < CPUFrequency()/2 { + // Cycles per half-period = CPUFrequency / (2 * Frequency) + // Subtract loop overhead (~15 cycles) to get delay cycles + cyclesPerHalfPeriod := CPUFrequency() / (2 * config.Frequency) + const loopOverhead = 15 + if cyclesPerHalfPeriod > loopOverhead { + s.delayCycles = uint16(cyclesPerHalfPeriod - loopOverhead) + } else { + s.delayCycles = 0 + } + } else { + // Max speed - no delay + s.delayCycles = 0 + } + + // Store LSBFirst setting for use in Transfer + s.lsbFirst = config.LSBFirst + + return nil +} + +// reverseByte reverses the bit order of a byte (MSB <-> LSB) +// Used for LSB-first SPI mode since USI hardware only supports MSB-first +func reverseByte(b byte) byte { + b = (b&0xF0)>>4 | (b&0x0F)<<4 + b = (b&0xCC)>>2 | (b&0x33)<<2 + b = (b&0xAA)>>1 | (b&0x55)<<1 + return b +} + +// Transfer performs a single byte SPI transfer (send and receive simultaneously) +// This implements the USI-based SPI transfer using the "clock strobing" technique +func (s *SPI) Transfer(b byte) (byte, error) { + // For LSB-first mode, reverse the bits before sending + // USI hardware only supports MSB-first, so we do it in software + if s.lsbFirst { + b = reverseByte(b) + } + + // Load the byte to transmit into the USI Data Register + avr.USIDR.Set(b) + + // Clear the counter overflow flag by writing 1 to it (AVR quirk) + // This also resets the 4-bit counter to 0 + avr.USISR.Set(avr.USISR_USIOIF) + + // Clock the data out/in + // We need 16 clock toggles (8 bits × 2 edges per bit) + // The USI counter counts each clock edge, so it overflows at 16 + // After 16 toggles, the clock returns to its idle state (set by CPOL in Configure) + // + // IMPORTANT: Only toggle USITC here! + // - USITC toggles the clock pin + // - The USICR mode bits (USIWM0, USICS1, USICS0, USICLK) were set in Configure() + // - SetBits preserves those bits and only sets USITC + if s.delayCycles == 0 { + // Fast path: no delay, run at maximum speed + for !avr.USISR.HasBits(avr.USISR_USIOIF) { + avr.USICR.SetBits(avr.USICR_USITC) + } + } else { + // Frequency-controlled path: add delay between clock toggles + for !avr.USISR.HasBits(avr.USISR_USIOIF) { + avr.USICR.SetBits(avr.USICR_USITC) + // Delay loop for frequency control + // Each iteration is approximately 3 cycles on AVR (dec, brne) + for i := s.delayCycles; i > 0; i-- { + avr.Asm("nop") + } + } + } + + // Get the received byte + result := avr.USIDR.Get() + + // For LSB-first mode, reverse the received bits + if s.lsbFirst { + result = reverseByte(result) + } + + return result, nil +} diff --git a/src/machine/machine_esp32c3.go b/src/machine/machine_esp32c3.go index eb2a18b5ee..e5e10c326b 100644 --- a/src/machine/machine_esp32c3.go +++ b/src/machine/machine_esp32c3.go @@ -27,16 +27,29 @@ const ( PinInput PinInputPullup PinInputPulldown + PinAnalog +) + +const ( + GPIO0 Pin = 0 + GPIO1 Pin = 1 + GPIO2 Pin = 2 + GPIO3 Pin = 3 + GPIO4 Pin = 4 + GPIO5 Pin = 5 + GPIO6 Pin = 6 +) + +const ( + ADC0 Pin = GPIO0 + ADC1 Pin = GPIO1 + ADC2 Pin = GPIO2 + ADC3 Pin = GPIO3 + ADC4 Pin = GPIO4 + ADC5 Pin = GPIO5 // avoid when WiFi is used. ) const ( - GPIO0 Pin = 0 - GPIO1 Pin = 1 - GPIO2 Pin = 2 - GPIO3 Pin = 3 - GPIO4 Pin = 4 - GPIO5 Pin = 5 - GPIO6 Pin = 6 GPIO7 Pin = 7 GPIO8 Pin = 8 GPIO9 Pin = 9 @@ -76,13 +89,15 @@ func (p Pin) Configure(config PinConfig) { const function = 1 // function 1 is GPIO for every pin muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos - // Make this pin an input pin (always). - muxConfig |= esp.IO_MUX_GPIO_FUN_IE + // FUN_IE: disable for PinAnalog (high-Z for ADC) + if config.Mode != PinAnalog { + muxConfig |= esp.IO_MUX_GPIO_FUN_IE + } // Set drive strength: 0 is lowest, 3 is highest. muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos - // Select pull mode. + // Select pull mode (no pulls for PinAnalog). if config.Mode == PinInputPullup { muxConfig |= esp.IO_MUX_GPIO_FUN_WPU } else if config.Mode == PinInputPulldown { @@ -99,12 +114,27 @@ func (p Pin) Configure(config PinConfig) { case PinOutput: // Set the 'output enable' bit. esp.GPIO.ENABLE_W1TS.Set(1 << p) - case PinInput, PinInputPullup, PinInputPulldown: + case PinInput, PinInputPullup, PinInputPulldown, PinAnalog: // Clear the 'output enable' bit. esp.GPIO.ENABLE_W1TC.Set(1 << p) } } +// configure is the same as Configure, but allows setting a specific GPIO matrix signal. +func (p Pin) configure(config PinConfig, signal uint32) { + p.Configure(config) + if signal == 256 { + return + } + if config.Mode == PinOutput { + p.outFunc().Set(signal) + } else { + inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)) + } +} + +func initI2CExt1Clock() {} + // outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the // output function selection. func (p Pin) outFunc() *volatile.Register32 { @@ -509,102 +539,6 @@ func (uart *UART) writeByte(b byte) error { func (uart *UART) flush() {} -type Serialer interface { - WriteByte(c byte) error - Write(data []byte) (n int, err error) - Configure(config UARTConfig) error - Buffered() int - ReadByte() (byte, error) - DTR() bool - RTS() bool -} - -func initUSB() { - // nothing to do here -} - -// USB Serial/JTAG Controller -// See esp32-c3_technical_reference_manual_en.pdf -// pg. 736 -type USB_DEVICE struct { - Bus *esp.USB_DEVICE_Type -} - -var ( - _USBCDC = &USB_DEVICE{ - Bus: esp.USB_DEVICE, - } - - USBCDC Serialer = _USBCDC -) - -var ( - errUSBWrongSize = errors.New("USB: invalid write size") - errUSBCouldNotWriteAllData = errors.New("USB: could not write all data") - errUSBBufferEmpty = errors.New("USB: read buffer empty") -) - -func (usbdev *USB_DEVICE) Configure(config UARTConfig) error { - return nil -} - -func (usbdev *USB_DEVICE) WriteByte(c byte) error { - if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { - return errUSBCouldNotWriteAllData - } - - usbdev.Bus.SetEP1_RDWR_BYTE(uint32(c)) - usbdev.flush() - - return nil -} - -func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) { - if len(data) == 0 || len(data) > 64 { - return 0, errUSBWrongSize - } - - for i, c := range data { - if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { - if i > 0 { - usbdev.flush() - } - - return i, errUSBCouldNotWriteAllData - } - usbdev.Bus.SetEP1_RDWR_BYTE(uint32(c)) - } - - usbdev.flush() - return len(data), nil -} - -func (usbdev *USB_DEVICE) Buffered() int { - return int(usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL()) -} - -func (usbdev *USB_DEVICE) ReadByte() (byte, error) { - if usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 { - return byte(usbdev.Bus.GetEP1_RDWR_BYTE()), nil - } - - return 0, nil -} - -func (usbdev *USB_DEVICE) DTR() bool { - return false -} - -func (usbdev *USB_DEVICE) RTS() bool { - return false -} - -func (usbdev *USB_DEVICE) flush() { - usbdev.Bus.SetEP1_CONF_WR_DONE(1) - for usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { - } -} - // GetRNG returns 32-bit random numbers using the ESP32-C3 true random number generator, // Random numbers are generated based on the thermal noise in the system and the // asynchronous clock mismatch. diff --git a/src/machine/machine_esp32c3_adc.go b/src/machine/machine_esp32c3_adc.go new file mode 100644 index 0000000000..a908666264 --- /dev/null +++ b/src/machine/machine_esp32c3_adc.go @@ -0,0 +1,205 @@ +//go:build esp32c3 && !m5stamp_c3 + +package machine + +import ( + "device/esp" + "errors" +) + +// newRegI2C returns the regI2C configured for ESP32-C3: hostID=0, drefInit=1. +func newRegI2C() regI2C { return regI2C{hostID: 0, drefInit: 1} } + +const ( + // ADC attenuation values for ESP32-C3 APB_SARADC. + // 0 dB : ~0 .. 1.1 V + // 11 dB : ~0 .. 3.3 V (matches typical VDD) + atten0dB = 0 + atten11dB = 3 +) + +func InitADC() { + esp.SYSTEM.SetPERIP_RST_EN0_APB_SARADC_RST(1) + esp.SYSTEM.SetPERIP_CLK_EN0_APB_SARADC_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_APB_SARADC_RST(0) + + esp.RTC_CNTL.SetANA_CONF_SAR_I2C_PU(1) + esp.RTC_CNTL.SetSENSOR_CTRL_FORCE_XPD_SAR(1) + esp.APB_SARADC.SetCTRL_SARADC_XPD_SAR_FORCE(1) + esp.APB_SARADC.SetFSM_WAIT_SARADC_XPD_WAIT(8) + esp.APB_SARADC.SetFSM_WAIT_SARADC_RSTB_WAIT(8) + esp.APB_SARADC.SetFSM_WAIT_SARADC_STANDBY_WAIT(100) + esp.APB_SARADC.SetCLKM_CONF_CLK_SEL(2) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_NUM(1) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_B(0) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_A(0) + esp.APB_SARADC.SetCLKM_CONF_CLK_EN(1) + + adcSelfCalibrate() +} + +// ESP32-C3: ADC1 = GPIO0–GPIO4 (ch 0–4), ADC2 = GPIO5 (ch 0). ADC2 shares with Wi‑Fi; +// readings may be noisy when Wi‑Fi is active. +func (a ADC) Configure(config ADCConfig) error { + if a.Pin > 5 { + return errors.New("invalid ADC pin for ESP32-C3") + } + a.Pin.Configure(PinConfig{Mode: PinAnalog}) + return nil +} + +func (a ADC) Get() uint16 { + if a.Pin > 5 { + return 0 + } + adc1 := a.Pin <= 4 + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB) + esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1) + esp.APB_SARADC.SetINT_CLR_APB_SARADC2_DONE_INT_CLR(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + var raw uint32 + if adc1 { + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(uint32(a.Pin)) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1) + for esp.APB_SARADC.GetINT_RAW_APB_SARADC1_DONE_INT_RAW() == 0 { + } + raw = esp.APB_SARADC.GetSAR1DATA_STATUS_APB_SARADC1_DATA() + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0) + } else { + // ADC2: GPIO5 = channel 0. Grant arbiter to ADC2 first, then set channel and start. + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(1) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(8) // (1<<3)|0 for ADC2 channel 0 + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC2_ONETIME_SAMPLE(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1) + for esp.APB_SARADC.GetINT_RAW_APB_SARADC2_DONE_INT_RAW() == 0 { + } + raw = esp.APB_SARADC.GetSAR2DATA_STATUS_APB_SARADC2_DATA() + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC2_ONETIME_SAMPLE(0) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(0) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(0) + } + return uint16(raw&0xfff) << 4 +} + +// adcSelfCalibration +const ( + adcCalTimesC3 = 15 + adcCalRtcMagicC3 = uint32(0xADC1C401) + adcCalInitMinC3 = uint32(1000) + adcCalInitMaxC3 = uint32(4096) +) + +// selfCalibrate sets ADC1/ADC2 init code from RTC or runs self-calibration (GND). +// eFuse is not used: on ESP32-C3 the ADC calibration fields in BLK2 are often unprogrammed. +func adcSelfCalibrate() { + reg := newRegI2C() + reg.sarEnable() + + var adc1Code uint32 + if saved, ok := restoreFromRTC(); ok { + adc1Code = saved + } else { + calSetupADC1() + reg.calibrationInit(0) + reg.calibrationPrepare(0) + adc1Code = reg.calibrateBinarySearch(0, adcCalTimesC3, readADC1) + if adc1Code < adcCalInitMinC3 { + adc1Code = adcCalInitMinC3 + } + if adc1Code > adcCalInitMaxC3 { + adc1Code = adcCalInitMaxC3 + } + saveToRTC(adc1Code) + reg.calibrationFinish(0) + } + + applyADC1Code(reg, adc1Code) + applyADC2Code(reg, adc1Code) +} + +// calSetupADC1 configures APB_SARADC for oneshot sampling on ADC1 channel 0 +// with fixed attenuation. This is used only during self‑calibration. +func calSetupADC1() { + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1) +} + +// calSetupADC2 configures APB_SARADC for oneshot sampling on ADC2 (GPIO5, ch 0). +// On C3, onetime_channel = (unit<<3)|channel → ADC2 ch0 = 8. +func calSetupADC2() { + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(8) // (1<<3)|0 for ADC2 + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(1) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC2_ONETIME_SAMPLE(1) +} + +// readADC1 performs a single ADC1 conversion using the APB_SARADC +// oneshot path and returns the raw 12‑bit result (0..4095). +func readADC1() uint32 { + esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1) + for esp.APB_SARADC.GetINT_RAW_APB_SARADC1_DONE_INT_RAW() == 0 { + } + raw := esp.APB_SARADC.GetSAR1DATA_STATUS_APB_SARADC1_DATA() & 0xfff + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + return uint32(raw) +} + +// readADC2 performs a single ADC2 conversion and returns the raw 12‑bit result (0..4095). +func readADC2() uint32 { + esp.APB_SARADC.SetINT_CLR_APB_SARADC2_DONE_INT_CLR(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1) + for esp.APB_SARADC.GetINT_RAW_APB_SARADC2_DONE_INT_RAW() == 0 { + } + raw := esp.APB_SARADC.GetSAR2DATA_STATUS_APB_SARADC2_DATA() & 0xfff + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(0) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(0) + return uint32(raw) +} + +func restoreFromRTC() (uint32, bool) { + if esp.RTC_CNTL.GetSTORE0() != adcCalRtcMagicC3 { + return 0, false + } + code := esp.RTC_CNTL.GetSTORE1() + if code < adcCalInitMinC3 || code > adcCalInitMaxC3 { + return 0, false + } + return code, true +} + +func saveToRTC(code uint32) { + if code < adcCalInitMinC3 || code > adcCalInitMaxC3 { + return + } + esp.RTC_CNTL.SetSTORE0(adcCalRtcMagicC3) + esp.RTC_CNTL.SetSTORE1(code) +} + +// applyADC1Code sets ADC1 init code and finishes calibration. +func applyADC1Code(reg regI2C, code uint32) { + calSetupADC1() + reg.calibrationInit(0) + reg.calibrationPrepare(0) + reg.setCalibrationParam(0, code) + reg.calibrationFinish(0) +} + +// applyADC2Code sets ADC2 init code and finishes calibration. On C3 eFuse V1 +// there is no separate ADC2 calibration; IDF uses ADC1 init code for both units. +func applyADC2Code(reg regI2C, code uint32) { + reg.calibrationInit(1) + reg.calibrationPrepare(1) + reg.setCalibrationParam(1, code) + reg.calibrationFinish(1) +} diff --git a/src/machine/machine_esp32c3_flash.go b/src/machine/machine_esp32c3_flash.go new file mode 100644 index 0000000000..5229abfb47 --- /dev/null +++ b/src/machine/machine_esp32c3_flash.go @@ -0,0 +1,137 @@ +//go:build esp32c3 + +package machine + +import ( + "runtime/interrupt" + "unsafe" +) + +/* +#include +extern int esp_rom_spiflash_read(uint32_t src_addr, uint32_t *data, uint32_t len); +extern int esp_rom_spiflash_write(uint32_t dest_addr, const uint32_t *data, uint32_t len); +extern int esp_rom_spiflash_erase_sector(uint32_t sector_num); +extern int esp_rom_spiflash_unlock(void); +extern void Cache_Invalidate_Addr(uint32_t addr, uint32_t size); +*/ +import "C" + +// compile-time check for ensuring we fulfill BlockDevice interface +var _ BlockDevice = flashBlockDevice{} + +var Flash flashBlockDevice + +type flashBlockDevice struct { +} + +// ReadAt reads the given number of bytes from the block device. +func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + if readAddress(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotReadPastEOF + } + + data := unsafe.Slice((*byte)(unsafe.Add(unsafe.Pointer(FlashDataStart()), off)), len(p)) + copy(p, data) + + return len(p), nil +} + +// WriteAt writes the given number of bytes to the block device. +// Only word (32 bits) length data can be programmed. +// If the length of p is not long enough it will be padded with 0xFF bytes. +// This method assumes that the destination is already erased. +func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + return f.writeAt(p, off) +} + +// Size returns the number of bytes in this block device. +func (f flashBlockDevice) Size() int64 { + return int64(FlashDataEnd() - FlashDataStart()) +} + +const writeBlockSize = 4 + +// WriteBlockSize returns the block size in which data can be written to +// memory. It can be used by a client to optimize writes, non-aligned writes +// should always work correctly. +func (f flashBlockDevice) WriteBlockSize() int64 { + return writeBlockSize +} + +const eraseBlockSizeValue = 1 << 12 + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +func (f flashBlockDevice) EraseBlockSize() int64 { + return eraseBlockSize() +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +func (f flashBlockDevice) EraseBlocks(start, length int64) error { + return f.eraseBlocks(start, length) +} + +// return the correct address to be used for reads +func readAddress(off int64) uintptr { + return FlashDataStart() + uintptr(off) +} + +const flashDROMStart = 0x3C000000 + +// return the correct physical address to be used for write/erase +func writeAddress(off int64) uint32 { + // DROM maps 1:1 with flash physical offset, starting at 0x3C000000. + return uint32(readAddress(off) - flashDROMStart) +} + +func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { + if readAddress(off)+uintptr(len(p)) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + address := writeAddress(off) + padded := flashPad(p, int(f.WriteBlockSize())) + + state := interrupt.Disable() + defer interrupt.Restore(state) + + C.esp_rom_spiflash_unlock() + res := C.esp_rom_spiflash_write(C.uint32_t(address), (*C.uint32_t)(unsafe.Pointer(&padded[0])), C.uint32_t(len(padded))) + C.Cache_Invalidate_Addr(C.uint32_t(readAddress(off)), C.uint32_t(len(padded))) + if res != 0 { + return 0, errFlashCannotWriteData + } + + return len(padded), nil +} + +func (f flashBlockDevice) eraseBlocks(start, length int64) error { + address := writeAddress(start * f.EraseBlockSize()) + if uintptr(unsafe.Add(unsafe.Pointer(uintptr(address)+flashDROMStart), length*f.EraseBlockSize())) > FlashDataEnd() { + return errFlashCannotErasePastEOF + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + C.esp_rom_spiflash_unlock() + sector := address / uint32(f.EraseBlockSize()) + + for i := int64(0); i < length; i++ { + res := C.esp_rom_spiflash_erase_sector(C.uint32_t(sector + uint32(i))) + C.Cache_Invalidate_Addr(C.uint32_t(readAddress((start+i)*f.EraseBlockSize())), C.uint32_t(f.EraseBlockSize())) + if res != 0 { + return errFlashCannotErasePage + } + } + + return nil +} diff --git a/src/machine/machine_esp32c3_i2c.go b/src/machine/machine_esp32c3_i2c.go index dd334b0db7..222c107f4d 100644 --- a/src/machine/machine_esp32c3_i2c.go +++ b/src/machine/machine_esp32c3_i2c.go @@ -4,350 +4,18 @@ package machine import ( "device/esp" - "runtime/volatile" - "unsafe" ) -var ( - I2C0 = &I2C{} -) - -type I2C struct{} - -// I2CConfig is used to store config info for I2C. -type I2CConfig struct { - Frequency uint32 // in Hz - SCL Pin - SDA Pin -} - const ( - clkXTAL = 0 - clkFOSC = 1 - clkXTALFrequency = uint32(40e6) - clkFOSCFrequency = uint32(17.5e6) - i2cClkSourceFrequency = clkXTALFrequency - i2cClkSource = clkXTAL + I2CEXT0_SCL_OUT_IDX = 53 + I2CEXT0_SDA_OUT_IDX = 54 ) -func (i2c *I2C) Configure(config I2CConfig) error { - if config.Frequency == 0 { - config.Frequency = 400 * KHz - } - if config.SCL == 0 { - config.SCL = SCL_PIN - } - if config.SDA == 0 { - config.SDA = SDA_PIN - } - - i2c.initClock(config) - i2c.initNoiseFilter() - i2c.initPins(config) - i2c.initFrequency(config) - i2c.startMaster() - return nil -} - -//go:inline -func (i2c *I2C) initClock(config I2CConfig) { - // reset I2C clock - esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1) - esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1) - esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0) - // disable interrupts - esp.I2C0.INT_ENA.ClearBits(0x3fff) - esp.I2C0.INT_CLR.ClearBits(0x3fff) - - esp.I2C0.SetCLK_CONF_SCLK_SEL(i2cClkSource) - esp.I2C0.SetCLK_CONF_SCLK_ACTIVE(1) - esp.I2C0.SetCLK_CONF_SCLK_DIV_NUM(i2cClkSourceFrequency / (config.Frequency * 1024)) - esp.I2C0.SetCTR_CLK_EN(1) -} - -//go:inline -func (i2c *I2C) initNoiseFilter() { - esp.I2C0.FILTER_CFG.Set(0x377) -} - -//go:inline -func (i2c *I2C) initPins(config I2CConfig) { - var muxConfig uint32 - const function = 1 // function 1 is just GPIO - - // SDA - muxConfig = function << esp.IO_MUX_GPIO_MCU_SEL_Pos - // Make this pin an input pin (always). - muxConfig |= esp.IO_MUX_GPIO_FUN_IE - // Set drive strength: 0 is lowest, 3 is highest. - muxConfig |= 1 << esp.IO_MUX_GPIO_FUN_DRV_Pos - config.SDA.mux().Set(muxConfig) - config.SDA.outFunc().Set(54) - inFunc(54).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | config.SDA)) - config.SDA.Set(true) - // Configure the pad with the given IO mux configuration. - config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) - - esp.GPIO.ENABLE.SetBits(1 << int(config.SDA)) - esp.I2C0.SetCTR_SDA_FORCE_OUT(1) - - // SCL - muxConfig = function << esp.IO_MUX_GPIO_MCU_SEL_Pos - // Make this pin an input pin (always). - muxConfig |= esp.IO_MUX_GPIO_FUN_IE - // Set drive strength: 0 is lowest, 3 is highest. - muxConfig |= 1 << esp.IO_MUX_GPIO_FUN_DRV_Pos - config.SCL.mux().Set(muxConfig) - config.SCL.outFunc().Set(53) - inFunc(53).Set(uint32(config.SCL)) - config.SCL.Set(true) - // Configure the pad with the given IO mux configuration. - config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) - - esp.GPIO.ENABLE.SetBits(1 << int(config.SCL)) - esp.I2C0.SetCTR_SCL_FORCE_OUT(1) -} - -//go:inline -func (i2c *I2C) initFrequency(config I2CConfig) { - - clkmDiv := i2cClkSourceFrequency/(config.Frequency*1024) + 1 - sclkFreq := i2cClkSourceFrequency / clkmDiv - halfCycle := sclkFreq / config.Frequency / 2 - //SCL - sclLow := halfCycle - sclWaitHigh := uint32(0) - if config.Frequency > 50000 { - sclWaitHigh = halfCycle / 8 // compensate the time when freq > 50K - } - sclHigh := halfCycle - sclWaitHigh - // SDA - sdaHold := halfCycle / 4 - sda_sample := halfCycle / 2 - setup := halfCycle - hold := halfCycle - - esp.I2C0.SetSCL_LOW_PERIOD(sclLow - 1) - esp.I2C0.SetSCL_HIGH_PERIOD(sclHigh) - esp.I2C0.SetSCL_HIGH_PERIOD_SCL_WAIT_HIGH_PERIOD(25) - esp.I2C0.SetSCL_RSTART_SETUP_TIME(setup) - esp.I2C0.SetSCL_STOP_SETUP_TIME(setup) - esp.I2C0.SetSCL_START_HOLD_TIME(hold - 1) - esp.I2C0.SetSCL_STOP_HOLD_TIME(hold - 1) - esp.I2C0.SetSDA_SAMPLE_TIME(sda_sample) - esp.I2C0.SetSDA_HOLD_TIME(sdaHold) -} - -//go:inline -func (i2c *I2C) startMaster() { - // FIFO mode for data - esp.I2C0.SetFIFO_CONF_NONFIFO_EN(0) - // Reset TX & RX buffers - esp.I2C0.SetFIFO_CONF_RX_FIFO_RST(1) - esp.I2C0.SetFIFO_CONF_RX_FIFO_RST(0) - esp.I2C0.SetFIFO_CONF_TX_FIFO_RST(1) - esp.I2C0.SetFIFO_CONF_TX_FIFO_RST(0) - // set timeout value - esp.I2C0.TO.Set(0x10) - // enable master mode - esp.I2C0.CTR.Set(0x113) - esp.I2C0.SetCTR_CONF_UPGATE(1) - resetMaster() -} - -//go:inline -func resetMaster() { - // reset FSM - esp.I2C0.SetCTR_FSM_RST(1) - // clear the bus - esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_NUM(9) - esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_EN(1) - esp.I2C0.SetSCL_STRETCH_CONF_SLAVE_SCL_STRETCH_EN(1) - esp.I2C0.SetCTR_CONF_UPGATE(1) - esp.I2C0.FILTER_CFG.Set(0x377) - // wait for SCL_RST_SLV_EN - for esp.I2C0.GetSCL_SP_CONF_SCL_RST_SLV_EN() != 0 { +var ( + I2C0 = &I2C{ + Bus: esp.I2C0, + funcSCL: I2CEXT0_SCL_OUT_IDX, + funcSDA: I2CEXT0_SDA_OUT_IDX, + useExt1: false, } - esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_NUM(0) -} - -type i2cCommandType = uint32 -type i2cAck = uint32 - -const ( - i2cCMD_RSTART i2cCommandType = 6 << 11 - i2cCMD_WRITE i2cCommandType = 1<<11 | 1<<8 // WRITE + ack_check_en - i2cCMD_READ i2cCommandType = 3<<11 | 1<<8 // READ + ack_check_en - i2cCMD_READLAST i2cCommandType = 3<<11 | 5<<8 // READ + ack_check_en + NACK - i2cCMD_STOP i2cCommandType = 2 << 11 - i2cCMD_END i2cCommandType = 4 << 11 ) - -type i2cCommand struct { - cmd i2cCommandType - data []byte - head int -} - -//go:linkname nanotime runtime.nanotime -func nanotime() int64 - -func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { - const intMask = esp.I2C_INT_STATUS_END_DETECT_INT_ST_Msk | esp.I2C_INT_STATUS_TRANS_COMPLETE_INT_ST_Msk | esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk | esp.I2C_INT_STATUS_NACK_INT_ST_Msk - esp.I2C0.INT_CLR.SetBits(intMask) - esp.I2C0.INT_ENA.SetBits(intMask) - esp.I2C0.SetCTR_CONF_UPGATE(1) - - defer func() { - esp.I2C0.INT_CLR.SetBits(intMask) - esp.I2C0.INT_ENA.ClearBits(intMask) - }() - - timeoutNS := int64(timeoutMS) * 1000000 - needAddress := true - needRestart := false - readLast := false - var readTo []byte - for cmdIdx, reg := 0, &esp.I2C0.COMD0; cmdIdx < len(cmd); { - c := &cmd[cmdIdx] - - switch c.cmd { - case i2cCMD_RSTART: - reg.Set(i2cCMD_RSTART) - reg = nextAddress(reg) - cmdIdx++ - - case i2cCMD_WRITE: - count := 32 - if needAddress { - needAddress = false - esp.I2C0.SetDATA_FIFO_RDATA((uint32(addr) & 0x7f) << 1) - count-- - esp.I2C0.SLAVE_ADDR.Set(uint32(addr)) - esp.I2C0.SetCTR_CONF_UPGATE(1) - } - for ; count > 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 { - esp.I2C0.SetDATA_FIFO_RDATA(uint32(c.data[c.head])) - } - reg.Set(i2cCMD_WRITE | uint32(32-count)) - reg = nextAddress(reg) - - if c.head < len(c.data) { - reg.Set(i2cCMD_END) - reg = nil - } else { - cmdIdx++ - } - needRestart = true - - case i2cCMD_READ: - if needAddress { - needAddress = false - esp.I2C0.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) - esp.I2C0.SLAVE_ADDR.Set(uint32(addr)) - reg.Set(i2cCMD_WRITE | 1) - reg = nextAddress(reg) - } - if needRestart { - // We need to send RESTART again after i2cCMD_WRITE. - reg.Set(i2cCMD_RSTART) - - reg = nextAddress(reg) - reg.Set(i2cCMD_WRITE | 1) - - reg = nextAddress(reg) - esp.I2C0.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) - needRestart = false - } - count := 32 - bytes := len(c.data) - c.head - // Only last byte in sequence must be sent with ACK set to 1 to indicate end of data. - split := bytes <= count - if split { - bytes-- - } - if bytes > 32 { - bytes = 32 - } - reg.Set(i2cCMD_READ | uint32(bytes)) - reg = nextAddress(reg) - - if split { - readLast = true - reg.Set(i2cCMD_READLAST | 1) - reg = nextAddress(reg) - readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte - cmdIdx++ - } else { - reg.Set(i2cCMD_END) - readTo = c.data[c.head : c.head+bytes] - reg = nil - } - - case i2cCMD_STOP: - reg.Set(i2cCMD_STOP) - reg = nil - cmdIdx++ - } - if reg == nil { - // transmit now - esp.I2C0.SetCTR_CONF_UPGATE(1) - esp.I2C0.SetCTR_TRANS_START(1) - end := nanotime() + timeoutNS - var mask uint32 - for mask = esp.I2C0.INT_STATUS.Get(); mask&intMask == 0; mask = esp.I2C0.INT_STATUS.Get() { - if nanotime() > end { - if readTo != nil { - return errI2CReadTimeout - } - return errI2CWriteTimeout - } - } - switch { - case mask&esp.I2C_INT_STATUS_NACK_INT_ST_Msk != 0 && !readLast: - return errI2CAckExpected - case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0: - if readTo != nil { - return errI2CReadTimeout - } - return errI2CWriteTimeout - } - esp.I2C0.INT_CLR.SetBits(intMask) - for i := 0; i < len(readTo); i++ { - readTo[i] = byte(esp.I2C0.GetDATA_FIFO_RDATA() & 0xff) - c.head++ - } - readTo = nil - reg = &esp.I2C0.COMD0 - } - } - return nil -} - -// Tx does a single I2C transaction at the specified address. -// It clocks out the given address, writes the bytes in w, reads back len(r) -// bytes and stores them in r, and generates a stop condition on the bus. -func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { - // timeout in microseconds. - const timeout = 40 // 40ms is a reasonable time for a real-time system. - - cmd := make([]i2cCommand, 0, 8) - cmd = append(cmd, i2cCommand{cmd: i2cCMD_RSTART}) - if len(w) > 0 { - cmd = append(cmd, i2cCommand{cmd: i2cCMD_WRITE, data: w}) - } - if len(r) > 0 { - cmd = append(cmd, i2cCommand{cmd: i2cCMD_READ, data: r}) - } - cmd = append(cmd, i2cCommand{cmd: i2cCMD_STOP}) - - return i2c.transmit(addr, cmd, timeout) -} - -func (i2c *I2C) SetBaudRate(br uint32) error { - return nil -} - -func nextAddress(reg *volatile.Register32) *volatile.Register32 { - return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4)) -} diff --git a/src/machine/machine_esp32c3_pwm.go b/src/machine/machine_esp32c3_pwm.go new file mode 100644 index 0000000000..d7e575e8d9 --- /dev/null +++ b/src/machine/machine_esp32c3_pwm.go @@ -0,0 +1,178 @@ +//go:build esp32c3 + +package machine + +import "device/esp" + +// LEDC PWM for ESP32-C3: 4 timers (PWM0–PWM3), 6 channels per timer; each timer has its own frequency. +// Range: frequency from a few Hz up to ~40 MHz (at 1-bit resolution); duty resolution 1–15 bits +// (higher frequency gives lower resolution). Clock source: APB 80 MHz. Low-speed mode only. +// See ESP-IDF LEDC driver, TRM LED PWM Controller. + +// GPIO matrix output signal indices for LEDC (soc/gpio_sig_map.h) +const ( + LEDC_LS_SIG_OUT0_IDX = 45 +) + +const ledcChannelsC3 = 6 + +var ( + PWM0 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC3, timerNum: 0} + PWM1 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC3, timerNum: 1} + PWM2 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC3, timerNum: 2} + PWM3 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC3, timerNum: 3} +) + +// chanOp implements LEDC low-speed channel ops for ESP32-C3 (channels 0–5 only). +func (pwm *LEDCPWM) chanOp(ch uint8, op ledcChanOp, duty uint32, inverting bool) { + invVal := uint32(0) + if inverting { + invVal = 1 + } + switch ch { + case 0: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH0_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH0_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH0_CONF0_IDLE_LV(0) + esp.LEDC.SetCH0_HPOINT_HPOINT(0) + esp.LEDC.SetCH0_DUTY_DUTY(0) + esp.LEDC.SetCH0_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH0_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH0_CONF1_DUTY_INC(1) + esp.LEDC.SetCH0_CONF1_DUTY_START(1) + esp.LEDC.SetCH0_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH0_DUTY_DUTY(duty) + esp.LEDC.SetCH0_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH0_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH0_CONF1_DUTY_INC(1) + esp.LEDC.SetCH0_CONF1_DUTY_START(1) + esp.LEDC.SetCH0_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH0_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH0_CONF0_IDLE_LV(invVal) + } + case 1: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH1_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH1_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH1_CONF0_IDLE_LV(0) + esp.LEDC.SetCH1_HPOINT_HPOINT(0) + esp.LEDC.SetCH1_DUTY_DUTY(0) + esp.LEDC.SetCH1_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH1_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH1_CONF1_DUTY_INC(1) + esp.LEDC.SetCH1_CONF1_DUTY_START(1) + esp.LEDC.SetCH1_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH1_DUTY_DUTY(duty) + esp.LEDC.SetCH1_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH1_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH1_CONF1_DUTY_INC(1) + esp.LEDC.SetCH1_CONF1_DUTY_START(1) + esp.LEDC.SetCH1_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH1_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH1_CONF0_IDLE_LV(invVal) + } + case 2: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH2_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH2_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH2_CONF0_IDLE_LV(0) + esp.LEDC.SetCH2_HPOINT_HPOINT(0) + esp.LEDC.SetCH2_DUTY_DUTY(0) + esp.LEDC.SetCH2_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH2_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH2_CONF1_DUTY_INC(1) + esp.LEDC.SetCH2_CONF1_DUTY_START(1) + esp.LEDC.SetCH2_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH2_DUTY_DUTY(duty) + esp.LEDC.SetCH2_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH2_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH2_CONF1_DUTY_INC(1) + esp.LEDC.SetCH2_CONF1_DUTY_START(1) + esp.LEDC.SetCH2_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH2_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH2_CONF0_IDLE_LV(invVal) + } + case 3: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH3_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH3_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH3_CONF0_IDLE_LV(0) + esp.LEDC.SetCH3_HPOINT_HPOINT(0) + esp.LEDC.SetCH3_DUTY_DUTY(0) + esp.LEDC.SetCH3_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH3_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH3_CONF1_DUTY_INC(1) + esp.LEDC.SetCH3_CONF1_DUTY_START(1) + esp.LEDC.SetCH3_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH3_DUTY_DUTY(duty) + esp.LEDC.SetCH3_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH3_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH3_CONF1_DUTY_INC(1) + esp.LEDC.SetCH3_CONF1_DUTY_START(1) + esp.LEDC.SetCH3_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH3_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH3_CONF0_IDLE_LV(invVal) + } + case 4: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH4_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH4_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH4_CONF0_IDLE_LV(0) + esp.LEDC.SetCH4_HPOINT_HPOINT(0) + esp.LEDC.SetCH4_DUTY_DUTY(0) + esp.LEDC.SetCH4_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH4_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH4_CONF1_DUTY_INC(1) + esp.LEDC.SetCH4_CONF1_DUTY_START(1) + esp.LEDC.SetCH4_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH4_DUTY_DUTY(duty) + esp.LEDC.SetCH4_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH4_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH4_CONF1_DUTY_INC(1) + esp.LEDC.SetCH4_CONF1_DUTY_START(1) + esp.LEDC.SetCH4_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH4_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH4_CONF0_IDLE_LV(invVal) + } + case 5: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH5_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH5_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH5_CONF0_IDLE_LV(0) + esp.LEDC.SetCH5_HPOINT_HPOINT(0) + esp.LEDC.SetCH5_DUTY_DUTY(0) + esp.LEDC.SetCH5_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH5_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH5_CONF1_DUTY_INC(1) + esp.LEDC.SetCH5_CONF1_DUTY_START(1) + esp.LEDC.SetCH5_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH5_DUTY_DUTY(duty) + esp.LEDC.SetCH5_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH5_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH5_CONF1_DUTY_INC(1) + esp.LEDC.SetCH5_CONF1_DUTY_START(1) + esp.LEDC.SetCH5_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH5_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH5_CONF0_IDLE_LV(invVal) + } + } +} diff --git a/src/machine/machine_esp32c3_spi.go b/src/machine/machine_esp32c3_spi.go index aec3ca77a8..5fd0a853dd 100644 --- a/src/machine/machine_esp32c3_spi.go +++ b/src/machine/machine_esp32c3_spi.go @@ -16,11 +16,6 @@ import ( ) const ( - SPI_MODE0 = uint8(0) - SPI_MODE1 = uint8(1) - SPI_MODE2 = uint8(2) - SPI_MODE3 = uint8(3) - FSPICLK_IN_IDX = uint32(63) FSPICLK_OUT_IDX = uint32(63) FSPIQ_IN_IDX = uint32(64) @@ -53,66 +48,9 @@ type SPI struct { var ( // SPI0 and SPI1 are reserved for use by the caching system etc. SPI2 = &SPI{esp.SPI2} + SPI0 = SPI2 ) -// SPIConfig is used to store config info for SPI. -type SPIConfig struct { - Frequency uint32 - SCK Pin // Serial Clock - SDO Pin // Serial Data Out (MOSI) - SDI Pin // Serial Data In (MISO) - CS Pin // Chip Select (optional) - LSBFirst bool // MSB is default - Mode uint8 // SPI_MODE0 is default -} - -// Compute the SPI bus frequency from the CPU frequency. -func freqToClockDiv(hz uint32) uint32 { - fcpu := CPUFrequency() - if hz >= fcpu { // maximum frequency - return 1 << 31 - } - if hz < (fcpu / (16 * 64)) { // minimum frequency - return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63 - } - - // iterate looking for an exact match - // or iterate all 16 prescaler options - // looking for the smallest error - var bestPre, bestN, bestErr uint32 - bestN = 1 - bestErr = 0xffffffff - q := uint32(float32(pplClockFreq)/float32(hz) + float32(0.5)) - for p := uint32(0); p < 16; p++ { - n := q/(p+1) - 1 - if n < 1 { // prescaler became too large, stop enum - break - } - if n > 63 { // prescaler too small, skip to next - continue - } - - freq := fcpu / ((p + 1) * (n + 1)) - if freq == hz { // exact match - return p<<18 | n<<12 | (n/2)<<6 | n - } - - var err uint32 - if freq < hz { - err = hz - freq - } else { - err = freq - hz - } - if err < bestErr { - bestErr = err - bestPre = p - bestN = n - } - } - - return bestPre<<18 | bestN<<12 | (bestN/2)<<6 | bestN -} - // Configure and make the SPI peripheral ready to use. func (spi *SPI) Configure(config SPIConfig) error { // right now this is only setup to work for the esp32c3 spi2 bus @@ -171,16 +109,16 @@ func (spi *SPI) Configure(config SPIConfig) error { // set spi2 data mode switch config.Mode { - case SPI_MODE0: + case Mode0: spi.Bus.SetMISC_CK_IDLE_EDGE(0) spi.Bus.SetUSER_CK_OUT_EDGE(0) - case SPI_MODE1: + case Mode1: spi.Bus.SetMISC_CK_IDLE_EDGE(0) spi.Bus.SetUSER_CK_OUT_EDGE(1) - case SPI_MODE2: + case Mode2: spi.Bus.SetMISC_CK_IDLE_EDGE(1) spi.Bus.SetUSER_CK_OUT_EDGE(1) - case SPI_MODE3: + case Mode3: spi.Bus.SetMISC_CK_IDLE_EDGE(1) spi.Bus.SetUSER_CK_OUT_EDGE(0) default: @@ -253,36 +191,7 @@ func (spi *SPI) Tx(w, r []byte) error { // Fill tx buffer. transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0)))) - if len(w) >= 64 { - // We can fill the entire 64-byte transfer buffer with data. - // This loop is slightly faster than the loop below. - for i := 0; i < 16; i++ { - word := uint32(w[i*4]) | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24 - transferWords[i].Set(word) - } - } else { - // We can't fill the entire transfer buffer, so we need to be a bit - // more careful. - // Note that parts of the transfer buffer that aren't used still - // need to be set to zero, otherwise we might be transferring - // garbage from a previous transmission if w is smaller than r. - for i := 0; i < 16; i++ { - var word uint32 - if i*4+3 < len(w) { - word |= uint32(w[i*4+3]) << 24 - } - if i*4+2 < len(w) { - word |= uint32(w[i*4+2]) << 16 - } - if i*4+1 < len(w) { - word |= uint32(w[i*4+1]) << 8 - } - if i*4+0 < len(w) { - word |= uint32(w[i*4+0]) << 0 - } - transferWords[i].Set(word) - } - } + spiTxFillBuffer(transferWords, w) // Do the transfer. spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(uint32(chunkSize)*8 - 1) diff --git a/src/machine/machine_esp32c3_usb.go b/src/machine/machine_esp32c3_usb.go new file mode 100644 index 0000000000..6480348243 --- /dev/null +++ b/src/machine/machine_esp32c3_usb.go @@ -0,0 +1,241 @@ +//go:build esp32c3 + +package machine + +import ( + "device/esp" + "errors" + "machine/usb" + "machine/usb/descriptor" + "runtime/interrupt" +) + +// USB Serial/JTAG Controller +// See esp32-c3_technical_reference_manual_en.pdf pg. 736 +// +// The ESP32-C3 has a built-in USB Serial/JTAG controller that provides a +// CDC-ACM serial port. The USB protocol and enumeration are handled entirely +// in hardware; software only reads/writes the EP1 FIFO. + +const cpuInterruptFromUSB = 10 + +// flushTimeout is the maximum number of busy-wait iterations in flush(). +// Prevents hanging when no USB host is connected. +const flushTimeout = 200000 + +type USB_DEVICE struct { + Bus *esp.USB_DEVICE_Type + Buffer *RingBuffer +} + +var ( + _USBCDC = &USB_DEVICE{ + Bus: esp.USB_DEVICE, + Buffer: NewRingBuffer(), + } + + USBCDC Serialer = _USBCDC +) + +var ( + errUSBWrongSize = errors.New("USB: invalid write size") + errUSBCouldNotWriteAllData = errors.New("USB: could not write all data") +) + +type Serialer interface { + WriteByte(c byte) error + Write(data []byte) (n int, err error) + Configure(config UARTConfig) error + Buffered() int + ReadByte() (byte, error) + DTR() bool + RTS() bool +} + +var usbConfigured bool + +// USBDevice provides a stub USB device for the ESP32-C3. The hardware +// only supports a fixed-function CDC-ACM serial port, so the programmable +// USB device features are no-ops. +type USBDevice struct { + initcomplete bool + InitEndpointComplete bool +} + +var USBDev = &USBDevice{} + +func (dev *USBDevice) SetStallEPIn(ep uint32) {} +func (dev *USBDevice) SetStallEPOut(ep uint32) {} +func (dev *USBDevice) ClearStallEPIn(ep uint32) {} +func (dev *USBDevice) ClearStallEPOut(ep uint32) {} + +// initUSB is intentionally empty — the interp phase evaluates init() +// functions at compile time and cannot access hardware registers. +// Actual hardware setup is deferred to the first Configure() call. +func initUSB() {} + +// Configure initialises the USB Serial/JTAG controller clock, pads, and +// interrupt so that received data is buffered automatically. +func (usbdev *USB_DEVICE) Configure(config UARTConfig) error { + if usbConfigured { + return nil + } + usbConfigured = true + + // Enable the USB_DEVICE peripheral clock. + // Do NOT reset the peripheral — the ROM bootloader has already + // configured the USB Serial/JTAG controller and the host may + // already be connected. Resetting would drop the USB link. + esp.SYSTEM.SetPERIP_CLK_EN0_USB_DEVICE_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_USB_DEVICE_RST(0) + + // Ensure internal PHY is selected and USB pads are enabled. + usbdev.Bus.SetCONF0_PHY_SEL(0) + usbdev.Bus.SetCONF0_USB_PAD_ENABLE(1) + usbdev.Bus.SetCONF0_DP_PULLUP(1) + + // Clear any pending interrupts. + usbdev.Bus.INT_CLR.Set(0xFFFFFFFF) + + // Enable the RX-packet-received interrupt. + usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1) + + // Map the USB peripheral interrupt to CPU interrupt cpuInterruptFromUSB. + esp.INTERRUPT_CORE0.SetUSB_INTR_MAP(cpuInterruptFromUSB) + + _ = interrupt.New(cpuInterruptFromUSB, func(interrupt.Interrupt) { + _USBCDC.handleInterrupt() + }).Enable() + + return nil +} + +// ensureConfigured triggers lazy initialization on first use. +func (usbdev *USB_DEVICE) ensureConfigured() { + if !usbConfigured { + usbdev.Configure(UARTConfig{}) + } +} + +// handleInterrupt drains the hardware RX FIFO into the software ring buffer. +func (usbdev *USB_DEVICE) handleInterrupt() { + // Read INT_ST while INT_ENA is still set (INT_ST = INT_RAW & INT_ENA). + intStatus := usbdev.Bus.INT_ST.Get() + + // Disable the RX interrupt to prevent re-triggering while we drain. + usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(0) + + if intStatus&esp.USB_DEVICE_INT_ST_SERIAL_OUT_RECV_PKT_INT_ST != 0 { + // Drain all available bytes from the EP1 OUT FIFO. + // Use EP1.Get() directly — the generated GetEP1_RDWR_BYTE is + // functionally identical, but a direct load makes the FIFO-pop + // intent explicit. + for usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 { + b := byte(usbdev.Bus.EP1.Get()) + usbdev.Buffer.Put(b) + } + // Clear the interrupt. + usbdev.Bus.SetINT_CLR_SERIAL_OUT_RECV_PKT_INT_CLR(1) + } + + // Re-enable the RX interrupt. + usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1) +} + +func (usbdev *USB_DEVICE) WriteByte(c byte) error { + usbdev.ensureConfigured() + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + // FIFO full — try flushing first, then recheck. + usbdev.flush() + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + return errUSBCouldNotWriteAllData + } + } + + // Use EP1.Set() (direct store) instead of SetEP1_RDWR_BYTE which + // does a read-modify-write — the read side-effect pops a byte from + // the RX FIFO. + usbdev.Bus.EP1.Set(uint32(c)) + usbdev.flush() + + return nil +} + +func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) { + usbdev.ensureConfigured() + if len(data) == 0 { + return 0, nil + } + + for i, c := range data { + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + if i > 0 { + usbdev.flush() + } + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + return i, errUSBCouldNotWriteAllData + } + } + usbdev.Bus.EP1.Set(uint32(c)) + } + + usbdev.flush() + return len(data), nil +} + +// Buffered returns the number of bytes waiting in the receive ring buffer. +func (usbdev *USB_DEVICE) Buffered() int { + usbdev.ensureConfigured() + return int(usbdev.Buffer.Used()) +} + +// ReadByte returns a byte from the receive ring buffer. +func (usbdev *USB_DEVICE) ReadByte() (byte, error) { + b, ok := usbdev.Buffer.Get() + if !ok { + return 0, nil + } + return b, nil +} + +func (usbdev *USB_DEVICE) DTR() bool { + return false +} + +func (usbdev *USB_DEVICE) RTS() bool { + return false +} + +// flush signals WR_DONE and waits (with timeout) for the hardware to +// consume the data. A timeout prevents hanging when no USB host is present. +func (usbdev *USB_DEVICE) flush() { + usbdev.Bus.SetEP1_CONF_WR_DONE(1) + for i := 0; i < flushTimeout; i++ { + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() != 0 { + return + } + } +} + +// The ESP32-C3 USB Serial/JTAG controller is fixed-function hardware. +// It only provides a CDC-ACM serial port; the USB protocol and endpoint +// configuration are handled entirely in silicon. The functions below +// are no-op stubs so that higher-level USB packages (HID, MIDI, …) +// compile, but they cannot add real endpoints on this hardware. + +// ConfigureUSBEndpoint is a no-op on ESP32-C3 — the hardware does not +// support programmable USB endpoints. +func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) { +} + +// SendZlp is a no-op on ESP32-C3 — the hardware handles control +// transfers internally. +func SendZlp() { +} + +// SendUSBInPacket is a no-op on ESP32-C3 — the hardware does not +// support arbitrary IN endpoints. Returns false to indicate the +// packet was not sent. +func SendUSBInPacket(ep uint32, data []byte) bool { + return false +} diff --git a/src/machine/machine_esp32s3.go b/src/machine/machine_esp32s3.go index 65261bd3c8..7a5be3d4a1 100644 --- a/src/machine/machine_esp32s3.go +++ b/src/machine/machine_esp32s3.go @@ -70,6 +70,7 @@ const ( PinInput PinInputPullup PinInputPulldown + PinAnalog ) // Hardware pin numbers @@ -121,6 +122,29 @@ const ( GPIO48 Pin = 48 ) +const ( + ADC0 Pin = GPIO1 + ADC2 Pin = GPIO2 + ADC3 Pin = GPIO3 + ADC4 Pin = GPIO4 + ADC5 Pin = GPIO5 + ADC6 Pin = GPIO6 + ADC7 Pin = GPIO7 + ADC8 Pin = GPIO8 + ADC9 Pin = GPIO9 + ADC10 Pin = GPIO10 + ADC11 Pin = GPIO11 + ADC12 Pin = GPIO12 + ADC13 Pin = GPIO13 + ADC14 Pin = GPIO14 + ADC15 Pin = GPIO15 + ADC16 Pin = GPIO16 + ADC17 Pin = GPIO17 + ADC18 Pin = GPIO18 + ADC19 Pin = GPIO19 + ADC20 Pin = GPIO20 +) + // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { // Output function 256 is a special value reserved for use as a regular GPIO @@ -146,8 +170,10 @@ func (p Pin) configure(config PinConfig, signal uint32) { // MCU_SEL: Function 1 is always GPIO ioConfig |= (1 << esp.IO_MUX_GPIO_MCU_SEL_Pos) - // FUN_IE: Make this pin an input pin (always set for GPIO operation) - ioConfig |= esp.IO_MUX_GPIO_FUN_IE + // FUN_IE: disable for PinAnalog (high-Z for ADC), enable for digital + if config.Mode != PinAnalog { + ioConfig |= esp.IO_MUX_GPIO_FUN_IE + } // DRV: Set drive strength to 20 mA as a default. Pins 17 and 18 are special var drive uint32 @@ -158,7 +184,7 @@ func (p Pin) configure(config PinConfig, signal uint32) { } ioConfig |= (drive << esp.IO_MUX_GPIO_FUN_DRV_Pos) - // WPU/WPD: Select pull mode. + // WPU/WPD: no pulls for PinAnalog if config.Mode == PinInputPullup { ioConfig |= esp.IO_MUX_GPIO_FUN_WPU } else if config.Mode == PinInputPulldown { @@ -181,14 +207,14 @@ func (p Pin) configure(config PinConfig, signal uint32) { // output signal, or the special value 256 which indicates regular GPIO // usage. p.outFunc().Set(signal) - case PinInput, PinInputPullup, PinInputPulldown: + case PinInput, PinInputPullup, PinInputPulldown, PinAnalog: // Clear the 'output enable' bit. if p < 32 { esp.GPIO.ENABLE_W1TC.Set(1 << p) } else { esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32)) } - if signal != 256 { + if signal != 256 && config.Mode != PinAnalog { // Signal is a peripheral function (not a simple GPIO). Connect this // signal to the pin. // Note that outFunc and inFunc work in the opposite direction. @@ -273,6 +299,10 @@ func (p Pin) Get() bool { } } +func (p Pin) pinReg() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.PIN0), uintptr(p)*4)) +} + var DefaultUART = UART0 var ( @@ -309,4 +339,37 @@ func (uart *UART) writeByte(b byte) error { func (uart *UART) flush() {} -// TODO: SPI +// GetRNG returns 32-bit random numbers using the ESP32-S3 true random number generator, +// Random numbers are generated based on the thermal noise in the system and the +// asynchronous clock mismatch. +// For maximum entropy also make sure that the SAR_ADC is enabled. +// See esp32-s3_technical_reference_manual_en.pdf p.920 +func GetRNG() (ret uint32, err error) { + // ensure ADC clock is initialized + initADCClock() + + // ensure fast RTC clock is enabled + if esp.RTC_CNTL.GetCLK_CONF_DIG_CLK8M_EN() == 0 { + esp.RTC_CNTL.SetCLK_CONF_DIG_CLK8M_EN(1) + } + + return esp.RNG.DATA.Get(), nil +} + +func initADCClock() { + if esp.APB_SARADC.GetCLKM_CONF_CLK_EN() == 1 { + return + } + + // only support ADC_CTRL_CLK set to 1 + esp.APB_SARADC.SetCLKM_CONF_CLK_SEL(1) + + esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_GATED(1) + + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_NUM(15) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_B(1) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_A(0) + + esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_DIV(1) + esp.APB_SARADC.SetCLKM_CONF_CLK_EN(1) +} diff --git a/src/machine/machine_esp32s3_adc.go b/src/machine/machine_esp32s3_adc.go new file mode 100644 index 0000000000..8e9c4edae7 --- /dev/null +++ b/src/machine/machine_esp32s3_adc.go @@ -0,0 +1,432 @@ +//go:build esp32s3 + +// ESP32-S3: 2 SAR ADCs, 12-bit hardware; Get() returns 0..65520 (scaled from 12-bit). +// Pin mapping: ADC1 = GPIO 1..10 (channel = GPIO-1); ADC2 = GPIO 11..20 (channel = GPIO-11). +// Get() returns raw, uncalibrated ADC values; accurate 0–3.3V mapping should be done +// either by a two-point calibration in user code or by using the eFuse-based +// calibration logic (see IDF adc_cali / our ADCSelfCalibrate implementation). +// +// Registers used (TRM / IDF): +// SYSTEM: PERIP_RST_EN0.APB_SARADC_RST, PERIP_CLK_EN0.APB_SARADC_CLK_EN +// RTC_CNTL: ANA_CONF.SAR_I2C_PU, I2C_RESET_POR_FORCE_PU +// ADC1 RTC path (oneshot, TRM/IDF): +// SENS.SAR_MEAS1_MUX.SAR1_DIG_FORCE = 0 → ADC1 under RTC (not digital/APB) +// SENS.SAR_MEAS1_CTRL2.MEAS1_START_FORCE = 1, SAR1_EN_PAD_FORCE = 1 → SW triggers and selects channel +// Per conversion: set attenuation (SAR_ATTEN1), channel (SAR1_EN_PAD), then MEAS1_START_SAR 0→1; wait MEAS1_DONE_SAR; read MEAS1_DATA_SAR. +// SENS.SAR_MEAS1_CTRL1: amp/ref (FORCE_XPD_AMP etc). SAR_MEAS1_CTRL2: MEAS1_DONE_SAR (done), MEAS1_START_SAR (start), MEAS1_DATA_SAR (12-bit result). +// APB_SARADC: FSM_WAIT, CLKM, etc. used for clock/shared logic; ADC2 uses ARB_CTRL. + +package machine + +import ( + "device/esp" + "errors" + "runtime/volatile" + "unsafe" +) + +// newRegI2C returns the regI2C configured for ESP32-S3: hostID=1, drefInit=4. +func newRegI2C() regI2C { return regI2C{hostID: 1, drefInit: 4} } + +var adcDigiRefMv uint32 + +func InitADC() { + // SYSTEM: reset and enable APB_SARADC clock so SAR registers are accessible. + esp.SYSTEM.SetPERIP_RST_EN0_APB_SARADC_RST(1) + esp.SYSTEM.SetPERIP_CLK_EN0_APB_SARADC_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_APB_SARADC_RST(0) + + // SENS.SAR_PERI_CLK_GATE_CONF: enable SENS SAR peripheral clock (matches Arduino/IDF runtime state). + esp.SENS.SetSAR_PERI_CLK_GATE_CONF_SARADC_CLK_EN(1) + + // RTC_CNTL.ANA_CONF: keep internal SAR I2C (regI2C analog bus) powered and out of reset. + esp.RTC_CNTL.SetANA_CONF_I2C_RESET_POR_FORCE_PD(0) + esp.RTC_CNTL.SetANA_CONF_SAR_I2C_PU(1) + esp.RTC_CNTL.SetANA_CONF_I2C_RESET_POR_FORCE_PU(1) + + // SENS.SAR_POWER: power up SAR analog block and enable SAR internal clock. + esp.SENS.SetSAR_POWER_XPD_SAR_FORCE_XPD_SAR(3) + esp.SENS.SetSAR_POWER_XPD_SAR_SARCLK_EN(1) + + // SENS.SAR_MEAS1_CTRL1: force ADC1 front-end amplifier and reference on in RTC oneshot mode. + esp.SENS.SetSAR_MEAS1_CTRL1_FORCE_XPD_AMP(3) + esp.SENS.SetSAR_MEAS1_CTRL1_AMP_RST_FB_FORCE(3) + esp.SENS.SetSAR_MEAS1_CTRL1_AMP_SHORT_REF_FORCE(3) + esp.SENS.SetSAR_MEAS1_CTRL1_AMP_SHORT_REF_GND_FORCE(3) + + // SENS.SAR_AMP_CTRL1/2: amplifier/reference settling timings (same as cold-boot defaults). + esp.SENS.SetSAR_AMP_CTRL1_SAR_AMP_WAIT1(10) + esp.SENS.SetSAR_AMP_CTRL1_SAR_AMP_WAIT2(10) + esp.SENS.SetSAR_AMP_CTRL2_SAR_XPD_SAR_AMP_FSM_IDLE(1) + esp.SENS.SetSAR_AMP_CTRL2_SAR_AMP_SHORT_REF_GND_FSM_IDLE(1) + + // ADC2 uses the same InitADC() as ADC1 (shared APB_SARADC clock/FSM). + // SENS.SAR_MEAS2_CTRL1: ADC2 FSM wait timings for power-up/reset/standby. + esp.SENS.SetSAR_MEAS2_CTRL1_SAR_SAR2_XPD_WAIT(8) + esp.SENS.SetSAR_MEAS2_CTRL1_SAR_SAR2_RSTB_WAIT(8) + esp.SENS.SetSAR_MEAS2_CTRL1_SAR_SAR2_STANDBY_WAIT(100) + esp.SENS.SetSAR_MEAS2_CTRL1_SAR_SAR2_RSTB_FORCE(3) + + // SENS.SAR_MEAS1_MUX / SAR_MEAS1_CTRL2: route ADC1 to RTC controller and use SW to select channel/start. + esp.SENS.SetSAR_MEAS1_MUX_SAR1_DIG_FORCE(0) // 0 = controlled by RTC/SENS, not digital/APB. + esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_FORCE(1) // SW triggers conversion. + esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD_FORCE(1) // SW selects which ADC1 pad is enabled. + + // APB_SARADC: shared FSM/clock config used by both ADC units and the ADC2 arbiter. + esp.APB_SARADC.SetFSM_WAIT_SARADC_XPD_WAIT(8) + esp.APB_SARADC.SetFSM_WAIT_SARADC_RSTB_WAIT(8) + esp.APB_SARADC.SetFSM_WAIT_SARADC_STANDBY_WAIT(100) + esp.APB_SARADC.SetCTRL_SARADC_XPD_SAR_FORCE(3) + esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_GATED(1) + esp.APB_SARADC.SetCTRL2_SARADC_SAR1_INV(0) + esp.APB_SARADC.SetCTRL2_SARADC_SAR2_INV(0) + esp.APB_SARADC.SetCLKM_CONF_CLK_SEL(2) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_NUM(1) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_B(0) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_A(0) + esp.APB_SARADC.SetCLKM_CONF_CLK_EN(1) + esp.APB_SARADC.SetFILTER_CTRL1_FILTER_FACTOR0(0) + esp.APB_SARADC.SetFILTER_CTRL1_FILTER_FACTOR1(0) + + adcSelfCalibrate() + adcDigiRefMv = getDigiRef() +} + +const ( + attenDefault = 3 // 11 dB, ~0..3.3 V (IDF ADC_ATTEN_DB_12) +) + +func setSensAtten1(ch, atten uint32) { + // SENS.SAR_ATTEN1: 2 bits per channel + v := esp.SENS.GetSAR_ATTEN1() + v &^= 3 << (ch * 2) + v |= (atten & 3) << (ch * 2) + esp.SENS.SetSAR_ATTEN1(v) +} + +func setSensAtten2(ch, atten uint32) { + // SENS.SAR_ATTEN2: 2 bits per channel + v := esp.SENS.GetSAR_ATTEN2() + v &^= 3 << (ch * 2) + v |= (atten & 3) << (ch * 2) + esp.SENS.SetSAR_ATTEN2(v) +} + +func (a ADC) Configure(config ADCConfig) error { + if a.Pin < 1 || a.Pin > 20 { + return errors.New("invalid ADC pin for ESP32-S3") + } + a.Pin.Configure(PinConfig{Mode: PinAnalog}) + + return nil +} + +func (a ADC) Get() uint16 { + if a.Pin < 1 || a.Pin > 20 { + return 0 + } + + var ch uint32 + var raw uint32 + if a.Pin <= 10 { + ch = uint32(a.Pin - 1) // GPIO1→ch0 … GPIO10→ch9 + esp.SENS.SetSAR_MEAS1_MUX_SAR1_DIG_FORCE(0) + esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_FORCE(1) + esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD_FORCE(1) + setSensAtten1(ch, attenDefault) + esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD(1 << ch) + for esp.SENS.GetSAR_SLAVE_ADDR1_SAR_SARADC_MEAS_STATUS() != 0 { + } + esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_SAR(0) + esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_SAR(1) + for esp.SENS.GetSAR_MEAS1_CTRL2_MEAS1_DONE_SAR() == 0 { + } + raw = esp.SENS.GetSAR_MEAS1_CTRL2_MEAS1_DATA_SAR() + } else { + ch = uint32(a.Pin - 11) // GPIO11→ch0 … GPIO20→ch9 + // SENS.SAR_MEAS2_CTRL2: force SW control, select channel + esp.SENS.SetSAR_MEAS2_CTRL2_MEAS2_START_FORCE(1) + esp.SENS.SetSAR_MEAS2_CTRL2_SAR2_EN_PAD_FORCE(1) + esp.SENS.SetSAR_MEAS2_CTRL2_SAR2_EN_PAD(1 << ch) + setSensAtten2(ch, attenDefault) + // APB_SARADC.ARB_CTRL: grant ADC2 to APB for oneshot + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(1) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(1) + // SENS.SAR_MEAS2_CTRL2.MEAS2_START_SAR: one-shot start + esp.SENS.SetSAR_MEAS2_CTRL2_MEAS2_START_SAR(0) + esp.SENS.SetSAR_MEAS2_CTRL2_MEAS2_START_SAR(1) + for esp.SENS.GetSAR_MEAS2_CTRL2_MEAS2_DONE_SAR() == 0 { + } + raw = esp.SENS.GetSAR_MEAS2_CTRL2_MEAS2_DATA_SAR() + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(0) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(0) + } + + return uint16(raw&0xfff) << 4 +} + +func (a ADC) GetVoltage() (raw uint32, v float64) { + const samples = 4 + var sum uint32 + for i := 0; i < samples; i++ { + sum += uint32(a.Get()) + } + raw = sum / samples + + // Default full-scale for 11 dB is approximately 3.3 V assuming + // Vref ≈ 1.1 V and gain ≈ 3. If eFuse provided a per-chip DIGI_REF + // (Vref in mV) via adcCalibration, use it to adjust the + // full-scale range instead. + scale := 3.3 + if adcDigiRefMv != 0 { + scale = 3.0 * float64(adcDigiRefMv) / 1000.0 + } + + v = float64(raw) / 65520.0 * scale + return raw, v +} + +// ADC hardware self-calibration for ESP32-S3. +// +// Mapping to ESP-IDF (adc_hal_common.c, hal/esp32s3/adc_ll.h): +// - adc_hal_self_calibration() → ADCSelfCalibrate() +// - adc_ll_calibration_init() → regI2C.calibrationInit (DREF=4); +// in IDF it is not called from self_cal, we call it explicitly. +// - adc_ll_calibration_prepare() → SarEnable + calibrationPrepare (ENCAL_GND=1) +// - adc_ll_calibration_finish() → calibrationFinish (ENCAL_GND=0) +// - adc_ll_set_calibration_param() → setCalibrationParam() +// - read_cal_channel() → adcCalibration.readADC1(): +// wait for meas_status==0, start 0→1, wait done, read data +// (similar to adc_oneshot_ll_start + get_raw_result). +// - Loop: 10 iterations, code 0..4096, binary search on self_cal==0; drop min/max; +// rounding (remainder%8 < 4 without +1, otherwise +1) — same as in adc_hal_common.c. +// - raw_check_valid: for ADC1 in IDF always true — we do not check it. +// +// Differences: +// - regI2C: not ROM helper but direct access to 0x6000E000 (protocol like I2C_RTC_CONFIG2). +// - cal_setup: same SENS/atten/controller fields, but through our registers. +// - Result is stored only in hardware for the current session (not in eFuse). +// - eFuse V1: init_code and digi_ref are taken from eFuse — same idea as Arduino/IDF. + +const ( + adcCalTimes = 10 + adcCalRtcMagic = uint32(0xADC1C401) + adcCalInitMin = uint32(2000) + adcCalInitMax = uint32(3900) + adcDigiRefMinMv = uint32(920) + adcDigiRefMaxMv = uint32(1150) +) + +// adcCalibration encapsulates the self-calibration flow for ADC1 +// and remembers per-chip calibration data (such as DIGI_REF) when it is +// available from eFuse. +func adcSelfCalibrate() { + reg := newRegI2C() + f := fuse{} + + if vref, ok := f.adc1DigiRefAtten3(); ok { + adcDigiRefMv = vref + } + + if saved, ok := restoreFromRTC(); ok { + reg.sarEnable() + reg.calibrationInit(0) + adc1CalibrateHigh(reg, saved) + return + } + + initCode, useEfuse := f.adc1InitCodeAtten3() + adc1CalibrationSetup(reg) + + if useEfuse { + saveToRTC(initCode) + adc1CalibrateHigh(reg, initCode) + return + } + + finalCode := reg.calibrateBinarySearch(0, adcCalTimes, readADC1) + saveToRTC(finalCode) + adc1CalibrateHigh(reg, finalCode) +} + +func getDigiRef() uint32 { + return adcDigiRefMv +} + +func adc1CalibrationSetup(reg regI2C) { + reg.sarEnable() + + esp.SENS.SetSAR_MEAS1_MUX_SAR1_DIG_FORCE(0) + esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_FORCE(0) + esp.SENS.SetSAR_MEAS2_CTRL2_MEAS2_START_FORCE(0) + esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD(0) + setSensAtten1(0, attenDefault) + esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_FORCE(1) + esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD_FORCE(1) + + reg.calibrationInit(0) + reg.calibrationPrepare(0) +} + +func adc1CalibrateHigh(reg regI2C, code uint32) { + reg.setCalibrationParam(0, code) + reg.calibrationFinish(0) + adc1StartWithPadForce() +} + +func adc1StartWithPadForce() { + esp.SENS.SetSAR_MEAS1_CTRL2_SAR1_EN_PAD_FORCE(1) + esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_FORCE(1) +} + +// readADC1 performs one ADC1 conversion via RTC path (used during calibration). +// Internal GND is connected via ENCAL_GND, so the pin input is disconnected. +// Matches IDF: wait conversion idle (meas_status==0), then start 0→1, wait done, read data. +func readADC1() uint32 { + for esp.SENS.GetSAR_SLAVE_ADDR1_SAR_SARADC_MEAS_STATUS() != 0 { + } + esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_SAR(0) + esp.SENS.SetSAR_MEAS1_CTRL2_MEAS1_START_SAR(1) + for esp.SENS.GetSAR_MEAS1_CTRL2_MEAS1_DONE_SAR() == 0 { + } + return uint32(esp.SENS.GetSAR_MEAS1_CTRL2_MEAS1_DATA_SAR() & 0xfff) +} + +func restoreFromRTC() (uint32, bool) { + if esp.RTC_CNTL.GetSTORE0() != adcCalRtcMagic { + return 0, false + } + code := esp.RTC_CNTL.GetSTORE1() + if code < adcCalInitMin || code > adcCalInitMax { + return 0, false + } + return code, true +} + +func saveToRTC(code uint32) { + esp.RTC_CNTL.SetSTORE0(adcCalRtcMagic) + esp.RTC_CNTL.SetSTORE1(code) +} + +// fuse +const ( + // Base address for eFuse controller (EFUSE_BLKx region in TRM). + efuseBase = uintptr(0x60007000) + + // EFUSE_*_REG offsets mirror ESP-IDF's efuse_reg.h layout. + efuseClkReg = efuseBase + 0x1c8 + efuseConfReg = efuseBase + 0x1cc + efuseCmdReg = efuseBase + 0x1d4 + efuseDacConfReg = efuseBase + 0x1e8 + efuseWrTimConf1Reg = efuseBase + 0x1f4 + efuseWrTimConf2Reg = efuseBase + 0x1f8 + efuseRdData4Reg = efuseBase + 0x6c // EFUSE_RD_WR_DIS_REG / RD_DATA4 + efuseRdData5Reg = efuseBase + 0x70 // EFUSE_RD_REPEAT_DATA1_REG / RD_DATA5 + efuseRdData7Reg = efuseBase + 0x78 // EFUSE_RD_REPEAT_DATA3_REG / RD_DATA7 + + // Read opcode and clock enable bit used by EFUSE HAL (see efuse_ll). + efuseReadOpCode = uint32(0x5AA5) + efuseClkEnBit = uint32(1 << 16) + efuseBlkVersionV1 = 1 // EFUSE_BLK_VERSION major version = 1 + + // SYSTEM_PERIP_CLK_EN0 register and EFUSE clock gate bit. + systemPeripClkEn0 = uintptr(0x600C0018) + systemEfuseClkEnBit = uint32(1 << 14) +) + +type fuse struct{} + +// adc1InitCodeAtten3 extracts the ADC1 INIT_CODE (offset trim) for +// attenuation index 3 (typically 11 dB) from EFUSE_BLK2. This mirrors +// the logic used by ESP-IDF's ADC calibration HAL for ESP32-S3. +// +// The code is built from four differential eFuse fields (diff0..diff3) +// and constant offsets (1850, 90, 70) as described in Espressif's +// internal calibration formulas. +func (f *fuse) adc1InitCodeAtten3() (uint32, bool) { + for try := 0; try < 2; try++ { + f.triggerReadSequence() + data4, data5, blkVer := f.readBlock2Data4Data5() + if blkVer != efuseBlkVersionV1 { + continue + } + diff0 := (data4 >> 21) & 0xFF + diff1 := (data4 >> 29) | ((data5 & 7) << 3) + diff2 := (data5 >> 3) & 0x3F + diff3 := (data5 >> 9) & 0x3F + icode0 := diff0 + 1850 + icode1 := diff1 + icode0 + 90 + icode2 := diff2 + icode1 + icode3 := diff3 + icode2 + 70 + if icode3 >= adcCalInitMin && icode3 <= adcCalInitMax { + return icode3, true + } + } + return 0, false +} + +// adc1DigiRefAtten3 reads the digital reference (DIGI_REF) for +// ADC1 at attenuation index 3 from EFUSE_BLK2 / RD_DATA7. This is +// similar to what the ESP-IDF ADC calibration HAL uses when present. +func (f *fuse) adc1DigiRefAtten3() (uint32, bool) { + f.triggerReadSequence() + _, _, blkVer := f.readBlock2Data4Data5() + if blkVer != efuseBlkVersionV1 { + return 0, false + } + data7 := f.readBlock2Data7() + diff3 := (data7 >> 1) & 0xFF + digiRef := diff3 + 900 + if digiRef < adcDigiRefMinMv || digiRef > adcDigiRefMaxMv { + return 0, false + } + return digiRef, true +} + +// triggerReadSequence performs one eFuse read operation using the +// controller's timing/opcode sequence. This roughly corresponds to +// the low-level logic in the ESP-IDF eFuse HAL (see efuse_ll_* in +// the IDF sources and the "eFuse Manager" docs: +// https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/system/efuse.html). +func (f *fuse) triggerReadSequence() { + clk := (*volatile.Register32)(unsafe.Pointer(systemPeripClkEn0)) + clk.Set(clk.Get() | systemEfuseClkEnBit) + efuseClk := (*volatile.Register32)(unsafe.Pointer(efuseClkReg)) + efuseClk.Set(efuseClk.Get() | efuseClkEnBit) + dac := (*volatile.Register32)(unsafe.Pointer(efuseDacConfReg)) + dac.Set(0x28 | (0xFF << 9)) + (*volatile.Register32)(unsafe.Pointer(efuseWrTimConf1Reg)).Set(0x3000 << 8) + (*volatile.Register32)(unsafe.Pointer(efuseWrTimConf2Reg)).Set(0x190) + (*volatile.Register32)(unsafe.Pointer(efuseConfReg)).Set(efuseReadOpCode) + cmd := (*volatile.Register32)(unsafe.Pointer(efuseCmdReg)) + cmd.Set(1) + for cmd.Get()&1 != 0 { + } +} + +// readBlock2Data4Data5 reads the EFUSE_BLK2 data words that contain +// ADC calibration and version information. It returns RD_DATA4, +// RD_DATA5 and the decoded block version (BLK_VERSION). +// +// Layout is derived from the ESP32-S3 TRM and IDF eFuse tables. +func (f *fuse) readBlock2Data4Data5() (data4, data5 uint32, blkVer uint8) { + data4 = (*volatile.Register32)(unsafe.Pointer(efuseRdData4Reg)).Get() + data5 = (*volatile.Register32)(unsafe.Pointer(efuseRdData5Reg)).Get() + blkVer = uint8(data4 & 3) + return data4, data5, blkVer +} + +// readBlock2Data7 reads RD_DATA7 from EFUSE_BLK2, which for ADC +// calibration contains additional reference (DIGI_REF) data fields. +func (f *fuse) readBlock2Data7() uint32 { + return (*volatile.Register32)(unsafe.Pointer(efuseRdData7Reg)).Get() +} + +// readAdcCalibBlock2 triggers an eFuse read and returns the raw +// EFUSE_BLK2 words used for ADC calibration (RD_DATA4/5) along +// with the decoded block version. This is a small helper similar +// in spirit to the internal IDF helpers around EFUSE_BLK2. +func (f *fuse) readAdcCalibBlock2() (data4, data5 uint32, blkVer uint8) { + f.triggerReadSequence() + return f.readBlock2Data4Data5() +} diff --git a/src/machine/machine_esp32s3_i2c.go b/src/machine/machine_esp32s3_i2c.go new file mode 100644 index 0000000000..ce1337e899 --- /dev/null +++ b/src/machine/machine_esp32s3_i2c.go @@ -0,0 +1,35 @@ +//go:build esp32s3 + +package machine + +import ( + "device/esp" +) + +const ( + I2CEXT0_SCL_OUT_IDX = 89 + I2CEXT0_SDA_OUT_IDX = 90 + I2CEXT1_SCL_OUT_IDX = 91 + I2CEXT1_SDA_OUT_IDX = 92 +) + +var ( + I2C0 = &I2C{ + Bus: esp.I2C0, + funcSCL: I2CEXT0_SCL_OUT_IDX, + funcSDA: I2CEXT0_SDA_OUT_IDX, + useExt1: false, + } + I2C1 = &I2C{ + Bus: esp.I2C1, + funcSCL: I2CEXT1_SCL_OUT_IDX, + funcSDA: I2CEXT1_SDA_OUT_IDX, + useExt1: true, + } +) + +func initI2CExt1Clock() { + esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT1_RST(1) + esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT1_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT1_RST(0) +} diff --git a/src/machine/machine_esp32s3_pwm.go b/src/machine/machine_esp32s3_pwm.go new file mode 100644 index 0000000000..f4015a5f95 --- /dev/null +++ b/src/machine/machine_esp32s3_pwm.go @@ -0,0 +1,226 @@ +//go:build esp32s3 + +package machine + +import "device/esp" + +// LEDC PWM for ESP32-S3: 4 timers (PWM0–PWM3), 8 channels per timer; each timer has its own frequency. +// Range: frequency from a few Hz up to ~40 MHz (at 1-bit resolution); duty resolution 1–15 bits +// (higher frequency gives lower resolution). Clock source: APB 80 MHz. Low-speed mode only. +// Duty must not equal 2^resolution (overflow risk). See ESP-IDF LEDC driver, TRM LED PWM Controller. + +// GPIO matrix output signal indices for LEDC (soc/gpio_sig_map.h) +const ( + LEDC_LS_SIG_OUT0_IDX = 73 +) + +const ledcChannelsS3 = 8 + +var ( + PWM0 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsS3, timerNum: 0} + PWM1 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsS3, timerNum: 1} + PWM2 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsS3, timerNum: 2} + PWM3 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsS3, timerNum: 3} +) + +// chanOp implements LEDC low-speed channel ops for ESP32-S3 (channels 0–7). +func (pwm *LEDCPWM) chanOp(ch uint8, op ledcChanOp, duty uint32, inverting bool) { + invVal := uint32(0) + if inverting { + invVal = 1 + } + switch ch { + case 0: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH0_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH0_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH0_CONF0_IDLE_LV(0) + esp.LEDC.SetCH0_HPOINT_HPOINT(0) + esp.LEDC.SetCH0_DUTY_DUTY(0) + esp.LEDC.SetCH0_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH0_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH0_CONF1_DUTY_INC(1) + esp.LEDC.SetCH0_CONF1_DUTY_START(1) + esp.LEDC.SetCH0_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH0_DUTY_DUTY(duty) + esp.LEDC.SetCH0_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH0_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH0_CONF1_DUTY_INC(1) + esp.LEDC.SetCH0_CONF1_DUTY_START(1) + esp.LEDC.SetCH0_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH0_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH0_CONF0_IDLE_LV(invVal) + } + case 1: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH1_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH1_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH1_CONF0_IDLE_LV(0) + esp.LEDC.SetCH1_HPOINT_HPOINT(0) + esp.LEDC.SetCH1_DUTY_DUTY(0) + esp.LEDC.SetCH1_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH1_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH1_CONF1_DUTY_INC(1) + esp.LEDC.SetCH1_CONF1_DUTY_START(1) + esp.LEDC.SetCH1_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH1_DUTY_DUTY(duty) + esp.LEDC.SetCH1_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH1_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH1_CONF1_DUTY_INC(1) + esp.LEDC.SetCH1_CONF1_DUTY_START(1) + esp.LEDC.SetCH1_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH1_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH1_CONF0_IDLE_LV(invVal) + } + case 2: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH2_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH2_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH2_CONF0_IDLE_LV(0) + esp.LEDC.SetCH2_HPOINT_HPOINT(0) + esp.LEDC.SetCH2_DUTY_DUTY(0) + esp.LEDC.SetCH2_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH2_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH2_CONF1_DUTY_INC(1) + esp.LEDC.SetCH2_CONF1_DUTY_START(1) + esp.LEDC.SetCH2_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH2_DUTY_DUTY(duty) + esp.LEDC.SetCH2_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH2_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH2_CONF1_DUTY_INC(1) + esp.LEDC.SetCH2_CONF1_DUTY_START(1) + esp.LEDC.SetCH2_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH2_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH2_CONF0_IDLE_LV(invVal) + } + case 3: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH3_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH3_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH3_CONF0_IDLE_LV(0) + esp.LEDC.SetCH3_HPOINT_HPOINT(0) + esp.LEDC.SetCH3_DUTY_DUTY(0) + esp.LEDC.SetCH3_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH3_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH3_CONF1_DUTY_INC(1) + esp.LEDC.SetCH3_CONF1_DUTY_START(1) + esp.LEDC.SetCH3_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH3_DUTY_DUTY(duty) + esp.LEDC.SetCH3_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH3_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH3_CONF1_DUTY_INC(1) + esp.LEDC.SetCH3_CONF1_DUTY_START(1) + esp.LEDC.SetCH3_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH3_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH3_CONF0_IDLE_LV(invVal) + } + case 4: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH4_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH4_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH4_CONF0_IDLE_LV(0) + esp.LEDC.SetCH4_HPOINT_HPOINT(0) + esp.LEDC.SetCH4_DUTY_DUTY(0) + esp.LEDC.SetCH4_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH4_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH4_CONF1_DUTY_INC(1) + esp.LEDC.SetCH4_CONF1_DUTY_START(1) + esp.LEDC.SetCH4_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH4_DUTY_DUTY(duty) + esp.LEDC.SetCH4_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH4_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH4_CONF1_DUTY_INC(1) + esp.LEDC.SetCH4_CONF1_DUTY_START(1) + esp.LEDC.SetCH4_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH4_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH4_CONF0_IDLE_LV(invVal) + } + case 5: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH5_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH5_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH5_CONF0_IDLE_LV(0) + esp.LEDC.SetCH5_HPOINT_HPOINT(0) + esp.LEDC.SetCH5_DUTY_DUTY(0) + esp.LEDC.SetCH5_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH5_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH5_CONF1_DUTY_INC(1) + esp.LEDC.SetCH5_CONF1_DUTY_START(1) + esp.LEDC.SetCH5_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH5_DUTY_DUTY(duty) + esp.LEDC.SetCH5_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH5_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH5_CONF1_DUTY_INC(1) + esp.LEDC.SetCH5_CONF1_DUTY_START(1) + esp.LEDC.SetCH5_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH5_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH5_CONF0_IDLE_LV(invVal) + } + case 6: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH6_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH6_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH6_CONF0_IDLE_LV(0) + esp.LEDC.SetCH6_HPOINT_HPOINT(0) + esp.LEDC.SetCH6_DUTY_DUTY(0) + esp.LEDC.SetCH6_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH6_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH6_CONF1_DUTY_INC(1) + esp.LEDC.SetCH6_CONF1_DUTY_START(1) + esp.LEDC.SetCH6_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH6_DUTY_DUTY(duty) + esp.LEDC.SetCH6_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH6_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH6_CONF1_DUTY_INC(1) + esp.LEDC.SetCH6_CONF1_DUTY_START(1) + esp.LEDC.SetCH6_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH6_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH6_CONF0_IDLE_LV(invVal) + } + case 7: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH7_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH7_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH7_CONF0_IDLE_LV(0) + esp.LEDC.SetCH7_HPOINT_HPOINT(0) + esp.LEDC.SetCH7_DUTY_DUTY(0) + esp.LEDC.SetCH7_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH7_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH7_CONF1_DUTY_INC(1) + esp.LEDC.SetCH7_CONF1_DUTY_START(1) + esp.LEDC.SetCH7_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH7_DUTY_DUTY(duty) + esp.LEDC.SetCH7_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH7_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH7_CONF1_DUTY_INC(1) + esp.LEDC.SetCH7_CONF1_DUTY_START(1) + esp.LEDC.SetCH7_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH7_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH7_CONF0_IDLE_LV(invVal) + } + } +} diff --git a/src/machine/machine_esp32s3_spi.go b/src/machine/machine_esp32s3_spi.go new file mode 100644 index 0000000000..fb320f5ed1 --- /dev/null +++ b/src/machine/machine_esp32s3_spi.go @@ -0,0 +1,364 @@ +//go:build esp32s3 + +package machine + +// ESP32-S3 SPI support based on ESP-IDF HAL +// Simple but correct implementation following spi_ll.h +// SPI0 = hardware SPI2 (FSPI), SPI1 = hardware SPI3 (HSPI) +// https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/spi_master.html + +import ( + "device/esp" + "errors" + "runtime/volatile" + "unsafe" +) + +const ( + // ESP32-S3 PLL clock frequency (same as ESP32-C3) + pplClockFreq = 80e6 + + // Default SPI frequency - maximum safe speed + SPI_DEFAULT_FREQUENCY = 80e6 // 80MHz +) + +const ( + // IO MUX function number for SPI direct connection + SPI_IOMUX_FUNC = 4 +) + +// ESP32-S3 GPIO Matrix signal indices for SPI - CORRECTED from ESP-IDF gpio_sig_map.h +const ( + // SPI2 (FSPI) signals - Hardware SPI2 - CORRECT VALUES from ESP-IDF + SPI2_CLK_OUT_IDX = uint32(101) // FSPICLK_OUT_IDX + SPI2_CLK_IN_IDX = uint32(101) // FSPICLK_IN_IDX + SPI2_Q_OUT_IDX = uint32(102) // FSPIQ_OUT_IDX (MISO) + SPI2_Q_IN_IDX = uint32(102) // FSPIQ_IN_IDX + SPI2_D_OUT_IDX = uint32(103) // FSPID_OUT_IDX (MOSI) + SPI2_D_IN_IDX = uint32(103) // FSPID_IN_IDX + SPI2_CS0_OUT_IDX = uint32(110) // FSPICS0_OUT_IDX + + // SPI3 (HSPI) signals - Hardware SPI3 - CORRECTED from ESP-IDF gpio_sig_map.h + // Source: /esp-idf/components/soc/esp32s3/include/soc/gpio_sig_map.h + SPI3_CLK_OUT_IDX = uint32(66) // Line 136: SPI3_CLK_OUT_IDX + SPI3_CLK_IN_IDX = uint32(66) // Line 135: SPI3_CLK_IN_IDX + SPI3_Q_OUT_IDX = uint32(67) // Line 138: SPI3_Q_OUT_IDX (MISO) + SPI3_Q_IN_IDX = uint32(67) // Line 137: SPI3_Q_IN_IDX + SPI3_D_OUT_IDX = uint32(68) // Line 140: SPI3_D_OUT_IDX (MOSI) + SPI3_D_IN_IDX = uint32(68) // Line 139: SPI3_D_IN_IDX + SPI3_CS0_OUT_IDX = uint32(71) // Line 146: SPI3_CS0_OUT_IDX +) + +type SPI struct { + Bus interface{} + busID uint8 +} + +var ( + SPI0 = &SPI{Bus: esp.SPI2, busID: 2} // Primary SPI (FSPI) + SPI1 = &SPI{Bus: esp.SPI3, busID: 3} // Secondary SPI (HSPI) +) + +// Configure and make the SPI peripheral ready to use. +// Implementation following ESP-IDF HAL with GPIO Matrix routing +func (spi *SPI) Configure(config SPIConfig) error { + // Set default + if config.Frequency == 0 { + config.Frequency = SPI_DEFAULT_FREQUENCY + } + + switch spi.busID { + case 2: // SPI2 (FSPI) + if config.SCK == 0 { + config.SCK = SPI1_SCK_PIN + } + if config.SDO == 0 { + config.SDO = SPI1_MOSI_PIN + } + if config.SDI == 0 { + config.SDI = SPI1_MISO_PIN + } + case 3: // SPI3 (HSPI) + if config.SCK == 0 { + config.SCK = SPI2_SCK_PIN + } + if config.SDO == 0 { + config.SDO = SPI2_MOSI_PIN + } + if config.SDI == 0 { + config.SDI = SPI2_MISO_PIN + } + default: + } + + // Get GPIO Matrix signal indices for this SPI bus + var sckOutIdx, mosiOutIdx, misoInIdx, csOutIdx uint32 + switch spi.busID { + case 2: // SPI2 (FSPI) + sckOutIdx = SPI2_CLK_OUT_IDX + mosiOutIdx = SPI2_D_OUT_IDX + misoInIdx = SPI2_Q_IN_IDX + csOutIdx = SPI2_CS0_OUT_IDX + case 3: // SPI3 (HSPI) + sckOutIdx = SPI3_CLK_OUT_IDX + mosiOutIdx = SPI3_D_OUT_IDX + misoInIdx = SPI3_Q_IN_IDX + csOutIdx = SPI3_CS0_OUT_IDX + default: + return ErrInvalidSPIBus + } + + // Check if we can use IO MUX direct connection for better performance + if isDefaultSPIPins(spi.busID, config) { + // Use IO MUX direct connection - better signal quality and performance + // Configure pins using IO MUX direct connection (SPI function) + if config.SCK != NoPin { + config.SCK.configure(PinConfig{Mode: PinOutput}, SPI_IOMUX_FUNC) + } + if config.SDO != NoPin { + config.SDO.configure(PinConfig{Mode: PinOutput}, SPI_IOMUX_FUNC) + } + if config.SDI != NoPin { + config.SDI.configure(PinConfig{Mode: PinInput}, SPI_IOMUX_FUNC) + } + if config.CS != NoPin { + config.CS.configure(PinConfig{Mode: PinOutput}, SPI_IOMUX_FUNC) + } + } else { + // Use GPIO Matrix routing - more flexible but slightly slower + // Configure SDI (MISO) pin + if config.SDI != NoPin { + config.SDI.Configure(PinConfig{Mode: PinInput}) + inFunc(misoInIdx).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.SDI)) + } + + // Configure SDO (MOSI) pin + if config.SDO != NoPin { + config.SDO.Configure(PinConfig{Mode: PinOutput}) + config.SDO.outFunc().Set(mosiOutIdx) + } + + // Configure SCK (Clock) pin + if config.SCK != NoPin { + config.SCK.Configure(PinConfig{Mode: PinOutput}) + config.SCK.outFunc().Set(sckOutIdx) + } + + // Configure CS (Chip Select) pin + if config.CS != NoPin { + config.CS.Configure(PinConfig{Mode: PinOutput}) + config.CS.outFunc().Set(csOutIdx) + } + } + + // Enable peripheral clock and reset + // Without bootloader, we need to be more explicit about clock initialization + switch spi.busID { + case 2: // Hardware SPI2 (FSPI) + esp.SYSTEM.SetPERIP_CLK_EN0_SPI2_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(1) + esp.SYSTEM.SetPERIP_RST_EN0_SPI2_RST(0) + case 3: // Hardware SPI3 (HSPI) + esp.SYSTEM.SetPERIP_CLK_EN0_SPI3_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_SPI3_RST(1) + esp.SYSTEM.SetPERIP_RST_EN0_SPI3_RST(0) + } + + // Get bus handle - both SPI2 and SPI3 use SPI2_Type + bus, ok := spi.Bus.(*esp.SPI2_Type) + if !ok { + return ErrInvalidSPIBus + } + + // Reset timing: cs_setup_time = 0, cs_hold_time = 0 + bus.USER1.Set(0) + + // Use all 64 bytes of the buffer + bus.SetUSER_USR_MISO_HIGHPART(0) + bus.SetUSER_USR_MOSI_HIGHPART(0) + + // Disable unneeded interrupts and clear all USER bits first + bus.SLAVE.Set(0) + bus.USER.Set(0) + + // Clear other important registers like ESP32-C3 + bus.MISC.Set(0) + bus.CTRL.Set(0) + bus.CLOCK.Set(0) + + // Clear data buffers like ESP32-C3 + bus.W0.Set(0) + bus.W1.Set(0) + bus.W2.Set(0) + bus.W3.Set(0) + + // Configure master clock gate - CRITICAL: need CLK_EN bit! + bus.SetCLK_GATE_CLK_EN(1) // Enable basic SPI clock (bit 0) + bus.SetCLK_GATE_MST_CLK_ACTIVE(1) // Enable master clock (bit 1) + bus.SetCLK_GATE_MST_CLK_SEL(1) // Select master clock (bit 2) + + // Configure DMA following ESP-IDF HAL + // Reset DMA configuration + bus.DMA_CONF.Set(0) + // Set DMA segment transaction clear enable bits + bus.SetDMA_CONF_SLV_TX_SEG_TRANS_CLR_EN(1) + bus.SetDMA_CONF_SLV_RX_SEG_TRANS_CLR_EN(1) + // dma_seg_trans_en = 0 (already 0 from DMA_CONF.Set(0)) + + // Configure master mode + bus.SetUSER_USR_MOSI(1) // Enable MOSI + bus.SetUSER_USR_MISO(1) // Enable MISO + bus.SetUSER_DOUTDIN(1) // Full-duplex mode + bus.SetCTRL_WR_BIT_ORDER(0) // MSB first + bus.SetCTRL_RD_BIT_ORDER(0) // MSB first + + // CRITICAL: Enable clock output (from working test) + bus.SetMISC_CK_DIS(0) // Enable CLK output - THIS IS KEY! + + // Configure SPI mode (CPOL/CPHA) following ESP-IDF HAL + switch config.Mode { + case Mode0: + // CPOL=0, CPHA=0 (default) + case Mode1: + bus.SetUSER_CK_OUT_EDGE(1) // CPHA=1 + case Mode2: + bus.SetMISC_CK_IDLE_EDGE(1) // CPOL=1 + bus.SetUSER_CK_OUT_EDGE(1) // CPHA=1 + case Mode3: + bus.SetMISC_CK_IDLE_EDGE(1) // CPOL=1 + } + + // Configure SPI bus clock using ESP32-C3 algorithm for better accuracy + bus.CLOCK.Set(freqToClockDiv(config.Frequency)) + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. +// Implementation following ESP-IDF HAL spi_ll_user_start with proper USER register setup +func (spi *SPI) Transfer(w byte) (byte, error) { + // Both SPI2 and SPI3 use SPI2_Type + bus, ok := spi.Bus.(*esp.SPI2_Type) + if !ok { + return 0, errors.New("invalid SPI bus type") + } + + // Set transfer length (8 bits = 7 in register) + bus.SetMS_DLEN_MS_DATA_BITLEN(7) + + // Clear any pending interrupt flags BEFORE starting transaction + bus.SetDMA_INT_CLR_TRANS_DONE_INT_CLR(1) + + // Write data to buffer (use W0 register) + bus.W0.Set(uint32(w)) + + // CRITICAL: Apply configuration before transmission (like ESP-IDF spi_ll_apply_config) + bus.SetCMD_UPDATE(1) + for bus.GetCMD_UPDATE() != 0 { + // Wait for config to be applied + } + + // Start transaction following ESP-IDF HAL spi_ll_user_start + bus.SetCMD_USR(1) + + // Wait for completion using CMD_USR flag (like ESP32-C3 approach) + // Hardware clears CMD_USR when transaction is complete + timeout := 100000 + for bus.GetCMD_USR() != 0 && timeout > 0 { + timeout-- + // Wait for CMD_USR to be cleared by hardware + } + + if timeout == 0 { + return 0, errors.New("SPI transfer timeout") + } + + // Read received data from W0 register + result := byte(bus.W0.Get() & 0xFF) + return result, nil +} + +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// This is accomplished by sending zero bits if r is bigger than w or discarding +// the incoming data if w is bigger than r. +// Optimized implementation ported from ESP32-C3 for better performance. +func (spi *SPI) Tx(w, r []byte) error { + toTransfer := len(w) + if len(r) > toTransfer { + toTransfer = len(r) + } + + // Get bus handle - both SPI2 and SPI3 use SPI2_Type + bus, ok := spi.Bus.(*esp.SPI2_Type) + if !ok { + return ErrInvalidSPIBus + } + + for toTransfer > 0 { + // Chunk 64 bytes at a time. + chunkSize := toTransfer + if chunkSize > 64 { + chunkSize = 64 + } + + // Fill tx buffer. + transferWords := (*[16]volatile.Register32)(unsafe.Add(unsafe.Pointer(&bus.W0), 0)) + spiTxFillBuffer(transferWords, w) + + // Do the transfer. + bus.SetMS_DLEN_MS_DATA_BITLEN(uint32(chunkSize)*8 - 1) + + bus.SetCMD_UPDATE(1) + for bus.GetCMD_UPDATE() != 0 { + } + + bus.SetCMD_USR(1) + for bus.GetCMD_USR() != 0 { + } + + // Read rx buffer. + rxSize := chunkSize + if rxSize > len(r) { + rxSize = len(r) + } + for i := 0; i < rxSize; i++ { + r[i] = byte(transferWords[i/4].Get() >> ((i % 4) * 8)) + } + + // Cut off some part of the output buffer so the next iteration we will + // only send the remaining bytes. + if len(w) < chunkSize { + w = nil + } else { + w = w[chunkSize:] + } + if len(r) < chunkSize { + r = nil + } else { + r = r[chunkSize:] + } + toTransfer -= chunkSize + } + + return nil +} + +// isDefaultSPIPins checks if the given pins match the default SPI pin configuration +// that supports IO MUX direct connection for better performance +func isDefaultSPIPins(busID uint8, config SPIConfig) bool { + switch busID { + case 2: // SPI2 (FSPI) + return config.SCK == SPI1_SCK_PIN && + config.SDO == SPI1_MOSI_PIN && + config.SDI == SPI1_MISO_PIN && + (config.CS == SPI1_CS_PIN || config.CS == NoPin) + case 3: // SPI3 (HSPI) + return config.SCK == SPI2_SCK_PIN && + config.SDO == SPI2_MOSI_PIN && + config.SDI == SPI2_MISO_PIN && + (config.CS == SPI2_CS_PIN || config.CS == NoPin) + default: + return false + } +} diff --git a/src/machine/machine_esp32xx_adc.go b/src/machine/machine_esp32xx_adc.go new file mode 100644 index 0000000000..30fec86cbe --- /dev/null +++ b/src/machine/machine_esp32xx_adc.go @@ -0,0 +1,235 @@ +//go:build esp32s3 || (esp32c3 && !m5stamp_c3) + +// Shared regI2C-based ADC calibration helpers for ESP32-S3 and ESP32-C3. +// +// The internal I2C bus ("regI2C") and SAR ADC trim register layout are +// identical across both chips; chip-specific differences (host ID, DREF +// init value, calibration iterations) are captured in the regI2C struct +// fields, keeping each target file free of duplicated low-level code. + +package machine + +import ( + "device/esp" + "runtime/volatile" + "unsafe" +) + +// regI2C wraps the internal I2C bus used for SAR ADC calibration registers. +// Fields hold chip-specific parameters that differ between ESP32-S3 and ESP32-C3. +type regI2C struct { + // hostID is the I2C_SAR_ADC_HOSTID (1 for ESP32-S3, 0 for ESP32-C3). + hostID uint8 + // drefInit is the DREF reference value written during calibrationInit + // (4 for ESP32-S3, 1 for ESP32-C3). + drefInit uint8 +} + +// SAR ADC I2C register layout constants shared across ESP32-S3 and ESP32-C3. +// Source: ESP-IDF soc/regi2c_saradc.h +const ( + // i2cSarADC is the I2C_SAR_ADC block address on the internal bus. + i2cSarADC = uint8(0x69) + + // DREF (reference) bitfields for ADC1 and ADC2. + adc1DrefAddr = uint8(0x2) + adc1DrefMSB = uint8(6) + adc1DrefLSB = uint8(4) + adc2DrefAddr = uint8(0x5) + adc2DrefMSB = uint8(6) + adc2DrefLSB = uint8(4) + + // ENCAL_GND: routes internal ground to ADC input during self-calibration. + adc1EncalGndAddr = uint8(0x7) + adc1EncalGndMSB = uint8(5) + adc1EncalGndLSB = uint8(5) + adc2EncalGndAddr = uint8(0x7) + adc2EncalGndMSB = uint8(7) + adc2EncalGndLSB = uint8(7) + + // INIT_CODE (offset) high/low for ADC1 and ADC2. + adc1InitCodeHighAddr = uint8(0x1) + adc1InitCodeHighMSB = uint8(3) + adc1InitCodeHighLSB = uint8(0) + adc1InitCodeLowAddr = uint8(0x0) + adc1InitCodeLowMSB = uint8(7) + adc1InitCodeLowLSB = uint8(0) + adc2InitCodeHighAddr = uint8(0x4) + adc2InitCodeHighMSB = uint8(3) + adc2InitCodeHighLSB = uint8(0) + adc2InitCodeLowAddr = uint8(0x3) + adc2InitCodeLowMSB = uint8(7) + adc2InitCodeLowLSB = uint8(0) + + // ANA_CONFIG / ANA_CONFIG2: enable analog SAR I2C domain. + anaConfigReg = uintptr(0x6000E044) + i2cSarEnMask = uint32(1 << 18) + anaConfig2Reg = uintptr(0x6000E048) + anaSarCfg2En = uint32(1 << 16) + + // REGI2C master control register and helper masks. + i2cMstCtrlReg = uintptr(0x6000E000) + i2cMstBusyBit = uint32(1 << 25) + i2cMstWrCntlBit = uint32(1 << 24) + i2cMstDataMask = uint32(0xFF << 16) + i2cMstDataShift = 16 + i2cMstBusyTimeout = 10000 + + // adcCalOffsetRange is the binary search upper bound (12-bit full scale). + adcCalOffsetRange = uint32(4096) + + // adcCalMaxIterations is the maximum number of calibration iterations + // supported by calibrateBinarySearch. Must be >= max(S3=10, C3=15). + adcCalMaxIterations = 16 +) + +// waitIdle polls the REGI2C master BUSY bit until it clears or a +// timeout expires, matching the busy-wait helper in ESP-IDF's regi2c_ctrl.c. +func (r regI2C) waitIdle(reg *volatile.Register32) bool { + for i := 0; i < i2cMstBusyTimeout; i++ { + if reg.Get()&i2cMstBusyBit == 0 { + return true + } + } + return false +} + +// writeMask is a software implementation of the IDF REGI2C_WRITE_MASK macro. +// It reads the current byte at regAddr on the SAR ADC I2C block, updates +// only the [msb:lsb] bitfield, and writes it back via the internal I2C master. +func (r regI2C) writeMask(regAddr, msb, lsb, data uint8) { + reg := (*volatile.Register32)(unsafe.Pointer(i2cMstCtrlReg)) + if !r.waitIdle(reg) { + return + } + reg.Set(uint32(i2cSarADC) | uint32(regAddr)<<8) + if !r.waitIdle(reg) { + return + } + cur := (reg.Get() & i2cMstDataMask) >> i2cMstDataShift + mask := uint32(1<<(msb-lsb+1)-1) << lsb + cur &^= mask + cur |= uint32(data&(1<<(msb-lsb+1)-1)) << lsb + reg.Set(uint32(i2cSarADC) | uint32(regAddr)<<8 | i2cMstWrCntlBit | (cur<> 8) + lsb := uint8(param & 0xFF) + if adcN == 0 { + r.writeMask(adc1InitCodeHighAddr, adc1InitCodeHighMSB, adc1InitCodeHighLSB, msb) + r.writeMask(adc1InitCodeLowAddr, adc1InitCodeLowMSB, adc1InitCodeLowLSB, lsb) + } else { + r.writeMask(adc2InitCodeHighAddr, adc2InitCodeHighMSB, adc2InitCodeHighLSB, msb) + r.writeMask(adc2InitCodeLowAddr, adc2InitCodeLowMSB, adc2InitCodeLowLSB, lsb) + } +} + +// calibrateBinarySearch runs the ADC self-calibration binary search loop. +// It performs 'iterations' rounds of binary search to find the optimal offset +// code, drops the min/max outliers, and returns the rounded mean of the +// remaining values. This matches adc_hal_self_calibration() in ESP-IDF. +// +// The readADC callback must perform a single conversion using the target's +// oneshot path (SENS or APB_SARADC) and return the raw 12-bit result. +// During calibration, ENCAL_GND is active so the ADC reads its internal ground. +func (r regI2C) calibrateBinarySearch(adcN uint8, iterations int, readADC func() uint32) uint32 { + if iterations > adcCalMaxIterations { + iterations = adcCalMaxIterations + } + var codeList [adcCalMaxIterations]uint32 + var codeSum uint32 + + for rpt := 0; rpt < iterations; rpt++ { + codeH := adcCalOffsetRange + codeL := uint32(0) + chkCode := (codeH + codeL) / 2 + r.setCalibrationParam(adcN, chkCode) + selfCal := readADC() + + for codeH-codeL > 1 { + if selfCal == 0 { + codeH = chkCode + } else { + codeL = chkCode + } + chkCode = (codeH + codeL) / 2 + r.setCalibrationParam(adcN, chkCode) + selfCal = readADC() + if codeH-codeL == 1 { + chkCode++ + r.setCalibrationParam(adcN, chkCode) + selfCal = readADC() + } + } + codeList[rpt] = chkCode + codeSum += chkCode + } + + // Drop min and max outliers, then average with IDF-style rounding. + codeMin := codeList[0] + codeMax := codeList[0] + for i := 0; i < iterations; i++ { + if codeList[i] < codeMin { + codeMin = codeList[i] + } + if codeList[i] > codeMax { + codeMax = codeList[i] + } + } + remaining := codeSum - codeMax - codeMin + divisor := uint32(iterations - 2) + finalCode := remaining / divisor + if remaining%divisor >= 4 { + finalCode++ + } + + return finalCode +} diff --git a/src/machine/machine_esp32xx_i2c.go b/src/machine/machine_esp32xx_i2c.go new file mode 100644 index 0000000000..0ccae5ccee --- /dev/null +++ b/src/machine/machine_esp32xx_i2c.go @@ -0,0 +1,336 @@ +//go:build (esp32c3 || esp32s3) && !m5stamp_c3 + +package machine + +import ( + "device/esp" + "runtime/volatile" + "unsafe" +) + +type I2C struct { + Bus *esp.I2C_Type + funcSCL, funcSDA uint32 + useExt1 bool + txCmdBuf [8]i2cCommand +} + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 // in Hz + SCL Pin + SDA Pin +} + +const ( + clkXTAL = 0 + clkFOSC = 1 + clkXTALFrequency = uint32(40e6) + clkFOSCFrequency = uint32(17.5e6) + i2cClkSourceFrequency = clkXTALFrequency + i2cClkSource = clkXTAL +) + +func (i2c *I2C) Configure(config I2CConfig) error { + if config.Frequency == 0 { + config.Frequency = 400 * KHz + } + if config.SCL == 0 { + config.SCL = SCL_PIN + } + if config.SDA == 0 { + config.SDA = SDA_PIN + } + + i2c.initClock(config) + i2c.initNoiseFilter() + i2c.initPins(config) + i2c.initFrequency(config) + i2c.startMaster() + return nil +} + +//go:inline +func (i2c *I2C) initClock(config I2CConfig) { + if !i2c.useExt1 { + esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1) + esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0) + } else { + initI2CExt1Clock() + } + // disable interrupts + i2c.Bus.INT_CLR.Set(0x3fff) + i2c.Bus.INT_ENA.ClearBits(0x3fff) + + i2c.Bus.SetCLK_CONF_SCLK_SEL(i2cClkSource) + i2c.Bus.SetCLK_CONF_SCLK_ACTIVE(1) + i2c.Bus.SetCLK_CONF_SCLK_DIV_NUM(i2cClkSourceFrequency / (config.Frequency * 1024)) + i2c.Bus.SetCTR_CLK_EN(1) +} + +//go:inline +func (i2c *I2C) initNoiseFilter() { + i2c.Bus.FILTER_CFG.Set(0x377) +} + +//go:inline +func (i2c *I2C) initPins(config I2CConfig) { + config.SDA.configure(PinConfig{Mode: PinOutput}, i2c.funcSDA) + inFunc(i2c.funcSDA).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.SDA)< 50000 { + sclWaitHigh = halfCycle / 8 // compensate the time when freq > 50K + } + sclHigh := halfCycle - sclWaitHigh + // SDA + sdaHold := halfCycle / 4 + sda_sample := halfCycle / 2 + setup := halfCycle + hold := halfCycle + + i2c.Bus.SetSCL_LOW_PERIOD(sclLow - 1) + i2c.Bus.SetSCL_HIGH_PERIOD(sclHigh) + i2c.Bus.SetSCL_HIGH_PERIOD_SCL_WAIT_HIGH_PERIOD(25) + i2c.Bus.SetSCL_RSTART_SETUP_TIME(setup) + i2c.Bus.SetSCL_STOP_SETUP_TIME(setup) + i2c.Bus.SetSCL_START_HOLD_TIME(hold - 1) + i2c.Bus.SetSCL_STOP_HOLD_TIME(hold - 1) + i2c.Bus.SetSDA_SAMPLE_TIME(sda_sample) + i2c.Bus.SetSDA_HOLD_TIME(sdaHold) +} + +//go:inline +func (i2c *I2C) startMaster() { + // FIFO mode for data + i2c.Bus.SetFIFO_CONF_NONFIFO_EN(0) + // Reset TX & RX buffers + i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(1) + i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(0) + i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(1) + i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(0) + // set timeout value + i2c.Bus.TO.Set(0x10) + // enable master mode + i2c.Bus.CTR.Set(0x113) + i2c.Bus.SetCTR_CONF_UPGATE(1) + i2c.resetMaster() +} + +//go:inline +func (i2c *I2C) resetMaster() { + // reset FSM + i2c.Bus.SetCTR_FSM_RST(1) + // clear the bus + i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_NUM(9) + i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_EN(1) + i2c.Bus.SetSCL_STRETCH_CONF_SLAVE_SCL_STRETCH_EN(1) + i2c.Bus.SetCTR_CONF_UPGATE(1) + i2c.Bus.FILTER_CFG.Set(0x377) + // wait for SCL_RST_SLV_EN + for i2c.Bus.GetSCL_SP_CONF_SCL_RST_SLV_EN() != 0 { + } + i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_NUM(0) +} + +type i2cCommandType = uint32 +type i2cAck = uint32 + +const ( + i2cCMD_RSTART i2cCommandType = 6 << 11 + i2cCMD_WRITE i2cCommandType = 1<<11 | 1<<8 // WRITE + ack_check_en + i2cCMD_READ i2cCommandType = 3<<11 | 1<<8 // READ + ack_check_en + i2cCMD_READLAST i2cCommandType = 3<<11 | 5<<8 // READ + ack_check_en + NACK + i2cCMD_STOP i2cCommandType = 2 << 11 + i2cCMD_END i2cCommandType = 4 << 11 +) + +type i2cCommand struct { + cmd i2cCommandType + data []byte + head int +} + +//go:linkname nanotime runtime.nanotime +func nanotime() int64 + +func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { + const intMask = esp.I2C_INT_STATUS_END_DETECT_INT_ST_Msk | esp.I2C_INT_STATUS_TRANS_COMPLETE_INT_ST_Msk | esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk | esp.I2C_INT_STATUS_NACK_INT_ST_Msk + i2c.Bus.INT_CLR.SetBits(intMask) + i2c.Bus.INT_ENA.SetBits(intMask) + i2c.Bus.SetCTR_CONF_UPGATE(1) + + defer func() { + i2c.Bus.INT_CLR.SetBits(intMask) + i2c.Bus.INT_ENA.ClearBits(intMask) + }() + + timeoutNS := int64(timeoutMS) * 1000000 + needAddress := true + needRestart := false + readLast := false + var readTo []byte + for cmdIdx, reg := 0, &i2c.Bus.COMD0; cmdIdx < len(cmd); { + c := &cmd[cmdIdx] + + switch c.cmd { + case i2cCMD_RSTART: + reg.Set(i2cCMD_RSTART) + reg = nextAddress(reg) + cmdIdx++ + + case i2cCMD_WRITE: + count := 32 + if needAddress { + needAddress = false + i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr) & 0x7f) << 1) + count-- + i2c.Bus.SLAVE_ADDR.Set(uint32(addr)) + i2c.Bus.SetCTR_CONF_UPGATE(1) + } + for ; count > 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 { + i2c.Bus.SetDATA_FIFO_RDATA(uint32(c.data[c.head])) + } + reg.Set(i2cCMD_WRITE | uint32(32-count)) + reg = nextAddress(reg) + + if c.head < len(c.data) { + reg.Set(i2cCMD_END) + reg = nil + } else { + cmdIdx++ + } + needRestart = true + + case i2cCMD_READ: + if needAddress { + needAddress = false + i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) + i2c.Bus.SLAVE_ADDR.Set(uint32(addr)) + reg.Set(i2cCMD_WRITE | 1) + reg = nextAddress(reg) + } + if needRestart { + // We need to send RESTART again after i2cCMD_WRITE. + reg.Set(i2cCMD_RSTART) + + reg = nextAddress(reg) + reg.Set(i2cCMD_WRITE | 1) + + reg = nextAddress(reg) + i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) + needRestart = false + } + count := 32 + bytes := len(c.data) - c.head + // Only last byte in sequence must be sent with ACK set to 1 to indicate end of data. + split := bytes <= count + if split { + bytes-- + } + if bytes > 32 { + bytes = 32 + } + if bytes > 0 { + reg.Set(i2cCMD_READ | uint32(bytes)) + reg = nextAddress(reg) + } + if split { + readLast = true + reg.Set(i2cCMD_READLAST | 1) + reg = nextAddress(reg) + readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte + cmdIdx++ + } else { + reg.Set(i2cCMD_END) + readTo = c.data[c.head : c.head+bytes] + reg = nil + } + + case i2cCMD_STOP: + reg.Set(i2cCMD_STOP) + reg = nil + cmdIdx++ + } + if reg == nil { + // transmit now + i2c.Bus.SetCTR_CONF_UPGATE(1) + i2c.Bus.SetCTR_TRANS_START(1) + end := nanotime() + timeoutNS + var mask uint32 + for mask = i2c.Bus.INT_STATUS.Get(); mask&intMask == 0; mask = i2c.Bus.INT_STATUS.Get() { + if nanotime() > end { + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + } + switch { + case mask&esp.I2C_INT_STATUS_NACK_INT_ST_Msk != 0 && !readLast: + return errI2CAckExpected + case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0: + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + i2c.Bus.INT_CLR.SetBits(intMask) + for i := 0; i < len(readTo); i++ { + readTo[i] = byte(i2c.Bus.GetDATA_FIFO_RDATA() & 0xff) + c.head++ + } + readTo = nil + reg = &i2c.Bus.COMD0 + } + } + return nil +} + +// Tx does a single I2C transaction at the specified address. +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { + // timeout in milliseconds. + const timeout = 40 // 40ms is a reasonable time for a real-time system. + + cmd := i2c.txCmdBuf[:0] + cmd = append(cmd, i2cCommand{cmd: i2cCMD_RSTART}) + if len(w) > 0 { + cmd = append(cmd, i2cCommand{cmd: i2cCMD_WRITE, data: w}) + } + if len(r) > 0 { + cmd = append(cmd, i2cCommand{cmd: i2cCMD_READ, data: r}) + } + cmd = append(cmd, i2cCommand{cmd: i2cCMD_STOP}) + + return i2c.transmit(addr, cmd, timeout) +} + +func (i2c *I2C) SetBaudRate(br uint32) error { + return nil +} + +func nextAddress(reg *volatile.Register32) *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4)) +} diff --git a/src/machine/machine_esp32xx_pwm.go b/src/machine/machine_esp32xx_pwm.go new file mode 100644 index 0000000000..03a63d60be --- /dev/null +++ b/src/machine/machine_esp32xx_pwm.go @@ -0,0 +1,179 @@ +//go:build esp32c3 || esp32s3 + +// PWM on ESP32-C3/S3 uses the LEDC (LED Control) peripheral, low-speed mode only. +// One timer drives multiple channels; each channel has its own duty, shared frequency. +// Pin routing is via GPIO matrix (SigOutBase + channel index). +// +// Channel config (chanOp) follows the hardware contract from: +// - ESP-IDF: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html +// (timer config → channel config → duty + update_duty). +// - SVD (e.g. lib/cmsis-svd/data/Espressif/esp32s3.svd): CONF0.PARA_UP "updates +// HPOINT, DUTY_START, SIG_OUT_EN, TIMER_SEL, DUTY_NUM, DUTY_CYCLE, DUTY_SCALE, +// DUTY_INC for channel and is auto-cleared by hardware"; CONF1.DUTY_START "other +// CONF1 fields take effect when this bit is set to 1". + +package machine + +import ( + "device/esp" + "errors" +) + +const ledcApbClock = 80_000000 + +const ledcDutyFracBits = 4 // DUTY register has 4 fractional bits; write value<<4 + +const ledcDividerFracBits = 8 // Clock divider register = actual_divider * 256 + +var errPWMNoChannel = errors.New("pwm: no free channel") + +type LEDCPWM struct { + SigOutBase uint32 // GPIO matrix signal index for channel 0 (e.g. 73 on S3, 45 on C3) + NumChannels uint8 + timerNum uint8 // 0–3: which LEDC timer (frequency) this PWM uses + dutyRes uint8 + configured bool + channelPin [8]Pin +} + +type ledcChanOp uint8 + +const ( + ledcChanOpInit ledcChanOp = iota // initial per-channel setup (timer, enable, HPOINT/DUTY/CONF1, PARA_UP) + ledcChanOpSetDuty // update duty and latch it (DUTY + CONF1 + PARA_UP) + ledcChanOpSetInvert // change idle level (IDLE_LV) +) + +func (pwm *LEDCPWM) Configure(config PWMConfig) error { + // Enable LEDC clock and release reset (SYSTEM perip_clk_en0 / perip_rst_en0). + esp.SYSTEM.SetPERIP_RST_EN0_LEDC_RST(1) + esp.SYSTEM.SetPERIP_CLK_EN0_LEDC_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_LEDC_RST(0) + + // LEDC global: APB clock source, enable internal clock. + esp.LEDC.SetCONF_APB_CLK_SEL(1) + esp.LEDC.SetCONF_CLK_EN(1) + + period := config.Period + if period == 0 { + period = 1_000_000 + } + freq := uint64(1e9) / period + dutyRes := uint8(10) + switch { + case freq < 100: + dutyRes = 14 + case freq < 1000: + dutyRes = 12 + case freq > 100_000: + dutyRes = 8 + } + + // Timer divider: period_ns = (2^dutyRes * divActual/256) / 80MHz * 1e9 => divReg = divActual<<8. + divActual := ledcApbClock / (uint32(freq) * (1 << dutyRes)) + if divActual == 0 { + divActual = 1 + } + divReg := divActual << ledcDividerFracBits + if divReg > 0x3ffff { + return ErrPWMPeriodTooLong + } + + // Selected timer: resolution, divider, no pause, reset then latch config with PARA_UP. + pwm.setTimerConf(dutyRes, divReg) + + pwm.dutyRes = dutyRes + pwm.configured = true + for i := range pwm.channelPin { + pwm.channelPin[i] = NoPin + } + return nil +} + +func (pwm *LEDCPWM) setTimerConf(dutyRes uint8, divReg uint32) { + t := pwm.timerNum + switch t { + case 0: + esp.LEDC.SetTIMER0_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetTIMER0_CONF_CLK_DIV(divReg) + esp.LEDC.SetTIMER0_CONF_TICK_SEL(0) + esp.LEDC.SetTIMER0_CONF_PAUSE(0) + esp.LEDC.SetTIMER0_CONF_RST(1) + esp.LEDC.SetTIMER0_CONF_RST(0) + esp.LEDC.SetTIMER0_CONF_PARA_UP(1) + case 1: + esp.LEDC.SetTIMER1_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetTIMER1_CONF_CLK_DIV(divReg) + esp.LEDC.SetTIMER1_CONF_TICK_SEL(0) + esp.LEDC.SetTIMER1_CONF_PAUSE(0) + esp.LEDC.SetTIMER1_CONF_RST(1) + esp.LEDC.SetTIMER1_CONF_RST(0) + esp.LEDC.SetTIMER1_CONF_PARA_UP(1) + case 2: + esp.LEDC.SetTIMER2_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetTIMER2_CONF_CLK_DIV(divReg) + esp.LEDC.SetTIMER2_CONF_TICK_SEL(0) + esp.LEDC.SetTIMER2_CONF_PAUSE(0) + esp.LEDC.SetTIMER2_CONF_RST(1) + esp.LEDC.SetTIMER2_CONF_RST(0) + esp.LEDC.SetTIMER2_CONF_PARA_UP(1) + case 3: + esp.LEDC.SetTIMER3_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetTIMER3_CONF_CLK_DIV(divReg) + esp.LEDC.SetTIMER3_CONF_TICK_SEL(0) + esp.LEDC.SetTIMER3_CONF_PAUSE(0) + esp.LEDC.SetTIMER3_CONF_RST(1) + esp.LEDC.SetTIMER3_CONF_RST(0) + esp.LEDC.SetTIMER3_CONF_PARA_UP(1) + } +} + +func (pwm *LEDCPWM) Channel(pin Pin) (uint8, error) { + if !pwm.configured { + return 0, errors.New("pwm: not configured") + } + if pin == NoPin { + return 0, ErrInvalidOutputPin + } + var ch uint8 + for ch = 0; ch < pwm.NumChannels; ch++ { + if pwm.channelPin[ch] == NoPin { + break + } + } + if ch >= pwm.NumChannels { + return 0, errPWMNoChannel + } + + pwm.channelPin[ch] = pin + signal := pwm.SigOutBase + uint32(ch) + pin.configure(PinConfig{Mode: PinOutput}, signal) // GPIO matrix: pin <- LEDC_LS_SIG_OUTn + pwm.chanOp(ch, ledcChanOpInit, 0, false) + return ch, nil +} + +func (pwm *LEDCPWM) Set(channel uint8, value uint32) { + if channel >= pwm.NumChannels { + return + } + top := uint32(1< top { + value = top + } + dutyVal := value << ledcDutyFracBits + pwm.chanOp(channel, ledcChanOpSetDuty, dutyVal, false) +} + +func (pwm *LEDCPWM) Top() uint32 { + if !pwm.configured { + return 0 + } + return uint32(1<= pwm.NumChannels { + return + } + pwm.chanOp(channel, ledcChanOpSetInvert, 0, inverting) +} diff --git a/src/machine/machine_esp32xx_spi.go b/src/machine/machine_esp32xx_spi.go new file mode 100644 index 0000000000..755b4bec67 --- /dev/null +++ b/src/machine/machine_esp32xx_spi.go @@ -0,0 +1,101 @@ +//go:build esp32s3 || esp32c3 + +package machine + +import ( + "runtime/volatile" +) + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + SCK Pin // Serial Clock + SDO Pin // Serial Data Out (MOSI) + SDI Pin // Serial Data In (MISO) + CS Pin // Chip Select (optional) + LSBFirst bool // MSB is default + Mode uint8 // Mode0 is default +} + +// freqToClockDiv computes the SPI bus clock divider register value. +// SPI peripherals on ESP32-C3 and ESP32-S3 are clocked from the APB bus +// (pplClockFreq, 80 MHz on both chips). +func freqToClockDiv(hz uint32) uint32 { + if hz >= pplClockFreq { // maximum frequency + return 1 << 31 + } + if hz < (pplClockFreq / (16 * 64)) { // minimum frequency + return 15<<18 | 63<<12 | 31<<6 | 63 // pre=15, n=63 + } + + // Iterate all 16 prescaler options looking for an exact match + // or the smallest error. + var bestPre, bestN, bestErr uint32 + bestN = 1 + bestErr = 0xffffffff + q := uint32(float32(pplClockFreq)/float32(hz) + float32(0.5)) + for p := uint32(0); p < 16; p++ { + n := q/(p+1) - 1 + if n < 1 { // prescaler became too large, stop enum + break + } + if n > 63 { // prescaler too small, skip to next + continue + } + + freq := pplClockFreq / ((p + 1) * (n + 1)) + if freq == hz { // exact match + return p<<18 | n<<12 | (n/2)<<6 | n + } + + var err uint32 + if freq < hz { + err = hz - freq + } else { + err = freq - hz + } + if err < bestErr { + bestErr = err + bestPre = p + bestN = n + } + } + + return bestPre<<18 | bestN<<12 | (bestN/2)<<6 | bestN +} + +// spiTxFillBuffer writes data from w into the 16-word (64-byte) SPI +// hardware transfer buffer. Unused words are zeroed so that no stale +// data from a previous transfer is sent when w is shorter than 64 bytes. +func spiTxFillBuffer(buf *[16]volatile.Register32, w []byte) { + if len(w) >= 64 { + // We can fill the entire 64-byte transfer buffer with data. + // This loop is slightly faster than the loop below. + for i := 0; i < 16; i++ { + word := uint32(w[i*4]) | uint32(w[i*4+1])<<8 | uint32(w[i*4+2])<<16 | uint32(w[i*4+3])<<24 + buf[i].Set(word) + } + } else { + // We can't fill the entire transfer buffer, so we need to be a bit + // more careful. + // Note that parts of the transfer buffer that aren't used still + // need to be set to zero, otherwise we might be transferring + // garbage from a previous transmission if w is smaller than r. + for i := 0; i < 16; i++ { + var word uint32 + if i*4+3 < len(w) { + word |= uint32(w[i*4+3]) << 24 + } + if i*4+2 < len(w) { + word |= uint32(w[i*4+2]) << 16 + } + if i*4+1 < len(w) { + word |= uint32(w[i*4+1]) << 8 + } + if i*4+0 < len(w) { + word |= uint32(w[i*4+0]) << 0 + } + buf[i].Set(word) + } + } +} diff --git a/src/machine/machine_esp32xx_usb.go b/src/machine/machine_esp32xx_usb.go new file mode 100644 index 0000000000..5066fab677 --- /dev/null +++ b/src/machine/machine_esp32xx_usb.go @@ -0,0 +1,226 @@ +//go:build esp32s3 + +package machine + +import ( + "device/esp" + "errors" + "machine/usb" + "machine/usb/descriptor" + "runtime/interrupt" +) + +// USB Serial/JTAG Controller +// See esp32-s3_technical_reference_manual_en.pdf +// +// The ESP32-S3 has a built-in USB Serial/JTAG controller that provides a +// CDC-ACM serial port. The USB protocol and enumeration are handled entirely +// in hardware; software only reads/writes the EP1 FIFO. + +const cpuInterruptFromUSB = 8 + +// flushTimeout is the maximum number of busy-wait iterations in flush(). +// Prevents hanging when no USB host is connected. +const flushTimeout = 200000 + +type USB_DEVICE struct { + Bus *esp.USB_DEVICE_Type + Buffer *RingBuffer +} + +var ( + _USBCDC = &USB_DEVICE{ + Bus: esp.USB_DEVICE, + Buffer: NewRingBuffer(), + } + + USBCDC Serialer = _USBCDC +) + +var ( + errUSBWrongSize = errors.New("USB: invalid write size") + errUSBCouldNotWriteAllData = errors.New("USB: could not write all data") +) + +type Serialer interface { + WriteByte(c byte) error + Write(data []byte) (n int, err error) + Configure(config UARTConfig) error + Buffered() int + ReadByte() (byte, error) + DTR() bool + RTS() bool +} + +var usbConfigured bool + +// USBDevice provides a stub USB device for the ESP32-S3. The hardware +// only supports a fixed-function CDC-ACM serial port, so the programmable +// USB device features are no-ops. +type USBDevice struct { + initcomplete bool + InitEndpointComplete bool +} + +var USBDev = &USBDevice{} + +func (dev *USBDevice) SetStallEPIn(ep uint32) {} +func (dev *USBDevice) SetStallEPOut(ep uint32) {} +func (dev *USBDevice) ClearStallEPIn(ep uint32) {} +func (dev *USBDevice) ClearStallEPOut(ep uint32) {} + +// initUSB is intentionally empty — the interp phase evaluates init() +// functions at compile time and cannot access hardware registers. +// Actual hardware setup is deferred to the first Configure() call. +func initUSB() {} + +// usbHandleInterrupt is the top-level interrupt handler passed to +// interrupt.New. It must be a plain function (not a closure) because +// interrupt.New is a compiler intrinsic that does not support closures. +func usbHandleInterrupt(interrupt.Interrupt) { + _USBCDC.handleInterrupt() +} + +// Configure initialises the USB Serial/JTAG controller. +func (usbdev *USB_DEVICE) Configure(config UARTConfig) error { + if usbConfigured { + return nil + } + usbConfigured = true + + // Enable the USB_DEVICE peripheral clock. + esp.SYSTEM.SetPERIP_CLK_EN1_USB_DEVICE_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN1_USB_DEVICE_RST(0) + + // Clear any pending interrupts, then enable the RX interrupt. + usbdev.Bus.INT_CLR.Set(0xFFFFFFFF) + usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1) + + // Map the USB_DEVICE peripheral interrupt to CPU interrupt line. + esp.INTERRUPT_CORE0.SetUSB_DEVICE_INT_MAP(cpuInterruptFromUSB) + + _ = interrupt.New(cpuInterruptFromUSB, usbHandleInterrupt).Enable() + + return nil +} + +// ensureConfigured triggers lazy initialization on first use. +func (usbdev *USB_DEVICE) ensureConfigured() { + if !usbConfigured { + usbdev.Configure(UARTConfig{}) + } +} + +// handleInterrupt is called from the CPU interrupt vector when the USB +// peripheral raises an interrupt. Disable INT_ENA to prevent the +// level-triggered interrupt from re-asserting immediately (data may +// still be in the FIFO). Buffered() re-enables after draining. +func (usbdev *USB_DEVICE) handleInterrupt() { + usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(0) + usbdev.Bus.SetINT_CLR_SERIAL_OUT_RECV_PKT_INT_CLR(1) +} + +func (usbdev *USB_DEVICE) WriteByte(c byte) error { + usbdev.ensureConfigured() + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + // FIFO full — try flushing first, then recheck. + usbdev.flush() + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + return errUSBCouldNotWriteAllData + } + } + + // Use EP1.Set() (direct store) instead of SetEP1_RDWR_BYTE which + // does a read-modify-write — the read side-effect pops a byte from + // the RX FIFO. + usbdev.Bus.EP1.Set(uint32(c)) + usbdev.flush() + + return nil +} + +func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) { + usbdev.ensureConfigured() + if len(data) == 0 { + return 0, nil + } + + for i, c := range data { + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + if i > 0 { + usbdev.flush() + } + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + return i, errUSBCouldNotWriteAllData + } + } + usbdev.Bus.EP1.Set(uint32(c)) + } + + usbdev.flush() + return len(data), nil +} + +// Buffered returns the number of bytes waiting in the receive ring buffer. +// It drains any data sitting in the hardware FIFO and re-enables the +// peripheral-level USB interrupt (which the ISR disables to prevent a +// level-triggered interrupt storm). +func (usbdev *USB_DEVICE) Buffered() int { + usbdev.ensureConfigured() + // Drain the hardware FIFO into the ring buffer. + for usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 { + b := byte(usbdev.Bus.EP1.Get()) + usbdev.Buffer.Put(b) + } + // Clear pending flags and re-enable the RX interrupt at the peripheral level. + usbdev.Bus.INT_CLR.Set(0xFFFFFFFF) + usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1) + return int(usbdev.Buffer.Used()) +} + +// ReadByte returns a byte from the receive ring buffer. +func (usbdev *USB_DEVICE) ReadByte() (byte, error) { + b, ok := usbdev.Buffer.Get() + if !ok { + return 0, nil + } + return b, nil +} + +func (usbdev *USB_DEVICE) DTR() bool { + return false +} + +func (usbdev *USB_DEVICE) RTS() bool { + return false +} + +// flush signals WR_DONE and waits (with timeout) for the hardware to +// consume the data. A timeout prevents hanging when no USB host is present. +func (usbdev *USB_DEVICE) flush() { + usbdev.Bus.SetEP1_CONF_WR_DONE(1) + for i := 0; i < flushTimeout; i++ { + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() != 0 { + return + } + } +} + +// The ESP32-S3 USB Serial/JTAG controller is fixed-function hardware. +// It only provides a CDC-ACM serial port; the USB protocol and endpoint +// configuration are handled entirely in silicon. The functions below +// are no-op stubs so that higher-level USB packages (HID, MIDI, …) +// compile, but they cannot add real endpoints on this hardware. + +// ConfigureUSBEndpoint is a no-op on ESP32-S3. +func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) { +} + +// SendZlp is a no-op on ESP32-S3. +func SendZlp() { +} + +// SendUSBInPacket is a no-op on ESP32-S3. +func SendUSBInPacket(ep uint32, data []byte) bool { + return false +} diff --git a/src/machine/machine_rp2040_rom.go b/src/machine/machine_rp2040_rom.go index 5541e2a9bf..0f7c6819a5 100644 --- a/src/machine/machine_rp2040_rom.go +++ b/src/machine/machine_rp2040_rom.go @@ -115,7 +115,7 @@ void ram_func flash_cs_force(bool high) { // &ioqspi_hw->io[1].ctrl uint32_t *addr = (uint32_t*)(IO_QSPI_BASE + (1 * 8) + 4); - *addr = ((*addr) & !IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS) + *addr = ((*addr) & ~IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS) | (field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB); } diff --git a/src/machine/machine_rp2_adc.go b/src/machine/machine_rp2_adc.go index e0d6a459a9..12ff152dc9 100644 --- a/src/machine/machine_rp2_adc.go +++ b/src/machine/machine_rp2_adc.go @@ -19,10 +19,8 @@ var adcAref uint32 // InitADC resets the ADC peripheral. func InitADC() { - rp.RESETS.RESET.SetBits(rp.RESETS_RESET_ADC) - rp.RESETS.RESET.ClearBits(rp.RESETS_RESET_ADC) - for !rp.RESETS.RESET_DONE.HasBits(rp.RESETS_RESET_ADC) { - } + resetBlock(rp.RESETS_RESET_ADC) + unresetBlockWait(rp.RESETS_RESET_ADC) // enable ADC rp.ADC.CS.Set(rp.ADC_CS_EN) adcAref = 3300 diff --git a/src/machine/machine_rp2_i2c.go b/src/machine/machine_rp2_i2c.go index 54a5e5357b..e4de7a783b 100644 --- a/src/machine/machine_rp2_i2c.go +++ b/src/machine/machine_rp2_i2c.go @@ -259,10 +259,7 @@ func (i2c *I2C) init(config I2CConfig) error { //go:inline func (i2c *I2C) reset() { resetVal := i2c.deinit() - rp.RESETS.RESET.ClearBits(resetVal) - // Wait until reset is done. - for !rp.RESETS.RESET_DONE.HasBits(resetVal) { - } + unresetBlockWait(resetVal) } // deinit sets reset bit for I2C. Must call reset to reenable I2C after deinit. @@ -276,15 +273,13 @@ func (i2c *I2C) deinit() (resetVal uint32) { resetVal = rp.RESETS_RESET_I2C1 } // Perform I2C reset. - rp.RESETS.RESET.SetBits(resetVal) + resetBlock(resetVal) return resetVal } // tx performs blocking write followed by read to I2C bus. func (i2c *I2C) tx(addr uint8, tx, rx []byte) (err error) { - const timeout_us = 4_000 - deadline := ticks() + timeout_us if addr >= 0x80 || isReservedI2CAddr(addr) { return errInvalidTgtAddr } @@ -295,6 +290,14 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte) (err error) { return nil } + // Base 4ms for small register pokes. + // Add per-byte budget. 100us/byte is conservative at 400kHz and still ok at 100kHz for modest sizes. + timeout_us := uint64(4_000) + uint64(txlen+rxlen)*100 + // Cap so it doesn't go insane: + timeout_us = min(timeout_us, 500_000) + + deadline := ticks() + timeout_us + err = i2c.disable() if err != nil { return err diff --git a/src/machine/machine_rp2_spi.go b/src/machine/machine_rp2_spi.go index 75e4f86b7b..f3fb256f61 100644 --- a/src/machine/machine_rp2_spi.go +++ b/src/machine/machine_rp2_spi.go @@ -212,10 +212,7 @@ func (spi *SPI) setFormat(mode uint8) { //go:inline func (spi *SPI) reset() { resetVal := spi.deinit() - rp.RESETS.RESET.ClearBits(resetVal) - // Wait until reset is done. - for !rp.RESETS.RESET_DONE.HasBits(resetVal) { - } + unresetBlockWait(resetVal) } //go:inline @@ -227,7 +224,7 @@ func (spi *SPI) deinit() (resetVal uint32) { resetVal = rp.RESETS_RESET_SPI1 } // Perform SPI reset. - rp.RESETS.RESET.SetBits(resetVal) + resetBlock(resetVal) return resetVal } diff --git a/src/machine/machine_rp2_uart.go b/src/machine/machine_rp2_uart.go index 872418a766..37e2ca9c2a 100644 --- a/src/machine/machine_rp2_uart.go +++ b/src/machine/machine_rp2_uart.go @@ -73,6 +73,27 @@ func (uart *UART) Configure(config UARTConfig) error { return nil } +// Close the UART and disable its interrupt/power use. +func (uart *UART) Close() error { + uart.Interrupt.Disable() + + // Disable UART. + uart.Bus.UARTCR.ClearBits(rp.UART0_UARTCR_UARTEN) + + var resetVal uint32 + switch { + case uart.Bus == rp.UART0: + resetVal = rp.RESETS_RESET_UART0 + case uart.Bus == rp.UART1: + resetVal = rp.RESETS_RESET_UART1 + } + + // reset UART + resetBlock(resetVal) + + return nil +} + // SetBaudRate sets the baudrate to be used for the UART. func (uart *UART) SetBaudRate(br uint32) { div := 8 * CPUFrequency() / br @@ -148,10 +169,8 @@ func initUART(uart *UART) { } // reset UART - rp.RESETS.RESET.SetBits(resetVal) - rp.RESETS.RESET.ClearBits(resetVal) - for !rp.RESETS.RESET_DONE.HasBits(resetVal) { - } + resetBlock(resetVal) + unresetBlockWait(resetVal) } // handleInterrupt should be called from the appropriate interrupt handler for diff --git a/src/machine/machine_stm32_adc_g0.go b/src/machine/machine_stm32_adc_g0.go index d8cca3c8df..7d7150ade1 100644 --- a/src/machine/machine_stm32_adc_g0.go +++ b/src/machine/machine_stm32_adc_g0.go @@ -123,7 +123,7 @@ func (a ADC) Get() uint16 { // Select the channel to convert using CHSELR // CHSELR uses a bitfield where bit N = 1 enables channel N - stm32.ADC.CHSELR.Set(1 << ch) + stm32.ADC.CHSELR0.Set(1 << ch) // Wait for channel configuration ready for stm32.ADC.GetISR_CCRDY() == 0 { diff --git a/src/machine/machine_stm32_adc_u5.go b/src/machine/machine_stm32_adc_u5.go new file mode 100644 index 0000000000..6620185ffb --- /dev/null +++ b/src/machine/machine_stm32_adc_u5.go @@ -0,0 +1,131 @@ +//go:build stm32u5 + +package machine + +import ( + "device/stm32" + "unsafe" +) + +// InitADC initializes the registers needed for ADC1. +func InitADC() { + // Enable ADC bus clock. + enableAltFuncClock(unsafe.Pointer(stm32.ADC1)) + + // Declare VDDA analog supply valid. Without ASV the ADC LDO cannot start. + stm32.PWR.SVMCR.SetBits(stm32.PWR_SVMCR_ASV) + + // Set ADC12_Common CCR: synchronous clock HCLK/1. + // ADC12_Common is mapped as ADC_Type; CCR at offset 0x08 = .CR field. + stm32.ADC12_Common.CR.Set(1 << 16) // CKMODE=01 + + // Exit deep power-down mode. + stm32.ADC1.CR.ClearBits(stm32.ADC_CR_DEEPPWD) + + // Enable internal voltage regulator and wait for LDO ready. + stm32.ADC1.CR.SetBits(stm32.ADC_CR_ADVREGEN) + for !stm32.ADC1.ISR.HasBits(stm32.ADC_ISR_LDORDY) { + } + + // Calibrate ADC (single-ended). + stm32.ADC1.CR.SetBits(stm32.ADC_CR_ADCAL) + for stm32.ADC1.CR.HasBits(stm32.ADC_CR_ADCAL) { + } + + // 12-bit resolution, overwrite DR on overrun. + stm32.ADC1.CFGR1.Set(stm32.ADC_CFGR1_RES_TwelveBit<= used { + return r.buf[pos : pos+used], nil + } + return r.buf[pos:], r.buf[:used-contig] +} + +// Discard marks numBytes as read, advancing the read position. +// Panics if numBytes exceeds Used (indicates a race violating SPSC) +func (r *ring512) Discard(numBytes uint32) { + if numBytes == 0 { + return + } + head, tail := r.lims() + used := head - tail + if numBytes > used { + panic("ring: discard exceeds used") + } + r.tail.Store(tail + numBytes) +} + +// Put writes data into the ring buffer. Returns true if all data was +// written, false if insufficient free space (no partial writes). +func (r *ring512) Put(data []byte) bool { + wlen := uint32(len(data)) + if wlen == 0 { + return true + } + head, tail := r.lims() + used := head - tail + free := uint32(ringBufLen) - used + if wlen > free { + return false + } + pos := head & ringMask + n := uint32(copy(r.buf[pos:], data)) + if n < wlen { + copy(r.buf[:], data[n:]) + } + r.head.Store(head + wlen) + return true +} + +func (r *ring512) lims() (head, tail uint32) { + return r.head.Load(), r.tail.Load() +} diff --git a/src/machine/usb/cdc/ring_test.go b/src/machine/usb/cdc/ring_test.go new file mode 100644 index 0000000000..e79581a5b4 --- /dev/null +++ b/src/machine/usb/cdc/ring_test.go @@ -0,0 +1,658 @@ +package cdc + +import ( + "bytes" + "fmt" + "math/rand" + "sync" + "testing" +) + +// peekAll returns all readable data by concatenating both Peek segments. +func peekAll(r *ring512) []byte { + d1, d2 := r.Peek() + if len(d2) == 0 { + return d1 + } + out := make([]byte, len(d1)+len(d2)) + copy(out, d1) + copy(out[len(d1):], d2) + return out +} + +// drain reads all data from the ring, verifying Peek length matches Used. +func drain(t *testing.T, r *ring512) []byte { + t.Helper() + d1, d2 := r.Peek() + n := uint32(len(d1) + len(d2)) + if n != r.Used() { + t.Fatalf("Peek returned %d bytes but Used()=%d", n, r.Used()) + } + var out []byte + out = append(out, d1...) + out = append(out, d2...) + r.Discard(n) + return out +} + +// --- Basic Functionality --- + +func TestRing512_PutPeekDiscard(t *testing.T) { + var r ring512 + data := []byte("hello world") + if !r.Put(data) { + t.Fatal("Put failed on empty buffer") + } + got := peekAll(&r) + if !bytes.Equal(got, data) { + t.Fatalf("Peek = %q, want %q", got, data) + } + if r.Used() != uint32(len(data)) { + t.Fatalf("Used = %d, want %d", r.Used(), len(data)) + } + r.Discard(uint32(len(data))) + if r.Used() != 0 { + t.Fatalf("Used after full discard = %d, want 0", r.Used()) + } + d1, d2 := r.Peek() + if d1 != nil || d2 != nil { + t.Fatalf("Peek after full discard = (%v, %v), want (nil, nil)", d1, d2) + } +} + +func TestRing512_Reset(t *testing.T) { + var r ring512 + r.Put([]byte("data")) + r.Reset() + if r.Used() != 0 { + t.Fatalf("Used after Reset = %d", r.Used()) + } + if r.Free() != 512 { + t.Fatalf("Free after Reset = %d", r.Free()) + } +} + +func TestRing512_PutEmpty(t *testing.T) { + var r ring512 + if !r.Put(nil) { + t.Fatal("Put nil should succeed") + } + if !r.Put([]byte{}) { + t.Fatal("Put empty slice should succeed") + } + if r.Used() != 0 { + t.Fatalf("Used = %d after empty puts", r.Used()) + } +} + +func TestRing512_PutFull(t *testing.T) { + var r ring512 + data := make([]byte, 512) + for i := range data { + data[i] = byte(i) + } + if !r.Put(data) { + t.Fatal("Put 512 bytes failed on empty buffer") + } + if r.Free() != 0 { + t.Fatalf("Free after filling = %d", r.Free()) + } + if r.Put([]byte{0x42}) { + t.Fatal("Put on full buffer should fail") + } + got := peekAll(&r) + if !bytes.Equal(got, data) { + t.Fatalf("Peek full buffer: got len %d, want 512", len(got)) + } +} + +func TestRing512_PutExactFit(t *testing.T) { + var r ring512 + data := make([]byte, 512) + for i := range data { + data[i] = byte(i) + } + if !r.Put(data) { + t.Fatal("Put exact fit failed") + } + if r.Used() != 512 { + t.Fatalf("Used = %d, want 512", r.Used()) + } + r.Discard(512) + if r.Used() != 0 { + t.Fatal("buffer not empty after discard all") + } +} + +// --- Full buffer with wrapped position --- + +func TestRing512_FullBufferWrapped(t *testing.T) { + var r ring512 + + r.Put(make([]byte, 200)) + r.Discard(100) // tail=100, head=200, used=100 + + free := r.Free() + if free != 412 { + t.Fatalf("Free = %d, want 412", free) + } + fill := make([]byte, free) + for i := range fill { + fill[i] = byte(i) + } + if !r.Put(fill) { + t.Fatalf("Put(%d) into %d free space failed", free, free) + } + if r.Used() != 512 { + t.Fatalf("Used = %d, want 512 (full)", r.Used()) + } + if r.Free() != 0 { + t.Fatalf("Free = %d, want 0 (full)", r.Free()) + } + drained := drain(t, &r) + if len(drained) != 512 { + t.Fatalf("drained %d bytes, want 512", len(drained)) + } +} + +// --- Wrapping Tests --- + +func TestRing512_Wrap(t *testing.T) { + var r ring512 + r.Put(make([]byte, 500)) + r.Discard(490) // tail=490, head=500, used=10 + + wrapData := make([]byte, 30) + for i := range wrapData { + wrapData[i] = byte(i + 100) + } + if !r.Put(wrapData) { + t.Fatal("wrapped Put failed") + } + if r.Used() != 40 { + t.Fatalf("Used = %d, want 40", r.Used()) + } + + d1, d2 := r.Peek() + if len(d1)+len(d2) != 40 { + t.Fatalf("Peek total = %d, want 40", len(d1)+len(d2)) + } + if d2 == nil { + t.Fatal("expected wrapped data in d2") + } + drained := drain(t, &r) + if len(drained) != 40 { + t.Fatalf("drained %d bytes, want 40", len(drained)) + } +} + +func TestRing512_WrapDataIntegrity(t *testing.T) { + var r ring512 + r.Put(make([]byte, 500)) + r.Discard(500) + + data := make([]byte, 100) + for i := range data { + data[i] = byte(i) + } + if !r.Put(data) { + t.Fatal("wrapped put failed") + } + got := drain(t, &r) + if !bytes.Equal(got, data) { + t.Fatal("data integrity failure across wrap") + } +} + +// --- Edge Cases --- + +func TestRing512_DiscardPartial(t *testing.T) { + var r ring512 + r.Put([]byte("abcdefgh")) + r.Discard(3) + got := peekAll(&r) + if !bytes.Equal(got, []byte("defgh")) { + t.Fatalf("after partial discard, Peek = %q, want %q", got, "defgh") + } +} + +func TestRing512_DiscardZero(t *testing.T) { + var r ring512 + r.Discard(0) + r.Put([]byte("hi")) + r.Discard(0) + if r.Used() != 2 { + t.Fatalf("Used = %d after zero discard", r.Used()) + } +} + +func TestRing512_DiscardPanicOnOverread(t *testing.T) { + var r ring512 + r.Put([]byte("hi")) + defer func() { + if rec := recover(); rec == nil { + t.Fatal("expected panic on over-discard, got none") + } + }() + r.Discard(100) +} + +func TestRing512_FreeUsedInvariant(t *testing.T) { + var r ring512 + check := func(label string) { + if r.Free()+r.Used() != 512 { + t.Fatalf("%s: Free(%d) + Used(%d) != 512", label, r.Free(), r.Used()) + } + } + check("empty") + r.Put(make([]byte, 200)) + check("after put 200") + r.Discard(50) + check("after discard 50") + r.Put(make([]byte, 362)) + check("after fill to full") + r.Discard(512) + check("after drain") +} + +func TestRing512_PutOversize(t *testing.T) { + var r ring512 + if r.Put(make([]byte, 513)) { + t.Fatal("Put(513) should fail on empty 512 buffer") + } + r.Put(make([]byte, 1)) + if r.Put(make([]byte, 512)) { + t.Fatal("Put(512) should fail with 1 byte used") + } +} + +func TestRing512_MultiplePutPeekDiscard(t *testing.T) { + var r ring512 + for i := 0; i < 2000; i++ { + msg := []byte(fmt.Sprintf("msg%04d", i)) + if !r.Put(msg) { + t.Fatalf("Put failed at iteration %d, Free=%d, Used=%d", i, r.Free(), r.Used()) + } + got := drain(t, &r) + if !bytes.Equal(got, msg) { + t.Fatalf("iter %d: got %q, want %q", i, got, msg) + } + } +} + +func TestRing512_HeadTailOverflow(t *testing.T) { + var r ring512 + near := uint32(0xFFFFFFFF - 100) + r.head.Store(near) + r.tail.Store(near) + + if r.Used() != 0 || r.Free() != 512 { + t.Fatalf("Used=%d Free=%d, want 0/512", r.Used(), r.Free()) + } + + for i := 0; i < 300; i++ { + data := []byte{byte(i), byte(i + 1), byte(i + 2)} + if !r.Put(data) { + t.Fatalf("Put failed at iter %d (head=%d tail=%d)", i, r.head.Load(), r.tail.Load()) + } + got := drain(t, &r) + if !bytes.Equal(got, data) { + t.Fatalf("iter %d: data mismatch", i) + } + } +} + +// --- Peek two-segment tests --- + +func TestRing512_PeekNoWrap(t *testing.T) { + var r ring512 + r.Put([]byte("hello")) + d1, d2 := r.Peek() + if !bytes.Equal(d1, []byte("hello")) { + t.Fatalf("d1 = %q, want %q", d1, "hello") + } + if d2 != nil { + t.Fatalf("d2 = %v, want nil", d2) + } +} + +func TestRing512_PeekWrapped(t *testing.T) { + var r ring512 + r.Put(make([]byte, 508)) + r.Discard(508) // tail=508, head=508 + + data := []byte("abcdefghij") // 10 bytes: 4 at end, 6 at start + if !r.Put(data) { + t.Fatal("put failed") + } + d1, d2 := r.Peek() + if len(d1) != 4 { + t.Fatalf("d1 len = %d, want 4", len(d1)) + } + if len(d2) != 6 { + t.Fatalf("d2 len = %d, want 6", len(d2)) + } + var got []byte + got = append(got, d1...) + got = append(got, d2...) + if !bytes.Equal(got, data) { + t.Fatalf("got %q, want %q", got, data) + } +} + +func TestRing512_PeekEmpty(t *testing.T) { + var r ring512 + d1, d2 := r.Peek() + if d1 != nil || d2 != nil { + t.Fatalf("Peek on empty = (%v, %v), want (nil, nil)", d1, d2) + } +} + +func TestRing512_PeekTotalEqualsUsed(t *testing.T) { + var r ring512 + // Test at many wrap positions. + for offset := 0; offset < 512; offset += 37 { + r.Reset() + if offset > 0 { + r.Put(make([]byte, offset)) + r.Discard(uint32(offset)) + } + sz := 200 + r.Put(make([]byte, sz)) + d1, d2 := r.Peek() + total := len(d1) + len(d2) + if total != sz { + t.Fatalf("offset=%d: Peek total=%d, want %d", offset, total, sz) + } + } +} + +// --- Concurrent SPSC Test --- + +func TestRing512_SPSC(t *testing.T) { + for trial := 0; trial < 20; trial++ { + var r ring512 + const totalBytes = 1 << 18 + produced := make([]byte, totalBytes) + for i := range produced { + produced[i] = byte(i + trial) + } + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + sent := 0 + for sent < totalBytes { + chunkSize := 1 + rand.Intn(128) + if sent+chunkSize > totalBytes { + chunkSize = totalBytes - sent + } + if r.Put(produced[sent : sent+chunkSize]) { + sent += chunkSize + } + } + }() + + consumed := make([]byte, 0, totalBytes) + go func() { + defer wg.Done() + for len(consumed) < totalBytes { + d1, d2 := r.Peek() + n := len(d1) + len(d2) + if n == 0 { + continue + } + consumed = append(consumed, d1...) + consumed = append(consumed, d2...) + r.Discard(uint32(n)) + } + }() + + wg.Wait() + if !bytes.Equal(consumed, produced) { + for i := range consumed { + if i >= len(produced) || consumed[i] != produced[i] { + t.Fatalf("trial %d: mismatch at byte %d", trial, i) + } + } + t.Fatalf("trial %d: length mismatch: got %d want %d", trial, len(consumed), len(produced)) + } + } +} + +func TestRing512_SPSCSmallChunks(t *testing.T) { + var r ring512 + const totalBytes = 1 << 16 + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + for i := 0; i < totalBytes; i++ { + for !r.Put([]byte{byte(i)}) { + } + } + }() + + consumed := make([]byte, 0, totalBytes) + go func() { + defer wg.Done() + for len(consumed) < totalBytes { + d1, d2 := r.Peek() + n := len(d1) + len(d2) + if n == 0 { + continue + } + consumed = append(consumed, d1...) + consumed = append(consumed, d2...) + r.Discard(uint32(n)) + } + }() + + wg.Wait() + for i, b := range consumed { + if b != byte(i) { + t.Fatalf("mismatch at %d: got %d want %d", i, b, byte(i)) + } + } +} + +// --- Fuzz Testing --- + +type refRing struct{ data []byte } + +func (r *refRing) Put(d []byte) bool { + if len(r.data)+len(d) > 512 { + return false + } + r.data = append(r.data, d...) + return true +} +func (r *refRing) Discard(n uint32) { r.data = r.data[n:] } +func (r *refRing) Used() uint32 { return uint32(len(r.data)) } + +// FuzzRing512 compares ring512 against a trivially correct reference. +func FuzzRing512(f *testing.F) { + f.Add([]byte{0, 10, 1, 5, 2, 0, 10, 1, 10}) + f.Add([]byte{0, 0}) + f.Add([]byte{0, 255, 0, 255, 1, 255, 1, 255}) + f.Add(bytes.Repeat([]byte{0, 64, 1, 64}, 50)) + f.Add([]byte{0, 200, 1, 100, 0, 156, 0, 156}) + + f.Fuzz(func(t *testing.T, ops []byte) { + var ring ring512 + var ref refRing + + i := 0 + for i+1 < len(ops) { + op := ops[i] % 4 + arg := ops[i+1] + i += 2 + + switch op { + case 0: // Put + size := int(arg) + if size > 512 { + size = 512 + } + data := make([]byte, size) + for j := range data { + data[j] = byte(j) + } + gotOK := ring.Put(data) + refOK := ref.Put(data) + if gotOK != refOK { + t.Fatalf("Put(%d): ring=%v ref=%v", size, gotOK, refOK) + } + + case 1: // Discard + used := ring.Used() + if used != ref.Used() { + t.Fatalf("Used mismatch: ring=%d ref=%d", used, ref.Used()) + } + if used == 0 { + continue + } + n := uint32(arg) % (used + 1) + ring.Discard(n) + ref.Discard(n) + + case 2: // Peek + verify + rUsed := ring.Used() + if rUsed != ref.Used() { + t.Fatalf("Used mismatch: ring=%d ref=%d", rUsed, ref.Used()) + } + if rUsed == 0 { + d1, d2 := ring.Peek() + if d1 != nil || d2 != nil { + t.Fatal("Peek non-nil on empty ring") + } + continue + } + got := peekAll(&ring) + if uint32(len(got)) != rUsed { + t.Fatalf("Peek returned %d bytes, Used=%d", len(got), rUsed) + } + if !bytes.Equal(got, ref.data) { + t.Fatal("Peek data mismatch") + } + + case 3: // Invariant + if ring.Free()+ring.Used() != 512 { + t.Fatalf("invariant: Free(%d)+Used(%d)!=512", ring.Free(), ring.Used()) + } + } + } + + if ring.Used() != ref.Used() { + t.Fatalf("final Used mismatch: ring=%d ref=%d", ring.Used(), ref.Used()) + } + }) +} + +// FuzzRing512_Op2 uses raw fuzz bytes as an operation stream with +// data integrity tracking. +func FuzzRing512_Op2(f *testing.F) { + f.Add([]byte{0, 255, 0, 255, 0, 2, 1, 255}) + f.Add([]byte{0, 200, 1, 100, 0, 255, 0, 157, 1, 255, 1, 255}) + seed := make([]byte, 40) + for i := range seed { + if i%4 < 2 { + seed[i] = 0 + } else { + seed[i] = 1 + } + if i%2 == 1 { + seed[i] = byte(3 + i%13) + } + } + f.Add(seed) + + f.Fuzz(func(t *testing.T, ops []byte) { + const buflen = 512 + const maxOps = 128 + var ring ring512 + var written []byte + totalRead := 0 + + i := 0 + nops := 0 + for i+1 < len(ops) && nops < maxOps { + op := ops[i] % 3 + sz := int(ops[i+1]) + i += 2 + nops++ + + switch op { + case 0: // Put + if sz == 0 { + continue + } + free := int(ring.Free()) + if sz > free { + sz = free + } + if sz == 0 { + continue + } + data := make([]byte, sz) + for j := range data { + data[j] = byte(len(written) + j) + } + if !ring.Put(data) { + t.Fatalf("Put(%d) failed with Free()=%d", sz, free) + } + written = append(written, data...) + + case 1: // Read + used := int(ring.Used()) + if used == 0 || sz == 0 { + continue + } + if sz > used { + sz = used + } + d1, d2 := ring.Peek() + // Concatenate and take sz bytes. + var got []byte + if sz <= len(d1) { + got = d1[:sz] + } else { + got = make([]byte, sz) + copy(got, d1) + copy(got[len(d1):], d2) + } + expect := written[totalRead : totalRead+sz] + if !bytes.Equal(got, expect) { + t.Fatalf("data mismatch at read offset %d", totalRead) + } + ring.Discard(uint32(sz)) + totalRead += sz + + case 2: // Reset + ring.Reset() + totalRead = len(written) + } + + if ring.Free()+ring.Used() != buflen { + t.Fatalf("invariant: Free(%d)+Used(%d)!=%d", ring.Free(), ring.Used(), buflen) + } + if int(ring.Used()) != len(written)-totalRead { + t.Fatalf("Used()=%d expected %d", ring.Used(), len(written)-totalRead) + } + } + + // Final drain. + d1, d2 := ring.Peek() + var remaining []byte + remaining = append(remaining, d1...) + remaining = append(remaining, d2...) + expect := written[totalRead:] + if !bytes.Equal(remaining, expect) { + t.Fatalf("final drain mismatch: got %d bytes, want %d", len(remaining), len(expect)) + } + }) +} diff --git a/src/machine/usb/cdc/usbcdc.go b/src/machine/usb/cdc/usbcdc.go index 5b5ffbf7c4..b9e68369a2 100644 --- a/src/machine/usb/cdc/usbcdc.go +++ b/src/machine/usb/cdc/usbcdc.go @@ -1,10 +1,13 @@ +//go:build baremetal + package cdc import ( "errors" "machine" "machine/usb" - "runtime/interrupt" + "sync/atomic" + _ "unsafe" ) var ( @@ -21,64 +24,56 @@ type cdcLineInfo struct { lineState uint8 } -// Read from the RX buffer. -func (usbcdc *USBCDC) Read(data []byte) (n int, err error) { - // check if RX buffer is empty - size := usbcdc.Buffered() - if size == 0 { - return 0, nil - } +// USBCDC is the USB CDC aka serial over USB interface. +type USBCDC struct { + tx ring512 + rx ring512 + inflight atomic.Uint32 + rbuf [1]byte + wbuf [1]byte +} - // Make sure we do not read more from buffer than the data slice can hold. - if len(data) < size { - size = len(data) - } +var ( + // USB is a USB CDC interface. + USB *USBCDC - // only read number of bytes used from buffer - for i := 0; i < size; i++ { - v, _ := usbcdc.ReadByte() - data[i] = v - } + usbLineInfo = cdcLineInfo{115200, 0x00, 0x00, 0x08, 0x00} +) - return size, nil +// Read from the RX buffer. +func (usbcdc *USBCDC) Read(data []byte) (n int, err error) { + data1, data2 := usbcdc.rx.Peek() + n += copy(data, data1) + n += copy(data[n:], data2) + usbcdc.rx.Discard(uint32(n)) + return n, nil } // ReadByte reads a single byte from the RX buffer. // If there is no data in the buffer, returns an error. func (usbcdc *USBCDC) ReadByte() (byte, error) { // check if RX buffer is empty - buf, ok := usbcdc.rxBuffer.Get() - if !ok { - return 0, ErrBufferEmpty + b, _ := usbcdc.rx.Peek() + if len(b) > 0 { + c := b[0] + usbcdc.rx.Discard(1) + return c, nil } - return buf, nil + return 0, ErrBufferEmpty } // Buffered returns the number of bytes currently stored in the RX buffer. func (usbcdc *USBCDC) Buffered() int { - return int(usbcdc.rxBuffer.Used()) + return int(usbcdc.rx.Used()) } // Receive handles adding data to the UART's data buffer. // Usually called by the IRQ handler for a machine. func (usbcdc *USBCDC) Receive(data byte) { - usbcdc.rxBuffer.Put(data) + usbcdc.rbuf[0] = data + usbcdc.rx.Put(usbcdc.rbuf[:]) } -// USBCDC is the USB CDC aka serial over USB interface. -type USBCDC struct { - rxBuffer *rxRingBuffer - txBuffer *txRingBuffer - waitTxc bool -} - -var ( - // USB is a USB CDC interface. - USB *USBCDC - - usbLineInfo = cdcLineInfo{115200, 0x00, 0x00, 0x08, 0x00} -) - // Configure the USB CDC interface. The config is here for compatibility with the UART interface. func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error { return nil @@ -86,32 +81,63 @@ func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error { // Flush flushes buffered data. func (usbcdc *USBCDC) Flush() { - mask := interrupt.Disable() - if b, ok := usbcdc.txBuffer.Get(); ok { - machine.SendUSBInPacket(cdcEndpointIn, b) - } else { - usbcdc.waitTxc = false + for usbcdc.tx.Used() > 0 { + gosched() } - interrupt.Restore(mask) } // Write data to the USBCDC. func (usbcdc *USBCDC) Write(data []byte) (n int, err error) { - if usbLineInfo.lineState > 0 { - mask := interrupt.Disable() - usbcdc.txBuffer.Put(data) - if !usbcdc.waitTxc { - usbcdc.waitTxc = true - usbcdc.Flush() + n = len(data) + if usbLineInfo.lineState <= 0 { + return n, nil + } + for len(data) > 0 { + tosend := min(len(data), int(usbcdc.tx.Free())) + if tosend == 0 { + gosched() + continue } - interrupt.Restore(mask) + usbcdc.tx.Put(data[:tosend]) + data = data[tosend:] + usbcdc.kickTx() } - return len(data), nil + return n, nil +} + +// kickTx starts a transfer if none is in flight. Called from main context only. +func (usbcdc *USBCDC) kickTx() { + if usbcdc.inflight.Load() > 0 { + return // txhandler will chain the next packet. + } + usbcdc.sendFromRing() +} + +func (usbcdc *USBCDC) txhandler() { + inflight := usbcdc.inflight.Load() + usbcdc.inflight.Store(0) + usbcdc.tx.Discard(inflight) + usbcdc.sendFromRing() +} + +// sendFromRing sends one USB packet from the ring and sets inflight. +// Called from kickTx (main) or txhandler (ISR), but never concurrently +// because kickTx only runs when inflight==0 and txhandler only runs +// when inflight>0. +func (usbcdc *USBCDC) sendFromRing() { + d1, _ := usbcdc.tx.Peek() + if len(d1) == 0 { + return + } + chunk := d1[:min(usb.EndpointPacketSize, len(d1))] + usbcdc.inflight.Store(uint32(len(chunk))) + machine.SendUSBInPacket(cdcEndpointIn, chunk) } // WriteByte writes a byte of data to the USB CDC interface. func (usbcdc *USBCDC) WriteByte(c byte) error { - usbcdc.Write([]byte{c}) + usbcdc.wbuf[0] = c + usbcdc.Write(usbcdc.wbuf[:]) return nil } @@ -124,9 +150,8 @@ func (usbcdc *USBCDC) RTS() bool { } func cdcCallbackRx(b []byte) { - for i := range b { - USB.Receive(b[i]) - } + free := USB.rx.Free() + USB.rx.Put(b[:min(len(b), int(free))]) } var cdcSetupBuff [cdcLineInfoSize]byte @@ -187,5 +212,8 @@ func cdcSetup(setup usb.Setup) bool { func EnableUSBCDC() { machine.USBCDC = New() - machine.EnableCDC(USB.Flush, cdcCallbackRx, cdcSetup) + machine.EnableCDC(USB.txhandler, cdcCallbackRx, cdcSetup) } + +//go:linkname gosched runtime.Gosched +func gosched() diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index 4b818100b5..999c115cb7 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -924,6 +924,52 @@ func testTypeAssert[T comparable, V any](t *testing.T, val V, wantVal T, wantOk } } +func TestTypeAssertStruct(t *testing.T) { + type taStruct struct { + i int + b bool + } + + var a any + + // struct + a = taStruct{3, true} + if s, ok := a.(taStruct); ok { + if s.i != 3 || s.b != true { + t.Errorf("a.(S) failed: got s.i=%v, s.b=%v\n", s.i, s.b) + } + } else { + t.Errorf("a.(S) failed: got ok=false") + } + + if s, ok := TypeAssert[taStruct](ValueOf(a)); ok { + if s.i != 3 || s.b != true { + t.Errorf("TypeAssert[S] failed: got s.i=%v, s.b=%v\n", s.i, s.b) + } + } else { + t.Errorf("TypeAssert[S] failed: got ok=false") + } + + // struct ptr + a = &taStruct{3, true} + if s, ok := a.(*taStruct); ok { + if s.i != 3 || s.b != true { + t.Errorf("a.(*S) failed: got s.i=%v, s.b=%v\n", s.i, s.b) + } + } else { + t.Errorf("a.(*S) failed: got ok=false") + } + + if s, ok := TypeAssert[*taStruct](ValueOf(a)); ok { + if s.i != 3 || s.b != true { + t.Errorf("TypeAssert[*S] failed: got s.i=%v, s.b=%v\n", s.i, s.b) + } + } else { + t.Errorf("TypeAssert[*S] failed: got ok=false") + } + +} + type testTypeWithMethod struct{ val string } func (v testTypeWithMethod) String() string { return v.val } diff --git a/src/runtime/float.go b/src/runtime/float.go index c80c8b7abf..b5fee4c5c4 100644 --- a/src/runtime/float.go +++ b/src/runtime/float.go @@ -52,3 +52,130 @@ func float64bits(f float64) uint64 { func float64frombits(b uint64) float64 { return *(*float64)(unsafe.Pointer(&b)) } + +// The fmimimum/fmaximum are missing from most libm implementations. +// Just define them ourselves. + +//export fminimum +func fminimum(x, y float64) float64 { + return minimumFloat64(x, y) +} + +//export fminimumf +func fminimumf(x, y float32) float32 { + return minimumFloat32(x, y) +} + +//export fmaximum +func fmaximum(x, y float64) float64 { + return maximumFloat64(x, y) +} + +//export fmaximumf +func fmaximumf(x, y float32) float32 { + return maximumFloat32(x, y) +} + +// Create seperate copies of the function that are not exported. +// This is necessary so that LLVM does not recognize them as builtins. +// If tests called the builtins, LLVM would just override them on most platforms. + +func minimumFloat32(x, y float32) float32 { + return minimumFloat[float32, int32](x, y, minPosNaN32, magMask32) +} + +func minimumFloat64(x, y float64) float64 { + return minimumFloat[float64, int64](x, y, minPosNaN64, magMask64) +} + +func maximumFloat32(x, y float32) float32 { + return maximumFloat[float32, int32](x, y, minPosNaN32, magMask32) +} + +func maximumFloat64(x, y float64) float64 { + return maximumFloat[float64, int64](x, y, minPosNaN64, magMask64) +} + +// minimumFloat is a generic implementation of the floating-point minimum operation. +// This implementation uses integer operations because this is mainly used for platforms without an FPU. +func minimumFloat[T float, I floatInt](x, y T, minPosNaN, magMask I) T { + xBits := *(*I)(unsafe.Pointer(&x)) + yBits := *(*I)(unsafe.Pointer(&y)) + + // Handle the special case of a positive NaN value. + switch { + case xBits >= minPosNaN: + return x + case yBits >= minPosNaN: + return y + } + + // The exponent-mantissa portion of the float is comparable via unsigned comparison (excluding the NaN case). + // We can turn a float into a signed-comparable value by reversing the comparison order of negative values. + // We can reverse the order by inverting the bits. + // This also ensures that positive zero compares greater than negative zero (as required by the spec). + // Negative NaN values will compare less than any other value, so they require no special handling to propogate. + if xBits < 0 { + xBits ^= magMask + } + if yBits < 0 { + yBits ^= magMask + } + if xBits <= yBits { + return x + } else { + return y + } +} + +// maximumFloat is a generic implementation of the floating-point maximum operation. +// This implementation uses integer operations because this is mainly used for platforms without an FPU. +func maximumFloat[T float, I floatInt](x, y T, minPosNaN, magMask I) T { + xBits := *(*I)(unsafe.Pointer(&x)) + yBits := *(*I)(unsafe.Pointer(&y)) + + // The exponent-mantissa portion of the float is comparable via unsigned comparison (excluding the NaN case). + // We can turn a float into a signed-comparable value by reversing the comparison order of negative values. + // We can reverse the order by inverting the bits. + // This also ensures that positive zero compares greater than negative zero (as required by the spec). + // Positive NaN values will compare greater than any other value, so they require no special handling to propogate. + if xBits < 0 { + xBits ^= magMask + } + if yBits < 0 { + yBits ^= magMask + } + // Handle the special case of a negative NaN value. + maxNegNaN := ^minPosNaN + switch { + case xBits <= maxNegNaN: + return x + case yBits <= maxNegNaN: + return y + } + if xBits >= yBits { + return x + } else { + return y + } +} + +const ( + signPos64 = 63 + exponentPos64 = 52 + minPosNaN64 = ((1 << signPos64) - (1 << exponentPos64)) + 1 + magMask64 = 1< lastCPUInt { + return errInterruptRange + } + + // Mark as used. + cpuIntUsed[i.num] = true + + // Read current INTENABLE, set the bit for this CPU interrupt. + cur := readINTENABLE() + cur |= 1 << uint(i.num) + writeINTENABLE(cur) + + return nil +} + +// In returns whether the CPU is currently inside an interrupt handler. +func In() bool { + return inInterrupt +} + +// handleInterrupt is called from the assembly vector code in esp32s3.S. +// It determines which CPU interrupt(s) fired and dispatches to the +// registered Go handlers. +// +//export handleInterrupt +func handleInterrupt() { + inInterrupt = true + + // INTERRUPT register shows pending + enabled CPU interrupts. + pending := readINTERRUPT() + enabled := readINTENABLE() + active := pending & enabled + + for i := firstCPUInt; i <= lastCPUInt; i++ { + if active&(1<> 32)) + + // Enable the alarm (auto-clears when alarm fires). + esp.TIMG0.T0CONFIG.SetBits(esp.TIMG_T0CONFIG_ALARM_EN) + + // Wait for any interrupt (timer alarm or other) or a timeout. + for interruptPending.Get() == 0 { + if ticks() >= target { + return + } + } + } +} + //go:extern _vector_table var _vector_table [0]uintptr diff --git a/src/runtime/runtime_esp32s3.go b/src/runtime/runtime_esp32s3.go index 35cd26da85..474c5a8fad 100644 --- a/src/runtime/runtime_esp32s3.go +++ b/src/runtime/runtime_esp32s3.go @@ -3,7 +3,10 @@ package runtime import ( + "device" "device/esp" + "machine" + "unsafe" ) // This is the function called on startup after the flash (IROM/DROM) is @@ -49,8 +52,22 @@ func main() { // Change CPU frequency from 80MHz to 240MHz by setting SYSTEM_PLL_FREQ_SEL to // 1 and SYSTEM_CPUPERIOD_SEL to 2 (see table "CPU Clock Frequency" in the // reference manual). + // We do this gradually to allow PLL and system to stabilize. esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1) + + // First switch to 160MHz (intermediate step) + esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(1) + // Small delay to let PLL stabilize at 160MHz + for i := 0; i < 1000; i++ { + _ = esp.SYSTEM.CPU_PER_CONF.Get() + } + + // Now switch to 240MHz esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2) + // Small delay to let PLL stabilize at 240MHz + for i := 0; i < 1000; i++ { + _ = esp.SYSTEM.CPU_PER_CONF.Get() + } // Clear bss. Repeat many times while we wait for cpu/clock to stabilize for x := 0; x < 30; x++ { @@ -60,6 +77,12 @@ func main() { // Initialize main system timer used for time.Now. initTimer() + // Set up the Xtensa interrupt vector table. + interruptInit() + + // Initialize timer alarm interrupt for the scheduler. + initTimerInterrupt() + // Initialize the heap, call main.main, etc. run() @@ -67,11 +90,52 @@ func main() { exit(0) } +func init() { + // Initialize UART. + machine.InitSerial() +} + func abort() { // lock up forever print("abort called\n") } +// interruptInit installs the Xtensa vector table by writing its address +// to the VECBASE special register and ensures all CPU interrupts are +// initially disabled. +func interruptInit() { + // Disable all CPU interrupts while we configure. + device.AsmFull("wsr {zero}, INTENABLE", map[string]interface{}{ + "zero": uintptr(0), + }) + + // Write the vector table address to VECBASE (SR 231). + vecbase := uintptr(unsafe.Pointer(&_vector_table)) + device.AsmFull("wsr {vecbase}, VECBASE", map[string]interface{}{ + "vecbase": vecbase, + }) + + // Clear PS.EXCM and PS.INTLEVEL so that level-1 interrupts can fire. + // The ROM bootloader leaves PS.EXCM=1 (exception mode), which masks + // all interrupts at level ≤ EXCMLEVEL (level 1 on ESP32-S3). + // PS.INTLEVEL may also be non-zero. Both must be 0 for peripheral + // interrupts to trigger. + // + // We also set PS.UM=1 (bit 5) so that level-1 interrupts route to + // the User exception vector at VECBASE+0x340, where our handler lives. + // With PS.UM=0 (the ROM default), they would go to the Kernel exception + // vector at VECBASE+0x300 which is an infinite-loop stub. + ps := uintptr(device.AsmFull("rsr {}, PS", nil)) + ps &^= 0x1F // clear INTLEVEL (bits 0-3) and EXCM (bit 4) + ps |= 0x20 // set PS.UM (bit 5) — use User exception vector + device.AsmFull("wsr {ps}, PS", map[string]interface{}{ + "ps": ps, + }) + + // Synchronize pipeline after writing special registers. + device.Asm("rsync") +} + //go:extern _vector_table var _vector_table [0]uintptr diff --git a/src/runtime/runtime_esp32sx.go b/src/runtime/runtime_esp32sx.go index b30dc37e97..2a92783559 100644 --- a/src/runtime/runtime_esp32sx.go +++ b/src/runtime/runtime_esp32sx.go @@ -5,6 +5,8 @@ package runtime import ( "device/esp" "machine" + "runtime/interrupt" + "runtime/volatile" "unsafe" ) @@ -73,11 +75,62 @@ func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) * 25 } -// sleepTicks busy-waits until the given number of ticks have passed. +// CPU interrupt number used for the TIMG0 timer alarm. +const timerAlarmCPUInterrupt = 9 + +var interruptPending volatile.Register8 + +func signalInterrupt() { + interruptPending.Set(1) +} + +var timerAlarmInterrupt interrupt.Interrupt + +// timerAlarmHandler clears the timer interrupt at the peripheral level +// and disables INT_ENA to prevent level-triggered re-assertion. +func timerAlarmHandler(interrupt.Interrupt) { + esp.TIMG0.INT_ENA_TIMERS.ClearBits(1) + esp.TIMG0.INT_CLR_TIMERS.Set(1) +} + +// initTimerInterrupt routes the TIMG0 timer 0 alarm interrupt to a CPU +// interrupt and registers a handler that clears the alarm flag. +func initTimerInterrupt() { + // Clear any stale timer interrupt before enabling. + esp.TIMG0.INT_CLR_TIMERS.Set(1) + + // Map the TIMG0 T0 peripheral interrupt to a CPU interrupt line. + esp.INTERRUPT_CORE0.SetTG_T0_INT_MAP(timerAlarmCPUInterrupt) + + // Register the interrupt handler and enable it once. + timerAlarmInterrupt = interrupt.New(timerAlarmCPUInterrupt, timerAlarmHandler) + timerAlarmInterrupt.Enable() +} + +// sleepTicks spins until the given number of ticks have elapsed, using the +// TIMG0 alarm interrupt to avoid busy-waiting for the entire duration. func sleepTicks(d timeUnit) { - sleepUntil := ticks() + d - for ticks() < sleepUntil { - // TODO: suspend the CPU to not burn power here unnecessarily. + target := ticks() + d + for ticks() < target { + // Set the alarm to fire at the target tick count. + interruptPending.Set(0) + + esp.TIMG0.T0ALARMLO.Set(uint32(target)) + esp.TIMG0.T0ALARMHI.Set(uint32(target >> 32)) + + // Enable the alarm (auto-clears when alarm fires). + esp.TIMG0.T0CONFIG.SetBits(esp.TIMG_TCONFIG_ALARM_EN) + + // Re-enable the timer interrupt (handler disables INT_ENA). + esp.TIMG0.INT_CLR_TIMERS.Set(1) + esp.TIMG0.INT_ENA_TIMERS.SetBits(1) + + // Wait for any interrupt (timer alarm or other) or timeout. + for interruptPending.Get() == 0 { + if ticks() >= target { + return + } + } } } diff --git a/src/runtime/runtime_esp32xx.go b/src/runtime/runtime_esp32xx.go index f1c62243f1..cde65b8cb9 100644 --- a/src/runtime/runtime_esp32xx.go +++ b/src/runtime/runtime_esp32xx.go @@ -55,14 +55,6 @@ func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) * 25 } -// sleepTicks busy-waits until the given number of ticks have passed. -func sleepTicks(d timeUnit) { - sleepUntil := ticks() + d - for ticks() < sleepUntil { - // TODO: suspend the CPU to not burn power here unnecessarily. - } -} - func exit(code int) { abort() } diff --git a/src/runtime/runtime_stm32l4x5.go b/src/runtime/runtime_stm32l4x5.go index 273eb726f5..c7c242975f 100644 --- a/src/runtime/runtime_stm32l4x5.go +++ b/src/runtime/runtime_stm32l4x5.go @@ -1,4 +1,4 @@ -//go:build stm32 && stm32l4x5 +//go:build stm32 && stm32l4y5 package runtime diff --git a/src/runtime/runtime_stm32u5.go b/src/runtime/runtime_stm32u5.go new file mode 100644 index 0000000000..e54721f393 --- /dev/null +++ b/src/runtime/runtime_stm32u5.go @@ -0,0 +1,41 @@ +//go:build stm32u5 + +package runtime + +import ( + "device/stm32" + "machine" +) + +func putchar(c byte) { + machine.Serial.WriteByte(c) +} + +func getchar() byte { + for machine.Serial.Buffered() == 0 { + Gosched() + } + v, _ := machine.Serial.ReadByte() + return v +} + +func buffered() int { + return machine.Serial.Buffered() +} + +func initCLK() { + // Use MSI at 4MHz — the reset default clock configuration. + // The MCU boots with MSI at 4MHz, VOS Range 4, and 0 flash wait states. + + // Enable PWR peripheral clock (required on STM32U5 before accessing PWR registers). + stm32.RCC.AHB3ENR.SetBits(stm32.RCC_AHB3ENR_PWREN) + _ = stm32.RCC.AHB3ENR.Get() // read-back for clock stabilization + + // Switch from VOS Range 4 to Range 3. Range 4 doesn't support ADC (RM0456 §6.3.6). + // Range 3 supports up to 50 MHz HCLK and enables ADC. No flash wait-state change needed at 4 MHz. + // The EPOD booster must be enabled before changing VOS (RM0456 §10.5.4). + stm32.PWR.VOSR.SetBits(stm32.PWR_VOSR_BOOSTEN) + stm32.PWR.VOSR.ReplaceBits(stm32.PWR_VOSR_VOS_Range3<DROM @@ -83,6 +87,7 @@ SECTIONS . = ALIGN (4); _sbss = ABSOLUTE(.); *(.sbss) + *(.sbss.*) *(.bss .bss.*) . = ALIGN (4); _ebss = ABSOLUTE(.); @@ -127,6 +132,9 @@ SECTIONS *(.wifi0iram*) *(.wifislpiram*) *(.wifirxiram*) + *(.wifiorslpiram*) + *(.iram1*) + *(.coexiram*) __init_start = .; *(.init) __init_end = .; @@ -156,8 +164,12 @@ SECTIONS *(.text.exception_vectors) . = ALIGN (4); *(.text .text.*) + _irom_end = .; } >IROM + PROVIDE(__flash_data_start = ALIGN(_irom_end - 0x42000000 + 0x3C000000, 4096)); + PROVIDE(__flash_data_end = 0x3C000000 + 4M); + /DISCARD/ : { *(.eh_frame) /* causes 'no memory region specified' error in lld */ @@ -1806,154 +1818,154 @@ r_sch_plan_offset_req_hook = 0x40001ce4; ***************************************/ /* Functions */ -esp_pp_rom_version_get = 0x400015b0; -RC_GetBlockAckTime = 0x400015b4; -ebuf_list_remove = 0x400015b8; +PROVIDE(esp_pp_rom_version_get = 0x400015b0); +PROVIDE(RC_GetBlockAckTime = 0x400015b4); +PROVIDE(ebuf_list_remove = 0x400015b8); /*esf_buf_alloc = 0x400015bc;*/ -GetAccess = 0x400015c8; -hal_mac_is_low_rate_enabled = 0x400015cc; -hal_mac_tx_get_blockack = 0x400015d0; +PROVIDE(GetAccess = 0x400015c8); +PROVIDE(hal_mac_is_low_rate_enabled = 0x400015cc); +PROVIDE(hal_mac_tx_get_blockack = 0x400015d0); /* hal_mac_tx_set_ppdu = 0x400015d4;*/ -ic_get_trc = 0x400015d8; +PROVIDE(ic_get_trc = 0x400015d8); /* ic_mac_deinit = 0x400015dc; */ -ic_mac_init = 0x400015e0; -ic_interface_enabled = 0x400015e4; -is_lmac_idle = 0x400015e8; +PROVIDE(ic_mac_init = 0x400015e0); +PROVIDE(ic_interface_enabled = 0x400015e4); +PROVIDE(is_lmac_idle = 0x400015e8); /*lmacAdjustTimestamp = 0x400015ec;*/ -lmacDiscardAgedMSDU = 0x400015f0; +PROVIDE(lmacDiscardAgedMSDU = 0x400015f0); /*lmacDiscardMSDU = 0x400015f4;*/ -lmacEndFrameExchangeSequence = 0x400015f8; -lmacIsIdle = 0x400015fc; -lmacIsLongFrame = 0x40001600; +PROVIDE(lmacEndFrameExchangeSequence = 0x400015f8); +PROVIDE(lmacIsIdle = 0x400015fc); +PROVIDE(lmacIsLongFrame = 0x40001600); /*lmacMSDUAged = 0x40001604;*/ -lmacPostTxComplete = 0x40001608; -lmacProcessAllTxTimeout = 0x4000160c; -lmacProcessCollisions = 0x40001610; -lmacProcessRxSucData = 0x40001614; -lmacReachLongLimit = 0x40001618; -lmacReachShortLimit = 0x4000161c; -lmacRecycleMPDU = 0x40001620; -lmacRxDone = 0x40001624; +PROVIDE(lmacPostTxComplete = 0x40001608); +PROVIDE(lmacProcessAllTxTimeout = 0x4000160c); +PROVIDE(lmacProcessCollisions = 0x40001610); +PROVIDE(lmacProcessRxSucData = 0x40001614); +PROVIDE(lmacReachLongLimit = 0x40001618); +PROVIDE(lmacReachShortLimit = 0x4000161c); +PROVIDE(lmacRecycleMPDU = 0x40001620); +PROVIDE(lmacRxDone = 0x40001624); /*lmacSetTxFrame = 0x40001628;*/ /*lmacTxFrame = 0x40001630;*/ -mac_tx_set_duration = 0x40001634; +PROVIDE(mac_tx_set_duration = 0x40001634); /* mac_tx_set_htsig = 0x40001638;*/ -mac_tx_set_plcp0 = 0x4000163c; +PROVIDE(mac_tx_set_plcp0 = 0x4000163c); /* mac_tx_set_plcp1 = 0x40001640;*/ -mac_tx_set_plcp2 = 0x40001644; +PROVIDE(mac_tx_set_plcp2 = 0x40001644); /* pm_check_state = 0x40001648; */ -pm_disable_dream_timer = 0x4000164c; -pm_disable_sleep_delay_timer = 0x40001650; -pm_dream = 0x40001654; -pm_mac_wakeup = 0x40001658; -pm_mac_sleep = 0x4000165c; -pm_enable_active_timer = 0x40001660; -pm_enable_sleep_delay_timer = 0x40001664; -pm_local_tsf_process = 0x40001668; -pm_set_beacon_filter = 0x4000166c; -pm_is_in_wifi_slice_threshold = 0x40001670; -pm_is_waked = 0x40001674; -pm_keep_alive = 0x40001678; +PROVIDE(pm_disable_dream_timer = 0x4000164c); +PROVIDE(pm_disable_sleep_delay_timer = 0x40001650); +PROVIDE(pm_dream = 0x40001654); +PROVIDE(pm_mac_wakeup = 0x40001658); +PROVIDE(pm_mac_sleep = 0x4000165c); +PROVIDE(pm_enable_active_timer = 0x40001660); +PROVIDE(pm_enable_sleep_delay_timer = 0x40001664); +PROVIDE(pm_local_tsf_process = 0x40001668); +PROVIDE(pm_set_beacon_filter = 0x4000166c); +PROVIDE(pm_is_in_wifi_slice_threshold = 0x40001670); +PROVIDE(pm_is_waked = 0x40001674); +PROVIDE(pm_keep_alive = 0x40001678); /* pm_on_beacon_rx = 0x4000167c; */ -pm_on_data_rx = 0x40001680; -pm_on_tbtt = 0x40001684; +PROVIDE(pm_on_data_rx = 0x40001680); +PROVIDE(pm_on_tbtt = 0x40001684); /* pm_parse_beacon = 0x40001688;*/ /* pm_process_tim = 0x4000168c; */ /*pm_rx_beacon_process = 0x40001690;*/ /* pm_rx_data_process = 0x40001694; */ /*pm_sleep = 0x40001698;*/ -pm_sleep_for = 0x4000169c; +PROVIDE(pm_sleep_for = 0x4000169c); /* pm_tbtt_process = 0x400016a0; */ -ppAMPDU2Normal = 0x400016a4; +PROVIDE(ppAMPDU2Normal = 0x400016a4); /*ppAssembleAMPDU = 0x400016a8;*/ -ppCalFrameTimes = 0x400016ac; -ppCalSubFrameLength = 0x400016b0; +PROVIDE(ppCalFrameTimes = 0x400016ac); +PROVIDE(ppCalSubFrameLength = 0x400016b0); /*ppCalTxAMPDULength = 0x400016b4;*/ -ppCheckTxAMPDUlength = 0x400016b8; -ppDequeueRxq_Locked = 0x400016bc; -ppDequeueTxQ = 0x400016c0; -ppEmptyDelimiterLength = 0x400016c4; -ppEnqueueRxq = 0x400016c8; -ppEnqueueTxDone = 0x400016cc; -ppGetTxQFirstAvail_Locked = 0x400016d0; -ppGetTxframe = 0x400016d4; -ppProcessRxPktHdr = 0x400016e0; -ppProcessTxQ = 0x400016e4; -ppRecordBarRRC = 0x400016e8; -lmacRequestTxopQueue = 0x400016ec; -lmacReleaseTxopQueue = 0x400016f0; -ppRecycleAmpdu = 0x400016f4; -ppRecycleRxPkt = 0x400016f8; -ppResortTxAMPDU = 0x400016fc; -ppResumeTxAMPDU = 0x40001700; +PROVIDE(ppCheckTxAMPDUlength = 0x400016b8); +PROVIDE(ppDequeueRxq_Locked = 0x400016bc); +PROVIDE(ppDequeueTxQ = 0x400016c0); +PROVIDE(ppEmptyDelimiterLength = 0x400016c4); +PROVIDE(ppEnqueueRxq = 0x400016c8); +PROVIDE(ppEnqueueTxDone = 0x400016cc); +PROVIDE(ppGetTxQFirstAvail_Locked = 0x400016d0); +PROVIDE(ppGetTxframe = 0x400016d4); +PROVIDE(ppProcessRxPktHdr = 0x400016e0); +PROVIDE(ppProcessTxQ = 0x400016e4); +PROVIDE(ppRecordBarRRC = 0x400016e8); +PROVIDE(lmacRequestTxopQueue = 0x400016ec); +PROVIDE(lmacReleaseTxopQueue = 0x400016f0); +PROVIDE(ppRecycleAmpdu = 0x400016f4); +PROVIDE(ppRecycleRxPkt = 0x400016f8); +PROVIDE(ppResortTxAMPDU = 0x400016fc); +PROVIDE(ppResumeTxAMPDU = 0x40001700); /* ppRxFragmentProc = 0x40001704; */ /* ppRxPkt = 0x40001708; */ -ppRxProtoProc = 0x4000170c; -ppSearchTxQueue = 0x40001710; -ppSearchTxframe = 0x40001714; -ppSelectNextQueue = 0x40001718; -ppSubFromAMPDU = 0x4000171c; -ppTask = 0x40001720; -ppTxPkt = 0x40001724; -ppTxProtoProc = 0x40001728; -ppTxqUpdateBitmap = 0x4000172c; -pp_coex_tx_request = 0x40001730; -pp_hdrsize = 0x40001734; -pp_post = 0x40001738; -pp_process_hmac_waiting_txq = 0x4000173c; -rcGetAmpduSched = 0x40001740; -rcUpdateRxDone = 0x40001744; -rc_get_trc = 0x40001748; -rc_get_trc_by_index = 0x4000174c; -rcAmpduLowerRate = 0x40001750; -rcampduuprate = 0x40001754; -rcClearCurAMPDUSched = 0x40001758; -rcClearCurSched = 0x4000175c; -rcClearCurStat = 0x40001760; -rcLowerSched = 0x40001768; -rcSetTxAmpduLimit = 0x4000176c; +PROVIDE(ppRxProtoProc = 0x4000170c); +PROVIDE(ppSearchTxQueue = 0x40001710); +PROVIDE(ppSearchTxframe = 0x40001714); +PROVIDE(ppSelectNextQueue = 0x40001718); +PROVIDE(ppSubFromAMPDU = 0x4000171c); +PROVIDE(ppTask = 0x40001720); +PROVIDE(ppTxPkt = 0x40001724); +PROVIDE(ppTxProtoProc = 0x40001728); +PROVIDE(ppTxqUpdateBitmap = 0x4000172c); +PROVIDE(pp_coex_tx_request = 0x40001730); +PROVIDE(pp_hdrsize = 0x40001734); +PROVIDE(pp_post = 0x40001738); +PROVIDE(pp_process_hmac_waiting_txq = 0x4000173c); +PROVIDE(rcGetAmpduSched = 0x40001740); +PROVIDE(rcUpdateRxDone = 0x40001744); +PROVIDE(rc_get_trc = 0x40001748); +PROVIDE(rc_get_trc_by_index = 0x4000174c); +PROVIDE(rcAmpduLowerRate = 0x40001750); +PROVIDE(rcampduuprate = 0x40001754); +PROVIDE(rcClearCurAMPDUSched = 0x40001758); +PROVIDE(rcClearCurSched = 0x4000175c); +PROVIDE(rcClearCurStat = 0x40001760); +PROVIDE(rcLowerSched = 0x40001768); +PROVIDE(rcSetTxAmpduLimit = 0x4000176c); /* rcTxUpdatePer = 0x40001770;*/ -rcUpdateAckSnr = 0x40001774; +PROVIDE(rcUpdateAckSnr = 0x40001774); /*rcUpdateRate = 0x40001778;*/ /* rcUpdateTxDone = 0x4000177c; */ -rcUpdateTxDoneAmpdu2 = 0x40001780; -rcUpSched = 0x40001784; -rssi_margin = 0x40001788; -rx11NRate2AMPDULimit = 0x4000178c; -TRC_AMPDU_PER_DOWN_THRESHOLD = 0x40001790; -TRC_AMPDU_PER_UP_THRESHOLD = 0x40001794; -trc_calc_duration = 0x40001798; -trc_isTxAmpduOperational = 0x4000179c; -trc_onAmpduOp = 0x400017a0; -TRC_PER_IS_GOOD = 0x400017a4; -trc_SetTxAmpduState = 0x400017a8; -trc_tid_isTxAmpduOperational = 0x400017ac; -trcAmpduSetState = 0x400017b0; -wDev_AppendRxBlocks = 0x400017b8; -wDev_DiscardFrame = 0x400017bc; -wDev_GetNoiseFloor = 0x400017c0; -wDev_IndicateAmpdu = 0x400017c4; +PROVIDE(rcUpdateTxDoneAmpdu2 = 0x40001780); +PROVIDE(rcUpSched = 0x40001784); +PROVIDE(rssi_margin = 0x40001788); +PROVIDE(rx11NRate2AMPDULimit = 0x4000178c); +PROVIDE(TRC_AMPDU_PER_DOWN_THRESHOLD = 0x40001790); +PROVIDE(TRC_AMPDU_PER_UP_THRESHOLD = 0x40001794); +PROVIDE(trc_calc_duration = 0x40001798); +PROVIDE(trc_isTxAmpduOperational = 0x4000179c); +PROVIDE(trc_onAmpduOp = 0x400017a0); +PROVIDE(TRC_PER_IS_GOOD = 0x400017a4); +PROVIDE(trc_SetTxAmpduState = 0x400017a8); +PROVIDE(trc_tid_isTxAmpduOperational = 0x400017ac); +PROVIDE(trcAmpduSetState = 0x400017b0); +PROVIDE(wDev_AppendRxBlocks = 0x400017b8); +PROVIDE(wDev_DiscardFrame = 0x400017bc); +PROVIDE(wDev_GetNoiseFloor = 0x400017c0); +PROVIDE(wDev_IndicateAmpdu = 0x400017c4); /*wDev_IndicateFrame = 0x400017c8;*/ -wdev_bank_store = 0x400017cc; -wdev_bank_load = 0x400017d0; -wdev_mac_reg_load = 0x400017d4; -wdev_mac_reg_store = 0x400017d8; -wdev_mac_special_reg_load = 0x400017dc; -wdev_mac_special_reg_store = 0x400017e0; -wdev_mac_wakeup = 0x400017e4; -wdev_mac_sleep = 0x400017e8; -hal_mac_is_dma_enable = 0x400017ec; +PROVIDE(wdev_bank_store = 0x400017cc); +PROVIDE(wdev_bank_load = 0x400017d0); +PROVIDE(wdev_mac_reg_load = 0x400017d4); +PROVIDE(wdev_mac_reg_store = 0x400017d8); +PROVIDE(wdev_mac_special_reg_load = 0x400017dc); +PROVIDE(wdev_mac_special_reg_store = 0x400017e0); +PROVIDE(wdev_mac_wakeup = 0x400017e4); +PROVIDE(wdev_mac_sleep = 0x400017e8); +PROVIDE(hal_mac_is_dma_enable = 0x400017ec); /*wDev_ProcessFiq = 0x400017f0;*/ /*wDev_ProcessRxSucData = 0x400017f4;*/ -wdevProcessRxSucDataAll = 0x400017f8; -wdev_csi_len_align = 0x400017fc; -ppDequeueTxDone_Locked = 0x40001800; +PROVIDE(wdevProcessRxSucDataAll = 0x400017f8); +PROVIDE(wdev_csi_len_align = 0x400017fc); +PROVIDE(ppDequeueTxDone_Locked = 0x40001800); /*pm_tx_data_done_process = 0x40001808;*/ -config_is_cache_tx_buf_enabled = 0x4000180c; +PROVIDE(config_is_cache_tx_buf_enabled = 0x4000180c); //ppMapWaitTxq = 0x40001810; -ppProcessWaitingQueue = 0x40001814; -ppDisableQueue = 0x40001818; -pm_allow_tx = 0x4000181c; +PROVIDE(ppProcessWaitingQueue = 0x40001814); +PROVIDE(ppDisableQueue = 0x40001818); +PROVIDE(pm_allow_tx = 0x4000181c); /* Data (.data, .bss, .rodata) */ our_instances_ptr = 0x3ff1ee44; pTxRx = 0x3fcdf968; @@ -2010,41 +2022,41 @@ s_encap_amsdu_func = 0x3fcdf870; ***************************************/ /* Functions */ -esp_net80211_rom_version_get = 0x40001820; -ampdu_dispatch = 0x40001824; -ampdu_dispatch_all = 0x40001828; -ampdu_dispatch_as_many_as_possible = 0x4000182c; -ampdu_dispatch_movement = 0x40001830; -ampdu_dispatch_upto = 0x40001834; -chm_is_at_home_channel = 0x40001838; -cnx_node_is_existing = 0x4000183c; -cnx_node_search = 0x40001840; -ic_ebuf_recycle_rx = 0x40001844; -ic_ebuf_recycle_tx = 0x40001848; -ic_reset_rx_ba = 0x4000184c; -ieee80211_align_eb = 0x40001850; -ieee80211_ampdu_reorder = 0x40001854; -ieee80211_ampdu_start_age_timer = 0x40001858; +PROVIDE(esp_net80211_rom_version_get = 0x40001820); +PROVIDE(ampdu_dispatch = 0x40001824); +PROVIDE(ampdu_dispatch_all = 0x40001828); +PROVIDE(ampdu_dispatch_as_many_as_possible = 0x4000182c); +PROVIDE(ampdu_dispatch_movement = 0x40001830); +PROVIDE(ampdu_dispatch_upto = 0x40001834); +PROVIDE(chm_is_at_home_channel = 0x40001838); +PROVIDE(cnx_node_is_existing = 0x4000183c); +PROVIDE(cnx_node_search = 0x40001840); +PROVIDE(ic_ebuf_recycle_rx = 0x40001844); +PROVIDE(ic_ebuf_recycle_tx = 0x40001848); +PROVIDE(ic_reset_rx_ba = 0x4000184c); +PROVIDE(ieee80211_align_eb = 0x40001850); +PROVIDE(ieee80211_ampdu_reorder = 0x40001854); +PROVIDE(ieee80211_ampdu_start_age_timer = 0x40001858); /*ieee80211_encap_esfbuf = 0x4000185c;*/ -ieee80211_is_tx_allowed = 0x40001860; -ieee80211_output_pending_eb = 0x40001864; +PROVIDE(ieee80211_is_tx_allowed = 0x40001860); +PROVIDE(ieee80211_output_pending_eb = 0x40001864); /*ieee80211_output_process = 0x40001868;*/ -ieee80211_set_tx_desc = 0x4000186c; -rom_sta_input = 0x40001870; -wifi_get_macaddr = 0x40001874; -wifi_rf_phy_disable = 0x40001878; -wifi_rf_phy_enable = 0x4000187c; -ic_ebuf_alloc = 0x40001880; -ieee80211_classify = 0x40001884; -ieee80211_copy_eb_header = 0x40001888; -ieee80211_recycle_cache_eb = 0x4000188c; -ieee80211_search_node = 0x40001890; -roundup2 = 0x40001894; -ieee80211_crypto_encap = 0x40001898; +PROVIDE(ieee80211_set_tx_desc = 0x4000186c); +PROVIDE(rom_sta_input = 0x40001870); +PROVIDE(wifi_get_macaddr = 0x40001874); +PROVIDE(wifi_rf_phy_disable = 0x40001878); +PROVIDE(wifi_rf_phy_enable = 0x4000187c); +PROVIDE(ic_ebuf_alloc = 0x40001880); +PROVIDE(ieee80211_classify = 0x40001884); +PROVIDE(ieee80211_copy_eb_header = 0x40001888); +PROVIDE(ieee80211_recycle_cache_eb = 0x4000188c); +PROVIDE(ieee80211_search_node = 0x40001890); +PROVIDE(roundup2 = 0x40001894); +PROVIDE(ieee80211_crypto_encap = 0x40001898); /* ieee80211_crypto_decap = 0x4000189c; */ /* ieee80211_decap = 0x400018a0; */ -ieee80211_set_tx_pti = 0x400018a4; -wifi_is_started = 0x400018a8; +PROVIDE(ieee80211_set_tx_pti = 0x400018a4); +PROVIDE(wifi_is_started = 0x400018a8); /* Data (.data, .bss, .rodata) */ net80211_funcs = 0x3fcdf86c; g_scan = 0x3fcdf868; @@ -2062,26 +2074,26 @@ sta_rxcb = 0x3fcdf84c; ***************************************/ /* Functions */ -esp_coex_rom_version_get = 0x400018ac; -coex_bt_release = 0x400018b0; -coex_bt_request = 0x400018b4; -coex_core_ble_conn_dyn_prio_get = 0x400018b8; -coex_core_event_duration_get = 0x400018bc; -coex_core_pti_get = 0x400018c0; -coex_core_release = 0x400018c4; -coex_core_request = 0x400018c8; -coex_core_status_get = 0x400018cc; +PROVIDE(esp_coex_rom_version_get = 0x400018ac); +PROVIDE(coex_bt_release = 0x400018b0); +PROVIDE(coex_bt_request = 0x400018b4); +PROVIDE(coex_core_ble_conn_dyn_prio_get = 0x400018b8); +PROVIDE(coex_core_event_duration_get = 0x400018bc); +PROVIDE(coex_core_pti_get = 0x400018c0); +PROVIDE(coex_core_release = 0x400018c4); +PROVIDE(coex_core_request = 0x400018c8); +PROVIDE(coex_core_status_get = 0x400018cc); /*coex_core_timer_idx_get = 0x400018d0;*/ -coex_event_duration_get = 0x400018d4; -coex_hw_timer_disable = 0x400018d8; -coex_hw_timer_enable = 0x400018dc; -coex_hw_timer_set = 0x400018e0; -coex_schm_interval_set = 0x400018e4; -coex_schm_lock = 0x400018e8; -coex_schm_unlock = 0x400018ec; -coex_status_get = 0x400018f0; -coex_wifi_release = 0x400018f4; -esp_coex_ble_conn_dynamic_prio_get = 0x400018f8; +PROVIDE(coex_event_duration_get = 0x400018d4); +PROVIDE(coex_hw_timer_disable = 0x400018d8); +PROVIDE(coex_hw_timer_enable = 0x400018dc); +PROVIDE(coex_hw_timer_set = 0x400018e0); +PROVIDE(coex_schm_interval_set = 0x400018e4); +PROVIDE(coex_schm_lock = 0x400018e8); +PROVIDE(coex_schm_unlock = 0x400018ec); +PROVIDE(coex_status_get = 0x400018f0); +PROVIDE(coex_wifi_release = 0x400018f4); +PROVIDE(esp_coex_ble_conn_dynamic_prio_get = 0x400018f8); /* Data (.data, .bss, .rodata) */ coex_env_ptr = 0x3fcdf848; coex_pti_tab_ptr = 0x3fcdf844; @@ -2096,143 +2108,143 @@ g_coex_param_ptr = 0x3fcdf834; ***************************************/ /* Functions */ -phy_get_romfuncs = 0x400018fc; -rom_abs_temp = 0x40001900; -rom_bb_bss_cbw40_dig = 0x40001904; -rom_bb_wdg_test_en = 0x40001908; -rom_bb_wdt_get_status = 0x4000190c; -rom_bb_wdt_int_enable = 0x40001910; -rom_bb_wdt_rst_enable = 0x40001914; -rom_bb_wdt_timeout_clear = 0x40001918; -rom_cbw2040_cfg = 0x4000191c; -rom_check_noise_floor = 0x40001920; -rom_chip_i2c_readReg = 0x40001924; -rom_chip_i2c_writeReg = 0x40001928; -rom_correct_rf_ana_gain = 0x4000192c; -rom_dc_iq_est = 0x40001930; -rom_disable_agc = 0x40001934; -rom_en_pwdet = 0x40001938; -rom_enable_agc = 0x4000193c; -rom_get_bbgain_db = 0x40001940; -rom_get_data_sat = 0x40001944; -rom_get_i2c_read_mask = 0x40001948; -rom_get_pwctrl_correct = 0x4000194c; -rom_get_rf_gain_qdb = 0x40001950; -rom_i2c_readReg = 0x40001954; -rom_i2c_readReg_Mask = 0x40001958; -rom_i2c_writeReg = 0x4000195c; -rom_i2c_writeReg_Mask = 0x40001960; +PROVIDE(phy_get_romfuncs = 0x400018fc); +PROVIDE(rom_abs_temp = 0x40001900); +PROVIDE(rom_bb_bss_cbw40_dig = 0x40001904); +PROVIDE(rom_bb_wdg_test_en = 0x40001908); +PROVIDE(rom_bb_wdt_get_status = 0x4000190c); +PROVIDE(rom_bb_wdt_int_enable = 0x40001910); +PROVIDE(rom_bb_wdt_rst_enable = 0x40001914); +PROVIDE(rom_bb_wdt_timeout_clear = 0x40001918); +PROVIDE(rom_cbw2040_cfg = 0x4000191c); +PROVIDE(rom_check_noise_floor = 0x40001920); +PROVIDE(rom_chip_i2c_readReg = 0x40001924); +PROVIDE(rom_chip_i2c_writeReg = 0x40001928); +PROVIDE(rom_correct_rf_ana_gain = 0x4000192c); +PROVIDE(rom_dc_iq_est = 0x40001930); +PROVIDE(rom_disable_agc = 0x40001934); +PROVIDE(rom_en_pwdet = 0x40001938); +PROVIDE(rom_enable_agc = 0x4000193c); +PROVIDE(rom_get_bbgain_db = 0x40001940); +PROVIDE(rom_get_data_sat = 0x40001944); +PROVIDE(rom_get_i2c_read_mask = 0x40001948); +PROVIDE(rom_get_pwctrl_correct = 0x4000194c); +PROVIDE(rom_get_rf_gain_qdb = 0x40001950); +PROVIDE(rom_i2c_readReg = 0x40001954); +PROVIDE(rom_i2c_readReg_Mask = 0x40001958); +PROVIDE(rom_i2c_writeReg = 0x4000195c); +PROVIDE(rom_i2c_writeReg_Mask = 0x40001960); /* rom_index_to_txbbgain = 0x40001964; */ -rom_iq_est_disable = 0x40001968; -rom_iq_est_enable = 0x4000196c; -rom_linear_to_db = 0x40001970; -rom_loopback_mode_en = 0x40001974; -rom_mhz2ieee = 0x40001978; -rom_noise_floor_auto_set = 0x4000197c; -rom_pbus_debugmode = 0x40001980; -rom_pbus_force_mode = 0x40001984; -rom_pbus_force_test = 0x40001988; -rom_pbus_rd = 0x4000198c; -rom_pbus_rd_addr = 0x40001990; -rom_pbus_rd_shift = 0x40001994; -rom_pbus_set_dco = 0x40001998; -rom_pbus_set_rxgain = 0x4000199c; -rom_pbus_workmode = 0x400019a0; -rom_pbus_xpd_rx_off = 0x400019a4; -rom_pbus_xpd_rx_on = 0x400019a8; -rom_pbus_xpd_tx_off = 0x400019ac; +PROVIDE(rom_iq_est_disable = 0x40001968); +PROVIDE(rom_iq_est_enable = 0x4000196c); +PROVIDE(rom_linear_to_db = 0x40001970); +PROVIDE(rom_loopback_mode_en = 0x40001974); +PROVIDE(rom_mhz2ieee = 0x40001978); +PROVIDE(rom_noise_floor_auto_set = 0x4000197c); +PROVIDE(rom_pbus_debugmode = 0x40001980); +PROVIDE(rom_pbus_force_mode = 0x40001984); +PROVIDE(rom_pbus_force_test = 0x40001988); +PROVIDE(rom_pbus_rd = 0x4000198c); +PROVIDE(rom_pbus_rd_addr = 0x40001990); +PROVIDE(rom_pbus_rd_shift = 0x40001994); +PROVIDE(rom_pbus_set_dco = 0x40001998); +PROVIDE(rom_pbus_set_rxgain = 0x4000199c); +PROVIDE(rom_pbus_workmode = 0x400019a0); +PROVIDE(rom_pbus_xpd_rx_off = 0x400019a4); +PROVIDE(rom_pbus_xpd_rx_on = 0x400019a8); +PROVIDE(rom_pbus_xpd_tx_off = 0x400019ac); /* rom_pbus_xpd_tx_on = 0x400019b0; */ -rom_phy_byte_to_word = 0x400019b4; -rom_phy_disable_cca = 0x400019b8; -rom_phy_enable_cca = 0x400019bc; -rom_phy_get_noisefloor = 0x400019c0; -rom_phy_get_rx_freq = 0x400019c4; -rom_phy_set_bbfreq_init = 0x400019c8; -rom_pow_usr = 0x400019cc; -rom_pwdet_sar2_init = 0x400019d0; -rom_read_hw_noisefloor = 0x400019d4; -rom_read_sar_dout = 0x400019d8; -rom_set_cal_rxdc = 0x400019dc; -rom_set_chan_cal_interp = 0x400019e0; -rom_set_loopback_gain = 0x400019e4; -rom_set_noise_floor = 0x400019e8; -rom_set_rxclk_en = 0x400019ec; +PROVIDE(rom_phy_byte_to_word = 0x400019b4); +PROVIDE(rom_phy_disable_cca = 0x400019b8); +PROVIDE(rom_phy_enable_cca = 0x400019bc); +PROVIDE(rom_phy_get_noisefloor = 0x400019c0); +PROVIDE(rom_phy_get_rx_freq = 0x400019c4); +PROVIDE(rom_phy_set_bbfreq_init = 0x400019c8); +PROVIDE(rom_pow_usr = 0x400019cc); +PROVIDE(rom_pwdet_sar2_init = 0x400019d0); +PROVIDE(rom_read_hw_noisefloor = 0x400019d4); +PROVIDE(rom_read_sar_dout = 0x400019d8); +PROVIDE(rom_set_cal_rxdc = 0x400019dc); +PROVIDE(rom_set_chan_cal_interp = 0x400019e0); +PROVIDE(rom_set_loopback_gain = 0x400019e4); +PROVIDE(rom_set_noise_floor = 0x400019e8); +PROVIDE(rom_set_rxclk_en = 0x400019ec); /* rom_set_tx_dig_gain = 0x400019f0; */ /* rom_set_txcap_reg = 0x400019f4; */ -rom_set_txclk_en = 0x400019f8; -rom_spur_cal = 0x400019fc; -rom_spur_reg_write_one_tone = 0x40001a00; -rom_target_power_add_backoff = 0x40001a04; -rom_tx_pwctrl_bg_init = 0x40001a08; +PROVIDE(rom_set_txclk_en = 0x400019f8); +PROVIDE(rom_spur_cal = 0x400019fc); +PROVIDE(rom_spur_reg_write_one_tone = 0x40001a00); +PROVIDE(rom_target_power_add_backoff = 0x40001a04); +PROVIDE(rom_tx_pwctrl_bg_init = 0x40001a08); /* rom_txbbgain_to_index = 0x40001a0c; */ -rom_wifi_11g_rate_chg = 0x40001a10; -rom_write_gain_mem = 0x40001a14; -chip726_phyrom_version = 0x40001a18; -rom_disable_wifi_agc = 0x40001a1c; -rom_enable_wifi_agc = 0x40001a20; -rom_set_tx_gain_table = 0x40001a24; -rom_bt_index_to_bb = 0x40001a28; -rom_bt_bb_to_index = 0x40001a2c; -rom_wr_bt_tx_atten = 0x40001a30; -rom_wr_bt_tx_gain_mem = 0x40001a34; -rom_spur_coef_cfg = 0x40001a38; -rom_bb_bss_cbw40 = 0x40001a3c; -rom_set_cca = 0x40001a40; -rom_tx_paon_set = 0x40001a44; -rom_i2cmst_reg_init = 0x40001a48; -rom_iq_corr_enable = 0x40001a4c; -rom_fe_reg_init = 0x40001a50; +PROVIDE(rom_wifi_11g_rate_chg = 0x40001a10); +PROVIDE(rom_write_gain_mem = 0x40001a14); +PROVIDE(chip726_phyrom_version = 0x40001a18); +PROVIDE(rom_disable_wifi_agc = 0x40001a1c); +PROVIDE(rom_enable_wifi_agc = 0x40001a20); +PROVIDE(rom_set_tx_gain_table = 0x40001a24); +PROVIDE(rom_bt_index_to_bb = 0x40001a28); +PROVIDE(rom_bt_bb_to_index = 0x40001a2c); +PROVIDE(rom_wr_bt_tx_atten = 0x40001a30); +PROVIDE(rom_wr_bt_tx_gain_mem = 0x40001a34); +PROVIDE(rom_spur_coef_cfg = 0x40001a38); +PROVIDE(rom_bb_bss_cbw40 = 0x40001a3c); +PROVIDE(rom_set_cca = 0x40001a40); +PROVIDE(rom_tx_paon_set = 0x40001a44); +PROVIDE(rom_i2cmst_reg_init = 0x40001a48); +PROVIDE(rom_iq_corr_enable = 0x40001a4c); +PROVIDE(rom_fe_reg_init = 0x40001a50); /* rom_agc_reg_init = 0x40001a54; */ /* rom_bb_reg_init = 0x40001a58; */ -rom_mac_enable_bb = 0x40001a5c; -rom_bb_wdg_cfg = 0x40001a60; -rom_force_txon = 0x40001a64; -rom_fe_txrx_reset = 0x40001a68; -rom_set_rx_comp = 0x40001a6c; +PROVIDE(rom_mac_enable_bb = 0x40001a5c); +PROVIDE(rom_bb_wdg_cfg = 0x40001a60); +PROVIDE(rom_force_txon = 0x40001a64); +PROVIDE(rom_fe_txrx_reset = 0x40001a68); +PROVIDE(rom_set_rx_comp = 0x40001a6c); /* rom_set_pbus_reg = 0x40001a70; */ -rom_write_chan_freq = 0x40001a74; +PROVIDE(rom_write_chan_freq = 0x40001a74); /* rom_phy_xpd_rf = 0x40001a78; */ -rom_set_xpd_sar = 0x40001a7c; -rom_write_dac_gain2 = 0x40001a80; -rom_rtc_sar2_init = 0x40001a84; -rom_get_target_power_offset = 0x40001a88; +PROVIDE(rom_set_xpd_sar = 0x40001a7c); +PROVIDE(rom_write_dac_gain2 = 0x40001a80); +PROVIDE(rom_rtc_sar2_init = 0x40001a84); +PROVIDE(rom_get_target_power_offset = 0x40001a88); /* rom_write_txrate_power_offset = 0x40001a8c; */ -rom_get_rate_fcc_index = 0x40001a90; -rom_get_rate_target_power = 0x40001a94; -rom_write_wifi_dig_gain = 0x40001a98; -rom_bt_correct_rf_ana_gain = 0x40001a9c; -rom_pkdet_vol_start = 0x40001aa0; -rom_read_sar2_code = 0x40001aa4; -rom_get_sar2_vol = 0x40001aa8; -rom_get_pll_vol = 0x40001aac; -rom_get_phy_target_power = 0x40001ab0; +PROVIDE(rom_get_rate_fcc_index = 0x40001a90); +PROVIDE(rom_get_rate_target_power = 0x40001a94); +PROVIDE(rom_write_wifi_dig_gain = 0x40001a98); +PROVIDE(rom_bt_correct_rf_ana_gain = 0x40001a9c); +PROVIDE(rom_pkdet_vol_start = 0x40001aa0); +PROVIDE(rom_read_sar2_code = 0x40001aa4); +PROVIDE(rom_get_sar2_vol = 0x40001aa8); +PROVIDE(rom_get_pll_vol = 0x40001aac); +PROVIDE(rom_get_phy_target_power = 0x40001ab0); /* rom_temp_to_power = 0x40001ab4; */ -rom_phy_track_pll_cap = 0x40001ab8; -rom_phy_pwdet_always_en = 0x40001abc; -rom_phy_pwdet_onetime_en = 0x40001ac0; -rom_get_i2c_mst0_mask = 0x40001ac4; -rom_get_i2c_hostid = 0x40001ac8; -rom_enter_critical_phy = 0x40001acc; -rom_exit_critical_phy = 0x40001ad0; -rom_chip_i2c_readReg_org = 0x40001ad4; -rom_i2c_paral_set_mst0 = 0x40001ad8; -rom_i2c_paral_set_read = 0x40001adc; -rom_i2c_paral_read = 0x40001ae0; -rom_i2c_paral_write = 0x40001ae4; -rom_i2c_paral_write_num = 0x40001ae8; -rom_i2c_paral_write_mask = 0x40001aec; -rom_bb_bss_cbw40_ana = 0x40001af0; -rom_chan_to_freq = 0x40001af4; +PROVIDE(rom_phy_track_pll_cap = 0x40001ab8); +PROVIDE(rom_phy_pwdet_always_en = 0x40001abc); +PROVIDE(rom_phy_pwdet_onetime_en = 0x40001ac0); +PROVIDE(rom_get_i2c_mst0_mask = 0x40001ac4); +PROVIDE(rom_get_i2c_hostid = 0x40001ac8); +PROVIDE(rom_enter_critical_phy = 0x40001acc); +PROVIDE(rom_exit_critical_phy = 0x40001ad0); +PROVIDE(rom_chip_i2c_readReg_org = 0x40001ad4); +PROVIDE(rom_i2c_paral_set_mst0 = 0x40001ad8); +PROVIDE(rom_i2c_paral_set_read = 0x40001adc); +PROVIDE(rom_i2c_paral_read = 0x40001ae0); +PROVIDE(rom_i2c_paral_write = 0x40001ae4); +PROVIDE(rom_i2c_paral_write_num = 0x40001ae8); +PROVIDE(rom_i2c_paral_write_mask = 0x40001aec); +PROVIDE(rom_bb_bss_cbw40_ana = 0x40001af0); +PROVIDE(rom_chan_to_freq = 0x40001af4); /* rom_open_i2c_xpd = 0x40001af8; */ -rom_dac_rate_set = 0x40001afc; +PROVIDE(rom_dac_rate_set = 0x40001afc); /* rom_tsens_read_init = 0x40001b00; */ /* rom_tsens_code_read = 0x40001b04; */ -rom_tsens_index_to_dac = 0x40001b08; -rom_tsens_index_to_offset = 0x40001b0c; +PROVIDE(rom_tsens_index_to_dac = 0x40001b08); +PROVIDE(rom_tsens_index_to_offset = 0x40001b0c); /* rom_tsens_dac_cal = 0x40001b10; */ -rom_code_to_temp = 0x40001b14; -rom_write_pll_cap_mem = 0x40001b18; -rom_pll_correct_dcap = 0x40001b1c; -rom_phy_en_hw_set_freq = 0x40001b20; -rom_phy_dis_hw_set_freq = 0x40001b24; +PROVIDE(rom_code_to_temp = 0x40001b14); +PROVIDE(rom_write_pll_cap_mem = 0x40001b18); +PROVIDE(rom_pll_correct_dcap = 0x40001b1c); +PROVIDE(rom_phy_en_hw_set_freq = 0x40001b20); +PROVIDE(rom_phy_dis_hw_set_freq = 0x40001b24); /* rom_pll_vol_cal = 0x40001b28; */ diff --git a/targets/esp32s3-interrupts.S b/targets/esp32s3-interrupts.S new file mode 100644 index 0000000000..bdc731fd12 --- /dev/null +++ b/targets/esp32s3-interrupts.S @@ -0,0 +1,321 @@ + +// Xtensa interrupt/exception vector table for the ESP32-S3. +// +// The ESP32-S3 uses an Xtensa LX7 core with the windowed register ABI. +// Interrupt vectors are placed at fixed offsets from the VECBASE special +// register. We only handle level-1 (user) interrupts for now. +// +// Vector offsets (from ESP32-S3 core-isa.h XCHAL definitions): +// 0x000 Window overflow 4 +// 0x040 Window underflow 4 +// 0x080 Window overflow 8 +// 0x0C0 Window underflow 8 +// 0x100 Window overflow 12 +// 0x140 Window underflow 12 +// 0x180 Level-2 interrupt +// 0x1C0 Level-3 interrupt +// 0x200 Level-4 interrupt +// 0x240 Level-5 interrupt +// 0x280 Debug exception (level-6) +// 0x2C0 NMI (level-7) +// 0x300 Kernel exception +// 0x340 User exception (level-1 interrupt) +// 0x3C0 Double exception + +// PS register field definitions. +#define PS_WOE 0x00040000 +#define PS_EXCM 0x00000010 +#define PS_INTLEVEL_MASK 0x0000000F + +// ----------------------------------------------------------------------- +// Vector table — must be aligned to 0x400 (1024 bytes). +// ----------------------------------------------------------------------- + .section .text.exception_vectors,"ax" + .global _vector_table + .balign 0x400 +_vector_table: + +// ----------------------------------------------------------------------- +// Offset 0x000 — Window overflow 4 +// ----------------------------------------------------------------------- + .org _vector_table + 0x000 +_window_overflow4: + s32e a0, a5, -16 + s32e a1, a5, -12 + s32e a2, a5, -8 + s32e a3, a5, -4 + rfwo + +// ----------------------------------------------------------------------- +// Offset 0x040 — Window underflow 4 +// ----------------------------------------------------------------------- + .org _vector_table + 0x040 +_window_underflow4: + l32e a0, a5, -16 + l32e a1, a5, -12 + l32e a2, a5, -8 + l32e a3, a5, -4 + rfwu + +// ----------------------------------------------------------------------- +// Offset 0x080 — Window overflow 8 +// ----------------------------------------------------------------------- + .org _vector_table + 0x080 +_window_overflow8: + s32e a0, a9, -16 + l32e a0, a1, -12 + s32e a1, a9, -12 + s32e a2, a9, -8 + s32e a3, a9, -4 + s32e a4, a0, -32 + s32e a5, a0, -28 + s32e a6, a0, -24 + s32e a7, a0, -20 + rfwo + +// ----------------------------------------------------------------------- +// Offset 0x0C0 — Window underflow 8 +// ----------------------------------------------------------------------- + .org _vector_table + 0x0C0 +_window_underflow8: + l32e a0, a9, -16 + l32e a1, a9, -12 + l32e a2, a9, -8 + l32e a7, a1, -12 + l32e a3, a9, -4 + l32e a4, a7, -32 + l32e a5, a7, -28 + l32e a6, a7, -24 + l32e a7, a7, -20 + rfwu + +// ----------------------------------------------------------------------- +// Offset 0x100 — Window overflow 12 +// ----------------------------------------------------------------------- + .org _vector_table + 0x100 +_window_overflow12: + s32e a0, a13, -16 + l32e a0, a1, -12 + s32e a1, a13, -12 + s32e a2, a13, -8 + s32e a3, a13, -4 + s32e a4, a0, -48 + s32e a5, a0, -44 + s32e a6, a0, -40 + s32e a7, a0, -36 + s32e a8, a0, -32 + s32e a9, a0, -28 + s32e a10, a0, -24 + s32e a11, a0, -20 + rfwo + +// ----------------------------------------------------------------------- +// Offset 0x140 — Window underflow 12 +// ----------------------------------------------------------------------- + .org _vector_table + 0x140 +_window_underflow12: + l32e a0, a13, -16 + l32e a1, a13, -12 + l32e a2, a13, -8 + l32e a11, a1, -12 + l32e a3, a13, -4 + l32e a4, a11, -48 + l32e a5, a11, -44 + l32e a6, a11, -40 + l32e a7, a11, -36 + l32e a8, a11, -32 + l32e a9, a11, -28 + l32e a10, a11, -24 + l32e a11, a11, -20 + rfwu + +// ----------------------------------------------------------------------- +// Offset 0x180 — Level-2 interrupt (stub — loops forever) +// ----------------------------------------------------------------------- + .org _vector_table + 0x180 +_level2_vector: + j _level2_vector + +// ----------------------------------------------------------------------- +// Offset 0x1C0 — Level-3 interrupt (stub — loops forever) +// ----------------------------------------------------------------------- + .org _vector_table + 0x1C0 +_level3_vector: + j _level3_vector + +// ----------------------------------------------------------------------- +// Offset 0x200 — Level-4 interrupt (stub — loops forever) +// ----------------------------------------------------------------------- + .org _vector_table + 0x200 +_level4_vector: + j _level4_vector + +// ----------------------------------------------------------------------- +// Offset 0x240 — Level-5 interrupt (stub — loops forever) +// ----------------------------------------------------------------------- + .org _vector_table + 0x240 +_level5_vector: + j _level5_vector + +// ----------------------------------------------------------------------- +// Offset 0x280 — Debug exception / level-6 (stub — loops forever) +// ----------------------------------------------------------------------- + .org _vector_table + 0x280 +_debug_vector: + j _debug_vector + +// ----------------------------------------------------------------------- +// Offset 0x2C0 — NMI / level-7 (stub — loops forever) +// ----------------------------------------------------------------------- + .org _vector_table + 0x2C0 +_nmi_vector: + j _nmi_vector + +// ----------------------------------------------------------------------- +// Offset 0x300 — Kernel exception (stub — loops forever) +// ----------------------------------------------------------------------- + .org _vector_table + 0x300 +_kernel_vector: + j _kernel_vector + +// ----------------------------------------------------------------------- +// Offset 0x340 — User exception / level-1 interrupt +// +// Save a0 and jump to the full handler below the vector table. +// ----------------------------------------------------------------------- + .org _vector_table + 0x340 + .global _level1_vector +_level1_vector: + wsr a0, EXCSAVE1 // save a0 — only scratch register available + j _handle_level1 // jump to full handler (PC-relative, no literal pool) + +// ----------------------------------------------------------------------- +// Offset 0x3C0 — Double exception (stub — loops forever) +// ----------------------------------------------------------------------- + .org _vector_table + 0x3C0 +_double_vector: + j _double_vector + +// ----------------------------------------------------------------------- +// Level-1 interrupt handler — lives outside the vector table so there +// is no 64-byte size constraint. +// +// Saves the interrupted context on the current stack, clears PS.EXCM +// (so window overflow/underflow work), calls the Go handleInterrupt +// dispatcher, restores context, and returns via rfe. +// +// We call handleInterrupt via callx4 (window rotation by 4). This is +// required because: +// - callx0 does not set PS.CALLINC, so the Go function's "entry" +// instruction would use whatever CALLINC the interrupted code left, +// causing incorrect window rotation and a garbage stack pointer. +// - callx0 puts the return address in a0 with the raw PC (0x42xxx for +// flash), whose top 2 bits (01) cause retw to decrement WindowBase +// by 1 even though nothing was incremented. +// +// With callx4, CALLINC is explicitly set to 1 and the return address +// in a4 has the top 2 bits set to 01 — matching the window rotation +// that entry performs. After retw, WindowBase is correctly restored. +// Our a0..a3 (including a1, the frame pointer) are NOT in the callee's +// register window (callee uses physical regs +4..+19), so a1 is +// preserved across the call without needing EXCSAVE1. +// ----------------------------------------------------------------------- +// Literal data for l32r (must be at a lower address than the l32r). + .balign 4 +.LhandleInterrupt_addr: + .word handleInterrupt + + .global _handle_level1 +_handle_level1: + // --- allocate 96-byte exception frame on the interrupted stack --- + // Layout (offsets from a1 after adjustment): + // 0: a0 4: a1(orig) 8: a2 12: a3 16: a4 20: a5 + // 24: a6 28: a7 32: a8 36: a9 40: a10 44: a11 + // 48: a12 52: a13 56: a14 60: a15 + // 64: SAR 68: EPC1 72: PS + addi a0, a1, -96 // a0 = new frame pointer + s32i a1, a0, 4 // save original a1 (SP) + mov a1, a0 // a1 = frame pointer + + rsr a0, EXCSAVE1 // recover original a0 + s32i a0, a1, 0 // save original a0 + + // Save general registers a2..a15. + s32i a2, a1, 8 + s32i a3, a1, 12 + s32i a4, a1, 16 + s32i a5, a1, 20 + s32i a6, a1, 24 + s32i a7, a1, 28 + s32i a8, a1, 32 + s32i a9, a1, 36 + s32i a10, a1, 40 + s32i a11, a1, 44 + s32i a12, a1, 48 + s32i a13, a1, 52 + s32i a14, a1, 56 + s32i a15, a1, 60 + + // Save special registers. + rsr a2, SAR + s32i a2, a1, 64 + rsr a2, EPC1 + s32i a2, a1, 68 + + // Clear PS.EXCM (bit 4) so window overflow/underflow exceptions work + // during the Go call. Set PS.INTLEVEL=1 to prevent re-entry of + // level-1 interrupts. + rsr a2, PS + s32i a2, a1, 72 // save PS (with EXCM=1 set by hardware) + movi a3, ~0x1F // mask: clear INTLEVEL (bits 0-3) + EXCM (bit 4) + and a2, a2, a3 + movi a3, 1 // INTLEVEL = 1 + or a2, a2, a3 + wsr a2, PS + rsync + + // Call the Go interrupt dispatcher via callx4. + // callx4 explicitly sets PS.CALLINC=1 and puts the return address + // (with top 2 bits = 01) in a4. After entry rotates the window by + // 4, the callee sees: a0 = our a4 (return addr), a1 = our a5 - N. + // We set a5 = our frame pointer so the callee gets a valid stack. + mov a5, a1 + l32r a2, .LhandleInterrupt_addr + callx4 a2 + // After retw, WindowBase is restored. a0..a3 are preserved because + // they are outside the callee's register window. + + // --- restore context --- + + // Restore PS (restores EXCM=1). + l32i a2, a1, 72 + wsr a2, PS + rsync + + // Restore special registers. + l32i a2, a1, 64 + wsr a2, SAR + l32i a2, a1, 68 + wsr a2, EPC1 + + // Restore general registers a15..a2. + l32i a15, a1, 60 + l32i a14, a1, 56 + l32i a13, a1, 52 + l32i a12, a1, 48 + l32i a11, a1, 44 + l32i a10, a1, 40 + l32i a9, a1, 36 + l32i a8, a1, 32 + l32i a7, a1, 28 + l32i a6, a1, 24 + l32i a5, a1, 20 + l32i a4, a1, 16 + l32i a3, a1, 12 + l32i a2, a1, 8 + + // Restore a0 and a1 (a1 must be last since it is the frame pointer). + l32i a0, a1, 0 + l32i a1, a1, 4 // restores original SP (deallocates frame) + + rfe diff --git a/targets/esp32s3-supermini.json b/targets/esp32s3-supermini.json new file mode 100644 index 0000000000..bec908eb8b --- /dev/null +++ b/targets/esp32s3-supermini.json @@ -0,0 +1,4 @@ +{ + "inherits": ["esp32s3"], + "build-tags": ["esp32s3_supermini"] +} diff --git a/targets/esp32s3-wroom1.json b/targets/esp32s3-wroom1.json new file mode 100644 index 0000000000..56f8d57d21 --- /dev/null +++ b/targets/esp32s3-wroom1.json @@ -0,0 +1,4 @@ +{ + "inherits": ["esp32s3"], + "build-tags": ["esp32s3_wroom1"] +} \ No newline at end of file diff --git a/targets/esp32s3.json b/targets/esp32s3.json index f245b82ab8..9eac6dfbd6 100644 --- a/targets/esp32s3.json +++ b/targets/esp32s3.json @@ -1,21 +1,23 @@ { "inherits": ["xtensa"], + "inheritable-only": true, "cpu": "esp32s3", "features": "+atomctl,+bool,+clamps,+coprocessor,+debug,+density,+div32,+esp32s3,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+minmax,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed", "build-tags": ["esp32s3", "esp"], "scheduler": "tasks", - "serial": "uart", + "serial": "usb", "linker": "ld.lld", - "default-stack-size": 2048, + "default-stack-size": 8192, "rtlib": "compiler-rt", "libc": "picolibc", "linkerscript": "targets/esp32s3.ld", "extra-files": [ "src/device/esp/esp32.S", + "targets/esp32s3-interrupts.S", "src/internal/task/task_stack_esp32.S" ], "binary-format": "esp32s3", - "flash-command": "esptool.py --chip=esp32s3 --port {port} write_flash 0x0000 {bin} -ff 80m -fm dout", + "flash-method": "esp32jtag", "emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw", "gdb": ["xtensa-esp32-elf-gdb"] } diff --git a/targets/esp32s3.ld b/targets/esp32s3.ld index 3d23174a04..28181f5b5b 100644 --- a/targets/esp32s3.ld +++ b/targets/esp32s3.ld @@ -39,6 +39,19 @@ SECTIONS *(.text.call_start_cpu0) } >IRAM AT >DRAM +/* Xtensa exception/interrupt vector table — must be 0x400-aligned. */ +.text.exception_vectors : ALIGN(0x400) +{ + *(.text.exception_vectors) +} >IRAM AT >DRAM + +/* Level-1 interrupt handler (called from the vector stub). */ +.text._handle_level1 : ALIGN(4) +{ + *(.literal._handle_level1) + *(.text._handle_level1) +} >IRAM AT >DRAM + /* All other code and literals */ .text : ALIGN(4) { diff --git a/targets/esp8266.json b/targets/esp8266.json index bb02c3db29..83d5bcd1bb 100644 --- a/targets/esp8266.json +++ b/targets/esp8266.json @@ -1,5 +1,6 @@ { "inherits": ["xtensa"], + "inheritable-only": true, "cpu": "esp8266", "features": "+debug,+density,+exception,+extendedl32r,+highpriinterrupts,+interrupt,+mul32,+nsa,+prid,+regprotect,+rvector,+timerint", "build-tags": ["esp8266", "esp"], @@ -14,5 +15,5 @@ "src/internal/task/task_stack_esp8266.S" ], "binary-format": "esp8266", - "flash-command": "esptool.py --chip=esp8266 --port {port} write_flash 0x00000 {bin} -fm qio" + "flash-method": "esp32flash" } diff --git a/targets/nucleo-f722ze.json b/targets/nucleo-f722ze.json index f426b332d7..88d7aed3ec 100644 --- a/targets/nucleo-f722ze.json +++ b/targets/nucleo-f722ze.json @@ -1,10 +1,10 @@ { "inherits": ["cortex-m7"], - "build-tags": ["nucleof722ze", "stm32f7x2", "stm32f7", "stm32"], + "build-tags": ["nucleof722ze", "stm32f722", "stm32f7x2", "stm32f7", "stm32"], "serial": "uart", "linkerscript": "targets/stm32f7x2zetx.ld", "extra-files": [ - "src/device/stm32/stm32f7x2.s" + "src/device/stm32/stm32f722.s" ], "flash-method": "openocd", "openocd-interface": "stlink-v2-1", diff --git a/targets/rp2040.json b/targets/rp2040.json index 3f9fea4590..e2969fea5a 100644 --- a/targets/rp2040.json +++ b/targets/rp2040.json @@ -1,7 +1,8 @@ { "inherits": ["cortex-m0plus"], + "inheritable-only": true, "build-tags": ["rp2040", "rp"], - "scheduler": "cores", + "scheduler": "tasks", "flash-1200-bps-reset": "true", "flash-method": "msd", "serial": "usb", diff --git a/targets/rp2350.json b/targets/rp2350.json index ebffcbdece..f4567652da 100644 --- a/targets/rp2350.json +++ b/targets/rp2350.json @@ -1,7 +1,8 @@ { "inherits": ["cortex-m33"], + "inheritable-only": true, "build-tags": ["rp2350", "rp"], - "scheduler": "cores", + "scheduler": "tasks", "flash-1200-bps-reset": "true", "flash-method": "msd", "serial": "usb", diff --git a/targets/rp2350b.json b/targets/rp2350b.json index 5db4d48492..6e628c6b77 100644 --- a/targets/rp2350b.json +++ b/targets/rp2350b.json @@ -1,5 +1,6 @@ { "inherits": ["rp2350"], + "inheritable-only": true, "build-tags": ["rp2350b"], "serial-port": ["2e8a:000f"] } \ No newline at end of file diff --git a/targets/stm32u585.json b/targets/stm32u585.json new file mode 100644 index 0000000000..2b6d6eeb38 --- /dev/null +++ b/targets/stm32u585.json @@ -0,0 +1,16 @@ +{ + "inherits": [ + "cortex-m33" + ], + "build-tags": [ + "stm32u585", + "stm32u5", + "stm32" + ], + "extra-files": [ + "src/device/stm32/stm32u585.s" + ], + "linkerscript": "targets/stm32u585.ld", + "flash-method": "openocd", + "openocd-target": "stm32u5x" +} diff --git a/targets/stm32u585.ld b/targets/stm32u585.ld new file mode 100644 index 0000000000..c2ffd3c003 --- /dev/null +++ b/targets/stm32u585.ld @@ -0,0 +1,10 @@ + +MEMORY +{ + FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 2048K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 768K +} + +_stack_size = 4K; + +INCLUDE "targets/arm.ld" diff --git a/targets/swan.json b/targets/swan.json index fbb46a5569..92587690e8 100644 --- a/targets/swan.json +++ b/targets/swan.json @@ -1,6 +1,6 @@ { "inherits": ["cortex-m4"], - "build-tags": ["swan", "stm32l4r5", "stm32l4x5", "stm32l4", "stm32"], + "build-tags": ["swan", "stm32l4r5", "stm32l4y5", "stm32l4", "stm32"], "serial": "uart", "linkerscript": "targets/stm32l4x5.ld", "extra-files": [ diff --git a/targets/vicharak_shrike-lite.json b/targets/vicharak_shrike-lite.json new file mode 100644 index 0000000000..c801b2c27b --- /dev/null +++ b/targets/vicharak_shrike-lite.json @@ -0,0 +1,14 @@ +{ + "inherits": [ + "rp2040" + ], + "serial-port": ["2e8a:0003"], + "default-stack-size": 8192, + "build-tags": ["vicharak_shrike_lite"], + "ldflags": [ + "--defsym=__flash_size=4M" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} diff --git a/targets/xiao-rp2350.json b/targets/xiao-rp2350.json new file mode 100644 index 0000000000..dd65bbcbb9 --- /dev/null +++ b/targets/xiao-rp2350.json @@ -0,0 +1,8 @@ +{ + "inherits": [ + "rp2350" + ], + "serial-port": ["2e8a:000a"], + "build-tags": ["xiao_rp2350"], + "default-stack-size": 8192 +} diff --git a/testdata/corpus.yaml b/testdata/corpus.yaml index 0ed29adbe4..36ac337cf6 100644 --- a/testdata/corpus.yaml +++ b/testdata/corpus.yaml @@ -29,8 +29,7 @@ - repo: github.com/dgryski/go-camellia - repo: github.com/dgryski/go-change - repo: github.com/dgryski/go-chaskey - tags: appengine noasm - skipwasi: true # siphash has build tag issues + tags: appengine noasm # for dchest/siphash - repo: github.com/dgryski/go-clefia - repo: github.com/dgryski/go-clockpro - repo: github.com/dgryski/go-cobs @@ -56,7 +55,6 @@ - repo: github.com/dgryski/go-linlog - repo: github.com/dgryski/go-maglev tags: appengine # for dchest/siphash - skipwasi: true - repo: github.com/dgryski/go-marvin32 - repo: github.com/dgryski/go-md5crypt - repo: github.com/dgryski/go-metro @@ -66,7 +64,6 @@ tags: noasm - repo: github.com/dgryski/go-mpchash tags: appengine # for dchest/siphash - skipwasi: true - repo: github.com/dgryski/go-neeva - repo: github.com/dgryski/go-nibz - repo: github.com/dgryski/go-nibblesort @@ -289,3 +286,8 @@ - repo: github.com/philhofer/fwd - repo: github.com/blevesearch/sear - repo: github.com/steveyen/gtreap +- repo: github.com/orsinium-labs/tinymath +- repo: github.com/orsinium-labs/jsony +- repo: github.com/tidwall/gjson +- repo: github.com/dchest/siphash + tags: appengine diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 1a3d539621..ef9dedff97 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" "regexp" + "slices" "sort" "strconv" "strings" @@ -18,6 +19,7 @@ import ( ) var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$") +var validDimableName = regexp.MustCompile(`^((%s)|(%s)[_A-Za-z]{1}[_A-Za-z0-9]*)|([_A-Za-z]{1}[_A-Za-z0-9]*(\[%s\])?)|([_A-Za-z]{1}[_A-Za-z0-9]*(%s)?[_A-Za-z0-9]*)$`) var enumBitSpecifier = regexp.MustCompile("^#x*[01]+[01x]*$") type SVDFile struct { @@ -60,22 +62,18 @@ type SVDRegister struct { } type SVDField struct { - Name string `xml:"name"` - Description string `xml:"description"` - Lsb *uint32 `xml:"lsb"` - Msb *uint32 `xml:"msb"` - BitOffset *uint32 `xml:"bitOffset"` - BitWidth *uint32 `xml:"bitWidth"` - BitRange *string `xml:"bitRange"` - EnumeratedValues struct { - DerivedFrom string `xml:"derivedFrom,attr"` - Name string `xml:"name"` - EnumeratedValue []struct { - Name string `xml:"name"` - Description string `xml:"description"` - Value string `xml:"value"` - } `xml:"enumeratedValue"` - } `xml:"enumeratedValues"` + DerivedFrom string `xml:"derivedFrom,attr"` + Name string `xml:"name"` + Description string `xml:"description"` + Dim *string `xml:"dim"` + DimIndex *string `xml:"dimIndex"` + DimIncrement string `xml:"dimIncrement"` + Lsb *uint32 `xml:"lsb"` + Msb *uint32 `xml:"msb"` + BitOffset *uint32 `xml:"bitOffset"` + BitWidth *uint32 `xml:"bitWidth"` + BitRange *string `xml:"bitRange"` + EnumeratedValues []SVDEnumeration `xml:"enumeratedValues"` } type SVDCluster struct { @@ -89,6 +87,17 @@ type SVDCluster struct { AddressOffset string `xml:"addressOffset"` } +type SVDEnumeration struct { + DerivedFrom string `xml:"derivedFrom,attr"` + Name string `xml:"name"` + EnumeratedValue []struct { + Name string `xml:"name"` + Description string `xml:"description"` + Value string `xml:"value"` + IsDefault bool `xml:"isDefault"` + } `xml:"enumeratedValue"` +} + type Device struct { Metadata *Metadata Interrupts []*Interrupt @@ -120,6 +129,7 @@ type Interrupt struct { type Peripheral struct { Name string + Alias string GroupName string BaseAddress uint64 Description string @@ -173,23 +183,36 @@ func splitLine(s string) []string { // Replace characters that are not allowed in a symbol name with a '_'. This is // useful to be able to process SVD files with errors. func cleanName(text string) string { - if !validName.MatchString(text) { - result := make([]rune, 0, len(text)) - for _, c := range text { + return cleanIdentifier(text, validName) +} + +func cleanDimableName(text string) string { + return cleanIdentifier(text, validDimableName) +} + +func cleanIdentifier(text string, valid *regexp.Regexp) string { + text = cleanString(text, valid) + if len(text) != 0 && (text[0] >= '0' && text[0] <= '9') { + // Identifiers may not start with a number. + // Add an underscore instead. + text = "_" + text + } + return text +} + +func cleanString(s string, valid *regexp.Regexp) string { + if !valid.MatchString(s) { + result := make([]rune, 0, len(s)) + for _, c := range s { if validName.MatchString(string(c)) { result = append(result, c) } else { result = append(result, '_') } } - text = string(result) + s = string(result) } - if len(text) != 0 && (text[0] >= '0' && text[0] <= '9') { - // Identifiers may not start with a number. - // Add an underscore instead. - text = "_" + text - } - return text + return s } func processSubCluster(p *Peripheral, cluster *SVDCluster, clusterOffset uint64, clusterName string, peripheralDict map[string]*Peripheral) []*Peripheral { @@ -353,6 +376,7 @@ func readSVD(path, sourceURL string) (*Device, error) { // comes later in the file. To make sure this works, sort the peripherals if // needed. orderedPeripherals := orderPeripherals(device.Peripherals) + globalDerivationCtx.peripherals = orderedPeripherals for _, periphEl := range orderedPeripherals { description := formatText(periphEl.Description) @@ -490,21 +514,42 @@ func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral { var sortedPeripherals []*SVDPeripheral var missingBasePeripherals []*SVDPeripheral knownBasePeripherals := map[string]struct{}{} - for i := range input { - p := &input[i] + + tryProcess := func(p *SVDPeripheral) { groupName := p.GroupName - if groupName == "" { - groupName = p.Name + if groupName != "" { + knownBasePeripherals[groupName] = struct{}{} } - knownBasePeripherals[groupName] = struct{}{} + knownBasePeripherals[p.Name] = struct{}{} if p.DerivedFrom != "" { if _, ok := knownBasePeripherals[p.DerivedFrom]; !ok { missingBasePeripherals = append(missingBasePeripherals, p) - continue + return } } sortedPeripherals = append(sortedPeripherals, p) } + for i := range input { + tryProcess(&input[i]) + } + orderPeripheralsByNumBitfields(sortedPeripherals) + + // missingBasePeripherals may still contain unordered entries; + // repeat the process until missingBasePeripheral does not change anymore. + prevNumPending := 0 + for { + pending := missingBasePeripherals + if len(pending) == prevNumPending { + break + } + // reuse the same slice as input and for keeping track of + // missing base periphal + missingBasePeripherals = missingBasePeripherals[:0] + for _, p := range pending { + tryProcess(p) + } + prevNumPending = len(pending) + } // Let's hope all base peripherals are now included. sortedPeripherals = append(sortedPeripherals, missingBasePeripherals...) @@ -512,6 +557,51 @@ func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral { return sortedPeripherals } +func orderPeripheralsByNumBitfields(list []*SVDPeripheral) { + seenGroup := make(map[string]struct{}) + for i, p := range list { + groupName := p.GroupName + if groupName == "" || p.DerivedFrom != "" { + continue + } + if _, ok := seenGroup[groupName]; ok { + continue + } + iMax, nMax := -1, p.bitfieldCount() + for j, p2 := range list[i+1:] { + if p2.GroupName != groupName || p2.DerivedFrom != "" { + continue + } + if n2 := p2.bitfieldCount(); n2 > nMax { + iMax = i + 1 + j + nMax = n2 + } + } + if iMax != -1 { + pMax := list[iMax] + // swap peripherals + copy(list[i+1:iMax+1], list[i:iMax]) + list[i] = pMax + seenGroup[groupName] = struct{}{} + } + } +} + +func (p *SVDPeripheral) bitfieldCount() int { + n := 0 + for _, r := range p.Registers { + for _, f := range r.Fields { + dim := decodeDim(f.Dim) + if dim > 0 { + n += dim + } else { + n++ + } + } + } + return n +} + func addInterrupt(interrupts map[string]*Interrupt, name, interruptName string, index int, description string) { if _, ok := interrupts[name]; ok { if interrupts[name].Value != index { @@ -544,14 +634,23 @@ func addInterrupt(interrupts map[string]*Interrupt, name, interruptName string, func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPrefix string) ([]Constant, []Bitfield) { var fields []Constant var bitfields []Bitfield + var enumDefault enumDefaultResolver enumSeen := map[string]int64{} for _, fieldEl := range fieldEls { + + if fieldEl.DerivedFrom != "" { + err := globalDerivationCtx.deriveField(fieldEl, fieldEls) + if err != nil { + fmt.Fprintf(os.Stderr, "unable to derive field %q from %q: %v\n", fieldEl.Name, fieldEl.DerivedFrom, err.Error()) + } + } + // Some bitfields (like the STM32H7x7) contain invalid bitfield // names like "CNT[31]". Replace invalid characters with "_" when // needed. - fieldName := cleanName(fieldEl.Name) - if !unicode.IsUpper(rune(fieldName[0])) && !unicode.IsDigit(rune(fieldName[0])) { - fieldName = strings.ToUpper(fieldName) + fieldNameTpl := cleanDimableName(fieldEl.Name) + if !unicode.IsUpper(rune(fieldNameTpl[0])) && !unicode.IsDigit(rune(fieldNameTpl[0])) { + fieldNameTpl = strings.ReplaceAll(strings.ToUpper(fieldNameTpl), "%S", "%s") } // Find the lsb/msb that is encoded in various ways. @@ -581,129 +680,332 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre msb = uint32(m) } else { // this is an error. what to do? - fmt.Fprintln(os.Stderr, "unable to find lsb/msb in field:", fieldName) + fmt.Fprintln(os.Stderr, "unable to find lsb/msb in field:", fieldNameTpl) continue } - // The enumerated values can be the same as another field, so to avoid - // duplication SVD files can simply refer to another set of enumerated - // values in the same register. - // See: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_registers.html#elem_enumeratedValues - enumeratedValues := fieldEl.EnumeratedValues - if enumeratedValues.DerivedFrom != "" { - parts := strings.Split(enumeratedValues.DerivedFrom, ".") - if len(parts) == 1 { - found := false - for _, otherFieldEl := range fieldEls { - if otherFieldEl.EnumeratedValues.Name == parts[0] { - found = true - enumeratedValues = otherFieldEl.EnumeratedValues + da := decodeDimArray(fieldEl.Dim, fieldEl.DimIndex, fieldEl.DimIncrement, "field", fieldNameTpl) + da.rangeElems(func(ia int, _ uint32) bool { + if da != nil { + lsb += da.incr + msb += da.incr + } + fieldName := da.replace(fieldNameTpl, ia) + + // The enumerated values can be the same as another field, so to avoid + // duplication SVD files can simply refer to another set of enumerated + // values in the same register. + // See: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_registers.html#elem_enumeratedValues + for i := range fieldEl.EnumeratedValues { + enumeratedValues := &fieldEl.EnumeratedValues[i] + if enumeratedValues.DerivedFrom != "" { + parts := strings.Split(enumeratedValues.DerivedFrom, ".") + if len(parts) == 1 { + found := false + for _, otherFieldEl := range fieldEls { + for i := range otherFieldEl.EnumeratedValues { + otherEnum := &otherFieldEl.EnumeratedValues[i] + if otherEnum.Name == parts[0] { + found = true + *enumeratedValues = *otherEnum + } + } + } + if !found { + fmt.Fprintf(os.Stderr, "Warning: could not find enumeratedValue.derivedFrom of %s for register field %s\n", enumeratedValues.DerivedFrom, fieldName) + } + } else { + // The derivedFrom attribute may also point to enumerated values + // in other registers and even peripherals, but this feature + // isn't often used in SVD files. + fmt.Fprintf(os.Stderr, "TODO: enumeratedValue.derivedFrom to a different register: %s\n", enumeratedValues.DerivedFrom) } } - if !found { - fmt.Fprintf(os.Stderr, "Warning: could not find enumeratedValue.derivedFrom of %s for register field %s\n", enumeratedValues.DerivedFrom, fieldName) - } - } else { - // The derivedFrom attribute may also point to enumerated values - // in other registers and even peripherals, but this feature - // isn't often used in SVD files. - fmt.Fprintf(os.Stderr, "TODO: enumeratedValue.derivedFrom to a different register: %s\n", enumeratedValues.DerivedFrom) } - } - bitfields = append(bitfields, Bitfield{ - Name: fieldName, - Offset: lsb, - Mask: (0xffffffff >> (31 - (msb - lsb))) << lsb, - }) - fields = append(fields, Constant{ - Name: fmt.Sprintf("%s_%s%s_%s_Pos", groupName, bitfieldPrefix, regName, fieldName), - Description: fmt.Sprintf("Position of %s field.", fieldName), - Value: uint64(lsb), - }) - fields = append(fields, Constant{ - Name: fmt.Sprintf("%s_%s%s_%s_Msk", groupName, bitfieldPrefix, regName, fieldName), - Description: fmt.Sprintf("Bit mask of %s field.", fieldName), - Value: (0xffffffffffffffff >> (63 - (msb - lsb))) << lsb, - }) - if lsb == msb { // single bit + bitfields = append(bitfields, Bitfield{ + Name: fieldName, + Offset: lsb, + Mask: (0xffffffff >> (31 - (msb - lsb))) << lsb, + }) fields = append(fields, Constant{ - Name: fmt.Sprintf("%s_%s%s_%s", groupName, bitfieldPrefix, regName, fieldName), - Description: fmt.Sprintf("Bit %s.", fieldName), - Value: 1 << lsb, + Name: fmt.Sprintf("%s_%s%s_%s_Pos", groupName, bitfieldPrefix, regName, fieldName), + Description: fmt.Sprintf("Position of %s field.", fieldName), + Value: uint64(lsb), }) - } - for _, enumEl := range enumeratedValues.EnumeratedValue { - enumName := enumEl.Name - // Renesas has enum without actual values that we have to skip - if enumEl.Value == "" { - continue + fields = append(fields, Constant{ + Name: fmt.Sprintf("%s_%s%s_%s_Msk", groupName, bitfieldPrefix, regName, fieldName), + Description: fmt.Sprintf("Bit mask of %s field.", fieldName), + Value: (0xffffffffffffffff >> (63 - (msb - lsb))) << lsb, + }) + if lsb == msb { // single bit + fields = append(fields, Constant{ + Name: fmt.Sprintf("%s_%s%s_%s", groupName, bitfieldPrefix, regName, fieldName), + Description: fmt.Sprintf("Bit %s.", fieldName), + Value: 1 << lsb, + }) } + for i := range fieldEl.EnumeratedValues { + enumDefault.reset(1<<(msb+1-lsb) - 1) + fields0Pos := len(fields) + for _, enumEl := range fieldEl.EnumeratedValues[i].EnumeratedValue { + enumName := enumEl.Name + + if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { + continue + } + if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) { + enumName = strings.ToUpper(enumName) + } + enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName) + enumDescription := formatText(enumEl.Description) + + if enumEl.IsDefault { + enumDefault.setDefaultAction(func(value uint64) { + if value == 0 { + // put zero value in front of other constants + fields = slices.Insert(fields, fields0Pos, Constant{}) + appendConstant(fields[:fields0Pos], enumName, enumDescription, value, enumSeen) + } else { + fields = appendConstant(fields, enumName, enumDescription, value, enumSeen) + } + }) + continue + } - if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { - continue + // Renesas has enum without actual values that we have to skip + if enumEl.Value == "" { + continue + } + + var enumValue uint64 + var err error + if strings.HasPrefix(enumEl.Value, "0b") { + val := strings.TrimPrefix(enumEl.Value, "0b") + enumValue, err = strconv.ParseUint(val, 2, 64) + } else { + enumValue, err = strconv.ParseUint(enumEl.Value, 0, 64) + } + if err != nil { + if enumBitSpecifier.MatchString(enumEl.Value) { + // NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values + enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64) + if err != nil { + panic(err) + } + } else { + panic(err) + } + } + enumDefault.collectValue(enumValue) + fields = appendConstant(fields, enumName, enumDescription, enumValue, enumSeen) + } + enumDefault.resolve() } - if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) { - enumName = strings.ToUpper(enumName) + return true + }) + } + return fields, bitfields +} + +var globalDerivationCtx derivationContext + +type derivationContext struct { + peripherals []*SVDPeripheral +} + +func (ctx *derivationContext) deriveField(fieldEl *SVDField, localFieldEls []*SVDField) error { + from := fieldEl.DerivedFrom + parts := strings.Split(from, ".") + srcName := parts[0] + var srcFieldEl *SVDField + switch len(parts) { + case 3, 4: + src, err := ctx.lookupGlobal(parts) + if err != nil { + return err + } + srcFieldEl = src + case 1: + // resolve locally, in current register + for _, f := range localFieldEls { + if f == fieldEl { + continue } - enumDescription := formatText(enumEl.Description) - var enumValue uint64 - var err error - if strings.HasPrefix(enumEl.Value, "0b") { - val := strings.TrimPrefix(enumEl.Value, "0b") - enumValue, err = strconv.ParseUint(val, 2, 64) - } else { - enumValue, err = strconv.ParseUint(enumEl.Value, 0, 64) + if f.Name == srcName { + srcFieldEl = f + break } - if err != nil { - if enumBitSpecifier.MatchString(enumEl.Value) { - // NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values - enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64) - if err != nil { - panic(err) + } + if srcFieldEl == nil { + return fmt.Errorf("not found") + } + default: + return fmt.Errorf("cannot decode source path") + } + + // copy enumeratedValues from source to current field + if fieldEl.DimIndex == nil && strings.Contains(fieldEl.Name, "%s") { + fieldEl.DimIndex = srcFieldEl.DimIndex + fieldEl.DimIncrement = srcFieldEl.DimIncrement + fieldEl.Dim = srcFieldEl.Dim + } + if fieldEl.Description == "" { + fieldEl.Description = srcFieldEl.Description + } + if fieldEl.BitWidth == nil { + fieldEl.BitWidth = srcFieldEl.BitWidth + } + if fieldEl.BitOffset == nil { + fieldEl.BitOffset = srcFieldEl.BitOffset + } + if fieldEl.BitRange == nil { + fieldEl.BitRange = srcFieldEl.BitRange + } + + fieldEl.EnumeratedValues = srcFieldEl.EnumeratedValues + return nil +} + +func (ctx *derivationContext) lookupGlobal(path []string) (*SVDField, error) { + curPath := path[:1] + for _, p := range ctx.peripherals { + if p.Name == path[0] { + if len(path) == 4 { + curPath = path[:2] + for _, c := range p.Clusters { + if c.Name == path[1] { + return ctx.lookupFieldInRegs(path[2:], c.Registers, curPath) } - } else { - panic(err) } + return nil, fmt.Errorf("cluster not found: %q", path[2]) + } - enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName) - - // Avoid duplicate values. Duplicate names with the same value are - // allowed, but the same name with a different value is not. Instead - // of trying to work around those cases, remove the value entirely - // as there is probably not one correct answer in such a case. - // For example, SVD files from NXP have enums limited to 20 - // characters, leading to lots of duplicates when these enum names - // are long. Nothing here can really fix those cases. - previousEnumValue, seenBefore := enumSeen[enumName] - if seenBefore { - if previousEnumValue < 0 { - // There was a mismatch before, ignore all equally named fields. - continue + return ctx.lookupFieldInRegs(path[1:], p.Registers, curPath) + } + } + return nil, fmt.Errorf("peripheral not found: %s", path[0]) +} + +func (ctx *derivationContext) lookupFieldInRegs(path []string, registers []*SVDRegister, curPath []string) (*SVDField, error) { + curPath = curPath[:len(curPath)+1] + for _, r := range registers { + if r.Name == path[0] { + curPath = curPath[:len(curPath)+1] + for _, f := range r.Fields { + if f.Name == path[1] { + return f, nil } - if int64(enumValue) != previousEnumValue { - // There is a mismatch. Mark it as such, and remove the - // existing enum bitfield value. - enumSeen[enumName] = -1 - for i, field := range fields { - if field.Name == enumName { - fields = append(fields[:i], fields[i+1:]...) - break - } - } + } + return nil, fmt.Errorf("field not found: %q", strings.Join(curPath, ".")) + } + } + return nil, fmt.Errorf("register not found: %q", strings.Join(curPath, ".")) +} + +func appendConstant(fields []Constant, enumName, enumDescription string, enumValue uint64, enumSeen map[string]int64) []Constant { + // Avoid duplicate values. Duplicate names with the same value are + // allowed, but the same name with a different value is not. Instead + // of trying to work around those cases, remove the value entirely + // as there is probably not one correct answer in such a case. + // For example, SVD files from NXP have enums limited to 20 + // characters, leading to lots of duplicates when these enum names + // are long. Nothing here can really fix those cases. + previousEnumValue, seenBefore := enumSeen[enumName] + if seenBefore { + if previousEnumValue < 0 { + // There was a mismatch before, ignore all equally named fields. + return fields + } + if int64(enumValue) != previousEnumValue { + // There is a mismatch. Mark it as such, and remove the + // existing enum bitfield value. + enumSeen[enumName] = -1 + for i, field := range fields { + if field.Name == enumName { + fields = append(fields[:i], fields[i+1:]...) + break } - continue } - enumSeen[enumName] = int64(enumValue) + } + return fields + } + enumSeen[enumName] = int64(enumValue) - fields = append(fields, Constant{ - Name: enumName, - Description: enumDescription, - Value: enumValue, - }) + fields = append(fields, Constant{ + Name: enumName, + Description: enumDescription, + Value: enumValue, + }) + return fields +} + +// enumDefaultResolver helps determine the actual numeric value for an +// enumeratedValue marked as the default (i.e., where "isDefault" is set). +// +// Some SVD files use "isDefault" to indicate a fallback value (e.g., Div1 in +// clock prescaler registers) without specifying the exact value when it's not +// critical. This type is used to collect all defined enumValues, and once +// collection is complete, derive a sensible default value that does not conflict +// with any explicitly defined ones. +// +// Typically, it prefers zero as a default if available; otherwise, it will +// choose a suitable unused value below the field's maximum. +type enumDefaultResolver struct { + values []uint64 + maxValue uint64 + handleDefault func(value uint64) +} + +func (dr *enumDefaultResolver) reset(maxValue uint64) { + dr.values = dr.values[:0] + dr.maxValue = maxValue + dr.handleDefault = nil +} + +func (dr *enumDefaultResolver) setDefaultAction(action func(v uint64)) { + dr.handleDefault = action +} + +func (dr *enumDefaultResolver) collectValue(value uint64) { + dr.values = append(dr.values, value) +} + +// resolve tries to find an actual value for the enumerated Value +// marked as default. +func (dr *enumDefaultResolver) resolve() { + if dr.handleDefault == nil { + return + } + list := dr.values + n := len(list) + if n == 0 { + return + } + slices.Sort(list) + + var value uint64 + // try to use zero as default value + if list[0] == 0 { + // not available, now try the highest value +1 + largest := list[n-1] + if largest < dr.maxValue { + value = largest + 1 + } else { + value = 1 + // not available, now lookup the first free value + for _, enumValue := range list[1:] { + if value < enumValue { + break + } + value = enumValue + 1 + if value == dr.maxValue { + return + } + } } } - return fields, bitfields + dr.handleDefault(value) } type Register struct { @@ -739,38 +1041,57 @@ func (r *Register) address() uint64 { } func (r *Register) dim() int { - if r.element.Dim == nil { + return decodeDim(r.element.Dim) +} + +func decodeDim(s *string) int { + if s == nil { return -1 // no dim elements } - dim, err := strconv.ParseInt(*r.element.Dim, 0, 32) + dim, err := strconv.ParseInt(*s, 0, 32) if err != nil { panic(err) } return int(dim) } -func (r *Register) dimIndex() []string { +type dimArray struct { + dim int + idx []string + incr uint32 +} + +func decodeDimArray(dimSpec, dimIndex *string, dimIncr, elType, elName string) *dimArray { + dim := decodeDim(dimSpec) + if dim <= 0 { + return nil + } + a := new(dimArray) + a.dim = dim + defer func() { if err := recover(); err != nil { - fmt.Println("register", r.name()) + fmt.Println(elType, elName) panic(err) } }() - dim := r.dim() - if r.element.DimIndex == nil { - if dim <= 0 { - return nil - } + incr, err := strconv.ParseUint(dimIncr, 0, 32) + if err != nil { + panic(err) + } + a.incr = uint32(incr) + if dimIndex == nil { idx := make([]string, dim) for i := range idx { idx[i] = strconv.FormatInt(int64(i), 10) } - return idx + a.idx = idx + return a } - t := strings.Split(*r.element.DimIndex, "-") + t := strings.Split(*dimIndex, "-") if len(t) == 2 { // renesas uses hex letters e.g. A-B if strings.Contains("ABCDEFabcdef", t[0]) { @@ -797,17 +1118,40 @@ func (r *Register) dimIndex() []string { for i := x; i <= y; i++ { idx[i-x] = strconv.FormatInt(i, 10) } - return idx + a.idx = idx + return a } else if len(t) > 2 { panic("invalid dimIndex") } - s := strings.Split(*r.element.DimIndex, ",") + s := strings.Split(*dimIndex, ",") if len(s) != dim { panic("invalid dimIndex") } + a.idx = s + return a +} - return s +func (da *dimArray) replace(s string, i int) string { + if da == nil { + return s + } + if i >= len(da.idx) { + return s + } + return strings.ReplaceAll(s, "%s", da.idx[i]) +} + +func (da *dimArray) rangeElems(yield func(i int, incr uint32) bool) { + if da == nil { + yield(0, 0) + return + } + for i := range da.dim { + if !yield(i, uint32(i)*da.incr) { + return + } + } } func (r *Register) size() int { @@ -823,37 +1167,32 @@ func (r *Register) size() int { func parseRegister(groupName string, regEl *SVDRegister, baseAddress uint64, bitfieldPrefix string) []*PeripheralField { reg := NewRegister(regEl, baseAddress) - - if reg.dim() != -1 { - dimIncrement, err := strconv.ParseUint(regEl.DimIncrement, 0, 32) - if err != nil { - panic(err) + name := reg.name() + da := decodeDimArray(regEl.Dim, regEl.DimIndex, regEl.DimIncrement, "register", name) + if da != nil && strings.Contains(name, "%s") { + // a "spaced array" of registers, special processing required + // we need to generate a separate register for each "element" + var results []*PeripheralField + shortName := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(name, "_%s", ""), "%s", "")) + for i := range da.idx { + regAddress := reg.address() + (uint64(i) * uint64(da.incr)) + results = append(results, &PeripheralField{ + Name: strings.ToUpper(da.replace(name, i)), + Address: regAddress, + Description: reg.description(), + Array: -1, + ElementSize: reg.size(), + ShortName: shortName, + }) } - if strings.Contains(reg.name(), "%s") { - // a "spaced array" of registers, special processing required - // we need to generate a separate register for each "element" - var results []*PeripheralField - shortName := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(reg.name(), "_%s", ""), "%s", "")) - for i, j := range reg.dimIndex() { - regAddress := reg.address() + (uint64(i) * dimIncrement) - results = append(results, &PeripheralField{ - Name: strings.ToUpper(strings.ReplaceAll(reg.name(), "%s", j)), - Address: regAddress, - Description: reg.description(), - Array: -1, - ElementSize: reg.size(), - ShortName: shortName, - }) - } - // set first result bitfield - results[0].Constants, results[0].Bitfields = parseBitfields(groupName, shortName, regEl.Fields, bitfieldPrefix) - results[0].HasBitfields = len(results[0].Bitfields) > 0 - for i := 1; i < len(results); i++ { - results[i].Bitfields = results[0].Bitfields - results[i].HasBitfields = results[0].HasBitfields - } - return results + // set first result bitfield + results[0].Constants, results[0].Bitfields = parseBitfields(groupName, shortName, regEl.Fields, bitfieldPrefix) + results[0].HasBitfields = len(results[0].Bitfields) > 0 + for i := 1; i < len(results); i++ { + results[i].Bitfields = results[0].Bitfields + results[i].HasBitfields = results[0].HasBitfields } + return results } regName := reg.name() if !unicode.IsUpper(rune(regName[0])) && !unicode.IsDigit(rune(regName[0])) { @@ -979,14 +1318,20 @@ var ( {{- end}} {{- end}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) + {{- if .Alias}} + {{.Alias}} = {{.Name}} + {{- end}} {{- "\n"}} {{- end}} ) `)) + pkgName := filepath.Base(strings.TrimRight(outdir, "/")) + tweakDevice(device, pkgName) + err = t.Execute(w, map[string]interface{}{ "device": device, - "pkgName": filepath.Base(strings.TrimRight(outdir, "/")), + "pkgName": pkgName, "interruptMax": maxInterruptValue, "interruptSystem": interruptSystem, "interruptHandlers": interruptHandlers, diff --git a/tools/gen-device-svd/tweak.go b/tools/gen-device-svd/tweak.go new file mode 100644 index 0000000000..8ee505da14 --- /dev/null +++ b/tools/gen-device-svd/tweak.go @@ -0,0 +1,124 @@ +package main + +import ( + "slices" + "strings" +) + +func tweakDevice(d *Device, pkgName string) { + if pkgName != "stm32" { + // no-op for device types that do not need tweaks + return + } + + // Source file machine_stm32_iwdg.go relies on the presence of + // a register IWDG. On some devices, though, like the h723, + // there are two registers, IWDG1 and IWDG2. In this case we + // define an alias IWDG for IWDG1. + addUnnumberedAlias(d, "IWDG", "IWDG1") + + for _, p := range d.Peripherals { + switch p.GroupName { + case "TIM": + // SVDs like stm32l4r5.svd define CCMR*_Input and _Output + // alternate registers, with _Input sorted before _Output. + // This would result in the _Output fields missing from the + // TIM_type struct definition, hence compilation would fail. + // Therefore we adjust the order of these alternate registers + // accordingly. + stm32EnsureCCMROrder(p.Registers) + + case "USART": + isr := p.lookupRegister("ISR") + if isr == nil { + continue + } + + // Some of the upstream SVD files, like the one for stm32wl5x_cm4, + // lack FIFO enabled variants of the USART ISR register, + // even if the register manual defines them. To make sure + // that TXFNF is not missing from the generated .go files, + // we add TXFNF here in case FIFOEN is present. + if p.lookupRegister("CR1").hasBitfield("FIFOEN") { + stm32EnsureBit(isr, "TXFNF", "TXE", "USART_ISR_") + } + + // Svdtools handles the presence of alternate USART ISR registers, + // like in case of the stm32l4r5, adjusting names like "ISR_enabled" + // to "ISR", deleting "ISR_disabled" or "ISR_ALTERNATE" register definitions + // from the SVD. + // As this would result in USART_ISR_TXE definitions missing in the + // generated .go file, a constant for TXE is added here + // in case TXFNF is defined. + stm32EnsureBit(isr, "TXE", "TXFNF", "USART_ISR_") + } + } +} + +func addUnnumberedAlias(d *Device, dest, src string) { + if _, ok := d.PeripheralDict[dest]; !ok { + if p := d.PeripheralDict[src]; p != nil { + p.Alias = dest + } + } +} + +func stm32EnsureCCMROrder(registers []*PeripheralField) { + for i, r := range registers { + if i > 0 { + prev := registers[i-1] + if r.Address == prev.Address { + // alternate field + if strings.HasPrefix(prev.Name, "CCMR") && strings.HasPrefix(r.Name, "CCMR") && strings.HasSuffix(r.Name, "_Output") { + // swap register pointers + registers[i-1], registers[i] = r, prev + } + } + } + } +} + +func stm32EnsureBit(reg *PeripheralField, want, have, prefix string) { + iWant := -1 + iHave := -1 + wantConst := prefix + want + haveConst := prefix + have + for i := range reg.Constants { + f := ®.Constants[i] + if f.Name == wantConst { + iWant = i + break + } + if f.Name == haveConst { + iHave = i + break + } + } + if iHave != -1 && iWant == -1 { + iWant = iHave + 1 + reg.Constants = slices.Insert(reg.Constants, iWant, reg.Constants[iHave]) + reg.Constants[iWant].Name = wantConst + reg.Constants[iWant].Description = "Bit " + want + ". (added by gen-device-svd)" + } +} + +func (p *Peripheral) lookupRegister(name string) *PeripheralField { + for _, r := range p.Registers { + if r.Name == name { + return r + } + } + return nil +} + +func (r *PeripheralField) hasBitfield(name string) bool { + if r == nil { + return false + } + for i := range r.Bitfields { + if r.Bitfields[i].Name == name { + return true + } + } + return false +} diff --git a/transform/allocs.go b/transform/allocs.go index 870faa5b75..d9c7e47096 100644 --- a/transform/allocs.go +++ b/transform/allocs.go @@ -6,8 +6,10 @@ package transform // interprocedural escape analysis. import ( + "bufio" "fmt" "go/token" + "os" "regexp" "tinygo.org/x/go-llvm" @@ -37,6 +39,10 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, maxStackAlloc u complex128Type := ctx.StructType([]llvm.Type{ctx.DoubleType(), ctx.DoubleType()}, false) maxAlign := int64(targetData.ABITypeAlignment(complex128Type)) + if printAllocs != nil { + fmt.Fprintln(os.Stderr, "mode: set") + } + for _, heapalloc := range getUses(allocator) { logAllocs := printAllocs != nil && printAllocs.MatchString(heapalloc.InstructionParent().Parent().Name()) if heapalloc.Operand(0).IsAConstantInt().IsNil() { @@ -173,5 +179,35 @@ func valueEscapesAt(value llvm.Value) llvm.Value { // logAlloc prints a message to stderr explaining why the given object had to be // allocated on the heap. func logAlloc(logger func(token.Position, string), allocCall llvm.Value, reason string) { - logger(getPosition(allocCall), "object allocated on the heap: "+reason) + pos := getPosition(allocCall) + if pos.Filename == "" || pos.Line <= 0 { + logger(pos, "") + return + } + + endCol := lineLengthAt(pos.Filename, pos.Line) + if endCol < 1 { + endCol = 1 + } + + // Only emit the coverprofile line, without position prefix. + logger(token.Position{}, fmt.Sprintf("%s:%d.1,%d.%d 1 0", pos.Filename, pos.Line, pos.Line, endCol)) +} + +func lineLengthAt(filename string, lineNumber int) int { + f, err := os.Open(filename) + if err != nil { + return 0 + } + defer f.Close() + + scanner := bufio.NewScanner(f) + line := 1 + for scanner.Scan() { + if line == lineNumber { + return len(scanner.Text()) + } + line++ + } + return 0 } diff --git a/transform/allocs_test.go b/transform/allocs_test.go index 7f7ff5b75e..df39605b16 100644 --- a/transform/allocs_test.go +++ b/transform/allocs_test.go @@ -57,9 +57,9 @@ func TestAllocs2(t *testing.T) { sort.Slice(testOutputs, func(i, j int) bool { return testOutputs[i].line < testOutputs[j].line }) - testOutput := "" + testOutput := make([]string, 0) for _, out := range testOutputs { - testOutput += out.String() + "\n" + testOutput = append(testOutput, out.String()) } // Load expected test output (the OUT: lines). @@ -67,15 +67,18 @@ func TestAllocs2(t *testing.T) { if err != nil { t.Fatal("could not read test input:", err) } - var expectedTestOutput string - for i, line := range strings.Split(strings.ReplaceAll(string(testInput), "\r\n", "\n"), "\n") { + var expectedTestOutput []string + for _, line := range strings.Split(strings.ReplaceAll(string(testInput), "\r\n", "\n"), "\n") { if idx := strings.Index(line, " // OUT: "); idx > 0 { msg := line[idx+len(" // OUT: "):] - expectedTestOutput += "allocs2.go:" + strconv.Itoa(i+1) + ": " + msg + "\n" + expectedTestOutput = append(expectedTestOutput, msg) } } - if testOutput != expectedTestOutput { - t.Errorf("output does not match expected output:\n%s", testOutput) + for i := range testOutput { + if !strings.HasSuffix(testOutput[i], expectedTestOutput[i]) { + t.Errorf("output does not match expected output:\n%s\n%s\n", testOutput[i], expectedTestOutput[i]) + return + } } } diff --git a/transform/optimizer.go b/transform/optimizer.go index 54f9762bc4..1209754310 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -88,7 +88,11 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { // Run TinyGo-specific interprocedural optimizations. OptimizeAllocs(mod, config.Options.PrintAllocs, maxStackSize, func(pos token.Position, msg string) { - fmt.Fprintln(os.Stderr, pos.String()+": "+msg) + if pos.Filename != "" { + fmt.Fprintf(os.Stderr, "%s:%d:%d: %s\n", pos.Filename, pos.Line, pos.Column, msg) + } else { + fmt.Fprintln(os.Stderr, msg) // No prefix! + } }) OptimizeStringToBytes(mod) OptimizeStringEqual(mod) diff --git a/transform/testdata/allocs2.go b/transform/testdata/allocs2.go index 9fcebb212f..2b0306db10 100644 --- a/transform/testdata/allocs2.go +++ b/transform/testdata/allocs2.go @@ -10,7 +10,7 @@ func main() { derefInt(&n1) // This should eventually be modified to not escape. - n2 := 6 // OUT: object allocated on the heap: escapes at line 14 + n2 := 6 // OUT: allocs2.go:52.1,52.42 1 0 returnIntPtr(&n2) s1 := make([]int, 3) @@ -20,22 +20,22 @@ func main() { readIntSlice(s2[:]) // This should also be modified to not escape. - s3 := make([]int, 3) // OUT: object allocated on the heap: escapes at line 24 + s3 := make([]int, 3) // OUT: allocs2.go:51.1,51.42 1 0 returnIntSlice(s3) - useSlice(make([]int, getUnknownNumber())) // OUT: object allocated on the heap: size is not constant + useSlice(make([]int, getUnknownNumber())) // OUT: allocs2.go:48.1,48.55 1 0 - s4 := make([]byte, 300) // OUT: object allocated on the heap: object size 300 exceeds maximum stack allocation size 256 + s4 := make([]byte, 300) // OUT: allocs2.go:46.1,46.56 1 0 readByteSlice(s4) - s5 := make([]int, 4) // OUT: object allocated on the heap: escapes at line 32 + s5 := make([]int, 4) // OUT: allocs2.go:38.1,38.56 1 0 _ = append(s5, 5) s6 := make([]int, 3) s7 := []int{1, 2, 3} copySlice(s6, s7) - c1 := getComplex128() // OUT: object allocated on the heap: escapes at line 39 + c1 := getComplex128() // OUT: allocs2.go:31.1,31.55 1 0 useInterface(c1) n3 := 5 @@ -43,13 +43,13 @@ func main() { return n3 }() - callVariadic(3, 5, 8) // OUT: object allocated on the heap: escapes at line 46 + callVariadic(3, 5, 8) // OUT: allocs2.go:28.1,28.58 1 0 - s8 := []int{3, 5, 8} // OUT: object allocated on the heap: escapes at line 49 + s8 := []int{3, 5, 8} // OUT: allocs2.go:26.1,26.76 1 0 callVariadic(s8...) - n4 := 3 // OUT: object allocated on the heap: escapes at line 53 - n5 := 7 // OUT: object allocated on the heap: escapes at line 53 + n4 := 3 // OUT: allocs2.go:23.1,23.55 1 0 + n5 := 7 // OUT: allocs2.go:13.1,13.42 1 0 func() { n4 = n5 }()