diff --git a/.circleci/config.yml b/.circleci/config.yml index e8654f3e9f..01a694b894 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -105,7 +105,7 @@ jobs: # This tests the latest supported LLVM version when linking against system # libraries. docker: - - image: golang:1.25-bullseye + - image: golang:1.26-bookworm steps: - test-linux: llvm: "20" diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 1db9de1b48..a55f7a8845 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -40,7 +40,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.7' + go-version: '1.26.0' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v4 @@ -135,7 +135,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.7' + go-version: '1.26.0' cache: true - name: Build TinyGo (LLVM ${{ matrix.version }}) run: go install -tags=llvm${{ matrix.version }} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e11d8193af..2d26f9357f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -18,7 +18,7 @@ jobs: # statically linked binary. runs-on: ubuntu-latest container: - image: golang:1.25-alpine + image: golang:1.26-alpine outputs: version: ${{ steps.version.outputs.version }} steps: @@ -40,7 +40,7 @@ jobs: - name: Cache Go uses: actions/cache@v4 with: - key: go-cache-linux-alpine-v1-${{ hashFiles('go.mod') }} + key: go-cache-linux-alpine-v2-${{ hashFiles('go.mod') }} path: | ~/.cache/go-build ~/go/pkg/mod @@ -48,7 +48,7 @@ jobs: uses: actions/cache/restore@v4 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 @@ -73,7 +73,7 @@ jobs: uses: actions/cache/restore@v4 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' @@ -97,7 +97,7 @@ jobs: uses: actions/cache@v4 id: cache-binaryen with: - key: binaryen-linux-alpine-v1 + key: binaryen-linux-alpine-v2 path: build/wasm-opt - name: Build Binaryen if: steps.cache-binaryen.outputs.cache-hit != 'true' @@ -137,7 +137,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.7' + go-version: '1.26.0' cache: true - name: Install wasmtime uses: bytecodealliance/actions/wasmtime/setup@v1 @@ -181,7 +181,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.7' + go-version: '1.26.0' cache: true - name: Install Node.js uses: actions/setup-node@v4 @@ -298,7 +298,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.7' + go-version: '1.26.0' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v4 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 0cfeb518be..608aba2a11 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -41,7 +41,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.7' + go-version: '1.26.0' cache: true - name: Restore cached LLVM source uses: actions/cache/restore@v4 @@ -94,7 +94,7 @@ jobs: - name: Cache Go cache uses: actions/cache@v4 with: - key: go-cache-windows-v1-${{ hashFiles('go.mod') }} + key: go-cache-windows-v2-${{ hashFiles('go.mod') }} path: | C:/Users/runneradmin/AppData/Local/go-build C:/Users/runneradmin/go/pkg/mod @@ -147,7 +147,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.7' + go-version: '1.26.0' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 @@ -177,7 +177,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.7' + go-version: '1.26.0' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 @@ -213,7 +213,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.7' + go-version: '1.26.0' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 diff --git a/Dockerfile b/Dockerfile index 4901581a02..1fe6d2d74e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # tinygo-llvm stage obtains the llvm source for TinyGo -FROM golang:1.25 AS tinygo-llvm +FROM golang:1.26 AS tinygo-llvm RUN apt-get update && \ apt-get install -y apt-utils make cmake clang-17 ninja-build && \ @@ -33,7 +33,7 @@ RUN cd /tinygo/ && \ # tinygo-compiler copies the compiler build over to a base Go container (without # all the build tools etc). -FROM golang:1.25 AS tinygo-compiler +FROM golang:1.26 AS tinygo-compiler # Copy tinygo build. COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo diff --git a/GNUmakefile b/GNUmakefile index f4c3dc533b..2c640e403b 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -816,6 +816,8 @@ endif @$(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 @@ -875,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 @@ -909,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 @@ -921,9 +931,40 @@ 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-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 @@ -936,6 +977,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 4c8a2c69be..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: diff --git a/builder/config.go b/builder/config.go index bef366f8c8..736fa08ec2 100644 --- a/builder/config.go +++ b/builder/config.go @@ -26,7 +26,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { // Version range supported by TinyGo. const minorMin = 19 - const minorMax = 25 + const minorMax = 26 // Check that we support this Go toolchain version. gorootMajor, gorootMinor, err := goenv.GetGorootVersion() diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 0f52f3c6be..4b0d6301ea 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) { // microcontrollers {"hifive1b", "examples/echo", 3668, 280, 0, 2244}, {"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/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/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/loader/goroot.go b/loader/goroot.go index 00a7124d80..6a0f002413 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -245,6 +245,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { "internal/cm/": false, "internal/futex/": false, "internal/fuzz/": false, + "internal/itoa": false, "internal/reflectlite/": false, "internal/gclayout": false, "internal/task/": false, diff --git a/main.go b/main.go index e2736fd17b..30985022e1 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,7 @@ 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" @@ -385,6 +386,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 +522,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) } @@ -1019,6 +1040,55 @@ 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() + + // 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/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/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/abi/escape.go b/src/internal/abi/escape.go index 1f4c763312..5dcdd636b7 100644 --- a/src/internal/abi/escape.go +++ b/src/internal/abi/escape.go @@ -15,3 +15,35 @@ func Escape[T any](x T) T { // as-is. panic("internal/abi.Escape: unreachable (implemented in the compiler)") } + +// EscapeNonString forces v to be on the heap, if v contains a +// non-string pointer. +// +// This is used in hash/maphash.Comparable. We cannot hash pointers +// to local variables on stack, as their addresses might change on +// stack growth. Strings are okay as the hash depends on only the +// content, not the pointer. +// +// This is essentially +// +// if hasNonStringPointers(T) { Escape(v) } +// +// Implemented as a compiler intrinsic. +func EscapeNonString[T any](v T) { panic("intrinsic") } + +// EscapeToResultNonString models a data flow edge from v to the result, +// if v contains a non-string pointer. If v contains only string pointers, +// it returns a copy of v, but is not modeled as a data flow edge +// from the escape analysis's perspective. +// +// This is used in unique.clone, to model the data flow edge on the +// value with strings excluded, because strings are cloned (by +// content). +// +// TODO: probably we should define this as a intrinsic and EscapeNonString +// could just be "heap = EscapeToResultNonString(v)". This way we can model +// an edge to the result but not necessarily heap. +func EscapeToResultNonString[T any](v T) T { + EscapeNonString(v) + return *(*T)(NoEscape(unsafe.Pointer(&v))) +} diff --git a/src/internal/itoa/itoa.go b/src/internal/itoa/itoa.go new file mode 100644 index 0000000000..4340ae0e2d --- /dev/null +++ b/src/internal/itoa/itoa.go @@ -0,0 +1,57 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Simple conversions to avoid depending on strconv. + +package itoa + +// Itoa converts val to a decimal string. +func Itoa(val int) string { + if val < 0 { + return "-" + Uitoa(uint(-val)) + } + return Uitoa(uint(val)) +} + +// Uitoa converts val to a decimal string. +func Uitoa(val uint) string { + if val == 0 { // avoid string allocation + return "0" + } + var buf [20]byte // big enough for 64bit value base 10 + i := len(buf) - 1 + for val >= 10 { + q := val / 10 + buf[i] = byte('0' + val - q*10) + i-- + val = q + } + // val < 10 + buf[i] = byte('0' + val) + return string(buf[i:]) +} + +const hex = "0123456789abcdef" + +// Uitox converts val (a uint) to a hexadecimal string. +func Uitox(val uint) string { + if val == 0 { // avoid string allocation + return "0x0" + } + var buf [20]byte // big enough for 64bit value base 16 + 0x + i := len(buf) - 1 + for val >= 16 { + q := val / 16 + buf[i] = hex[val%16] + i-- + val = q + } + // val < 16 + buf[i] = hex[val%16] + i-- + buf[i] = 'x' + i-- + buf[i] = '0' + return string(buf[i:]) +} diff --git a/src/internal/itoa/itoa_test.go b/src/internal/itoa/itoa_test.go new file mode 100644 index 0000000000..8bed888532 --- /dev/null +++ b/src/internal/itoa/itoa_test.go @@ -0,0 +1,51 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package itoa_test + +import ( + "fmt" + "internal/itoa" + "math" + "testing" +) + +var ( + minInt64 int64 = math.MinInt64 + maxInt64 int64 = math.MaxInt64 + maxUint64 uint64 = math.MaxUint64 +) + +func TestItoa(t *testing.T) { + tests := []int{int(minInt64), math.MinInt32, -999, -100, -1, 0, 1, 100, 999, math.MaxInt32, int(maxInt64)} + for _, tt := range tests { + got := itoa.Itoa(tt) + want := fmt.Sprint(tt) + if want != got { + t.Fatalf("Itoa(%d) = %s, want %s", tt, got, want) + } + } +} + +func TestUitoa(t *testing.T) { + tests := []uint{0, 1, 100, 999, math.MaxUint32, uint(maxUint64)} + for _, tt := range tests { + got := itoa.Uitoa(tt) + want := fmt.Sprint(tt) + if want != got { + t.Fatalf("Uitoa(%d) = %s, want %s", tt, got, want) + } + } +} + +func TestUitox(t *testing.T) { + tests := []uint{0, 1, 15, 100, 999, math.MaxUint32, uint(maxUint64)} + for _, tt := range tests { + got := itoa.Uitox(tt) + want := fmt.Sprintf("%#x", tt) + if want != got { + t.Fatalf("Uitox(%x) = %s, want %s", tt, got, want) + } + } +} 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..9e8996d9f4 --- /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 + D1 = PB6 + D2 = PB3 + D3 = PB0 + D4 = PA12 + D5 = PA11 + D6 = PB1 + D7 = PB2 + D8 = PB4 + D9 = PB8 + D10 = PB9 + D11 = PB15 + D12 = PB14 + D13 = PB13 + D18 = PC1 + D19 = PC0 + D20 = PB10 + D21 = PB11 +) + +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_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_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_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/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_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_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..06fdca913a --- /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 = 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-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..a2e79dc0c1 --- /dev/null +++ b/src/machine/machine_esp32xx_usb.go @@ -0,0 +1,102 @@ +//go:build esp32s3 + +package machine + +import ( + "device/esp" + "errors" +) + +// 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") +) + +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() {} + +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 { + } +} 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/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<DROM @@ -83,6 +87,7 @@ SECTIONS . = ALIGN (4); _sbss = ABSOLUTE(.); *(.sbss) + *(.sbss.*) *(.bss .bss.*) . = ALIGN (4); _ebss = ABSOLUTE(.); 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..1b9159230a 100644 --- a/targets/esp32s3.json +++ b/targets/esp32s3.json @@ -4,7 +4,7 @@ "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, "rtlib": "compiler-rt", @@ -15,7 +15,7 @@ "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/esp8266.json b/targets/esp8266.json index bb02c3db29..b6e4a3500f 100644 --- a/targets/esp8266.json +++ b/targets/esp8266.json @@ -14,5 +14,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..6470d57f50 100644 --- a/targets/rp2040.json +++ b/targets/rp2040.json @@ -1,7 +1,7 @@ { "inherits": ["cortex-m0plus"], "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..e3a6549fd2 100644 --- a/targets/rp2350.json +++ b/targets/rp2350.json @@ -1,7 +1,7 @@ { "inherits": ["cortex-m33"], "build-tags": ["rp2350", "rp"], - "scheduler": "cores", + "scheduler": "tasks", "flash-1200-bps-reset": "true", "flash-method": "msd", "serial": "usb", 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/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/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 }()