mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-27 07:08:42 +00:00
Compare commits
27 Commits
dev
...
tgdoc-tool
| Author | SHA1 | Date | |
|---|---|---|---|
| cc959c5aab | |||
| ef77ed3103 | |||
| ca2694c5d9 | |||
| fd1d10c9b7 | |||
| 5c37d1ba61 | |||
| 610dd19c40 | |||
| 44ca224056 | |||
| c1cddffbe9 | |||
| 9bbad6700b | |||
| 24f965425d | |||
| b6b723aeec | |||
| 934d5f41bf | |||
| 4b0e858964 | |||
| 32378537b8 | |||
| f5b2a08b15 | |||
| f23e18a8de | |||
| 66d7099c96 | |||
| bef0dc5d21 | |||
| e79cdc1122 | |||
| f0256cab18 | |||
| 5d8e071bfb | |||
| a0069b6282 | |||
| 707d37a4c1 | |||
| 1876b65b18 | |||
| 1fe934e8a8 | |||
| 8bd2233b57 | |||
| ff58c50118 |
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
+10
@@ -814,6 +814,8 @@ 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
|
||||
# test pwm
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -896,6 +898,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)
|
||||
@@ -917,6 +923,10 @@ ifneq ($(XTENSA), 0)
|
||||
@$(MD5SUM) test.bin
|
||||
$(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/mcp3008
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32s3-wroom1 examples/mcp3008
|
||||
@$(MD5SUM) test.bin
|
||||
endif
|
||||
$(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
|
||||
@@ -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
|
||||
|
||||
+9
-4
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+90
-90
@@ -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)
|
||||
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)
|
||||
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)
|
||||
|
||||
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
|
||||
if bitmapLen < pointerBits {
|
||||
rawMask := binary.LittleEndian.Uint64(bitmap[0:8])
|
||||
layout := rawMask*pointerBits + bitmapLen
|
||||
layout <<= 1
|
||||
layout |= 1
|
||||
|
||||
// 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)
|
||||
// Check if the layout fits.
|
||||
layout &= 1<<pointerBits - 1
|
||||
if (layout>>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 {
|
||||
// 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
|
||||
}
|
||||
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")
|
||||
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")
|
||||
|
||||
Vendored
+8
@@ -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() {
|
||||
|
||||
Vendored
+8
-4
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Generated
+4
-4
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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 }:
|
||||
|
||||
+1
-1
@@ -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).
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build esp32s3_wroom1
|
||||
|
||||
package machine
|
||||
|
||||
const (
|
||||
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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -509,102 +509,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.
|
||||
|
||||
@@ -308,5 +308,3 @@ func (uart *UART) writeByte(b byte) error {
|
||||
}
|
||||
|
||||
func (uart *UART) flush() {}
|
||||
|
||||
// TODO: SPI
|
||||
|
||||
@@ -0,0 +1,460 @@
|
||||
//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 (
|
||||
SPI_MODE0 = uint8(0)
|
||||
SPI_MODE1 = uint8(1)
|
||||
SPI_MODE2 = uint8(2)
|
||||
SPI_MODE3 = uint8(3)
|
||||
|
||||
// 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)
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 SPI_MODE0:
|
||||
// CPOL=0, CPHA=0 (default)
|
||||
case SPI_MODE1:
|
||||
bus.SetUSER_CK_OUT_EDGE(1) // CPHA=1
|
||||
case SPI_MODE2:
|
||||
bus.SetMISC_CK_IDLE_EDGE(1) // CPOL=1
|
||||
bus.SetUSER_CK_OUT_EDGE(1) // CPHA=1
|
||||
case SPI_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))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Compute the SPI bus frequency from the APB clock frequency.
|
||||
// Note: APB clock is always 80MHz on ESP32-S3, independent of CPU frequency.
|
||||
// Ported from ESP32-C3 implementation for better accuracy.
|
||||
func freqToClockDiv(hz uint32) uint32 {
|
||||
// Use APB clock frequency (80MHz), not CPU frequency!
|
||||
// SPI peripheral is connected to APB bus which stays at 80MHz
|
||||
const apbFreq = pplClockFreq // 80MHz
|
||||
|
||||
if hz >= apbFreq { // maximum frequency
|
||||
return 1 << 31
|
||||
}
|
||||
if hz < (apbFreq / (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(apbFreq)/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 := apbFreq / ((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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
//go:build esp32s3 || esp32c3
|
||||
|
||||
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 {
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2)
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<<signPos64 - 1
|
||||
|
||||
signPos32 = 31
|
||||
exponentPos32 = 23
|
||||
minPosNaN32 = ((1 << signPos32) - (1 << exponentPos32)) + 1
|
||||
magMask32 = 1<<signPos32 - 1
|
||||
)
|
||||
|
||||
type float interface {
|
||||
float32 | float64
|
||||
}
|
||||
|
||||
type floatInt interface {
|
||||
int32 | int64
|
||||
}
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
package runtime_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
func TestFloatMinMax32(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, c := range []struct {
|
||||
x float32
|
||||
y float32
|
||||
min float32
|
||||
max float32
|
||||
}{
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
min: 0,
|
||||
max: 0,
|
||||
},
|
||||
{
|
||||
x: -12,
|
||||
y: 2,
|
||||
min: -12,
|
||||
max: 2,
|
||||
},
|
||||
{
|
||||
x: 2,
|
||||
y: -12,
|
||||
min: -12,
|
||||
max: 2,
|
||||
},
|
||||
{
|
||||
x: float32(math.Copysign(0, -1)),
|
||||
y: 0,
|
||||
min: float32(math.Copysign(0, -1)),
|
||||
max: 0,
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: float32(math.Copysign(0, -1)),
|
||||
min: float32(math.Copysign(0, -1)),
|
||||
max: 0,
|
||||
},
|
||||
{
|
||||
x: float32(math.Inf(-1)),
|
||||
y: float32(math.Inf(1)),
|
||||
min: float32(math.Inf(-1)),
|
||||
max: float32(math.Inf(1)),
|
||||
},
|
||||
{
|
||||
x: math.MaxFloat32,
|
||||
y: math.SmallestNonzeroFloat32,
|
||||
min: math.SmallestNonzeroFloat32,
|
||||
max: math.MaxFloat32,
|
||||
},
|
||||
{
|
||||
x: math.Float32frombits(float32PositiveNaN),
|
||||
y: 0,
|
||||
min: math.Float32frombits(float32PositiveNaN),
|
||||
max: math.Float32frombits(float32PositiveNaN),
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: math.Float32frombits(float32PositiveNaN),
|
||||
min: math.Float32frombits(float32PositiveNaN),
|
||||
max: math.Float32frombits(float32PositiveNaN),
|
||||
},
|
||||
{
|
||||
x: math.Float32frombits(float32PositiveNaN),
|
||||
y: math.Float32frombits(float32PositiveNaN),
|
||||
min: math.Float32frombits(float32PositiveNaN),
|
||||
max: math.Float32frombits(float32PositiveNaN),
|
||||
},
|
||||
{
|
||||
x: math.Float32frombits(float32NegativeNaN),
|
||||
y: 0,
|
||||
min: math.Float32frombits(float32NegativeNaN),
|
||||
max: math.Float32frombits(float32NegativeNaN),
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: math.Float32frombits(float32NegativeNaN),
|
||||
min: math.Float32frombits(float32NegativeNaN),
|
||||
max: math.Float32frombits(float32NegativeNaN),
|
||||
},
|
||||
{
|
||||
x: math.Float32frombits(float32NegativeNaN),
|
||||
y: math.Float32frombits(float32NegativeNaN),
|
||||
min: math.Float32frombits(float32NegativeNaN),
|
||||
max: math.Float32frombits(float32NegativeNaN),
|
||||
},
|
||||
} {
|
||||
if min := minimumFloat32(c.x, c.y); math.Float32bits(min) != math.Float32bits(c.min) {
|
||||
t.Errorf("minimumFloat32(%f, %f) = %f (expected %f)", c.x, c.y, min, c.min)
|
||||
}
|
||||
if max := maximumFloat32(c.x, c.y); math.Float32bits(max) != math.Float32bits(c.max) {
|
||||
t.Errorf("maximumFloat32(%f, %f) = %f (expected %f)", c.x, c.y, max, c.max)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// float32PositiveNaN is the smallest positive NaN value for a float32.
|
||||
float32PositiveNaN = 0x7FC00001
|
||||
// float32NegativeNaN is the smallest negative NaN value for a float32.
|
||||
float32NegativeNaN = 0xFFC00001
|
||||
)
|
||||
|
||||
//go:linkname minimumFloat32 runtime.minimumFloat32
|
||||
func minimumFloat32(x, y float32) float32
|
||||
|
||||
//go:linkname maximumFloat32 runtime.maximumFloat32
|
||||
func maximumFloat32(x, y float32) float32
|
||||
|
||||
func TestFloatMinMax64(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, c := range []struct {
|
||||
x float64
|
||||
y float64
|
||||
min float64
|
||||
max float64
|
||||
}{
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
min: 0,
|
||||
max: 0,
|
||||
},
|
||||
{
|
||||
x: -12,
|
||||
y: 2,
|
||||
min: -12,
|
||||
max: 2,
|
||||
},
|
||||
{
|
||||
x: 2,
|
||||
y: -12,
|
||||
min: -12,
|
||||
max: 2,
|
||||
},
|
||||
{
|
||||
x: math.Copysign(0, -1),
|
||||
y: 0,
|
||||
min: math.Copysign(0, -1),
|
||||
max: 0,
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: math.Copysign(0, -1),
|
||||
min: math.Copysign(0, -1),
|
||||
max: 0,
|
||||
},
|
||||
{
|
||||
x: math.Inf(-1),
|
||||
y: math.Inf(1),
|
||||
min: math.Inf(-1),
|
||||
max: math.Inf(1),
|
||||
},
|
||||
{
|
||||
x: math.MaxFloat64,
|
||||
y: math.SmallestNonzeroFloat64,
|
||||
min: math.SmallestNonzeroFloat64,
|
||||
max: math.MaxFloat64,
|
||||
},
|
||||
{
|
||||
x: math.Float64frombits(float64PositiveNaN),
|
||||
y: 0,
|
||||
min: math.Float64frombits(float64PositiveNaN),
|
||||
max: math.Float64frombits(float64PositiveNaN),
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: math.Float64frombits(float64PositiveNaN),
|
||||
min: math.Float64frombits(float64PositiveNaN),
|
||||
max: math.Float64frombits(float64PositiveNaN),
|
||||
},
|
||||
{
|
||||
x: math.Float64frombits(float64PositiveNaN),
|
||||
y: math.Float64frombits(float64PositiveNaN),
|
||||
min: math.Float64frombits(float64PositiveNaN),
|
||||
max: math.Float64frombits(float64PositiveNaN),
|
||||
},
|
||||
{
|
||||
x: math.Float64frombits(float64NegativeNaN),
|
||||
y: 0,
|
||||
min: math.Float64frombits(float64NegativeNaN),
|
||||
max: math.Float64frombits(float64NegativeNaN),
|
||||
},
|
||||
{
|
||||
x: 0,
|
||||
y: math.Float64frombits(float64NegativeNaN),
|
||||
min: math.Float64frombits(float64NegativeNaN),
|
||||
max: math.Float64frombits(float64NegativeNaN),
|
||||
},
|
||||
{
|
||||
x: math.Float64frombits(float64NegativeNaN),
|
||||
y: 0,
|
||||
min: math.Float64frombits(float64NegativeNaN),
|
||||
max: math.Float64frombits(float64NegativeNaN),
|
||||
},
|
||||
} {
|
||||
if min := minimumFloat64(c.x, c.y); math.Float64bits(min) != math.Float64bits(c.min) {
|
||||
t.Errorf("minimumFloat64(%f, %f) = %f (expected %f)", c.x, c.y, min, c.min)
|
||||
}
|
||||
if max := maximumFloat64(c.x, c.y); math.Float64bits(max) != math.Float64bits(c.max) {
|
||||
t.Errorf("maximumFloat64(%f, %f) = %f (expected %f)", c.x, c.y, max, c.max)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// float64PositiveNaN is the smallest positive NaN value for a float64.
|
||||
float64PositiveNaN = 0x7FF8000000000001
|
||||
// float64NegativeNaN is the smallest negative NaN value for a float64.
|
||||
float64NegativeNaN = 0xFFF8000000000001
|
||||
)
|
||||
|
||||
//go:linkname minimumFloat64 runtime.minimumFloat64
|
||||
func minimumFloat64(x, y float64) float64
|
||||
|
||||
//go:linkname maximumFloat64 runtime.maximumFloat64
|
||||
func maximumFloat64(x, y float64) float64
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"inherits": ["esp32s3"],
|
||||
"build-tags": ["esp32s3_wroom1"]
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
Vendored
+6
-4
@@ -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
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build/constraint"
|
||||
"go/doc"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// buildTagSet creates the full set of tags that should evaluate to true
|
||||
// for a given target, matching TinyGo's Config.BuildTags() behavior.
|
||||
func buildTagSet(t Target) map[string]bool {
|
||||
tags := make(map[string]bool)
|
||||
for _, tag := range t.BuildTags {
|
||||
tags[tag] = true
|
||||
}
|
||||
if t.GOOS != "" {
|
||||
tags[t.GOOS] = true
|
||||
}
|
||||
if t.GOARCH != "" {
|
||||
tags[t.GOARCH] = true
|
||||
}
|
||||
tags["tinygo"] = true
|
||||
tags["purego"] = true
|
||||
tags["osusergo"] = true
|
||||
tags["math_big_pure_go"] = true
|
||||
if t.GC != "" {
|
||||
tags["gc."+t.GC] = true
|
||||
}
|
||||
if t.Scheduler != "" {
|
||||
tags["scheduler."+t.Scheduler] = true
|
||||
}
|
||||
if t.Serial != "" {
|
||||
tags["serial."+t.Serial] = true
|
||||
}
|
||||
// Go version tags — TinyGo currently tracks Go 1.22.
|
||||
for i := 1; i <= 22; i++ {
|
||||
tags[fmt.Sprintf("go1.%d", i)] = true
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// extractBuildConstraint reads the file header (before the package clause)
|
||||
// looking for a //go:build constraint line.
|
||||
func extractBuildConstraint(data []byte) (constraint.Expr, error) {
|
||||
scanner := bufio.NewScanner(bytes.NewReader(data))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "package ") {
|
||||
break
|
||||
}
|
||||
if constraint.IsGoBuild(trimmed) {
|
||||
return constraint.Parse(trimmed)
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ExtractDocs filters .go files in pkgDir by the target's build tags,
|
||||
// parses them, and returns a *doc.Package via go/doc.NewFromFiles.
|
||||
func ExtractDocs(t Target, pkgDir string, allDecls bool) (*doc.Package, *token.FileSet, error) {
|
||||
tags := buildTagSet(t)
|
||||
|
||||
entries, err := os.ReadDir(pkgDir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
var files []*ast.File
|
||||
|
||||
for _, e := range entries {
|
||||
name := e.Name()
|
||||
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
|
||||
continue
|
||||
}
|
||||
|
||||
path := filepath.Join(pkgDir, name)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
expr, err := extractBuildConstraint(data)
|
||||
if err != nil {
|
||||
continue // skip files with unparseable constraints
|
||||
}
|
||||
|
||||
if expr != nil && !expr.Eval(func(tag string) bool { return tags[tag] }) {
|
||||
continue
|
||||
}
|
||||
|
||||
f, err := parser.ParseFile(fset, path, data, parser.ParseComments)
|
||||
if err != nil {
|
||||
continue // skip files with syntax errors
|
||||
}
|
||||
|
||||
files = append(files, f)
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return nil, nil, fmt.Errorf("no matching files for target %s", t.Name)
|
||||
}
|
||||
|
||||
var opts []any
|
||||
if allDecls {
|
||||
opts = append(opts, doc.AllDecls)
|
||||
}
|
||||
|
||||
docPkg, err := doc.NewFromFiles(fset, files, "machine", opts...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return docPkg, fset, nil
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/doc"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Result holds the extracted documentation for a single target.
|
||||
type Result struct {
|
||||
Target Target
|
||||
Pkg *doc.Package
|
||||
Fset *token.FileSet
|
||||
}
|
||||
|
||||
// JSON-serializable API types.
|
||||
|
||||
type PackageAPI struct {
|
||||
Target string `json:"target"`
|
||||
Package string `json:"package"`
|
||||
Doc string `json:"doc,omitempty"`
|
||||
Constants []APIValue `json:"constants,omitempty"`
|
||||
Variables []APIValue `json:"variables,omitempty"`
|
||||
Types []APIType `json:"types,omitempty"`
|
||||
Functions []APIFunc `json:"functions,omitempty"`
|
||||
}
|
||||
|
||||
type APIValue struct {
|
||||
Doc string `json:"doc,omitempty"`
|
||||
Names []string `json:"names"`
|
||||
Decl string `json:"decl"`
|
||||
}
|
||||
|
||||
type APIType struct {
|
||||
Doc string `json:"doc,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Decl string `json:"decl"`
|
||||
Constants []APIValue `json:"constants,omitempty"`
|
||||
Variables []APIValue `json:"variables,omitempty"`
|
||||
Functions []APIFunc `json:"functions,omitempty"`
|
||||
Methods []APIFunc `json:"methods,omitempty"`
|
||||
}
|
||||
|
||||
type APIFunc struct {
|
||||
Doc string `json:"doc,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Decl string `json:"decl"`
|
||||
Recv string `json:"recv,omitempty"`
|
||||
}
|
||||
|
||||
func formatNode(fset *token.FileSet, node ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
format.Node(&buf, fset, node)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func convertValues(fset *token.FileSet, vals []*doc.Value) []APIValue {
|
||||
out := make([]APIValue, 0, len(vals))
|
||||
for _, v := range vals {
|
||||
out = append(out, APIValue{
|
||||
Doc: v.Doc,
|
||||
Names: v.Names,
|
||||
Decl: formatNode(fset, v.Decl),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func convertFuncs(fset *token.FileSet, funcs []*doc.Func) []APIFunc {
|
||||
out := make([]APIFunc, 0, len(funcs))
|
||||
for _, f := range funcs {
|
||||
out = append(out, APIFunc{
|
||||
Doc: f.Doc,
|
||||
Name: f.Name,
|
||||
Decl: formatNode(fset, f.Decl),
|
||||
Recv: f.Recv,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func convertPackage(targetName string, pkg *doc.Package, fset *token.FileSet) *PackageAPI {
|
||||
api := &PackageAPI{
|
||||
Target: targetName,
|
||||
Package: pkg.Name,
|
||||
Doc: pkg.Doc,
|
||||
Constants: convertValues(fset, pkg.Consts),
|
||||
Variables: convertValues(fset, pkg.Vars),
|
||||
Functions: convertFuncs(fset, pkg.Funcs),
|
||||
}
|
||||
for _, t := range pkg.Types {
|
||||
api.Types = append(api.Types, APIType{
|
||||
Doc: t.Doc,
|
||||
Name: t.Name,
|
||||
Decl: formatNode(fset, t.Decl),
|
||||
Constants: convertValues(fset, t.Consts),
|
||||
Variables: convertValues(fset, t.Vars),
|
||||
Functions: convertFuncs(fset, t.Funcs),
|
||||
Methods: convertFuncs(fset, t.Methods),
|
||||
})
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
func main() {
|
||||
jsonOutput := flag.Bool("json", false, "output JSON to stdout")
|
||||
httpAddr := flag.String("http", "", "serve HTTP documentation (e.g. :8080)")
|
||||
allDecls := flag.Bool("all", false, "include unexported identifiers")
|
||||
includeBase := flag.Bool("base", false, "include base/parent targets")
|
||||
flag.Parse()
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "usage: tgdoc [flags] <targets-dir> <package-dir>\n")
|
||||
fmt.Fprintf(os.Stderr, "\nexample: tgdoc -http :8080 ./targets ./src/machine\n")
|
||||
fmt.Fprintf(os.Stderr, "\nFlags:\n")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
targetsDir := args[0]
|
||||
pkgDir := args[1]
|
||||
|
||||
if *httpAddr != "" && !strings.Contains(*httpAddr, ":") {
|
||||
fmt.Fprintf(os.Stderr, "error: -http value %q doesn't look like an address (expected e.g. :8080)\n", *httpAddr)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
targets, err := LoadTargets(targetsDir, *includeBase)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var results []Result
|
||||
for _, t := range targets {
|
||||
pkg, fset, err := ExtractDocs(t, pkgDir, *allDecls)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "warning: %s: %v\n", t.Name, err)
|
||||
continue
|
||||
}
|
||||
results = append(results, Result{t, pkg, fset})
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool { return results[i].Target.Name < results[j].Target.Name })
|
||||
|
||||
if *httpAddr != "" {
|
||||
serve(*httpAddr, results)
|
||||
return
|
||||
}
|
||||
|
||||
// Default: JSON output.
|
||||
_ = jsonOutput
|
||||
apis := make([]*PackageAPI, 0, len(results))
|
||||
for _, r := range results {
|
||||
apis = append(apis, convertPackage(r.Target.Name, r.Pkg, r.Fset))
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(apis); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/doc"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IdentIndex is the precomputed identifier index across all targets.
|
||||
type IdentIndex struct {
|
||||
Entries []*IdentEntry
|
||||
ByKey map[string]*IdentEntry
|
||||
Total int // total number of targets
|
||||
}
|
||||
|
||||
// IdentEntry is one identifier (type, func, method, const, or var) across all targets.
|
||||
type IdentEntry struct {
|
||||
Name string
|
||||
Kind string // "type", "func", "method", "const", "var"
|
||||
Count int
|
||||
Key string // URL key: "kind/name"
|
||||
Groups []IdentGroup
|
||||
}
|
||||
|
||||
// IdentGroup is a set of targets with identical declaration for an identifier.
|
||||
type IdentGroup struct {
|
||||
Decl string // representative formatted declaration
|
||||
Targets []string // sorted target names
|
||||
}
|
||||
|
||||
func buildIdentIndex(results []Result) *IdentIndex {
|
||||
type occ struct {
|
||||
target string
|
||||
decl string // grouping key + display
|
||||
}
|
||||
type ikey struct {
|
||||
name, kind string
|
||||
}
|
||||
|
||||
idx := make(map[ikey][]occ)
|
||||
|
||||
addFunc := func(target string, fset *token.FileSet, f *doc.Func, prefix string) {
|
||||
name := f.Name
|
||||
if prefix != "" {
|
||||
name = prefix + "." + f.Name
|
||||
}
|
||||
kind := "func"
|
||||
if f.Recv != "" {
|
||||
kind = "method"
|
||||
}
|
||||
decl := fmtNode(fset, f.Decl)
|
||||
idx[ikey{name, kind}] = append(idx[ikey{name, kind}], occ{target, decl})
|
||||
}
|
||||
|
||||
addValues := func(target string, fset *token.FileSet, vals []*doc.Value, kind string) {
|
||||
for _, v := range vals {
|
||||
for _, name := range v.Names {
|
||||
typeKey := resolveValueType(fset, v.Decl, name)
|
||||
idx[ikey{name, kind}] = append(idx[ikey{name, kind}], occ{target, typeKey})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range results {
|
||||
t := r.Target.Name
|
||||
fset := r.Fset
|
||||
pkg := r.Pkg
|
||||
|
||||
for _, ty := range pkg.Types {
|
||||
decl := fmtNode(fset, ty.Decl)
|
||||
idx[ikey{ty.Name, "type"}] = append(idx[ikey{ty.Name, "type"}], occ{t, decl})
|
||||
|
||||
for _, m := range ty.Methods {
|
||||
addFunc(t, fset, m, ty.Name)
|
||||
}
|
||||
for _, f := range ty.Funcs {
|
||||
addFunc(t, fset, f, "")
|
||||
}
|
||||
addValues(t, fset, ty.Consts, "const")
|
||||
addValues(t, fset, ty.Vars, "var")
|
||||
}
|
||||
|
||||
for _, f := range pkg.Funcs {
|
||||
addFunc(t, fset, f, "")
|
||||
}
|
||||
addValues(t, fset, pkg.Consts, "const")
|
||||
addValues(t, fset, pkg.Vars, "var")
|
||||
}
|
||||
|
||||
index := &IdentIndex{
|
||||
ByKey: make(map[string]*IdentEntry),
|
||||
Total: len(results),
|
||||
}
|
||||
|
||||
for key, occs := range idx {
|
||||
groups := make(map[string][]string) // decl -> targets
|
||||
for _, o := range occs {
|
||||
groups[o.decl] = append(groups[o.decl], o.target)
|
||||
}
|
||||
|
||||
var gs []IdentGroup
|
||||
for decl, targets := range groups {
|
||||
sort.Strings(targets)
|
||||
gs = append(gs, IdentGroup{Decl: decl, Targets: targets})
|
||||
}
|
||||
sort.Slice(gs, func(i, j int) bool { return len(gs[i].Targets) > len(gs[j].Targets) })
|
||||
|
||||
urlKey := key.kind + "/" + key.name
|
||||
entry := &IdentEntry{
|
||||
Name: key.name,
|
||||
Kind: key.kind,
|
||||
Count: len(occs),
|
||||
Key: urlKey,
|
||||
Groups: gs,
|
||||
}
|
||||
index.Entries = append(index.Entries, entry)
|
||||
index.ByKey[urlKey] = entry
|
||||
}
|
||||
|
||||
sort.Slice(index.Entries, func(i, j int) bool {
|
||||
if index.Entries[i].Count != index.Entries[j].Count {
|
||||
return index.Entries[i].Count > index.Entries[j].Count
|
||||
}
|
||||
return index.Entries[i].Name < index.Entries[j].Name
|
||||
})
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
// resolveValueType returns the effective type string for a const/var name
|
||||
// within a GenDecl, handling iota type inheritance for constants.
|
||||
func resolveValueType(fset *token.FileSet, gd *ast.GenDecl, name string) string {
|
||||
isConst := gd.Tok == token.CONST
|
||||
var lastType ast.Expr
|
||||
for _, spec := range gd.Specs {
|
||||
vs := spec.(*ast.ValueSpec)
|
||||
if vs.Type != nil {
|
||||
lastType = vs.Type
|
||||
}
|
||||
for _, n := range vs.Names {
|
||||
if n.Name != name {
|
||||
continue
|
||||
}
|
||||
effType := vs.Type
|
||||
if effType == nil && isConst {
|
||||
effType = lastType
|
||||
}
|
||||
if effType != nil {
|
||||
return fmtNode(fset, effType)
|
||||
}
|
||||
return "(untyped)"
|
||||
}
|
||||
}
|
||||
return "(unknown)"
|
||||
}
|
||||
|
||||
// fmtNode formats an AST node to Go source. Separate from formatNode in main.go
|
||||
// to make it clear this is the server's version (same logic).
|
||||
func fmtNode(fset *token.FileSet, node ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
format.Node(&buf, fset, node)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// --- HTTP Server ---
|
||||
|
||||
func serve(addr string, results []Result) {
|
||||
index := buildIdentIndex(results)
|
||||
|
||||
byTarget := make(map[string]Result, len(results))
|
||||
for _, r := range results {
|
||||
byTarget[r.Target.Name] = r
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
data := pageData{Index: index, Results: results}
|
||||
pageTmpl.Execute(w, data)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/id/", func(w http.ResponseWriter, r *http.Request) {
|
||||
key := strings.TrimPrefix(r.URL.Path, "/id/")
|
||||
entry, ok := index.ByKey[key]
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
data := pageData{Index: index, Results: results, Selected: entry, SelectedKey: key}
|
||||
pageTmpl.Execute(w, data)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/target/", func(w http.ResponseWriter, r *http.Request) {
|
||||
name := strings.TrimPrefix(r.URL.Path, "/target/")
|
||||
res, ok := byTarget[name]
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
api := convertPackage(res.Target.Name, res.Pkg, res.Fset)
|
||||
targetTmpl.Execute(w, api)
|
||||
})
|
||||
|
||||
log.Printf("serving documentation on http://localhost%s", addr)
|
||||
log.Fatal(http.ListenAndServe(addr, mux))
|
||||
}
|
||||
|
||||
type pageData struct {
|
||||
Index *IdentIndex
|
||||
Results []Result
|
||||
Selected *IdentEntry
|
||||
SelectedKey string
|
||||
}
|
||||
|
||||
// --- Templates ---
|
||||
|
||||
var pageTmpl = template.Must(template.New("page").Funcs(template.FuncMap{
|
||||
"kindBadge": func(kind string) string {
|
||||
switch kind {
|
||||
case "type":
|
||||
return "T"
|
||||
case "func":
|
||||
return "F"
|
||||
case "method":
|
||||
return "M"
|
||||
case "const":
|
||||
return "C"
|
||||
case "var":
|
||||
return "V"
|
||||
}
|
||||
return "?"
|
||||
},
|
||||
"pre": func(s string) template.HTML {
|
||||
return template.HTML("<pre>" + template.HTMLEscapeString(s) + "</pre>")
|
||||
},
|
||||
"kindClass": func(kind string) string { return "kind-" + kind },
|
||||
}).Parse(`<!DOCTYPE html>
|
||||
<html><head>
|
||||
<title>tgdoc{{if .Selected}} — {{.Selected.Name}}{{end}}</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: system-ui, -apple-system, sans-serif; }
|
||||
.layout { display: flex; height: 100vh; }
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar { width: 320px; min-width: 320px; border-right: 1px solid #ddd; display: flex; flex-direction: column; background: #fafafa; }
|
||||
.sidebar-header { padding: 12px; border-bottom: 1px solid #ddd; }
|
||||
.sidebar-header h2 { font-size: 1.1em; margin-bottom: 8px; }
|
||||
.sidebar-header input { width: 100%; padding: 6px 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 0.9em; }
|
||||
.sidebar-header .filter-row { display: flex; gap: 4px; margin-top: 6px; flex-wrap: wrap; }
|
||||
.sidebar-header .filter-btn { font-size: 0.7em; padding: 2px 6px; border: 1px solid #ccc; border-radius: 3px; background: white; cursor: pointer; }
|
||||
.sidebar-header .filter-btn.active { background: #333; color: white; border-color: #333; }
|
||||
.ident-list { overflow-y: auto; flex: 1; }
|
||||
.ident-list a { display: flex; align-items: center; padding: 3px 12px; text-decoration: none; color: #333; font-size: 0.85em; border-left: 3px solid transparent; }
|
||||
.ident-list a:hover { background: #f0f0f0; }
|
||||
.ident-list a.active { background: #e8e8f4; border-left-color: #4444cc; }
|
||||
.ident-list .badge { display: inline-block; width: 18px; height: 18px; line-height: 18px; text-align: center; border-radius: 3px; font-size: 0.65em; font-weight: bold; margin-right: 6px; color: white; flex-shrink: 0; }
|
||||
.ident-list .count { margin-left: auto; color: #999; font-size: 0.8em; padding-left: 8px; flex-shrink: 0; }
|
||||
.ident-list .ngroups { color: salmon; font-size: 0.8em; padding-left: 2px; flex-shrink: 0; }
|
||||
.ident-list .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.kind-type .badge { background: #2196F3; }
|
||||
.kind-func .badge { background: #4CAF50; }
|
||||
.kind-method .badge { background: #9C27B0; }
|
||||
.kind-const .badge { background: #FF9800; }
|
||||
.kind-var .badge { background: #795548; }
|
||||
|
||||
/* Main content */
|
||||
main { flex: 1; overflow-y: auto; padding: 2em; }
|
||||
main h1 { font-size: 1.5em; border-bottom: 2px solid #333; padding-bottom: 0.3em; margin-bottom: 0.5em; }
|
||||
main .subtitle { color: #666; margin-bottom: 1.5em; }
|
||||
.group { margin-bottom: 1.5em; border: 1px solid #e0e0e0; border-radius: 6px; overflow: hidden; }
|
||||
.group-header { background: #f5f5f5; padding: 10px 14px; font-size: 0.9em; border-bottom: 1px solid #e0e0e0; }
|
||||
.group-header strong { font-size: 1.05em; }
|
||||
.group pre { margin: 0; padding: 14px; background: #fcfcfc; overflow-x: auto; font-size: 0.85em; border-bottom: 1px solid #eee; }
|
||||
.group .targets { padding: 10px 14px; display: flex; flex-wrap: wrap; gap: 4px; }
|
||||
.group .targets a { background: #e8e8e8; padding: 2px 8px; border-radius: 3px; font-size: 0.78em; text-decoration: none; color: #333; }
|
||||
.group .targets a:hover { background: #d0d0d0; }
|
||||
.welcome nav a { margin-right: 1em; }
|
||||
</style>
|
||||
</head><body>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h2><a href="/" style="text-decoration:none;color:inherit">tgdoc</a></h2>
|
||||
<input type="text" id="filter" placeholder="Filter identifiers..." oninput="filterList()">
|
||||
<div class="filter-row">
|
||||
<button class="filter-btn active" data-kind="all" onclick="toggleKind(this)">All</button>
|
||||
<button class="filter-btn active" data-kind="type" onclick="toggleKind(this)">T</button>
|
||||
<button class="filter-btn active" data-kind="func" onclick="toggleKind(this)">F</button>
|
||||
<button class="filter-btn active" data-kind="method" onclick="toggleKind(this)">M</button>
|
||||
<button class="filter-btn active" data-kind="const" onclick="toggleKind(this)">C</button>
|
||||
<button class="filter-btn active" data-kind="var" onclick="toggleKind(this)">V</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ident-list">
|
||||
{{range .Index.Entries}}
|
||||
<a href="/id/{{.Key}}" class="{{kindClass .Kind}}{{if eq .Key $.SelectedKey}} active{{end}}" data-name="{{.Name}}" data-kind="{{.Kind}}">
|
||||
<span class="badge">{{kindBadge .Kind}}</span>
|
||||
<span class="name">{{.Name}}</span>
|
||||
<span class="count">{{.Count}}{{if gt (len .Groups) 1}}<span class="ngroups">/{{len .Groups}}</span>{{end}}</span>
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</aside>
|
||||
<main>
|
||||
{{if .Selected}}
|
||||
<h1><span class="badge" style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:0.6em;vertical-align:middle;color:white;background:{{if eq .Selected.Kind "type"}}#2196F3{{else if eq .Selected.Kind "func"}}#4CAF50{{else if eq .Selected.Kind "method"}}#9C27B0{{else if eq .Selected.Kind "const"}}#FF9800{{else}}#795548{{end}}">{{.Selected.Kind}}</span> {{.Selected.Name}}</h1>
|
||||
<p class="subtitle">Present in {{.Selected.Count}} / {{.Index.Total}} targets
|
||||
{{if eq (len .Selected.Groups) 1}}— identical across all targets{{else}}— {{len .Selected.Groups}} distinct signatures{{end}}</p>
|
||||
|
||||
{{range .Selected.Groups}}
|
||||
<div class="group">
|
||||
<div class="group-header"><strong>{{len .Targets}} target{{if ne (len .Targets) 1}}s{{end}}</strong></div>
|
||||
{{pre .Decl}}
|
||||
<div class="targets">{{range .Targets}}<a href="/target/{{.}}">{{.}}</a> {{end}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{else}}
|
||||
<div class="welcome">
|
||||
<h1>tgdoc</h1>
|
||||
<p class="subtitle">{{len .Index.Entries}} identifiers across {{.Index.Total}} targets</p>
|
||||
<p>Select an identifier from the sidebar to see its appearances across targets.</p>
|
||||
<p style="margin-top:1em">Browse by target:</p>
|
||||
<nav style="margin-top:0.5em;column-count:3">
|
||||
{{range .Results}}<a href="/target/{{.Target.Name}}" style="display:block;padding:2px 0">{{.Target.Name}}</a>
|
||||
{{end}}
|
||||
</nav>
|
||||
</div>
|
||||
{{end}}
|
||||
</main>
|
||||
</div>
|
||||
<script>
|
||||
var activeKinds = new Set(["type","func","method","const","var"]);
|
||||
function filterList() {
|
||||
var q = document.getElementById("filter").value.toLowerCase();
|
||||
document.querySelectorAll(".ident-list a").forEach(function(a) {
|
||||
var name = a.dataset.name.toLowerCase();
|
||||
var kind = a.dataset.kind;
|
||||
var textMatch = !q || name.includes(q);
|
||||
var kindMatch = activeKinds.has(kind);
|
||||
a.style.display = (textMatch && kindMatch) ? "" : "none";
|
||||
});
|
||||
}
|
||||
function toggleKind(btn) {
|
||||
var kind = btn.dataset.kind;
|
||||
if (kind === "all") {
|
||||
var allActive = activeKinds.size === 5;
|
||||
document.querySelectorAll(".filter-btn").forEach(function(b) {
|
||||
if (allActive) { b.classList.remove("active"); activeKinds.clear(); }
|
||||
else { b.classList.add("active"); activeKinds.add(b.dataset.kind); }
|
||||
});
|
||||
if (!allActive) activeKinds.delete("all");
|
||||
} else {
|
||||
btn.classList.toggle("active");
|
||||
if (activeKinds.has(kind)) activeKinds.delete(kind); else activeKinds.add(kind);
|
||||
var allBtn = document.querySelector('[data-kind="all"]');
|
||||
if (activeKinds.size === 5) allBtn.classList.add("active"); else allBtn.classList.remove("active");
|
||||
}
|
||||
filterList();
|
||||
}
|
||||
</script>
|
||||
</body></html>`))
|
||||
|
||||
var targetTmpl = template.Must(template.New("target").Funcs(template.FuncMap{
|
||||
"pre": func(s string) template.HTML {
|
||||
return template.HTML("<pre>" + template.HTMLEscapeString(s) + "</pre>")
|
||||
},
|
||||
}).Parse(`<!DOCTYPE html>
|
||||
<html><head>
|
||||
<title>{{.Target}} — tgdoc</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; max-width: 900px; margin: 2em auto; padding: 0 1em; }
|
||||
h1 { border-bottom: 2px solid #333; padding-bottom: 0.3em; }
|
||||
h2 { margin-top: 2em; color: #444; }
|
||||
h3 { margin-top: 1.5em; }
|
||||
pre { background: #f5f5f5; padding: 0.8em; overflow-x: auto; font-size: 0.9em; border-radius: 4px; }
|
||||
.doc { color: #555; margin-bottom: 0.5em; white-space: pre-wrap; }
|
||||
.section { margin-left: 1em; }
|
||||
a { text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head><body>
|
||||
<p><a href="/">← identifier index</a></p>
|
||||
<h1>{{.Target}}</h1>
|
||||
<p>package {{.Package}}</p>
|
||||
{{if .Doc}}<div class="doc">{{.Doc}}</div>{{end}}
|
||||
|
||||
{{if .Constants}}
|
||||
<h2>Constants</h2>
|
||||
{{range .Constants}}
|
||||
{{if .Doc}}<div class="doc">{{.Doc}}</div>{{end}}
|
||||
{{pre .Decl}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .Variables}}
|
||||
<h2>Variables</h2>
|
||||
{{range .Variables}}
|
||||
{{if .Doc}}<div class="doc">{{.Doc}}</div>{{end}}
|
||||
{{pre .Decl}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .Functions}}
|
||||
<h2>Functions</h2>
|
||||
{{range .Functions}}
|
||||
<h3>func {{.Name}}</h3>
|
||||
{{if .Doc}}<div class="doc">{{.Doc}}</div>{{end}}
|
||||
{{pre .Decl}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .Types}}
|
||||
<h2>Types</h2>
|
||||
{{range .Types}}
|
||||
<h3>type {{.Name}}</h3>
|
||||
{{if .Doc}}<div class="doc">{{.Doc}}</div>{{end}}
|
||||
{{pre .Decl}}
|
||||
{{if .Constants}}<div class="section"><h4>Associated Constants</h4>
|
||||
{{range .Constants}}{{pre .Decl}}{{end}}</div>{{end}}
|
||||
{{if .Variables}}<div class="section"><h4>Associated Variables</h4>
|
||||
{{range .Variables}}{{pre .Decl}}{{end}}</div>{{end}}
|
||||
{{if .Functions}}<div class="section"><h4>Constructors</h4>
|
||||
{{range .Functions}}
|
||||
{{if .Doc}}<div class="doc">{{.Doc}}</div>{{end}}
|
||||
{{pre .Decl}}
|
||||
{{end}}</div>{{end}}
|
||||
{{if .Methods}}<div class="section"><h4>Methods</h4>
|
||||
{{range .Methods}}
|
||||
{{if .Doc}}<div class="doc">{{.Doc}}</div>{{end}}
|
||||
{{pre .Decl}}
|
||||
{{end}}</div>{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
</body></html>`))
|
||||
@@ -0,0 +1,168 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// targetSpec is the minimal subset of TinyGo's target JSON we need.
|
||||
type targetSpec struct {
|
||||
Inherits []string `json:"inherits,omitempty"`
|
||||
BuildTags []string `json:"build-tags,omitempty"`
|
||||
GOOS string `json:"goos,omitempty"`
|
||||
GOARCH string `json:"goarch,omitempty"`
|
||||
GC string `json:"gc,omitempty"`
|
||||
Scheduler string `json:"scheduler,omitempty"`
|
||||
Serial string `json:"serial,omitempty"`
|
||||
FlashMethod string `json:"flash-method,omitempty"`
|
||||
FlashCommand string `json:"flash-command,omitempty"`
|
||||
Emulator string `json:"emulator,omitempty"`
|
||||
}
|
||||
|
||||
// Target is a resolved target with inheritance applied.
|
||||
type Target struct {
|
||||
Name string
|
||||
BuildTags []string
|
||||
GOOS string
|
||||
GOARCH string
|
||||
GC string
|
||||
Scheduler string
|
||||
Serial string
|
||||
}
|
||||
|
||||
// override merges src properties into dst following TinyGo's semantics:
|
||||
// strings override if non-empty, slices append with dedup.
|
||||
func (dst *targetSpec) override(src *targetSpec) {
|
||||
if src.GOOS != "" {
|
||||
dst.GOOS = src.GOOS
|
||||
}
|
||||
if src.GOARCH != "" {
|
||||
dst.GOARCH = src.GOARCH
|
||||
}
|
||||
if src.GC != "" {
|
||||
dst.GC = src.GC
|
||||
}
|
||||
if src.Scheduler != "" {
|
||||
dst.Scheduler = src.Scheduler
|
||||
}
|
||||
if src.Serial != "" {
|
||||
dst.Serial = src.Serial
|
||||
}
|
||||
if src.FlashMethod != "" {
|
||||
dst.FlashMethod = src.FlashMethod
|
||||
}
|
||||
if src.FlashCommand != "" {
|
||||
dst.FlashCommand = src.FlashCommand
|
||||
}
|
||||
if src.Emulator != "" {
|
||||
dst.Emulator = src.Emulator
|
||||
}
|
||||
dst.BuildTags = appendUnique(dst.BuildTags, src.BuildTags...)
|
||||
}
|
||||
|
||||
func appendUnique(dst []string, src ...string) []string {
|
||||
seen := make(map[string]bool, len(dst))
|
||||
for _, s := range dst {
|
||||
seen[s] = true
|
||||
}
|
||||
for _, s := range src {
|
||||
if !seen[s] {
|
||||
dst = append(dst, s)
|
||||
seen[s] = true
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func loadRawTargets(dir string) (map[string]*targetSpec, error) {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
specs := make(map[string]*targetSpec)
|
||||
for _, e := range entries {
|
||||
if !e.Type().IsRegular() || !strings.HasSuffix(e.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSuffix(e.Name(), ".json")
|
||||
data, err := os.ReadFile(filepath.Join(dir, e.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var spec targetSpec
|
||||
if err := json.Unmarshal(data, &spec); err != nil {
|
||||
return nil, fmt.Errorf("parsing %s: %w", e.Name(), err)
|
||||
}
|
||||
specs[name] = &spec
|
||||
}
|
||||
return specs, nil
|
||||
}
|
||||
|
||||
func resolveSpec(name string, raw map[string]*targetSpec, cache map[string]*targetSpec, resolving map[string]bool) (*targetSpec, error) {
|
||||
if cached, ok := cache[name]; ok {
|
||||
return cached, nil
|
||||
}
|
||||
if resolving[name] {
|
||||
return nil, fmt.Errorf("circular inheritance: %s", name)
|
||||
}
|
||||
spec, ok := raw[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown target: %s", name)
|
||||
}
|
||||
|
||||
resolving[name] = true
|
||||
defer delete(resolving, name)
|
||||
|
||||
result := &targetSpec{}
|
||||
for _, parent := range spec.Inherits {
|
||||
resolved, err := resolveSpec(parent, raw, cache, resolving)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolving %s parent %s: %w", name, parent, err)
|
||||
}
|
||||
result.override(resolved)
|
||||
}
|
||||
result.override(spec)
|
||||
|
||||
cache[name] = result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// LoadTargets reads all target JSONs from dir, resolves inheritance,
|
||||
// and returns the resolved targets. If includeBase is false, targets
|
||||
// without flash/emulator configuration are excluded (base/parent targets).
|
||||
func LoadTargets(dir string, includeBase bool) ([]Target, error) {
|
||||
raw, err := loadRawTargets(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cache := make(map[string]*targetSpec)
|
||||
resolving := make(map[string]bool)
|
||||
|
||||
var targets []Target
|
||||
for name := range raw {
|
||||
resolved, err := resolveSpec(name, raw, cache, resolving)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !includeBase && resolved.FlashMethod == "" && resolved.FlashCommand == "" && resolved.Emulator == "" {
|
||||
continue
|
||||
}
|
||||
targets = append(targets, Target{
|
||||
Name: name,
|
||||
BuildTags: resolved.BuildTags,
|
||||
GOOS: resolved.GOOS,
|
||||
GOARCH: resolved.GOARCH,
|
||||
GC: resolved.GC,
|
||||
Scheduler: resolved.Scheduler,
|
||||
Serial: resolved.Serial,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(targets, func(i, j int) bool { return targets[i].Name < targets[j].Name })
|
||||
return targets, nil
|
||||
}
|
||||
Reference in New Issue
Block a user