diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 1ff0be8def..1db9de1b48 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.5' + go-version: '1.25.7' 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.5' + go-version: '1.25.7' cache: true - name: Build TinyGo (LLVM ${{ matrix.version }}) run: go install -tags=llvm${{ matrix.version }} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d5a6619fbc..e11d8193af 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -137,7 +137,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Install wasmtime uses: bytecodealliance/actions/wasmtime/setup@v1 @@ -181,7 +181,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Install Node.js uses: actions/setup-node@v4 @@ -298,7 +298,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v4 diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index e65ae3193a..6c5b9f9a41 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -42,7 +42,7 @@ jobs: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} path: | llvm-project/compiler-rt - - uses: cachix/install-nix-action@v22 + - uses: cachix/install-nix-action@v31 - name: Test run: | nix develop --ignore-environment --keep HOME --command bash -c "go install && ~/go/bin/tinygo version && ~/go/bin/tinygo build -o test ./testdata/cgo" diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 60b0d8cb5d..0cfeb518be 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.5' + go-version: '1.25.7' cache: true - name: Restore cached LLVM source uses: actions/cache/restore@v4 @@ -147,7 +147,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 @@ -177,7 +177,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 @@ -213,7 +213,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: '1.25.5' + go-version: '1.25.7' cache: true - name: Download TinyGo build uses: actions/download-artifact@v4 diff --git a/GNUmakefile b/GNUmakefile index 99a654ca7f..8aa4f4ea0c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -814,6 +814,10 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=waveshare-rp2040-tiny examples/echo @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=vicharak_shrike-lite examples/echo + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=xiao-rp2350 examples/blinky1 + @$(MD5SUM) test.hex # test pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm @$(MD5SUM) test.hex @@ -896,6 +900,10 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=digispark examples/pwm + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=digispark examples/mcp3008 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1 @$(MD5SUM) test.hex ifneq ($(XTENSA), 0) @@ -903,6 +911,8 @@ ifneq ($(XTENSA), 0) @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1 @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinkm + @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/machinetest @@ -915,9 +925,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 @@ -930,6 +971,9 @@ 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=esp32c6 examples/machinetest + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=makerfabs-esp32c3spi35 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 diff --git a/README.md b/README.md index 518dcdad18..86955a2d9d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ TinyGo is a Go compiler intended for use in small places such as microcontroller It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language. +> [!IMPORTANT] +> You can help TinyGo with a financial contribution using OpenCollective. Please see https://opencollective.com/tinygo for more information. Thank you! + ## Embedded Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED: @@ -63,7 +66,7 @@ tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.go You can also use the same syntax as Go 1.24+: ```shell -GOARCH=wasip1 GOOS=wasm tinygo build -buildmode=c-shared -o add.wasm add.go +GOOS=wasip1 GOARCH=wasm tinygo build -buildmode=c-shared -o add.wasm add.go ``` ## Installation diff --git a/builder/build.go b/builder/build.go index a598f01965..44775b6246 100644 --- a/builder/build.go +++ b/builder/build.go @@ -19,6 +19,7 @@ import ( "os/exec" "path/filepath" "runtime" + "slices" "sort" "strconv" "strings" @@ -281,9 +282,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe allFiles[file.Name] = append(allFiles[file.Name], file) } } - for name, files := range allFiles { - name := name - files := files + // Sort embedded files by name to maintain output determinism. + embedNames := make([]string, 0, len(allFiles)) + for _, files := range allFiles { + embedNames = append(embedNames, files[0].Name) + } + slices.Sort(embedNames) + for _, name := range embedNames { job := &compileJob{ description: "make object file for " + name, run: func(job *compileJob) error { @@ -298,7 +303,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe sum := sha256.Sum256(data) hexSum := hex.EncodeToString(sum[:16]) - for _, file := range files { + for _, file := range allFiles[name] { file.Size = uint64(len(data)) file.Hash = hexSum if file.NeedsData { @@ -1042,7 +1047,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if err != nil { return result, err } - case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266": + case "esp32", "esp32-img", "esp32c3", "esp32c6", "esp32s3", "esp8266": // Special format for the ESP family of chips (parsed by the ROM // bootloader). result.Binary = filepath.Join(tmpdir, "main"+outext) diff --git a/builder/builder_test.go b/builder/builder_test.go index 8e62d9183c..0e0a156d4a 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -28,6 +28,7 @@ func TestClangAttributes(t *testing.T) { "cortex-m4", "cortex-m7", "esp32c3", + "esp32c6", "esp32s3", "fe310", "gameboy-advance", diff --git a/builder/esp.go b/builder/esp.go index c866a5c316..c4fd4a9730 100644 --- a/builder/esp.go +++ b/builder/esp.go @@ -9,7 +9,6 @@ package builder import ( "bytes" - "crypto/sha256" "debug/elf" "encoding/binary" "fmt" @@ -100,12 +99,24 @@ func makeESPFirmwareImage(infile, outfile, format string) error { chip_id := map[string]uint16{ "esp32": 0x0000, "esp32c3": 0x0005, + "esp32c6": 0x000D, "esp32s3": 0x0009, }[chip] + // SPI flash speed/size byte (byte 3 of header): + // Upper nibble = flash size, lower nibble = flash frequency. + // The espflasher auto-detects and patches the flash size (upper nibble), + // but the frequency (lower nibble) must be correct per chip. + spiSpeedSize := map[string]uint8{ + "esp32": 0x1f, // 80MHz=0x0F, 2MB=0x10 + "esp32c3": 0x1f, // 80MHz=0x0F, 2MB=0x10 + "esp32c6": 0x10, // 80MHz=0x00, 2MB=0x10 (C6 uses different freq encoding) + "esp32s3": 0x1f, // 80MHz=0x0F, 2MB=0x10 + }[chip] + // Image header. switch chip { - case "esp32", "esp32c3", "esp32s3": + case "esp32", "esp32c3", "esp32c6", "esp32s3": // Header format: // https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71 // Note: not adding a SHA256 hash as the binary is modified by @@ -126,12 +137,12 @@ func makeESPFirmwareImage(infile, outfile, format string) error { }{ magic: 0xE9, segment_count: byte(len(segments)), - spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO - spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB + spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO + spi_speed_size: spiSpeedSize, entry_addr: uint32(inf.Entry), wp_pin: 0xEE, // disable WP pin chip_id: chip_id, - hash_appended: true, // add a SHA256 hash + hash_appended: false, // disabled: espflasher patches header, invalidating the hash }) case "esp8266": // Header format: @@ -173,11 +184,9 @@ func makeESPFirmwareImage(infile, outfile, format string) error { outf.Write(make([]byte, 15-outf.Len()%16)) outf.WriteByte(checksum) - if chip != "esp8266" { - // SHA256 hash (to protect against image corruption, not for security). - hash := sha256.Sum256(outf.Bytes()) - outf.Write(hash[:]) - } + // Note: SHA256 hash intentionally omitted. espflasher patches the header + // (SPI mode/speed/size), which invalidates the hash. The ROM would report + // "SHA-256 comparison failed" and boot anyway, so it's just noise. // QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the // image to be a certain size. 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/compiler/gc.go b/compiler/gc.go index fc0e6e687f..5ca79b91ba 100644 --- a/compiler/gc.go +++ b/compiler/gc.go @@ -99,6 +99,9 @@ func typeHasPointers(t llvm.Type) bool { } return false case llvm.ArrayTypeKind: + if t.ArrayLength() == 0 { + return false + } if typeHasPointers(t.ElementType()) { return true } diff --git a/compiler/llvm.go b/compiler/llvm.go index de387b39c0..7ce6c7d615 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -1,10 +1,10 @@ package compiler import ( + "encoding/binary" "fmt" "go/token" "go/types" - "math/big" "strings" "github.com/tinygo-org/tinygo/compileopts" @@ -231,6 +231,12 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l // // For details on what's in this value, see src/runtime/gc_precise.go. func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value { + if !typeHasPointers(t) { + // There are no pointers in this type, so we can simplify the layout. + layout := (uint64(1) << 1) | 1 + return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) + } + // Use the element type for arrays. This works even for nested arrays. for { kind := t.TypeKind() @@ -248,54 +254,29 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va break } - // Do a few checks to see whether we need to generate any object layout - // information at all. + // Create the pointer bitmap. objectSizeBytes := c.targetData.TypeAllocSize(t) - pointerSize := c.targetData.TypeAllocSize(c.dataPtrType) - pointerAlignment := c.targetData.PrefTypeAlignment(c.dataPtrType) - if objectSizeBytes < pointerSize { - // Too small to contain a pointer. - layout := (uint64(1) << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) - } - bitmap := c.getPointerBitmap(t, pos) - if bitmap.BitLen() == 0 { - // There are no pointers in this type, so we can simplify the layout. - // TODO: this can be done in many other cases, e.g. when allocating an - // array (like [4][]byte, which repeats a slice 4 times). - layout := (uint64(1) << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) - } - if objectSizeBytes%uint64(pointerAlignment) != 0 { - // This shouldn't happen except for packed structs, which aren't - // currently used. - c.addError(pos, "internal error: unexpected object size for object with pointer field") - return llvm.ConstNull(c.dataPtrType) - } - objectSizeWords := objectSizeBytes / uint64(pointerAlignment) + pointerAlignment := uint64(c.targetData.PrefTypeAlignment(c.dataPtrType)) + bitmapLen := objectSizeBytes / pointerAlignment + bitmapBytes := (bitmapLen + 7) / 8 + bitmap := make([]byte, bitmapBytes, max(bitmapBytes, 8)) + c.buildPointerBitmap(bitmap, pointerAlignment, pos, t, 0) + // Try to encode the layout inline. + pointerSize := c.targetData.TypeAllocSize(c.dataPtrType) pointerBits := pointerSize * 8 - var sizeFieldBits uint64 - switch pointerBits { - case 16: - sizeFieldBits = 4 - case 32: - sizeFieldBits = 5 - case 64: - sizeFieldBits = 6 - default: - panic("unknown pointer size") - } - layoutFieldBits := pointerBits - 1 - sizeFieldBits - - // Try to emit the value as an inline integer. This is possible in most - // cases. - if objectSizeWords < layoutFieldBits { - // If it can be stored directly in the pointer value, do so. - // The runtime knows that if the least significant bit of the pointer is - // set, the pointer contains the value itself. - layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) + if bitmapLen < pointerBits { + rawMask := binary.LittleEndian.Uint64(bitmap[0:8]) + layout := rawMask*pointerBits + bitmapLen + layout <<= 1 + layout |= 1 + + // Check if the layout fits. + layout &= 1<>1)/pointerBits == rawMask { + // No set bits were shifted off. + return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) + } } // Unfortunately, the object layout is too big to fit in a pointer-sized @@ -303,25 +284,24 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va // Try first whether the global already exists. All objects with a // particular name have the same type, so this is possible. - globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap) + globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", bitmapLen, (bitmapLen+15)/16, bitmap) global := c.mod.NamedGlobal(globalName) if !global.IsNil() { return global } // Create the global initializer. - bitmapBytes := make([]byte, int(objectSizeWords+7)/8) - bitmap.FillBytes(bitmapBytes) - reverseBytes(bitmapBytes) // big-endian to little-endian - var bitmapByteValues []llvm.Value - for _, b := range bitmapBytes { - bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)) + bitmapByteValues := make([]llvm.Value, bitmapBytes) + i8 := c.ctx.Int8Type() + for i, b := range bitmap { + bitmapByteValues[i] = llvm.ConstInt(i8, uint64(b), false) } initializer := c.ctx.ConstStruct([]llvm.Value{ - llvm.ConstInt(c.uintptrType, objectSizeWords, false), - llvm.ConstArray(c.ctx.Int8Type(), bitmapByteValues), + llvm.ConstInt(c.uintptrType, bitmapLen, false), + llvm.ConstArray(i8, bitmapByteValues), }, false) + // Create the actual global. global = llvm.AddGlobal(c.mod, initializer.Type(), globalName) global.SetInitializer(initializer) global.SetUnnamedAddr(true) @@ -329,6 +309,7 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va global.SetLinkage(llvm.LinkOnceODRLinkage) if c.targetData.PrefTypeAlignment(c.uintptrType) < 2 { // AVR doesn't have alignment by default. + // The lowest bit must be unset to distinguish this from an inline layout. global.SetAlignment(2) } if c.Debug && pos != token.NoPos { @@ -360,52 +341,71 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va return global } -// getPointerBitmap scans the given LLVM type for pointers and sets bits in a -// bigint at the word offset that contains a pointer. This scan is recursive. -func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int { - alignment := c.targetData.PrefTypeAlignment(c.dataPtrType) - switch typ.TypeKind() { +// buildPointerBitmap scans the given LLVM type for pointers and sets bits in a +// bitmap at the word offset that contains a pointer. This scan is recursive. +func (c *compilerContext) buildPointerBitmap( + dst []byte, + ptrAlign uint64, + pos token.Pos, + t llvm.Type, + offset uint64, +) { + switch t.TypeKind() { case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind: - return big.NewInt(0) + // These types do not contain pointers. + case llvm.PointerTypeKind: - return big.NewInt(1) + // Set the corresponding position in the bitmap. + dst[offset/8] |= 1 << (offset % 8) + case llvm.StructTypeKind: - ptrs := big.NewInt(0) - for i, subtyp := range typ.StructElementTypes() { - subptrs := c.getPointerBitmap(subtyp, pos) - if subptrs.BitLen() == 0 { - continue - } - offset := c.targetData.ElementOffset(typ, i) - if offset%uint64(alignment) != 0 { - // This error will let the compilation fail, but by continuing - // the error can still easily be shown. - c.addError(pos, "internal error: allocated struct contains unaligned pointer") + // Recurse over struct elements. + for i, et := range t.StructElementTypes() { + eo := c.targetData.ElementOffset(t, i) + if eo%uint64(ptrAlign) != 0 { + if typeHasPointers(et) { + // This error will let the compilation fail, but by continuing + // the error can still easily be shown. + c.addError(pos, "internal error: allocated struct contains unaligned pointer") + } continue } - subptrs.Lsh(subptrs, uint(offset)/uint(alignment)) - ptrs.Or(ptrs, subptrs) + c.buildPointerBitmap( + dst, + ptrAlign, + pos, + et, + offset+(eo/ptrAlign), + ) } - return ptrs + case llvm.ArrayTypeKind: - subtyp := typ.ElementType() - subptrs := c.getPointerBitmap(subtyp, pos) - ptrs := big.NewInt(0) - if subptrs.BitLen() == 0 { - return ptrs + // Recurse over array elements. + len := t.ArrayLength() + if len <= 0 { + return } - elementSize := c.targetData.TypeAllocSize(subtyp) - if elementSize%uint64(alignment) != 0 { - // This error will let the compilation fail (but continues so that - // other errors can be shown). - c.addError(pos, "internal error: allocated array contains unaligned pointer") - return ptrs + et := t.ElementType() + elementSize := c.targetData.TypeAllocSize(et) + if elementSize%ptrAlign != 0 { + if typeHasPointers(et) { + // This error will let the compilation fail (but continues so that + // other errors can be shown). + c.addError(pos, "internal error: allocated array contains unaligned pointer") + } + return } - for i := 0; i < typ.ArrayLength(); i++ { - ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment)) - ptrs.Or(ptrs, subptrs) + elementSize /= ptrAlign + for i := 0; i < len; i++ { + c.buildPointerBitmap( + dst, + ptrAlign, + pos, + et, + offset+uint64(i)*elementSize, + ) } - return ptrs + default: // Should not happen. panic("unknown LLVM type") diff --git a/compiler/testdata/gc.go b/compiler/testdata/gc.go index 20e5967028..9aa00a4c6f 100644 --- a/compiler/testdata/gc.go +++ b/compiler/testdata/gc.go @@ -24,6 +24,10 @@ var ( x *byte y [61]uintptr } + struct5 *struct { + x *byte + y [30]uintptr + } slice1 []byte slice2 []*int @@ -58,6 +62,10 @@ func newStruct() { x *byte y [61]uintptr }) + struct5 = new(struct { + x *byte + y [30]uintptr + }) } func newFuncValue() *func() { diff --git a/compiler/testdata/gc.ll b/compiler/testdata/gc.ll index d2be74cbcf..42a278b66e 100644 --- a/compiler/testdata/gc.ll +++ b/compiler/testdata/gc.ll @@ -16,11 +16,12 @@ target triple = "wasm32-unknown-wasi" @main.struct2 = hidden global ptr null, align 4 @main.struct3 = hidden global ptr null, align 4 @main.struct4 = hidden global ptr null, align 4 +@main.struct5 = hidden global ptr null, align 4 @main.slice1 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.slice2 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 -@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } -@"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" } +@"runtime/gc.layout:62-0100000000000020" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } +@"runtime/gc.layout:62-0100000000000000" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" } @"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 80, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4 @"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:complex128" }, align 4 @@ -80,12 +81,15 @@ entry: %new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.struct2, align 4 - %new2 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-2000000000000001", ptr undef) #3 + %new2 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0100000000000020", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.struct3, align 4 - %new3 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0001", ptr undef) #3 + %new3 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0100000000000000", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new3, ptr @main.struct4, align 4 + %new4 = call align 4 dereferenceable(124) ptr @runtime.alloc(i32 124, ptr nonnull inttoptr (i32 127 to ptr), ptr undef) #3 + call void @runtime.trackPointer(ptr nonnull %new4, ptr nonnull %stackalloc, ptr undef) #3 + store ptr %new4, ptr @main.struct5, align 4 ret void } diff --git a/flake.lock b/flake.lock index 877c18b461..9ff701357d 100644 --- a/flake.lock +++ b/flake.lock @@ -20,16 +20,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1747953325, - "narHash": "sha256-y2ZtlIlNTuVJUZCqzZAhIw5rrKP4DOSklev6c8PyCkQ=", + "lastModified": 1770136044, + "narHash": "sha256-tlFqNG/uzz2++aAmn4v8J0vAkV3z7XngeIIB3rM3650=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "55d1f923c480dadce40f5231feb472e81b0bab48", + "rev": "e576e3c9cf9bad747afcddd9e34f51d18c855b4e", "type": "github" }, "original": { "id": "nixpkgs", - "ref": "nixos-25.05", + "ref": "nixos-25.11", "type": "indirect" } }, diff --git a/flake.nix b/flake.nix index 4feea0a4b8..85ab404940 100644 --- a/flake.nix +++ b/flake.nix @@ -34,7 +34,7 @@ inputs = { # Use a recent stable release, but fix the version to make it reproducible. # This version should be updated from time to time. - nixpkgs.url = "nixpkgs/nixos-25.05"; + nixpkgs.url = "nixpkgs/nixos-25.11"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: diff --git a/go.mod b/go.mod index 7d4ea5f189..035527189e 100644 --- a/go.mod +++ b/go.mod @@ -13,17 +13,17 @@ require ( github.com/mattn/go-tty v0.0.4 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 github.com/tetratelabs/wazero v1.6.0 - go.bug.st/serial v1.6.0 + go.bug.st/serial v1.6.4 golang.org/x/net v0.35.0 golang.org/x/sys v0.30.0 golang.org/x/tools v0.30.0 gopkg.in/yaml.v2 v2.4.0 + tinygo.org/x/espflasher v0.4.0 tinygo.org/x/go-llvm v0.0.0-20250422114502-b8f170971e74 ) require ( github.com/creack/goselect v0.1.2 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/stretchr/testify v1.8.4 // indirect golang.org/x/text v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index 8c2330c3c5..a3b398191d 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= -go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A= -go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= +go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= +go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= @@ -58,5 +58,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +tinygo.org/x/espflasher v0.4.0 h1:0N+Ei+0qT/wGkGIoMaY2g+oI519VA5G4kUVUYHedTv8= +tinygo.org/x/espflasher v0.4.0/go.mod h1:a3hRV9EETPUkfPE6P14p4A6jKKth+oD5gtQz3nmij+8= tinygo.org/x/go-llvm v0.0.0-20250422114502-b8f170971e74 h1:ovavgTdIBWCH8YWlcfq9gkpoyT1+IxMKSn+Df27QwE8= tinygo.org/x/go-llvm v0.0.0-20250422114502-b8f170971e74/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= diff --git a/goenv/version.go b/goenv/version.go index 423f95906e..9ade0e0b79 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -10,7 +10,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.40.1" +const version = "0.41.0-dev" // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012). diff --git a/lib/stm32-svd b/lib/stm32-svd index e6db8e32d5..bfed180b63 160000 --- a/lib/stm32-svd +++ b/lib/stm32-svd @@ -1 +1 @@ -Subproject commit e6db8e32d5d42293a528434ec12e7f88479a8649 +Subproject commit bfed180b639863db414221bdb6aa77e198f464f5 diff --git a/main.go b/main.go index e2736fd17b..fd1deefde2 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,54 @@ 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 { + opts := espflasher.DefaultOptions() + // On Windows, we have to explicitly specify the reset mode to use USB JTAG. + if runtime.GOOS == "windows" && resetMode == jtagReset { + 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..d3e3dde106 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 || esp32c6 || esp32s3 || tkey || (tinygo.riscv32 && virt) // If you update the above build constraint, you'll probably also need to update // src/runtime/rand_hwrng.go. diff --git a/src/device/esp/esp32c6.S b/src/device/esp/esp32c6.S new file mode 100644 index 0000000000..7afd39f681 --- /dev/null +++ b/src/device/esp/esp32c6.S @@ -0,0 +1,66 @@ +// This is a very minimal bootloader for the ESP32-C6. It only initializes the +// flash and then continues with the generic RISC-V initialization code, which +// in turn will call runtime.main. +// It is written in assembly (and not in a higher level language) to make sure +// it is entirely loaded into IRAM and doesn't accidentally call functions +// stored in IROM. +// +// The ESP32-C6 has a unified IRAM/DRAM address space at 0x40800000, and +// separate DROM (0x42800000) / IROM (0x42000000) flash-mapped regions. + +.section .init +.global call_start_cpu0 +.type call_start_cpu0,@function +call_start_cpu0: + // At this point: + // - The ROM bootloader is finished and has jumped to here. + // - We're running from IRAM: both IRAM and DRAM segments have been loaded + // by the ROM bootloader. + // - We have a usable stack (but not the one we would like to use). + // - No flash mappings (MMU) are set up yet. + + // Reset MMU, see bootloader_reset_mmu in the ESP-IDF. + call Cache_Suspend_ICache + mv s0, a0 // autoload value + call Cache_Invalidate_ICache_All + call Cache_MMU_Init + + // Set up flash mapping (both IROM and DROM). + // On ESP32-C6, Cache_Dbus_MMU_Set is replaced by Cache_MSPI_MMU_Set + // which has an extra "sensitive" parameter. + // C equivalent: + // Cache_MSPI_MMU_Set(0, 0, 0x42000000, 0, 64, 256, 0) + // Maps 16MB starting at 0x42000000, covering both IROM and DROM. + li a0, 0 // sensitive: no flash encryption + li a1, 0 // ext_ram: MMU_ACCESS_FLASH + li a2, 0x42000000 // vaddr: start of flash-mapped region + li a3, 0 // paddr: physical address in the flash chip + li a4, 64 // psize: always 64 (kilobytes) + li a5, 256 // num: pages (16MB / 64K = 256, covers IROM+DROM) + li a6, 0 // fixed + call Cache_MSPI_MMU_Set + + // Enable the flash cache. + mv a0, s0 // restore autoload value from Cache_Suspend_ICache call + call Cache_Resume_ICache + + // Jump to generic RISC-V initialization, which initializes the stack + // pointer and globals register. It should not return. + j _start + +.section .text.exception_vectors +.global _vector_table +.type _vector_table,@function + +_vector_table: + + .option push + .option norvc + + .rept 32 + j handleInterruptASM /* interrupt handler */ + .endr + + .option pop + +.size _vector_table, .-_vector_table 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/digispark.go b/src/examples/pwm/digispark.go new file mode 100644 index 0000000000..848d518546 --- /dev/null +++ b/src/examples/pwm/digispark.go @@ -0,0 +1,12 @@ +//go:build digispark + +package main + +import "machine" + +var ( + // Use Timer1 for PWM (recommended for ATtiny85) + pwm = machine.Timer1 + pinA = machine.P1 // PB1, Timer1 channel A (LED pin) + pinB = machine.P4 // PB4, Timer1 channel B +) diff --git a/src/examples/pwm/esp32c3-supermini.go b/src/examples/pwm/esp32c3-supermini.go new file mode 100644 index 0000000000..edca88b97e --- /dev/null +++ b/src/examples/pwm/esp32c3-supermini.go @@ -0,0 +1,11 @@ +//go:build esp32c3_supermini + +package main + +import "machine" + +var ( + pwm = machine.PWM0 + pinA = machine.GPIO5 + pinB = machine.GPIO6 +) diff --git a/src/examples/pwm/esp32s3-wroom1.go b/src/examples/pwm/esp32s3-wroom1.go new file mode 100644 index 0000000000..b3e5dcb584 --- /dev/null +++ b/src/examples/pwm/esp32s3-wroom1.go @@ -0,0 +1,11 @@ +//go:build esp32s3_wroom1 + +package main + +import "machine" + +var ( + pwm = machine.PWM0 + pinA = machine.GPIO17 + pinB = machine.GPIO18 +) diff --git a/src/examples/pwm/xiao_esp32s3.go b/src/examples/pwm/xiao_esp32s3.go new file mode 100644 index 0000000000..c614abc861 --- /dev/null +++ b/src/examples/pwm/xiao_esp32s3.go @@ -0,0 +1,11 @@ +//go:build xiao_esp32s3 + +package main + +import "machine" + +var ( + pwm = machine.PWM0 + pinA = machine.D0 + pinB = machine.D1 +) diff --git a/src/examples/serial-stress/main.go b/src/examples/serial-stress/main.go new file mode 100644 index 0000000000..cdda2cfa7c --- /dev/null +++ b/src/examples/serial-stress/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "machine" + "strconv" + "time" +) + +func main() { + time.Sleep(2 * time.Second) // connect via serial + buf1 := makeBuffer('|', 600) + buf2 := makeBuffer('/', 600) + println("start") + serialWrite(buf1) + serialWrite(buf2) +} + +func makeBuffer(sep byte, size int) []byte { + buf := make([]byte, size) + for i := 0; i < size-5; i += 5 { + buf[i] = sep + strconv.AppendInt(buf[i+1:i+1:i+5], int64(i), 10) + } + return buf +} + +func serialWrite(b []byte) { + machine.Serial.Write(b) +} diff --git a/src/machine/board_digispark.go b/src/machine/board_digispark.go index f380aae85c..d7106a5544 100644 --- a/src/machine/board_digispark.go +++ b/src/machine/board_digispark.go @@ -2,17 +2,26 @@ package machine +// Digispark is a tiny ATtiny85-based board with 6 I/O pins. +// +// PWM is available on the following pins: +// - P0 (PB0): Timer0 channel A +// - P1 (PB1): Timer0 channel B or Timer1 channel A (LED pin) +// - P4 (PB4): Timer1 channel B +// +// Timer1 is recommended for PWM as it provides more flexible frequency control. + // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 16000000 } const ( - P0 Pin = PB0 - P1 Pin = PB1 + P0 Pin = PB0 // PWM available (Timer0 OC0A) + P1 Pin = PB1 // PWM available (Timer0 OC0B or Timer1 OC1A) P2 Pin = PB2 P3 Pin = PB3 - P4 Pin = PB4 + P4 Pin = PB4 // PWM available (Timer1 OC1B) P5 Pin = PB5 LED = P1 diff --git a/src/machine/board_esp32c3-12f.go b/src/machine/board_esp32c3-12f.go index f023bb9d61..0988c7adcb 100644 --- a/src/machine/board_esp32c3-12f.go +++ b/src/machine/board_esp32c3-12f.go @@ -30,9 +30,6 @@ const ( // ADC pins const ( - ADC0 Pin = ADC1_0 - ADC1 Pin = ADC2_0 - ADC1_0 Pin = IO0 ADC1_1 Pin = IO1 ADC1_2 Pin = IO2 diff --git a/src/machine/board_esp32c6.go b/src/machine/board_esp32c6.go new file mode 100644 index 0000000000..00b8289f37 --- /dev/null +++ b/src/machine/board_esp32c6.go @@ -0,0 +1,79 @@ +//go:build esp32c6 + +// This file contains the default pin mappings for the ESP32-C6 generic target. + +package machine + +// Digital Pins +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO10 = GPIO10 + IO11 = GPIO11 + IO12 = GPIO12 + IO13 = GPIO13 + IO14 = GPIO14 + IO15 = GPIO15 + IO16 = GPIO16 + IO17 = GPIO17 + IO18 = GPIO18 + IO19 = GPIO19 + IO20 = GPIO20 + IO21 = GPIO21 + IO22 = GPIO22 + IO23 = GPIO23 + IO24 = GPIO24 + IO25 = GPIO25 + IO26 = GPIO26 + IO27 = GPIO27 + IO28 = GPIO28 + IO29 = GPIO29 + IO30 = GPIO30 +) + +// Built-in WS2812 (NeoPixel) addressable RGB LED on the ESP32-C6-DevKitC. +// Use tinygo.org/x/drivers/ws2812 to control it. +const ( + LED = WS2812 + WS2812 = GPIO8 + NEOPIXEL = GPIO8 +) + +// Analog pins +const ( + A0 = GPIO0 + A1 = GPIO1 + A2 = GPIO2 + A3 = GPIO3 + A4 = GPIO4 + A5 = GPIO5 + A6 = GPIO6 +) + +// UART pins +const ( + UART_RX_PIN = GPIO17 + UART_TX_PIN = GPIO16 +) + +// I2C pins +const ( + SDA_PIN = GPIO6 + SCL_PIN = GPIO7 +) + +// SPI pins +const ( + SPI_MISO_PIN = GPIO2 + SPI_MOSI_PIN = GPIO7 + SPI_SS_PIN = GPIO10 + SPI_SCK_PIN = GPIO6 +) diff --git a/src/machine/board_esp32s3-wroom1.go b/src/machine/board_esp32s3-wroom1.go new file mode 100644 index 0000000000..20c8883183 --- /dev/null +++ b/src/machine/board_esp32s3-wroom1.go @@ -0,0 +1,18 @@ +//go:build esp32s3_wroom1 + +package machine + +const ( + SCL_PIN = GPIO17 + SDA_PIN = GPIO18 + + SPI1_SCK_PIN = GPIO12 // SCK + SPI1_MOSI_PIN = GPIO11 // SDO (MOSI) + SPI1_MISO_PIN = GPIO13 // SDI (MISO) + SPI1_CS_PIN = GPIO10 // CS + + SPI2_SCK_PIN = GPIO36 // SCK + SPI2_MOSI_PIN = GPIO35 // SDO (MOSI) + SPI2_MISO_PIN = GPIO37 // SDI (MISO) + SPI2_CS_PIN = GPIO34 // CS +) diff --git a/src/machine/board_feather-m0.go b/src/machine/board_feather-m0.go index f38d8ec889..15ec77d37a 100644 --- a/src/machine/board_feather-m0.go +++ b/src/machine/board_feather-m0.go @@ -43,6 +43,15 @@ const ( USBCDC_DP_PIN = PA25 ) +// UART0 pins +const ( + UART0_TX_PIN = D1 + UART0_RX_PIN = D0 +) + +// UART0 on the Feather M0. +var UART0 = &sercomUSART0 + // UART1 pins const ( UART_TX_PIN = D10 diff --git a/src/machine/board_vicharak_shrike-lite.go b/src/machine/board_vicharak_shrike-lite.go new file mode 100644 index 0000000000..8899e7125c --- /dev/null +++ b/src/machine/board_vicharak_shrike-lite.go @@ -0,0 +1,118 @@ +//go:build vicharak_shrike_lite + +// Pin mappings for Vicharak Shrike-Lite. +// +// Reference: https://vicharak-in.github.io/shrike/shrike_pinouts.html + +package machine + +// Digital +const ( + IO0 Pin = GPIO0 + IO1 Pin = GPIO1 + IO2 Pin = GPIO2 + IO3 Pin = GPIO3 + IO4 Pin = GPIO4 + IO5 Pin = GPIO5 + IO6 Pin = GPIO6 + IO7 Pin = GPIO7 + IO8 Pin = GPIO8 + IO9 Pin = GPIO9 + IO10 Pin = GPIO10 + IO11 Pin = GPIO11 + IO12 Pin = GPIO12 + IO13 Pin = GPIO13 + IO14 Pin = GPIO14 + IO15 Pin = GPIO15 + IO16 Pin = GPIO16 + IO17 Pin = GPIO17 + IO18 Pin = GPIO18 + IO19 Pin = GPIO19 + IO20 Pin = GPIO20 + IO21 Pin = GPIO21 + IO22 Pin = GPIO22 + IO23 Pin = GPIO23 + IO24 Pin = GPIO24 + IO25 Pin = GPIO25 + IO26 Pin = GPIO26 + IO27 Pin = GPIO27 + IO28 Pin = GPIO28 + IO29 Pin = GPIO29 +) + +// FPGA Pins +const ( + FPGA_EN Pin = IO13 + FPGA_PWR Pin = IO12 + // SPI_SCLK + F3 Pin = IO2 + // SPI_SS + F4 Pin = IO1 + // SPI_SI (MOSI) + F5 Pin = IO3 + // SPI_SO (MISO) / CONFIG + F6 Pin = IO0 + F18 Pin = IO14 + F17 Pin = IO15 +) + +// Analog pins +const ( + A0 Pin = IO26 + A1 Pin = IO27 + A2 Pin = IO28 + A3 Pin = IO29 +) + +// LED +const ( + LED = IO4 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = IO24 + I2C0_SCL_PIN Pin = IO25 + + I2C1_SDA_PIN Pin = IO6 + I2C1_SCL_PIN Pin = IO7 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = IO18 + SPI0_SDO_PIN Pin = IO19 + SPI0_SDI_PIN Pin = IO20 + + SPI1_SCK_PIN Pin = IO10 + SPI1_SDO_PIN Pin = IO11 + SPI1_SDI_PIN Pin = IO8 +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// UART pins +const ( + UART0_TX_PIN = IO28 + UART0_RX_PIN = IO29 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN + UART1_TX_PIN = IO24 + UART1_RX_PIN = IO25 +) + +var DefaultUART = UART0 + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Shrike-Lite" + usb_STRING_MANUFACTURER = "Vicharak" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) diff --git a/src/machine/board_xiao-esp32s3.go b/src/machine/board_xiao-esp32s3.go index 9181bffc2e..6e1e67b832 100644 --- a/src/machine/board_xiao-esp32s3.go +++ b/src/machine/board_xiao-esp32s3.go @@ -47,9 +47,15 @@ const ( // SPI pins const ( - SPI_SCK_PIN = GPIO7 - SPI_SDI_PIN = GPIO9 - SPI_SDO_PIN = GPIO8 + SPI1_SCK_PIN = GPIO7 // D8 + SPI1_MISO_PIN = GPIO8 // D9 + SPI1_MOSI_PIN = GPIO9 // D10 + SPI1_CS_PIN = NoPin + + SPI2_SCK_PIN = NoPin + SPI2_MOSI_PIN = NoPin + SPI2_MISO_PIN = NoPin + SPI2_CS_PIN = NoPin ) // Onboard LEDs diff --git a/src/machine/board_xiao-rp2350.go b/src/machine/board_xiao-rp2350.go new file mode 100644 index 0000000000..30003d4600 --- /dev/null +++ b/src/machine/board_xiao-rp2350.go @@ -0,0 +1,93 @@ +//go:build xiao_rp2350 + +// This file contains the pin mappings for the Seeed XIAO RP2350 boards. +// +// XIAO RP2350 is a microcontroller using the Raspberry Pi RP2350 chip. +// +// - https://wiki.seeedstudio.com/XIAO-RP2350/ +package machine + +// Digital Pins +const ( + D0 Pin = GPIO26 + D1 Pin = GPIO27 + D2 Pin = GPIO28 + D3 Pin = GPIO5 + D4 Pin = GPIO6 + D5 Pin = GPIO7 + D6 Pin = GPIO0 + D7 Pin = GPIO1 + D8 Pin = GPIO2 + D9 Pin = GPIO4 + D10 Pin = GPIO3 + D11 Pin = GPIO21 + D12 Pin = GPIO20 + D13 Pin = GPIO17 + D14 Pin = GPIO16 + D15 Pin = GPIO11 + D16 Pin = GPIO12 + D17 Pin = GPIO10 +) + +// Analog pins +const ( + A0 Pin = D0 + A1 Pin = D1 + A2 Pin = D2 +) + +// Onboard LEDs +const ( + NEOPIXEL = GPIO22 + WS2812 = GPIO22 + NEO_PWR = GPIO23 + NEOPIXEL_POWER = GPIO23 + + LED = GPIO25 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = D14 + I2C0_SCL_PIN Pin = D13 + + I2C1_SDA_PIN Pin = D4 + I2C1_SCL_PIN Pin = D5 +) + +// SPI pins +const ( + SPI0_SCK_PIN Pin = D8 + SPI0_SDO_PIN Pin = D10 + SPI0_SDI_PIN Pin = D9 + + SPI1_SCK_PIN Pin = D17 + SPI1_SDO_PIN Pin = D15 + SPI1_SDI_PIN Pin = D16 +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "XIAO RP2350" + usb_STRING_MANUFACTURER = "Seeed" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x000a +) diff --git a/src/machine/i2c.go b/src/machine/i2c.go index 016f1a969b..bc50a34f73 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 || esp32c6 || esp32s3 package machine diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 20dc41b8de..158be4911b 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -11,6 +11,7 @@ import ( "device/sam" "errors" "internal/binary" + "runtime/interrupt" "unsafe" ) diff --git a/src/machine/machine_attiny85.go b/src/machine/machine_attiny85.go index 33424c6052..6d31846b5a 100644 --- a/src/machine/machine_attiny85.go +++ b/src/machine/machine_attiny85.go @@ -21,3 +21,524 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { // Very simple for the attiny85, which only has a single port. return avr.PORTB, 1 << uint8(p) } + +// PWM is one PWM peripheral, which consists of a counter and two output +// channels (that can be connected to two fixed pins). You can set the frequency +// using SetPeriod, but only for all the channels in this PWM peripheral at +// once. +type PWM struct { + num uint8 +} + +var ( + Timer0 = PWM{0} // 8 bit timer for PB0 and PB1 + Timer1 = PWM{1} // 8 bit high-speed timer for PB1 and PB4 +) + +// GTCCR bits for Timer1 that are not defined in the device file +const ( + gtccrPWM1B = 0x40 // Pulse Width Modulator B Enable + gtccrCOM1B0 = 0x10 // Comparator B Output Mode bit 0 + gtccrCOM1B1 = 0x20 // Comparator B Output Mode bit 1 +) + +// Configure enables and configures this PWM. +// +// For Timer0, there is only a limited number of periods available, namely the +// CPU frequency divided by 256 and again divided by 1, 8, 64, 256, or 1024. +// For a MCU running at 8MHz, this would be a period of 32µs, 256µs, 2048µs, +// 8192µs, or 32768µs. +// +// For Timer1, the period is more flexible as it uses OCR1C as the top value. +// Timer1 also supports more prescaler values (1 to 16384). +func (pwm PWM) Configure(config PWMConfig) error { + switch pwm.num { + case 0: // Timer/Counter 0 (8-bit) + // Calculate the timer prescaler. + var prescaler uint8 + switch config.Period { + case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()): + prescaler = 1 + case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()): + prescaler = 2 + case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()): + prescaler = 3 + case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()): + prescaler = 4 + case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()): + prescaler = 5 + default: + return ErrPWMPeriodTooLong + } + + avr.TCCR0B.Set(prescaler) + // Set the PWM mode to fast PWM (mode = 3). + avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) + + case 1: // Timer/Counter 1 (8-bit high-speed) + // Timer1 on ATtiny85 is different from ATmega328: + // - It's 8-bit with configurable top (OCR1C) + // - Has more prescaler options (1-16384) + // - PWM mode is enabled per-channel via PWM1A/PWM1B bits + var top uint64 + if config.Period == 0 { + // Use a top appropriate for LEDs. + top = 0xff + } else { + // Calculate top value: top = period * (CPUFrequency / 1e9) + top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + // Timer1 prescaler values: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384 + const maxTop = 256 + var prescaler uint8 + switch { + case top <= maxTop: + prescaler = 1 // prescaler 1 + case top/2 <= maxTop: + prescaler = 2 // prescaler 2 + top /= 2 + case top/4 <= maxTop: + prescaler = 3 // prescaler 4 + top /= 4 + case top/8 <= maxTop: + prescaler = 4 // prescaler 8 + top /= 8 + case top/16 <= maxTop: + prescaler = 5 // prescaler 16 + top /= 16 + case top/32 <= maxTop: + prescaler = 6 // prescaler 32 + top /= 32 + case top/64 <= maxTop: + prescaler = 7 // prescaler 64 + top /= 64 + case top/128 <= maxTop: + prescaler = 8 // prescaler 128 + top /= 128 + case top/256 <= maxTop: + prescaler = 9 // prescaler 256 + top /= 256 + case top/512 <= maxTop: + prescaler = 10 // prescaler 512 + top /= 512 + case top/1024 <= maxTop: + prescaler = 11 // prescaler 1024 + top /= 1024 + case top/2048 <= maxTop: + prescaler = 12 // prescaler 2048 + top /= 2048 + case top/4096 <= maxTop: + prescaler = 13 // prescaler 4096 + top /= 4096 + case top/8192 <= maxTop: + prescaler = 14 // prescaler 8192 + top /= 8192 + case top/16384 <= maxTop: + prescaler = 15 // prescaler 16384 + top /= 16384 + default: + return ErrPWMPeriodTooLong + } + + // Set prescaler (CS1[3:0] bits) + avr.TCCR1.Set(prescaler) + // Set top value + avr.OCR1C.Set(uint8(top - 1)) + } + return nil +} + +// SetPeriod updates the period of this PWM peripheral. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// If you use a period of 0, a period that works well for LEDs will be picked. +// +// SetPeriod will not change the prescaler, but also won't change the current +// value in any of the channels. This means that you may need to update the +// value for the particular channel. +// +// Note that you cannot pick any arbitrary period after the PWM peripheral has +// been configured. If you want to switch between frequencies, pick the lowest +// frequency (longest period) once when calling Configure and adjust the +// frequency here as needed. +func (pwm PWM) SetPeriod(period uint64) error { + if pwm.num == 0 { + return ErrPWMPeriodTooLong // Timer0 doesn't support dynamic period + } + + // Timer1 can adjust period via OCR1C + var top uint64 + if period == 0 { + top = 0xff + } else { + top = period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + // Get current prescaler + prescaler := avr.TCCR1.Get() & 0x0f + // Timer1 prescaler values follow a power-of-2 pattern: + // prescaler n maps to divisor 2^(n-1), so we can use a simple shift + if prescaler > 0 && prescaler <= 15 { + top >>= (prescaler - 1) + } + + if top > 256 { + return ErrPWMPeriodTooLong + } + + avr.OCR1C.Set(uint8(top - 1)) + avr.TCNT1.Set(0) + + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. It +// will only change with a call to Configure or SetPeriod, otherwise it is +// constant. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to Set +// (see Set documentation for more information). +func (pwm PWM) Top() uint32 { + if pwm.num == 1 { + // Timer1 has configurable top via OCR1C + return uint32(avr.OCR1C.Get()) + 1 + } + // Timer0 goes from 0 to 0xff (256 in total) + return 256 +} + +// Counter returns the current counter value of the timer in this PWM +// peripheral. It may be useful for debugging. +func (pwm PWM) Counter() uint32 { + switch pwm.num { + case 0: + return uint32(avr.TCNT0.Get()) + case 1: + return uint32(avr.TCNT1.Get()) + } + return 0 +} + +// Prescaler lookup tables using uint16 (more efficient than uint64 on AVR) +// Timer0 prescaler lookup table (index 0-7 maps to prescaler bits) +var timer0Prescalers = [8]uint16{0, 1, 8, 64, 256, 1024, 0, 0} + +// Timer1 prescaler lookup table (index 0-15 maps to prescaler bits) +var timer1Prescalers = [16]uint16{0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384} + +// Period returns the used PWM period in nanoseconds. It might deviate slightly +// from the configured period due to rounding. +func (pwm PWM) Period() uint64 { + var prescaler uint64 + switch pwm.num { + case 0: + prescalerBits := avr.TCCR0B.Get() & 0x7 + prescaler = uint64(timer0Prescalers[prescalerBits]) + if prescaler == 0 { + return 0 + } + case 1: + prescalerBits := avr.TCCR1.Get() & 0x0f + prescaler = uint64(timer1Prescalers[prescalerBits]) + if prescaler == 0 { + return 0 + } + } + top := uint64(pwm.Top()) + return prescaler * top * 1000 / uint64(CPUFrequency()/1e6) +} + +// Channel returns a PWM channel for the given pin. +func (pwm PWM) Channel(pin Pin) (uint8, error) { + pin.Configure(PinConfig{Mode: PinOutput}) + pin.Low() + switch pwm.num { + case 0: + switch pin { + case PB0: // OC0A + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + return 0, nil + case PB1: // OC0B + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + return 1, nil + } + case 1: + switch pin { + case PB1: // OC1A + // Enable PWM on channel A + avr.TCCR1.SetBits(avr.TCCR1_PWM1A | avr.TCCR1_COM1A1) + return 0, nil + case PB4: // OC1B + // Enable PWM on channel B (controlled via GTCCR) + avr.GTCCR.SetBits(gtccrPWM1B | gtccrCOM1B1) + return 1, nil + } + } + return 0, ErrInvalidOutputPin +} + +// SetInverting sets whether to invert the output of this channel. +// Without inverting, a 25% duty cycle would mean the output is high for 25% of +// the time and low for the rest. Inverting flips the output as if a NOT gate +// was placed at the output, meaning that the output would be 25% low and 75% +// high with a duty cycle of 25%. +func (pwm PWM) SetInverting(channel uint8, inverting bool) { + switch pwm.num { + case 0: + switch channel { + case 0: // channel A, PB0 + if inverting { + avr.PORTB.SetBits(1 << 0) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0) + } else { + avr.PORTB.ClearBits(1 << 0) + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0) + } + case 1: // channel B, PB1 + if inverting { + avr.PORTB.SetBits(1 << 1) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0) + } else { + avr.PORTB.ClearBits(1 << 1) + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0) + } + } + case 1: + switch channel { + case 0: // channel A, PB1 + if inverting { + avr.PORTB.SetBits(1 << 1) + avr.TCCR1.SetBits(avr.TCCR1_COM1A0) + } else { + avr.PORTB.ClearBits(1 << 1) + avr.TCCR1.ClearBits(avr.TCCR1_COM1A0) + } + case 1: // channel B, PB4 + if inverting { + avr.PORTB.SetBits(1 << 4) + avr.GTCCR.SetBits(gtccrCOM1B0) + } else { + avr.PORTB.ClearBits(1 << 4) + avr.GTCCR.ClearBits(gtccrCOM1B0) + } + } + } +} + +// Set updates the channel value. This is used to control the channel duty +// cycle, in other words the fraction of time the channel output is high (or low +// when inverted). For example, to set it to a 25% duty cycle, use: +// +// pwm.Set(channel, pwm.Top() / 4) +// +// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, +// pwm.Top()) will set the output to high, assuming the output isn't inverted. +func (pwm PWM) Set(channel uint8, value uint32) { + switch pwm.num { + case 0: + switch channel { + case 0: // channel A, PB0 + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1) + } else { + avr.OCR0A.Set(uint8(value - 1)) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + } + case 1: // channel B, PB1 + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1) + } else { + avr.OCR0B.Set(uint8(value - 1)) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + } + } + case 1: + switch channel { + case 0: // channel A, PB1 + if value == 0 { + avr.TCCR1.ClearBits(avr.TCCR1_COM1A1) + } else { + avr.OCR1A.Set(uint8(value - 1)) + avr.TCCR1.SetBits(avr.TCCR1_COM1A1) + } + case 1: // channel B, PB4 + if value == 0 { + avr.GTCCR.ClearBits(gtccrCOM1B1) + } else { + avr.OCR1B.Set(uint8(value - 1)) + avr.GTCCR.SetBits(gtccrCOM1B1) + } + } + } +} + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + LSBFirst bool + Mode uint8 +} + +// SPI is the USI-based SPI implementation for ATTiny85. +// The ATTiny85 doesn't have dedicated SPI hardware, but uses the USI +// (Universal Serial Interface) in three-wire mode. +// +// Fixed pin mapping (directly controlled by USI hardware): +// - PB2: SCK (clock) +// - PB1: DO/MOSI (data out) +// - PB0: DI/MISO (data in) +// +// Note: CS pin must be managed by the user. +type SPI struct { + // Delay cycles for frequency control (0 = max speed) + delayCycles uint16 + + // USICR value configured for the selected SPI mode + usicrValue uint8 + + // LSB-first mode (requires software bit reversal) + lsbFirst bool +} + +// SPI0 is the USI-based SPI interface on the ATTiny85 +var SPI0 = SPI{} + +// Configure sets up the USI for SPI communication. +// Note: The user must configure and control the CS pin separately. +func (s *SPI) Configure(config SPIConfig) error { + // Configure USI pins (fixed by hardware) + // PB1 (DO/MOSI) -> OUTPUT + // PB2 (USCK/SCK) -> OUTPUT + // PB0 (DI/MISO) -> INPUT + PB1.Configure(PinConfig{Mode: PinOutput}) + PB2.Configure(PinConfig{Mode: PinOutput}) + PB0.Configure(PinConfig{Mode: PinInput}) + + // Reset USI registers + avr.USIDR.Set(0) + avr.USISR.Set(0) + + // Configure USI for SPI mode: + // - USIWM0: Three-wire mode (SPI) + // - USICS1: External clock source (software controlled via USITC) + // - USICLK: Clock strobe - enables counter increment on USITC toggle + // - USICS0: Controls clock phase (CPHA) + // + // SPI Modes: + // Mode 0 (CPOL=0, CPHA=0): Clock idle low, sample on rising edge + // Mode 1 (CPOL=0, CPHA=1): Clock idle low, sample on falling edge + // Mode 2 (CPOL=1, CPHA=0): Clock idle high, sample on falling edge + // Mode 3 (CPOL=1, CPHA=1): Clock idle high, sample on rising edge + // + // For USI, USICS0 controls the sampling edge when USICS1=1: + // USICS0=0: Positive edge (rising) + // USICS0=1: Negative edge (falling) + switch config.Mode { + case Mode0: // CPOL=0, CPHA=0: idle low, sample rising + PB2.Low() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICLK + case Mode1: // CPOL=0, CPHA=1: idle low, sample falling + PB2.Low() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICS0 | avr.USICR_USICLK + case Mode2: // CPOL=1, CPHA=0: idle high, sample falling + PB2.High() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICS0 | avr.USICR_USICLK + case Mode3: // CPOL=1, CPHA=1: idle high, sample rising + PB2.High() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICLK + default: // Default to Mode 0 + PB2.Low() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICLK + } + avr.USICR.Set(s.usicrValue) + + // Calculate delay cycles for frequency control + // Each bit transfer requires 2 clock toggles (rising + falling edge) + // The loop overhead is approximately 10-15 cycles per toggle on AVR + // We calculate additional delay cycles needed to achieve the target frequency + if config.Frequency > 0 && config.Frequency < CPUFrequency()/2 { + // Cycles per half-period = CPUFrequency / (2 * Frequency) + // Subtract loop overhead (~15 cycles) to get delay cycles + cyclesPerHalfPeriod := CPUFrequency() / (2 * config.Frequency) + const loopOverhead = 15 + if cyclesPerHalfPeriod > loopOverhead { + s.delayCycles = uint16(cyclesPerHalfPeriod - loopOverhead) + } else { + s.delayCycles = 0 + } + } else { + // Max speed - no delay + s.delayCycles = 0 + } + + // Store LSBFirst setting for use in Transfer + s.lsbFirst = config.LSBFirst + + return nil +} + +// reverseByte reverses the bit order of a byte (MSB <-> LSB) +// Used for LSB-first SPI mode since USI hardware only supports MSB-first +func reverseByte(b byte) byte { + b = (b&0xF0)>>4 | (b&0x0F)<<4 + b = (b&0xCC)>>2 | (b&0x33)<<2 + b = (b&0xAA)>>1 | (b&0x55)<<1 + return b +} + +// Transfer performs a single byte SPI transfer (send and receive simultaneously) +// This implements the USI-based SPI transfer using the "clock strobing" technique +func (s *SPI) Transfer(b byte) (byte, error) { + // For LSB-first mode, reverse the bits before sending + // USI hardware only supports MSB-first, so we do it in software + if s.lsbFirst { + b = reverseByte(b) + } + + // Load the byte to transmit into the USI Data Register + avr.USIDR.Set(b) + + // Clear the counter overflow flag by writing 1 to it (AVR quirk) + // This also resets the 4-bit counter to 0 + avr.USISR.Set(avr.USISR_USIOIF) + + // Clock the data out/in + // We need 16 clock toggles (8 bits × 2 edges per bit) + // The USI counter counts each clock edge, so it overflows at 16 + // After 16 toggles, the clock returns to its idle state (set by CPOL in Configure) + // + // IMPORTANT: Only toggle USITC here! + // - USITC toggles the clock pin + // - The USICR mode bits (USIWM0, USICS1, USICS0, USICLK) were set in Configure() + // - SetBits preserves those bits and only sets USITC + if s.delayCycles == 0 { + // Fast path: no delay, run at maximum speed + for !avr.USISR.HasBits(avr.USISR_USIOIF) { + avr.USICR.SetBits(avr.USICR_USITC) + } + } else { + // Frequency-controlled path: add delay between clock toggles + for !avr.USISR.HasBits(avr.USISR_USIOIF) { + avr.USICR.SetBits(avr.USICR_USITC) + // Delay loop for frequency control + // Each iteration is approximately 3 cycles on AVR (dec, brne) + for i := s.delayCycles; i > 0; i-- { + avr.Asm("nop") + } + } + } + + // Get the received byte + result := avr.USIDR.Get() + + // For LSB-first mode, reverse the received bits + if s.lsbFirst { + result = reverseByte(result) + } + + return result, nil +} diff --git a/src/machine/machine_esp32c3.go b/src/machine/machine_esp32c3.go index eb2a18b5ee..e5e10c326b 100644 --- a/src/machine/machine_esp32c3.go +++ b/src/machine/machine_esp32c3.go @@ -27,16 +27,29 @@ const ( PinInput PinInputPullup PinInputPulldown + PinAnalog +) + +const ( + GPIO0 Pin = 0 + GPIO1 Pin = 1 + GPIO2 Pin = 2 + GPIO3 Pin = 3 + GPIO4 Pin = 4 + GPIO5 Pin = 5 + GPIO6 Pin = 6 +) + +const ( + ADC0 Pin = GPIO0 + ADC1 Pin = GPIO1 + ADC2 Pin = GPIO2 + ADC3 Pin = GPIO3 + ADC4 Pin = GPIO4 + ADC5 Pin = GPIO5 // avoid when WiFi is used. ) const ( - GPIO0 Pin = 0 - GPIO1 Pin = 1 - GPIO2 Pin = 2 - GPIO3 Pin = 3 - GPIO4 Pin = 4 - GPIO5 Pin = 5 - GPIO6 Pin = 6 GPIO7 Pin = 7 GPIO8 Pin = 8 GPIO9 Pin = 9 @@ -76,13 +89,15 @@ func (p Pin) Configure(config PinConfig) { const function = 1 // function 1 is GPIO for every pin muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos - // Make this pin an input pin (always). - muxConfig |= esp.IO_MUX_GPIO_FUN_IE + // FUN_IE: disable for PinAnalog (high-Z for ADC) + if config.Mode != PinAnalog { + muxConfig |= esp.IO_MUX_GPIO_FUN_IE + } // Set drive strength: 0 is lowest, 3 is highest. muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos - // Select pull mode. + // Select pull mode (no pulls for PinAnalog). if config.Mode == PinInputPullup { muxConfig |= esp.IO_MUX_GPIO_FUN_WPU } else if config.Mode == PinInputPulldown { @@ -99,12 +114,27 @@ func (p Pin) Configure(config PinConfig) { case PinOutput: // Set the 'output enable' bit. esp.GPIO.ENABLE_W1TS.Set(1 << p) - case PinInput, PinInputPullup, PinInputPulldown: + case PinInput, PinInputPullup, PinInputPulldown, PinAnalog: // Clear the 'output enable' bit. esp.GPIO.ENABLE_W1TC.Set(1 << p) } } +// configure is the same as Configure, but allows setting a specific GPIO matrix signal. +func (p Pin) configure(config PinConfig, signal uint32) { + p.Configure(config) + if signal == 256 { + return + } + if config.Mode == PinOutput { + p.outFunc().Set(signal) + } else { + inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)) + } +} + +func initI2CExt1Clock() {} + // outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the // output function selection. func (p Pin) outFunc() *volatile.Register32 { @@ -509,102 +539,6 @@ func (uart *UART) writeByte(b byte) error { func (uart *UART) flush() {} -type Serialer interface { - WriteByte(c byte) error - Write(data []byte) (n int, err error) - Configure(config UARTConfig) error - Buffered() int - ReadByte() (byte, error) - DTR() bool - RTS() bool -} - -func initUSB() { - // nothing to do here -} - -// USB Serial/JTAG Controller -// See esp32-c3_technical_reference_manual_en.pdf -// pg. 736 -type USB_DEVICE struct { - Bus *esp.USB_DEVICE_Type -} - -var ( - _USBCDC = &USB_DEVICE{ - Bus: esp.USB_DEVICE, - } - - USBCDC Serialer = _USBCDC -) - -var ( - errUSBWrongSize = errors.New("USB: invalid write size") - errUSBCouldNotWriteAllData = errors.New("USB: could not write all data") - errUSBBufferEmpty = errors.New("USB: read buffer empty") -) - -func (usbdev *USB_DEVICE) Configure(config UARTConfig) error { - return nil -} - -func (usbdev *USB_DEVICE) WriteByte(c byte) error { - if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { - return errUSBCouldNotWriteAllData - } - - usbdev.Bus.SetEP1_RDWR_BYTE(uint32(c)) - usbdev.flush() - - return nil -} - -func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) { - if len(data) == 0 || len(data) > 64 { - return 0, errUSBWrongSize - } - - for i, c := range data { - if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { - if i > 0 { - usbdev.flush() - } - - return i, errUSBCouldNotWriteAllData - } - usbdev.Bus.SetEP1_RDWR_BYTE(uint32(c)) - } - - usbdev.flush() - return len(data), nil -} - -func (usbdev *USB_DEVICE) Buffered() int { - return int(usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL()) -} - -func (usbdev *USB_DEVICE) ReadByte() (byte, error) { - if usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 { - return byte(usbdev.Bus.GetEP1_RDWR_BYTE()), nil - } - - return 0, nil -} - -func (usbdev *USB_DEVICE) DTR() bool { - return false -} - -func (usbdev *USB_DEVICE) RTS() bool { - return false -} - -func (usbdev *USB_DEVICE) flush() { - usbdev.Bus.SetEP1_CONF_WR_DONE(1) - for usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { - } -} - // GetRNG returns 32-bit random numbers using the ESP32-C3 true random number generator, // Random numbers are generated based on the thermal noise in the system and the // asynchronous clock mismatch. diff --git a/src/machine/machine_esp32c3_adc.go b/src/machine/machine_esp32c3_adc.go new file mode 100644 index 0000000000..a908666264 --- /dev/null +++ b/src/machine/machine_esp32c3_adc.go @@ -0,0 +1,205 @@ +//go:build esp32c3 && !m5stamp_c3 + +package machine + +import ( + "device/esp" + "errors" +) + +// newRegI2C returns the regI2C configured for ESP32-C3: hostID=0, drefInit=1. +func newRegI2C() regI2C { return regI2C{hostID: 0, drefInit: 1} } + +const ( + // ADC attenuation values for ESP32-C3 APB_SARADC. + // 0 dB : ~0 .. 1.1 V + // 11 dB : ~0 .. 3.3 V (matches typical VDD) + atten0dB = 0 + atten11dB = 3 +) + +func InitADC() { + esp.SYSTEM.SetPERIP_RST_EN0_APB_SARADC_RST(1) + esp.SYSTEM.SetPERIP_CLK_EN0_APB_SARADC_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_APB_SARADC_RST(0) + + esp.RTC_CNTL.SetANA_CONF_SAR_I2C_PU(1) + esp.RTC_CNTL.SetSENSOR_CTRL_FORCE_XPD_SAR(1) + esp.APB_SARADC.SetCTRL_SARADC_XPD_SAR_FORCE(1) + esp.APB_SARADC.SetFSM_WAIT_SARADC_XPD_WAIT(8) + esp.APB_SARADC.SetFSM_WAIT_SARADC_RSTB_WAIT(8) + esp.APB_SARADC.SetFSM_WAIT_SARADC_STANDBY_WAIT(100) + esp.APB_SARADC.SetCLKM_CONF_CLK_SEL(2) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_NUM(1) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_B(0) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_A(0) + esp.APB_SARADC.SetCLKM_CONF_CLK_EN(1) + + adcSelfCalibrate() +} + +// ESP32-C3: ADC1 = GPIO0–GPIO4 (ch 0–4), ADC2 = GPIO5 (ch 0). ADC2 shares with Wi‑Fi; +// readings may be noisy when Wi‑Fi is active. +func (a ADC) Configure(config ADCConfig) error { + if a.Pin > 5 { + return errors.New("invalid ADC pin for ESP32-C3") + } + a.Pin.Configure(PinConfig{Mode: PinAnalog}) + return nil +} + +func (a ADC) Get() uint16 { + if a.Pin > 5 { + return 0 + } + adc1 := a.Pin <= 4 + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB) + esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1) + esp.APB_SARADC.SetINT_CLR_APB_SARADC2_DONE_INT_CLR(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + var raw uint32 + if adc1 { + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(uint32(a.Pin)) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1) + for esp.APB_SARADC.GetINT_RAW_APB_SARADC1_DONE_INT_RAW() == 0 { + } + raw = esp.APB_SARADC.GetSAR1DATA_STATUS_APB_SARADC1_DATA() + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0) + } else { + // ADC2: GPIO5 = channel 0. Grant arbiter to ADC2 first, then set channel and start. + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(0) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(1) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(8) // (1<<3)|0 for ADC2 channel 0 + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC2_ONETIME_SAMPLE(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1) + for esp.APB_SARADC.GetINT_RAW_APB_SARADC2_DONE_INT_RAW() == 0 { + } + raw = esp.APB_SARADC.GetSAR2DATA_STATUS_APB_SARADC2_DATA() + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC2_ONETIME_SAMPLE(0) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(0) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(0) + } + return uint16(raw&0xfff) << 4 +} + +// adcSelfCalibration +const ( + adcCalTimesC3 = 15 + adcCalRtcMagicC3 = uint32(0xADC1C401) + adcCalInitMinC3 = uint32(1000) + adcCalInitMaxC3 = uint32(4096) +) + +// selfCalibrate sets ADC1/ADC2 init code from RTC or runs self-calibration (GND). +// eFuse is not used: on ESP32-C3 the ADC calibration fields in BLK2 are often unprogrammed. +func adcSelfCalibrate() { + reg := newRegI2C() + reg.sarEnable() + + var adc1Code uint32 + if saved, ok := restoreFromRTC(); ok { + adc1Code = saved + } else { + calSetupADC1() + reg.calibrationInit(0) + reg.calibrationPrepare(0) + adc1Code = reg.calibrateBinarySearch(0, adcCalTimesC3, readADC1) + if adc1Code < adcCalInitMinC3 { + adc1Code = adcCalInitMinC3 + } + if adc1Code > adcCalInitMaxC3 { + adc1Code = adcCalInitMaxC3 + } + saveToRTC(adc1Code) + reg.calibrationFinish(0) + } + + applyADC1Code(reg, adc1Code) + applyADC2Code(reg, adc1Code) +} + +// calSetupADC1 configures APB_SARADC for oneshot sampling on ADC1 channel 0 +// with fixed attenuation. This is used only during self‑calibration. +func calSetupADC1() { + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC1_ONETIME_SAMPLE(1) +} + +// calSetupADC2 configures APB_SARADC for oneshot sampling on ADC2 (GPIO5, ch 0). +// On C3, onetime_channel = (unit<<3)|channel → ADC2 ch0 = 8. +func calSetupADC2() { + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_CHANNEL(8) // (1<<3)|0 for ADC2 + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(1) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC2_ONETIME_SAMPLE(1) +} + +// readADC1 performs a single ADC1 conversion using the APB_SARADC +// oneshot path and returns the raw 12‑bit result (0..4095). +func readADC1() uint32 { + esp.APB_SARADC.SetINT_CLR_APB_SARADC1_DONE_INT_CLR(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1) + for esp.APB_SARADC.GetINT_RAW_APB_SARADC1_DONE_INT_RAW() == 0 { + } + raw := esp.APB_SARADC.GetSAR1DATA_STATUS_APB_SARADC1_DATA() & 0xfff + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + return uint32(raw) +} + +// readADC2 performs a single ADC2 conversion and returns the raw 12‑bit result (0..4095). +func readADC2() uint32 { + esp.APB_SARADC.SetINT_CLR_APB_SARADC2_DONE_INT_CLR(1) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(1) + for esp.APB_SARADC.GetINT_RAW_APB_SARADC2_DONE_INT_RAW() == 0 { + } + raw := esp.APB_SARADC.GetSAR2DATA_STATUS_APB_SARADC2_DATA() & 0xfff + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_START(0) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_APB_FORCE(0) + esp.APB_SARADC.SetARB_CTRL_ADC_ARB_GRANT_FORCE(0) + return uint32(raw) +} + +func restoreFromRTC() (uint32, bool) { + if esp.RTC_CNTL.GetSTORE0() != adcCalRtcMagicC3 { + return 0, false + } + code := esp.RTC_CNTL.GetSTORE1() + if code < adcCalInitMinC3 || code > adcCalInitMaxC3 { + return 0, false + } + return code, true +} + +func saveToRTC(code uint32) { + if code < adcCalInitMinC3 || code > adcCalInitMaxC3 { + return + } + esp.RTC_CNTL.SetSTORE0(adcCalRtcMagicC3) + esp.RTC_CNTL.SetSTORE1(code) +} + +// applyADC1Code sets ADC1 init code and finishes calibration. +func applyADC1Code(reg regI2C, code uint32) { + calSetupADC1() + reg.calibrationInit(0) + reg.calibrationPrepare(0) + reg.setCalibrationParam(0, code) + reg.calibrationFinish(0) +} + +// applyADC2Code sets ADC2 init code and finishes calibration. On C3 eFuse V1 +// there is no separate ADC2 calibration; IDF uses ADC1 init code for both units. +func applyADC2Code(reg regI2C, code uint32) { + reg.calibrationInit(1) + reg.calibrationPrepare(1) + reg.setCalibrationParam(1, code) + reg.calibrationFinish(1) +} diff --git a/src/machine/machine_esp32c3_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_esp32c3s3_adc_sar.go b/src/machine/machine_esp32c3s3_adc_sar.go new file mode 100644 index 0000000000..899b9d6f55 --- /dev/null +++ b/src/machine/machine_esp32c3s3_adc_sar.go @@ -0,0 +1,19 @@ +//go:build (esp32c3 && !m5stamp_c3) || esp32s3 + +package machine + +import ( + "device/esp" + "runtime/volatile" + "unsafe" +) + +// sarEnable enables the analog SAR I2C domain before any regI2C access, +// matching the prologue in adc_ll_calibration_prepare(). +func (r regI2C) sarEnable() { + cfg := (*volatile.Register32)(unsafe.Pointer(anaConfigReg)) + cfg2 := (*volatile.Register32)(unsafe.Pointer(anaConfig2Reg)) + esp.RTC_CNTL.SetANA_CONF_SAR_I2C_PU(1) + cfg.Set(cfg.Get() &^ i2cSarEnMask) + cfg2.Set(cfg2.Get() | anaSarCfg2En) +} diff --git a/src/machine/machine_esp32c3s3_periph.go b/src/machine/machine_esp32c3s3_periph.go new file mode 100644 index 0000000000..8d186da1d7 --- /dev/null +++ b/src/machine/machine_esp32c3s3_periph.go @@ -0,0 +1,19 @@ +//go:build esp32c3 || esp32s3 + +package machine + +import "device/esp" + +// enableI2C0PeriphClock enables the I2C0 peripheral clock via SYSTEM. +func enableI2C0PeriphClock() { + 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) +} + +// enableLEDCPeriphClock enables the LEDC peripheral clock via SYSTEM. +func enableLEDCPeriphClock() { + 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) +} diff --git a/src/machine/machine_esp32c6.go b/src/machine/machine_esp32c6.go new file mode 100644 index 0000000000..b0d28f56db --- /dev/null +++ b/src/machine/machine_esp32c6.go @@ -0,0 +1,578 @@ +//go:build esp32c6 + +package machine + +import ( + "device/esp" + "device/riscv" + "errors" + "runtime/interrupt" + "runtime/volatile" + "sync" + "unsafe" +) + +const deviceName = esp.Device +const maxPin = 31 +const cpuInterruptFromPin = 6 + +// CPUFrequency returns the current CPU frequency of the chip. +// Currently it is a fixed frequency but it may allow changing in the future. +func CPUFrequency() uint32 { + return 160e6 // 160MHz +} + +const ( + PinOutput PinMode = iota + 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 + GPIO7 Pin = 7 + GPIO8 Pin = 8 + GPIO9 Pin = 9 + GPIO10 Pin = 10 + GPIO11 Pin = 11 + GPIO12 Pin = 12 + GPIO13 Pin = 13 + GPIO14 Pin = 14 + GPIO15 Pin = 15 + GPIO16 Pin = 16 + GPIO17 Pin = 17 + GPIO18 Pin = 18 + GPIO19 Pin = 19 + GPIO20 Pin = 20 + GPIO21 Pin = 21 + GPIO22 Pin = 22 + GPIO23 Pin = 23 + GPIO24 Pin = 24 + GPIO25 Pin = 25 + GPIO26 Pin = 26 + GPIO27 Pin = 27 + GPIO28 Pin = 28 + GPIO29 Pin = 29 + GPIO30 Pin = 30 +) + +const ( + ADC0 Pin = GPIO0 + ADC1 Pin = GPIO1 + ADC2 Pin = GPIO2 + ADC3 Pin = GPIO3 + ADC4 Pin = GPIO4 + ADC5 Pin = GPIO5 + ADC6 Pin = GPIO6 +) + +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + PinRising PinChange = iota + 1 + PinFalling + PinToggle +) + +// Configure this pin with the given configuration. +func (p Pin) Configure(config PinConfig) { + if p == NoPin { + // This simplifies pin configuration in peripherals such as SPI. + return + } + + var muxConfig uint32 + + // Configure this pin as a GPIO pin. + const function = 1 // function 1 is GPIO for every pin + muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos + + // 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 (no pulls for PinAnalog). + if config.Mode == PinInputPullup { + muxConfig |= esp.IO_MUX_GPIO_FUN_WPU + } else if config.Mode == PinInputPulldown { + muxConfig |= esp.IO_MUX_GPIO_FUN_WPD + } + + // Configure the pad with the given IO mux configuration. + p.mux().Set(muxConfig) + + // Set the output signal to the simple GPIO output. + p.outFunc().Set(0x80) + + switch config.Mode { + case PinOutput: + // Set the 'output enable' bit. + esp.GPIO.ENABLE_W1TS.Set(1 << p) + 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)) + } +} + +// enableI2C0PeriphClock enables the I2C0 peripheral clock via PCR. +func enableI2C0PeriphClock() { + esp.PCR.SetI2C0_CONF_I2C0_RST_EN(1) + esp.PCR.SetI2C0_CONF_I2C0_CLK_EN(1) + esp.PCR.SetI2C0_CONF_I2C0_RST_EN(0) +} + +// enableLEDCPeriphClock enables the LEDC peripheral clock via PCR. +func enableLEDCPeriphClock() { + esp.PCR.SetLEDC_CONF_LEDC_RST_EN(1) + esp.PCR.SetLEDC_CONF_LEDC_CLK_EN(1) + esp.PCR.SetLEDC_CONF_LEDC_RST_EN(0) +} + +func initI2CExt1Clock() {} + +// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the +// output function selection. +func (p Pin) outFunc() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4)) +} + +func (p Pin) pinReg() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4))) +} + +// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input +// function selection. +func inFunc(signal uint32) *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG), uintptr(signal)*4)) +} + +// mux returns the I/O mux configuration register corresponding to the given +// GPIO pin. +func (p Pin) mux() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.IO_MUX.GPIO0), uintptr(p)*4)) +} + +// pin returns the PIN register corresponding to the given GPIO pin. +func (p Pin) pin() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.PIN0), uintptr(p)*4)) +} + +// Set the pin to high or low. +// Warning: only use this on an output pin! +func (p Pin) Set(value bool) { + if value { + reg, mask := p.portMaskSet() + reg.Set(mask) + } else { + reg, mask := p.portMaskClear() + reg.Set(mask) + } +} + +// Get returns the current value of a GPIO pin when configured as an input or as +// an output. +func (p Pin) Get() bool { + reg := &esp.GPIO.IN + return (reg.Get()>>p)&1 > 0 +} + +// Return the register and mask to enable a given GPIO pin. This can be used to +// implement bit-banged drivers. +// +// Warning: only use this on an output pin! +func (p Pin) PortMaskSet() (*uint32, uint32) { + reg, mask := p.portMaskSet() + return ®.Reg, mask +} + +// Return the register and mask to disable a given GPIO pin. This can be used to +// implement bit-banged drivers. +// +// Warning: only use this on an output pin! +func (p Pin) PortMaskClear() (*uint32, uint32) { + reg, mask := p.portMaskClear() + return ®.Reg, mask +} + +func (p Pin) portMaskSet() (*volatile.Register32, uint32) { + return &esp.GPIO.OUT_W1TS, 1 << p +} + +func (p Pin) portMaskClear() (*volatile.Register32, uint32) { + return &esp.GPIO.OUT_W1TC, 1 << p +} + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// You can pass a nil func to unset the pin change interrupt. If you do so, +// the change parameter is ignored and can be set to any value (such as 0). +// If the pin is already configured with a callback, you must first unset +// this pins interrupt before you can set a new callback. +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) { + if p >= maxPin { + return ErrInvalidInputPin + } + + if callback == nil { + // Disable this pin interrupt + p.pin().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk) + + if pinCallbacks[p] != nil { + pinCallbacks[p] = nil + } + return nil + } + + if pinCallbacks[p] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + pinCallbacks[p] = callback + + onceSetupPinInterrupt.Do(func() { + err = setupPinInterrupt() + }) + if err != nil { + return err + } + + p.pin().Set( + (p.pin().Get() & ^uint32(esp.GPIO_PIN_INT_TYPE_Msk|esp.GPIO_PIN_INT_ENA_Msk)) | + uint32(change)< 1 { + return errWrongStopBitSize + } + // - data length + uart.Bus.SetCONF0_BIT_NUM(uint32(dataBits - 5)) + // - stop bit + uart.Bus.SetCONF0_STOP_BIT_NUM(uint32(stopBits)) + // - parity check + switch parity { + case ParityNone: + uart.Bus.SetCONF0_PARITY_EN(0) + case ParityEven: + uart.Bus.SetCONF0_PARITY_EN(1) + uart.Bus.SetCONF0_PARITY(0) + case ParityOdd: + uart.Bus.SetCONF0_PARITY_EN(1) + uart.Bus.SetCONF0_PARITY(1) + } + return nil +} + +func initUARTClock(bus *esp.UART_Type, regs registerSet) { + // On ESP32-C6, use the PCR peripheral for clock enable/reset. + switch { + case bus == esp.UART0: + // Enable UART0 clock via PCR + esp.PCR.UART0_CONF.SetBits(1 << 0) // UART0_CLK_EN + // Reset sequence + esp.PCR.UART0_CONF.SetBits(1 << 1) // UART0_RST_EN + esp.PCR.UART0_CONF.ClearBits(1 << 1) // clear reset + // Enable UART0 function clock + esp.PCR.UART0_SCLK_CONF.SetBits(1 << 22) // UART0_SCLK_EN + // Select 80MHz PLL clock source (UART0_SCLK_SEL = 1) + esp.PCR.UART0_SCLK_CONF.Set( + (esp.PCR.UART0_SCLK_CONF.Get() & ^uint32(3<<20)) | (1 << 20)) + case bus == esp.UART1: + // Enable UART1 clock via PCR + esp.PCR.UART1_CONF.SetBits(1 << 0) // UART1_CLK_EN + // Reset sequence + esp.PCR.UART1_CONF.SetBits(1 << 1) // UART1_RST_EN + esp.PCR.UART1_CONF.ClearBits(1 << 1) // clear reset + // Enable UART1 function clock + esp.PCR.UART1_SCLK_CONF.SetBits(1 << 22) // UART1_SCLK_EN + // Select 80MHz PLL clock source (UART1_SCLK_SEL = 1) + esp.PCR.UART1_SCLK_CONF.Set( + (esp.PCR.UART1_SCLK_CONF.Get() & ^uint32(3<<20)) | (1 << 20)) + } + + bus.SetCLK_CONF_RST_CORE(1) + bus.SetCLK_CONF_RST_CORE(0) + // synchronize core register + bus.SetREG_UPDATE(0) + // wait for Core Clock to ready for configuration + for bus.GetREG_UPDATE() > 0 { + riscv.Asm("nop") + } +} + +func (uart *UART) SetBaudRate(baudRate uint32) { + // based on esp-idf + max_div := uint32((1 << 12) - 1) + sclk_div := (pplClockFreq + (max_div * baudRate) - 1) / (max_div * baudRate) + clk_div := (pplClockFreq << 4) / (baudRate * sclk_div) + uart.Bus.SetCLKDIV(clk_div >> 4) + uart.Bus.SetCLKDIV_FRAG(clk_div & 0xf) + uart.Bus.SetCLK_CONF_SCLK_DIV_NUM(sclk_div - 1) +} + +func (uart *UART) setupPins(config UARTConfig, regs registerSet) { + config.RX.Configure(PinConfig{Mode: PinInputPullup}) + config.TX.Configure(PinConfig{Mode: PinInputPullup}) + + // link TX with GPIO signal X (technical reference manual) (this is not interrupt signal!) + config.TX.outFunc().Set(regs.gpioMatrixSignal) + // link RX with GPIO signal X and route signals via GPIO matrix (GPIO_SIGn_IN_SEL 0x40) + inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX)) +} + +func (uart *UART) configureInterrupt(intrMapReg *volatile.Register32) { + // Disable all UART interrupts + uart.Bus.INT_ENA.ClearBits(0x0ffff) + + intrMapReg.Set(7) + onceUart.Do(func() { + _ = interrupt.New(7, func(i interrupt.Interrupt) { + UART0.serveInterrupt(0) + UART1.serveInterrupt(1) + }).Enable() + }) +} + +func (uart *UART) serveInterrupt(num int) { + // get interrupt status + interrutFlag := uart.Bus.INT_ST.Get() + if (interrutFlag & uartInterrupts) == 0 { + return + } + + // block UART interrupts while processing + uart.Bus.INT_ENA.ClearBits(uartInterrupts) + + if interrutFlag&esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA > 0 { + for uart.Bus.GetSTATUS_RXFIFO_CNT() > 0 { + b := uart.Bus.GetFIFO_RXFIFO_RD_BYTE() + if !uart.Buffer.Put(byte(b & 0xff)) { + uart.DataOverflowDetected = true + } + } + } + if interrutFlag&esp.UART_INT_ENA_PARITY_ERR_INT_ENA > 0 { + uart.ParityErrorDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_FRM_ERR_INT_ENA { + uart.DataErrorDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA { + uart.DataOverflowDetected = true + } + if 0 != interrutFlag&esp.UART_INT_ENA_GLITCH_DET_INT_ENA { + uart.DataErrorDetected = true + } + + // Clear the UART interrupt status + uart.Bus.INT_CLR.SetBits(interrutFlag) + uart.Bus.INT_CLR.ClearBits(interrutFlag) + // Enable interrupts + uart.Bus.INT_ENA.Set(uartInterrupts) +} + +const uart_empty_thresh_default = 10 + +func (uart *UART) enableTransmitter() { + uart.Bus.SetCONF0_TXFIFO_RST(1) + uart.Bus.SetCONF0_TXFIFO_RST(0) + // TXINFO empty threshold is when txfifo_empty_int interrupt produced after the amount of data in Tx-FIFO is less than this register value. + uart.Bus.SetCONF1_TXFIFO_EMPTY_THRHD(uart_empty_thresh_default) +} + +func (uart *UART) enableReceiver() { + uart.Bus.SetCONF0_RXFIFO_RST(1) + uart.Bus.SetCONF0_RXFIFO_RST(0) + // using value 1 so that we can start populate ring buffer with data as we get it + uart.Bus.SetCONF1_RXFIFO_FULL_THRHD(1) + // enable interrupts for: + uart.Bus.SetINT_ENA_RXFIFO_FULL_INT_ENA(1) + uart.Bus.SetINT_ENA_FRM_ERR_INT_ENA(1) + uart.Bus.SetINT_ENA_PARITY_ERR_INT_ENA(1) + uart.Bus.SetINT_ENA_GLITCH_DET_INT_ENA(1) + uart.Bus.SetINT_ENA_RXFIFO_OVF_INT_ENA(1) +} + +func (uart *UART) writeByte(b byte) error { + for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 { + // Read UART_TXFIFO_CNT from the status register, which indicates how + // many bytes there are in the transmit buffer. Wait until there are + // less than 128 bytes in this buffer (the default buffer size). + } + uart.Bus.FIFO.Set(uint32(b)) + return nil +} + +func (uart *UART) flush() {} + +// GetRNG returns 32-bit random numbers using the ESP32-C6 true random number generator. +// Random numbers are generated based on the thermal noise in the system and the +// asynchronous clock mismatch. +func GetRNG() (ret uint32, err error) { + return esp.RNG.DATA.Get(), nil +} diff --git a/src/machine/machine_esp32c6_adc.go b/src/machine/machine_esp32c6_adc.go new file mode 100644 index 0000000000..eb7e34fb46 --- /dev/null +++ b/src/machine/machine_esp32c6_adc.go @@ -0,0 +1,81 @@ +//go:build esp32c6 + +package machine + +import ( + "device/esp" + "errors" + "runtime/volatile" + "unsafe" +) + +// newRegI2C returns the regI2C configured for ESP32-C6: hostID=0, drefInit=1. +func newRegI2C() regI2C { return regI2C{hostID: 0, drefInit: 1} } + +const ( + // ADC attenuation values for ESP32-C6 APB_SARADC. + // 0 dB : ~0 .. 1.1 V + // 11 dB : ~0 .. 3.3 V (matches typical VDD) + atten0dB = 0 + atten11dB = 3 +) + +// sarEnable enables the analog SAR I2C domain before any regI2C access. +// On ESP32-C6, RTC_CNTL does not exist; the SAR analog power is managed +// through the analog config registers directly. +func (r regI2C) sarEnable() { + cfg := (*volatile.Register32)(unsafe.Pointer(anaConfigReg)) + cfg2 := (*volatile.Register32)(unsafe.Pointer(anaConfig2Reg)) + cfg.Set(cfg.Get() &^ i2cSarEnMask) + cfg2.Set(cfg2.Get() | anaSarCfg2En) +} + +func InitADC() { + // Enable APB_SARADC clock via PCR. + esp.PCR.SARADC_CONF.SetBits(1 << 0) // SARADC_REG_CLK_EN + // Reset sequence + esp.PCR.SARADC_CONF.SetBits(1 << 1) // SARADC_RST_EN + esp.PCR.SARADC_CONF.ClearBits(1 << 1) // clear reset + + 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) + + esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_GATED(1) + esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_DIV(1) +} + +// ESP32-C6: ADC1 = GPIO0–GPIO6 (ch 0–6). ADC2 is not available on C6. +func (a ADC) Configure(config ADCConfig) error { + if a.Pin > 6 { + return errors.New("invalid ADC pin for ESP32-C6") + } + a.Pin.Configure(PinConfig{Mode: PinAnalog}) + return nil +} + +func (a ADC) Get() uint16 { + if a.Pin > 6 { + return 0 + } + esp.APB_SARADC.SetONETIME_SAMPLE_SARADC_ONETIME_ATTEN(atten11dB) + 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_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) + + return uint16(raw&0xfff) << 4 +} diff --git a/src/machine/machine_esp32c6_i2c.go b/src/machine/machine_esp32c6_i2c.go new file mode 100644 index 0000000000..2662f64894 --- /dev/null +++ b/src/machine/machine_esp32c6_i2c.go @@ -0,0 +1,22 @@ +//go:build esp32c6 + +package machine + +import ( + "device/esp" +) + +// GPIO matrix signal indices for I2C0 on ESP32-C6. +const ( + I2CEXT0_SCL_OUT_IDX = 53 + I2CEXT0_SDA_OUT_IDX = 54 +) + +var ( + I2C0 = &I2C{ + Bus: esp.I2C0, + funcSCL: I2CEXT0_SCL_OUT_IDX, + funcSDA: I2CEXT0_SDA_OUT_IDX, + useExt1: false, + } +) diff --git a/src/machine/machine_esp32c6_pwm.go b/src/machine/machine_esp32c6_pwm.go new file mode 100644 index 0000000000..5d49831285 --- /dev/null +++ b/src/machine/machine_esp32c6_pwm.go @@ -0,0 +1,177 @@ +//go:build esp32c6 + +package machine + +import "device/esp" + +// LEDC PWM for ESP32-C6: 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. + +// GPIO matrix output signal indices for LEDC. +const ( + LEDC_LS_SIG_OUT0_IDX = 45 +) + +const ledcChannelsC6 = 6 + +var ( + PWM0 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC6, timerNum: 0} + PWM1 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC6, timerNum: 1} + PWM2 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC6, timerNum: 2} + PWM3 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC6, timerNum: 3} +) + +// chanOp implements LEDC low-speed channel ops for ESP32-C6 (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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH0_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH0_GAMMA_WR_CH_GAMMA_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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH0_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH0_GAMMA_WR_CH_GAMMA_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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH1_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH1_GAMMA_WR_CH_GAMMA_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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH1_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH1_GAMMA_WR_CH_GAMMA_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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH2_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH2_GAMMA_WR_CH_GAMMA_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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH2_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH2_GAMMA_WR_CH_GAMMA_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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH3_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH3_GAMMA_WR_CH_GAMMA_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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH3_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH3_GAMMA_WR_CH_GAMMA_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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH4_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH4_GAMMA_WR_CH_GAMMA_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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH4_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH4_GAMMA_WR_CH_GAMMA_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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH5_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH5_GAMMA_WR_CH_GAMMA_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_GAMMA_WR_CH_GAMMA_DUTY_CYCLE(1) + esp.LEDC.SetCH5_GAMMA_WR_CH_GAMMA_DUTY_NUM(1) + esp.LEDC.SetCH5_GAMMA_WR_CH_GAMMA_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_esp32c6_spi.go b/src/machine/machine_esp32c6_spi.go new file mode 100644 index 0000000000..5ca51d24e7 --- /dev/null +++ b/src/machine/machine_esp32c6_spi.go @@ -0,0 +1,218 @@ +//go:build esp32c6 + +package machine + +// On the C6 variant, SPI2 is a general purpose SPI controller. SPI0 and SPI1 +// are used internally to access the ESP32-C6's attached flash memory. Due to +// different registers between SPI2 and the other SPI ports, this driver +// currently supports only the general purpose FSPI SPI2 controller. + +import ( + "device/esp" + "errors" + "runtime/volatile" + "unsafe" +) + +// GPIO matrix signal indices for SPI2 on ESP32-C6 (from gpio_sig_map.h). +const ( + FSPICLK_IN_IDX = uint32(63) + FSPICLK_OUT_IDX = uint32(63) + FSPIQ_IN_IDX = uint32(64) + FSPIQ_OUT_IDX = uint32(64) + FSPID_IN_IDX = uint32(65) + FSPID_OUT_IDX = uint32(65) + FSPIHD_IN_IDX = uint32(66) + FSPIHD_OUT_IDX = uint32(66) + FSPIWP_IN_IDX = uint32(67) + FSPIWP_OUT_IDX = uint32(67) + FSPICS0_IN_IDX = uint32(68) + FSPICS0_OUT_IDX = uint32(68) + FSPICS1_OUT_IDX = uint32(69) + FSPICS2_OUT_IDX = uint32(70) + FSPICS3_OUT_IDX = uint32(71) + FSPICS4_OUT_IDX = uint32(72) + FSPICS5_OUT_IDX = uint32(73) +) + +var ( + ErrInvalidSPIBus = errors.New("machine: SPI bus is invalid") + ErrInvalidSPIMode = errors.New("machine: SPI mode is invalid") +) + +// Serial Peripheral Interface on the ESP32-C6. +type SPI struct { + Bus *esp.SPI2_Type +} + +var ( + // SPI0 and SPI1 are reserved for use by the caching system etc. + SPI2 = &SPI{esp.SPI2} + SPI0 = SPI2 +) + +// Configure and make the SPI peripheral ready to use. +func (spi *SPI) Configure(config SPIConfig) error { + if spi.Bus != esp.SPI2 { + return ErrInvalidSPIBus + } + + // Enable SPI2 clock and reset via PCR. + esp.PCR.SPI2_CONF.SetBits(1 << 0) // SPI2_CLK_EN + esp.PCR.SPI2_CONF.SetBits(1 << 1) // SPI2_RST_EN + esp.PCR.SPI2_CONF.ClearBits(1 << 1) // clear reset + + // init the spi2 bus + spi.Bus.SLAVE.Set(0) + spi.Bus.MISC.Set(0) + spi.Bus.USER.Set(0) + spi.Bus.USER1.Set(0) + spi.Bus.CTRL.Set(0) + spi.Bus.CLK_GATE.Set(0) + spi.Bus.DMA_CONF.Set(0) + spi.Bus.SetDMA_CONF_RX_AFIFO_RST(1) + spi.Bus.SetDMA_CONF_BUF_AFIFO_RST(1) + spi.Bus.CLOCK.Set(0) + + // clear data buf + spi.Bus.SetW0(0) + spi.Bus.SetW1(0) + spi.Bus.SetW2(0) + spi.Bus.SetW3(0) + spi.Bus.SetW4(0) + spi.Bus.SetW5(0) + spi.Bus.SetW6(0) + spi.Bus.SetW7(0) + spi.Bus.SetW8(0) + spi.Bus.SetW9(0) + spi.Bus.SetW10(0) + spi.Bus.SetW11(0) + spi.Bus.SetW12(0) + spi.Bus.SetW13(0) + spi.Bus.SetW14(0) + spi.Bus.SetW15(0) + + // start the spi2 bus + spi.Bus.SetCLK_GATE_CLK_EN(1) + spi.Bus.SetCLK_GATE_MST_CLK_SEL(1) + spi.Bus.SetCLK_GATE_MST_CLK_ACTIVE(1) + spi.Bus.SetDMA_CONF_SLV_TX_SEG_TRANS_CLR_EN(1) + spi.Bus.SetDMA_CONF_SLV_RX_SEG_TRANS_CLR_EN(1) + spi.Bus.SetDMA_CONF_DMA_SLV_SEG_TRANS_EN(0) + spi.Bus.SetUSER_USR_MOSI(1) + spi.Bus.SetUSER_USR_MISO(1) + spi.Bus.SetUSER_DOUTDIN(1) + + // set spi2 data mode + switch config.Mode { + case Mode0: + spi.Bus.SetMISC_CK_IDLE_EDGE(0) + spi.Bus.SetUSER_CK_OUT_EDGE(0) + case Mode1: + spi.Bus.SetMISC_CK_IDLE_EDGE(0) + spi.Bus.SetUSER_CK_OUT_EDGE(1) + case Mode2: + spi.Bus.SetMISC_CK_IDLE_EDGE(1) + spi.Bus.SetUSER_CK_OUT_EDGE(1) + case Mode3: + spi.Bus.SetMISC_CK_IDLE_EDGE(1) + spi.Bus.SetUSER_CK_OUT_EDGE(0) + default: + return ErrInvalidSPIMode + } + + // set spi2 bit order + if config.LSBFirst { + spi.Bus.SetCTRL_WR_BIT_ORDER(1) // LSB first + spi.Bus.SetCTRL_RD_BIT_ORDER(1) + } else { + spi.Bus.SetCTRL_WR_BIT_ORDER(0) // MSB first + spi.Bus.SetCTRL_RD_BIT_ORDER(0) + } + + // configure SPI bus clock + spi.Bus.CLOCK.Set(freqToClockDiv(config.Frequency)) + + // configure gpio pin matrix + config.SDI.Configure(PinConfig{Mode: PinInput}) + inFunc(FSPIQ_IN_IDX).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.SDI)) + config.SDO.Configure(PinConfig{Mode: PinOutput}) + config.SDO.outFunc().Set(FSPID_OUT_IDX) + config.SCK.Configure(PinConfig{Mode: PinOutput}) + config.SCK.outFunc().Set(FSPICLK_OUT_IDX) + if config.CS != NoPin { + config.CS.Configure(PinConfig{Mode: PinOutput}) + config.CS.outFunc().Set(FSPICS0_OUT_IDX) + } + + return nil +} + +// Transfer writes/reads a single byte using the SPI interface. +func (spi *SPI) Transfer(w byte) (byte, error) { + spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(7) + + spi.Bus.SetW0(uint32(w)) + + // Send/receive byte. + spi.Bus.SetCMD_UPDATE(1) + for spi.Bus.GetCMD_UPDATE() != 0 { + } + + spi.Bus.SetCMD_USR(1) + for spi.Bus.GetCMD_USR() != 0 { + } + + // The received byte is stored in W0. + return byte(spi.Bus.GetW0()), nil +} + +// Tx handles read/write operation for SPI interface. +func (spi *SPI) Tx(w, r []byte) error { + toTransfer := len(w) + if len(r) > toTransfer { + toTransfer = len(r) + } + + for toTransfer > 0 { + chunkSize := toTransfer + if chunkSize > 64 { + chunkSize = 64 + } + + transferWords := (*[16]volatile.Register32)(unsafe.Pointer(uintptr(unsafe.Pointer(&spi.Bus.W0)))) + spiTxFillBuffer(transferWords, w) + + spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(uint32(chunkSize)*8 - 1) + + spi.Bus.SetCMD_UPDATE(1) + for spi.Bus.GetCMD_UPDATE() != 0 { + } + + spi.Bus.SetCMD_USR(1) + for spi.Bus.GetCMD_USR() != 0 { + } + + rxSize := 64 + if rxSize > len(r) { + rxSize = len(r) + } + for i := 0; i < rxSize; i++ { + r[i] = byte(transferWords[i/4].Get() >> ((i % 4) * 8)) + } + + if len(w) < chunkSize { + w = nil + } else { + w = w[chunkSize:] + } + if len(r) < chunkSize { + r = nil + } else { + r = r[chunkSize:] + } + toTransfer -= chunkSize + } + + return nil +} 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..2ebe059eb6 --- /dev/null +++ b/src/machine/machine_esp32xx_adc.go @@ -0,0 +1,224 @@ +//go:build esp32c6 || 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 ( + "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..ed0e686d69 --- /dev/null +++ b/src/machine/machine_esp32xx_i2c.go @@ -0,0 +1,334 @@ +//go:build (esp32c3 || esp32c6 || 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 { + enableI2C0PeriphClock() + } 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..666042ebf0 --- /dev/null +++ b/src/machine/machine_esp32xx_pwm.go @@ -0,0 +1,177 @@ +//go:build esp32c3 || esp32c6 || 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. + enableLEDCPeriphClock() + + // 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..56b1e4a2fa --- /dev/null +++ b/src/machine/machine_esp32xx_spi.go @@ -0,0 +1,101 @@ +//go:build esp32c3 || esp32c6 || esp32s3 + +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..c7c40b4409 --- /dev/null +++ b/src/machine/machine_esp32xx_usb.go @@ -0,0 +1,102 @@ +//go:build esp32c3 || esp32c6 || 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_rp2_adc.go b/src/machine/machine_rp2_adc.go index e0d6a459a9..12ff152dc9 100644 --- a/src/machine/machine_rp2_adc.go +++ b/src/machine/machine_rp2_adc.go @@ -19,10 +19,8 @@ var adcAref uint32 // InitADC resets the ADC peripheral. func InitADC() { - rp.RESETS.RESET.SetBits(rp.RESETS_RESET_ADC) - rp.RESETS.RESET.ClearBits(rp.RESETS_RESET_ADC) - for !rp.RESETS.RESET_DONE.HasBits(rp.RESETS_RESET_ADC) { - } + resetBlock(rp.RESETS_RESET_ADC) + unresetBlockWait(rp.RESETS_RESET_ADC) // enable ADC rp.ADC.CS.Set(rp.ADC_CS_EN) adcAref = 3300 diff --git a/src/machine/machine_rp2_i2c.go b/src/machine/machine_rp2_i2c.go index 54a5e5357b..e4de7a783b 100644 --- a/src/machine/machine_rp2_i2c.go +++ b/src/machine/machine_rp2_i2c.go @@ -259,10 +259,7 @@ func (i2c *I2C) init(config I2CConfig) error { //go:inline func (i2c *I2C) reset() { resetVal := i2c.deinit() - rp.RESETS.RESET.ClearBits(resetVal) - // Wait until reset is done. - for !rp.RESETS.RESET_DONE.HasBits(resetVal) { - } + unresetBlockWait(resetVal) } // deinit sets reset bit for I2C. Must call reset to reenable I2C after deinit. @@ -276,15 +273,13 @@ func (i2c *I2C) deinit() (resetVal uint32) { resetVal = rp.RESETS_RESET_I2C1 } // Perform I2C reset. - rp.RESETS.RESET.SetBits(resetVal) + resetBlock(resetVal) return resetVal } // tx performs blocking write followed by read to I2C bus. func (i2c *I2C) tx(addr uint8, tx, rx []byte) (err error) { - const timeout_us = 4_000 - deadline := ticks() + timeout_us if addr >= 0x80 || isReservedI2CAddr(addr) { return errInvalidTgtAddr } @@ -295,6 +290,14 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte) (err error) { return nil } + // Base 4ms for small register pokes. + // Add per-byte budget. 100us/byte is conservative at 400kHz and still ok at 100kHz for modest sizes. + timeout_us := uint64(4_000) + uint64(txlen+rxlen)*100 + // Cap so it doesn't go insane: + timeout_us = min(timeout_us, 500_000) + + deadline := ticks() + timeout_us + err = i2c.disable() if err != nil { return err diff --git a/src/machine/machine_rp2_spi.go b/src/machine/machine_rp2_spi.go index 75e4f86b7b..f3fb256f61 100644 --- a/src/machine/machine_rp2_spi.go +++ b/src/machine/machine_rp2_spi.go @@ -212,10 +212,7 @@ func (spi *SPI) setFormat(mode uint8) { //go:inline func (spi *SPI) reset() { resetVal := spi.deinit() - rp.RESETS.RESET.ClearBits(resetVal) - // Wait until reset is done. - for !rp.RESETS.RESET_DONE.HasBits(resetVal) { - } + unresetBlockWait(resetVal) } //go:inline @@ -227,7 +224,7 @@ func (spi *SPI) deinit() (resetVal uint32) { resetVal = rp.RESETS_RESET_SPI1 } // Perform SPI reset. - rp.RESETS.RESET.SetBits(resetVal) + resetBlock(resetVal) return resetVal } diff --git a/src/machine/machine_rp2_uart.go b/src/machine/machine_rp2_uart.go index 872418a766..37e2ca9c2a 100644 --- a/src/machine/machine_rp2_uart.go +++ b/src/machine/machine_rp2_uart.go @@ -73,6 +73,27 @@ func (uart *UART) Configure(config UARTConfig) error { return nil } +// Close the UART and disable its interrupt/power use. +func (uart *UART) Close() error { + uart.Interrupt.Disable() + + // Disable UART. + uart.Bus.UARTCR.ClearBits(rp.UART0_UARTCR_UARTEN) + + var resetVal uint32 + switch { + case uart.Bus == rp.UART0: + resetVal = rp.RESETS_RESET_UART0 + case uart.Bus == rp.UART1: + resetVal = rp.RESETS_RESET_UART1 + } + + // reset UART + resetBlock(resetVal) + + return nil +} + // SetBaudRate sets the baudrate to be used for the UART. func (uart *UART) SetBaudRate(br uint32) { div := 8 * CPUFrequency() / br @@ -148,10 +169,8 @@ func initUART(uart *UART) { } // reset UART - rp.RESETS.RESET.SetBits(resetVal) - rp.RESETS.RESET.ClearBits(resetVal) - for !rp.RESETS.RESET_DONE.HasBits(resetVal) { - } + resetBlock(resetVal) + unresetBlockWait(resetVal) } // handleInterrupt should be called from the appropriate interrupt handler for diff --git a/src/machine/machine_stm32_adc_g0.go b/src/machine/machine_stm32_adc_g0.go index d8cca3c8df..7d7150ade1 100644 --- a/src/machine/machine_stm32_adc_g0.go +++ b/src/machine/machine_stm32_adc_g0.go @@ -123,7 +123,7 @@ func (a ADC) Get() uint16 { // Select the channel to convert using CHSELR // CHSELR uses a bitfield where bit N = 1 enables channel N - stm32.ADC.CHSELR.Set(1 << ch) + stm32.ADC.CHSELR0.Set(1 << ch) // Wait for channel configuration ready for stm32.ADC.GetISR_CCRDY() == 0 { diff --git a/src/machine/machine_stm32_uart.go b/src/machine/machine_stm32_uart.go index 6d37582448..6beafe4e1c 100644 --- a/src/machine/machine_stm32_uart.go +++ b/src/machine/machine_stm32_uart.go @@ -1,4 +1,4 @@ -//go:build stm32 && !stm32g0 +//go:build stm32 package machine diff --git a/src/machine/machine_stm32g0.go b/src/machine/machine_stm32g0.go index b338faef1a..530c77aade 100644 --- a/src/machine/machine_stm32g0.go +++ b/src/machine/machine_stm32g0.go @@ -230,7 +230,7 @@ func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { func (uart *UART) setRegisters() { uart.rxReg = &uart.Bus.RDR uart.txReg = &uart.Bus.TDR - uart.statusReg = &uart.Bus.ISR_FIFO_ENABLED + uart.statusReg = &uart.Bus.ISR uart.txEmptyFlag = stm32.USART_ISR_TXE } @@ -508,7 +508,7 @@ func (t *TIM) registerUPInterrupt() interrupt.Interrupt { case &TIM3: return interrupt.New(stm32.IRQ_TIM3_TIM4, TIM3.handleUPInterrupt) case &TIM6: - return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleUPInterrupt) + return interrupt.New(stm32.IRQ_TIM6_DAC_LPTIM1, TIM6.handleUPInterrupt) case &TIM7: return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) case &TIM14: @@ -533,7 +533,7 @@ func (t *TIM) registerOCInterrupt() interrupt.Interrupt { case &TIM3: return interrupt.New(stm32.IRQ_TIM3_TIM4, TIM3.handleOCInterrupt) case &TIM6: - return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleOCInterrupt) + return interrupt.New(stm32.IRQ_TIM6_DAC_LPTIM1, TIM6.handleOCInterrupt) case &TIM7: return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) case &TIM14: diff --git a/src/machine/machine_stm32g0_uart.go b/src/machine/machine_stm32g0_uart.go deleted file mode 100644 index 18544c3ecd..0000000000 --- a/src/machine/machine_stm32g0_uart.go +++ /dev/null @@ -1,86 +0,0 @@ -//go:build stm32g0 - -package machine - -// Peripheral abstraction layer for UARTs on the stm32g0 family. - -import ( - "device/stm32" - "runtime/interrupt" - "runtime/volatile" - "unsafe" -) - -// UART representation -type UART struct { - Buffer *RingBuffer - Bus *stm32.USART_Type - Interrupt interrupt.Interrupt - TxAltFuncSelector uint8 - RxAltFuncSelector uint8 - - // Registers specific to the chip - rxReg *volatile.Register32 - txReg *volatile.Register32 - statusReg *volatile.Register32 - txEmptyFlag uint32 -} - -// Configure the UART. -func (uart *UART) Configure(config UARTConfig) { - // Default baud rate to 115200. - if config.BaudRate == 0 { - config.BaudRate = 115200 - } - - // Set the GPIO pins to defaults if they're not set - if config.TX == 0 && config.RX == 0 { - config.TX = UART_TX_PIN - config.RX = UART_RX_PIN - } - - // STM32 families have different, but compatible, registers for - // basic UART functions. For each family populate the registers - // into `uart`. - uart.setRegisters() - - // Enable USART clock - enableAltFuncClock(unsafe.Pointer(uart.Bus)) - - uart.configurePins(config) - - // Set baud rate - uart.SetBaudRate(config.BaudRate) - - // Enable USART port, tx, rx and rx interrupts - // STM32G0 uses CR1_FIFO_ENABLED register - uart.Bus.CR1_FIFO_ENABLED.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE) - - // Enable RX IRQ - uart.Interrupt.SetPriority(0xc0) - uart.Interrupt.Enable() -} - -// handleInterrupt should be called from the appropriate interrupt handler for -// this UART instance. -func (uart *UART) handleInterrupt(interrupt.Interrupt) { - uart.Receive(byte((uart.rxReg.Get() & 0xFF))) -} - -// SetBaudRate sets the communication speed for the UART. Defer to chip-specific -// routines for calculation -func (uart *UART) SetBaudRate(br uint32) { - divider := uart.getBaudRateDivisor(br) - uart.Bus.BRR.Set(divider) -} - -// WriteByte writes a byte of data to the UART. -func (uart *UART) writeByte(c byte) error { - uart.txReg.Set(uint32(c)) - - for !uart.statusReg.HasBits(uart.txEmptyFlag) { - } - return nil -} - -func (uart *UART) flush() {} diff --git a/src/machine/machine_stm32l0x1.go b/src/machine/machine_stm32l0x1.go index f0d23ca495..292b81f462 100644 --- a/src/machine/machine_stm32l0x1.go +++ b/src/machine/machine_stm32l0x1.go @@ -188,8 +188,8 @@ func (t *TIM) enableMainOutput() { // nothing to do - no BDTR register } -type arrtype = uint16 -type arrRegType = volatile.Register16 +type arrtype = uint32 +type arrRegType = volatile.Register32 const ( ARR_MAX = 0x10000 diff --git a/src/machine/machine_stm32l0x2.go b/src/machine/machine_stm32l0x2.go index 2a747920be..fe1ba3a229 100644 --- a/src/machine/machine_stm32l0x2.go +++ b/src/machine/machine_stm32l0x2.go @@ -245,8 +245,8 @@ func (t *TIM) enableMainOutput() { // nothing to do - no BDTR register } -type arrtype = uint16 -type arrRegType = volatile.Register16 +type arrtype = uint32 +type arrRegType = volatile.Register32 const ( ARR_MAX = 0x10000 diff --git a/src/machine/machine_stm32l4x5.go b/src/machine/machine_stm32l4x5.go index c8c550c3da..28fb5da334 100644 --- a/src/machine/machine_stm32l4x5.go +++ b/src/machine/machine_stm32l4x5.go @@ -1,4 +1,4 @@ -//go:build stm32l4x5 +//go:build stm32l4y5 package machine diff --git a/src/machine/spi.go b/src/machine/spi.go index 9a1033ca7d..3247f05a09 100644 --- a/src/machine/spi.go +++ b/src/machine/spi.go @@ -1,4 +1,4 @@ -//go:build !baremetal || atmega || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2) +//go:build !baremetal || atmega || attiny85 || esp32 || esp32c3 || esp32c6 || esp32s3 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2) package machine diff --git a/src/machine/spi_tx.go b/src/machine/spi_tx.go index 97385bb596..aec3f52fe1 100644 --- a/src/machine/spi_tx.go +++ b/src/machine/spi_tx.go @@ -1,4 +1,4 @@ -//go:build atmega || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2) +//go:build atmega || attiny85 || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2) // This file implements the SPI Tx function for targets that don't have a custom // (faster) implementation for it. diff --git a/src/machine/usb/cdc/buffer.go b/src/machine/usb/cdc/buffer.go deleted file mode 100644 index ad5eb3645b..0000000000 --- a/src/machine/usb/cdc/buffer.go +++ /dev/null @@ -1,119 +0,0 @@ -package cdc - -import ( - "runtime/volatile" -) - -const rxRingBufferSize = 128 - -// rxRingBuffer is ring buffer implementation inspired by post at -// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php -type rxRingBuffer struct { - buffer [rxRingBufferSize]volatile.Register8 - head volatile.Register8 - tail volatile.Register8 -} - -// NewRxRingBuffer returns a new ring buffer. -func NewRxRingBuffer() *rxRingBuffer { - return &rxRingBuffer{} -} - -// Used returns how many bytes in buffer have been used. -func (rb *rxRingBuffer) Used() uint8 { - return uint8(rb.head.Get() - rb.tail.Get()) -} - -// Put stores a byte in the buffer. If the buffer is already -// full, the method will return false. -func (rb *rxRingBuffer) Put(val byte) bool { - if rb.Used() != rxRingBufferSize { - rb.head.Set(rb.head.Get() + 1) - rb.buffer[rb.head.Get()%rxRingBufferSize].Set(val) - return true - } - return false -} - -// Get returns a byte from the buffer. If the buffer is empty, -// the method will return a false as the second value. -func (rb *rxRingBuffer) Get() (byte, bool) { - if rb.Used() != 0 { - rb.tail.Set(rb.tail.Get() + 1) - return rb.buffer[rb.tail.Get()%rxRingBufferSize].Get(), true - } - return 0, false -} - -// Clear resets the head and tail pointer to zero. -func (rb *rxRingBuffer) Clear() { - rb.head.Set(0) - rb.tail.Set(0) -} - -const txRingBufferSize = 8 - -// txRingBuffer is ring buffer implementation inspired by post at -// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php -type txRingBuffer struct { - buffer [txRingBufferSize]struct { - buf [64]byte - size int - } - head volatile.Register8 - tail volatile.Register8 -} - -// NewTxRingBuffer returns a new ring buffer. -func NewTxRingBuffer() *txRingBuffer { - return &txRingBuffer{} -} - -// Used returns how many bytes in buffer have been used. -func (rb *txRingBuffer) Used() uint8 { - return uint8(rb.head.Get() - rb.tail.Get()) -} - -// Put stores a byte in the buffer. If the buffer is already -// full, the method will return false. -func (rb *txRingBuffer) Put(val []byte) bool { - if rb.Used() == txRingBufferSize { - return false - } - - if rb.Used() == 0 { - rb.head.Set(rb.head.Get() + 1) - rb.buffer[rb.head.Get()%txRingBufferSize].size = 0 - } - buf := &rb.buffer[rb.head.Get()%txRingBufferSize] - - for i := 0; i < len(val); i++ { - if buf.size == 64 { - // next - // TODO: Make sure that data is not corrupted even when the buffer is full - rb.head.Set(rb.head.Get() + 1) - buf = &rb.buffer[rb.head.Get()%txRingBufferSize] - rb.buffer[rb.head.Get()%txRingBufferSize].size = 0 - } - buf.buf[buf.size] = val[i] - buf.size++ - } - return true -} - -// Get returns a byte from the buffer. If the buffer is empty, -// the method will return a false as the second value. -func (rb *txRingBuffer) Get() ([]byte, bool) { - if rb.Used() != 0 { - rb.tail.Set(rb.tail.Get() + 1) - size := rb.buffer[rb.tail.Get()%txRingBufferSize].size - return rb.buffer[rb.tail.Get()%txRingBufferSize].buf[:size], true - } - return nil, false -} - -// Clear resets the head and tail pointer to zero. -func (rb *txRingBuffer) Clear() { - rb.head.Set(0) - rb.tail.Set(0) -} diff --git a/src/machine/usb/cdc/cdc.go b/src/machine/usb/cdc/cdc.go index f180535df1..44bd7f7e0e 100644 --- a/src/machine/usb/cdc/cdc.go +++ b/src/machine/usb/cdc/cdc.go @@ -1,3 +1,5 @@ +//go:build baremetal + package cdc const ( @@ -9,10 +11,7 @@ const ( // New returns USBCDC struct. func New() *USBCDC { if USB == nil { - USB = &USBCDC{ - rxBuffer: NewRxRingBuffer(), - txBuffer: NewTxRingBuffer(), - } + USB = &USBCDC{} } return USB } diff --git a/src/machine/usb/cdc/ring.go b/src/machine/usb/cdc/ring.go new file mode 100644 index 0000000000..ca8ce4b175 --- /dev/null +++ b/src/machine/usb/cdc/ring.go @@ -0,0 +1,98 @@ +package cdc + +import "sync/atomic" + +// ring512 is an interrupt/concurrent-safe ring buffer for a single-producer, +// single-consumer (SPSC) pair. The writer calls Put, the reader calls +// Peek/Discard. Reset may only be called when neither side is active. +// +// Implementation uses monotonic counters (head/tail) instead of bounded +// offsets. Unsigned subtraction (head - tail) always yields +// correct used count regardless of uint32 overflow. +type ring512 struct { + buf [ringBufLen]byte // power of 2 so compiler can optimize & mask. + // head counts total bytes written. Only the writer stores to head. + head atomic.Uint32 + // tail counts total bytes read. Only the reader stores to tail. + tail atomic.Uint32 +} + +const ( + ringBufLen = 512 + ringMask = ringBufLen - 1 // 0x1FF +) + +// Reset empties the ring buffer. Must not be called concurrently with +// Put, Peek, or Discard. +func (r *ring512) Reset() { + r.head.Store(0) + r.tail.Store(0) +} + +// Free returns number of bytes that can be written via Put. +func (r *ring512) Free() uint32 { + return ringBufLen - r.Used() +} + +// Used returns number of bytes ready to be peeked/discarded. +func (r *ring512) Used() uint32 { + return r.head.Load() - r.tail.Load() +} + +// Peek returns contiguous views into the readable portions of the buffer +// without advancing the read position. When data wraps around the end of +// the internal buffer, two segments are returned. Second data2 is nil on fully contiguous buffer. +// Returns nil,nil when empty. +func (r *ring512) Peek() (data1, data2 []byte) { + head, tail := r.lims() + used := head - tail + if used == 0 { + return nil, nil + } + pos := tail & ringMask + contig := ringBufLen - pos + if contig >= 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< 31 { + return errors.New("interrupt for ESP32-C6 must be in range of 1 through 31") + } + mask := riscv.DisableInterrupts() + defer riscv.EnableInterrupts(mask) + + // enable CPU interrupt number i.num + esp.INTPRI.CPU_INT_ENABLE.SetBits(1 << i.num) + + // Set pulse interrupt type (rising edge detection) + esp.INTPRI.CPU_INT_TYPE.SetBits(1 << i.num) + + // Set default threshold to defaultThreshold + reg := (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.INTPRI.CPU_INT_PRI_0), i.num*4)) + reg.Set(defaultThreshold) + + // Reset interrupt before reenabling + esp.INTPRI.CPU_INT_CLEAR.SetBits(1 << i.num) + esp.INTPRI.CPU_INT_CLEAR.ClearBits(1 << i.num) + + // we must wait for any pending write operations to complete + riscv.Asm("fence") + return nil +} + +// Adding pseudo function calls that is replaced by the compiler with the actual +// functions registered through interrupt.New. +// +//go:linkname callHandlers runtime/interrupt.callHandlers +func callHandlers(num int) + +const ( + IRQNUM_1 = 1 + iota + IRQNUM_2 + IRQNUM_3 + IRQNUM_4 + IRQNUM_5 + IRQNUM_6 + IRQNUM_7 + IRQNUM_8 + IRQNUM_9 + IRQNUM_10 + IRQNUM_11 + IRQNUM_12 + IRQNUM_13 + IRQNUM_14 + IRQNUM_15 + IRQNUM_16 + IRQNUM_17 + IRQNUM_18 + IRQNUM_19 + IRQNUM_20 + IRQNUM_21 + IRQNUM_22 + IRQNUM_23 + IRQNUM_24 + IRQNUM_25 + IRQNUM_26 + IRQNUM_27 + IRQNUM_28 + IRQNUM_29 + IRQNUM_30 + IRQNUM_31 +) + +const ( + defaultThreshold = 5 + disableThreshold = 10 +) + +//go:inline +func callHandler(n int) { + switch n { + case IRQNUM_1: + callHandlers(IRQNUM_1) + case IRQNUM_2: + callHandlers(IRQNUM_2) + case IRQNUM_3: + callHandlers(IRQNUM_3) + case IRQNUM_4: + callHandlers(IRQNUM_4) + case IRQNUM_5: + callHandlers(IRQNUM_5) + case IRQNUM_6: + callHandlers(IRQNUM_6) + case IRQNUM_7: + callHandlers(IRQNUM_7) + case IRQNUM_8: + callHandlers(IRQNUM_8) + case IRQNUM_9: + callHandlers(IRQNUM_9) + case IRQNUM_10: + callHandlers(IRQNUM_10) + case IRQNUM_11: + callHandlers(IRQNUM_11) + case IRQNUM_12: + callHandlers(IRQNUM_12) + case IRQNUM_13: + callHandlers(IRQNUM_13) + case IRQNUM_14: + callHandlers(IRQNUM_14) + case IRQNUM_15: + callHandlers(IRQNUM_15) + case IRQNUM_16: + callHandlers(IRQNUM_16) + case IRQNUM_17: + callHandlers(IRQNUM_17) + case IRQNUM_18: + callHandlers(IRQNUM_18) + case IRQNUM_19: + callHandlers(IRQNUM_19) + case IRQNUM_20: + callHandlers(IRQNUM_20) + case IRQNUM_21: + callHandlers(IRQNUM_21) + case IRQNUM_22: + callHandlers(IRQNUM_22) + case IRQNUM_23: + callHandlers(IRQNUM_23) + case IRQNUM_24: + callHandlers(IRQNUM_24) + case IRQNUM_25: + callHandlers(IRQNUM_25) + case IRQNUM_26: + callHandlers(IRQNUM_26) + case IRQNUM_27: + callHandlers(IRQNUM_27) + case IRQNUM_28: + callHandlers(IRQNUM_28) + case IRQNUM_29: + callHandlers(IRQNUM_29) + case IRQNUM_30: + callHandlers(IRQNUM_30) + case IRQNUM_31: + callHandlers(IRQNUM_31) + } +} + +//export handleInterrupt +func handleInterrupt() { + mcause := riscv.MCAUSE.Get() + exception := mcause&(1<<31) == 0 + interruptNumber := uint32(mcause & 0x1f) + + if !exception && interruptNumber > 0 { + // save MSTATUS & MEPC, which could be overwritten by another CPU interrupt + mstatus := riscv.MSTATUS.Get() + mepc := riscv.MEPC.Get() + // Using threshold to temporary disable this interrupts. + // FYI: using CPU interrupt enable bit make runtime to loose interrupts. + reg := (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.INTPRI.CPU_INT_PRI_0), interruptNumber*4)) + thresholdSave := reg.Get() + reg.Set(disableThreshold) + riscv.Asm("fence") + + interruptBit := uint32(1 << interruptNumber) + + // reset pending status interrupt + if esp.INTPRI.CPU_INT_TYPE.Get()&interruptBit != 0 { + // this is edge type interrupt + esp.INTPRI.CPU_INT_CLEAR.SetBits(interruptBit) + esp.INTPRI.CPU_INT_CLEAR.ClearBits(interruptBit) + } else { + // this is level type interrupt + esp.INTPRI.CPU_INT_CLEAR.ClearBits(interruptBit) + } + + // enable CPU interrupts + riscv.MSTATUS.SetBits(riscv.MSTATUS_MIE) + + // Call registered interrupt handler(s) + callHandler(int(interruptNumber)) + + // disable CPU interrupts + riscv.MSTATUS.ClearBits(riscv.MSTATUS_MIE) + + // restore interrupt threshold to enable interrupt again + reg.Set(thresholdSave) + riscv.Asm("fence") + + // Zero MCAUSE so that interrupt.In() returns false once we + // return to normal (non-interrupt) code. Other RISC-V targets + // (FE310, K210) do the same. + riscv.MCAUSE.Set(0) + + // restore MSTATUS & MEPC + riscv.MSTATUS.Set(mstatus) + riscv.MEPC.Set(mepc) + + // do not enable CPU interrupts now + // the 'MRET' in src/device/riscv/handleinterrupt.S will copies the state of MPIE back into MIE, and subsequently clears MPIE. + } else { + // Topmost bit is clear, so it is an exception of some sort. + handleException(mcause) + } +} + +func handleException(mcause uintptr) { + println("*** Exception: pc:", riscv.MEPC.Get()) + println("*** Exception: code:", uint32(mcause&0x1f)) + println("*** Exception: mcause:", mcause) + switch uint32(mcause & 0x1f) { + case riscv.InstructionAccessFault: + println("*** virtual address:", riscv.MTVAL.Get()) + case riscv.IllegalInstruction: + println("*** opcode:", riscv.MTVAL.Get()) + case riscv.LoadAccessFault: + println("*** read address:", riscv.MTVAL.Get()) + case riscv.StoreOrAMOAccessFault: + println("*** write address:", riscv.MTVAL.Get()) + } + for { + riscv.Asm("wfi") + } +} diff --git a/src/runtime/rand_hwrng.go b/src/runtime/rand_hwrng.go index bb5b2e8f85..93bcfd0243 100644 --- a/src/runtime/rand_hwrng.go +++ b/src/runtime/rand_hwrng.go @@ -1,4 +1,4 @@ -//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350) +//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32c6 || esp32s3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350) // If you update the above build constraint, you'll probably also need to update // src/crypto/rand/rand_baremetal.go. diff --git a/src/runtime/rand_norng.go b/src/runtime/rand_norng.go index 0e3b6d09b3..b073a8d28d 100644 --- a/src/runtime/rand_norng.go +++ b/src/runtime/rand_norng.go @@ -1,4 +1,4 @@ -//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350) +//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32c6 || esp32s3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350) package runtime diff --git a/src/runtime/runtime_esp32_io.go b/src/runtime/runtime_esp32_io.go new file mode 100644 index 0000000000..d7cb2e1c6d --- /dev/null +++ b/src/runtime/runtime_esp32_io.go @@ -0,0 +1,21 @@ +//go:build esp32 || esp32c3 + +package runtime + +import "machine" + +func putchar(c byte) { + machine.Serial.WriteByte(c) +} + +func getchar() byte { + for machine.Serial.Buffered() == 0 { + Gosched() + } + v, _ := machine.Serial.ReadByte() + return v +} + +func buffered() int { + return machine.Serial.Buffered() +} diff --git a/src/runtime/runtime_esp32c3.go b/src/runtime/runtime_esp32c3.go index 013c939246..f85f7dec78 100644 --- a/src/runtime/runtime_esp32c3.go +++ b/src/runtime/runtime_esp32c3.go @@ -54,10 +54,6 @@ func main() { // Configure interrupt handler interruptInit() - // Initialize UART. - machine.USBCDC.Configure(machine.UARTConfig{}) - machine.InitSerial() - // Initialize main system timer used for time.Now. initTimer() @@ -68,6 +64,11 @@ func main() { exit(0) } +func init() { + // Initialize UART. + machine.InitSerial() +} + func abort() { // lock up forever for { diff --git a/src/runtime/runtime_esp32c6.go b/src/runtime/runtime_esp32c6.go new file mode 100644 index 0000000000..a2cbd5dc3b --- /dev/null +++ b/src/runtime/runtime_esp32c6.go @@ -0,0 +1,141 @@ +//go:build esp32c6 + +package runtime + +import ( + "device/esp" + "device/riscv" + "machine" + "runtime/volatile" + "unsafe" +) + +// This is the function called on startup after the flash (IROM/DROM) is +// initialized and the stack pointer has been set. +// +//export main +func main() { + // This initialization configures the following things: + // * It disables all watchdog timers. They might be useful at some point in + // the future, but will need integration into the scheduler. For now, + // they're all disabled. + // * It sets the CPU frequency to 160MHz, which is the maximum speed allowed + // for this CPU. Lower frequencies might be possible in the future, but + // running fast and sleeping quickly is often also a good strategy to save + // power. + + // Disable Timer Group 0 watchdog (unlock first). + esp.TIMG0.WDTWPROTECT.Set(0x50D83AA1) + esp.TIMG0.WDTCONFIG0.Set(0) + + // Disable Timer Group 1 watchdog (unlock first). + esp.TIMG1.WDTWPROTECT.Set(0x50D83AA1) + esp.TIMG1.WDTCONFIG0.Set(0) + + // Disable LP watchdog (write-protect key first). + esp.LP_WDT.WDTWPROTECT.Set(0x50D83AA1) + esp.LP_WDT.WDTCONFIG0.Set(0) + + // Disable super watchdog. + esp.LP_WDT.SWD_WPROTECT.Set(0x50D83AA1) + esp.LP_WDT.SWD_CONF.SetBits(1 << 30) // SWD_DISABLE bit + + // Change CPU frequency to 160MHz from SPLL (480MHz). + // + // Clock tree: SPLL (480MHz) → HP root → CPU / AHB / APB + // + // Set dividers BEFORE switching the clock source so the first PLL + // cycle already arrives divided: + // HP root = SPLL / (HS_DIV_NUM+1) = 480 / 3 = 160 MHz + // CPU = HP root / (CPU_HS_DIV_NUM+1) = 160 / 1 = 160 MHz + // AHB = HP root / (AHB_HS_DIV_NUM+1) = 160 / 4 = 40 MHz + // APB = AHB / (APB_HS_DIV_NUM+1) = 40 / 1 = 40 MHz + esp.PCR.CPU_FREQ_CONF.Set(0 << 8) // CPU_HS_DIV_NUM = 0 (div1) + esp.PCR.AHB_FREQ_CONF.Set(3 << 8) // AHB_HS_DIV_NUM = 3 (div4) + esp.PCR.APB_FREQ_CONF.Set(0 << 8) // APB_HS_DIV_NUM = 0 (div1) + + // Switch to PLL: SOC_CLK_SEL = 1 (SPLL), HS_DIV_NUM = 2 (div3). + esp.PCR.SYSCLK_CONF.Set(1<<16 | 2<<8) + + clearbss() + + // Configure interrupt handler + interruptInit() + + // Initialize main system timer used for time.Now. + initTimer() + + // Initialize the heap, call main.main, etc. + run() + + // Fallback: if main ever returns, hang the CPU. + exit(0) +} + +func init() { + machine.InitSerial() +} + +func abort() { + // lock up forever + for { + riscv.Asm("wfi") + } +} + +// interruptInit initialize the interrupt controller and called from runtime once. +func interruptInit() { + mie := riscv.DisableInterrupts() + + // Reset all interrupt source priorities to zero. + priReg := &esp.INTPRI.CPU_INT_PRI_1 + for i := 0; i < 31; i++ { + priReg.Set(0) + priReg = (*volatile.Register32)(unsafe.Add(unsafe.Pointer(priReg), 4)) + } + + // default threshold for interrupts is 5 + esp.INTPRI.CPU_INT_THRESH.Set(5) + + // Set the interrupt address. + // Set MODE field to 1 - a vector base address (only supported by ESP32-C6) + // Note that this address must be aligned to 256 bytes. + riscv.MTVEC.Set((uintptr(unsafe.Pointer(&_vector_table))) | 1) + + riscv.EnableInterrupts(mie) +} + +//go:extern _vector_table +var _vector_table [0]uintptr + +// Serial I/O via USB-Serial-JTAG controller. +// The ESP32-C6-DevKitC connects its USB port to the internal USB-Serial-JTAG +// peripheral, not to UART0. The ROM bootloader also uses this path, so output +// appears on the same port as boot messages. + +func putchar(c byte) { + // Wait for the USB-Serial-JTAG TX FIFO to have space. + // Use a timeout to avoid hanging if no host is reading. + for i := 0; i < 10000; i++ { + if esp.USB_DEVICE.EP1_CONF.Get()&0x2 != 0 { // SERIAL_IN_EP_DATA_FREE + break + } + } + // Write byte directly to EP1 FIFO. We must NOT use the generated + // SetEP1_RDWR_BYTE accessor because it does a read-modify-write, + // and reading EP1 pops a byte from the RX FIFO. + esp.USB_DEVICE.EP1.Set(uint32(c)) + // Signal that data has been written to the FIFO. + esp.USB_DEVICE.EP1_CONF.Set(1) // WR_DONE +} + +func getchar() byte { + // Not implemented for USB-Serial-JTAG yet. + for { + riscv.Asm("wfi") + } +} + +func buffered() int { + return 0 +} diff --git a/src/runtime/runtime_esp32s3.go b/src/runtime/runtime_esp32s3.go index 35cd26da85..b469ddf9e9 100644 --- a/src/runtime/runtime_esp32s3.go +++ b/src/runtime/runtime_esp32s3.go @@ -4,6 +4,7 @@ package runtime import ( "device/esp" + "machine" ) // This is the function called on startup after the flash (IROM/DROM) is @@ -49,8 +50,22 @@ func main() { // Change CPU frequency from 80MHz to 240MHz by setting SYSTEM_PLL_FREQ_SEL to // 1 and SYSTEM_CPUPERIOD_SEL to 2 (see table "CPU Clock Frequency" in the // reference manual). + // We do this gradually to allow PLL and system to stabilize. esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1) + + // First switch to 160MHz (intermediate step) + esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(1) + // Small delay to let PLL stabilize at 160MHz + for i := 0; i < 1000; i++ { + _ = esp.SYSTEM.CPU_PER_CONF.Get() + } + + // Now switch to 240MHz esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2) + // Small delay to let PLL stabilize at 240MHz + for i := 0; i < 1000; i++ { + _ = esp.SYSTEM.CPU_PER_CONF.Get() + } // Clear bss. Repeat many times while we wait for cpu/clock to stabilize for x := 0; x < 30; x++ { @@ -67,6 +82,11 @@ func main() { exit(0) } +func init() { + // Initialize UART. + machine.InitSerial() +} + func abort() { // lock up forever print("abort called\n") diff --git a/src/runtime/runtime_esp32xx.go b/src/runtime/runtime_esp32xx.go index f1c62243f1..9a6e19cb11 100644 --- a/src/runtime/runtime_esp32xx.go +++ b/src/runtime/runtime_esp32xx.go @@ -1,10 +1,9 @@ -//go:build esp32 || esp32c3 +//go:build esp32 || esp32c3 || esp32c6 package runtime import ( "device/esp" - "machine" "unsafe" ) @@ -66,19 +65,3 @@ func sleepTicks(d timeUnit) { func exit(code int) { abort() } - -func putchar(c byte) { - machine.Serial.WriteByte(c) -} - -func getchar() byte { - for machine.Serial.Buffered() == 0 { - Gosched() - } - v, _ := machine.Serial.ReadByte() - return v -} - -func buffered() int { - return machine.Serial.Buffered() -} diff --git a/src/runtime/runtime_stm32l4x5.go b/src/runtime/runtime_stm32l4x5.go index 273eb726f5..c7c242975f 100644 --- a/src/runtime/runtime_stm32l4x5.go +++ b/src/runtime/runtime_stm32l4x5.go @@ -1,4 +1,4 @@ -//go:build stm32 && stm32l4x5 +//go:build stm32 && stm32l4y5 package runtime diff --git a/src/runtime/runtime_wasmentry.go b/src/runtime/runtime_wasmentry.go index 005b58641e..59cacb3b04 100644 --- a/src/runtime/runtime_wasmentry.go +++ b/src/runtime/runtime_wasmentry.go @@ -34,8 +34,8 @@ func wasmEntryReactor() { // Initialize the heap. heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - initRand() initHeap() + initRand() if hasScheduler { // A package initializer might do funky stuff like start a goroutine and diff --git a/targets/esp32.json b/targets/esp32.json index f49282fdd5..7aad3da4e9 100644 --- a/targets/esp32.json +++ b/targets/esp32.json @@ -15,7 +15,7 @@ "src/internal/task/task_stack_esp32.S" ], "binary-format": "esp32", - "flash-command": "esptool.py --chip=esp32 --port {port} write_flash 0x1000 {bin} -ff 80m -fm dout", + "flash-method": "esp32flash", "emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw", "gdb": ["xtensa-esp32-elf-gdb"] } diff --git a/targets/esp32c3.json b/targets/esp32c3.json index fd993b8bb8..96af15d515 100644 --- a/targets/esp32c3.json +++ b/targets/esp32c3.json @@ -13,7 +13,7 @@ "src/device/esp/esp32c3.S" ], "binary-format": "esp32c3", - "flash-command": "esptool.py --chip=esp32c3 --port {port} write_flash 0x0 {bin}", + "flash-method": "esp32jtag", "serial-port": ["303a:1001"], "openocd-interface": "esp_usb_jtag", "openocd-target": "esp32c3", diff --git a/targets/esp32c6.json b/targets/esp32c6.json new file mode 100644 index 0000000000..44af0304ea --- /dev/null +++ b/targets/esp32c6.json @@ -0,0 +1,19 @@ +{ + "inherits": ["riscv32"], + "features": "+32bit,+a,+c,+m,+zaamo,+zalrsc,+zmmul,-b,-d,-e,-experimental-sdext,-experimental-sdtrig,-experimental-smctr,-experimental-ssctr,-experimental-svukte,-experimental-xqcia,-experimental-xqciac,-experimental-xqcicli,-experimental-xqcicm,-experimental-xqcics,-experimental-xqcicsr,-experimental-xqciint,-experimental-xqcilo,-experimental-xqcilsm,-experimental-xqcisls,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-experimental-zvbc32e,-experimental-zvkgs,-f,-h,-relax,-sha,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smdbltrp,-smepmp,-smmpm,-smnpm,-smrnmi,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssdbltrp,-ssnpm,-sspm,-ssqosid,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-supm,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-svvptc,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xmipscmove,-xmipslsp,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zabha,-zacas,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "build-tags": ["esp32c6", "esp"], + "serial": "usb", + "rtlib": "compiler-rt", + "libc": "picolibc", + "linkerscript": "targets/esp32c6.ld", + "extra-files": [ + "src/device/esp/esp32c6.S" + ], + "binary-format": "esp32c6", + "flash-method": "esp32jtag", + "serial-port": ["303a:1001"], + "openocd-interface": "esp_usb_jtag", + "openocd-target": "esp32c6", + "openocd-commands": ["gdb_memory_map disable"], + "gdb": ["riscv32-esp-elf-gdb"] +} diff --git a/targets/esp32c6.ld b/targets/esp32c6.ld new file mode 100644 index 0000000000..63ad6eff86 --- /dev/null +++ b/targets/esp32c6.ld @@ -0,0 +1,853 @@ +/* Linker script for the ESP32-C6 + * + * The ESP32-C6 has a simpler memory layout than the ESP32-C3: + * - It has 512kB of HP-SRAM. Unlike the C3, IRAM and DRAM share the same + * address space at 0x40800000, so there is no need for separate DRAM/IRAM + * regions. + * - It has 16kB of LP-SRAM at 0x50000000 for low-power / RTC use. + * - DROM and IROM are in separate, non-overlapping 8MB address ranges: + * IROM at 0x42000000 and DROM at 0x42800000. + * - The MMU works in pages of 64kB, which means the bottom 16 bits of the + * address in flash and the address in DROM/IROM need to match. + * - Memory in SRAM is loaded at reset by the ROM bootloader. + * + * The firmware image segments are sorted by virtual address (by esp.go): + * 1. .data (SRAM 0x408...) - loaded by ROM bootloader + * 2. .iram (SRAM 0x408...) - loaded by ROM bootloader + * 3. .text (IROM 0x420...) - flash-mapped via MMU + * 4. .rodata (DROM 0x428...) - flash-mapped via MMU + * + * IMPORTANT: SRAM sections (.data, .iram) are defined BEFORE the dummy + * sections so that SIZEOF() references are backward (not forward). lld may + * not correctly resolve forward SIZEOF() references, which would produce + * wrong dummy sizes and corrupt the firmware image. + */ + +MEMORY +{ + /* HP-SRAM: unified IRAM/DRAM address space (512K). + * Unlike the C3, IRAM and DRAM share the same address space, so we use + * a single memory region to avoid linker overlap errors. + */ + SRAM (rwx) : ORIGIN = 0x40800000, LENGTH = 512K + + /* DROM and IROM are in separate, non-overlapping address ranges. */ + DROM (r) : ORIGIN = 0x42800000, LENGTH = 8M /* Data bus (read-only, flash-mapped) */ + IROM (rx) : ORIGIN = 0x42000000, LENGTH = 8M /* Instruction bus (flash-mapped) */ +} + +/* The entry point. It is set in the image flashed to the chip, so must be + * defined. + */ +ENTRY(call_start_cpu0) + +SECTIONS +{ + /* === SRAM sections (loaded by ROM bootloader) === */ + + /* Put the stack at the bottom of SRAM, so that the application will + * crash on stack overflow instead of silently corrupting memory. + * See: http://blog.japaric.io/stack-overflow-protection/ + */ + .stack (NOLOAD) : + { + . = ALIGN(16); + . += _stack_size; + _stack_top = .; + } >SRAM + + /* Global variables that are mutable and zero-initialized. + * These must be zeroed at startup (unlike data, which is loaded by the + * bootloader). + */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN (4); + _sbss = ABSOLUTE(.); + *(.sbss) + *(.bss .bss.*) + . = ALIGN (4); + _ebss = ABSOLUTE(.); + } >SRAM + + /* Mutable global variables. This data (in the SRAM segment) is initialized + * by the ROM bootloader. + */ + .data : ALIGN(4) + { + . = ALIGN (4); + _sdata = ABSOLUTE(.); + *(.sdata) + *(.data .data.*) + *(.dram*) + . = ALIGN (4); + _edata = ABSOLUTE(.); + } >SRAM + + /* Code that must run from IRAM (not flash-mapped), for example interrupt + * vectors and code that runs before the flash has been mapped. + * Since IRAM and DRAM share the same address space on ESP32-C6, this is + * placed sequentially after .data in the same SRAM region. + */ + .iram : ALIGN(4) + { + *(.init) /* call_start_cpu0 (before flash is mapped) */ + *(.iram*) /* code that must run from IRAM */ + *(.text.handleInterruptASM) /* must be in SRAM, within JAL range of _vector_table */ + . = ALIGN(256); + *(.text.exception_vectors) + . = ALIGN(4); + } > SRAM + + /* Heap starts after IRAM. */ + . = ALIGN(4); + _heap_start = ABSOLUTE(.); + _heap_end = ORIGIN(SRAM) + LENGTH(SRAM); + + /* === IROM sections (flash-mapped code) === */ + + /* Dummy section to align .text virtual address with its flash offset. + * In the firmware image, .data and .iram (SRAM) come before .text (IROM). + * SIZEOF(.data) and SIZEOF(.iram) are backward references (defined above). + * .data may be empty (esp.go skips empty sections), so use a ternary. + */ + .irom_dummy (NOLOAD) : ALIGN(0x10000) + { + . += 0x18; /* image header */ + . += (SIZEOF(.data) > 0) ? (SIZEOF(.data) + 0x8) : 0; /* data segment + header (if non-empty) */ + . += SIZEOF(.iram) + 0x8; /* iram segment + header */ + . += 0x8; /* text segment header */ + } > IROM + + /* Code stored in IROM (flash-mapped). + * This is the main program code. + */ + .text : ALIGN(4) + { + *(.text .text.*) + } > IROM + + /* === DROM sections (flash-mapped read-only data) === */ + + /* Dummy section to align .rodata virtual address with its flash offset. + * Segments in the image are sorted by address: SRAM (.data, .iram), + * IROM (.text), then DROM (.rodata). So .rodata comes last, and we must + * account for all preceding segments. + * All SIZEOF() references are backward (sections defined above). + */ + .rodata_dummy (NOLOAD): ALIGN(4) + { + . += 0x18; /* image header (24 bytes) */ + . += (SIZEOF(.data) > 0) ? (SIZEOF(.data) + 0x8) : 0; /* data segment + header (if non-empty) */ + . += SIZEOF(.iram) + 0x8; /* iram segment + header */ + . += SIZEOF(.text) + 0x8; /* text segment + header */ + . += 0x8; /* rodata segment header */ + } > DROM + + /* Constant global variables, stored in DROM. */ + .rodata : ALIGN(4) + { + *(.rodata*) + . = ALIGN (4); + } >DROM + + /DISCARD/ : + { + *(.eh_frame) /* we don't do exception handling in C */ + } + + /* For the garbage collector. */ + .tinygo_stacksizes (INFO) : + { + *(.tinygo_stacksizes) + } + .tinygo_arm_entries (INFO) : + { + *(.tinygo_arm_entries) + } +} + +/* For the GC and scheduler. */ +_globals_start = _sbss; +_globals_end = _edata; + +/* Default stack size. */ +_stack_size = 4K; + +/* ROM function addresses for ESP32-C6. + * Source: ESP-IDF components/esp_rom/esp32c6/ld/esp32c6.rom.ld + */ +PROVIDE( ets_delay_us = 0x40000040 ); + +/* Cache ROM functions */ +Cache_Get_ICache_Line_Size = 0x40000628; +Cache_Get_Mode = 0x4000062c; +Cache_Address_Through_Cache = 0x40000630; +ROM_Boot_Cache_Init = 0x40000634; +MMU_Set_Page_Mode = 0x40000638; +MMU_Get_Page_Mode = 0x4000063c; +Cache_Invalidate_ICache_Items = 0x40000640; +Cache_Op_Addr = 0x40000644; +Cache_Invalidate_Addr = 0x40000648; +Cache_Invalidate_ICache_All = 0x4000064c; +Cache_Mask_All = 0x40000650; +Cache_UnMask_Dram0 = 0x40000654; +Cache_Suspend_ICache_Autoload = 0x40000658; +Cache_Resume_ICache_Autoload = 0x4000065c; +Cache_Start_ICache_Preload = 0x40000660; +Cache_ICache_Preload_Done = 0x40000664; +Cache_End_ICache_Preload = 0x40000668; +Cache_Config_ICache_Autoload = 0x4000066c; +Cache_Enable_ICache_Autoload = 0x40000670; +Cache_Disable_ICache_Autoload = 0x40000674; +Cache_Enable_ICache_PreLock = 0x40000678; +Cache_Disable_ICache_PreLock = 0x4000067c; +Cache_Lock_ICache_Items = 0x40000680; +Cache_Unlock_ICache_Items = 0x40000684; +Cache_Lock_Addr = 0x40000688; +Cache_Unlock_Addr = 0x4000068c; +Cache_Disable_ICache = 0x40000690; +Cache_Enable_ICache = 0x40000694; +Cache_Suspend_ICache = 0x40000698; +Cache_Resume_ICache = 0x4000069c; +Cache_Freeze_ICache_Enable = 0x400006a0; +Cache_Freeze_ICache_Disable = 0x400006a4; +Cache_Set_IDROM_MMU_Size = 0x400006a8; +Cache_Get_IROM_MMU_End = 0x400006ac; +Cache_Get_DROM_MMU_End = 0x400006b0; +Cache_MMU_Init = 0x400006b4; +Cache_MSPI_MMU_Set = 0x400006b8; +Cache_Travel_Tag_Memory = 0x400006bc; +Cache_Get_Virtual_Addr = 0x400006c0; + +/* Coexist ROM functions + * Source: esp32c6.rom.coexist.ld + */ +esp_coex_rom_version_get = 0x40000afc; +coex_bt_release = 0x40000b00; +coex_bt_request = 0x40000b04; +coex_core_ble_conn_dyn_prio_get = 0x40000b08; +coex_core_pti_get = 0x40000b10; +coex_core_release = 0x40000b14; +coex_core_request = 0x40000b18; +coex_core_status_get = 0x40000b1c; +coex_event_duration_get = 0x40000b24; +coex_hw_timer_disable = 0x40000b28; +coex_hw_timer_enable = 0x40000b2c; +coex_hw_timer_set = 0x40000b30; +coex_schm_interval_set = 0x40000b34; +coex_schm_lock = 0x40000b38; +coex_schm_unlock = 0x40000b3c; +coex_wifi_release = 0x40000b44; +esp_coex_ble_conn_dynamic_prio_get = 0x40000b48; +/* Coexist ROM data */ +coex_env_ptr = 0x4087ffc4; +coex_pti_tab_ptr = 0x4087ffc0; +coex_schm_env_ptr = 0x4087ffbc; +coexist_funcs = 0x4087ffb8; +g_coa_funcs_p = 0x4087ffb4; +g_coex_param_ptr = 0x4087ffb0; + +/* net80211 ROM functions + * Source: esp32c6.rom.net80211.ld + */ +esp_net80211_rom_version_get = 0x40000b4c; +ampdu_dispatch = 0x40000b50; +ampdu_dispatch_all = 0x40000b54; +ampdu_dispatch_as_many_as_possible = 0x40000b58; +ampdu_dispatch_movement = 0x40000b5c; +ampdu_dispatch_upto = 0x40000b60; +chm_is_at_home_channel = 0x40000b64; +cnx_node_is_existing = 0x40000b68; +cnx_node_search = 0x40000b6c; +ic_ebuf_recycle_rx = 0x40000b70; +ic_ebuf_recycle_tx = 0x40000b74; +ic_reset_rx_ba = 0x40000b78; +ieee80211_align_eb = 0x40000b7c; +ieee80211_ampdu_start_age_timer = 0x40000b84; +ieee80211_is_tx_allowed = 0x40000b8c; +ieee80211_output_pending_eb = 0x40000b90; +wifi_get_macaddr = 0x40000ba0; +wifi_rf_phy_disable = 0x40000ba4; +wifi_rf_phy_enable = 0x40000ba8; +ic_ebuf_alloc = 0x40000bac; +ieee80211_copy_eb_header = 0x40000bb4; +ieee80211_recycle_cache_eb = 0x40000bb8; +ieee80211_search_node = 0x40000bbc; +ieee80211_crypto_encap = 0x40000bc0; +ieee80211_decap = 0x40000bc8; +wifi_is_started = 0x40000bcc; +ieee80211_gettid = 0x40000bd0; +/* net80211 ROM data */ +net80211_funcs = 0x4087ffac; +g_scan = 0x4087ffa8; +g_chm = 0x4087ffa4; +g_ic_ptr = 0x4087ffa0; +g_hmac_cnt_ptr = 0x4087ff9c; +g_tx_cacheq_ptr = 0x4087ff98; +s_netstack_free = 0x4087ff94; +mesh_rxcb = 0x4087ff90; +sta_rxcb = 0x4087ff8c; +g_itwt_fid = 0x4087ff88; + +/* pp ROM functions + * Source: esp32c6.rom.pp.ld + */ +esp_pp_rom_version_get = 0x40000bd8; +ppCalTxopRTSThreshold = 0x40000bdc; +RC_GetBlockAckTime = 0x40000be0; +ebuf_list_remove = 0x40000be4; +GetAccess = 0x40000bf4; +hal_mac_is_low_rate_enabled = 0x40000bf8; +hal_mac_tx_get_blockack = 0x40000bfc; +ic_get_trc = 0x40000c04; +ic_interface_enabled = 0x40000c10; +is_lmac_idle = 0x40000c14; +lmacDiscardAgedMSDU = 0x40000c1c; +lmacIsIdle = 0x40000c28; +lmacIsLongFrame = 0x40000c2c; +lmacPostTxComplete = 0x40000c34; +lmacProcessAllTxTimeout = 0x40000c38; +lmacProcessCollisions = 0x40000c3c; +lmacReachLongLimit = 0x40000c44; +lmacReachShortLimit = 0x40000c48; +lmacRecycleMPDU = 0x40000c4c; +lmacRxDone = 0x40000c50; +mac_tx_set_duration = 0x40000c60; +mac_tx_set_plcp2 = 0x40000c6c; +pm_disable_sleep_delay_timer = 0x40000c78; +pm_mac_wakeup = 0x40000c80; +pm_mac_sleep = 0x40000c84; +pm_enable_sleep_delay_timer = 0x40000c8c; +pm_local_tsf_process = 0x40000c90; +pm_is_waked = 0x40000c9c; +pm_on_data_rx = 0x40000ca8; +pm_sleep_for = 0x40000cc4; +ppAMPDU2Normal = 0x40000ccc; +ppCalFrameTimes = 0x40000cd4; +ppCalSubFrameLength = 0x40000cd8; +ppCheckTxAMPDUlength = 0x40000ce0; +ppDequeueRxq_Locked = 0x40000ce4; +ppDequeueTxQ = 0x40000ce8; +ppEmptyDelimiterLength = 0x40000cec; +ppEnqueueRxq = 0x40000cf0; +ppEnqueueTxDone = 0x40000cf4; +ppGetTxframe = 0x40000cf8; +ppProcessRxPktHdr = 0x40000d04; +ppRecordBarRRC = 0x40000d0c; +ppRecycleAmpdu = 0x40000d10; +ppRecycleRxPkt = 0x40000d14; +ppResumeTxAMPDU = 0x40000d1c; +ppSearchTxQueue = 0x40000d2c; +ppSearchTxframe = 0x40000d30; +ppSelectNextQueue = 0x40000d34; +ppSubFromAMPDU = 0x40000d38; +ppTxProtoProc = 0x40000d44; +ppTxqUpdateBitmap = 0x40000d48; +pp_hdrsize = 0x40000d50; +pp_post = 0x40000d54; +pp_process_hmac_waiting_txq = 0x40000d58; +rcGetAmpduSched = 0x40000d5c; +rcUpdateRxDone = 0x40000d60; +rc_get_trc = 0x40000d64; +rc_get_trc_by_index = 0x40000d68; +rcAmpduLowerRate = 0x40000d6c; +rcampduuprate = 0x40000d70; +rcClearCurAMPDUSched = 0x40000d74; +rcClearCurSched = 0x40000d78; +rcClearCurStat = 0x40000d7c; +rcLowerSched = 0x40000d84; +rcSetTxAmpduLimit = 0x40000d88; +rcTxUpdatePer = 0x40000d8c; +rcUpdateAckSnr = 0x40000d90; +rcUpSched = 0x40000da0; +rssi_margin = 0x40000da4; +rx11NRate2AMPDULimit = 0x40000da8; +TRC_AMPDU_PER_DOWN_THRESHOLD = 0x40000dac; +TRC_AMPDU_PER_UP_THRESHOLD = 0x40000db0; +trc_calc_duration = 0x40000db4; +trc_isTxAmpduOperational = 0x40000db8; +trc_onAmpduOp = 0x40000dbc; +TRC_PER_IS_GOOD = 0x40000dc0; +trc_SetTxAmpduState = 0x40000dc4; +trc_tid_isTxAmpduOperational = 0x40000dc8; +trcAmpduSetState = 0x40000dcc; +wDev_DiscardFrame = 0x40000dd8; +wDev_GetNoiseFloor = 0x40000ddc; +wDev_IndicateAmpdu = 0x40000de0; +wdev_mac_reg_load = 0x40000de8; +wdev_mac_reg_store = 0x40000dec; +wdev_mac_special_reg_load = 0x40000df0; +wdev_mac_special_reg_store = 0x40000df4; +wdev_mac_wakeup = 0x40000df8; +wdev_mac_sleep = 0x40000dfc; +hal_mac_is_dma_enable = 0x40000e00; +wdev_csi_len_align = 0x40000e10; +ppDequeueTxDone_Locked = 0x40000e14; +config_is_cache_tx_buf_enabled = 0x40000e20; +ppProcessWaitingQueue = 0x40000e28; +ppDisableQueue = 0x40000e2c; +pm_allow_tx = 0x40000e30; +ppProcTxCallback = 0x40000e38; +ppCalPreFecPaddingFactor = 0x40000e40; +hal_get_tsf_timer = 0x40000e4c; +ppTxPktForceWaked = 0x40000e50; +lmacProcessLongFrameSuccess = 0x40000e54; +lmacProcessShortFrameSuccess = 0x40000e58; +lmacProcessTBSuccess = 0x40000e60; +lmacProcessAckTimeout = 0x40000e68; +get_estimated_batime = 0x40000e74; +is_use_muedca = 0x40000e78; +hal_mac_clr_txq_state = 0x40000e84; +hal_mac_get_txq_complete = 0x40000e88; +ht_get_min_subframe_len = 0x40000e8c; +rx11ACRate2AMPDULimit = 0x40000e90; +pwr_hal_clear_intr_status = 0x40000e94; +pwr_hal_clear_mac_modem_beacon_miss_intr_filter = 0x40000e98; +pwr_hal_clear_mac_modem_rx_beacon_info = 0x40000e9c; +pwr_hal_clear_mac_modem_rx_beacon_miss_counter = 0x40000ea0; +pwr_hal_clear_mac_modem_rx_beacon_sleep_counter = 0x40000ea4; +pwr_hal_clear_mac_modem_state_wakeup_protect_signal = 0x40000ea8; +pwr_hal_get_intr_raw_signal = 0x40000eac; +pwr_hal_get_intr_status = 0x40000eb0; +pwr_hal_get_mac_modem_beacon_miss_limit_exceeded_status = 0x40000eb4; +pwr_hal_get_mac_modem_rx_beacon_location_state = 0x40000eb8; +pwr_hal_get_mac_modem_rx_beacon_valid_state = 0x40000ebc; +pwr_hal_get_mac_modem_state_sleep_limit_exceeded_status = 0x40000ec0; +pwr_hal_set_beacon_filter_abort_disable = 0x40000ec4; +pwr_hal_set_beacon_filter_abort_enable = 0x40000ec8; +pwr_hal_set_beacon_filter_abort_length = 0x40000ecc; +pwr_hal_set_beacon_filter_disable = 0x40000ed8; +pwr_hal_set_beacon_filter_enable = 0x40000edc; +pwr_hal_set_beacon_filter_force_dump_disable = 0x40000ee0; +pwr_hal_set_beacon_filter_force_dump_enable = 0x40000ee4; +pwr_hal_set_beacon_filter_force_dump_limit = 0x40000ee8; +pwr_hal_set_beacon_filter_force_sync_disable = 0x40000eec; +pwr_hal_set_beacon_filter_force_sync_enable = 0x40000ef0; +pwr_hal_set_beacon_filter_force_sync_limit = 0x40000ef4; +pwr_hal_set_beacon_filter_frame_crc_state = 0x40000ef8; +pwr_hal_set_beacon_filter_soc_wakeup_and_intr_disable = 0x40000efc; +pwr_hal_set_beacon_filter_soc_wakeup_and_intr_enable = 0x40000f00; +pwr_hal_set_beacon_filter_unicast_wakeup_disable = 0x40000f04; +pwr_hal_set_beacon_filter_unicast_wakeup_enable = 0x40000f08; +pwr_hal_set_lpclk_cycle_time = 0x40000f0c; +pwr_hal_set_lpclk_sync_disable = 0x40000f10; +pwr_hal_set_lpclk_sync_enable = 0x40000f14; +pwr_hal_set_mac_modem_beacon_miss_intr_disable = 0x40000f18; +pwr_hal_set_mac_modem_beacon_miss_intr_enable = 0x40000f1c; +pwr_hal_set_mac_modem_beacon_miss_limit = 0x40000f20; +pwr_hal_set_mac_modem_beacon_miss_limit_exceeded_wakeup_disable = 0x40000f24; +pwr_hal_set_mac_modem_beacon_miss_limit_exceeded_wakeup_enable = 0x40000f28; +pwr_hal_set_mac_modem_beacon_miss_timeout = 0x40000f2c; +pwr_hal_set_mac_modem_state_sleep_limit = 0x40000f30; +pwr_hal_set_mac_modem_state_sleep_limit_exceeded_wakeup_disable = 0x40000f34; +pwr_hal_set_mac_modem_state_sleep_limit_exceeded_wakeup_enable = 0x40000f38; +pwr_hal_set_mac_modem_state_wakeup_protect_disable = 0x40000f3c; +pwr_hal_set_mac_modem_state_wakeup_protect_early_time = 0x40000f40; +pwr_hal_set_mac_modem_state_wakeup_protect_enable = 0x40000f44; +pwr_hal_set_mac_modem_tbtt_auto_period_disable = 0x40000f48; +pwr_hal_set_mac_modem_tbtt_auto_period_enable = 0x40000f4c; +pwr_hal_set_mac_modem_tbtt_auto_period_interval = 0x40000f50; +pwr_hal_set_modem_state_interface = 0x40000f54; +hal_tsf_clear_soc_wakeup_request = 0x40000f58; +tsf_hal_clear_mac_modem_rf_power_state = 0x40000f5c; +tsf_hal_clear_soc_wakeup_request = 0x40000f60; +tsf_hal_get_counter_value = 0x40000f64; +tsf_hal_get_mac_modem_rf_power_state = 0x40000f68; +tsf_hal_get_tbtt_interval = 0x40000f6c; +tsf_hal_get_time = 0x40000f70; +tsf_hal_get_timer_target = 0x40000f74; +tsf_hal_is_tsf_enabled = 0x40000f78; +tsf_hal_map_tbtt_target_to_rx_frame = 0x40000f7c; +tsf_hal_map_tsf_to_bssid = 0x40000f80; +tsf_hal_set_counter_value = 0x40000f84; +tsf_hal_set_modem_wakeup_early_time = 0x40000f88; +tsf_hal_set_rx_beacon_abort_tsf_time_deviation_sync_disable = 0x40000f8c; +tsf_hal_set_rx_beacon_abort_tsf_time_deviation_sync_enable = 0x40000f90; +tsf_hal_set_rx_beacon_fail_tsf_time_deviation_sync_disable = 0x40000f94; +tsf_hal_set_rx_beacon_fail_tsf_time_deviation_sync_enable = 0x40000f98; +tsf_hal_set_rx_beacon_success_tsf_time_deviation_sync_disable = 0x40000f9c; +tsf_hal_set_rx_beacon_success_tsf_time_deviation_sync_enable = 0x40000fa0; +tsf_hal_set_tbtt_disable = 0x40000fa4; +tsf_hal_set_tbtt_early_time = 0x40000fa8; +tsf_hal_set_tbtt_enable = 0x40000fac; +tsf_hal_set_tbtt_interval = 0x40000fb0; +tsf_hal_set_tbtt_intr_disable = 0x40000fb4; +tsf_hal_set_tbtt_intr_enable = 0x40000fb8; +tsf_hal_set_tbtt_modem_wakeup_disable = 0x40000fbc; +tsf_hal_set_tbtt_modem_wakeup_enable = 0x40000fc0; +tsf_hal_set_tbtt_rf_ctrl_disable = 0x40000fc4; +tsf_hal_set_tbtt_rf_ctrl_enable = 0x40000fc8; +tsf_hal_set_tbtt_rf_ctrl_wait_cycles = 0x40000fcc; +tsf_hal_set_tbtt_soc_wakeup_disable = 0x40000fd0; +tsf_hal_set_tbtt_soc_wakeup_enable = 0x40000fd4; +tsf_hal_set_time = 0x40000fdc; +tsf_hal_set_timer_disable = 0x40000fe0; +tsf_hal_set_timer_enable = 0x40000fe4; +tsf_hal_set_timer_intr_disable = 0x40000fe8; +tsf_hal_set_timer_intr_enable = 0x40000fec; +tsf_hal_set_timer_modem_wakeup_disable = 0x40000ff0; +tsf_hal_set_timer_modem_wakeup_enable = 0x40000ff4; +tsf_hal_set_timer_rf_ctrl_disable = 0x40000ff8; +tsf_hal_set_timer_rf_ctrl_enable = 0x40000ffc; +tsf_hal_set_timer_rf_ctrl_wait_cycles = 0x40001000; +tsf_hal_set_timer_soc_wakeup_disable = 0x40001004; +tsf_hal_set_timer_soc_wakeup_enable = 0x40001008; +tsf_hal_set_timer_target = 0x4000100c; +tsf_hal_set_tsf_disable = 0x40001010; +tsf_hal_set_tsf_enable = 0x40001014; +tsf_hal_set_tsf_time_deviation = 0x40001018; +tsf_hal_set_tsf_time_deviation_sync_disable = 0x4000101c; +tsf_hal_set_tsf_time_deviation_sync_enable = 0x40001020; +tsf_hal_unmap_tbtt_target_to_rx_frame = 0x40001024; +rcGetRate = 0x4000103c; +rcGetDCMMaxRate = 0x40001040; +ppDirectRecycleAmpdu = 0x40001048; +ppAdd2AMPDUTail = 0x4000105c; +esp_test_disable_tx_statistics = 0x40001060; +esp_test_enable_tx_statistics = 0x40001064; +esp_test_clr_tx_statistics = 0x40001068; +esp_test_get_tx_statistics = 0x4000106c; +esp_test_clr_tx_tb_statistics = 0x40001070; +esp_test_get_tx_tb_statistics = 0x40001074; +test_tx_fail_statistics = 0x40001078; +esp_test_tx_enab_statistics = 0x40001088; +esp_test_tx_tb_complete = 0x4000108c; +esp_test_tx_count_retry = 0x40001090; +esp_test_tx_count_collision = 0x40001094; +esp_test_tx_count_timeout = 0x40001098; +hal_enable_tx_statistics = 0x4000109c; +test_rx_process_complete_noeb = 0x400010a0; +test_rx_process_complete_retry = 0x400010a4; +esp_test_rx_process_complete = 0x400010a8; +esp_test_clr_rx_statistics = 0x400010ac; +esp_test_get_rx_statistics = 0x400010b0; +test_free_rx_statistics = 0x400010b4; +esp_test_set_rx_error_occurs = 0x400010b8; +esp_test_get_rx_error_occurs = 0x400010bc; +esp_test_clr_rx_error_occurs = 0x400010c0; +esp_test_disable_rx_statistics = 0x400010c4; +esp_test_enable_rx_statistics = 0x400010c8; +hal_enable_rx_statistics = 0x400010cc; +get_user_num = 0x400010d0; +mumimo_spatial_cfg_get_nsts = 0x400010d4; +mumimo_spatial_cfg_get_nsts_tot = 0x400010d8; +test_mumimo_get_heltf_num = 0x400010dc; +test_mimo_update_user_info = 0x400010e0; +test_parse_rx_mu_mimo = 0x400010e4; +test_nonmimo_update_user_info = 0x400010e8; +test_parse_rx_mu_nonmimo = 0x400010ec; +esp_test_rx_parse_mu = 0x400010f0; +esp_test_get_rx_mu_statistics = 0x400010f4; +esp_test_clr_rx_mu_statistics = 0x400010f8; +esp_test_enable_rx_mu_statistics = 0x400010fc; +esp_test_disable_rx_mu_statistics = 0x40001100; +/* pp ROM data */ +our_instances_ptr = 0x4004ffe0; +pTxRx = 0x4087ff80; +lmacConfMib_ptr = 0x4087ff7c; +our_wait_eb = 0x4087ff78; +our_tx_eb = 0x4087ff74; +pp_wdev_funcs = 0x4087ff70; +g_osi_funcs_p = 0x4087ff6c; +wDevCtrl_ptr = 0x4087ff68; +g_wdev_last_desc_reset_ptr = 0x4004ffdc; +wDevMacSleep_ptr = 0x4087ff64; +g_lmac_cnt_ptr = 0x4087ff60; +our_controls_ptr = 0x4004ffd8; +pp_sig_cnt_ptr = 0x4087ff5c; +g_eb_list_desc_ptr = 0x4087ff58; +s_fragment_ptr = 0x4087ff54; +if_ctrl_ptr = 0x4087ff50; +g_intr_lock_mux = 0x4087ff4c; +g_wifi_global_lock = 0x4087ff48; +s_wifi_queue = 0x4087ff44; +pp_task_hdl = 0x4087ff40; +s_pp_task_create_sem = 0x4087ff3c; +s_pp_task_del_sem = 0x4087ff38; +g_wifi_menuconfig_ptr = 0x4087ff34; +xphyQueue = 0x4087ff30; +ap_no_lr_ptr = 0x4087ff2c; +rc11BSchedTbl_ptr = 0x4087ff28; +rc11NSchedTbl_ptr = 0x4087ff24; +rcLoRaSchedTbl_ptr = 0x4087ff20; +BasicOFDMSched_ptr = 0x4087ff1c; +trc_ctl_ptr = 0x4087ff18; +g_pm_cnt_ptr = 0x4087ff14; +g_pm_ptr = 0x4087ff10; +g_pm_cfg_ptr = 0x4087ff0c; +g_esp_mesh_quick_funcs_ptr = 0x4087ff08; +g_txop_queue_status_ptr = 0x4087ff04; +g_mac_sleep_en_ptr = 0x4087ff00; +g_mesh_is_root_ptr = 0x4087fefc; +g_mesh_topology_ptr = 0x4087fef8; +g_mesh_init_ps_type_ptr = 0x4087fef4; +g_mesh_is_started_ptr = 0x4087fef0; +g_config_func = 0x4087feec; +g_net80211_tx_func = 0x4087fee8; +g_timer_func = 0x4087fee4; +s_michael_mic_failure_cb = 0x4087fee0; +wifi_sta_rx_probe_req = 0x4087fedc; +g_tx_done_cb_func = 0x4087fed8; +g_per_conn_trc = 0x4087fe8c; +s_encap_amsdu_func = 0x4087fe88; +s_ht_ampdu_density_us = 0x4087fd02; +s_ht_ampdu_density = 0x4087fd01; +s_running_phy_type = 0x4087fd00; +complete_ena_tb_seqno = 0x4087fe4c; +complete_ena_tb_final = 0x4087fe48; +complete_ena_tb_count = 0x4087fe44; +g_dbg_interp_tsf = 0x4087fe3c; +g_dbg_interp_tsf_end = 0x4087fe38; +s_he_min_len_bytes = 0x4087fdf0; +s_he_dcm_min_len_bytes = 0x4087fdd0; +esp_wifi_cert_tx_mcs = 0x4087fcfc; +esp_wifi_cert_tx_bcc = 0x4087fcf8; +esp_wifi_cert_tx_nss = 0x4087fcec; +esp_test_tx_statistics_aci_bitmap = 0x4087fda4; +esp_test_tx_statistics = 0x4087fd94; +esp_test_tx_tb_statistics = 0x4087fd84; +esp_test_tx_fail_statistics = 0x4087fd24; +esp_test_rx_statistics = 0x4087fd1c; +esp_test_rx_mu_statistics = 0x4087fd18; +esp_test_mu_print_ru_allocation = 0x4087fd14; +sigb_ru_allocation_user_num = 0x4004ffc8; +sigb_common_ru_allocation = 0x4004ff38; +mu_mimo_special_cfg_user_num_2 = 0x4004fee8; +mu_mimo_special_cfg_user_num_3 = 0x4004fe80; +mu_mimo_special_cfg_user_num_4 = 0x4004fe28; +mu_mimo_special_cfg_user_num_5 = 0x4004fdf0; +mu_mimo_special_cfg_user_num_6 = 0x4004fdd0; +mu_mimo_special_cfg_user_num_7 = 0x4004fdc0; +mu_mimo_special_cfg_user_num_8 = 0x4004fdb8; +esp_test_rx_error_occurs = 0x4087fd10; +he_max_apep_length = 0x4004fd40; + +/* PHY ROM functions + * Source: esp32c6.rom.phy.ld + */ +phy_param_addr = 0x40001104; +phy_get_romfuncs = 0x40001108; +chip761_phyrom_version = 0x4000110c; +chip761_phyrom_version_num = 0x40001110; +get_rc_dout = 0x40001114; +rc_cal = 0x40001118; +rom_enter_critical_phy = 0x4000111c; +rom_exit_critical_phy = 0x40001120; +rom_set_chan_cal_interp = 0x40001124; +rom_loopback_mode_en = 0x40001128; +rom_bb_bss_cbw40 = 0x4000112c; +abs_temp = 0x40001130; +get_data_sat = 0x40001134; +phy_byte_to_word = 0x40001138; +set_chan_reg = 0x4000113c; +i2c_master_reset = 0x40001140; +rom_set_chan_freq_sw_start = 0x40001144; +freq_module_resetn = 0x40001148; +freq_chan_en_sw = 0x4000114c; +write_chan_freq = 0x40001150; +get_freq_mem_param = 0x40001154; +get_freq_mem_addr = 0x40001158; +bt_txpwr_freq = 0x4000115c; +wr_rf_freq_mem = 0x40001160; +read_rf_freq_mem = 0x40001164; +freq_i2c_mem_write = 0x40001168; +freq_num_get_data = 0x4000116c; +freq_i2c_num_addr = 0x40001170; +freq_i2c_write_set = 0x40001174; +pll_dac_mem_update = 0x40001178; +pll_cap_mem_update = 0x4000117c; +get_rf_freq_cap = 0x40001180; +get_rf_freq_init = 0x40001184; +phy_en_hw_set_freq = 0x40001188; +phy_dis_hw_set_freq = 0x4000118c; +rom_pwdet_sar2_init = 0x40001190; +rom_en_pwdet = 0x40001194; +rom_get_sar_sig_ref = 0x40001198; +rom_pwdet_tone_start = 0x4000119c; +rom_pwdet_wait_idle = 0x400011a0; +rom_read_sar_dout = 0x400011a4; +get_tone_sar_dout = 0x400011a8; +get_fm_sar_dout = 0x400011ac; +txtone_linear_pwr = 0x400011b0; +linear_to_db = 0x400011b4; +get_power_db = 0x400011b8; +meas_tone_pwr_db = 0x400011bc; +pkdet_vol_start = 0x400011c0; +read_sar2_code = 0x400011c4; +get_sar2_vol = 0x400011c8; +get_pll_vol = 0x400011cc; +tx_pwctrl_bg_init = 0x400011d0; +phy_pwdet_always_en = 0x400011d4; +phy_pwdet_onetime_en = 0x400011d8; +esp_tx_state_out_rom = 0x400011dc; +ant_dft_cfg_rom = 0x400011e0; +ant_wifitx_cfg_rom = 0x400011e4; +ant_wifirx_cfg_rom = 0x400011e8; +ant_bttx_cfg_rom = 0x400011ec; +ant_btrx_cfg_rom = 0x400011f0; +phy_chan_dump_cfg_rom = 0x400011f4; +phy_enable_low_rate = 0x400011f8; +phy_disable_low_rate = 0x400011fc; +phy_is_low_rate_enabled = 0x40001200; +phy_dig_reg_backup_rom = 0x40001204; +phy_chan_filt_set_rom = 0x40001208; +phy_rx11blr_cfg = 0x4000120c; +set_cca_rom = 0x40001210; +set_rx_sense_rom = 0x40001214; +rx_gain_force_rom = 0x40001218; +rom_rfpll_set_freq = 0x4000121c; +mhz2ieee = 0x40001220; +chan_to_freq = 0x40001224; +restart_cal = 0x40001228; +write_rfpll_sdm = 0x4000122c; +wait_rfpll_cal_end = 0x40001230; +set_rf_freq_offset = 0x40001234; +set_rfpll_freq = 0x40001238; +set_channel_rfpll_freq = 0x4000123c; +rfpll_cap_correct = 0x40001240; +rfpll_cap_init_cal = 0x40001244; +write_pll_cap = 0x40001248; +read_pll_cap = 0x4000124c; +chip_v7_set_chan_ana = 0x40001250; +freq_set_reg = 0x40001254; +gen_rx_gain_table = 0x40001258; +bt_txdc_cal = 0x4000125c; +bt_txiq_cal = 0x40001260; +txiq_cal_init = 0x40001264; +txdc_cal_init = 0x40001268; +txdc_cal = 0x4000126c; +txiq_get_mis_pwr = 0x40001270; +txiq_cover = 0x40001274; +rfcal_txiq = 0x40001278; +get_power_atten = 0x4000127c; +pwdet_ref_code = 0x40001280; +pwdet_code_cal = 0x40001284; +rfcal_txcap = 0x40001288; +tx_cap_init = 0x4000128c; +rfcal_pwrctrl = 0x40001290; +tx_pwctrl_init_cal = 0x40001294; +tx_pwctrl_init = 0x40001298; +bt_tx_pwctrl_init = 0x4000129c; +rom_i2c_enter_critical = 0x400012a0; +rom_i2c_exit_critical = 0x400012a4; +rom_get_i2c_read_mask = 0x400012a8; +rom_get_i2c_mst0_mask = 0x400012ac; +rom_get_i2c_hostid = 0x400012b0; +rom_chip_i2c_readReg_org = 0x400012b4; +rom_chip_i2c_readReg = 0x400012b8; +rom_chip_i2c_writeReg = 0x400012c0; +rom_set_txcap_reg = 0x400012d0; +i2c_paral_set_mst0 = 0x400012d4; +i2c_paral_set_read = 0x400012d8; +i2c_paral_read = 0x400012dc; +i2c_paral_write = 0x400012e0; +i2c_paral_write_num = 0x400012e4; +i2c_paral_write_mask = 0x400012e8; +i2c_sar2_init_code = 0x400012ec; +rom_pbus_force_mode = 0x400012f0; +rom_pbus_rd_addr = 0x400012f4; +rom_pbus_rd_shift = 0x400012f8; +rom_pbus_force_test = 0x400012fc; +rom_pbus_rd = 0x40001300; +rom_pbus_set_rxgain = 0x40001304; +rom_pbus_xpd_rx_off = 0x40001308; +rom_pbus_xpd_rx_on = 0x4000130c; +rom_pbus_xpd_tx_off = 0x40001310; +rom_pbus_xpd_tx_on = 0x40001314; +rom_set_loopback_gain = 0x40001318; +rom_txcal_debuge_mode = 0x4000131c; +pbus_debugmode = 0x40001320; +pbus_workmode = 0x40001324; +pbus_set_dco = 0x40001328; +txcal_work_mode = 0x4000132c; +rom_start_tx_tone_step = 0x40001330; +rom_stop_tx_tone = 0x40001334; +disable_agc = 0x40001338; +enable_agc = 0x4000133c; +phy_disable_cca = 0x40001340; +phy_enable_cca = 0x40001344; +write_gain_mem = 0x40001348; +bb_bss_cbw40_dig = 0x4000134c; +cbw2040_cfg = 0x40001350; +mac_tx_chan_offset = 0x40001354; +tx_paon_set = 0x40001358; +pwdet_reg_init = 0x4000135c; +i2cmst_reg_init = 0x40001360; +bt_gain_offset = 0x40001364; +fe_reg_init = 0x40001368; +mac_enable_bb = 0x4000136c; +bb_wdg_cfg = 0x40001370; +fe_txrx_reset = 0x40001374; +set_rx_comp = 0x40001378; +agc_reg_init = 0x4000137c; +bb_reg_init = 0x40001380; +open_i2c_xpd = 0x40001384; +txiq_set_reg = 0x40001388; +rxiq_set_reg = 0x4000138c; +set_txclk_en = 0x40001390; +set_rxclk_en = 0x40001394; +bb_wdg_test_en = 0x40001398; +noise_floor_auto_set = 0x4000139c; +read_hw_noisefloor = 0x400013a0; +iq_corr_enable = 0x400013a4; +wifi_agc_sat_gain = 0x400013a8; +phy_bbpll_cal = 0x400013ac; +phy_ant_init = 0x400013b0; +phy_set_bbfreq_init = 0x400013b4; +wifi_fbw_sel = 0x400013b8; +bt_filter_reg = 0x400013bc; +phy_rx_sense_set = 0x400013c0; +tx_state_set = 0x400013c4; +phy_close_pa = 0x400013c8; +phy_freq_correct = 0x400013cc; +set_pbus_reg = 0x400013d0; +wifi_rifs_mode_en = 0x400013d4; +nrx_freq_set = 0x400013d8; +fe_adc_on = 0x400013dc; +phy_force_pwr_index = 0x400013e0; +rom_iq_est_enable = 0x400013e4; +rom_iq_est_disable = 0x400013e8; +rom_bb_gain_index = 0x400013ec; +rom_rfrx_gain_index = 0x400013f0; +dc_iq_est = 0x400013f4; +set_cal_rxdc = 0x400013f8; +rxiq_get_mis = 0x400013fc; +rxiq_cover_mg_mp = 0x40001400; +rfcal_rxiq = 0x40001404; +get_rfcal_rxiq_data = 0x40001408; +get_dco_comp = 0x4000140c; +pbus_rx_dco_cal = 0x40001410; +rxdc_est_min = 0x40001414; +pbus_rx_dco_cal_1step = 0x40001418; +set_lb_txiq = 0x4000141c; +set_rx_gain_cal_iq = 0x40001420; +set_rx_gain_cal_dc = 0x40001424; +spur_reg_write_one_tone = 0x40001428; +spur_cal = 0x4000142c; +spur_coef_cfg = 0x40001430; +tsens_power_up = 0x40001434; +tsens_read_init = 0x40001438; +code_to_temp = 0x4000143c; +tsens_index_to_dac = 0x40001440; +tsens_index_to_offset = 0x40001444; +tsens_dac_cal = 0x40001448; +tsens_code_read = 0x4000144c; +tsens_temp_read = 0x40001450; +temp_to_power = 0x40001454; +get_temp_init = 0x40001458; +txbbgain_to_index = 0x4000145c; +index_to_txbbgain = 0x40001460; +bt_index_to_bb = 0x40001464; +bt_bb_to_index = 0x40001468; +bt_get_tx_gain = 0x4000146c; +dig_gain_check = 0x40001470; +wifi_get_tx_gain = 0x40001474; +wifi_11g_rate_chg = 0x40001478; +bt_chan_pwr_interp = 0x4000147c; +get_rate_fcc_index = 0x40001480; +get_chan_target_power = 0x40001484; +get_tx_gain_value = 0x40001488; +wifi_get_target_power = 0x4000148c; +/* PHY ROM data */ +phy_param_rom = 0x4087fce8; 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/swan.json b/targets/swan.json index fbb46a5569..92587690e8 100644 --- a/targets/swan.json +++ b/targets/swan.json @@ -1,6 +1,6 @@ { "inherits": ["cortex-m4"], - "build-tags": ["swan", "stm32l4r5", "stm32l4x5", "stm32l4", "stm32"], + "build-tags": ["swan", "stm32l4r5", "stm32l4y5", "stm32l4", "stm32"], "serial": "uart", "linkerscript": "targets/stm32l4x5.ld", "extra-files": [ diff --git a/targets/vicharak_shrike-lite.json b/targets/vicharak_shrike-lite.json new file mode 100644 index 0000000000..c801b2c27b --- /dev/null +++ b/targets/vicharak_shrike-lite.json @@ -0,0 +1,14 @@ +{ + "inherits": [ + "rp2040" + ], + "serial-port": ["2e8a:0003"], + "default-stack-size": 8192, + "build-tags": ["vicharak_shrike_lite"], + "ldflags": [ + "--defsym=__flash_size=4M" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} diff --git a/targets/xiao-rp2350.json b/targets/xiao-rp2350.json new file mode 100644 index 0000000000..dd65bbcbb9 --- /dev/null +++ b/targets/xiao-rp2350.json @@ -0,0 +1,8 @@ +{ + "inherits": [ + "rp2350" + ], + "serial-port": ["2e8a:000a"], + "build-tags": ["xiao_rp2350"], + "default-stack-size": 8192 +} diff --git a/testdata/corpus.yaml b/testdata/corpus.yaml index 0ed29adbe4..36ac337cf6 100644 --- a/testdata/corpus.yaml +++ b/testdata/corpus.yaml @@ -29,8 +29,7 @@ - repo: github.com/dgryski/go-camellia - repo: github.com/dgryski/go-change - repo: github.com/dgryski/go-chaskey - tags: appengine noasm - skipwasi: true # siphash has build tag issues + tags: appengine noasm # for dchest/siphash - repo: github.com/dgryski/go-clefia - repo: github.com/dgryski/go-clockpro - repo: github.com/dgryski/go-cobs @@ -56,7 +55,6 @@ - repo: github.com/dgryski/go-linlog - repo: github.com/dgryski/go-maglev tags: appengine # for dchest/siphash - skipwasi: true - repo: github.com/dgryski/go-marvin32 - repo: github.com/dgryski/go-md5crypt - repo: github.com/dgryski/go-metro @@ -66,7 +64,6 @@ tags: noasm - repo: github.com/dgryski/go-mpchash tags: appengine # for dchest/siphash - skipwasi: true - repo: github.com/dgryski/go-neeva - repo: github.com/dgryski/go-nibz - repo: github.com/dgryski/go-nibblesort @@ -289,3 +286,8 @@ - repo: github.com/philhofer/fwd - repo: github.com/blevesearch/sear - repo: github.com/steveyen/gtreap +- repo: github.com/orsinium-labs/tinymath +- repo: github.com/orsinium-labs/jsony +- repo: github.com/tidwall/gjson +- repo: github.com/dchest/siphash + tags: appengine diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 1a3d539621..ef9dedff97 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -10,6 +10,7 @@ import ( "os" "path/filepath" "regexp" + "slices" "sort" "strconv" "strings" @@ -18,6 +19,7 @@ import ( ) var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$") +var validDimableName = regexp.MustCompile(`^((%s)|(%s)[_A-Za-z]{1}[_A-Za-z0-9]*)|([_A-Za-z]{1}[_A-Za-z0-9]*(\[%s\])?)|([_A-Za-z]{1}[_A-Za-z0-9]*(%s)?[_A-Za-z0-9]*)$`) var enumBitSpecifier = regexp.MustCompile("^#x*[01]+[01x]*$") type SVDFile struct { @@ -60,22 +62,18 @@ type SVDRegister struct { } type SVDField struct { - Name string `xml:"name"` - Description string `xml:"description"` - Lsb *uint32 `xml:"lsb"` - Msb *uint32 `xml:"msb"` - BitOffset *uint32 `xml:"bitOffset"` - BitWidth *uint32 `xml:"bitWidth"` - BitRange *string `xml:"bitRange"` - EnumeratedValues struct { - DerivedFrom string `xml:"derivedFrom,attr"` - Name string `xml:"name"` - EnumeratedValue []struct { - Name string `xml:"name"` - Description string `xml:"description"` - Value string `xml:"value"` - } `xml:"enumeratedValue"` - } `xml:"enumeratedValues"` + DerivedFrom string `xml:"derivedFrom,attr"` + Name string `xml:"name"` + Description string `xml:"description"` + Dim *string `xml:"dim"` + DimIndex *string `xml:"dimIndex"` + DimIncrement string `xml:"dimIncrement"` + Lsb *uint32 `xml:"lsb"` + Msb *uint32 `xml:"msb"` + BitOffset *uint32 `xml:"bitOffset"` + BitWidth *uint32 `xml:"bitWidth"` + BitRange *string `xml:"bitRange"` + EnumeratedValues []SVDEnumeration `xml:"enumeratedValues"` } type SVDCluster struct { @@ -89,6 +87,17 @@ type SVDCluster struct { AddressOffset string `xml:"addressOffset"` } +type SVDEnumeration struct { + DerivedFrom string `xml:"derivedFrom,attr"` + Name string `xml:"name"` + EnumeratedValue []struct { + Name string `xml:"name"` + Description string `xml:"description"` + Value string `xml:"value"` + IsDefault bool `xml:"isDefault"` + } `xml:"enumeratedValue"` +} + type Device struct { Metadata *Metadata Interrupts []*Interrupt @@ -120,6 +129,7 @@ type Interrupt struct { type Peripheral struct { Name string + Alias string GroupName string BaseAddress uint64 Description string @@ -173,23 +183,36 @@ func splitLine(s string) []string { // Replace characters that are not allowed in a symbol name with a '_'. This is // useful to be able to process SVD files with errors. func cleanName(text string) string { - if !validName.MatchString(text) { - result := make([]rune, 0, len(text)) - for _, c := range text { + return cleanIdentifier(text, validName) +} + +func cleanDimableName(text string) string { + return cleanIdentifier(text, validDimableName) +} + +func cleanIdentifier(text string, valid *regexp.Regexp) string { + text = cleanString(text, valid) + if len(text) != 0 && (text[0] >= '0' && text[0] <= '9') { + // Identifiers may not start with a number. + // Add an underscore instead. + text = "_" + text + } + return text +} + +func cleanString(s string, valid *regexp.Regexp) string { + if !valid.MatchString(s) { + result := make([]rune, 0, len(s)) + for _, c := range s { if validName.MatchString(string(c)) { result = append(result, c) } else { result = append(result, '_') } } - text = string(result) + s = string(result) } - if len(text) != 0 && (text[0] >= '0' && text[0] <= '9') { - // Identifiers may not start with a number. - // Add an underscore instead. - text = "_" + text - } - return text + return s } func processSubCluster(p *Peripheral, cluster *SVDCluster, clusterOffset uint64, clusterName string, peripheralDict map[string]*Peripheral) []*Peripheral { @@ -353,6 +376,7 @@ func readSVD(path, sourceURL string) (*Device, error) { // comes later in the file. To make sure this works, sort the peripherals if // needed. orderedPeripherals := orderPeripherals(device.Peripherals) + globalDerivationCtx.peripherals = orderedPeripherals for _, periphEl := range orderedPeripherals { description := formatText(periphEl.Description) @@ -490,21 +514,42 @@ func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral { var sortedPeripherals []*SVDPeripheral var missingBasePeripherals []*SVDPeripheral knownBasePeripherals := map[string]struct{}{} - for i := range input { - p := &input[i] + + tryProcess := func(p *SVDPeripheral) { groupName := p.GroupName - if groupName == "" { - groupName = p.Name + if groupName != "" { + knownBasePeripherals[groupName] = struct{}{} } - knownBasePeripherals[groupName] = struct{}{} + knownBasePeripherals[p.Name] = struct{}{} if p.DerivedFrom != "" { if _, ok := knownBasePeripherals[p.DerivedFrom]; !ok { missingBasePeripherals = append(missingBasePeripherals, p) - continue + return } } sortedPeripherals = append(sortedPeripherals, p) } + for i := range input { + tryProcess(&input[i]) + } + orderPeripheralsByNumBitfields(sortedPeripherals) + + // missingBasePeripherals may still contain unordered entries; + // repeat the process until missingBasePeripheral does not change anymore. + prevNumPending := 0 + for { + pending := missingBasePeripherals + if len(pending) == prevNumPending { + break + } + // reuse the same slice as input and for keeping track of + // missing base periphal + missingBasePeripherals = missingBasePeripherals[:0] + for _, p := range pending { + tryProcess(p) + } + prevNumPending = len(pending) + } // Let's hope all base peripherals are now included. sortedPeripherals = append(sortedPeripherals, missingBasePeripherals...) @@ -512,6 +557,51 @@ func orderPeripherals(input []SVDPeripheral) []*SVDPeripheral { return sortedPeripherals } +func orderPeripheralsByNumBitfields(list []*SVDPeripheral) { + seenGroup := make(map[string]struct{}) + for i, p := range list { + groupName := p.GroupName + if groupName == "" || p.DerivedFrom != "" { + continue + } + if _, ok := seenGroup[groupName]; ok { + continue + } + iMax, nMax := -1, p.bitfieldCount() + for j, p2 := range list[i+1:] { + if p2.GroupName != groupName || p2.DerivedFrom != "" { + continue + } + if n2 := p2.bitfieldCount(); n2 > nMax { + iMax = i + 1 + j + nMax = n2 + } + } + if iMax != -1 { + pMax := list[iMax] + // swap peripherals + copy(list[i+1:iMax+1], list[i:iMax]) + list[i] = pMax + seenGroup[groupName] = struct{}{} + } + } +} + +func (p *SVDPeripheral) bitfieldCount() int { + n := 0 + for _, r := range p.Registers { + for _, f := range r.Fields { + dim := decodeDim(f.Dim) + if dim > 0 { + n += dim + } else { + n++ + } + } + } + return n +} + func addInterrupt(interrupts map[string]*Interrupt, name, interruptName string, index int, description string) { if _, ok := interrupts[name]; ok { if interrupts[name].Value != index { @@ -544,14 +634,23 @@ func addInterrupt(interrupts map[string]*Interrupt, name, interruptName string, func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPrefix string) ([]Constant, []Bitfield) { var fields []Constant var bitfields []Bitfield + var enumDefault enumDefaultResolver enumSeen := map[string]int64{} for _, fieldEl := range fieldEls { + + if fieldEl.DerivedFrom != "" { + err := globalDerivationCtx.deriveField(fieldEl, fieldEls) + if err != nil { + fmt.Fprintf(os.Stderr, "unable to derive field %q from %q: %v\n", fieldEl.Name, fieldEl.DerivedFrom, err.Error()) + } + } + // Some bitfields (like the STM32H7x7) contain invalid bitfield // names like "CNT[31]". Replace invalid characters with "_" when // needed. - fieldName := cleanName(fieldEl.Name) - if !unicode.IsUpper(rune(fieldName[0])) && !unicode.IsDigit(rune(fieldName[0])) { - fieldName = strings.ToUpper(fieldName) + fieldNameTpl := cleanDimableName(fieldEl.Name) + if !unicode.IsUpper(rune(fieldNameTpl[0])) && !unicode.IsDigit(rune(fieldNameTpl[0])) { + fieldNameTpl = strings.ReplaceAll(strings.ToUpper(fieldNameTpl), "%S", "%s") } // Find the lsb/msb that is encoded in various ways. @@ -581,129 +680,332 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre msb = uint32(m) } else { // this is an error. what to do? - fmt.Fprintln(os.Stderr, "unable to find lsb/msb in field:", fieldName) + fmt.Fprintln(os.Stderr, "unable to find lsb/msb in field:", fieldNameTpl) continue } - // The enumerated values can be the same as another field, so to avoid - // duplication SVD files can simply refer to another set of enumerated - // values in the same register. - // See: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_registers.html#elem_enumeratedValues - enumeratedValues := fieldEl.EnumeratedValues - if enumeratedValues.DerivedFrom != "" { - parts := strings.Split(enumeratedValues.DerivedFrom, ".") - if len(parts) == 1 { - found := false - for _, otherFieldEl := range fieldEls { - if otherFieldEl.EnumeratedValues.Name == parts[0] { - found = true - enumeratedValues = otherFieldEl.EnumeratedValues + da := decodeDimArray(fieldEl.Dim, fieldEl.DimIndex, fieldEl.DimIncrement, "field", fieldNameTpl) + da.rangeElems(func(ia int, _ uint32) bool { + if da != nil { + lsb += da.incr + msb += da.incr + } + fieldName := da.replace(fieldNameTpl, ia) + + // The enumerated values can be the same as another field, so to avoid + // duplication SVD files can simply refer to another set of enumerated + // values in the same register. + // See: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_registers.html#elem_enumeratedValues + for i := range fieldEl.EnumeratedValues { + enumeratedValues := &fieldEl.EnumeratedValues[i] + if enumeratedValues.DerivedFrom != "" { + parts := strings.Split(enumeratedValues.DerivedFrom, ".") + if len(parts) == 1 { + found := false + for _, otherFieldEl := range fieldEls { + for i := range otherFieldEl.EnumeratedValues { + otherEnum := &otherFieldEl.EnumeratedValues[i] + if otherEnum.Name == parts[0] { + found = true + *enumeratedValues = *otherEnum + } + } + } + if !found { + fmt.Fprintf(os.Stderr, "Warning: could not find enumeratedValue.derivedFrom of %s for register field %s\n", enumeratedValues.DerivedFrom, fieldName) + } + } else { + // The derivedFrom attribute may also point to enumerated values + // in other registers and even peripherals, but this feature + // isn't often used in SVD files. + fmt.Fprintf(os.Stderr, "TODO: enumeratedValue.derivedFrom to a different register: %s\n", enumeratedValues.DerivedFrom) } } - if !found { - fmt.Fprintf(os.Stderr, "Warning: could not find enumeratedValue.derivedFrom of %s for register field %s\n", enumeratedValues.DerivedFrom, fieldName) - } - } else { - // The derivedFrom attribute may also point to enumerated values - // in other registers and even peripherals, but this feature - // isn't often used in SVD files. - fmt.Fprintf(os.Stderr, "TODO: enumeratedValue.derivedFrom to a different register: %s\n", enumeratedValues.DerivedFrom) } - } - bitfields = append(bitfields, Bitfield{ - Name: fieldName, - Offset: lsb, - Mask: (0xffffffff >> (31 - (msb - lsb))) << lsb, - }) - fields = append(fields, Constant{ - Name: fmt.Sprintf("%s_%s%s_%s_Pos", groupName, bitfieldPrefix, regName, fieldName), - Description: fmt.Sprintf("Position of %s field.", fieldName), - Value: uint64(lsb), - }) - fields = append(fields, Constant{ - Name: fmt.Sprintf("%s_%s%s_%s_Msk", groupName, bitfieldPrefix, regName, fieldName), - Description: fmt.Sprintf("Bit mask of %s field.", fieldName), - Value: (0xffffffffffffffff >> (63 - (msb - lsb))) << lsb, - }) - if lsb == msb { // single bit + bitfields = append(bitfields, Bitfield{ + Name: fieldName, + Offset: lsb, + Mask: (0xffffffff >> (31 - (msb - lsb))) << lsb, + }) fields = append(fields, Constant{ - Name: fmt.Sprintf("%s_%s%s_%s", groupName, bitfieldPrefix, regName, fieldName), - Description: fmt.Sprintf("Bit %s.", fieldName), - Value: 1 << lsb, + Name: fmt.Sprintf("%s_%s%s_%s_Pos", groupName, bitfieldPrefix, regName, fieldName), + Description: fmt.Sprintf("Position of %s field.", fieldName), + Value: uint64(lsb), }) - } - for _, enumEl := range enumeratedValues.EnumeratedValue { - enumName := enumEl.Name - // Renesas has enum without actual values that we have to skip - if enumEl.Value == "" { - continue + fields = append(fields, Constant{ + Name: fmt.Sprintf("%s_%s%s_%s_Msk", groupName, bitfieldPrefix, regName, fieldName), + Description: fmt.Sprintf("Bit mask of %s field.", fieldName), + Value: (0xffffffffffffffff >> (63 - (msb - lsb))) << lsb, + }) + if lsb == msb { // single bit + fields = append(fields, Constant{ + Name: fmt.Sprintf("%s_%s%s_%s", groupName, bitfieldPrefix, regName, fieldName), + Description: fmt.Sprintf("Bit %s.", fieldName), + Value: 1 << lsb, + }) } + for i := range fieldEl.EnumeratedValues { + enumDefault.reset(1<<(msb+1-lsb) - 1) + fields0Pos := len(fields) + for _, enumEl := range fieldEl.EnumeratedValues[i].EnumeratedValue { + enumName := enumEl.Name + + if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { + continue + } + if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) { + enumName = strings.ToUpper(enumName) + } + enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName) + enumDescription := formatText(enumEl.Description) + + if enumEl.IsDefault { + enumDefault.setDefaultAction(func(value uint64) { + if value == 0 { + // put zero value in front of other constants + fields = slices.Insert(fields, fields0Pos, Constant{}) + appendConstant(fields[:fields0Pos], enumName, enumDescription, value, enumSeen) + } else { + fields = appendConstant(fields, enumName, enumDescription, value, enumSeen) + } + }) + continue + } - if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { - continue + // Renesas has enum without actual values that we have to skip + if enumEl.Value == "" { + continue + } + + var enumValue uint64 + var err error + if strings.HasPrefix(enumEl.Value, "0b") { + val := strings.TrimPrefix(enumEl.Value, "0b") + enumValue, err = strconv.ParseUint(val, 2, 64) + } else { + enumValue, err = strconv.ParseUint(enumEl.Value, 0, 64) + } + if err != nil { + if enumBitSpecifier.MatchString(enumEl.Value) { + // NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values + enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64) + if err != nil { + panic(err) + } + } else { + panic(err) + } + } + enumDefault.collectValue(enumValue) + fields = appendConstant(fields, enumName, enumDescription, enumValue, enumSeen) + } + enumDefault.resolve() } - if !unicode.IsUpper(rune(enumName[0])) && !unicode.IsDigit(rune(enumName[0])) { - enumName = strings.ToUpper(enumName) + return true + }) + } + return fields, bitfields +} + +var globalDerivationCtx derivationContext + +type derivationContext struct { + peripherals []*SVDPeripheral +} + +func (ctx *derivationContext) deriveField(fieldEl *SVDField, localFieldEls []*SVDField) error { + from := fieldEl.DerivedFrom + parts := strings.Split(from, ".") + srcName := parts[0] + var srcFieldEl *SVDField + switch len(parts) { + case 3, 4: + src, err := ctx.lookupGlobal(parts) + if err != nil { + return err + } + srcFieldEl = src + case 1: + // resolve locally, in current register + for _, f := range localFieldEls { + if f == fieldEl { + continue } - enumDescription := formatText(enumEl.Description) - var enumValue uint64 - var err error - if strings.HasPrefix(enumEl.Value, "0b") { - val := strings.TrimPrefix(enumEl.Value, "0b") - enumValue, err = strconv.ParseUint(val, 2, 64) - } else { - enumValue, err = strconv.ParseUint(enumEl.Value, 0, 64) + if f.Name == srcName { + srcFieldEl = f + break } - if err != nil { - if enumBitSpecifier.MatchString(enumEl.Value) { - // NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values - enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64) - if err != nil { - panic(err) + } + if srcFieldEl == nil { + return fmt.Errorf("not found") + } + default: + return fmt.Errorf("cannot decode source path") + } + + // copy enumeratedValues from source to current field + if fieldEl.DimIndex == nil && strings.Contains(fieldEl.Name, "%s") { + fieldEl.DimIndex = srcFieldEl.DimIndex + fieldEl.DimIncrement = srcFieldEl.DimIncrement + fieldEl.Dim = srcFieldEl.Dim + } + if fieldEl.Description == "" { + fieldEl.Description = srcFieldEl.Description + } + if fieldEl.BitWidth == nil { + fieldEl.BitWidth = srcFieldEl.BitWidth + } + if fieldEl.BitOffset == nil { + fieldEl.BitOffset = srcFieldEl.BitOffset + } + if fieldEl.BitRange == nil { + fieldEl.BitRange = srcFieldEl.BitRange + } + + fieldEl.EnumeratedValues = srcFieldEl.EnumeratedValues + return nil +} + +func (ctx *derivationContext) lookupGlobal(path []string) (*SVDField, error) { + curPath := path[:1] + for _, p := range ctx.peripherals { + if p.Name == path[0] { + if len(path) == 4 { + curPath = path[:2] + for _, c := range p.Clusters { + if c.Name == path[1] { + return ctx.lookupFieldInRegs(path[2:], c.Registers, curPath) } - } else { - panic(err) } + return nil, fmt.Errorf("cluster not found: %q", path[2]) + } - enumName = fmt.Sprintf("%s_%s%s_%s_%s", groupName, bitfieldPrefix, regName, fieldName, enumName) - - // Avoid duplicate values. Duplicate names with the same value are - // allowed, but the same name with a different value is not. Instead - // of trying to work around those cases, remove the value entirely - // as there is probably not one correct answer in such a case. - // For example, SVD files from NXP have enums limited to 20 - // characters, leading to lots of duplicates when these enum names - // are long. Nothing here can really fix those cases. - previousEnumValue, seenBefore := enumSeen[enumName] - if seenBefore { - if previousEnumValue < 0 { - // There was a mismatch before, ignore all equally named fields. - continue + return ctx.lookupFieldInRegs(path[1:], p.Registers, curPath) + } + } + return nil, fmt.Errorf("peripheral not found: %s", path[0]) +} + +func (ctx *derivationContext) lookupFieldInRegs(path []string, registers []*SVDRegister, curPath []string) (*SVDField, error) { + curPath = curPath[:len(curPath)+1] + for _, r := range registers { + if r.Name == path[0] { + curPath = curPath[:len(curPath)+1] + for _, f := range r.Fields { + if f.Name == path[1] { + return f, nil } - if int64(enumValue) != previousEnumValue { - // There is a mismatch. Mark it as such, and remove the - // existing enum bitfield value. - enumSeen[enumName] = -1 - for i, field := range fields { - if field.Name == enumName { - fields = append(fields[:i], fields[i+1:]...) - break - } - } + } + return nil, fmt.Errorf("field not found: %q", strings.Join(curPath, ".")) + } + } + return nil, fmt.Errorf("register not found: %q", strings.Join(curPath, ".")) +} + +func appendConstant(fields []Constant, enumName, enumDescription string, enumValue uint64, enumSeen map[string]int64) []Constant { + // Avoid duplicate values. Duplicate names with the same value are + // allowed, but the same name with a different value is not. Instead + // of trying to work around those cases, remove the value entirely + // as there is probably not one correct answer in such a case. + // For example, SVD files from NXP have enums limited to 20 + // characters, leading to lots of duplicates when these enum names + // are long. Nothing here can really fix those cases. + previousEnumValue, seenBefore := enumSeen[enumName] + if seenBefore { + if previousEnumValue < 0 { + // There was a mismatch before, ignore all equally named fields. + return fields + } + if int64(enumValue) != previousEnumValue { + // There is a mismatch. Mark it as such, and remove the + // existing enum bitfield value. + enumSeen[enumName] = -1 + for i, field := range fields { + if field.Name == enumName { + fields = append(fields[:i], fields[i+1:]...) + break } - continue } - enumSeen[enumName] = int64(enumValue) + } + return fields + } + enumSeen[enumName] = int64(enumValue) - fields = append(fields, Constant{ - Name: enumName, - Description: enumDescription, - Value: enumValue, - }) + fields = append(fields, Constant{ + Name: enumName, + Description: enumDescription, + Value: enumValue, + }) + return fields +} + +// enumDefaultResolver helps determine the actual numeric value for an +// enumeratedValue marked as the default (i.e., where "isDefault" is set). +// +// Some SVD files use "isDefault" to indicate a fallback value (e.g., Div1 in +// clock prescaler registers) without specifying the exact value when it's not +// critical. This type is used to collect all defined enumValues, and once +// collection is complete, derive a sensible default value that does not conflict +// with any explicitly defined ones. +// +// Typically, it prefers zero as a default if available; otherwise, it will +// choose a suitable unused value below the field's maximum. +type enumDefaultResolver struct { + values []uint64 + maxValue uint64 + handleDefault func(value uint64) +} + +func (dr *enumDefaultResolver) reset(maxValue uint64) { + dr.values = dr.values[:0] + dr.maxValue = maxValue + dr.handleDefault = nil +} + +func (dr *enumDefaultResolver) setDefaultAction(action func(v uint64)) { + dr.handleDefault = action +} + +func (dr *enumDefaultResolver) collectValue(value uint64) { + dr.values = append(dr.values, value) +} + +// resolve tries to find an actual value for the enumerated Value +// marked as default. +func (dr *enumDefaultResolver) resolve() { + if dr.handleDefault == nil { + return + } + list := dr.values + n := len(list) + if n == 0 { + return + } + slices.Sort(list) + + var value uint64 + // try to use zero as default value + if list[0] == 0 { + // not available, now try the highest value +1 + largest := list[n-1] + if largest < dr.maxValue { + value = largest + 1 + } else { + value = 1 + // not available, now lookup the first free value + for _, enumValue := range list[1:] { + if value < enumValue { + break + } + value = enumValue + 1 + if value == dr.maxValue { + return + } + } } } - return fields, bitfields + dr.handleDefault(value) } type Register struct { @@ -739,38 +1041,57 @@ func (r *Register) address() uint64 { } func (r *Register) dim() int { - if r.element.Dim == nil { + return decodeDim(r.element.Dim) +} + +func decodeDim(s *string) int { + if s == nil { return -1 // no dim elements } - dim, err := strconv.ParseInt(*r.element.Dim, 0, 32) + dim, err := strconv.ParseInt(*s, 0, 32) if err != nil { panic(err) } return int(dim) } -func (r *Register) dimIndex() []string { +type dimArray struct { + dim int + idx []string + incr uint32 +} + +func decodeDimArray(dimSpec, dimIndex *string, dimIncr, elType, elName string) *dimArray { + dim := decodeDim(dimSpec) + if dim <= 0 { + return nil + } + a := new(dimArray) + a.dim = dim + defer func() { if err := recover(); err != nil { - fmt.Println("register", r.name()) + fmt.Println(elType, elName) panic(err) } }() - dim := r.dim() - if r.element.DimIndex == nil { - if dim <= 0 { - return nil - } + incr, err := strconv.ParseUint(dimIncr, 0, 32) + if err != nil { + panic(err) + } + a.incr = uint32(incr) + if dimIndex == nil { idx := make([]string, dim) for i := range idx { idx[i] = strconv.FormatInt(int64(i), 10) } - return idx + a.idx = idx + return a } - t := strings.Split(*r.element.DimIndex, "-") + t := strings.Split(*dimIndex, "-") if len(t) == 2 { // renesas uses hex letters e.g. A-B if strings.Contains("ABCDEFabcdef", t[0]) { @@ -797,17 +1118,40 @@ func (r *Register) dimIndex() []string { for i := x; i <= y; i++ { idx[i-x] = strconv.FormatInt(i, 10) } - return idx + a.idx = idx + return a } else if len(t) > 2 { panic("invalid dimIndex") } - s := strings.Split(*r.element.DimIndex, ",") + s := strings.Split(*dimIndex, ",") if len(s) != dim { panic("invalid dimIndex") } + a.idx = s + return a +} - return s +func (da *dimArray) replace(s string, i int) string { + if da == nil { + return s + } + if i >= len(da.idx) { + return s + } + return strings.ReplaceAll(s, "%s", da.idx[i]) +} + +func (da *dimArray) rangeElems(yield func(i int, incr uint32) bool) { + if da == nil { + yield(0, 0) + return + } + for i := range da.dim { + if !yield(i, uint32(i)*da.incr) { + return + } + } } func (r *Register) size() int { @@ -823,37 +1167,32 @@ func (r *Register) size() int { func parseRegister(groupName string, regEl *SVDRegister, baseAddress uint64, bitfieldPrefix string) []*PeripheralField { reg := NewRegister(regEl, baseAddress) - - if reg.dim() != -1 { - dimIncrement, err := strconv.ParseUint(regEl.DimIncrement, 0, 32) - if err != nil { - panic(err) + name := reg.name() + da := decodeDimArray(regEl.Dim, regEl.DimIndex, regEl.DimIncrement, "register", name) + if da != nil && strings.Contains(name, "%s") { + // a "spaced array" of registers, special processing required + // we need to generate a separate register for each "element" + var results []*PeripheralField + shortName := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(name, "_%s", ""), "%s", "")) + for i := range da.idx { + regAddress := reg.address() + (uint64(i) * uint64(da.incr)) + results = append(results, &PeripheralField{ + Name: strings.ToUpper(da.replace(name, i)), + Address: regAddress, + Description: reg.description(), + Array: -1, + ElementSize: reg.size(), + ShortName: shortName, + }) } - if strings.Contains(reg.name(), "%s") { - // a "spaced array" of registers, special processing required - // we need to generate a separate register for each "element" - var results []*PeripheralField - shortName := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(reg.name(), "_%s", ""), "%s", "")) - for i, j := range reg.dimIndex() { - regAddress := reg.address() + (uint64(i) * dimIncrement) - results = append(results, &PeripheralField{ - Name: strings.ToUpper(strings.ReplaceAll(reg.name(), "%s", j)), - Address: regAddress, - Description: reg.description(), - Array: -1, - ElementSize: reg.size(), - ShortName: shortName, - }) - } - // set first result bitfield - results[0].Constants, results[0].Bitfields = parseBitfields(groupName, shortName, regEl.Fields, bitfieldPrefix) - results[0].HasBitfields = len(results[0].Bitfields) > 0 - for i := 1; i < len(results); i++ { - results[i].Bitfields = results[0].Bitfields - results[i].HasBitfields = results[0].HasBitfields - } - return results + // set first result bitfield + results[0].Constants, results[0].Bitfields = parseBitfields(groupName, shortName, regEl.Fields, bitfieldPrefix) + results[0].HasBitfields = len(results[0].Bitfields) > 0 + for i := 1; i < len(results); i++ { + results[i].Bitfields = results[0].Bitfields + results[i].HasBitfields = results[0].HasBitfields } + return results } regName := reg.name() if !unicode.IsUpper(rune(regName[0])) && !unicode.IsDigit(rune(regName[0])) { @@ -979,14 +1318,20 @@ var ( {{- end}} {{- end}} {{.Name}} = (*{{.GroupName}}_Type)(unsafe.Pointer(uintptr(0x{{printf "%x" .BaseAddress}}))) + {{- if .Alias}} + {{.Alias}} = {{.Name}} + {{- end}} {{- "\n"}} {{- end}} ) `)) + pkgName := filepath.Base(strings.TrimRight(outdir, "/")) + tweakDevice(device, pkgName) + err = t.Execute(w, map[string]interface{}{ "device": device, - "pkgName": filepath.Base(strings.TrimRight(outdir, "/")), + "pkgName": pkgName, "interruptMax": maxInterruptValue, "interruptSystem": interruptSystem, "interruptHandlers": interruptHandlers, diff --git a/tools/gen-device-svd/tweak.go b/tools/gen-device-svd/tweak.go new file mode 100644 index 0000000000..8ee505da14 --- /dev/null +++ b/tools/gen-device-svd/tweak.go @@ -0,0 +1,124 @@ +package main + +import ( + "slices" + "strings" +) + +func tweakDevice(d *Device, pkgName string) { + if pkgName != "stm32" { + // no-op for device types that do not need tweaks + return + } + + // Source file machine_stm32_iwdg.go relies on the presence of + // a register IWDG. On some devices, though, like the h723, + // there are two registers, IWDG1 and IWDG2. In this case we + // define an alias IWDG for IWDG1. + addUnnumberedAlias(d, "IWDG", "IWDG1") + + for _, p := range d.Peripherals { + switch p.GroupName { + case "TIM": + // SVDs like stm32l4r5.svd define CCMR*_Input and _Output + // alternate registers, with _Input sorted before _Output. + // This would result in the _Output fields missing from the + // TIM_type struct definition, hence compilation would fail. + // Therefore we adjust the order of these alternate registers + // accordingly. + stm32EnsureCCMROrder(p.Registers) + + case "USART": + isr := p.lookupRegister("ISR") + if isr == nil { + continue + } + + // Some of the upstream SVD files, like the one for stm32wl5x_cm4, + // lack FIFO enabled variants of the USART ISR register, + // even if the register manual defines them. To make sure + // that TXFNF is not missing from the generated .go files, + // we add TXFNF here in case FIFOEN is present. + if p.lookupRegister("CR1").hasBitfield("FIFOEN") { + stm32EnsureBit(isr, "TXFNF", "TXE", "USART_ISR_") + } + + // Svdtools handles the presence of alternate USART ISR registers, + // like in case of the stm32l4r5, adjusting names like "ISR_enabled" + // to "ISR", deleting "ISR_disabled" or "ISR_ALTERNATE" register definitions + // from the SVD. + // As this would result in USART_ISR_TXE definitions missing in the + // generated .go file, a constant for TXE is added here + // in case TXFNF is defined. + stm32EnsureBit(isr, "TXE", "TXFNF", "USART_ISR_") + } + } +} + +func addUnnumberedAlias(d *Device, dest, src string) { + if _, ok := d.PeripheralDict[dest]; !ok { + if p := d.PeripheralDict[src]; p != nil { + p.Alias = dest + } + } +} + +func stm32EnsureCCMROrder(registers []*PeripheralField) { + for i, r := range registers { + if i > 0 { + prev := registers[i-1] + if r.Address == prev.Address { + // alternate field + if strings.HasPrefix(prev.Name, "CCMR") && strings.HasPrefix(r.Name, "CCMR") && strings.HasSuffix(r.Name, "_Output") { + // swap register pointers + registers[i-1], registers[i] = r, prev + } + } + } + } +} + +func stm32EnsureBit(reg *PeripheralField, want, have, prefix string) { + iWant := -1 + iHave := -1 + wantConst := prefix + want + haveConst := prefix + have + for i := range reg.Constants { + f := ®.Constants[i] + if f.Name == wantConst { + iWant = i + break + } + if f.Name == haveConst { + iHave = i + break + } + } + if iHave != -1 && iWant == -1 { + iWant = iHave + 1 + reg.Constants = slices.Insert(reg.Constants, iWant, reg.Constants[iHave]) + reg.Constants[iWant].Name = wantConst + reg.Constants[iWant].Description = "Bit " + want + ". (added by gen-device-svd)" + } +} + +func (p *Peripheral) lookupRegister(name string) *PeripheralField { + for _, r := range p.Registers { + if r.Name == name { + return r + } + } + return nil +} + +func (r *PeripheralField) hasBitfield(name string) bool { + if r == nil { + return false + } + for i := range r.Bitfields { + if r.Bitfields[i].Name == name { + return true + } + } + return false +} diff --git a/transform/allocs.go b/transform/allocs.go index 870faa5b75..d9c7e47096 100644 --- a/transform/allocs.go +++ b/transform/allocs.go @@ -6,8 +6,10 @@ package transform // interprocedural escape analysis. import ( + "bufio" "fmt" "go/token" + "os" "regexp" "tinygo.org/x/go-llvm" @@ -37,6 +39,10 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, maxStackAlloc u complex128Type := ctx.StructType([]llvm.Type{ctx.DoubleType(), ctx.DoubleType()}, false) maxAlign := int64(targetData.ABITypeAlignment(complex128Type)) + if printAllocs != nil { + fmt.Fprintln(os.Stderr, "mode: set") + } + for _, heapalloc := range getUses(allocator) { logAllocs := printAllocs != nil && printAllocs.MatchString(heapalloc.InstructionParent().Parent().Name()) if heapalloc.Operand(0).IsAConstantInt().IsNil() { @@ -173,5 +179,35 @@ func valueEscapesAt(value llvm.Value) llvm.Value { // logAlloc prints a message to stderr explaining why the given object had to be // allocated on the heap. func logAlloc(logger func(token.Position, string), allocCall llvm.Value, reason string) { - logger(getPosition(allocCall), "object allocated on the heap: "+reason) + pos := getPosition(allocCall) + if pos.Filename == "" || pos.Line <= 0 { + logger(pos, "") + return + } + + endCol := lineLengthAt(pos.Filename, pos.Line) + if endCol < 1 { + endCol = 1 + } + + // Only emit the coverprofile line, without position prefix. + logger(token.Position{}, fmt.Sprintf("%s:%d.1,%d.%d 1 0", pos.Filename, pos.Line, pos.Line, endCol)) +} + +func lineLengthAt(filename string, lineNumber int) int { + f, err := os.Open(filename) + if err != nil { + return 0 + } + defer f.Close() + + scanner := bufio.NewScanner(f) + line := 1 + for scanner.Scan() { + if line == lineNumber { + return len(scanner.Text()) + } + line++ + } + return 0 } diff --git a/transform/allocs_test.go b/transform/allocs_test.go index 7f7ff5b75e..df39605b16 100644 --- a/transform/allocs_test.go +++ b/transform/allocs_test.go @@ -57,9 +57,9 @@ func TestAllocs2(t *testing.T) { sort.Slice(testOutputs, func(i, j int) bool { return testOutputs[i].line < testOutputs[j].line }) - testOutput := "" + testOutput := make([]string, 0) for _, out := range testOutputs { - testOutput += out.String() + "\n" + testOutput = append(testOutput, out.String()) } // Load expected test output (the OUT: lines). @@ -67,15 +67,18 @@ func TestAllocs2(t *testing.T) { if err != nil { t.Fatal("could not read test input:", err) } - var expectedTestOutput string - for i, line := range strings.Split(strings.ReplaceAll(string(testInput), "\r\n", "\n"), "\n") { + var expectedTestOutput []string + for _, line := range strings.Split(strings.ReplaceAll(string(testInput), "\r\n", "\n"), "\n") { if idx := strings.Index(line, " // OUT: "); idx > 0 { msg := line[idx+len(" // OUT: "):] - expectedTestOutput += "allocs2.go:" + strconv.Itoa(i+1) + ": " + msg + "\n" + expectedTestOutput = append(expectedTestOutput, msg) } } - if testOutput != expectedTestOutput { - t.Errorf("output does not match expected output:\n%s", testOutput) + for i := range testOutput { + if !strings.HasSuffix(testOutput[i], expectedTestOutput[i]) { + t.Errorf("output does not match expected output:\n%s\n%s\n", testOutput[i], expectedTestOutput[i]) + return + } } } diff --git a/transform/optimizer.go b/transform/optimizer.go index 54f9762bc4..1209754310 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -88,7 +88,11 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { // Run TinyGo-specific interprocedural optimizations. OptimizeAllocs(mod, config.Options.PrintAllocs, maxStackSize, func(pos token.Position, msg string) { - fmt.Fprintln(os.Stderr, pos.String()+": "+msg) + if pos.Filename != "" { + fmt.Fprintf(os.Stderr, "%s:%d:%d: %s\n", pos.Filename, pos.Line, pos.Column, msg) + } else { + fmt.Fprintln(os.Stderr, msg) // No prefix! + } }) OptimizeStringToBytes(mod) OptimizeStringEqual(mod) diff --git a/transform/testdata/allocs2.go b/transform/testdata/allocs2.go index 9fcebb212f..2b0306db10 100644 --- a/transform/testdata/allocs2.go +++ b/transform/testdata/allocs2.go @@ -10,7 +10,7 @@ func main() { derefInt(&n1) // This should eventually be modified to not escape. - n2 := 6 // OUT: object allocated on the heap: escapes at line 14 + n2 := 6 // OUT: allocs2.go:52.1,52.42 1 0 returnIntPtr(&n2) s1 := make([]int, 3) @@ -20,22 +20,22 @@ func main() { readIntSlice(s2[:]) // This should also be modified to not escape. - s3 := make([]int, 3) // OUT: object allocated on the heap: escapes at line 24 + s3 := make([]int, 3) // OUT: allocs2.go:51.1,51.42 1 0 returnIntSlice(s3) - useSlice(make([]int, getUnknownNumber())) // OUT: object allocated on the heap: size is not constant + useSlice(make([]int, getUnknownNumber())) // OUT: allocs2.go:48.1,48.55 1 0 - s4 := make([]byte, 300) // OUT: object allocated on the heap: object size 300 exceeds maximum stack allocation size 256 + s4 := make([]byte, 300) // OUT: allocs2.go:46.1,46.56 1 0 readByteSlice(s4) - s5 := make([]int, 4) // OUT: object allocated on the heap: escapes at line 32 + s5 := make([]int, 4) // OUT: allocs2.go:38.1,38.56 1 0 _ = append(s5, 5) s6 := make([]int, 3) s7 := []int{1, 2, 3} copySlice(s6, s7) - c1 := getComplex128() // OUT: object allocated on the heap: escapes at line 39 + c1 := getComplex128() // OUT: allocs2.go:31.1,31.55 1 0 useInterface(c1) n3 := 5 @@ -43,13 +43,13 @@ func main() { return n3 }() - callVariadic(3, 5, 8) // OUT: object allocated on the heap: escapes at line 46 + callVariadic(3, 5, 8) // OUT: allocs2.go:28.1,28.58 1 0 - s8 := []int{3, 5, 8} // OUT: object allocated on the heap: escapes at line 49 + s8 := []int{3, 5, 8} // OUT: allocs2.go:26.1,26.76 1 0 callVariadic(s8...) - n4 := 3 // OUT: object allocated on the heap: escapes at line 53 - n5 := 7 // OUT: object allocated on the heap: escapes at line 53 + n4 := 3 // OUT: allocs2.go:23.1,23.55 1 0 + n5 := 7 // OUT: allocs2.go:13.1,13.42 1 0 func() { n4 = n5 }()