mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-27 07:08:42 +00:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e18433a46 | |||
| c5cb58c9b8 | |||
| ef05ba2ea1 | |||
| 25f5f76a48 | |||
| a5292ecb0f | |||
| ab087d6f27 | |||
| fd1d10c9b7 | |||
| 5c37d1ba61 | |||
| 610dd19c40 | |||
| 44ca224056 | |||
| c1cddffbe9 | |||
| 9bbad6700b | |||
| 24f965425d | |||
| b6b723aeec | |||
| 934d5f41bf | |||
| 4b0e858964 | |||
| 32378537b8 | |||
| f5b2a08b15 | |||
| f23e18a8de | |||
| 66d7099c96 | |||
| bef0dc5d21 | |||
| e79cdc1122 | |||
| f0256cab18 | |||
| 5d8e071bfb | |||
| a0069b6282 | |||
| 707d37a4c1 | |||
| 1876b65b18 | |||
| 1fe934e8a8 | |||
| 8bd2233b57 | |||
| ff58c50118 | |||
| ca36fba7e4 | |||
| 743bf45e00 | |||
| 9bc5d21185 | |||
| c8b56bd161 | |||
| fe20079300 | |||
| 068d7c1b44 | |||
| 42fed60e61 | |||
| 53c1ccaa42 | |||
| 72f667cac9 | |||
| 4fff472808 | |||
| cfd74c2954 | |||
| 9bcca974ba | |||
| a40586d092 | |||
| bc9708f51a | |||
| 8ef36ed939 | |||
| c7f62bae9b | |||
| 1b741bf2b7 | |||
| 266d70816f | |||
| 27cedf09e8 | |||
| db9f1182f5 | |||
| f7fd5cf2d8 | |||
| 8d9c9a16e3 | |||
| 82deccac40 | |||
| 0e540dc3da | |||
| 897f2bc5fd | |||
| 750b0e5c18 | |||
| a65ac105cc |
@@ -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 }}
|
||||
|
||||
@@ -66,45 +66,3 @@ jobs:
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Trigger Drivers repo build on Github Actions
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/tinygo-org/drivers/actions/workflows/build.yml/dispatches \
|
||||
-d '{"ref": "dev"}'
|
||||
- name: Trigger Bluetooth repo build on Github Actions
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/tinygo-org/bluetooth/actions/workflows/linux.yml/dispatches \
|
||||
-d '{"ref": "dev"}'
|
||||
- name: Trigger TinyFS repo build on Github Actions
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/tinygo-org/tinyfs/actions/workflows/build.yml/dispatches \
|
||||
-d '{"ref": "dev"}'
|
||||
- name: Trigger TinyFont repo build on Github Actions
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/tinygo-org/tinyfont/actions/workflows/build.yml/dispatches \
|
||||
-d '{"ref": "dev"}'
|
||||
- name: Trigger TinyDraw repo build on Github Actions
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/tinygo-org/tinydraw/actions/workflows/build.yml/dispatches \
|
||||
-d '{"ref": "dev"}'
|
||||
- name: Trigger TinyTerm repo build on Github Actions
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/tinygo-org/tinyterm/actions/workflows/build.yml/dispatches \
|
||||
-d '{"ref": "dev"}'
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
url = https://github.com/WebAssembly/wasi-libc
|
||||
[submodule "lib/picolibc"]
|
||||
path = lib/picolibc
|
||||
url = https://github.com/keith-packard/picolibc.git
|
||||
url = https://github.com/picolibc/picolibc.git
|
||||
[submodule "lib/stm32-svd"]
|
||||
path = lib/stm32-svd
|
||||
url = https://github.com/tinygo-org/stm32-svd
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
0.40.1
|
||||
---
|
||||
* **machine**
|
||||
- nrf: fix flash writes when SoftDevice is enabled
|
||||
* **runtime**
|
||||
- runtime: avoid fixed math/rand sequence on RP2040/RP2350 (#5124)
|
||||
- runtime: add calls to initRand() during run() for all schedulers
|
||||
- runtime: call initRand() before initHeap() during initialization
|
||||
- runtime: use rand_hwrng hardwareRand for RP2040/RP2350 (#5135)
|
||||
* **libs**
|
||||
- picolibc: use updated location for git repo
|
||||
|
||||
0.40.0
|
||||
---
|
||||
* **general**
|
||||
|
||||
+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
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
Copyright (c) 2018-2025 The TinyGo Authors. All rights reserved.
|
||||
Copyright (c) 2018-2026 The TinyGo Authors. All rights reserved.
|
||||
|
||||
TinyGo includes portions of the Go standard library.
|
||||
Copyright (c) 2009-2024 The Go Authors. All rights reserved.
|
||||
Copyright 2009 The Go Authors. All rights reserved.
|
||||
See https://github.com/golang/go/blob/master/LICENSE for license information.
|
||||
|
||||
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
|
||||
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) {
|
||||
// microcontrollers
|
||||
{"hifive1b", "examples/echo", 3668, 280, 0, 2244},
|
||||
{"microbit", "examples/serial", 2694, 342, 8, 2248},
|
||||
{"wioterminal", "examples/pininterrupt", 7187, 1489, 116, 6888},
|
||||
{"wioterminal", "examples/pininterrupt", 6837, 1491, 120, 6888},
|
||||
|
||||
// TODO: also check wasm. Right now this is difficult, because
|
||||
// wasm binaries are run through wasm-opt and therefore the
|
||||
|
||||
+92
-17
@@ -1599,7 +1599,8 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
|
||||
elemsLen := b.CreateExtractValue(elems, 1, "append.elemsLen")
|
||||
elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem())
|
||||
elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false)
|
||||
result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcBuf, elemsBuf, srcLen, srcCap, elemsLen, elemSize}, "append.new")
|
||||
elemLayout := b.createObjectLayout(elemType, pos)
|
||||
result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcBuf, elemsBuf, srcLen, srcCap, elemsLen, elemSize, elemLayout}, "append.new")
|
||||
newPtr := b.CreateExtractValue(result, 0, "append.newPtr")
|
||||
newLen := b.CreateExtractValue(result, 1, "append.newLen")
|
||||
newCap := b.CreateExtractValue(result, 2, "append.newCap")
|
||||
@@ -1681,13 +1682,41 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
|
||||
case "copy":
|
||||
dst := argValues[0]
|
||||
src := argValues[1]
|
||||
// Fetch the lengths.
|
||||
dstLen := b.CreateExtractValue(dst, 1, "copy.dstLen")
|
||||
srcLen := b.CreateExtractValue(src, 1, "copy.srcLen")
|
||||
dstBuf := b.CreateExtractValue(dst, 0, "copy.dstArray")
|
||||
srcBuf := b.CreateExtractValue(src, 0, "copy.srcArray")
|
||||
// Find the minimum of the lengths.
|
||||
minFuncName := "llvm.umin.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
|
||||
minFunc := b.mod.NamedFunction(minFuncName)
|
||||
if minFunc.IsNil() {
|
||||
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType, b.uintptrType}, false)
|
||||
minFunc = llvm.AddFunction(b.mod, minFuncName, fnType)
|
||||
}
|
||||
minLen := b.CreateCall(minFunc.GlobalValueType(), minFunc, []llvm.Value{dstLen, srcLen}, "copy.n")
|
||||
// Multiply the length by the element size.
|
||||
elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem())
|
||||
elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false)
|
||||
return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil
|
||||
// NOTE: This is also NSW when uintptr is int, but we can only choose one through the C API?
|
||||
size := b.CreateNUWMul(minLen, elemSize, "copy.size")
|
||||
// Fetch the pointers.
|
||||
dstBuf := b.CreateExtractValue(dst, 0, "copy.dstPtr")
|
||||
srcBuf := b.CreateExtractValue(src, 0, "copy.srcPtr")
|
||||
// Create a memcpy.
|
||||
call := b.createMemCopy("memmove", dstBuf, srcBuf, size)
|
||||
align := b.targetData.ABITypeAlignment(elemType)
|
||||
if align > 1 {
|
||||
// Apply the type's alignment to the arguments.
|
||||
// LLVM sometimes turns constant-length moves into loads and stores.
|
||||
// It may use this alignment for the created loads and stores.
|
||||
alignAttr := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align))
|
||||
call.AddCallSiteAttribute(1, alignAttr)
|
||||
call.AddCallSiteAttribute(2, alignAttr)
|
||||
}
|
||||
// Extend and return the copied length.
|
||||
if b.targetData.TypeAllocSize(minLen.Type()) < b.targetData.TypeAllocSize(b.intType) {
|
||||
minLen = b.CreateZExt(minLen, b.intType, "copy.n.zext")
|
||||
}
|
||||
return minLen, nil
|
||||
case "delete":
|
||||
m := argValues[0]
|
||||
key := argValues[1]
|
||||
@@ -1715,20 +1744,66 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
|
||||
return llvmLen, nil
|
||||
case "min", "max":
|
||||
// min and max builtins, added in Go 1.21.
|
||||
// We can simply reuse the existing binop comparison code, which has all
|
||||
// the edge cases figured out already.
|
||||
tok := token.LSS
|
||||
if callName == "max" {
|
||||
tok = token.GTR
|
||||
}
|
||||
result := argValues[0]
|
||||
typ := argTypes[0]
|
||||
for _, arg := range argValues[1:] {
|
||||
cmp, err := b.createBinOp(tok, typ, typ, result, arg, pos)
|
||||
if err != nil {
|
||||
return result, err
|
||||
// Find the corresponding intrinsic name.
|
||||
ty := argTypes[0].Underlying().(*types.Basic)
|
||||
llvmType := b.getLLVMType(ty)
|
||||
info := ty.Info()
|
||||
var prefix, delimeter, typeName string
|
||||
if info&types.IsInteger != 0 {
|
||||
// This is an integer value.
|
||||
// Use the LLVM int min/max intrinsics.
|
||||
prefix = "llvm.s"
|
||||
if info&types.IsUnsigned != 0 {
|
||||
prefix = "llvm.u"
|
||||
}
|
||||
result = b.CreateSelect(cmp, result, arg, "")
|
||||
delimeter = ".i"
|
||||
typeName = strconv.Itoa(llvmType.IntTypeWidth())
|
||||
} else {
|
||||
switch ty.Kind() {
|
||||
case types.String:
|
||||
// Strings do not have an equivalent intrinsic.
|
||||
// Implement with compares and selects.
|
||||
tok := token.LSS
|
||||
if callName == "max" {
|
||||
tok = token.GTR
|
||||
}
|
||||
result := argValues[0]
|
||||
typ := argTypes[0]
|
||||
for _, arg := range argValues[1:] {
|
||||
cmp, err := b.createBinOp(tok, typ, typ, result, arg, pos)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result = b.CreateSelect(cmp, result, arg, "")
|
||||
}
|
||||
return result, nil
|
||||
case types.Float32:
|
||||
typeName = "f32"
|
||||
case types.Float64:
|
||||
typeName = "f64"
|
||||
default:
|
||||
return llvm.Value{}, b.makeError(pos, "todo: min/max: unknown type")
|
||||
}
|
||||
// There are a few edge cases with floating point min/max:
|
||||
// min(-0.0, +0.0) = -0.0
|
||||
// min(NaN, number) = NaN
|
||||
// The llvm.minimum.*/llvm.maximum.* intrinsics match this behavior.
|
||||
// Neither Go nor LLVM defines the bit representation of resulting NaNs.
|
||||
prefix = "llvm."
|
||||
delimeter = "imum."
|
||||
}
|
||||
intrinsicName := prefix + callName + delimeter + typeName
|
||||
// Find or create the intrinsic.
|
||||
llvmFn := b.mod.NamedFunction(intrinsicName)
|
||||
if llvmFn.IsNil() {
|
||||
fnType := llvm.FunctionType(llvmType, []llvm.Type{llvmType, llvmType}, false)
|
||||
llvmFn = llvm.AddFunction(b.mod, intrinsicName, fnType)
|
||||
}
|
||||
// Call the intrinsic repeatedly to merge the arguments.
|
||||
callType := llvmFn.GlobalValueType()
|
||||
result := argValues[0]
|
||||
for _, arg := range argValues[1:] {
|
||||
result = b.CreateCall(callType, llvmFn, []llvm.Value{result, arg}, "")
|
||||
}
|
||||
return result, nil
|
||||
case "panic":
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+13
-8
@@ -50,19 +50,24 @@ func (b *builder) defineIntrinsicFunction() {
|
||||
// and will otherwise be lowered to regular libc memcpy/memmove calls.
|
||||
func (b *builder) createMemoryCopyImpl() {
|
||||
b.createFunctionStart(true)
|
||||
fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
|
||||
params := b.fn.Params[0:3]
|
||||
b.createMemCopy(
|
||||
b.fn.Name(),
|
||||
b.getValue(params[0], getPos(b.fn)),
|
||||
b.getValue(params[1], getPos(b.fn)),
|
||||
b.getValue(params[2], getPos(b.fn)),
|
||||
)
|
||||
b.CreateRetVoid()
|
||||
}
|
||||
|
||||
func (b *builder) createMemCopy(kind string, dst, src, len llvm.Value) llvm.Value {
|
||||
fnName := "llvm." + kind + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
|
||||
llvmFn := b.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType, b.dataPtrType, b.uintptrType, b.ctx.Int1Type()}, false)
|
||||
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
|
||||
}
|
||||
var params []llvm.Value
|
||||
for _, param := range b.fn.Params {
|
||||
params = append(params, b.getValue(param, getPos(b.fn)))
|
||||
}
|
||||
params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false))
|
||||
b.CreateCall(llvmFn.GlobalValueType(), llvmFn, params, "")
|
||||
b.CreateRetVoid()
|
||||
return b.CreateCall(llvmFn.GlobalValueType(), llvmFn, []llvm.Value{dst, src, len, llvm.ConstInt(b.ctx.Int1Type(), 0, false)}, "")
|
||||
}
|
||||
|
||||
// createMemoryZeroImpl creates calls to llvm.memset.* to zero a block of
|
||||
|
||||
+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")
|
||||
|
||||
@@ -184,12 +184,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
// be modified.
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "runtime.sliceCopy":
|
||||
// Copying a slice won't capture any of the parameters.
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.stringFromBytes":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
Vendored
+33
-27
@@ -22,6 +22,9 @@ entry:
|
||||
ret i32 %a
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i32 @llvm.smin.i32(i32, i32) #3
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.min2(i32 %a, i32 %b, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
@@ -53,6 +56,9 @@ entry:
|
||||
ret i8 %0
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i8 @llvm.umin.i8(i8, i8) #3
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.minUnsigned(i32 %a, i32 %b, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
@@ -60,22 +66,29 @@ entry:
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i32 @llvm.umin.i32(i32, i32) #3
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden float @main.minFloat32(float %a, float %b, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%0 = fcmp olt float %a, %b
|
||||
%1 = select i1 %0, float %a, float %b
|
||||
ret float %1
|
||||
%0 = call float @llvm.minimum.f32(float %a, float %b)
|
||||
ret float %0
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare float @llvm.minimum.f32(float, float) #3
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden double @main.minFloat64(double %a, double %b, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%0 = fcmp olt double %a, %b
|
||||
%1 = select i1 %0, double %a, double %b
|
||||
ret double %1
|
||||
%0 = call double @llvm.minimum.f64(double %a, double %b)
|
||||
ret double %0
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare double @llvm.minimum.f64(double, double) #3
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden %runtime._string @main.minString(ptr readonly %a.data, i32 %a.len, ptr readonly %b.data, i32 %b.len, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
@@ -100,6 +113,9 @@ entry:
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i32 @llvm.smax.i32(i32, i32) #3
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.maxUint(i32 %a, i32 %b, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
@@ -107,14 +123,19 @@ entry:
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i32 @llvm.umax.i32(i32, i32) #3
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden float @main.maxFloat32(float %a, float %b, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%0 = fcmp ogt float %a, %b
|
||||
%1 = select i1 %0, float %a, float %b
|
||||
ret float %1
|
||||
%0 = call float @llvm.maximum.f32(float %a, float %b)
|
||||
ret float %0
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare float @llvm.maximum.f32(float, float) #3
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden %runtime._string @main.maxString(ptr readonly %a.data, i32 %a.len, ptr readonly %b.data, i32 %b.len, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
@@ -139,7 +160,7 @@ entry:
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write)
|
||||
declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #3
|
||||
declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #4
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.clearZeroSizedSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 {
|
||||
@@ -156,24 +177,9 @@ entry:
|
||||
|
||||
declare void @runtime.hashmapClear(ptr dereferenceable_or_null(40), ptr) #1
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i32 @llvm.smin.i32(i32, i32) #4
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i8 @llvm.umin.i8(i8, i8) #4
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i32 @llvm.umin.i32(i32, i32) #4
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i32 @llvm.smax.i32(i32, i32) #4
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i32 @llvm.umax.i32(i32, i32) #4
|
||||
|
||||
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #3 = { nocallback nofree nounwind willreturn memory(argmem: write) }
|
||||
attributes #4 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
|
||||
attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
|
||||
attributes #4 = { nocallback nofree nounwind willreturn memory(argmem: write) }
|
||||
attributes #5 = { nounwind }
|
||||
|
||||
+35
-28
@@ -17,8 +17,8 @@ entry:
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.regularFunctionGoroutine(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr undef) #9
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 %stacksize, ptr undef) #9
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr undef) #11
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 %stacksize, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ declare void @main.regularFunction(i32, ptr) #2
|
||||
define linkonce_odr void @"main.regularFunction$gowrapper"(ptr %0) unnamed_addr #3 {
|
||||
entry:
|
||||
%unpack.int = ptrtoint ptr %0 to i32
|
||||
call void @main.regularFunction(i32 %unpack.int, ptr undef) #9
|
||||
call void @main.regularFunction(i32 %unpack.int, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ declare void @"internal/task.start"(i32, ptr, i32, ptr) #2
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.inlineFunctionGoroutine(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr undef) #9
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 %stacksize, ptr undef) #9
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr undef) #11
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 %stacksize, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -61,18 +61,18 @@ entry:
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
%n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #11
|
||||
store i32 3, ptr %n, align 4
|
||||
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9
|
||||
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #11
|
||||
store i32 5, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
store ptr %n, ptr %1, align 4
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr undef) #9
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr undef) #11
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #11
|
||||
%2 = load i32, ptr %n, align 4
|
||||
call void @runtime.printlock(ptr undef) #9
|
||||
call void @runtime.printint32(i32 %2, ptr undef) #9
|
||||
call void @runtime.printunlock(ptr undef) #9
|
||||
call void @runtime.printlock(ptr undef) #11
|
||||
call void @runtime.printint32(i32 %2, ptr undef) #11
|
||||
call void @runtime.printunlock(ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -102,14 +102,14 @@ declare void @runtime.printunlock(ptr) #2
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9
|
||||
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #11
|
||||
store i32 5, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
store ptr %fn.context, ptr %1, align 4
|
||||
%2 = getelementptr inbounds nuw i8, ptr %0, i32 8
|
||||
store ptr %fn.funcptr, ptr %2, align 4
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr undef) #9
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr undef) #11
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ entry:
|
||||
%3 = load ptr, ptr %2, align 4
|
||||
%4 = getelementptr inbounds nuw i8, ptr %0, i32 8
|
||||
%5 = load ptr, ptr %4, align 4
|
||||
call void %5(i32 %1, ptr %3) #9
|
||||
call void %5(i32 %1, ptr %3) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -134,16 +134,21 @@ entry:
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.copyBuiltinGoroutine(ptr %dst.data, i32 %dst.len, i32 %dst.cap, ptr %src.data, i32 %src.len, i32 %src.cap, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%copy.n = call i32 @runtime.sliceCopy(ptr %dst.data, ptr %src.data, i32 %dst.len, i32 %src.len, i32 1, ptr undef) #9
|
||||
%copy.n = call i32 @llvm.umin.i32(i32 %dst.len, i32 %src.len)
|
||||
call void @llvm.memmove.p0.p0.i32(ptr align 1 %dst.data, ptr align 1 %src.data, i32 %copy.n, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #2
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i32 @llvm.umin.i32(i32, i32) #7
|
||||
|
||||
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.memmove.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg) #8
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
call void @runtime.chanClose(ptr %ch, ptr undef) #9
|
||||
call void @runtime.chanClose(ptr %ch, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -152,7 +157,7 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #2
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9
|
||||
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #11
|
||||
store ptr %itf.value, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
store ptr @"main$string", ptr %1, align 4
|
||||
@@ -160,15 +165,15 @@ entry:
|
||||
store i32 4, ptr %2, align 4
|
||||
%3 = getelementptr inbounds nuw i8, ptr %0, i32 12
|
||||
store ptr %itf.typecode, ptr %3, align 4
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr undef) #9
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr undef) #11
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #7
|
||||
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #9
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #8 {
|
||||
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #10 {
|
||||
entry:
|
||||
%1 = load ptr, ptr %0, align 4
|
||||
%2 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
@@ -177,7 +182,7 @@ entry:
|
||||
%5 = load i32, ptr %4, align 4
|
||||
%6 = getelementptr inbounds nuw i8, ptr %0, i32 12
|
||||
%7 = load ptr, ptr %6, align 4
|
||||
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #9
|
||||
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -188,6 +193,8 @@ attributes #3 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb
|
||||
attributes #4 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" }
|
||||
attributes #5 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" }
|
||||
attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" }
|
||||
attributes #7 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
|
||||
attributes #8 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
|
||||
attributes #9 = { nounwind }
|
||||
attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
|
||||
attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
|
||||
attributes #9 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
|
||||
attributes #10 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
|
||||
attributes #11 = { nounwind }
|
||||
|
||||
+41
-34
@@ -19,7 +19,7 @@ entry:
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.regularFunctionGoroutine(ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #9
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ declare void @runtime.deadlock(ptr) #1
|
||||
define linkonce_odr void @"main.regularFunction$gowrapper"(ptr %0) unnamed_addr #3 {
|
||||
entry:
|
||||
%unpack.int = ptrtoint ptr %0 to i32
|
||||
call void @main.regularFunction(i32 %unpack.int, ptr undef) #9
|
||||
call void @runtime.deadlock(ptr undef) #9
|
||||
call void @main.regularFunction(i32 %unpack.int, ptr undef) #11
|
||||
call void @runtime.deadlock(ptr undef) #11
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ declare void @"internal/task.start"(i32, ptr, i32, ptr) #1
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.inlineFunctionGoroutine(ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #9
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(ptr %0) unn
|
||||
entry:
|
||||
%unpack.int = ptrtoint ptr %0 to i32
|
||||
call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, ptr undef)
|
||||
call void @runtime.deadlock(ptr undef) #9
|
||||
call void @runtime.deadlock(ptr undef) #11
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -64,21 +64,21 @@ entry:
|
||||
define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #9
|
||||
%n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #11
|
||||
call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #11
|
||||
store i32 3, ptr %n, align 4
|
||||
call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull @"main.closureFunctionGoroutine$1", ptr nonnull %stackalloc, ptr undef) #9
|
||||
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #11
|
||||
call void @runtime.trackPointer(ptr nonnull @"main.closureFunctionGoroutine$1", ptr nonnull %stackalloc, ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #11
|
||||
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
|
||||
store i32 5, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
store ptr %n, ptr %1, align 4
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #11
|
||||
%2 = load i32, ptr %n, align 4
|
||||
call void @runtime.printlock(ptr undef) #9
|
||||
call void @runtime.printint32(i32 %2, ptr undef) #9
|
||||
call void @runtime.printunlock(ptr undef) #9
|
||||
call void @runtime.printlock(ptr undef) #11
|
||||
call void @runtime.printint32(i32 %2, ptr undef) #11
|
||||
call void @runtime.printunlock(ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ entry:
|
||||
%2 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
%3 = load ptr, ptr %2, align 4
|
||||
call void @"main.closureFunctionGoroutine$1"(i32 %1, ptr %3)
|
||||
call void @runtime.deadlock(ptr undef) #9
|
||||
call void @runtime.deadlock(ptr undef) #11
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -110,14 +110,14 @@ declare void @runtime.printunlock(ptr) #1
|
||||
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9
|
||||
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #11
|
||||
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
|
||||
store i32 5, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
store ptr %fn.context, ptr %1, align 4
|
||||
%2 = getelementptr inbounds nuw i8, ptr %0, i32 8
|
||||
store ptr %fn.funcptr, ptr %2, align 4
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 65536, ptr undef) #9
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 65536, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -129,8 +129,8 @@ entry:
|
||||
%3 = load ptr, ptr %2, align 4
|
||||
%4 = getelementptr inbounds nuw i8, ptr %0, i32 8
|
||||
%5 = load ptr, ptr %4, align 4
|
||||
call void %5(i32 %1, ptr %3) #9
|
||||
call void @runtime.deadlock(ptr undef) #9
|
||||
call void %5(i32 %1, ptr %3) #11
|
||||
call void @runtime.deadlock(ptr undef) #11
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -143,16 +143,21 @@ entry:
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.copyBuiltinGoroutine(ptr %dst.data, i32 %dst.len, i32 %dst.cap, ptr %src.data, i32 %src.len, i32 %src.cap, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%copy.n = call i32 @runtime.sliceCopy(ptr %dst.data, ptr %src.data, i32 %dst.len, i32 %src.len, i32 1, ptr undef) #9
|
||||
%copy.n = call i32 @llvm.umin.i32(i32 %dst.len, i32 %src.len)
|
||||
call void @llvm.memmove.p0.p0.i32(ptr align 1 %dst.data, ptr align 1 %src.data, i32 %copy.n, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #1
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i32 @llvm.umin.i32(i32, i32) #7
|
||||
|
||||
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.memmove.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg) #8
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
call void @runtime.chanClose(ptr %ch, ptr undef) #9
|
||||
call void @runtime.chanClose(ptr %ch, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -162,8 +167,8 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1
|
||||
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9
|
||||
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9
|
||||
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #11
|
||||
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
|
||||
store ptr %itf.value, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
store ptr @"main$string", ptr %1, align 4
|
||||
@@ -171,14 +176,14 @@ entry:
|
||||
store i32 4, ptr %2, align 4
|
||||
%3 = getelementptr inbounds nuw i8, ptr %0, i32 12
|
||||
store ptr %itf.typecode, ptr %3, align 4
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #11
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #7
|
||||
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #9
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #8 {
|
||||
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #10 {
|
||||
entry:
|
||||
%1 = load ptr, ptr %0, align 4
|
||||
%2 = getelementptr inbounds nuw i8, ptr %0, i32 4
|
||||
@@ -187,8 +192,8 @@ entry:
|
||||
%5 = load i32, ptr %4, align 4
|
||||
%6 = getelementptr inbounds nuw i8, ptr %0, i32 12
|
||||
%7 = load ptr, ptr %6, align 4
|
||||
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #9
|
||||
call void @runtime.deadlock(ptr undef) #9
|
||||
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #11
|
||||
call void @runtime.deadlock(ptr undef) #11
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -199,6 +204,8 @@ attributes #3 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+cal
|
||||
attributes #4 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" }
|
||||
attributes #5 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" }
|
||||
attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper" }
|
||||
attributes #7 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
|
||||
attributes #8 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
|
||||
attributes #9 = { nounwind }
|
||||
attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
|
||||
attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
|
||||
attributes #9 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
|
||||
attributes #10 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
|
||||
attributes #11 = { nounwind }
|
||||
|
||||
Vendored
+44
-36
@@ -38,7 +38,7 @@ lookup.next: ; preds = %entry
|
||||
ret i32 %1
|
||||
|
||||
lookup.throw: ; preds = %entry
|
||||
call void @runtime.lookupPanic(ptr undef) #3
|
||||
call void @runtime.lookupPanic(ptr undef) #5
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -48,49 +48,55 @@ declare void @runtime.lookupPanic(ptr) #1
|
||||
define hidden { ptr, i32, i32 } @main.sliceAppendValues(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%varargs = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %varargs, ptr nonnull %stackalloc, ptr undef) #3
|
||||
%varargs = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5
|
||||
call void @runtime.trackPointer(ptr nonnull %varargs, ptr nonnull %stackalloc, ptr undef) #5
|
||||
store i32 1, ptr %varargs, align 4
|
||||
%0 = getelementptr inbounds nuw i8, ptr %varargs, i32 4
|
||||
store i32 2, ptr %0, align 4
|
||||
%1 = getelementptr inbounds nuw i8, ptr %varargs, i32 8
|
||||
store i32 3, ptr %1, align 4
|
||||
%append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, ptr undef) #3
|
||||
%append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5
|
||||
%append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0
|
||||
%append.newLen = extractvalue { ptr, i32, i32 } %append.new, 1
|
||||
%append.newCap = extractvalue { ptr, i32, i32 } %append.new, 2
|
||||
%2 = insertvalue { ptr, i32, i32 } undef, ptr %append.newPtr, 0
|
||||
%3 = insertvalue { ptr, i32, i32 } %2, i32 %append.newLen, 1
|
||||
%4 = insertvalue { ptr, i32, i32 } %3, i32 %append.newCap, 2
|
||||
call void @runtime.trackPointer(ptr %append.newPtr, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr %append.newPtr, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret { ptr, i32, i32 } %4
|
||||
}
|
||||
|
||||
declare { ptr, i32, i32 } @runtime.sliceAppend(ptr, ptr nocapture readonly, i32, i32, i32, i32, ptr) #1
|
||||
declare { ptr, i32, i32 } @runtime.sliceAppend(ptr, ptr nocapture readonly, i32, i32, i32, i32, ptr, ptr) #1
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden { ptr, i32, i32 } @main.sliceAppendSlice(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %added.data, i32 %added.len, i32 %added.cap, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr %added.data, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, ptr undef) #3
|
||||
%append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr %added.data, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5
|
||||
%append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0
|
||||
%append.newLen = extractvalue { ptr, i32, i32 } %append.new, 1
|
||||
%append.newCap = extractvalue { ptr, i32, i32 } %append.new, 2
|
||||
%0 = insertvalue { ptr, i32, i32 } undef, ptr %append.newPtr, 0
|
||||
%1 = insertvalue { ptr, i32, i32 } %0, i32 %append.newLen, 1
|
||||
%2 = insertvalue { ptr, i32, i32 } %1, i32 %append.newCap, 2
|
||||
call void @runtime.trackPointer(ptr %append.newPtr, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr %append.newPtr, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret { ptr, i32, i32 } %2
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.sliceCopy(ptr %dst.data, i32 %dst.len, i32 %dst.cap, ptr %src.data, i32 %src.len, i32 %src.cap, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%copy.n = call i32 @runtime.sliceCopy(ptr %dst.data, ptr %src.data, i32 %dst.len, i32 %src.len, i32 4, ptr undef) #3
|
||||
%copy.n = call i32 @llvm.umin.i32(i32 %dst.len, i32 %src.len)
|
||||
%copy.size = shl nuw i32 %copy.n, 2
|
||||
call void @llvm.memmove.p0.p0.i32(ptr align 4 %dst.data, ptr align 4 %src.data, i32 %copy.size, i1 false)
|
||||
ret i32 %copy.n
|
||||
}
|
||||
|
||||
declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #1
|
||||
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
|
||||
declare i32 @llvm.umin.i32(i32, i32) #3
|
||||
|
||||
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.memmove.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg) #4
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden { ptr, i32, i32 } @main.makeByteSlice(i32 %len, ptr %context) unnamed_addr #2 {
|
||||
@@ -100,15 +106,15 @@ entry:
|
||||
br i1 %slice.maxcap, label %slice.throw, label %slice.next
|
||||
|
||||
slice.next: ; preds = %entry
|
||||
%makeslice.buf = call align 1 ptr @runtime.alloc(i32 %len, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
%makeslice.buf = call align 1 ptr @runtime.alloc(i32 %len, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5
|
||||
%0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0
|
||||
%1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1
|
||||
%2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2
|
||||
call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret { ptr, i32, i32 } %2
|
||||
|
||||
slice.throw: ; preds = %entry
|
||||
call void @runtime.slicePanic(ptr undef) #3
|
||||
call void @runtime.slicePanic(ptr undef) #5
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -123,15 +129,15 @@ entry:
|
||||
|
||||
slice.next: ; preds = %entry
|
||||
%makeslice.cap = shl nuw i32 %len, 1
|
||||
%makeslice.buf = call align 2 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
%makeslice.buf = call align 2 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5
|
||||
%0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0
|
||||
%1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1
|
||||
%2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2
|
||||
call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret { ptr, i32, i32 } %2
|
||||
|
||||
slice.throw: ; preds = %entry
|
||||
call void @runtime.slicePanic(ptr undef) #3
|
||||
call void @runtime.slicePanic(ptr undef) #5
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -144,15 +150,15 @@ entry:
|
||||
|
||||
slice.next: ; preds = %entry
|
||||
%makeslice.cap = mul i32 %len, 3
|
||||
%makeslice.buf = call align 1 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
%makeslice.buf = call align 1 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5
|
||||
%0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0
|
||||
%1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1
|
||||
%2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2
|
||||
call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret { ptr, i32, i32 } %2
|
||||
|
||||
slice.throw: ; preds = %entry
|
||||
call void @runtime.slicePanic(ptr undef) #3
|
||||
call void @runtime.slicePanic(ptr undef) #5
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -165,15 +171,15 @@ entry:
|
||||
|
||||
slice.next: ; preds = %entry
|
||||
%makeslice.cap = shl nuw i32 %len, 2
|
||||
%makeslice.buf = call align 4 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
%makeslice.buf = call align 4 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5
|
||||
%0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0
|
||||
%1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1
|
||||
%2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2
|
||||
call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %makeslice.buf, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret { ptr, i32, i32 } %2
|
||||
|
||||
slice.throw: ; preds = %entry
|
||||
call void @runtime.slicePanic(ptr undef) #3
|
||||
call void @runtime.slicePanic(ptr undef) #5
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -182,7 +188,7 @@ define hidden ptr @main.Add32(ptr %p, i32 %len, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%0 = getelementptr i8, ptr %p, i32 %len
|
||||
call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret ptr %0
|
||||
}
|
||||
|
||||
@@ -192,7 +198,7 @@ entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%0 = trunc i64 %len to i32
|
||||
%1 = getelementptr i8, ptr %p, i32 %0
|
||||
call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret ptr %1
|
||||
}
|
||||
|
||||
@@ -206,7 +212,7 @@ slicetoarray.next: ; preds = %entry
|
||||
ret ptr %s.data
|
||||
|
||||
slicetoarray.throw: ; preds = %entry
|
||||
call void @runtime.sliceToArrayPointerPanic(ptr undef) #3
|
||||
call void @runtime.sliceToArrayPointerPanic(ptr undef) #5
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -216,8 +222,8 @@ declare void @runtime.sliceToArrayPointerPanic(ptr) #1
|
||||
define hidden ptr @main.SliceToArrayConst(ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%makeslice = call align 4 dereferenceable(24) ptr @runtime.alloc(i32 24, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #3
|
||||
%makeslice = call align 4 dereferenceable(24) ptr @runtime.alloc(i32 24, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #5
|
||||
call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #5
|
||||
br i1 false, label %slicetoarray.throw, label %slicetoarray.next
|
||||
|
||||
slicetoarray.next: ; preds = %entry
|
||||
@@ -242,11 +248,11 @@ unsafe.Slice.next: ; preds = %entry
|
||||
%5 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0
|
||||
%6 = insertvalue { ptr, i32, i32 } %5, i32 %len, 1
|
||||
%7 = insertvalue { ptr, i32, i32 } %6, i32 %len, 2
|
||||
call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret { ptr, i32, i32 } %7
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(ptr undef) #3
|
||||
call void @runtime.unsafeSlicePanic(ptr undef) #5
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -266,11 +272,11 @@ unsafe.Slice.next: ; preds = %entry
|
||||
%4 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0
|
||||
%5 = insertvalue { ptr, i32, i32 } %4, i32 %3, 1
|
||||
%6 = insertvalue { ptr, i32, i32 } %5, i32 %3, 2
|
||||
call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret { ptr, i32, i32 } %6
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(ptr undef) #3
|
||||
call void @runtime.unsafeSlicePanic(ptr undef) #5
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -290,11 +296,11 @@ unsafe.Slice.next: ; preds = %entry
|
||||
%6 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0
|
||||
%7 = insertvalue { ptr, i32, i32 } %6, i32 %5, 1
|
||||
%8 = insertvalue { ptr, i32, i32 } %7, i32 %5, 2
|
||||
call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret { ptr, i32, i32 } %8
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(ptr undef) #3
|
||||
call void @runtime.unsafeSlicePanic(ptr undef) #5
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -314,15 +320,17 @@ unsafe.Slice.next: ; preds = %entry
|
||||
%6 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0
|
||||
%7 = insertvalue { ptr, i32, i32 } %6, i32 %5, 1
|
||||
%8 = insertvalue { ptr, i32, i32 } %7, i32 %5, 2
|
||||
call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3
|
||||
call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #5
|
||||
ret { ptr, i32, i32 } %8
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(ptr undef) #3
|
||||
call void @runtime.unsafeSlicePanic(ptr undef) #5
|
||||
unreachable
|
||||
}
|
||||
|
||||
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
|
||||
attributes #3 = { nounwind }
|
||||
attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
|
||||
attributes #4 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
|
||||
attributes #5 = { nounwind }
|
||||
|
||||
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.0"
|
||||
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).
|
||||
|
||||
+2
-11
@@ -2,7 +2,6 @@ package interp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -11,25 +10,17 @@ import (
|
||||
)
|
||||
|
||||
func TestInterp(t *testing.T) {
|
||||
llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0])
|
||||
if err != nil {
|
||||
// Note: this should never happen and if it does, it will always happen
|
||||
// for a particular build because llvm.Version is a constant.
|
||||
panic(err)
|
||||
}
|
||||
for _, name := range []string{
|
||||
"basic",
|
||||
"phi",
|
||||
"slice-copy",
|
||||
"consteval",
|
||||
"intrinsics",
|
||||
"copy",
|
||||
"interface",
|
||||
"revert",
|
||||
"alloc",
|
||||
} {
|
||||
name := name // make local to this closure
|
||||
if name == "slice-copy" && llvmVersion < 14 {
|
||||
continue
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runTest(t, "testdata/"+name)
|
||||
|
||||
+22
-51
@@ -312,33 +312,28 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
fmt.Fprintln(os.Stderr, indent+"runtime.alloc:", size, "->", ptr)
|
||||
}
|
||||
locals[inst.localIndex] = ptr
|
||||
case callFn.name == "runtime.sliceCopy":
|
||||
// sliceCopy implements the built-in copy function for slices.
|
||||
// It is implemented here so that it can be used even if the
|
||||
// runtime implementation is not available. Doing it this way
|
||||
// may also be faster.
|
||||
// Code:
|
||||
// func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int {
|
||||
// n := srcLen
|
||||
// if n > dstLen {
|
||||
// n = dstLen
|
||||
// }
|
||||
// memmove(dst, src, n*elemSize)
|
||||
// return int(n)
|
||||
// }
|
||||
dstLen := operands[3].Uint(r)
|
||||
srcLen := operands[4].Uint(r)
|
||||
elemSize := operands[5].Uint(r)
|
||||
n := srcLen
|
||||
if n > dstLen {
|
||||
n = dstLen
|
||||
case strings.HasPrefix(callFn.name, "llvm.umin."):
|
||||
locals[inst.localIndex] = makeLiteralInt(min(operands[1].Uint(r), operands[2].Uint(r)), inst.llvmInst.Type().IntTypeWidth())
|
||||
case strings.HasPrefix(callFn.name, "llvm.smin."):
|
||||
locals[inst.localIndex] = makeLiteralInt(uint64(min(operands[1].Int(r), operands[2].Int(r))), inst.llvmInst.Type().IntTypeWidth())
|
||||
case strings.HasPrefix(callFn.name, "llvm.umax."):
|
||||
locals[inst.localIndex] = makeLiteralInt(max(operands[1].Uint(r), operands[2].Uint(r)), inst.llvmInst.Type().IntTypeWidth())
|
||||
case strings.HasPrefix(callFn.name, "llvm.smax."):
|
||||
locals[inst.localIndex] = makeLiteralInt(uint64(max(operands[1].Int(r), operands[2].Int(r))), inst.llvmInst.Type().IntTypeWidth())
|
||||
case strings.HasPrefix(callFn.name, "llvm.memcpy.p0") || strings.HasPrefix(callFn.name, "llvm.memmove.p0"):
|
||||
// Copy a block of memory from one pointer to another.
|
||||
if operands[4].Uint(r) != 0 {
|
||||
// This is a volatile copy/move.
|
||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||
if err != nil {
|
||||
return nil, mem, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"copy:", operands[1], operands[2], n)
|
||||
}
|
||||
if n != 0 {
|
||||
nBytes := operands[3].Uint(r)
|
||||
if nBytes != 0 {
|
||||
// Only try to copy bytes when there are any bytes to copy.
|
||||
// This is not just an optimization. If one of the slices
|
||||
// This is not just an optimization. If one of the pointers
|
||||
// (or both) are nil, the asPointer method call will fail
|
||||
// even though copying a nil slice is allowed.
|
||||
dst, err := operands[1].asPointer(r)
|
||||
@@ -363,11 +358,10 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
}
|
||||
continue
|
||||
}
|
||||
nBytes := uint32(n * elemSize)
|
||||
srcObj := mem.get(src.index())
|
||||
dstObj := mem.getWritable(dst.index())
|
||||
if srcObj.buffer == nil || dstObj.buffer == nil {
|
||||
// If the buffer is nil, it means the slice is external.
|
||||
// If the buffer is nil, it means the memory is external.
|
||||
// This can happen for example when copying data out of
|
||||
// a //go:embed slice, which is not available at interp
|
||||
// time.
|
||||
@@ -380,33 +374,10 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
}
|
||||
dstBuf := dstObj.buffer.asRawValue(r)
|
||||
srcBuf := srcObj.buffer.asRawValue(r)
|
||||
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
|
||||
copy(dstBuf.buf[dst.offset():][:nBytes], srcBuf.buf[src.offset():][:nBytes])
|
||||
dstObj.buffer = dstBuf
|
||||
mem.put(dst.index(), dstObj)
|
||||
}
|
||||
locals[inst.localIndex] = makeLiteralInt(n, inst.llvmInst.Type().IntTypeWidth())
|
||||
case strings.HasPrefix(callFn.name, "llvm.memcpy.p0") || strings.HasPrefix(callFn.name, "llvm.memmove.p0"):
|
||||
// Copy a block of memory from one pointer to another.
|
||||
dst, err := operands[1].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
src, err := operands[2].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
nBytes := uint32(operands[3].Uint(r))
|
||||
dstObj := mem.getWritable(dst.index())
|
||||
dstBuf := dstObj.buffer.asRawValue(r)
|
||||
if mem.get(src.index()).buffer == nil {
|
||||
// Looks like the source buffer is not defined.
|
||||
// This can happen with //extern or //go:embed.
|
||||
return nil, mem, r.errorAt(inst, errUnsupportedRuntimeInst)
|
||||
}
|
||||
srcBuf := mem.get(src.index()).buffer.asRawValue(r)
|
||||
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
|
||||
dstObj.buffer = dstBuf
|
||||
mem.put(dst.index(), dstObj)
|
||||
case callFn.name == "runtime.typeAssert":
|
||||
// This function must be implemented manually as it is normally
|
||||
// implemented by the interface lowering pass.
|
||||
|
||||
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@string = internal unnamed_addr constant [3 x i8] c"foo"
|
||||
@moveDst = global [3 x i8] zeroinitializer
|
||||
@copyDst = global [3 x i8] zeroinitializer
|
||||
|
||||
@externalSrc = external global [2 x i8]
|
||||
@moveExternalDst = global [2 x i8] zeroinitializer
|
||||
|
||||
@moveEscapedSrc = global [4 x i8] c"abcd"
|
||||
@moveEscapedDst = global [4 x i8] zeroinitializer
|
||||
|
||||
@volatileSrc = global [2 x i8] c"xy"
|
||||
@volatileDst = global [2 x i8] zeroinitializer
|
||||
|
||||
declare void @use(ptr)
|
||||
|
||||
define void @runtime.initAll() {
|
||||
call void @main.init()
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init() {
|
||||
call void @testMove()
|
||||
call void @testCopy()
|
||||
call void @testMoveExternal()
|
||||
call void @testMoveEscaped()
|
||||
call void @testVolatileCopy()
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test a simple memmove between globals.
|
||||
define internal void @testMove() {
|
||||
call void @llvm.memmove.p0.p0.i64(ptr @moveDst, ptr @string, i64 3, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test a simple memcpy between globals.
|
||||
define internal void @testCopy() {
|
||||
call void @llvm.memcpy.p0.p0.i64(ptr @copyDst, ptr @string, i64 3, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test a memmove from an external global.
|
||||
; This should be run at runtime.
|
||||
define internal void @testMoveExternal() {
|
||||
call void @llvm.memmove.p0.p0.i64(ptr @moveExternalDst, ptr @externalSrc, i64 2, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test a memmove from an escaped (and potentially modified) source buffer.
|
||||
define internal void @testMoveEscaped() {
|
||||
call void @use(ptr @moveEscapedSrc)
|
||||
call void @llvm.memmove.p0.p0.i64(ptr @moveEscapedDst, ptr @moveEscapedSrc, i64 4, i1 false)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test a volatile memcpy.
|
||||
; This should always be run at runtime.
|
||||
define internal void @testVolatileCopy() {
|
||||
call void @llvm.memcpy.p0.p0.i64(ptr @volatileDst, ptr @volatileSrc, i64 2, i1 true)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @llvm.memmove.p0.p0.i64(ptr, ptr, i64, i1)
|
||||
|
||||
declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1)
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@moveDst = local_unnamed_addr global [3 x i8] c"foo"
|
||||
@copyDst = local_unnamed_addr global [3 x i8] c"foo"
|
||||
@externalSrc = external local_unnamed_addr global [2 x i8]
|
||||
@moveExternalDst = local_unnamed_addr global [2 x i8] zeroinitializer
|
||||
@moveEscapedSrc = global [4 x i8] c"abcd"
|
||||
@moveEscapedDst = local_unnamed_addr global [4 x i8] zeroinitializer
|
||||
@volatileSrc = global [2 x i8] c"xy"
|
||||
@volatileDst = global [2 x i8] zeroinitializer
|
||||
|
||||
declare void @use(ptr) local_unnamed_addr
|
||||
|
||||
define void @runtime.initAll() local_unnamed_addr {
|
||||
call void @llvm.memmove.p0.p0.i64(ptr @moveExternalDst, ptr @externalSrc, i64 2, i1 false)
|
||||
call void @use(ptr @moveEscapedSrc)
|
||||
call void @llvm.memmove.p0.p0.i64(ptr @moveEscapedDst, ptr @moveEscapedSrc, i64 4, i1 false)
|
||||
call void @llvm.memcpy.p0.p0.i64(ptr @volatileDst, ptr @volatileSrc, i64 2, i1 true)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @llvm.memmove.p0.p0.i64(ptr nocapture writeonly, ptr nocapture readonly, i64, i1 immarg) #0
|
||||
|
||||
declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
|
||||
|
||||
attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@uminResult = global i32 0
|
||||
@sminResult = global i32 0
|
||||
@umaxResult = global i32 0
|
||||
@smaxResult = global i32 0
|
||||
|
||||
define void @runtime.initAll() {
|
||||
call void @main.init()
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init() {
|
||||
call void @testUMin()
|
||||
call void @testSMin()
|
||||
call void @testUMax()
|
||||
call void @testSMax()
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @testUMin() {
|
||||
%umin = call i32 @llvm.umin.i32(i32 12, i32 -1)
|
||||
store i32 %umin, ptr @uminResult
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @llvm.umin.i32(i32, i32)
|
||||
|
||||
define internal void @testSMin() {
|
||||
%smin = call i32 @llvm.smin.i32(i32 12, i32 -1)
|
||||
store i32 %smin, ptr @sminResult
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @llvm.smin.i32(i32, i32)
|
||||
|
||||
define internal void @testUMax() {
|
||||
%umax = call i32 @llvm.umax.i32(i32 12, i32 -1)
|
||||
store i32 %umax, ptr @umaxResult
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @llvm.umax.i32(i32, i32)
|
||||
|
||||
define internal void @testSMax() {
|
||||
%smax = call i32 @llvm.smax.i32(i32 12, i32 -1)
|
||||
store i32 %smax, ptr @smaxResult
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @llvm.smax.i32(i32, i32)
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@uminResult = local_unnamed_addr global i32 12
|
||||
@sminResult = local_unnamed_addr global i32 -1
|
||||
@umaxResult = local_unnamed_addr global i32 -1
|
||||
@smaxResult = local_unnamed_addr global i32 12
|
||||
|
||||
define void @runtime.initAll() local_unnamed_addr {
|
||||
ret void
|
||||
}
|
||||
Vendored
-124
@@ -1,124 +0,0 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.uint8SliceSrc.buf = internal global [2 x i8] c"\03d"
|
||||
@main.uint8SliceSrc = internal unnamed_addr global { ptr, i64, i64 } { ptr @main.uint8SliceSrc.buf, i64 2, i64 2 }
|
||||
@main.uint8SliceDst = internal unnamed_addr global { ptr, i64, i64 } zeroinitializer
|
||||
@main.int16SliceSrc.buf = internal global [3 x i16] [i16 5, i16 123, i16 1024]
|
||||
@main.int16SliceSrc = internal unnamed_addr global { ptr, i64, i64 } { ptr @main.int16SliceSrc.buf, i64 3, i64 3 }
|
||||
@main.int16SliceDst = internal unnamed_addr global { ptr, i64, i64 } zeroinitializer
|
||||
@main.sliceSrcUntaint.buf = internal global [2 x i8] c"ab"
|
||||
@main.sliceDstUntaint.buf = internal global [2 x i8] zeroinitializer
|
||||
@main.sliceSrcTaint.buf = internal global [2 x i8] c"cd"
|
||||
@main.sliceDstTaint.buf = internal global [2 x i8] zeroinitializer
|
||||
@main.sliceSrcExternal1.buf = external global [2 x i8]
|
||||
@main.sliceDstExternal1.buf = internal global [2 x i8] zeroinitializer
|
||||
@main.sliceSrcExternal2.buf = internal global [2 x i8] zeroinitializer
|
||||
@main.sliceDstExternal2.buf = external global [2 x i8]
|
||||
|
||||
declare i64 @runtime.sliceCopy(ptr %dst, ptr %src, i64 %dstLen, i64 %srcLen, i64 %elemSize) unnamed_addr
|
||||
|
||||
declare ptr @runtime.alloc(i64, ptr) unnamed_addr
|
||||
|
||||
declare void @runtime.printuint8(i8)
|
||||
|
||||
declare void @runtime.printint16(i16)
|
||||
|
||||
declare void @use(ptr)
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @main.init()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
; print(uintSliceSrc[0])
|
||||
%uint8SliceSrc.buf = load ptr, ptr @main.uint8SliceSrc
|
||||
%uint8SliceSrc.val = load i8, ptr %uint8SliceSrc.buf
|
||||
call void @runtime.printuint8(i8 %uint8SliceSrc.val)
|
||||
|
||||
; print(uintSliceDst[0])
|
||||
%uint8SliceDst.buf = load ptr, ptr @main.uint8SliceDst
|
||||
%uint8SliceDst.val = load i8, ptr %uint8SliceDst.buf
|
||||
call void @runtime.printuint8(i8 %uint8SliceDst.val)
|
||||
|
||||
; print(int16SliceSrc[0])
|
||||
%int16SliceSrc.buf = load ptr, ptr @main.int16SliceSrc
|
||||
%int16SliceSrc.val = load i16, ptr %int16SliceSrc.buf
|
||||
call void @runtime.printint16(i16 %int16SliceSrc.val)
|
||||
|
||||
; print(int16SliceDst[0])
|
||||
%int16SliceDst.buf = load ptr, ptr @main.int16SliceDst
|
||||
%int16SliceDst.val = load i16, ptr %int16SliceDst.buf
|
||||
call void @runtime.printint16(i16 %int16SliceDst.val)
|
||||
|
||||
; print(sliceDstUntaint[0])
|
||||
%sliceDstUntaint.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstUntaint.buf, i32 0)
|
||||
call void @runtime.printuint8(i8 %sliceDstUntaint.val)
|
||||
|
||||
; print(sliceDstTaint[0])
|
||||
%sliceDstTaint.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstTaint.buf, i32 0)
|
||||
call void @runtime.printuint8(i8 %sliceDstTaint.val)
|
||||
|
||||
; print(sliceDstExternal1[0])
|
||||
%sliceDstExternal1.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstExternal1.buf, i32 0)
|
||||
call void @runtime.printuint8(i8 %sliceDstExternal1.val)
|
||||
|
||||
; print(sliceDstExternal2[0])
|
||||
%sliceDstExternal2.val = load i8, ptr getelementptr inbounds (i8, ptr @main.sliceDstExternal2.buf, i32 0)
|
||||
call void @runtime.printuint8(i8 %sliceDstExternal2.val)
|
||||
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init() unnamed_addr {
|
||||
entry:
|
||||
; equivalent of:
|
||||
; uint8SliceDst = make([]uint8, len(uint8SliceSrc))
|
||||
%uint8SliceSrc = load { ptr, i64, i64 }, ptr @main.uint8SliceSrc
|
||||
%uint8SliceSrc.len = extractvalue { ptr, i64, i64 } %uint8SliceSrc, 1
|
||||
%uint8SliceDst.buf = call ptr @runtime.alloc(i64 %uint8SliceSrc.len, ptr null)
|
||||
%0 = insertvalue { ptr, i64, i64 } undef, ptr %uint8SliceDst.buf, 0
|
||||
%1 = insertvalue { ptr, i64, i64 } %0, i64 %uint8SliceSrc.len, 1
|
||||
%2 = insertvalue { ptr, i64, i64 } %1, i64 %uint8SliceSrc.len, 2
|
||||
store { ptr, i64, i64 } %2, ptr @main.uint8SliceDst
|
||||
|
||||
; equivalent of:
|
||||
; copy(uint8SliceDst, uint8SliceSrc)
|
||||
%uint8SliceSrc.buf = extractvalue { ptr, i64, i64 } %uint8SliceSrc, 0
|
||||
%copy.n = call i64 @runtime.sliceCopy(ptr %uint8SliceDst.buf, ptr %uint8SliceSrc.buf, i64 %uint8SliceSrc.len, i64 %uint8SliceSrc.len, i64 1)
|
||||
|
||||
; equivalent of:
|
||||
; int16SliceDst = make([]int16, len(int16SliceSrc))
|
||||
%int16SliceSrc = load { ptr, i64, i64 }, ptr @main.int16SliceSrc
|
||||
%int16SliceSrc.len = extractvalue { ptr, i64, i64 } %int16SliceSrc, 1
|
||||
%int16SliceSrc.len.bytes = mul i64 %int16SliceSrc.len, 2
|
||||
%int16SliceDst.buf = call ptr @runtime.alloc(i64 %int16SliceSrc.len.bytes, ptr null)
|
||||
%3 = insertvalue { ptr, i64, i64 } undef, ptr %int16SliceDst.buf, 0
|
||||
%4 = insertvalue { ptr, i64, i64 } %3, i64 %int16SliceSrc.len, 1
|
||||
%5 = insertvalue { ptr, i64, i64 } %4, i64 %int16SliceSrc.len, 2
|
||||
store { ptr, i64, i64 } %5, ptr @main.int16SliceDst
|
||||
|
||||
; equivalent of:
|
||||
; copy(int16SliceDst, int16SliceSrc)
|
||||
%int16SliceSrc.buf = extractvalue { ptr, i64, i64 } %int16SliceSrc, 0
|
||||
%copy.n2 = call i64 @runtime.sliceCopy(ptr %int16SliceDst.buf, ptr %int16SliceSrc.buf, i64 %int16SliceSrc.len, i64 %int16SliceSrc.len, i64 2)
|
||||
|
||||
; Copy slice that has a known value.
|
||||
%copy.n3 = call i64 @runtime.sliceCopy(ptr @main.sliceDstUntaint.buf, ptr @main.sliceSrcUntaint.buf, i64 2, i64 2, i64 1)
|
||||
|
||||
; Copy slice that might have been modified by the external @use call.
|
||||
; This is a fix for https://github.com/tinygo-org/tinygo/issues/3890.
|
||||
call void @use(ptr @main.sliceSrcTaint.buf)
|
||||
%copy.n4 = call i64 @runtime.sliceCopy(ptr @main.sliceDstTaint.buf, ptr @main.sliceSrcTaint.buf, i64 2, i64 2, i64 1)
|
||||
|
||||
; Test that copying from or into external buffers works correctly.
|
||||
; These copy operations must be done at runtime.
|
||||
; https://github.com/tinygo-org/tinygo/issues/4895
|
||||
%copy.n5 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal1.buf, ptr @main.sliceSrcExternal1.buf, i64 2, i64 2, i64 1)
|
||||
%copy.n6 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal2.buf, ptr @main.sliceSrcExternal2.buf, i64 2, i64 2, i64 1)
|
||||
|
||||
ret void
|
||||
}
|
||||
Vendored
-42
@@ -1,42 +0,0 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.sliceSrcTaint.buf = internal global [2 x i8] c"cd"
|
||||
@main.sliceDstTaint.buf = internal global [2 x i8] zeroinitializer
|
||||
@main.sliceSrcExternal1.buf = external global [2 x i8]
|
||||
@main.sliceDstExternal1.buf = internal global [2 x i8] zeroinitializer
|
||||
@main.sliceSrcExternal2.buf = internal global [2 x i8] zeroinitializer
|
||||
@main.sliceDstExternal2.buf = external global [2 x i8]
|
||||
|
||||
declare i64 @runtime.sliceCopy(ptr, ptr, i64, i64, i64) unnamed_addr
|
||||
|
||||
declare void @runtime.printuint8(i8) local_unnamed_addr
|
||||
|
||||
declare void @runtime.printint16(i16) local_unnamed_addr
|
||||
|
||||
declare void @use(ptr) local_unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @use(ptr @main.sliceSrcTaint.buf)
|
||||
%copy.n4 = call i64 @runtime.sliceCopy(ptr @main.sliceDstTaint.buf, ptr @main.sliceSrcTaint.buf, i64 2, i64 2, i64 1)
|
||||
%copy.n5 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal1.buf, ptr @main.sliceSrcExternal1.buf, i64 2, i64 2, i64 1)
|
||||
%copy.n6 = call i64 @runtime.sliceCopy(ptr @main.sliceDstExternal2.buf, ptr @main.sliceSrcExternal2.buf, i64 2, i64 2, i64 1)
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printuint8(i8 3)
|
||||
call void @runtime.printuint8(i8 3)
|
||||
call void @runtime.printint16(i16 5)
|
||||
call void @runtime.printint16(i16 5)
|
||||
call void @runtime.printuint8(i8 97)
|
||||
%sliceDstTaint.val = load i8, ptr @main.sliceDstTaint.buf, align 1
|
||||
call void @runtime.printuint8(i8 %sliceDstTaint.val)
|
||||
%sliceDstExternal1.val = load i8, ptr @main.sliceDstExternal1.buf, align 1
|
||||
call void @runtime.printuint8(i8 %sliceDstExternal1.val)
|
||||
%sliceDstExternal2.val = load i8, ptr @main.sliceDstExternal2.buf, align 1
|
||||
call void @runtime.printuint8(i8 %sliceDstExternal2.val)
|
||||
ret void
|
||||
}
|
||||
@@ -222,6 +222,7 @@ func TestBuild(t *testing.T) {
|
||||
// to be sure.
|
||||
t.Parallel()
|
||||
options := optionsFromOSARCH("linux/mipsle/softfloat", sema)
|
||||
emuCheck(t, options)
|
||||
runTest("cgo/", options, t, nil, nil)
|
||||
})
|
||||
} else if runtime.GOOS == "windows" {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -13,12 +13,14 @@ const (
|
||||
// 64-bit int => bits = 6
|
||||
sizeBits = 4 + unsafe.Sizeof(uintptr(0))/4
|
||||
|
||||
ptrAlign = unsafe.Alignof(uintptr(0))
|
||||
|
||||
sizeShift = sizeBits + 1
|
||||
|
||||
NoPtrs = Layout(uintptr(0b0<<sizeShift) | uintptr(0b1<<1) | uintptr(1))
|
||||
Pointer = Layout(uintptr(0b1<<sizeShift) | uintptr(0b1<<1) | uintptr(1))
|
||||
String = Layout(uintptr(0b01<<sizeShift) | uintptr(0b10<<1) | uintptr(1))
|
||||
Slice = Layout(uintptr(0b001<<sizeShift) | uintptr(0b11<<1) | uintptr(1))
|
||||
NoPtrs = Layout((0 << sizeShift) | (1 << 1) | 1)
|
||||
Pointer = Layout((1 << sizeShift) | ((unsafe.Sizeof(unsafe.Pointer(nil)) / ptrAlign) << 1) | 1)
|
||||
String = Layout((1 << sizeShift) | ((unsafe.Sizeof("") / ptrAlign) << 1) | 1)
|
||||
Slice = Layout((1 << sizeShift) | ((unsafe.Sizeof([]byte{}) / ptrAlign) << 1) | 1)
|
||||
)
|
||||
|
||||
func (l Layout) AsPtr() unsafe.Pointer { return unsafe.Pointer(l) }
|
||||
|
||||
@@ -86,6 +86,64 @@ func (v Value) Interface() interface{} {
|
||||
return valueInterfaceUnsafe(v)
|
||||
}
|
||||
|
||||
func TypeAssert[T any](v Value) (T, bool) {
|
||||
if v.typecode == nil {
|
||||
panic("reflect.TypeAssert: zero Value")
|
||||
}
|
||||
if !v.isExported() {
|
||||
// Do not allow access to unexported values via TypeAssert,
|
||||
// because they might be pointers that should not be
|
||||
// writable or methods or function that should not be callable.
|
||||
panic("reflect.TypeAssert: cannot return value obtained from unexported field or method")
|
||||
}
|
||||
|
||||
typ := TypeFor[T]()
|
||||
|
||||
// If v is an interface, return the element inside the interface.
|
||||
//
|
||||
// T is a concrete type and v is an interface. For example:
|
||||
//
|
||||
// var v any = int(1)
|
||||
// val := ValueOf(&v).Elem()
|
||||
// TypeAssert[int](val) == val.Interface().(int)
|
||||
//
|
||||
// T is a interface and v is a non-nil interface value. For example:
|
||||
//
|
||||
// var v any = &someError{}
|
||||
// val := ValueOf(&v).Elem()
|
||||
// TypeAssert[error](val) == val.Interface().(error)
|
||||
//
|
||||
// T is a interface and v is a nil interface value. For example:
|
||||
//
|
||||
// var v error = nil
|
||||
// val := ValueOf(&v).Elem()
|
||||
// TypeAssert[error](val) == val.Interface().(error)
|
||||
if v.Kind() == Interface {
|
||||
val, ok := valueInterfaceUnsafe(v).(T)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// If T is an interface and v is a concrete type. For example:
|
||||
//
|
||||
// TypeAssert[any](ValueOf(1)) == ValueOf(1).Interface().(any)
|
||||
// TypeAssert[error](ValueOf(&someError{})) == ValueOf(&someError{}).Interface().(error)
|
||||
if typ.Kind() == Interface {
|
||||
val, ok := valueInterfaceUnsafe(v).(T)
|
||||
return val, ok
|
||||
}
|
||||
|
||||
// Both v and T must be concrete types.
|
||||
// The only way for an type-assertion to match is if the types are equal.
|
||||
if typ != v.typecode {
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
if !v.isIndirect() {
|
||||
return *(*T)(unsafe.Pointer(&v.value)), true
|
||||
}
|
||||
return *(*T)(v.value), true
|
||||
}
|
||||
|
||||
// valueInterfaceUnsafe is used by the runtime to hash map keys. It should not
|
||||
// be subject to the isExported check.
|
||||
func valueInterfaceUnsafe(v Value) interface{} {
|
||||
@@ -1738,6 +1796,9 @@ func (e *ValueError) Error() string {
|
||||
//go:linkname memcpy runtime.memcpy
|
||||
func memcpy(dst, src unsafe.Pointer, size uintptr)
|
||||
|
||||
//go:linkname memmove runtime.memmove
|
||||
func memmove(dst, src unsafe.Pointer, size uintptr)
|
||||
|
||||
//go:linkname memzero runtime.memzero
|
||||
func memzero(ptr unsafe.Pointer, size uintptr)
|
||||
|
||||
@@ -1745,10 +1806,7 @@ func memzero(ptr unsafe.Pointer, size uintptr)
|
||||
func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
|
||||
|
||||
//go:linkname sliceAppend runtime.sliceAppend
|
||||
func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr)
|
||||
|
||||
//go:linkname sliceCopy runtime.sliceCopy
|
||||
func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int
|
||||
func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr, layout unsafe.Pointer) (unsafe.Pointer, uintptr, uintptr)
|
||||
|
||||
// Copy copies the contents of src into dst until either
|
||||
// dst has been filled or src has been exhausted.
|
||||
@@ -1779,7 +1837,10 @@ func Copy(dst, src Value) int {
|
||||
dst.checkRO()
|
||||
}
|
||||
|
||||
return sliceCopy(dstbuf, srcbuf, dstlen, srclen, dst.typecode.elem().Size())
|
||||
minLen := min(dstlen, srclen)
|
||||
elemSize := dst.typecode.elem().Size()
|
||||
memmove(dstbuf, srcbuf, minLen*elemSize)
|
||||
return int(minLen)
|
||||
}
|
||||
|
||||
func buflen(v Value) (unsafe.Pointer, uintptr) {
|
||||
@@ -1810,7 +1871,7 @@ func buflen(v Value) (unsafe.Pointer, uintptr) {
|
||||
}
|
||||
|
||||
//go:linkname sliceGrow runtime.sliceGrow
|
||||
func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr)
|
||||
func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr, layout unsafe.Pointer) (unsafe.Pointer, uintptr, uintptr)
|
||||
|
||||
// extend slice to hold n new elements
|
||||
func extendSlice(v Value, n int) sliceHeader {
|
||||
@@ -1823,7 +1884,10 @@ func extendSlice(v Value, n int) sliceHeader {
|
||||
old = *(*sliceHeader)(v.value)
|
||||
}
|
||||
|
||||
nbuf, nlen, ncap := sliceGrow(old.data, old.len, old.cap, old.len+uintptr(n), v.typecode.elem().Size())
|
||||
elem := v.typecode.elem()
|
||||
elemSize := elem.Size()
|
||||
elemLayout := elem.gcLayout()
|
||||
nbuf, nlen, ncap := sliceGrow(old.data, old.len, old.cap, old.len+uintptr(n), elemSize, elemLayout)
|
||||
|
||||
return sliceHeader{
|
||||
data: nbuf,
|
||||
@@ -1862,8 +1926,10 @@ func AppendSlice(s, t Value) Value {
|
||||
}
|
||||
sSlice := (*sliceHeader)(s.value)
|
||||
tSlice := (*sliceHeader)(t.value)
|
||||
elemSize := s.typecode.elem().Size()
|
||||
ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize)
|
||||
elem := s.typecode.elem()
|
||||
elemSize := elem.Size()
|
||||
elemLayout := elem.gcLayout()
|
||||
ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize, elemLayout)
|
||||
result := &sliceHeader{
|
||||
data: ptr,
|
||||
len: len,
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
//go:build amken_trio
|
||||
|
||||
// RabbitPNP Toolhead Board
|
||||
// MCU: STM32G0B1CBTx (LQFP48, 128KB Flash, 144KB RAM)
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
// Vacuum sensors (PWM input via TIM2)
|
||||
const (
|
||||
VAC1 = PA0 // TIM2_CH1
|
||||
VAC2 = PA1 // TIM2_CH2
|
||||
)
|
||||
|
||||
// Motor 1 pins (stepper driver)
|
||||
const (
|
||||
M1_CS = PC7
|
||||
M1_DIR = PB13 // REFL
|
||||
M1_STEP = PB14 // REFR
|
||||
M1_ENN = PA10 // Enable (active low)
|
||||
)
|
||||
|
||||
// Motor 2 pins (stepper driver)
|
||||
const (
|
||||
M2_CS = PA9
|
||||
M2_DIR = PB12 // REFL
|
||||
M2_STEP = PB11 // REFR
|
||||
M2_ENN = PC6 // Enable (active low)
|
||||
)
|
||||
|
||||
// Motor 3 pins (stepper driver)
|
||||
const (
|
||||
M3_CS = PB15
|
||||
M3_DIR = PB2 // REFL
|
||||
M3_STEP = PB1 // REFR
|
||||
M3_ENN = PA8 // Enable (active low)
|
||||
)
|
||||
|
||||
// LED
|
||||
const (
|
||||
LED = LED1
|
||||
LED_BUILTIN = LED1
|
||||
LED1 = PB7
|
||||
)
|
||||
|
||||
// Solenoid and Neopixel (PWM via TIM4)
|
||||
const (
|
||||
SOLENOID = PB8 // TIM4_CH3
|
||||
NEOPIXEL = PB9 // TIM4_CH4
|
||||
)
|
||||
|
||||
// Endstops
|
||||
const (
|
||||
ENDSTOP_IN1 = PC13
|
||||
ENDSTOP_IN2 = PC14
|
||||
)
|
||||
|
||||
// Magnetic sensor
|
||||
const (
|
||||
MAG1 = PB3
|
||||
)
|
||||
|
||||
// Accelerometer chip select (LIS2D on SPI2)
|
||||
const (
|
||||
LIS2D_CS = PB5
|
||||
)
|
||||
|
||||
// SPI1 pins (motor drivers)
|
||||
const (
|
||||
SPI1_SCK_PIN = PA5
|
||||
SPI1_SDO_PIN = PA2 // MOSI
|
||||
SPI1_SDI_PIN = PA6 // MISO
|
||||
SPI0_SCK_PIN = SPI1_SCK_PIN
|
||||
SPI0_SDO_PIN = SPI1_SDO_PIN
|
||||
SPI0_SDI_PIN = SPI1_SDI_PIN
|
||||
)
|
||||
|
||||
// SPI2 pins (accelerometer)
|
||||
const (
|
||||
SPI2_SCK_PIN = PB10
|
||||
SPI2_SDO_PIN = PA4 // MOSI
|
||||
SPI2_SDI_PIN = PA3 // MISO
|
||||
)
|
||||
|
||||
// I2C2 pins
|
||||
const (
|
||||
I2C2_SCL_PIN = PA7
|
||||
I2C2_SDA_PIN = PB4
|
||||
I2C0_SCL_PIN = I2C2_SCL_PIN
|
||||
I2C0_SDA_PIN = I2C2_SDA_PIN
|
||||
)
|
||||
|
||||
// FDCAN1 pins
|
||||
const (
|
||||
CAN_RX = PD0
|
||||
CAN_TX = PD1
|
||||
)
|
||||
|
||||
// USB pins
|
||||
const (
|
||||
USB_DM = PA11
|
||||
USB_DP = PA12
|
||||
)
|
||||
|
||||
// UART pins (not directly connected but required by machine package)
|
||||
const (
|
||||
UART_TX_PIN = NoPin
|
||||
UART_RX_PIN = NoPin
|
||||
)
|
||||
|
||||
var (
|
||||
// SPI1 for motor drivers
|
||||
SPI1 = &SPI{
|
||||
Bus: stm32.SPI1,
|
||||
AltFuncSelector: AF0_SYSTEM,
|
||||
}
|
||||
SPI0 = SPI1
|
||||
|
||||
// SPI2 for accelerometer
|
||||
SPI2 = &SPI{
|
||||
Bus: stm32.SPI2,
|
||||
AltFuncSelector: AF1_TIM1_TIM2_TIM3_LPTIM1,
|
||||
}
|
||||
|
||||
// I2C2
|
||||
I2C2 = &I2C{
|
||||
Bus: stm32.I2C2,
|
||||
AltFuncSelector: AF6_SPI2_USART3_USART4_I2C1,
|
||||
}
|
||||
I2C0 = I2C2
|
||||
|
||||
// FDCAN1 on PD0 (RX) / PD1 (TX) with onboard transceiver
|
||||
CAN1 = &_CAN1
|
||||
_CAN1 = CAN{
|
||||
Bus: stm32.FDCAN1,
|
||||
TxAltFuncSelect: AF3_FDCAN1_FDCAN2,
|
||||
RxAltFuncSelect: AF3_FDCAN1_FDCAN2,
|
||||
instance: 0,
|
||||
}
|
||||
// Alias for convenience
|
||||
CAN0 = CAN1
|
||||
)
|
||||
|
||||
// Suppress unused import warning for interrupt package
|
||||
var _ = interrupt.New
|
||||
|
||||
func init() {
|
||||
// No UART configured on this board - uses USB or CAN for communication
|
||||
}
|
||||
@@ -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,131 @@
|
||||
//go:build nucleog0b1re
|
||||
|
||||
// Schematic: https://www.st.com/resource/en/user_manual/um2324-stm32-nucleo64-boards-mb1360-stmicroelectronics.pdf
|
||||
// Datasheet: https://www.st.com/resource/en/datasheet/stm32g0b1re.pdf
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
const (
|
||||
// Arduino Pins
|
||||
A0 = PA0
|
||||
A1 = PA1
|
||||
A2 = PA4
|
||||
A3 = PB1
|
||||
A4 = PA11
|
||||
A5 = PA12
|
||||
|
||||
D0 = PB7
|
||||
D1 = PB6
|
||||
D2 = PA10
|
||||
D3 = PB3
|
||||
D4 = PB5
|
||||
D5 = PB4
|
||||
D6 = PB10
|
||||
D7 = PA8
|
||||
D8 = PA9
|
||||
D9 = PC7
|
||||
D10 = PB0
|
||||
D11 = PA7
|
||||
D12 = PA6
|
||||
D13 = PA5
|
||||
D14 = PB9
|
||||
D15 = PB8
|
||||
)
|
||||
|
||||
// User LD4: the green LED is a user LED connected to ARDUINO signal D13 corresponding
|
||||
// to STM32 I/O PA5.
|
||||
const (
|
||||
LED = LED_BUILTIN
|
||||
LED_BUILTIN = LED_GREEN
|
||||
LED_GREEN = PA5
|
||||
)
|
||||
|
||||
// User B1: the user button is connected to PC13.
|
||||
const (
|
||||
BUTTON = PC13
|
||||
)
|
||||
|
||||
const (
|
||||
// UART pins
|
||||
// PA2 and PA3 are connected to the ST-Link Virtual Com Port (VCP)
|
||||
UART_TX_PIN = PA2
|
||||
UART_RX_PIN = PA3
|
||||
|
||||
// I2C pins
|
||||
// PB8 is SCL (connected to Arduino connector D15)
|
||||
// PB9 is SDA (connected to Arduino connector D14)
|
||||
I2C0_SCL_PIN = PB8
|
||||
I2C0_SDA_PIN = PB9
|
||||
|
||||
// SPI pins
|
||||
SPI1_SCK_PIN = PA5
|
||||
SPI1_SDI_PIN = PA6
|
||||
SPI1_SDO_PIN = PA7
|
||||
SPI0_SCK_PIN = SPI1_SCK_PIN
|
||||
SPI0_SDI_PIN = SPI1_SDI_PIN
|
||||
SPI0_SDO_PIN = SPI1_SDO_PIN
|
||||
|
||||
// CAN pins (directly accessible on Nucleo-G0B1RE board)
|
||||
// FDCAN1: PA11 (TX) / PA12 (RX) using AF9
|
||||
// FDCAN2: PD12 (TX) / PD13 (RX) using AF3
|
||||
CAN1_TX_PIN = PA11
|
||||
CAN1_RX_PIN = PA12
|
||||
CAN2_TX_PIN = PD12
|
||||
CAN2_RX_PIN = PD13
|
||||
)
|
||||
|
||||
var (
|
||||
// USART2 is the hardware serial port connected to the onboard ST-LINK
|
||||
// debugger to be exposed as virtual COM port over USB on Nucleo boards.
|
||||
UART1 = &_UART1
|
||||
_UART1 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: stm32.USART2,
|
||||
TxAltFuncSelector: AF1_TIM1_TIM2_TIM3_LPTIM1,
|
||||
RxAltFuncSelector: AF1_TIM1_TIM2_TIM3_LPTIM1,
|
||||
}
|
||||
DefaultUART = UART1
|
||||
|
||||
// I2C1 is documented, alias to I2C0 as well
|
||||
I2C1 = &I2C{
|
||||
Bus: stm32.I2C1,
|
||||
AltFuncSelector: AF6_SPI2_USART3_USART4_I2C1,
|
||||
}
|
||||
I2C0 = I2C1
|
||||
|
||||
// SPI1 is documented, alias to SPI0 as well
|
||||
SPI1 = &SPI{
|
||||
Bus: stm32.SPI1,
|
||||
AltFuncSelector: AF0_SYSTEM,
|
||||
}
|
||||
SPI0 = SPI1
|
||||
|
||||
// FDCAN1 on PA11 (TX) / PA12 (RX)
|
||||
CAN1 = &_CAN1
|
||||
_CAN1 = CAN{
|
||||
Bus: stm32.FDCAN1,
|
||||
TxAltFuncSelect: AF9_FDCAN1_FDCAN2,
|
||||
RxAltFuncSelect: AF9_FDCAN1_FDCAN2,
|
||||
instance: 0,
|
||||
}
|
||||
|
||||
// FDCAN2 on PD12 (TX) / PD13 (RX)
|
||||
CAN2 = &_CAN2
|
||||
_CAN2 = CAN{
|
||||
Bus: stm32.FDCAN2,
|
||||
TxAltFuncSelect: AF3_FDCAN1_FDCAN2,
|
||||
RxAltFuncSelect: AF3_FDCAN1_FDCAN2,
|
||||
instance: 1,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
UART1.Interrupt = interrupt.New(stm32.IRQ_USART2_LPUART2, _UART1.handleInterrupt)
|
||||
// Note: FDCAN interrupts share with USB (IRQ_UCPD1_UCPD2_USB = 8)
|
||||
// User should configure interrupts via SetInterrupt method if needed
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
//go:build stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
// unexported functions here are implemented in the device file
|
||||
// and added to the build tags of this file.
|
||||
|
||||
// These types are an alias for documentation purposes exclusively. We wish
|
||||
// the interface to be used by other ecosystems besides TinyGo which is why
|
||||
// we need these types to be a primitive types at the interface level.
|
||||
// If these types are defined at machine or machine/can level they are not
|
||||
// usable by non-TinyGo projects. This is not good news for fostering wider adoption
|
||||
// of our API in "big-Go" embedded system projects like TamaGo and periph.io
|
||||
type (
|
||||
// CAN IDs in tinygo are represented as 30 bit integers where
|
||||
// bits 1..29 store the actual ID and the 30th bit stores the IDE bit (if extended ID).
|
||||
// We include the extended ID bit in the ID itself to make comparison of IDs easier for users
|
||||
// since two identical IDs where one is extended and one is not are NOT equivalent IDs.
|
||||
canID = uint32
|
||||
// CAN flags bitmask are defined below.
|
||||
canFlags = uint32
|
||||
)
|
||||
|
||||
// CAN ID definitions.
|
||||
const (
|
||||
canIDStdMask canID = (1 << 11) - 1
|
||||
canIDExtendedMask canID = (1 << 29) - 1
|
||||
canIDExtendedBit canID = 1 << 30
|
||||
)
|
||||
|
||||
// CAN Flag bit definitions.
|
||||
const (
|
||||
canFlagBRS canFlags = 1 << 0 // Bit Rate Switch active on tx/rx of frame.
|
||||
canFlagFDF canFlags = 1 << 1 // Is a FD Frame.
|
||||
canFlagRTR canFlags = 1 << 2 // is a retransmission request frame.
|
||||
canFlagESI canFlags = 1 << 3 // Error status indicator active on tx/rx of frame.
|
||||
canFlagIDE canFlags = 1 << 4 // Extended ID.
|
||||
)
|
||||
|
||||
// TxFIFOLevel returns amount of CAN frames stored for transmission and total Tx fifo length.
|
||||
func (can *CAN) TxFIFOLevel() (level int, maxlevel int) {
|
||||
return can.txFIFOLevel()
|
||||
}
|
||||
|
||||
// Tx puts a CAN frame in TxFIFO for transmission. Returns error if TxFIFO is full.
|
||||
func (can *CAN) Tx(id canID, flags canFlags, data []byte) error {
|
||||
return can.tx(id, flags, data)
|
||||
}
|
||||
|
||||
// RxFIFOLevel returns amount of CAN frames received and stored and total Rx fifo length.
|
||||
// If the hardware is interrupt driven RxFIFOLevel should return 0,0.
|
||||
func (can *CAN) RxFIFOLevel() (level int, maxlevel int) {
|
||||
return can.rxFIFOLevel()
|
||||
}
|
||||
|
||||
type canRxCallback = func(data []byte, id canID, timestamp uint32, flags canFlags)
|
||||
|
||||
// SetRxCallback sets the receive callback. See [canFlags] for information on how bits are layed out.
|
||||
func (can *CAN) SetRxCallback(cb canRxCallback) {
|
||||
can.setRxCallback(cb)
|
||||
}
|
||||
|
||||
// RxPoll is called periodically for poll driven drivers. If the driver is interrupt driven
|
||||
// then RxPoll is a no-op and may return nil. Users may determine if a CAN is interrupt driven by
|
||||
// checking if RxFIFOLevel returns 0,0.
|
||||
func (can *CAN) RxPoll() error {
|
||||
return can.rxPoll()
|
||||
}
|
||||
|
||||
// DLC to bytes lookup table
|
||||
var dlcToBytes = [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64}
|
||||
|
||||
// dlcToLength converts a DLC value to actual byte length
|
||||
func dlcToLength(dlc byte) uint8 {
|
||||
if dlc > 15 {
|
||||
dlc = 15
|
||||
}
|
||||
return dlcToBytes[dlc]
|
||||
}
|
||||
|
||||
// lengthToDLC converts a byte length to DLC value
|
||||
func lengthToDLC(length uint8) (dlc byte) {
|
||||
switch {
|
||||
case length <= 8:
|
||||
dlc = length
|
||||
case length <= 12:
|
||||
dlc = 9
|
||||
case length <= 16:
|
||||
dlc = 10
|
||||
case length <= 20:
|
||||
dlc = 11
|
||||
case length <= 24:
|
||||
dlc = 12
|
||||
case length <= 32:
|
||||
dlc = 13
|
||||
case length <= 48:
|
||||
dlc = 14
|
||||
default:
|
||||
dlc = 15
|
||||
}
|
||||
return dlc
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350
|
||||
//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l0 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -337,7 +337,7 @@ func handleUSBSetAddress(setup usb.Setup) bool {
|
||||
|
||||
// SendUSBInPacket sends a packet for USB (interrupt in / bulk in).
|
||||
func SendUSBInPacket(ep uint32, data []byte) bool {
|
||||
sendUSBPacket(ep, data, 0)
|
||||
sendUSBPacket(ep, data)
|
||||
|
||||
// clear transfer complete flag
|
||||
setEPINTFLAG(ep, sam.USB_DEVICE_EPINTFLAG_TRCPT1)
|
||||
@@ -351,27 +351,28 @@ func SendUSBInPacket(ep uint32, data []byte) bool {
|
||||
// Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998
|
||||
//
|
||||
//go:noinline
|
||||
func sendUSBPacket(ep uint32, data []byte, maxsize uint16) {
|
||||
l := uint16(len(data))
|
||||
if 0 < maxsize && maxsize < l {
|
||||
l = maxsize
|
||||
func sendUSBPacket(ep uint32, data []byte) {
|
||||
// Select the corresponding buffer.
|
||||
buffer := udd_ep_control_cache_buffer[:]
|
||||
if ep != 0 {
|
||||
buffer = udd_ep_in_cache_buffer[ep][:]
|
||||
}
|
||||
|
||||
// Set endpoint address for sending data
|
||||
if ep == 0 {
|
||||
copy(udd_ep_control_cache_buffer[:], data[:l])
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_control_cache_buffer))))
|
||||
} else {
|
||||
copy(udd_ep_in_cache_buffer[ep][:], data[:l])
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
|
||||
}
|
||||
// Copy the packet to the buffer.
|
||||
copy(buffer[:len(data)], data)
|
||||
|
||||
// Select the corresponding endpoint descriptor.
|
||||
endpoint := &usbEndpointDescriptors[ep].DeviceDescBank[1]
|
||||
|
||||
// Set the endpoint address.
|
||||
endpoint.ADDR.Set(uint32(uintptr(unsafe.Pointer(unsafe.SliceData(buffer)))))
|
||||
|
||||
// clear multi-packet size which is total bytes already sent
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos)
|
||||
endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos)
|
||||
|
||||
// set byte count, which is total number of bytes to be sent
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits((uint32(l) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
endpoint.PCKSIZE.SetBits((uint32(len(data)) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
}
|
||||
|
||||
func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
|
||||
|
||||
@@ -340,7 +340,7 @@ func handleUSBSetAddress(setup usb.Setup) bool {
|
||||
|
||||
// SendUSBInPacket sends a packet for USB (interrupt in / bulk in).
|
||||
func SendUSBInPacket(ep uint32, data []byte) bool {
|
||||
sendUSBPacket(ep, data, 0)
|
||||
sendUSBPacket(ep, data)
|
||||
|
||||
// clear transfer complete flag
|
||||
setEPINTFLAG(ep, sam.USB_DEVICE_ENDPOINT_EPINTFLAG_TRCPT1)
|
||||
@@ -354,27 +354,28 @@ func SendUSBInPacket(ep uint32, data []byte) bool {
|
||||
// Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998
|
||||
//
|
||||
//go:noinline
|
||||
func sendUSBPacket(ep uint32, data []byte, maxsize uint16) {
|
||||
l := uint16(len(data))
|
||||
if 0 < maxsize && maxsize < l {
|
||||
l = maxsize
|
||||
func sendUSBPacket(ep uint32, data []byte) {
|
||||
// Select the corresponding buffer.
|
||||
buffer := udd_ep_control_cache_buffer[:]
|
||||
if ep != 0 {
|
||||
buffer = udd_ep_in_cache_buffer[ep][:]
|
||||
}
|
||||
|
||||
// Set endpoint address for sending data
|
||||
if ep == 0 {
|
||||
copy(udd_ep_control_cache_buffer[:], data[:l])
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_control_cache_buffer))))
|
||||
} else {
|
||||
copy(udd_ep_in_cache_buffer[ep][:], data[:l])
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
|
||||
}
|
||||
// Copy the packet to the buffer.
|
||||
copy(buffer[:len(data)], data)
|
||||
|
||||
// Select the corresponding endpoint descriptor.
|
||||
endpoint := &usbEndpointDescriptors[ep].DeviceDescBank[1]
|
||||
|
||||
// Set the endpoint address.
|
||||
endpoint.ADDR.Set(uint32(uintptr(unsafe.Pointer(unsafe.SliceData(buffer)))))
|
||||
|
||||
// clear multi-packet size which is total bytes already sent
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos)
|
||||
endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos)
|
||||
|
||||
// set byte count, which is total number of bytes to be sent
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits((uint32(l) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
endpoint.PCKSIZE.SetBits((uint32(len(data)) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
|
||||
}
|
||||
|
||||
func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -400,7 +400,8 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
// SoftDevices I've checked.
|
||||
// Documentation:
|
||||
// https://docs.nordicsemi.com/bundle/s140_v6.0.0_api/page/group_n_r_f_s_o_c_f_u_n_c_t_i_o_n_s.html
|
||||
result := arm.SVCall3(0x20+9, address, &p[0], uint32(len(p)))
|
||||
numberOfWords := len(padded) / 4 // flash access goes in 32-bit words
|
||||
result := arm.SVCall3(0x20+9, address, &padded[0], uint32(numberOfWords))
|
||||
if result != 0 {
|
||||
// Could not queue flash operation? Not sure when this can
|
||||
// happen.
|
||||
|
||||
@@ -256,7 +256,7 @@ func initEndpoint(ep, config uint32) {
|
||||
|
||||
// SendUSBInPacket sends a packet for USBHID (interrupt in / bulk in).
|
||||
func SendUSBInPacket(ep uint32, data []byte) bool {
|
||||
sendUSBPacket(ep, data, 0)
|
||||
sendUSBPacket(ep, data)
|
||||
|
||||
// clear transfer complete flag
|
||||
nrf.USBD.INTENCLR.Set(nrf.USBD_INTENCLR_ENDEPOUT0 << 4)
|
||||
@@ -267,33 +267,32 @@ func SendUSBInPacket(ep uint32, data []byte) bool {
|
||||
// Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998
|
||||
//
|
||||
//go:noinline
|
||||
func sendUSBPacket(ep uint32, data []byte, maxsize uint16) {
|
||||
func sendUSBPacket(ep uint32, data []byte) {
|
||||
// Select the corresponding buffer.
|
||||
count := len(data)
|
||||
if 0 < int(maxsize) && int(maxsize) < count {
|
||||
count = int(maxsize)
|
||||
}
|
||||
|
||||
var buffer []byte
|
||||
if ep == 0 {
|
||||
copy(udd_ep_control_cache_buffer[:], data[:count])
|
||||
buffer = udd_ep_control_cache_buffer[:]
|
||||
if count > usb.EndpointPacketSize {
|
||||
// The packet must be sent in chunks.
|
||||
sendOnEP0DATADONE.offset = usb.EndpointPacketSize
|
||||
sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[sendOnEP0DATADONE.offset]
|
||||
sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[usb.EndpointPacketSize]
|
||||
sendOnEP0DATADONE.count = count - usb.EndpointPacketSize
|
||||
count = usb.EndpointPacketSize
|
||||
}
|
||||
sendViaEPIn(
|
||||
ep,
|
||||
&udd_ep_control_cache_buffer[0],
|
||||
count,
|
||||
)
|
||||
} else {
|
||||
copy(udd_ep_in_cache_buffer[ep][:], data[:count])
|
||||
sendViaEPIn(
|
||||
ep,
|
||||
&udd_ep_in_cache_buffer[ep][0],
|
||||
count,
|
||||
)
|
||||
buffer = udd_ep_in_cache_buffer[ep][:]
|
||||
}
|
||||
|
||||
// Copy the packet to the buffer.
|
||||
copy(buffer[:len(data)], data)
|
||||
|
||||
// Send the first chunk of the packet.
|
||||
sendViaEPIn(
|
||||
ep,
|
||||
&buffer[0],
|
||||
count,
|
||||
)
|
||||
}
|
||||
|
||||
func handleEndpointRx(ep uint32) []byte {
|
||||
|
||||
@@ -128,7 +128,7 @@ func handleUSBSetAddress(setup usb.Setup) bool {
|
||||
const ackTimeout = 570
|
||||
|
||||
rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC)
|
||||
sendUSBPacket(0, []byte{}, 0)
|
||||
sendUSBPacket(0, []byte{})
|
||||
|
||||
// Wait for transfer to complete with a timeout.
|
||||
t := timer.timeElapsed()
|
||||
|
||||
@@ -131,7 +131,7 @@ func handleUSBSetAddress(setup usb.Setup) bool {
|
||||
const ackTimeout = 570
|
||||
|
||||
rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_ACK_REC)
|
||||
sendUSBPacket(0, []byte{}, 0)
|
||||
sendUSBPacket(0, []byte{})
|
||||
|
||||
// Wait for transfer to complete with a timeout.
|
||||
t := timer.timeElapsed()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -153,7 +153,7 @@ func (p *pwmGroup) Period() uint64 {
|
||||
top := p.getWrap()
|
||||
phc := p.getPhaseCorrect()
|
||||
Int, frac := p.getClockDiv()
|
||||
return (16*uint64(Int) + uint64(frac)) * uint64((top+1)*(phc+1)*1e9) / (16 * freq) // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16)
|
||||
return (16*uint64(Int) + uint64(frac)) * uint64((top+1)*(phc+1)) * uint64(1e9) / (16 * freq) // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16)
|
||||
}
|
||||
|
||||
// SetInverting sets whether to invert the output of this channel.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -72,19 +72,15 @@ func initEndpoint(ep, config uint32) {
|
||||
|
||||
// SendUSBInPacket sends a packet for USB (interrupt in / bulk in).
|
||||
func SendUSBInPacket(ep uint32, data []byte) bool {
|
||||
sendUSBPacket(ep, data, 0)
|
||||
sendUSBPacket(ep, data)
|
||||
return true
|
||||
}
|
||||
|
||||
// Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998
|
||||
//
|
||||
//go:noinline
|
||||
func sendUSBPacket(ep uint32, data []byte, maxsize uint16) {
|
||||
func sendUSBPacket(ep uint32, data []byte) {
|
||||
count := len(data)
|
||||
if 0 < int(maxsize) && int(maxsize) < count {
|
||||
count = int(maxsize)
|
||||
}
|
||||
|
||||
if ep == 0 {
|
||||
if count > usb.EndpointPacketSize {
|
||||
count = usb.EndpointPacketSize
|
||||
@@ -145,7 +141,7 @@ func setEPDataPID(ep uint32, dataOne bool) {
|
||||
}
|
||||
|
||||
func SendZlp() {
|
||||
sendUSBPacket(0, []byte{}, 0)
|
||||
sendUSBPacket(0, []byte{})
|
||||
}
|
||||
|
||||
func sendViaEPIn(ep uint32, data []byte, count int) {
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
//go:build stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ADC sampling time constants for STM32G0
|
||||
const (
|
||||
ADC_SMPR_1_5 = 0x0 // 1.5 ADC clock cycles
|
||||
ADC_SMPR_3_5 = 0x1 // 3.5 ADC clock cycles
|
||||
ADC_SMPR_7_5 = 0x2 // 7.5 ADC clock cycles
|
||||
ADC_SMPR_12_5 = 0x3 // 12.5 ADC clock cycles
|
||||
ADC_SMPR_19_5 = 0x4 // 19.5 ADC clock cycles
|
||||
ADC_SMPR_39_5 = 0x5 // 39.5 ADC clock cycles
|
||||
ADC_SMPR_79_5 = 0x6 // 79.5 ADC clock cycles
|
||||
ADC_SMPR_160_5 = 0x7 // 160.5 ADC clock cycles
|
||||
)
|
||||
|
||||
// InitADC initializes the registers needed for ADC.
|
||||
func InitADC() {
|
||||
// Enable ADC clock
|
||||
enableAltFuncClock(unsafe.Pointer(stm32.ADC))
|
||||
|
||||
// Ensure ADC is disabled before configuration
|
||||
if stm32.ADC.GetCR_ADEN() != 0 {
|
||||
// Clear ADEN by setting ADDIS
|
||||
stm32.ADC.SetCR_ADDIS(1)
|
||||
// Wait for ADC to be disabled
|
||||
for stm32.ADC.GetCR_ADEN() != 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// Enable ADC voltage regulator
|
||||
stm32.ADC.SetCR_ADVREGEN(1)
|
||||
|
||||
// Wait for ADC voltage regulator startup time (20us at max)
|
||||
// Using simple busy loop - approximately 1280 cycles at 64MHz = 20us
|
||||
for i := 0; i < 1280; i++ {
|
||||
// nop
|
||||
}
|
||||
|
||||
// Configure ADC:
|
||||
// - 12-bit resolution (RES = 0b00)
|
||||
// - Right alignment (ALIGN = 0)
|
||||
// - Single conversion mode (CONT = 0)
|
||||
// - Software trigger (EXTEN = 0b00)
|
||||
stm32.ADC.CFGR1.Set(0)
|
||||
|
||||
// Set clock mode to synchronous with PCLK/2
|
||||
stm32.ADC.SetCFGR2_CKMODE(0x1) // PCLK/2
|
||||
|
||||
// Set sample time to 12.5 cycles for all channels using SMP1
|
||||
stm32.ADC.SetSMPR_SMP1(ADC_SMPR_12_5)
|
||||
|
||||
// Calibrate ADC
|
||||
stm32.ADC.SetCR_ADCAL(1)
|
||||
for stm32.ADC.GetCR_ADCAL() != 0 {
|
||||
}
|
||||
|
||||
// Clear ADRDY by writing 1
|
||||
stm32.ADC.SetISR_ADRDY(1)
|
||||
|
||||
// Enable ADC
|
||||
stm32.ADC.SetCR_ADEN(1)
|
||||
|
||||
// Wait until ADC is ready
|
||||
for stm32.ADC.GetISR_ADRDY() == 0 {
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures an ADC pin to be able to read analog data.
|
||||
func (a ADC) Configure(config ADCConfig) {
|
||||
// Configure pin as analog input
|
||||
a.Pin.Configure(PinConfig{Mode: PinInputAnalog})
|
||||
|
||||
// Set sampling time based on config
|
||||
// Use SMP2 and set SMPSEL bit for this channel to select SMP2
|
||||
ch := a.getChannel()
|
||||
if ch <= 18 {
|
||||
// Select sampling time based on config (using SMP2 for per-channel control)
|
||||
// Map microseconds to sample cycles (at ~32MHz ADC clock after /2 prescaler)
|
||||
// Each cycle = 1/32MHz = 31.25ns
|
||||
var smpTime int
|
||||
switch {
|
||||
case config.SampleTime == 0:
|
||||
smpTime = ADC_SMPR_79_5 // Default to 79.5 cycles for good accuracy
|
||||
case config.SampleTime <= 1:
|
||||
smpTime = ADC_SMPR_1_5
|
||||
case config.SampleTime <= 2:
|
||||
smpTime = ADC_SMPR_3_5
|
||||
case config.SampleTime <= 3:
|
||||
smpTime = ADC_SMPR_7_5
|
||||
case config.SampleTime <= 4:
|
||||
smpTime = ADC_SMPR_12_5
|
||||
case config.SampleTime <= 5:
|
||||
smpTime = ADC_SMPR_19_5
|
||||
case config.SampleTime <= 10:
|
||||
smpTime = ADC_SMPR_39_5
|
||||
case config.SampleTime <= 20:
|
||||
smpTime = ADC_SMPR_79_5
|
||||
default:
|
||||
smpTime = ADC_SMPR_160_5
|
||||
}
|
||||
stm32.ADC.SetSMPR_SMP2(uint32(smpTime))
|
||||
|
||||
// Set SMPSEL bit for this channel to use SMP2
|
||||
stm32.ADC.SMPR.SetBits(1 << (8 + ch))
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the current value of a ADC pin in the range 0..0xffff.
|
||||
func (a ADC) Get() uint16 {
|
||||
ch := a.getChannel()
|
||||
|
||||
// Wait until channel configuration is ready if needed
|
||||
// (CCRDY indicates when CHSELR changes are applied)
|
||||
for stm32.ADC.GetISR_CCRDY() != 0 {
|
||||
stm32.ADC.SetISR_CCRDY(1) // Clear by writing 1
|
||||
}
|
||||
|
||||
// Select the channel to convert using CHSELR
|
||||
// CHSELR uses a bitfield where bit N = 1 enables channel N
|
||||
stm32.ADC.CHSELR.Set(1 << ch)
|
||||
|
||||
// Wait for channel configuration ready
|
||||
for stm32.ADC.GetISR_CCRDY() == 0 {
|
||||
}
|
||||
stm32.ADC.SetISR_CCRDY(1) // Clear flag
|
||||
|
||||
// Start conversion
|
||||
stm32.ADC.SetCR_ADSTART(1)
|
||||
|
||||
// Wait for end of conversion
|
||||
for stm32.ADC.GetISR_EOC() == 0 {
|
||||
}
|
||||
|
||||
// Read the 12-bit result and scale to 16-bit
|
||||
result := uint16(stm32.ADC.GetDR_DATA()) << 4
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// getChannel returns the ADC channel number for a given pin.
|
||||
// STM32G0B1 ADC channel mapping:
|
||||
// PA0-PA7: CH0-CH7
|
||||
// PB0-PB2: CH8-CH10
|
||||
// PB10-PB12: CH11-CH13 (some variants)
|
||||
// PC4-PC5: CH17-CH18 (some variants)
|
||||
func (a ADC) getChannel() uint8 {
|
||||
switch a.Pin {
|
||||
case PA0:
|
||||
return 0
|
||||
case PA1:
|
||||
return 1
|
||||
case PA2:
|
||||
return 2
|
||||
case PA3:
|
||||
return 3
|
||||
case PA4:
|
||||
return 4
|
||||
case PA5:
|
||||
return 5
|
||||
case PA6:
|
||||
return 6
|
||||
case PA7:
|
||||
return 7
|
||||
case PB0:
|
||||
return 8
|
||||
case PB1:
|
||||
return 9
|
||||
case PB2:
|
||||
return 10
|
||||
case PB10:
|
||||
return 11
|
||||
case PB11:
|
||||
return 12
|
||||
case PB12:
|
||||
return 13
|
||||
case PC4:
|
||||
return 17
|
||||
case PC5:
|
||||
return 18
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx
|
||||
//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx && !stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
package machine
|
||||
|
||||
// Flash support for STM32 chips, except for STM32L0 which have a different type
|
||||
// of flash.
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx
|
||||
//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx && !stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx
|
||||
//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx || stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build stm32 && !(stm32f103 || stm32l0x1)
|
||||
//go:build stm32 && !(stm32f103 || stm32l0x1 || stm32g0)
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build stm32 && !stm32f7x2 && !stm32l5x2
|
||||
//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//go:build stm32
|
||||
//go:build stm32 && !stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
// Peripheral abstraction layer for UARTs on the stm32 family.
|
||||
// Peripheral abstraction layer for UARTs on the stm32 family (except stm32g0).
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
|
||||
@@ -0,0 +1,567 @@
|
||||
//go:build stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
// Peripheral abstraction layer for the stm32g0
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// CPU frequency for STM32G0 (64MHz via PLL: HSI16 / 1 * 8 / 2)
|
||||
cpuFreq = 64000000
|
||||
)
|
||||
|
||||
func CPUFrequency() uint32 {
|
||||
return cpuFreq
|
||||
}
|
||||
|
||||
var deviceIDAddr = []uintptr{0x1FFF7590, 0x1FFF7594, 0x1FFF7598}
|
||||
|
||||
// Internal use: configured speed of the APB1 and APB2 timers, this should be kept
|
||||
// in sync with any changes to runtime package which configures the oscillators
|
||||
// and clock frequencies
|
||||
const APB1_TIM_FREQ = 64e6 // 64MHz (PLL: HSI16 / 1 * 8 / 2)
|
||||
const APB2_TIM_FREQ = 64e6 // 64MHz (PLL: HSI16 / 1 * 8 / 2)
|
||||
|
||||
const (
|
||||
PA0 = portA + 0
|
||||
PA1 = portA + 1
|
||||
PA2 = portA + 2
|
||||
PA3 = portA + 3
|
||||
PA4 = portA + 4
|
||||
PA5 = portA + 5
|
||||
PA6 = portA + 6
|
||||
PA7 = portA + 7
|
||||
PA8 = portA + 8
|
||||
PA9 = portA + 9
|
||||
PA10 = portA + 10
|
||||
PA11 = portA + 11
|
||||
PA12 = portA + 12
|
||||
PA13 = portA + 13
|
||||
PA14 = portA + 14
|
||||
PA15 = portA + 15
|
||||
|
||||
PB0 = portB + 0
|
||||
PB1 = portB + 1
|
||||
PB2 = portB + 2
|
||||
PB3 = portB + 3
|
||||
PB4 = portB + 4
|
||||
PB5 = portB + 5
|
||||
PB6 = portB + 6
|
||||
PB7 = portB + 7
|
||||
PB8 = portB + 8
|
||||
PB9 = portB + 9
|
||||
PB10 = portB + 10
|
||||
PB11 = portB + 11
|
||||
PB12 = portB + 12
|
||||
PB13 = portB + 13
|
||||
PB14 = portB + 14
|
||||
PB15 = portB + 15
|
||||
|
||||
PC0 = portC + 0
|
||||
PC1 = portC + 1
|
||||
PC2 = portC + 2
|
||||
PC3 = portC + 3
|
||||
PC4 = portC + 4
|
||||
PC5 = portC + 5
|
||||
PC6 = portC + 6
|
||||
PC7 = portC + 7
|
||||
PC8 = portC + 8
|
||||
PC9 = portC + 9
|
||||
PC10 = portC + 10
|
||||
PC11 = portC + 11
|
||||
PC12 = portC + 12
|
||||
PC13 = portC + 13
|
||||
PC14 = portC + 14
|
||||
PC15 = portC + 15
|
||||
|
||||
PD0 = portD + 0
|
||||
PD1 = portD + 1
|
||||
PD2 = portD + 2
|
||||
PD3 = portD + 3
|
||||
PD4 = portD + 4
|
||||
PD5 = portD + 5
|
||||
PD6 = portD + 6
|
||||
PD7 = portD + 7
|
||||
PD8 = portD + 8
|
||||
PD9 = portD + 9
|
||||
PD10 = portD + 10
|
||||
PD11 = portD + 11
|
||||
PD12 = portD + 12
|
||||
PD13 = portD + 13
|
||||
PD14 = portD + 14
|
||||
PD15 = portD + 15
|
||||
|
||||
PE0 = portE + 0
|
||||
PE1 = portE + 1
|
||||
PE2 = portE + 2
|
||||
PE3 = portE + 3
|
||||
PE4 = portE + 4
|
||||
PE5 = portE + 5
|
||||
PE6 = portE + 6
|
||||
PE7 = portE + 7
|
||||
PE8 = portE + 8
|
||||
PE9 = portE + 9
|
||||
PE10 = portE + 10
|
||||
PE11 = portE + 11
|
||||
PE12 = portE + 12
|
||||
PE13 = portE + 13
|
||||
PE14 = portE + 14
|
||||
PE15 = portE + 15
|
||||
|
||||
PF0 = portF + 0
|
||||
PF1 = portF + 1
|
||||
PF2 = portF + 2
|
||||
PF3 = portF + 3
|
||||
PF4 = portF + 4
|
||||
PF5 = portF + 5
|
||||
PF6 = portF + 6
|
||||
PF7 = portF + 7
|
||||
PF8 = portF + 8
|
||||
PF9 = portF + 9
|
||||
PF10 = portF + 10
|
||||
PF11 = portF + 11
|
||||
PF12 = portF + 12
|
||||
PF13 = portF + 13
|
||||
PF14 = portF + 14
|
||||
PF15 = portF + 15
|
||||
)
|
||||
|
||||
func (p Pin) getPort() *stm32.GPIO_Type {
|
||||
switch p / 16 {
|
||||
case 0:
|
||||
return stm32.GPIOA
|
||||
case 1:
|
||||
return stm32.GPIOB
|
||||
case 2:
|
||||
return stm32.GPIOC
|
||||
case 3:
|
||||
return stm32.GPIOD
|
||||
case 4:
|
||||
return stm32.GPIOE
|
||||
case 5:
|
||||
return stm32.GPIOF
|
||||
default:
|
||||
panic("machine: unknown port")
|
||||
}
|
||||
}
|
||||
|
||||
// enableClock enables the clock for this desired GPIO port.
|
||||
func (p Pin) enableClock() {
|
||||
switch p / 16 {
|
||||
case 0:
|
||||
stm32.RCC.SetIOPENR_GPIOAEN(1)
|
||||
case 1:
|
||||
stm32.RCC.SetIOPENR_GPIOBEN(1)
|
||||
case 2:
|
||||
stm32.RCC.SetIOPENR_GPIOCEN(1)
|
||||
case 3:
|
||||
stm32.RCC.SetIOPENR_GPIODEN(1)
|
||||
case 4:
|
||||
stm32.RCC.SetIOPENR_GPIOEEN(1)
|
||||
case 5:
|
||||
stm32.RCC.SetIOPENR_GPIOFEN(1)
|
||||
default:
|
||||
panic("machine: unknown port")
|
||||
}
|
||||
}
|
||||
|
||||
func (p Pin) registerInterrupt() interrupt.Interrupt {
|
||||
pin := uint8(p) % 16
|
||||
|
||||
switch pin {
|
||||
case 0:
|
||||
return interrupt.New(stm32.IRQ_EXTI0_1, func(interrupt.Interrupt) { handlePinInterrupt(0) })
|
||||
case 1:
|
||||
return interrupt.New(stm32.IRQ_EXTI0_1, func(interrupt.Interrupt) { handlePinInterrupt(1) })
|
||||
case 2:
|
||||
return interrupt.New(stm32.IRQ_EXTI2_3, func(interrupt.Interrupt) { handlePinInterrupt(2) })
|
||||
case 3:
|
||||
return interrupt.New(stm32.IRQ_EXTI2_3, func(interrupt.Interrupt) { handlePinInterrupt(3) })
|
||||
case 4:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(4) })
|
||||
case 5:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(5) })
|
||||
case 6:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(6) })
|
||||
case 7:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(7) })
|
||||
case 8:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(8) })
|
||||
case 9:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(9) })
|
||||
case 10:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(10) })
|
||||
case 11:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(11) })
|
||||
case 12:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(12) })
|
||||
case 13:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(13) })
|
||||
case 14:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(14) })
|
||||
case 15:
|
||||
return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(15) })
|
||||
}
|
||||
|
||||
return interrupt.Interrupt{}
|
||||
}
|
||||
|
||||
//---------- UART related types and code
|
||||
|
||||
// Configure the UART.
|
||||
func (uart *UART) configurePins(config UARTConfig) {
|
||||
// enable the alternate functions on the TX and RX pins
|
||||
config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector)
|
||||
config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector)
|
||||
}
|
||||
|
||||
// UART baudrate calc based on the bus and clockspeed
|
||||
func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 {
|
||||
return CPUFrequency() / baudRate
|
||||
}
|
||||
|
||||
// Register names vary by ST processor, these are for STM G0 family
|
||||
func (uart *UART) setRegisters() {
|
||||
uart.rxReg = &uart.Bus.RDR
|
||||
uart.txReg = &uart.Bus.TDR
|
||||
uart.statusReg = &uart.Bus.ISR_FIFO_ENABLED
|
||||
uart.txEmptyFlag = stm32.USART_ISR_TXE
|
||||
}
|
||||
|
||||
//---------- SPI related types and code
|
||||
|
||||
// SPI on the STM32G0 using MODER / alternate function pins
|
||||
type SPI struct {
|
||||
Bus *stm32.SPI_Type
|
||||
AltFuncSelector uint8
|
||||
}
|
||||
|
||||
func (spi *SPI) config8Bits() {
|
||||
// Set rx threshold to 8-bits, so RXNE flag is set for 1 byte
|
||||
spi.Bus.SetCR2_FRXTH(1)
|
||||
}
|
||||
|
||||
// Set baud rate for SPI
|
||||
func (spi *SPI) getBaudRate(config SPIConfig) uint32 {
|
||||
var conf uint32
|
||||
|
||||
localFrequency := config.Frequency
|
||||
|
||||
// Default
|
||||
if localFrequency == 0 {
|
||||
localFrequency = 4e6
|
||||
}
|
||||
|
||||
// set frequency dependent on PCLK prescaler
|
||||
switch {
|
||||
case localFrequency < 250000:
|
||||
conf = stm32.SPI_CR1_BR_Div256
|
||||
case localFrequency < 500000:
|
||||
conf = stm32.SPI_CR1_BR_Div128
|
||||
case localFrequency < 1000000:
|
||||
conf = stm32.SPI_CR1_BR_Div64
|
||||
case localFrequency < 2000000:
|
||||
conf = stm32.SPI_CR1_BR_Div32
|
||||
case localFrequency < 4000000:
|
||||
conf = stm32.SPI_CR1_BR_Div16
|
||||
case localFrequency < 8000000:
|
||||
conf = stm32.SPI_CR1_BR_Div8
|
||||
case localFrequency < 16000000:
|
||||
conf = stm32.SPI_CR1_BR_Div4
|
||||
case localFrequency < 32000000:
|
||||
conf = stm32.SPI_CR1_BR_Div2
|
||||
default:
|
||||
// None of the specific baudrates were selected; choose the lowest speed
|
||||
conf = stm32.SPI_CR1_BR_Div256
|
||||
}
|
||||
|
||||
return conf << stm32.SPI_CR1_BR_Pos
|
||||
}
|
||||
|
||||
// Configure SPI pins for input output and clock
|
||||
func (spi *SPI) configurePins(config SPIConfig) {
|
||||
config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector)
|
||||
config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector)
|
||||
config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector)
|
||||
}
|
||||
|
||||
//---------- I2C related types and code
|
||||
|
||||
// Gets the value for TIMINGR register
|
||||
func (i2c *I2C) getFreqRange(br uint32) uint32 {
|
||||
// These are 'magic' values calculated by STM32CubeMX
|
||||
// for 64MHz PCLK1 (PLL: HSI16 / 1 * 8 / 2).
|
||||
// TODO: Do calculations based on PCLK1
|
||||
switch br {
|
||||
case 10 * KHz:
|
||||
return 0xF010F3FE // 64MHz, 10kHz I2C
|
||||
case 100 * KHz:
|
||||
return 0x30A0A7FB // 64MHz, 100kHz I2C (Standard mode)
|
||||
case 400 * KHz:
|
||||
return 0x10802D9B // 64MHz, 400kHz I2C (Fast mode)
|
||||
case 500 * KHz:
|
||||
return 0x00802172 // 64MHz, 500kHz I2C
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Enable peripheral clock
|
||||
func enableAltFuncClock(bus unsafe.Pointer) {
|
||||
switch bus {
|
||||
case unsafe.Pointer(stm32.PWR): // Power interface clock enable
|
||||
stm32.RCC.SetAPBENR1_PWREN(1)
|
||||
case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable
|
||||
stm32.RCC.SetAPBENR1_I2C1EN(1)
|
||||
case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable
|
||||
stm32.RCC.SetAPBENR1_I2C2EN(1)
|
||||
case unsafe.Pointer(stm32.USART2): // USART2 clock enable
|
||||
stm32.RCC.SetAPBENR1_USART2EN(1)
|
||||
case unsafe.Pointer(stm32.USART3): // USART3 clock enable
|
||||
stm32.RCC.SetAPBENR1_USART3EN(1)
|
||||
case unsafe.Pointer(stm32.USART4): // USART4 clock enable
|
||||
stm32.RCC.SetAPBENR1_USART4EN(1)
|
||||
case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable
|
||||
stm32.RCC.SetAPBENR1_SPI2EN(1)
|
||||
case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable
|
||||
stm32.RCC.SetAPBENR1_WWDGEN(1)
|
||||
case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable
|
||||
stm32.RCC.SetAPBENR1_TIM2EN(1)
|
||||
case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable
|
||||
stm32.RCC.SetAPBENR1_TIM3EN(1)
|
||||
case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable
|
||||
stm32.RCC.SetAPBENR1_TIM6EN(1)
|
||||
case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable
|
||||
stm32.RCC.SetAPBENR1_TIM7EN(1)
|
||||
case unsafe.Pointer(stm32.LPUART1): // LPUART1 clock enable
|
||||
stm32.RCC.SetAPBENR1_LPUART1EN(1)
|
||||
case unsafe.Pointer(stm32.TIM1): // TIM1 clock enable
|
||||
stm32.RCC.SetAPBENR2_TIM1EN(1)
|
||||
case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable
|
||||
stm32.RCC.SetAPBENR2_SPI1EN(1)
|
||||
case unsafe.Pointer(stm32.USART1): // USART1 clock enable
|
||||
stm32.RCC.SetAPBENR2_USART1EN(1)
|
||||
case unsafe.Pointer(stm32.TIM14): // TIM14 clock enable
|
||||
stm32.RCC.SetAPBENR2_TIM14EN(1)
|
||||
case unsafe.Pointer(stm32.TIM15): // TIM15 clock enable
|
||||
stm32.RCC.SetAPBENR2_TIM15EN(1)
|
||||
case unsafe.Pointer(stm32.TIM16): // TIM16 clock enable
|
||||
stm32.RCC.SetAPBENR2_TIM16EN(1)
|
||||
case unsafe.Pointer(stm32.TIM17): // TIM17 clock enable
|
||||
stm32.RCC.SetAPBENR2_TIM17EN(1)
|
||||
case unsafe.Pointer(stm32.ADC): // ADC clock enable
|
||||
stm32.RCC.SetAPBENR2_ADCEN(1)
|
||||
case unsafe.Pointer(stm32.FDCAN1), unsafe.Pointer(stm32.FDCAN2): // FDCAN clock enable
|
||||
stm32.RCC.SetAPBENR1_FDCANEN(1)
|
||||
}
|
||||
}
|
||||
|
||||
//---------- Timer related code
|
||||
|
||||
// Alternate function constants for STM32G0
|
||||
const (
|
||||
AF0_SYSTEM = 0
|
||||
AF1_TIM1_TIM2_TIM3_LPTIM1 = 1
|
||||
AF2_TIM1_TIM2_TIM3_TIM14_I2C2 = 2
|
||||
AF3_USART5_USART6_LPUART2 = 3
|
||||
AF3_FDCAN1_FDCAN2 = 3 // FDCAN on PC2/PC3/PC4/PC5, PD12/PD13/PD14/PD15
|
||||
AF4_USART1_USART2_TIM14 = 4
|
||||
AF5_SPI1_SPI2_TIM16_TIM17 = 5
|
||||
AF6_SPI2_USART3_USART4_I2C1 = 6
|
||||
AF7_USART1_USART2_COMP1_COMP2 = 7
|
||||
AF8_I2C1_I2C2_UCPD1_UCPD2 = 8
|
||||
AF9_SPI2_TIM14_TIM15 = 9
|
||||
AF9_FDCAN1_FDCAN2 = 9 // FDCAN on PA11/PA12, PB8/PB9
|
||||
)
|
||||
|
||||
var (
|
||||
TIM1 = TIM{
|
||||
EnableRegister: &stm32.RCC.APBENR2,
|
||||
EnableFlag: stm32.RCC_APBENR2_TIM1EN,
|
||||
Device: stm32.TIM1,
|
||||
Channels: [4]TimerChannel{
|
||||
{Pins: []PinFunction{{PA8, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}},
|
||||
{Pins: []PinFunction{{PA9, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}},
|
||||
{Pins: []PinFunction{{PA10, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}},
|
||||
{Pins: []PinFunction{{PA11, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}},
|
||||
},
|
||||
busFreq: APB2_TIM_FREQ,
|
||||
}
|
||||
|
||||
TIM2 = TIM{
|
||||
EnableRegister: &stm32.RCC.APBENR1,
|
||||
EnableFlag: stm32.RCC_APBENR1_TIM2EN,
|
||||
Device: stm32.TIM2,
|
||||
Channels: [4]TimerChannel{
|
||||
{Pins: []PinFunction{{PA0, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}, {PA5, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}, {PA15, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}},
|
||||
{Pins: []PinFunction{{PA1, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}, {PB3, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}},
|
||||
{Pins: []PinFunction{{PA2, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}, {PB10, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}},
|
||||
{Pins: []PinFunction{{PA3, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}, {PB11, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}},
|
||||
},
|
||||
busFreq: APB1_TIM_FREQ,
|
||||
}
|
||||
|
||||
TIM3 = TIM{
|
||||
EnableRegister: &stm32.RCC.APBENR1,
|
||||
EnableFlag: stm32.RCC_APBENR1_TIM3EN,
|
||||
Device: stm32.TIM3,
|
||||
Channels: [4]TimerChannel{
|
||||
{Pins: []PinFunction{{PA6, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PB4, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PC6, AF1_TIM1_TIM2_TIM3_LPTIM1}}},
|
||||
{Pins: []PinFunction{{PA7, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PB5, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PC7, AF1_TIM1_TIM2_TIM3_LPTIM1}}},
|
||||
{Pins: []PinFunction{{PB0, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PC8, AF1_TIM1_TIM2_TIM3_LPTIM1}}},
|
||||
{Pins: []PinFunction{{PB1, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PC9, AF1_TIM1_TIM2_TIM3_LPTIM1}}},
|
||||
},
|
||||
busFreq: APB1_TIM_FREQ,
|
||||
}
|
||||
|
||||
TIM6 = TIM{
|
||||
EnableRegister: &stm32.RCC.APBENR1,
|
||||
EnableFlag: stm32.RCC_APBENR1_TIM6EN,
|
||||
Device: stm32.TIM6,
|
||||
Channels: [4]TimerChannel{
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
},
|
||||
busFreq: APB1_TIM_FREQ,
|
||||
}
|
||||
|
||||
TIM7 = TIM{
|
||||
EnableRegister: &stm32.RCC.APBENR1,
|
||||
EnableFlag: stm32.RCC_APBENR1_TIM7EN,
|
||||
Device: stm32.TIM7,
|
||||
Channels: [4]TimerChannel{
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
},
|
||||
busFreq: APB1_TIM_FREQ,
|
||||
}
|
||||
|
||||
TIM14 = TIM{
|
||||
EnableRegister: &stm32.RCC.APBENR2,
|
||||
EnableFlag: stm32.RCC_APBENR2_TIM14EN,
|
||||
Device: stm32.TIM14,
|
||||
Channels: [4]TimerChannel{
|
||||
{Pins: []PinFunction{{PA4, AF4_USART1_USART2_TIM14}, {PA7, AF4_USART1_USART2_TIM14}, {PB1, AF0_SYSTEM}}},
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
},
|
||||
busFreq: APB2_TIM_FREQ,
|
||||
}
|
||||
|
||||
TIM15 = TIM{
|
||||
EnableRegister: &stm32.RCC.APBENR2,
|
||||
EnableFlag: stm32.RCC_APBENR2_TIM15EN,
|
||||
Device: stm32.TIM15,
|
||||
Channels: [4]TimerChannel{
|
||||
{Pins: []PinFunction{{PA2, AF5_SPI1_SPI2_TIM16_TIM17}, {PB14, AF5_SPI1_SPI2_TIM16_TIM17}}},
|
||||
{Pins: []PinFunction{{PA3, AF5_SPI1_SPI2_TIM16_TIM17}, {PB15, AF5_SPI1_SPI2_TIM16_TIM17}}},
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
},
|
||||
busFreq: APB2_TIM_FREQ,
|
||||
}
|
||||
|
||||
TIM16 = TIM{
|
||||
EnableRegister: &stm32.RCC.APBENR2,
|
||||
EnableFlag: stm32.RCC_APBENR2_TIM16EN,
|
||||
Device: stm32.TIM16,
|
||||
Channels: [4]TimerChannel{
|
||||
{Pins: []PinFunction{{PA6, AF5_SPI1_SPI2_TIM16_TIM17}, {PB8, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}},
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
},
|
||||
busFreq: APB2_TIM_FREQ,
|
||||
}
|
||||
|
||||
TIM17 = TIM{
|
||||
EnableRegister: &stm32.RCC.APBENR2,
|
||||
EnableFlag: stm32.RCC_APBENR2_TIM17EN,
|
||||
Device: stm32.TIM17,
|
||||
Channels: [4]TimerChannel{
|
||||
{Pins: []PinFunction{{PA7, AF5_SPI1_SPI2_TIM16_TIM17}, {PB9, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}},
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
{Pins: []PinFunction{}},
|
||||
},
|
||||
busFreq: APB2_TIM_FREQ,
|
||||
}
|
||||
)
|
||||
|
||||
func (t *TIM) registerUPInterrupt() interrupt.Interrupt {
|
||||
switch t {
|
||||
case &TIM1:
|
||||
return interrupt.New(stm32.IRQ_TIM1_BRK_UP_TRG_COM, TIM1.handleUPInterrupt)
|
||||
case &TIM2:
|
||||
return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt)
|
||||
case &TIM3:
|
||||
return interrupt.New(stm32.IRQ_TIM3_TIM4, TIM3.handleUPInterrupt)
|
||||
case &TIM6:
|
||||
return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleUPInterrupt)
|
||||
case &TIM7:
|
||||
return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt)
|
||||
case &TIM14:
|
||||
return interrupt.New(stm32.IRQ_TIM14, TIM14.handleUPInterrupt)
|
||||
case &TIM15:
|
||||
return interrupt.New(stm32.IRQ_TIM15, TIM15.handleUPInterrupt)
|
||||
case &TIM16:
|
||||
return interrupt.New(stm32.IRQ_TIM16, TIM16.handleUPInterrupt)
|
||||
case &TIM17:
|
||||
return interrupt.New(stm32.IRQ_TIM17, TIM17.handleUPInterrupt)
|
||||
}
|
||||
|
||||
return interrupt.Interrupt{}
|
||||
}
|
||||
|
||||
func (t *TIM) registerOCInterrupt() interrupt.Interrupt {
|
||||
switch t {
|
||||
case &TIM1:
|
||||
return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleOCInterrupt)
|
||||
case &TIM2:
|
||||
return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt)
|
||||
case &TIM3:
|
||||
return interrupt.New(stm32.IRQ_TIM3_TIM4, TIM3.handleOCInterrupt)
|
||||
case &TIM6:
|
||||
return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleOCInterrupt)
|
||||
case &TIM7:
|
||||
return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt)
|
||||
case &TIM14:
|
||||
return interrupt.New(stm32.IRQ_TIM14, TIM14.handleOCInterrupt)
|
||||
case &TIM15:
|
||||
return interrupt.New(stm32.IRQ_TIM15, TIM15.handleOCInterrupt)
|
||||
case &TIM16:
|
||||
return interrupt.New(stm32.IRQ_TIM16, TIM16.handleOCInterrupt)
|
||||
case &TIM17:
|
||||
return interrupt.New(stm32.IRQ_TIM17, TIM17.handleOCInterrupt)
|
||||
}
|
||||
|
||||
return interrupt.Interrupt{}
|
||||
}
|
||||
|
||||
func (t *TIM) enableMainOutput() {
|
||||
t.Device.SetBDTR_MOE(1)
|
||||
}
|
||||
|
||||
type arrtype = uint32
|
||||
type arrRegType = volatile.Register32
|
||||
|
||||
const (
|
||||
ARR_MAX = 0x10000
|
||||
PSC_MAX = 0x10000
|
||||
)
|
||||
|
||||
func initRNG() {
|
||||
// STM32G0B1 does not have a hardware RNG peripheral
|
||||
// RNG is available on some other STM32G0 variants
|
||||
}
|
||||
@@ -0,0 +1,532 @@
|
||||
//go:build stm32g0b1
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"errors"
|
||||
"runtime/interrupt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Exported API in src/machine/can.go
|
||||
|
||||
// FDCAN Message RAM configuration
|
||||
// STM32G0B1 SRAMCAN base address: 0x4000B400
|
||||
// Each FDCAN instance has its own message RAM area
|
||||
const (
|
||||
sramcanBase = 0x4000B400
|
||||
|
||||
// Message RAM layout sizes (matching STM32 HAL)
|
||||
sramcanFLSNbr = 28 // Max. Filter List Standard Number
|
||||
sramcanFLENbr = 8 // Max. Filter List Extended Number
|
||||
sramcanRF0Nbr = 3 // RX FIFO 0 Elements Number
|
||||
sramcanRF1Nbr = 3 // RX FIFO 1 Elements Number
|
||||
sramcanTEFNbr = 3 // TX Event FIFO Elements Number
|
||||
sramcanTFQNbr = 3 // TX FIFO/Queue Elements Number
|
||||
|
||||
// Element sizes in bytes
|
||||
sramcanFLSSize = 1 * 4 // Filter Standard Element Size
|
||||
sramcanFLESize = 2 * 4 // Filter Extended Element Size
|
||||
sramcanRF0Size = 18 * 4 // RX FIFO 0 Element Size (for 64-byte data)
|
||||
sramcanRF1Size = 18 * 4 // RX FIFO 1 Element Size
|
||||
sramcanTEFSize = 2 * 4 // TX Event FIFO Element Size
|
||||
sramcanTFQSize = 18 * 4 // TX FIFO/Queue Element Size
|
||||
|
||||
// Start addresses (offsets from base)
|
||||
sramcanFLSSA = 0
|
||||
sramcanFLESA = sramcanFLSSA + (sramcanFLSNbr * sramcanFLSSize)
|
||||
sramcanRF0SA = sramcanFLESA + (sramcanFLENbr * sramcanFLESize)
|
||||
sramcanRF1SA = sramcanRF0SA + (sramcanRF0Nbr * sramcanRF0Size)
|
||||
sramcanTEFSA = sramcanRF1SA + (sramcanRF1Nbr * sramcanRF1Size)
|
||||
sramcanTFQSA = sramcanTEFSA + (sramcanTEFNbr * sramcanTEFSize)
|
||||
sramcanSize = sramcanTFQSA + (sramcanTFQNbr * sramcanTFQSize)
|
||||
)
|
||||
|
||||
// FDCAN element masks (for parsing message RAM)
|
||||
const (
|
||||
fdcanElementMaskSTDID = 0x1FFC0000 // Standard Identifier
|
||||
fdcanElementMaskEXTID = 0x1FFFFFFF // Extended Identifier
|
||||
fdcanElementMaskRTR = 0x20000000 // Remote Transmission Request
|
||||
fdcanElementMaskXTD = 0x40000000 // Extended Identifier flag
|
||||
fdcanElementMaskESI = 0x80000000 // Error State Indicator
|
||||
fdcanElementMaskTS = 0x0000FFFF // Timestamp
|
||||
fdcanElementMaskDLC = 0x000F0000 // Data Length Code
|
||||
fdcanElementMaskBRS = 0x00100000 // Bit Rate Switch
|
||||
fdcanElementMaskFDF = 0x00200000 // FD Format
|
||||
fdcanElementMaskEFC = 0x00800000 // Event FIFO Control
|
||||
fdcanElementMaskMM = 0xFF000000 // Message Marker
|
||||
fdcanElementMaskFIDX = 0x7F000000 // Filter Index
|
||||
fdcanElementMaskANMF = 0x80000000 // Accepted Non-matching Frame
|
||||
)
|
||||
|
||||
// Interrupt flags
|
||||
const (
|
||||
FDCAN_IT_RX_FIFO0_NEW_MESSAGE = 0x00000001
|
||||
FDCAN_IT_RX_FIFO0_FULL = 0x00000002
|
||||
FDCAN_IT_RX_FIFO0_MSG_LOST = 0x00000004
|
||||
FDCAN_IT_RX_FIFO1_NEW_MESSAGE = 0x00000010
|
||||
FDCAN_IT_RX_FIFO1_FULL = 0x00000020
|
||||
FDCAN_IT_RX_FIFO1_MSG_LOST = 0x00000040
|
||||
FDCAN_IT_TX_COMPLETE = 0x00000200
|
||||
FDCAN_IT_TX_ABORT_COMPLETE = 0x00000400
|
||||
FDCAN_IT_TX_FIFO_EMPTY = 0x00000800
|
||||
FDCAN_IT_BUS_OFF = 0x02000000
|
||||
FDCAN_IT_ERROR_WARNING = 0x01000000
|
||||
FDCAN_IT_ERROR_PASSIVE = 0x00800000
|
||||
)
|
||||
|
||||
// CAN is a STM32G0's CAN/FDCAN peripheral.
|
||||
type CAN struct {
|
||||
Bus *stm32.FDCAN_Type
|
||||
TxAltFuncSelect uint8
|
||||
RxAltFuncSelect uint8
|
||||
Interrupt interrupt.Interrupt
|
||||
instance uint8
|
||||
alwaysFD bool
|
||||
rxInterrupt bool
|
||||
}
|
||||
|
||||
// CANTransferRate represents CAN bus transfer rates
|
||||
type CANTransferRate uint32
|
||||
|
||||
const (
|
||||
FDCANTransferRate125kbps CANTransferRate = 125000
|
||||
FDCANTransferRate250kbps CANTransferRate = 250000
|
||||
FDCANTransferRate500kbps CANTransferRate = 500000
|
||||
FDCANTransferRate1000kbps CANTransferRate = 1000000
|
||||
FDCANTransferRate2000kbps CANTransferRate = 2000000 // FD only
|
||||
FDCANTransferRate4000kbps CANTransferRate = 4000000 // FD only
|
||||
)
|
||||
|
||||
// CANMode represents the FDCAN operating mode
|
||||
type CANMode uint8
|
||||
|
||||
const (
|
||||
CANModeNormal CANMode = 0
|
||||
CANModeBusMonitoring CANMode = 1
|
||||
CANModeInternalLoopback CANMode = 2
|
||||
CANModeExternalLoopback CANMode = 3
|
||||
)
|
||||
|
||||
// CANConfig holds FDCAN configuration parameters
|
||||
type CANConfig struct {
|
||||
TransferRate CANTransferRate // Nominal bit rate (arbitration phase)
|
||||
TransferRateFD CANTransferRate // Data bit rate (data phase), must be >= TransferRate
|
||||
Mode CANMode
|
||||
Tx Pin
|
||||
Rx Pin
|
||||
Standby Pin // Optional standby pin for CAN transceiver (set to NoPin if not used)
|
||||
AlwaysFD bool // Always transmit as FD frames, even when data fits in classic CAN
|
||||
EnableRxInterrupt bool // Enable interrupt-driven receive (messages delivered via SetRxCallback)
|
||||
}
|
||||
|
||||
// CANFilterConfig represents a message filter configuration
|
||||
type CANFilterConfig struct {
|
||||
Index uint8 // Filter index (0-27 for standard, 0-7 for extended)
|
||||
Type uint8 // 0=Range, 1=Dual, 2=Classic (ID/Mask)
|
||||
Config uint8 // 0=Disable, 1=FIFO0, 2=FIFO1, 3=Reject
|
||||
ID1 uint32 // First ID or filter
|
||||
ID2 uint32 // Second ID or mask
|
||||
IsExtendedID bool // true for 29-bit ID, false for 11-bit
|
||||
}
|
||||
|
||||
var (
|
||||
errCANInvalidTransferRate = errors.New("CAN: invalid TransferRate")
|
||||
errCANInvalidTransferRateFD = errors.New("CAN: invalid TransferRateFD")
|
||||
errCANTimeout = errors.New("CAN: timeout")
|
||||
errCANTxFifoFull = errors.New("CAN: Tx FIFO full")
|
||||
)
|
||||
|
||||
// enableFDCANClock enables the FDCAN peripheral clock
|
||||
func enableFDCANClock() {
|
||||
// FDCAN clock is on APB1
|
||||
stm32.RCC.SetAPBENR1_FDCANEN(1)
|
||||
}
|
||||
|
||||
// flags implemented as described in [CAN.SetRxCallback]
|
||||
var canRxCB [2]canRxCallback
|
||||
|
||||
// canInstances tracks CAN peripherals with interrupt-driven RX enabled.
|
||||
// A non-nil entry means setRxCallback was called with a non-nil callback.
|
||||
var canInstances [2]*CAN
|
||||
|
||||
// Configure initializes the FDCAN peripheral and starts it.
|
||||
func (can *CAN) Configure(config CANConfig) error {
|
||||
can.alwaysFD = config.AlwaysFD
|
||||
|
||||
if config.Standby != NoPin {
|
||||
config.Standby.Configure(PinConfig{Mode: PinOutput})
|
||||
config.Standby.Low()
|
||||
}
|
||||
|
||||
enableFDCANClock()
|
||||
|
||||
config.Tx.ConfigureAltFunc(PinConfig{Mode: PinOutput}, can.TxAltFuncSelect)
|
||||
config.Rx.ConfigureAltFunc(PinConfig{Mode: PinInputFloating}, can.RxAltFuncSelect)
|
||||
|
||||
// Exit sleep mode.
|
||||
can.Bus.SetCCCR_CSR(0)
|
||||
timeout := 10000
|
||||
for can.Bus.GetCCCR_CSA() != 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errCANTimeout
|
||||
}
|
||||
}
|
||||
|
||||
// Request initialization.
|
||||
can.Bus.SetCCCR_INIT(1)
|
||||
timeout = 10000
|
||||
for can.Bus.GetCCCR_INIT() == 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errCANTimeout
|
||||
}
|
||||
}
|
||||
|
||||
// Enable configuration change.
|
||||
can.Bus.SetCCCR_CCE(1)
|
||||
|
||||
if can.Bus == stm32.FDCAN1 {
|
||||
can.Bus.SetCKDIV_PDIV(0) // No clock division.
|
||||
}
|
||||
|
||||
can.Bus.SetCCCR_DAR(0) // Enable auto retransmission.
|
||||
can.Bus.SetCCCR_TXP(0) // Disable transmit pause.
|
||||
can.Bus.SetCCCR_PXHD(0) // Enable protocol exception handling.
|
||||
can.Bus.SetCCCR_FDOE(1) // FD operation.
|
||||
can.Bus.SetCCCR_BRSE(1) // Bit rate switching.
|
||||
|
||||
// Reset mode bits, then apply requested mode.
|
||||
can.Bus.SetCCCR_TEST(0)
|
||||
can.Bus.SetCCCR_MON(0)
|
||||
can.Bus.SetCCCR_ASM(0)
|
||||
can.Bus.SetTEST_LBCK(0)
|
||||
switch config.Mode {
|
||||
case CANModeBusMonitoring:
|
||||
can.Bus.SetCCCR_MON(1)
|
||||
case CANModeInternalLoopback:
|
||||
can.Bus.SetCCCR_TEST(1)
|
||||
can.Bus.SetCCCR_MON(1)
|
||||
can.Bus.SetTEST_LBCK(1)
|
||||
case CANModeExternalLoopback:
|
||||
can.Bus.SetCCCR_TEST(1)
|
||||
can.Bus.SetTEST_LBCK(1)
|
||||
}
|
||||
|
||||
// Nominal bit timing (64 MHz FDCAN clock, 16 tq/bit, ~80% sample point).
|
||||
if config.TransferRate == 0 {
|
||||
config.TransferRate = FDCANTransferRate500kbps
|
||||
}
|
||||
nbrp, ntseg1, ntseg2, nsjw, err := fdcanNominalBitTiming(config.TransferRate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
can.Bus.NBTP.Set(((nsjw - 1) << 25) | ((nbrp - 1) << 16) | ((ntseg1 - 1) << 8) | (ntseg2 - 1))
|
||||
|
||||
// Data bit timing (FD phase).
|
||||
if config.TransferRateFD == 0 {
|
||||
config.TransferRateFD = FDCANTransferRate1000kbps
|
||||
}
|
||||
if config.TransferRateFD < config.TransferRate {
|
||||
return errCANInvalidTransferRateFD
|
||||
}
|
||||
dbrp, dtseg1, dtseg2, dsjw, err := fdcanDataBitTiming(config.TransferRateFD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
can.Bus.DBTP.Set(((dbrp - 1) << 16) | ((dtseg1 - 1) << 8) | ((dtseg2 - 1) << 4) | (dsjw - 1))
|
||||
|
||||
// Enable timestamp counter (internal, prescaler=1).
|
||||
can.Bus.TSCC.Set(1)
|
||||
|
||||
// Clear message RAM.
|
||||
base := can.sramBase()
|
||||
for addr := base; addr < base+sramcanSize; addr += 4 {
|
||||
*(*uint32)(unsafe.Pointer(addr)) = 0
|
||||
}
|
||||
|
||||
// Set filter list sizes: LSS[20:16], LSE[27:24].
|
||||
rxgfc := can.Bus.RXGFC.Get()
|
||||
rxgfc &= ^uint32(0x0F1F0000)
|
||||
rxgfc |= uint32(sramcanFLSNbr) << 16
|
||||
rxgfc |= uint32(sramcanFLENbr) << 24
|
||||
can.Bus.RXGFC.Set(rxgfc)
|
||||
|
||||
// Start peripheral.
|
||||
can.Bus.SetCCCR_CCE(0)
|
||||
can.Bus.SetCCCR_INIT(0)
|
||||
timeout = 10000
|
||||
for can.Bus.GetCCCR_INIT() != 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errCANTimeout
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop puts the FDCAN peripheral back into initialization mode.
|
||||
func (can *CAN) Stop() error {
|
||||
can.Bus.SetCCCR_INIT(1)
|
||||
timeout := 10000
|
||||
for can.Bus.GetCCCR_INIT() == 0 {
|
||||
timeout--
|
||||
if timeout == 0 {
|
||||
return errCANTimeout
|
||||
}
|
||||
}
|
||||
can.Bus.SetCCCR_CCE(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// txFIFOLevel implements [CAN.TxFIFOLevel].
|
||||
func (can *CAN) txFIFOLevel() (int, int) {
|
||||
free := int(can.Bus.TXFQS.Get() & 0x07) // TFFL[2:0]
|
||||
return sramcanTFQNbr - free, sramcanTFQNbr
|
||||
}
|
||||
|
||||
// tx implements [CAN.Tx].
|
||||
func (can *CAN) tx(id canID, flags canFlags, data []byte) error {
|
||||
if can.Bus.TXFQS.Get()&0x00200000 != 0 { // TFQF bit
|
||||
return errCANTxFifoFull
|
||||
}
|
||||
|
||||
length := byte(len(data))
|
||||
if length > 64 {
|
||||
length = 64
|
||||
}
|
||||
|
||||
// Use FD framing if configured to always use FD, or if data exceeds classic CAN max.
|
||||
isFD := flags&canFlagFDF != 0 || length > 8
|
||||
|
||||
putIndex := (can.Bus.TXFQS.Get() >> 16) & 0x03 // TFQPI[1:0]
|
||||
txAddr := can.sramBase() + sramcanTFQSA + uintptr(putIndex)*sramcanTFQSize
|
||||
|
||||
// Header word 1: identifier and flags.
|
||||
var w1 uint32
|
||||
if flags&canFlagESI != 0 {
|
||||
w1 = (id & 0x1FFFFFFF) | fdcanElementMaskXTD
|
||||
} else {
|
||||
w1 = (id & 0x7FF) << 18
|
||||
}
|
||||
|
||||
// Header word 2: DLC, FD/BRS flags.
|
||||
dlc := lengthToDLC(length)
|
||||
w2 := uint32(dlc) << 16
|
||||
if isFD {
|
||||
w2 |= fdcanElementMaskFDF | fdcanElementMaskBRS
|
||||
}
|
||||
|
||||
*(*uint32)(unsafe.Pointer(txAddr)) = w1
|
||||
*(*uint32)(unsafe.Pointer(txAddr + 4)) = w2
|
||||
|
||||
// Copy data with 32-bit word access (Cortex-M0+).
|
||||
for w := byte(0); w < (length+3)/4; w++ {
|
||||
var word uint32
|
||||
base := w * 4
|
||||
for b := byte(0); b < 4 && base+b < length; b++ {
|
||||
word |= uint32(data[base+b]) << (b * 8)
|
||||
}
|
||||
*(*uint32)(unsafe.Pointer(txAddr + 8 + uintptr(w)*4)) = word
|
||||
}
|
||||
|
||||
can.Bus.TXBAR.Set(1 << putIndex)
|
||||
return nil
|
||||
}
|
||||
|
||||
// rxFIFOLevel implements [CAN.RxFIFOLevel].
|
||||
// Returns 0,0 when interrupt-driven (messages delivered via callback).
|
||||
func (can *CAN) rxFIFOLevel() (int, int) {
|
||||
if canInstances[can.instance] != nil {
|
||||
return 0, 0
|
||||
}
|
||||
level := int(can.Bus.RXF0S.Get() & 0x0F) // F0FL[3:0]
|
||||
return level, sramcanRF0Nbr
|
||||
}
|
||||
|
||||
// setRxCallback implements [CAN.SetRxCallback].
|
||||
// When cb is non-nil, interrupt-driven receive is enabled on RX FIFO 0.
|
||||
// The CAN.Interrupt field must be initialized with interrupt.New in the board file.
|
||||
func (can *CAN) setRxCallback(cb canRxCallback) {
|
||||
canRxCB[can.instance] = cb
|
||||
if cb != nil {
|
||||
canInstances[can.instance] = can
|
||||
// Enable RX FIFO 0 new message interrupt, routed to interrupt line 0.
|
||||
can.Bus.SetIE_RF0NE(1)
|
||||
can.Bus.SetILS_RxFIFO0(0)
|
||||
can.Bus.SetILE_EINT0(1)
|
||||
can.Interrupt.Enable()
|
||||
} else {
|
||||
can.Bus.SetIE_RF0NE(0)
|
||||
canInstances[can.instance] = nil
|
||||
}
|
||||
}
|
||||
|
||||
// rxPoll implements [CAN.RxPoll].
|
||||
// No-op when interrupt-driven receive is active.
|
||||
func (can *CAN) rxPoll() error {
|
||||
if canInstances[can.instance] != nil {
|
||||
return nil
|
||||
}
|
||||
cb := canRxCB[can.instance]
|
||||
if cb == nil {
|
||||
return nil
|
||||
}
|
||||
processRxFIFO0(can, cb)
|
||||
return nil
|
||||
}
|
||||
|
||||
// processRxFIFO0 drains RX FIFO 0 and delivers each message to cb.
|
||||
// Used by both rxPoll (poll mode) and canHandleInterrupt (interrupt mode).
|
||||
func processRxFIFO0(can *CAN, cb canRxCallback) {
|
||||
for can.Bus.RXF0S.Get()&0x0F != 0 {
|
||||
getIndex := (can.Bus.RXF0S.Get() >> 8) & 0x03 // F0GI[1:0]
|
||||
rxAddr := can.sramBase() + sramcanRF0SA + uintptr(getIndex)*sramcanRF0Size
|
||||
|
||||
w1 := *(*uint32)(unsafe.Pointer(rxAddr))
|
||||
w2 := *(*uint32)(unsafe.Pointer(rxAddr + 4))
|
||||
|
||||
extendedID := w1&fdcanElementMaskXTD != 0
|
||||
var id uint32
|
||||
var flags uint32
|
||||
if extendedID {
|
||||
flags |= canFlagIDE
|
||||
id = w1 & fdcanElementMaskEXTID
|
||||
} else {
|
||||
id = (w1 & fdcanElementMaskSTDID) >> 18
|
||||
}
|
||||
|
||||
timestamp := w2 & fdcanElementMaskTS
|
||||
dlc := byte((w2 & fdcanElementMaskDLC) >> 16)
|
||||
isFD := w2&fdcanElementMaskFDF != 0
|
||||
|
||||
if isFD {
|
||||
flags |= canFlagFDF
|
||||
}
|
||||
if w1&fdcanElementMaskRTR != 0 {
|
||||
flags |= canFlagRTR
|
||||
}
|
||||
if w2&fdcanElementMaskBRS != 0 {
|
||||
flags |= canFlagBRS
|
||||
}
|
||||
if w1&fdcanElementMaskESI != 0 {
|
||||
flags |= canFlagESI
|
||||
}
|
||||
|
||||
dataLen := dlcToLength(dlc)
|
||||
if !isFD && dataLen > 8 {
|
||||
dataLen = 8
|
||||
}
|
||||
var buf [64]byte
|
||||
for w := byte(0); w < (dataLen+3)/4; w++ {
|
||||
word := *(*uint32)(unsafe.Pointer(rxAddr + 8 + uintptr(w)*4))
|
||||
base := w * 4
|
||||
for b := byte(0); b < 4 && base+b < dataLen; b++ {
|
||||
buf[base+b] = byte(word >> (b * 8))
|
||||
}
|
||||
}
|
||||
|
||||
// Acknowledge before callback so the FIFO slot is freed.
|
||||
can.Bus.RXF0A.Set(uint32(getIndex))
|
||||
cb(buf[:dataLen], id, timestamp, flags)
|
||||
}
|
||||
}
|
||||
|
||||
// canHandleInterrupt is the shared interrupt handler for FDCAN interrupt line 0 (IRQ_TIM16).
|
||||
// Both FDCAN1 and FDCAN2 share this IRQ vector.
|
||||
func canHandleInterrupt(interrupt.Interrupt) {
|
||||
for i := range canInstances {
|
||||
can := canInstances[i]
|
||||
if can == nil {
|
||||
continue
|
||||
}
|
||||
ir := can.Bus.IR.Get()
|
||||
if ir&FDCAN_IT_RX_FIFO0_NEW_MESSAGE != 0 {
|
||||
can.Bus.IR.Set(FDCAN_IT_RX_FIFO0_NEW_MESSAGE) // Write 1 to clear
|
||||
if cb := canRxCB[i]; cb != nil {
|
||||
processRxFIFO0(can, cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigureFilter configures a message acceptance filter.
|
||||
func (can *CAN) ConfigureFilter(config CANFilterConfig) error {
|
||||
base := can.sramBase()
|
||||
|
||||
if config.IsExtendedID {
|
||||
if config.Index >= sramcanFLENbr {
|
||||
return errors.New("CAN: filter index out of range")
|
||||
}
|
||||
|
||||
filterAddr := base + sramcanFLESA + (uintptr(config.Index) * sramcanFLESize)
|
||||
|
||||
w1 := (uint32(config.Config) << 29) | (config.ID1 & 0x1FFFFFFF)
|
||||
w2 := (uint32(config.Type) << 30) | (config.ID2 & 0x1FFFFFFF)
|
||||
|
||||
*(*uint32)(unsafe.Pointer(filterAddr)) = w1
|
||||
*(*uint32)(unsafe.Pointer(filterAddr + 4)) = w2
|
||||
} else {
|
||||
if config.Index >= sramcanFLSNbr {
|
||||
return errors.New("CAN: filter index out of range")
|
||||
}
|
||||
|
||||
filterAddr := base + sramcanFLSSA + (uintptr(config.Index) * sramcanFLSSize)
|
||||
|
||||
w := (uint32(config.Type) << 30) |
|
||||
(uint32(config.Config) << 27) |
|
||||
((config.ID1 & 0x7FF) << 16) |
|
||||
(config.ID2 & 0x7FF)
|
||||
|
||||
*(*uint32)(unsafe.Pointer(filterAddr)) = w
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (can *CAN) sramBase() uintptr {
|
||||
if can.Bus == stm32.FDCAN2 {
|
||||
return uintptr(sramcanBase) + sramcanSize
|
||||
}
|
||||
return uintptr(sramcanBase)
|
||||
}
|
||||
|
||||
// fdcanNominalBitTiming returns prescaler and segment values for the nominal (arbitration) phase.
|
||||
// STM32G0 FDCAN clock = 64 MHz, 16 time quanta per bit, ~80% sample point.
|
||||
func fdcanNominalBitTiming(rate CANTransferRate) (brp, tseg1, tseg2, sjw uint32, err error) {
|
||||
switch rate {
|
||||
case FDCANTransferRate125kbps:
|
||||
return 32, 13, 2, 4, nil
|
||||
case FDCANTransferRate250kbps:
|
||||
return 16, 13, 2, 4, nil
|
||||
case FDCANTransferRate500kbps:
|
||||
return 8, 13, 2, 4, nil
|
||||
case FDCANTransferRate1000kbps:
|
||||
return 4, 13, 2, 4, nil
|
||||
default:
|
||||
return 0, 0, 0, 0, errCANInvalidTransferRate
|
||||
}
|
||||
}
|
||||
|
||||
// fdcanDataBitTiming returns prescaler and segment values for the data phase (FD).
|
||||
func fdcanDataBitTiming(rate CANTransferRate) (brp, tseg1, tseg2, sjw uint32, err error) {
|
||||
switch rate {
|
||||
case FDCANTransferRate125kbps:
|
||||
return 32, 13, 2, 4, nil
|
||||
case FDCANTransferRate250kbps:
|
||||
return 16, 13, 2, 4, nil
|
||||
case FDCANTransferRate500kbps:
|
||||
return 8, 13, 2, 4, nil
|
||||
case FDCANTransferRate1000kbps:
|
||||
return 4, 13, 2, 4, nil
|
||||
case FDCANTransferRate2000kbps:
|
||||
return 2, 13, 2, 4, nil
|
||||
case FDCANTransferRate4000kbps:
|
||||
return 1, 13, 2, 4, nil
|
||||
default:
|
||||
return 0, 0, 0, 0, errCANInvalidTransferRateFD
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
//go:build stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
)
|
||||
|
||||
// This variant of the GPIO input interrupt logic is for
|
||||
// STM32G0 chips which use a different EXTI register structure
|
||||
// with IMR1, RTSR1, FTSR1, and separate RPR1/FPR1 pending registers.
|
||||
|
||||
// Callbacks for pin interrupt events
|
||||
var pinCallbacks [16]func(Pin)
|
||||
|
||||
// The pin currently associated with interrupt callback
|
||||
// for a given slot.
|
||||
var interruptPins [16]Pin
|
||||
|
||||
// SetInterrupt sets an interrupt to be executed when a particular pin changes
|
||||
// state. The pin should already be configured as an input, including a pull up
|
||||
// or down if no external pull is provided.
|
||||
//
|
||||
// This call will replace a previously set callback on this pin. You can pass a
|
||||
// nil func to unset the pin change interrupt. If you do so, the change
|
||||
// parameter is ignored and can be set to any value (such as 0).
|
||||
func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
|
||||
port := uint32(uint8(p) / 16)
|
||||
pin := uint8(p) % 16
|
||||
|
||||
enableEXTIConfigRegisters()
|
||||
|
||||
if callback == nil {
|
||||
stm32.EXTI.IMR1.ClearBits(1 << pin)
|
||||
pinCallbacks[pin] = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
if pinCallbacks[pin] != nil {
|
||||
// The pin was already configured.
|
||||
// To properly re-configure a pin, unset it first and set a new
|
||||
// configuration.
|
||||
return ErrNoPinChangeChannel
|
||||
}
|
||||
|
||||
// Set the callback now (before the interrupt is enabled) to avoid
|
||||
// possible race condition
|
||||
pinCallbacks[pin] = callback
|
||||
interruptPins[pin] = p
|
||||
|
||||
crReg := getEXTIConfigRegister(pin)
|
||||
shift := (pin & 0x3) * 4
|
||||
crReg.ReplaceBits(port, 0xf, shift)
|
||||
|
||||
if (change & PinRising) != 0 {
|
||||
stm32.EXTI.RTSR1.SetBits(1 << pin)
|
||||
}
|
||||
if (change & PinFalling) != 0 {
|
||||
stm32.EXTI.FTSR1.SetBits(1 << pin)
|
||||
}
|
||||
stm32.EXTI.IMR1.SetBits(1 << pin)
|
||||
|
||||
intr := p.registerInterrupt()
|
||||
intr.SetPriority(0)
|
||||
intr.Enable()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePinInterrupt(pin uint8) {
|
||||
// STM32G0 has separate rising and falling pending registers
|
||||
// Check both and clear the appropriate one
|
||||
mask := uint32(1 << pin)
|
||||
if stm32.EXTI.RPR1.HasBits(mask) {
|
||||
// Writing 1 to the pending register clears the pending flag
|
||||
stm32.EXTI.RPR1.Set(mask)
|
||||
|
||||
callback := pinCallbacks[pin]
|
||||
if callback != nil {
|
||||
callback(interruptPins[pin])
|
||||
}
|
||||
}
|
||||
if stm32.EXTI.FPR1.HasBits(mask) {
|
||||
// Writing 1 to the pending register clears the pending flag
|
||||
stm32.EXTI.FPR1.Set(mask)
|
||||
|
||||
callback := pinCallbacks[pin]
|
||||
if callback != nil {
|
||||
callback(interruptPins[pin])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
//go:build stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
// SPI on STM32G0 uses 16-bit registers
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// SPIConfig is used to store config info for SPI.
|
||||
type SPIConfig struct {
|
||||
Frequency uint32
|
||||
SCK Pin
|
||||
SDO Pin
|
||||
SDI Pin
|
||||
LSBFirst bool
|
||||
Mode uint8
|
||||
}
|
||||
|
||||
// Configure is intended to setup the STM32 SPI peripheral
|
||||
func (spi *SPI) Configure(config SPIConfig) error {
|
||||
// disable SPI interface before any configuration changes
|
||||
spi.Bus.CR1.ClearBits(stm32.SPI_CR1_SPE)
|
||||
|
||||
// enable clock for SPI
|
||||
enableAltFuncClock(unsafe.Pointer(spi.Bus))
|
||||
|
||||
// init pins - use defaults if not specified
|
||||
if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
|
||||
config.SCK = SPI0_SCK_PIN
|
||||
config.SDO = SPI0_SDO_PIN
|
||||
config.SDI = SPI0_SDI_PIN
|
||||
}
|
||||
spi.configurePins(config)
|
||||
|
||||
// Get SPI baud rate divisor
|
||||
conf := spi.getBaudRate(config)
|
||||
|
||||
// set polarity and phase on the SPI interface
|
||||
switch config.Mode {
|
||||
case Mode1:
|
||||
conf |= stm32.SPI_CR1_CPHA
|
||||
case Mode2:
|
||||
conf |= stm32.SPI_CR1_CPOL
|
||||
case Mode3:
|
||||
conf |= stm32.SPI_CR1_CPOL | stm32.SPI_CR1_CPHA
|
||||
}
|
||||
|
||||
// set bit transfer order
|
||||
if config.LSBFirst {
|
||||
conf |= stm32.SPI_CR1_LSBFIRST
|
||||
}
|
||||
|
||||
// set SPI master
|
||||
conf |= stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI
|
||||
|
||||
// use software CS (GPIO) by default
|
||||
conf |= stm32.SPI_CR1_SSM
|
||||
|
||||
// Set CR1 configuration WITHOUT enabling SPE yet
|
||||
// (STM32G0 requires CR2 DS bits to be set before SPE is enabled)
|
||||
spi.Bus.CR1.Set(uint16(conf))
|
||||
|
||||
// Series-specific configuration to set 8-bit transfer mode (must be done before SPE)
|
||||
spi.config8Bits()
|
||||
|
||||
// Now enable SPI
|
||||
spi.Bus.SetCR1_SPE(1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Transfer writes/reads a single byte using the SPI interface.
|
||||
func (spi *SPI) Transfer(w byte) (byte, error) {
|
||||
// STM32G0 requires 8-bit access to DR for 8-bit transfers
|
||||
// Using 16-bit access causes data packing issues
|
||||
dr := (*volatile.Register8)(unsafe.Pointer(&spi.Bus.DR))
|
||||
|
||||
// Write data to be transmitted to the SPI data register (8-bit access)
|
||||
dr.Set(w)
|
||||
|
||||
// Wait until transmit complete
|
||||
for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) {
|
||||
}
|
||||
|
||||
// Wait until receive complete
|
||||
for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) {
|
||||
}
|
||||
|
||||
// Wait until SPI is not busy
|
||||
for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) {
|
||||
}
|
||||
|
||||
// Return received data from SPI data register (8-bit access)
|
||||
return dr.Get(), nil
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//go:build stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
// Peripheral abstraction layer for UARTs on the stm32g0 family.
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// UART representation
|
||||
type UART struct {
|
||||
Buffer *RingBuffer
|
||||
Bus *stm32.USART_Type
|
||||
Interrupt interrupt.Interrupt
|
||||
TxAltFuncSelector uint8
|
||||
RxAltFuncSelector uint8
|
||||
|
||||
// Registers specific to the chip
|
||||
rxReg *volatile.Register32
|
||||
txReg *volatile.Register32
|
||||
statusReg *volatile.Register32
|
||||
txEmptyFlag uint32
|
||||
}
|
||||
|
||||
// Configure the UART.
|
||||
func (uart *UART) Configure(config UARTConfig) {
|
||||
// Default baud rate to 115200.
|
||||
if config.BaudRate == 0 {
|
||||
config.BaudRate = 115200
|
||||
}
|
||||
|
||||
// Set the GPIO pins to defaults if they're not set
|
||||
if config.TX == 0 && config.RX == 0 {
|
||||
config.TX = UART_TX_PIN
|
||||
config.RX = UART_RX_PIN
|
||||
}
|
||||
|
||||
// STM32 families have different, but compatible, registers for
|
||||
// basic UART functions. For each family populate the registers
|
||||
// into `uart`.
|
||||
uart.setRegisters()
|
||||
|
||||
// Enable USART clock
|
||||
enableAltFuncClock(unsafe.Pointer(uart.Bus))
|
||||
|
||||
uart.configurePins(config)
|
||||
|
||||
// Set baud rate
|
||||
uart.SetBaudRate(config.BaudRate)
|
||||
|
||||
// Enable USART port, tx, rx and rx interrupts
|
||||
// STM32G0 uses CR1_FIFO_ENABLED register
|
||||
uart.Bus.CR1_FIFO_ENABLED.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
|
||||
|
||||
// Enable RX IRQ
|
||||
uart.Interrupt.SetPriority(0xc0)
|
||||
uart.Interrupt.Enable()
|
||||
}
|
||||
|
||||
// handleInterrupt should be called from the appropriate interrupt handler for
|
||||
// this UART instance.
|
||||
func (uart *UART) handleInterrupt(interrupt.Interrupt) {
|
||||
uart.Receive(byte((uart.rxReg.Get() & 0xFF)))
|
||||
}
|
||||
|
||||
// SetBaudRate sets the communication speed for the UART. Defer to chip-specific
|
||||
// routines for calculation
|
||||
func (uart *UART) SetBaudRate(br uint32) {
|
||||
divider := uart.getBaudRateDivisor(br)
|
||||
uart.Bus.BRR.Set(divider)
|
||||
}
|
||||
|
||||
// WriteByte writes a byte of data to the UART.
|
||||
func (uart *UART) writeByte(c byte) error {
|
||||
uart.txReg.Set(uint32(c))
|
||||
|
||||
for !uart.statusReg.HasBits(uart.txEmptyFlag) {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uart *UART) flush() {}
|
||||
@@ -0,0 +1,173 @@
|
||||
//go:build stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// WindowWatchdog provides access to the Window Watchdog (WWDG) peripheral.
|
||||
// Unlike IWDG, WWDG must be refreshed within a specific window - not too early
|
||||
// and not too late. This provides protection against both runaway code and
|
||||
// code that gets stuck in a loop refreshing the watchdog.
|
||||
var WindowWatchdog = &windowWatchdogImpl{}
|
||||
|
||||
// WindowWatchdogConfig holds configuration for the window watchdog timer.
|
||||
// The timeout (in microseconds) before the watchdog fires.
|
||||
// The valid range depends on System frequency.
|
||||
// At 64MHz: ~64µs to ~524ms
|
||||
type WindowWatchdogConfig struct {
|
||||
TimeoutMicros uint32
|
||||
|
||||
// The window value as a percentage of timeout (0-100).
|
||||
// Refresh must occur when counter is below this percentage of max.
|
||||
// Default (0) sets window to 100% (no window restriction).
|
||||
WindowPercent uint8
|
||||
}
|
||||
|
||||
// WWDG prescaler values
|
||||
const (
|
||||
wwdgPrescaler1 = 0 // CK Counter Clock (PCLK/4096) / 1
|
||||
wwdgPrescaler2 = 1 // CK Counter Clock (PCLK/4096) / 2
|
||||
wwdgPrescaler4 = 2 // CK Counter Clock (PCLK/4096) / 4
|
||||
wwdgPrescaler8 = 3 // CK Counter Clock (PCLK/4096) / 8
|
||||
wwdgPrescaler16 = 4 // CK Counter Clock (PCLK/4096) / 16
|
||||
wwdgPrescaler32 = 5 // CK Counter Clock (PCLK/4096) / 32
|
||||
wwdgPrescaler64 = 6 // CK Counter Clock (PCLK/4096) / 64
|
||||
wwdgPrescaler128 = 7 // CK Counter Clock (PCLK/4096) / 128
|
||||
)
|
||||
|
||||
// WWDG counter limits
|
||||
const (
|
||||
wwdgCounterMin = 0x40 // Minimum counter value (T6 must be set)
|
||||
wwdgCounterMax = 0x7F // Maximum counter value (7 bits)
|
||||
wwdgWindowMax = 0x7F // Maximum window value
|
||||
)
|
||||
|
||||
type windowWatchdogImpl struct {
|
||||
counter uint8 // Configured counter reload value
|
||||
prescaler uint8 // Configured prescaler
|
||||
}
|
||||
|
||||
// Configure the window watchdog.
|
||||
//
|
||||
// This method should not be called after the watchdog is started.
|
||||
// The WWDG cannot be disabled once started, except by a system reset.
|
||||
//
|
||||
// Timeout formula: t_WWDG = (1/PCLK) × 4096 × 2^WDGTB × (T[5:0] + 1)
|
||||
// Where T[5:0] = counter value - 0x40
|
||||
// Refer RM0444 Rev 6 861/1384
|
||||
func (wd *windowWatchdogImpl) Configure(config WindowWatchdogConfig) error {
|
||||
// Enable WWDG clock
|
||||
enableAltFuncClock(unsafe.Pointer(stm32.WWDG))
|
||||
|
||||
// Calculate prescaler and counter value from timeout
|
||||
// Base tick = PCLK / 4096
|
||||
// With prescaler: tick = PCLK / (4096 * 2^prescaler)
|
||||
// Timeout = tick * (counter - 0x3F)
|
||||
|
||||
pclk := CPUFrequency() // Assuming PCLK = CPU frequency (no APB prescaler)
|
||||
baseTick := (4096 * 1000000) / pclk // Base tick in nanoseconds * 1000 for precision
|
||||
|
||||
timeout := config.TimeoutMicros
|
||||
if timeout == 0 {
|
||||
timeout = 10000 // Default 10ms
|
||||
}
|
||||
|
||||
// Find the best prescaler and counter-combination
|
||||
var bestPrescaler uint8
|
||||
var bestCounter uint8
|
||||
found := false
|
||||
|
||||
for prescaler := uint8(0); prescaler <= 7; prescaler++ {
|
||||
// Tick duration in nanoseconds * 1000
|
||||
tickNs := baseTick << prescaler
|
||||
|
||||
// Counter value needed (counter - 0x3F = timeout / tick)
|
||||
// Rearranged: counter = (timeout * 1000 / tickNs) + 0x3F
|
||||
counterVal := (uint32(timeout) * 1000000 / tickNs) + 0x3F
|
||||
|
||||
if counterVal >= wwdgCounterMin && counterVal <= wwdgCounterMax {
|
||||
bestPrescaler = prescaler
|
||||
bestCounter = uint8(counterVal)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
// Use maximum timeout
|
||||
bestPrescaler = wwdgPrescaler128
|
||||
bestCounter = wwdgCounterMax
|
||||
}
|
||||
|
||||
wd.prescaler = bestPrescaler
|
||||
wd.counter = bestCounter
|
||||
|
||||
// Calculate window value
|
||||
windowVal := uint8(wwdgWindowMax)
|
||||
if config.WindowPercent > 0 && config.WindowPercent < 100 {
|
||||
// Window = 0x40 + ((counter - 0x40) * percent / 100)
|
||||
counterRange := uint16(bestCounter) - wwdgCounterMin
|
||||
windowOffset := (counterRange * uint16(config.WindowPercent)) / 100
|
||||
windowVal = uint8(wwdgCounterMin + windowOffset)
|
||||
}
|
||||
stm32.WWDG.CFR.Set((uint32(bestPrescaler) << stm32.WWDG_CFR_WDGTB_Pos) | uint32(windowVal))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start enables the window watchdog.
|
||||
// Once started, the WWDG cannot be disabled except by a system reset.
|
||||
func (wd *windowWatchdogImpl) Start() error {
|
||||
stm32.WWDG.CR.Set(uint32(wd.counter) | (1 << 7))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update refreshes the window watchdog counter.
|
||||
// This must be called within the configured window to prevent a reset.
|
||||
// Calling too early (counter > window) or too late (counter <= 0x3F) causes reset.
|
||||
func (wd *windowWatchdogImpl) Update() {
|
||||
stm32.WWDG.CR.Set(uint32(wd.counter) | (1 << 7))
|
||||
}
|
||||
|
||||
// GetCounter returns the current WWDG counter value.
|
||||
// Useful for timing refresh operations within the window.
|
||||
func (wd *windowWatchdogImpl) GetCounter() uint8 {
|
||||
return uint8(stm32.WWDG.CR.Get() & 0x7F)
|
||||
}
|
||||
|
||||
// EnableEarlyWakeupInterrupt enables the Early Wakeup Interrupt (EWI).
|
||||
// The EWI is triggered when the counter reaches 0x40, giving the application
|
||||
// a chance to refresh the watchdog or perform cleanup before reset.
|
||||
func (wd *windowWatchdogImpl) EnableEarlyWakeupInterrupt() {
|
||||
stm32.WWDG.CFR.SetBits(stm32.WWDG_CFR_EWI)
|
||||
}
|
||||
|
||||
// ClearEarlyWakeupFlag clears the Early Wakeup Interrupt flag.
|
||||
// Must be called in the interrupt handler.
|
||||
func (wd *windowWatchdogImpl) ClearEarlyWakeupFlag() {
|
||||
stm32.WWDG.SR.Set(0) // Write 0 to clear EWIF
|
||||
}
|
||||
|
||||
// IsEarlyWakeupFlagSet returns true if the Early Wakeup Interrupt flag is set.
|
||||
func (wd *windowWatchdogImpl) IsEarlyWakeupFlagSet() bool {
|
||||
return stm32.WWDG.SR.Get()&1 != 0
|
||||
}
|
||||
|
||||
// GetMaxTimeout returns the maximum timeout in microseconds for the current PCLK.
|
||||
// Max timeout = (1/PCLK) × 4096 × 128 × 64
|
||||
// At 64MHz: ~524ms = 524288µs
|
||||
func (wd *windowWatchdogImpl) GetMaxTimeout() uint32 {
|
||||
pclk := uint64(CPUFrequency())
|
||||
return uint32((uint64(4096) * 128 * 64 * 1000000) / pclk)
|
||||
}
|
||||
|
||||
// GetMinTimeout returns the minimum timeout in microseconds for the current PCLK.
|
||||
// Min timeout = (1/PCLK) × 4096 × 1 × 1
|
||||
// At 64MHz: ~64µs
|
||||
func (wd *windowWatchdogImpl) GetMinTimeout() uint32 {
|
||||
pclk := uint64(CPUFrequency())
|
||||
return uint32((uint64(4096) * 1000000) / pclk)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//go:build stm32g0
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
func getEXTIConfigRegister(pin uint8) *volatile.Register32 {
|
||||
switch (pin & 0xf) / 4 {
|
||||
case 0:
|
||||
return &stm32.EXTI.EXTICR1
|
||||
case 1:
|
||||
return &stm32.EXTI.EXTICR2
|
||||
case 2:
|
||||
return &stm32.EXTI.EXTICR3
|
||||
case 3:
|
||||
return &stm32.EXTI.EXTICR4
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func enableEXTIConfigRegisters() {
|
||||
// EXTI configuration is in the EXTI peripheral on STM32G0, no enable needed
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
//go:build stm32l0
|
||||
|
||||
package machine
|
||||
|
||||
// The STM32L0 series of MCUs has a different type of flash than other STM32
|
||||
// series chips. The programming interface is different, and the flash is erased
|
||||
// to zero bits instead of one bits as on most flash. So this requires a
|
||||
// different implementation.
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// compile-time check for ensuring we fulfill BlockDevice interface
|
||||
var _ BlockDevice = flashBlockDevice{}
|
||||
|
||||
var Flash flashBlockDevice
|
||||
|
||||
type flashBlockDevice struct {
|
||||
}
|
||||
|
||||
// ReadAt reads the given number of bytes from the block device.
|
||||
func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
|
||||
return 0, errFlashCannotReadPastEOF
|
||||
}
|
||||
|
||||
data := unsafe.Slice((*byte)(unsafe.Pointer(FlashDataStart()+uintptr(off))), len(p))
|
||||
copy(p, data)
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// WriteAt writes the given number of bytes to the block device.
|
||||
// Only word-sized (32 bits) length data can be programmed.
|
||||
// If the length of p is not long enough it will be padded with zero bytes.
|
||||
// This method assumes that the destination is already erased.
|
||||
func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
|
||||
return 0, errFlashCannotWritePastEOF
|
||||
}
|
||||
if uintptr(off)%4 != 0 {
|
||||
// Offset must be aligned on a word boundary.
|
||||
return 0, errFlashCannotWriteData
|
||||
}
|
||||
|
||||
unlockFlash()
|
||||
defer lockFlash()
|
||||
|
||||
// Write words in this area.
|
||||
for i := 0; i < len(p); i += 4 {
|
||||
// Construct the word to write.
|
||||
word := uint32(p[i])
|
||||
if i+1 < len(p) {
|
||||
word |= uint32(p[i+1]) << 8
|
||||
}
|
||||
if i+2 < len(p) {
|
||||
word |= uint32(p[i+2]) << 16
|
||||
}
|
||||
if i+3 < len(p) {
|
||||
word |= uint32(p[i+3]) << 24
|
||||
}
|
||||
|
||||
// Find the pointer address to write.
|
||||
address := FlashDataStart() + uintptr(off) + uintptr(i)
|
||||
|
||||
// Write the word to flash.
|
||||
(*volatile.Register32)(unsafe.Pointer(address)).Set(word)
|
||||
|
||||
// Check for any errors.
|
||||
if stm32.FLASH.SR.Get()&(stm32.Flash_SR_WRPERR|stm32.Flash_SR_NOTZEROERR|stm32.Flash_SR_SIZERR) != 0 {
|
||||
return i, errFlashCannotWriteData
|
||||
}
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Size returns the number of bytes in this block device.
|
||||
func (f flashBlockDevice) Size() int64 {
|
||||
return int64(FlashDataEnd() - FlashDataStart())
|
||||
}
|
||||
|
||||
// WriteBlockSize returns the block size in which data can be written to
|
||||
// memory. It can be used by a client to optimize writes, non-aligned writes
|
||||
// should always work correctly.
|
||||
func (f flashBlockDevice) WriteBlockSize() int64 {
|
||||
return 4
|
||||
}
|
||||
|
||||
func eraseBlockSize() int64 {
|
||||
return 128
|
||||
}
|
||||
|
||||
// EraseBlockSize returns the smallest erasable area on this particular chip
|
||||
// in bytes. This is used for the block size in EraseBlocks.
|
||||
// It must be a power of two, and may be as small as 1. A typical size is 4096.
|
||||
func (f flashBlockDevice) EraseBlockSize() int64 {
|
||||
return eraseBlockSize()
|
||||
}
|
||||
|
||||
// EraseBlocks erases the given number of blocks. An implementation may
|
||||
// transparently coalesce ranges of blocks into larger bundles if the chip
|
||||
// supports this. The start and len parameters are in block numbers, use
|
||||
// EraseBlockSize to map addresses to blocks.
|
||||
// Note that block 0 should map to the address of FlashDataStart().
|
||||
func (f flashBlockDevice) EraseBlocks(start, len int64) error {
|
||||
// Flash needs to be unlocked to be able to erase it.
|
||||
unlockFlash()
|
||||
defer lockFlash()
|
||||
|
||||
// Set the flash programming mode to erase a page.
|
||||
// Note: lockFlash() will reset these flags to 0 so we don't need to
|
||||
// explicitly set them to 0.
|
||||
stm32.FLASH.PECR.Set(stm32.Flash_PECR_ERASE | stm32.Flash_PECR_PROG)
|
||||
|
||||
// Erase all pages in this range.
|
||||
for i := uintptr(start); i < uintptr(start)+uintptr(len); i++ {
|
||||
// Find the pointer address somewhere in the page to erase.
|
||||
address := FlashDataStart() + i*uintptr(eraseBlockSize())
|
||||
|
||||
// To erase, write any value to that address.
|
||||
(*volatile.Register32)(unsafe.Pointer(address)).Set(uint32(address))
|
||||
|
||||
// Check for any errors.
|
||||
// The only error (that is not a programming error) that could happen is
|
||||
// if a row is in a protected sector.
|
||||
if stm32.FLASH.SR.Get()&(stm32.Flash_SR_WRPERR|stm32.Flash_SR_SIZERR) != 0 {
|
||||
return errFlashCannotErasePage
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func unlockFlash() {
|
||||
// Make sure the flash peripheral clock is enabled.
|
||||
stm32.RCC.AHBENR.SetBits(stm32.RCC_AHBENR_MIFEN)
|
||||
|
||||
// Wait for the flash memory not to be busy.
|
||||
for stm32.FLASH.GetSR_BSY() != 0 {
|
||||
}
|
||||
|
||||
// Disable interrupts while writing, since no memory operations may happen
|
||||
// while the unlock sequence is ongoing.
|
||||
mask := interrupt.Disable()
|
||||
|
||||
// Remove PELOCK bit.
|
||||
stm32.FLASH.PEKEYR.Set(0x89ABCDEF)
|
||||
stm32.FLASH.PEKEYR.Set(0x02030405)
|
||||
|
||||
// Remove PRGLOCK bit.
|
||||
stm32.FLASH.PRGKEYR.Set(0x8C9DAEBF)
|
||||
stm32.FLASH.PRGKEYR.Set(0x13141516)
|
||||
|
||||
interrupt.Restore(mask)
|
||||
}
|
||||
|
||||
func lockFlash() {
|
||||
// Set PELOCK to 1, which also automatically sets PRGLOCK to 1.
|
||||
stm32.FLASH.PECR.Set(stm32.Flash_PECR_PELOCK)
|
||||
}
|
||||
+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.
|
||||
|
||||
+72
-37
@@ -81,21 +81,6 @@ func usbSerial() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// strToUTF16LEDescriptor converts a utf8 string into a string descriptor
|
||||
// note: the following code only converts ascii characters to UTF16LE. In order
|
||||
// to do a "proper" conversion, we would need to pull in the 'unicode/utf16'
|
||||
// package, which at the time this was written added 512 bytes to the compiled
|
||||
// binary.
|
||||
func strToUTF16LEDescriptor(in string, out []byte) {
|
||||
out[0] = byte(len(out))
|
||||
out[1] = descriptor.TypeString
|
||||
for i, rune := range in {
|
||||
out[(i<<1)+2] = byte(rune)
|
||||
out[(i<<1)+3] = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const cdcLineInfoSize = 7
|
||||
|
||||
var (
|
||||
@@ -137,51 +122,53 @@ var (
|
||||
usbStallHandler [NumberOfUSBEndpoints]func(usb.Setup) bool
|
||||
)
|
||||
|
||||
var usbLangInfo = [4]byte{
|
||||
// length = 4 bytes
|
||||
0x04,
|
||||
// descriptor type = string
|
||||
0x03,
|
||||
// language codes
|
||||
// 0x0409 = English (United States)
|
||||
0x09, 0x04,
|
||||
}
|
||||
|
||||
// sendDescriptor creates and sends the various USB descriptor types that
|
||||
// can be requested by the host.
|
||||
func sendDescriptor(setup usb.Setup) {
|
||||
switch setup.WValueH {
|
||||
case descriptor.TypeConfiguration:
|
||||
sendUSBPacket(0, usbDescriptor.Configuration, setup.WLength)
|
||||
sendDescriptorData(usbDescriptor.Configuration, setup.WLength)
|
||||
return
|
||||
|
||||
case descriptor.TypeDevice:
|
||||
usbDescriptor.Configure(usbVendorID(), usbProductID())
|
||||
sendUSBPacket(0, usbDescriptor.Device, setup.WLength)
|
||||
sendDescriptorData(usbDescriptor.Device, setup.WLength)
|
||||
return
|
||||
|
||||
case descriptor.TypeString:
|
||||
switch setup.WValueL {
|
||||
case 0:
|
||||
usb_trans_buffer[0] = 0x04
|
||||
usb_trans_buffer[1] = 0x03
|
||||
usb_trans_buffer[2] = 0x09
|
||||
usb_trans_buffer[3] = 0x04
|
||||
sendUSBPacket(0, usb_trans_buffer[:4], setup.WLength)
|
||||
sendDescriptorData(usbLangInfo[:], setup.WLength)
|
||||
|
||||
case usb.IPRODUCT:
|
||||
b := usb_trans_buffer[:(len(usbProduct())<<1)+2]
|
||||
strToUTF16LEDescriptor(usbProduct(), b)
|
||||
sendUSBPacket(0, b, setup.WLength)
|
||||
sendDescriptorString(usbProduct(), setup.WLength)
|
||||
|
||||
case usb.IMANUFACTURER:
|
||||
b := usb_trans_buffer[:(len(usbManufacturer())<<1)+2]
|
||||
strToUTF16LEDescriptor(usbManufacturer(), b)
|
||||
sendUSBPacket(0, b, setup.WLength)
|
||||
sendDescriptorString(usbManufacturer(), setup.WLength)
|
||||
|
||||
case usb.ISERIAL:
|
||||
sz := len(usbSerial())
|
||||
if sz == 0 {
|
||||
serial := usbSerial()
|
||||
if len(serial) == 0 {
|
||||
SendZlp()
|
||||
} else {
|
||||
b := usb_trans_buffer[:(sz<<1)+2]
|
||||
strToUTF16LEDescriptor(usbSerial(), b)
|
||||
sendUSBPacket(0, b, setup.WLength)
|
||||
sendDescriptorString(serial, setup.WLength)
|
||||
}
|
||||
}
|
||||
// TODO: why do we do this when WValueL is unknown?
|
||||
return
|
||||
case descriptor.TypeHIDReport:
|
||||
if h, ok := usbDescriptor.HID[setup.WIndex]; ok {
|
||||
sendUSBPacket(0, h, setup.WLength)
|
||||
sendDescriptorData(h, setup.WLength)
|
||||
return
|
||||
}
|
||||
case descriptor.TypeDeviceQualifier:
|
||||
@@ -194,6 +181,39 @@ func sendDescriptor(setup usb.Setup) {
|
||||
return
|
||||
}
|
||||
|
||||
// sendDescriptorString sends a string descriptor, truncating it to fit maxLen or the buffer size.
|
||||
// note: the following code only converts ascii characters to UTF16LE. In order
|
||||
// to do a "proper" conversion, we would need to pull in the 'unicode/utf16'
|
||||
// package, which at the time this was written added 512 bytes to the compiled
|
||||
// binary.
|
||||
// TODO: old comment, re-evaluate
|
||||
func sendDescriptorString(data string, maxLen uint16) {
|
||||
if maxLen < 2 {
|
||||
// Something has gone horribly wrong.
|
||||
SendZlp()
|
||||
return
|
||||
}
|
||||
|
||||
// Clamp the length.
|
||||
maxEncBytes := min(len(usb_trans_buffer), len(udd_ep_control_cache_buffer), int(maxLen))
|
||||
data = data[:min(len(data), (maxEncBytes-2)/2)]
|
||||
|
||||
// Write the header.
|
||||
buf := usb_trans_buffer[:2*len(data)+2]
|
||||
hdr, body := buf[:2], buf[2:]
|
||||
hdr[0] = byte(len(buf))
|
||||
hdr[1] = descriptor.TypeString
|
||||
|
||||
// Convert the string to UTF16.
|
||||
// NOTE: Using range here would cause the length to disagree when multibyte codepoints are present.
|
||||
for i := 0; i < len(data); i++ {
|
||||
body[2*i] = byte(data[i])
|
||||
body[2*i+1] = 0
|
||||
}
|
||||
|
||||
sendUSBPacket(0, buf)
|
||||
}
|
||||
|
||||
func handleStandardSetup(setup usb.Setup) bool {
|
||||
switch setup.BRequest {
|
||||
case usb.GET_STATUS:
|
||||
@@ -206,7 +226,7 @@ func handleStandardSetup(setup usb.Setup) bool {
|
||||
}
|
||||
}
|
||||
|
||||
sendUSBPacket(0, usb_trans_buffer[:2], setup.WLength)
|
||||
sendDescriptorData(usb_trans_buffer[:2], setup.WLength)
|
||||
return true
|
||||
|
||||
case usb.CLEAR_FEATURE:
|
||||
@@ -251,7 +271,7 @@ func handleStandardSetup(setup usb.Setup) bool {
|
||||
|
||||
case usb.GET_CONFIGURATION:
|
||||
usb_trans_buffer[0] = usbConfiguration
|
||||
sendUSBPacket(0, usb_trans_buffer[:1], setup.WLength)
|
||||
sendDescriptorData(usb_trans_buffer[:1], setup.WLength)
|
||||
return true
|
||||
|
||||
case usb.SET_CONFIGURATION:
|
||||
@@ -271,7 +291,7 @@ func handleStandardSetup(setup usb.Setup) bool {
|
||||
|
||||
case usb.GET_INTERFACE:
|
||||
usb_trans_buffer[0] = usbSetInterface
|
||||
sendUSBPacket(0, usb_trans_buffer[:1], setup.WLength)
|
||||
sendDescriptorData(usb_trans_buffer[:1], setup.WLength)
|
||||
return true
|
||||
|
||||
case usb.SET_INTERFACE:
|
||||
@@ -285,6 +305,21 @@ func handleStandardSetup(setup usb.Setup) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// sendDescriptorData sends a descriptor, truncating it to fit maxLen or the buffer size.
|
||||
func sendDescriptorData(data []byte, maxLen uint16) {
|
||||
data = lenToCap(data)
|
||||
data = data[:min(len(data), len(udd_ep_control_cache_buffer), int(maxLen))]
|
||||
sendUSBPacket(0, data)
|
||||
}
|
||||
|
||||
// Set the cap of the slice to the length.
|
||||
// This is safe, but cannot be proven by the compiler.
|
||||
//
|
||||
//go:nobounds
|
||||
func lenToCap(b []byte) []byte {
|
||||
return b[:len(b):len(b)]
|
||||
}
|
||||
|
||||
func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) {
|
||||
if len(usbDescriptor.Device) == 0 {
|
||||
usbDescriptor = descriptor.CDC
|
||||
|
||||
@@ -166,6 +166,19 @@ func Chown(name string, uid, gid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lchown changes the numeric uid and gid of the named file.
|
||||
// If the file is a symbolic link, it changes the uid and gid of the link itself.
|
||||
// If there is an error, it will be of type [*PathError].
|
||||
//
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func Lchown(name string, uid, gid int) error {
|
||||
e := ignoringEINTR(func() error { return syscall.Lchown(name, uid, gid) })
|
||||
if e != nil {
|
||||
return &PathError{Op: "lchown", Path: name, Err: e}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ignoringEINTR makes a function call and repeats it if it returns an
|
||||
// EINTR error. This appears to be required even though we install all
|
||||
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
. "os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
@@ -70,3 +71,38 @@ func TestChownErr(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLchownErr(t *testing.T) {
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
|
||||
t.Log("skipping on " + runtime.GOOS)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
TEST_UID_ROOT = 0
|
||||
TEST_GID_ROOT = 0
|
||||
)
|
||||
|
||||
f := newFile("TestLchown", t)
|
||||
defer Remove(f.Name())
|
||||
defer f.Close()
|
||||
link := filepath.Join(TempDir(), "TestLchownLink")
|
||||
_ = Symlink(f.Name(), link)
|
||||
defer Remove(link)
|
||||
|
||||
// EACCES
|
||||
if err := Lchown(link, TEST_UID_ROOT, TEST_GID_ROOT); err != nil {
|
||||
errCmp := fs.PathError{Op: "lchown", Path: link, Err: errors.New("operation not permitted")}
|
||||
if errors.Is(err, &errCmp) {
|
||||
t.Fatalf("lchown(%s, uid=%v, gid=%v): got '%v', want 'operation not permitted'", link, TEST_UID_ROOT, TEST_GID_ROOT, err)
|
||||
}
|
||||
}
|
||||
|
||||
// ENOENT
|
||||
if err := Chown("invalid", Geteuid(), Getgid()); err != nil {
|
||||
errCmp := fs.PathError{Op: "lchown", Path: "invalid", Err: errors.New("no such file or directory")}
|
||||
if errors.Is(err, &errCmp) {
|
||||
t.Fatalf("chown(%s, uid=%v, gid=%v): got '%v', want 'no such file or directory'", link, Geteuid(), Getegid(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ func ValueOf(i interface{}) Value {
|
||||
return Value{reflectlite.ValueOf(i)}
|
||||
}
|
||||
|
||||
func TypeAssert[T any](v Value) (T, bool) {
|
||||
return reflectlite.TypeAssert[T](v.Value)
|
||||
}
|
||||
|
||||
func (v Value) Type() Type {
|
||||
return toType(v.Value.Type())
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package reflect_test
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
. "reflect"
|
||||
"runtime"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -869,3 +871,78 @@ func equal[T comparable](a, b []T) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestTypeAssert(t *testing.T) {
|
||||
testTypeAssert(t, int(123456789), int(123456789), true)
|
||||
testTypeAssert(t, int(-123456789), int(-123456789), true)
|
||||
testTypeAssert(t, int32(123456789), int32(123456789), true)
|
||||
testTypeAssert(t, int8(-123), int8(-123), true)
|
||||
testTypeAssert(t, [2]int{1234, -5678}, [2]int{1234, -5678}, true)
|
||||
testTypeAssert(t, "test value", "test value", true)
|
||||
testTypeAssert(t, any("test value"), any("test value"), true)
|
||||
|
||||
v := 123456789
|
||||
testTypeAssert(t, &v, &v, true)
|
||||
|
||||
testTypeAssert(t, int(123), uint(0), false)
|
||||
|
||||
testTypeAssert[any](t, 1, 1, true)
|
||||
testTypeAssert[fmt.Stringer](t, 1, nil, false)
|
||||
|
||||
vv := testTypeWithMethod{"test"}
|
||||
testTypeAssert[any](t, vv, vv, true)
|
||||
testTypeAssert[any](t, &vv, &vv, true)
|
||||
testTypeAssert[fmt.Stringer](t, vv, vv, true)
|
||||
testTypeAssert[fmt.Stringer](t, &vv, &vv, true)
|
||||
testTypeAssert[interface{ A() }](t, vv, nil, false)
|
||||
testTypeAssert[interface{ A() }](t, &vv, nil, false)
|
||||
testTypeAssert(t, any(vv), any(vv), true)
|
||||
testTypeAssert(t, fmt.Stringer(vv), fmt.Stringer(vv), true)
|
||||
|
||||
testTypeAssert(t, fmt.Stringer(vv), any(vv), true)
|
||||
testTypeAssert(t, any(vv), fmt.Stringer(vv), true)
|
||||
testTypeAssert(t, fmt.Stringer(vv), interface{ M() }(vv), true)
|
||||
testTypeAssert(t, interface{ M() }(vv), fmt.Stringer(vv), true)
|
||||
|
||||
testTypeAssert(t, any(int(1)), int(1), true)
|
||||
testTypeAssert(t, any(int(1)), byte(0), false)
|
||||
testTypeAssert(t, fmt.Stringer(vv), vv, true)
|
||||
}
|
||||
|
||||
func testTypeAssert[T comparable, V any](t *testing.T, val V, wantVal T, wantOk bool) {
|
||||
t.Helper()
|
||||
|
||||
v, ok := TypeAssert[T](ValueOf(&val).Elem())
|
||||
if v != wantVal || ok != wantOk {
|
||||
t.Errorf("TypeAssert[%v](%#v) = (%#v, %v); want = (%#v, %v)", TypeFor[T](), val, v, ok, wantVal, wantOk)
|
||||
}
|
||||
|
||||
// Additionally make sure that TypeAssert[T](v) behaves in the same way as v.Interface().(T).
|
||||
v2, ok2 := ValueOf(&val).Elem().Interface().(T)
|
||||
if v != v2 || ok != ok2 {
|
||||
t.Errorf("reflect.ValueOf(%#v).Interface().(%v) = (%#v, %v); want = (%#v, %v)", val, TypeFor[T](), v2, ok2, v, ok)
|
||||
}
|
||||
}
|
||||
|
||||
type testTypeWithMethod struct{ val string }
|
||||
|
||||
func (v testTypeWithMethod) String() string { return v.val }
|
||||
func (v testTypeWithMethod) M() {}
|
||||
|
||||
func TestTypeAssertPanic(t *testing.T) {
|
||||
if runtime.GOARCH == "wasm" {
|
||||
t.Log("recover not supported")
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("zero val", func(t *testing.T) {
|
||||
defer func() { recover() }()
|
||||
TypeAssert[int](Value{})
|
||||
t.Fatalf("TypeAssert did not panic")
|
||||
})
|
||||
t.Run("read only", func(t *testing.T) {
|
||||
defer func() { recover() }()
|
||||
TypeAssert[int](ValueOf(&testTypeWithMethod{}).FieldByName("val"))
|
||||
t.Fatalf("TypeAssert did not panic")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -69,13 +70,14 @@ const baremetal = true
|
||||
// timeOffset is how long the monotonic clock started after the Unix epoch. It
|
||||
// should be a positive integer under normal operation or zero when it has not
|
||||
// been set.
|
||||
var timeOffset int64
|
||||
var timeOffset atomic.Int64
|
||||
|
||||
//go:linkname now time.now
|
||||
func now() (sec int64, nsec int32, mono int64) {
|
||||
mono = nanotime()
|
||||
sec = (mono + timeOffset) / (1000 * 1000 * 1000)
|
||||
nsec = int32((mono + timeOffset) - sec*(1000*1000*1000))
|
||||
to := timeOffset.Load()
|
||||
sec = (mono + to) / (1000 * 1000 * 1000)
|
||||
nsec = int32((mono + to) - sec*(1000*1000*1000))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,8 +85,7 @@ func now() (sec int64, nsec int32, mono int64) {
|
||||
// positive value adds to the time (skipping some time), a negative value moves
|
||||
// the clock into the past.
|
||||
func AdjustTimeOffset(offset int64) {
|
||||
// TODO: do this atomically?
|
||||
timeOffset += offset
|
||||
timeOffset.Add(offset)
|
||||
}
|
||||
|
||||
// Picolibc is not configured to define its own errno value, instead it calls
|
||||
|
||||
@@ -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
|
||||
@@ -827,6 +827,7 @@ func ReadMemStats(m *MemStats) {
|
||||
liveBytes := uint64(liveBlocks * bytesPerBlock)
|
||||
m.HeapInuse = liveBytes
|
||||
m.HeapAlloc = liveBytes
|
||||
m.HeapObjects = uint64(liveHeads)
|
||||
m.Alloc = liveBytes
|
||||
|
||||
// Subtract live blocks from total blocks to count free blocks.
|
||||
|
||||
@@ -96,6 +96,7 @@ func ReadMemStats(m *MemStats) {
|
||||
m.Sys = uint64(heapEnd - heapStart)
|
||||
// no free -- current in use heap is the total allocated
|
||||
m.HeapAlloc = gcTotalAlloc
|
||||
m.HeapObjects = gcMallocs
|
||||
m.Alloc = m.HeapAlloc
|
||||
|
||||
gcLock.Unlock()
|
||||
|
||||
@@ -53,6 +53,13 @@ type MemStats struct {
|
||||
// HeapReleased is bytes of physical memory returned to the OS.
|
||||
HeapReleased uint64
|
||||
|
||||
// HeapObjects is the number of allocated heap objects.
|
||||
//
|
||||
// Like HeapAlloc, this increases as objects are allocated and
|
||||
// decreases as the heap is swept and unreachable objects are
|
||||
// freed.
|
||||
HeapObjects uint64
|
||||
|
||||
// TotalAlloc is cumulative bytes allocated for heap objects.
|
||||
//
|
||||
// TotalAlloc increases as heap objects are allocated, but
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt))
|
||||
//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350)
|
||||
|
||||
// If you update the above build constraint, you'll probably also need to update
|
||||
// src/crypto/rand/rand_baremetal.go.
|
||||
//
|
||||
// The rp2040 and rp2350 implementations are not included in src/crypto/rand/rand_baremetal.go
|
||||
// due to not being sufficiently random for the Go crypto libs.
|
||||
// However since the randomness here does not provide those same guarantees,
|
||||
// they are included in the list for hardwareRand() implementations.
|
||||
|
||||
package runtime
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt))
|
||||
//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350)
|
||||
|
||||
package runtime
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
//go:build stm32g0
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"machine"
|
||||
)
|
||||
|
||||
func putchar(c byte) {
|
||||
machine.Serial.WriteByte(c)
|
||||
}
|
||||
|
||||
func getchar() byte {
|
||||
for machine.Serial.Buffered() == 0 {
|
||||
Gosched()
|
||||
}
|
||||
v, _ := machine.Serial.ReadByte()
|
||||
return v
|
||||
}
|
||||
|
||||
func buffered() int {
|
||||
return machine.Serial.Buffered()
|
||||
}
|
||||
|
||||
func initCLK() {
|
||||
// Initialize clock to 64MHz using PLL with HSI16 as source
|
||||
// PLL configuration: HSI16 (16MHz) / PLLM(1) * PLLN(8) / PLLR(2) = 64MHz
|
||||
|
||||
// Enable PWR clock
|
||||
stm32.RCC.SetAPBENR1_PWREN(1)
|
||||
// Read back to ensure the write is complete (memory barrier)
|
||||
_ = stm32.RCC.APBENR1.Get()
|
||||
|
||||
// Set Power Regulator to enable max performance (Range 1)
|
||||
// VOS = 01 for Range 1 (high performance, up to 64 MHz)
|
||||
stm32.PWR.SetCR1_VOS(1)
|
||||
// Wait for voltage scaling to be ready (VOSF = 0 means ready)
|
||||
for stm32.PWR.SR2.HasBits(stm32.PWR_SR2_VOSF) {
|
||||
}
|
||||
|
||||
// Enable HSI16
|
||||
stm32.RCC.SetCR_HSION(1)
|
||||
for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSIRDY) {
|
||||
}
|
||||
|
||||
// Set HSI16 division factor to 1 (no division) - HSIDIV = 000
|
||||
stm32.RCC.SetCR_HSIDIV(0)
|
||||
|
||||
// Disable PLL before configuration
|
||||
stm32.RCC.SetCR_PLLON(0)
|
||||
for stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) {
|
||||
}
|
||||
|
||||
// Configure PLL: HSI16 / 1 * 8 / 2 = 64 MHz
|
||||
// PLLSRC = HSI16 (2)
|
||||
// PLLM = 0 (divide by 1)
|
||||
// PLLN = 8 (multiply by 8) -> VCO = 16 * 8 = 128 MHz
|
||||
// PLLR = 0 (divide by 2) -> SYSCLK = 128 / 2 = 64 MHz
|
||||
// PLLREN = 1 (enable R output for SYSCLK)
|
||||
const (
|
||||
PLLSRC_HSI16 = 2 // HSI16 as PLL source
|
||||
PLLM_DIV1 = 0 // /1
|
||||
PLLN_MUL8 = 8 // *8
|
||||
PLLR_DIV2 = 0 // /2 (0 = divide by 2)
|
||||
)
|
||||
stm32.RCC.PLLCFGR.Set(
|
||||
(PLLSRC_HSI16 << stm32.RCC_PLLCFGR_PLLSRC_Pos) |
|
||||
(PLLM_DIV1 << stm32.RCC_PLLCFGR_PLLM_Pos) |
|
||||
(PLLN_MUL8 << stm32.RCC_PLLCFGR_PLLN_Pos) |
|
||||
(PLLR_DIV2 << stm32.RCC_PLLCFGR_PLLR_Pos) |
|
||||
stm32.RCC_PLLCFGR_PLLREN) // Enable PLLR output
|
||||
|
||||
// Enable PLL
|
||||
stm32.RCC.SetCR_PLLON(1)
|
||||
for !stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) {
|
||||
}
|
||||
|
||||
// Set flash latency to 2 wait states (required for 64MHz in Range 1)
|
||||
// Must be set BEFORE switching to higher frequency clock
|
||||
const FLASH_LATENCY_2 = 2
|
||||
stm32.FLASH.SetACR_LATENCY(FLASH_LATENCY_2)
|
||||
for (stm32.FLASH.ACR.Get() & stm32.Flash_ACR_LATENCY_Msk) != FLASH_LATENCY_2 {
|
||||
}
|
||||
|
||||
// Set AHB prescaler to 1 (no division)
|
||||
stm32.RCC.SetCFGR_HPRE(0)
|
||||
// Set APB prescaler to 1 (no division)
|
||||
stm32.RCC.SetCFGR_PPRE(0)
|
||||
|
||||
// Switch system clock to PLL (SW = 010)
|
||||
const RCC_CFGR_SW_PLL = 2
|
||||
stm32.RCC.SetCFGR_SW(RCC_CFGR_SW_PLL)
|
||||
// Wait for PLL to be used as system clock (SWS = 010)
|
||||
for (stm32.RCC.CFGR.Get() & stm32.RCC_CFGR_SWS_Msk) != (RCC_CFGR_SW_PLL << stm32.RCC_CFGR_SWS_Pos) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//go:build stm32g0b1
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initCLK()
|
||||
|
||||
machine.InitSerial()
|
||||
|
||||
initTickTimer(&machine.TIM3)
|
||||
}
|
||||
@@ -243,8 +243,8 @@ func sleep(duration int64) {
|
||||
// run is called by the program entry point to execute the go program.
|
||||
// With a scheduler, init and the main function are invoked in a goroutine before starting the scheduler.
|
||||
func run() {
|
||||
initHeap()
|
||||
initRand()
|
||||
initHeap()
|
||||
go func() {
|
||||
initAll()
|
||||
callMain()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user