diff --git a/GNUmakefile b/GNUmakefile index 99a654ca7f..fc8d71d998 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -896,6 +896,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) diff --git a/compiler/gc.go b/compiler/gc.go index fc0e6e687f..5ca79b91ba 100644 --- a/compiler/gc.go +++ b/compiler/gc.go @@ -99,6 +99,9 @@ func typeHasPointers(t llvm.Type) bool { } return false case llvm.ArrayTypeKind: + if t.ArrayLength() == 0 { + return false + } if typeHasPointers(t.ElementType()) { return true } diff --git a/compiler/llvm.go b/compiler/llvm.go index de387b39c0..7ce6c7d615 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -1,10 +1,10 @@ package compiler import ( + "encoding/binary" "fmt" "go/token" "go/types" - "math/big" "strings" "github.com/tinygo-org/tinygo/compileopts" @@ -231,6 +231,12 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l // // For details on what's in this value, see src/runtime/gc_precise.go. func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value { + if !typeHasPointers(t) { + // There are no pointers in this type, so we can simplify the layout. + layout := (uint64(1) << 1) | 1 + return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) + } + // Use the element type for arrays. This works even for nested arrays. for { kind := t.TypeKind() @@ -248,54 +254,29 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va break } - // Do a few checks to see whether we need to generate any object layout - // information at all. + // Create the pointer bitmap. objectSizeBytes := c.targetData.TypeAllocSize(t) - pointerSize := c.targetData.TypeAllocSize(c.dataPtrType) - pointerAlignment := c.targetData.PrefTypeAlignment(c.dataPtrType) - if objectSizeBytes < pointerSize { - // Too small to contain a pointer. - layout := (uint64(1) << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) - } - bitmap := c.getPointerBitmap(t, pos) - if bitmap.BitLen() == 0 { - // There are no pointers in this type, so we can simplify the layout. - // TODO: this can be done in many other cases, e.g. when allocating an - // array (like [4][]byte, which repeats a slice 4 times). - layout := (uint64(1) << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) - } - if objectSizeBytes%uint64(pointerAlignment) != 0 { - // This shouldn't happen except for packed structs, which aren't - // currently used. - c.addError(pos, "internal error: unexpected object size for object with pointer field") - return llvm.ConstNull(c.dataPtrType) - } - objectSizeWords := objectSizeBytes / uint64(pointerAlignment) + pointerAlignment := uint64(c.targetData.PrefTypeAlignment(c.dataPtrType)) + bitmapLen := objectSizeBytes / pointerAlignment + bitmapBytes := (bitmapLen + 7) / 8 + bitmap := make([]byte, bitmapBytes, max(bitmapBytes, 8)) + c.buildPointerBitmap(bitmap, pointerAlignment, pos, t, 0) + // Try to encode the layout inline. + pointerSize := c.targetData.TypeAllocSize(c.dataPtrType) pointerBits := pointerSize * 8 - var sizeFieldBits uint64 - switch pointerBits { - case 16: - sizeFieldBits = 4 - case 32: - sizeFieldBits = 5 - case 64: - sizeFieldBits = 6 - default: - panic("unknown pointer size") - } - layoutFieldBits := pointerBits - 1 - sizeFieldBits - - // Try to emit the value as an inline integer. This is possible in most - // cases. - if objectSizeWords < layoutFieldBits { - // If it can be stored directly in the pointer value, do so. - // The runtime knows that if the least significant bit of the pointer is - // set, the pointer contains the value itself. - layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) + if bitmapLen < pointerBits { + rawMask := binary.LittleEndian.Uint64(bitmap[0:8]) + layout := rawMask*pointerBits + bitmapLen + layout <<= 1 + layout |= 1 + + // Check if the layout fits. + layout &= 1<>1)/pointerBits == rawMask { + // No set bits were shifted off. + return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) + } } // Unfortunately, the object layout is too big to fit in a pointer-sized @@ -303,25 +284,24 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va // Try first whether the global already exists. All objects with a // particular name have the same type, so this is possible. - globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap) + globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", bitmapLen, (bitmapLen+15)/16, bitmap) global := c.mod.NamedGlobal(globalName) if !global.IsNil() { return global } // Create the global initializer. - bitmapBytes := make([]byte, int(objectSizeWords+7)/8) - bitmap.FillBytes(bitmapBytes) - reverseBytes(bitmapBytes) // big-endian to little-endian - var bitmapByteValues []llvm.Value - for _, b := range bitmapBytes { - bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)) + bitmapByteValues := make([]llvm.Value, bitmapBytes) + i8 := c.ctx.Int8Type() + for i, b := range bitmap { + bitmapByteValues[i] = llvm.ConstInt(i8, uint64(b), false) } initializer := c.ctx.ConstStruct([]llvm.Value{ - llvm.ConstInt(c.uintptrType, objectSizeWords, false), - llvm.ConstArray(c.ctx.Int8Type(), bitmapByteValues), + llvm.ConstInt(c.uintptrType, bitmapLen, false), + llvm.ConstArray(i8, bitmapByteValues), }, false) + // Create the actual global. global = llvm.AddGlobal(c.mod, initializer.Type(), globalName) global.SetInitializer(initializer) global.SetUnnamedAddr(true) @@ -329,6 +309,7 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va global.SetLinkage(llvm.LinkOnceODRLinkage) if c.targetData.PrefTypeAlignment(c.uintptrType) < 2 { // AVR doesn't have alignment by default. + // The lowest bit must be unset to distinguish this from an inline layout. global.SetAlignment(2) } if c.Debug && pos != token.NoPos { @@ -360,52 +341,71 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va return global } -// getPointerBitmap scans the given LLVM type for pointers and sets bits in a -// bigint at the word offset that contains a pointer. This scan is recursive. -func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int { - alignment := c.targetData.PrefTypeAlignment(c.dataPtrType) - switch typ.TypeKind() { +// buildPointerBitmap scans the given LLVM type for pointers and sets bits in a +// bitmap at the word offset that contains a pointer. This scan is recursive. +func (c *compilerContext) buildPointerBitmap( + dst []byte, + ptrAlign uint64, + pos token.Pos, + t llvm.Type, + offset uint64, +) { + switch t.TypeKind() { case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind: - return big.NewInt(0) + // These types do not contain pointers. + case llvm.PointerTypeKind: - return big.NewInt(1) + // Set the corresponding position in the bitmap. + dst[offset/8] |= 1 << (offset % 8) + case llvm.StructTypeKind: - ptrs := big.NewInt(0) - for i, subtyp := range typ.StructElementTypes() { - subptrs := c.getPointerBitmap(subtyp, pos) - if subptrs.BitLen() == 0 { - continue - } - offset := c.targetData.ElementOffset(typ, i) - if offset%uint64(alignment) != 0 { - // This error will let the compilation fail, but by continuing - // the error can still easily be shown. - c.addError(pos, "internal error: allocated struct contains unaligned pointer") + // Recurse over struct elements. + for i, et := range t.StructElementTypes() { + eo := c.targetData.ElementOffset(t, i) + if eo%uint64(ptrAlign) != 0 { + if typeHasPointers(et) { + // This error will let the compilation fail, but by continuing + // the error can still easily be shown. + c.addError(pos, "internal error: allocated struct contains unaligned pointer") + } continue } - subptrs.Lsh(subptrs, uint(offset)/uint(alignment)) - ptrs.Or(ptrs, subptrs) + c.buildPointerBitmap( + dst, + ptrAlign, + pos, + et, + offset+(eo/ptrAlign), + ) } - return ptrs + case llvm.ArrayTypeKind: - subtyp := typ.ElementType() - subptrs := c.getPointerBitmap(subtyp, pos) - ptrs := big.NewInt(0) - if subptrs.BitLen() == 0 { - return ptrs + // Recurse over array elements. + len := t.ArrayLength() + if len <= 0 { + return } - elementSize := c.targetData.TypeAllocSize(subtyp) - if elementSize%uint64(alignment) != 0 { - // This error will let the compilation fail (but continues so that - // other errors can be shown). - c.addError(pos, "internal error: allocated array contains unaligned pointer") - return ptrs + et := t.ElementType() + elementSize := c.targetData.TypeAllocSize(et) + if elementSize%ptrAlign != 0 { + if typeHasPointers(et) { + // This error will let the compilation fail (but continues so that + // other errors can be shown). + c.addError(pos, "internal error: allocated array contains unaligned pointer") + } + return } - for i := 0; i < typ.ArrayLength(); i++ { - ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment)) - ptrs.Or(ptrs, subptrs) + elementSize /= ptrAlign + for i := 0; i < len; i++ { + c.buildPointerBitmap( + dst, + ptrAlign, + pos, + et, + offset+uint64(i)*elementSize, + ) } - return ptrs + default: // Should not happen. panic("unknown LLVM type") diff --git a/compiler/testdata/gc.go b/compiler/testdata/gc.go index 20e5967028..9aa00a4c6f 100644 --- a/compiler/testdata/gc.go +++ b/compiler/testdata/gc.go @@ -24,6 +24,10 @@ var ( x *byte y [61]uintptr } + struct5 *struct { + x *byte + y [30]uintptr + } slice1 []byte slice2 []*int @@ -58,6 +62,10 @@ func newStruct() { x *byte y [61]uintptr }) + struct5 = new(struct { + x *byte + y [30]uintptr + }) } func newFuncValue() *func() { diff --git a/compiler/testdata/gc.ll b/compiler/testdata/gc.ll index d2be74cbcf..42a278b66e 100644 --- a/compiler/testdata/gc.ll +++ b/compiler/testdata/gc.ll @@ -16,11 +16,12 @@ target triple = "wasm32-unknown-wasi" @main.struct2 = hidden global ptr null, align 4 @main.struct3 = hidden global ptr null, align 4 @main.struct4 = hidden global ptr null, align 4 +@main.struct5 = hidden global ptr null, align 4 @main.slice1 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.slice2 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 -@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } -@"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" } +@"runtime/gc.layout:62-0100000000000020" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } +@"runtime/gc.layout:62-0100000000000000" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" } @"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 80, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4 @"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:complex128" }, align 4 @@ -80,12 +81,15 @@ entry: %new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.struct2, align 4 - %new2 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-2000000000000001", ptr undef) #3 + %new2 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0100000000000020", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.struct3, align 4 - %new3 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0001", ptr undef) #3 + %new3 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0100000000000000", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new3, ptr @main.struct4, align 4 + %new4 = call align 4 dereferenceable(124) ptr @runtime.alloc(i32 124, ptr nonnull inttoptr (i32 127 to ptr), ptr undef) #3 + call void @runtime.trackPointer(ptr nonnull %new4, ptr nonnull %stackalloc, ptr undef) #3 + store ptr %new4, ptr @main.struct5, align 4 ret void } diff --git a/goenv/version.go b/goenv/version.go index 423f95906e..9ade0e0b79 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -10,7 +10,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const version = "0.40.1" +const version = "0.41.0-dev" // Return TinyGo version, either in the form 0.30.0 or as a development version // (like 0.30.0-dev-abcd012). diff --git a/src/examples/pwm/digispark.go b/src/examples/pwm/digispark.go new file mode 100644 index 0000000000..848d518546 --- /dev/null +++ b/src/examples/pwm/digispark.go @@ -0,0 +1,12 @@ +//go:build digispark + +package main + +import "machine" + +var ( + // Use Timer1 for PWM (recommended for ATtiny85) + pwm = machine.Timer1 + pinA = machine.P1 // PB1, Timer1 channel A (LED pin) + pinB = machine.P4 // PB4, Timer1 channel B +) diff --git a/src/examples/pwm/esp32.go b/src/examples/pwm/esp32.go new file mode 100644 index 0000000000..3b96a4de2d --- /dev/null +++ b/src/examples/pwm/esp32.go @@ -0,0 +1,12 @@ +//go:build esp32 +// +build esp32 + +package main + +import "machine" + +var ( + pwm = machine.PWM0 // Use high-speed timer 0 + pinA = machine.GPIO2 // Built-in LED on many ESP32 boards + pinB = machine.GPIO4 // Another GPIO for testing +) diff --git a/src/machine/board_digispark.go b/src/machine/board_digispark.go index f380aae85c..d7106a5544 100644 --- a/src/machine/board_digispark.go +++ b/src/machine/board_digispark.go @@ -2,17 +2,26 @@ package machine +// Digispark is a tiny ATtiny85-based board with 6 I/O pins. +// +// PWM is available on the following pins: +// - P0 (PB0): Timer0 channel A +// - P1 (PB1): Timer0 channel B or Timer1 channel A (LED pin) +// - P4 (PB4): Timer1 channel B +// +// Timer1 is recommended for PWM as it provides more flexible frequency control. + // Return the current CPU frequency in hertz. func CPUFrequency() uint32 { return 16000000 } const ( - P0 Pin = PB0 - P1 Pin = PB1 + P0 Pin = PB0 // PWM available (Timer0 OC0A) + P1 Pin = PB1 // PWM available (Timer0 OC0B or Timer1 OC1A) P2 Pin = PB2 P3 Pin = PB3 - P4 Pin = PB4 + P4 Pin = PB4 // PWM available (Timer1 OC1B) P5 Pin = PB5 LED = P1 diff --git a/src/machine/machine_attiny85.go b/src/machine/machine_attiny85.go index 33424c6052..6d31846b5a 100644 --- a/src/machine/machine_attiny85.go +++ b/src/machine/machine_attiny85.go @@ -21,3 +21,524 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { // Very simple for the attiny85, which only has a single port. return avr.PORTB, 1 << uint8(p) } + +// PWM is one PWM peripheral, which consists of a counter and two output +// channels (that can be connected to two fixed pins). You can set the frequency +// using SetPeriod, but only for all the channels in this PWM peripheral at +// once. +type PWM struct { + num uint8 +} + +var ( + Timer0 = PWM{0} // 8 bit timer for PB0 and PB1 + Timer1 = PWM{1} // 8 bit high-speed timer for PB1 and PB4 +) + +// GTCCR bits for Timer1 that are not defined in the device file +const ( + gtccrPWM1B = 0x40 // Pulse Width Modulator B Enable + gtccrCOM1B0 = 0x10 // Comparator B Output Mode bit 0 + gtccrCOM1B1 = 0x20 // Comparator B Output Mode bit 1 +) + +// Configure enables and configures this PWM. +// +// For Timer0, there is only a limited number of periods available, namely the +// CPU frequency divided by 256 and again divided by 1, 8, 64, 256, or 1024. +// For a MCU running at 8MHz, this would be a period of 32µs, 256µs, 2048µs, +// 8192µs, or 32768µs. +// +// For Timer1, the period is more flexible as it uses OCR1C as the top value. +// Timer1 also supports more prescaler values (1 to 16384). +func (pwm PWM) Configure(config PWMConfig) error { + switch pwm.num { + case 0: // Timer/Counter 0 (8-bit) + // Calculate the timer prescaler. + var prescaler uint8 + switch config.Period { + case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()): + prescaler = 1 + case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()): + prescaler = 2 + case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()): + prescaler = 3 + case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()): + prescaler = 4 + case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()): + prescaler = 5 + default: + return ErrPWMPeriodTooLong + } + + avr.TCCR0B.Set(prescaler) + // Set the PWM mode to fast PWM (mode = 3). + avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) + + case 1: // Timer/Counter 1 (8-bit high-speed) + // Timer1 on ATtiny85 is different from ATmega328: + // - It's 8-bit with configurable top (OCR1C) + // - Has more prescaler options (1-16384) + // - PWM mode is enabled per-channel via PWM1A/PWM1B bits + var top uint64 + if config.Period == 0 { + // Use a top appropriate for LEDs. + top = 0xff + } else { + // Calculate top value: top = period * (CPUFrequency / 1e9) + top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + // Timer1 prescaler values: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384 + const maxTop = 256 + var prescaler uint8 + switch { + case top <= maxTop: + prescaler = 1 // prescaler 1 + case top/2 <= maxTop: + prescaler = 2 // prescaler 2 + top /= 2 + case top/4 <= maxTop: + prescaler = 3 // prescaler 4 + top /= 4 + case top/8 <= maxTop: + prescaler = 4 // prescaler 8 + top /= 8 + case top/16 <= maxTop: + prescaler = 5 // prescaler 16 + top /= 16 + case top/32 <= maxTop: + prescaler = 6 // prescaler 32 + top /= 32 + case top/64 <= maxTop: + prescaler = 7 // prescaler 64 + top /= 64 + case top/128 <= maxTop: + prescaler = 8 // prescaler 128 + top /= 128 + case top/256 <= maxTop: + prescaler = 9 // prescaler 256 + top /= 256 + case top/512 <= maxTop: + prescaler = 10 // prescaler 512 + top /= 512 + case top/1024 <= maxTop: + prescaler = 11 // prescaler 1024 + top /= 1024 + case top/2048 <= maxTop: + prescaler = 12 // prescaler 2048 + top /= 2048 + case top/4096 <= maxTop: + prescaler = 13 // prescaler 4096 + top /= 4096 + case top/8192 <= maxTop: + prescaler = 14 // prescaler 8192 + top /= 8192 + case top/16384 <= maxTop: + prescaler = 15 // prescaler 16384 + top /= 16384 + default: + return ErrPWMPeriodTooLong + } + + // Set prescaler (CS1[3:0] bits) + avr.TCCR1.Set(prescaler) + // Set top value + avr.OCR1C.Set(uint8(top - 1)) + } + return nil +} + +// SetPeriod updates the period of this PWM peripheral. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// If you use a period of 0, a period that works well for LEDs will be picked. +// +// SetPeriod will not change the prescaler, but also won't change the current +// value in any of the channels. This means that you may need to update the +// value for the particular channel. +// +// Note that you cannot pick any arbitrary period after the PWM peripheral has +// been configured. If you want to switch between frequencies, pick the lowest +// frequency (longest period) once when calling Configure and adjust the +// frequency here as needed. +func (pwm PWM) SetPeriod(period uint64) error { + if pwm.num == 0 { + return ErrPWMPeriodTooLong // Timer0 doesn't support dynamic period + } + + // Timer1 can adjust period via OCR1C + var top uint64 + if period == 0 { + top = 0xff + } else { + top = period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + // Get current prescaler + prescaler := avr.TCCR1.Get() & 0x0f + // Timer1 prescaler values follow a power-of-2 pattern: + // prescaler n maps to divisor 2^(n-1), so we can use a simple shift + if prescaler > 0 && prescaler <= 15 { + top >>= (prescaler - 1) + } + + if top > 256 { + return ErrPWMPeriodTooLong + } + + avr.OCR1C.Set(uint8(top - 1)) + avr.TCNT1.Set(0) + + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. It +// will only change with a call to Configure or SetPeriod, otherwise it is +// constant. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to Set +// (see Set documentation for more information). +func (pwm PWM) Top() uint32 { + if pwm.num == 1 { + // Timer1 has configurable top via OCR1C + return uint32(avr.OCR1C.Get()) + 1 + } + // Timer0 goes from 0 to 0xff (256 in total) + return 256 +} + +// Counter returns the current counter value of the timer in this PWM +// peripheral. It may be useful for debugging. +func (pwm PWM) Counter() uint32 { + switch pwm.num { + case 0: + return uint32(avr.TCNT0.Get()) + case 1: + return uint32(avr.TCNT1.Get()) + } + return 0 +} + +// Prescaler lookup tables using uint16 (more efficient than uint64 on AVR) +// Timer0 prescaler lookup table (index 0-7 maps to prescaler bits) +var timer0Prescalers = [8]uint16{0, 1, 8, 64, 256, 1024, 0, 0} + +// Timer1 prescaler lookup table (index 0-15 maps to prescaler bits) +var timer1Prescalers = [16]uint16{0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384} + +// Period returns the used PWM period in nanoseconds. It might deviate slightly +// from the configured period due to rounding. +func (pwm PWM) Period() uint64 { + var prescaler uint64 + switch pwm.num { + case 0: + prescalerBits := avr.TCCR0B.Get() & 0x7 + prescaler = uint64(timer0Prescalers[prescalerBits]) + if prescaler == 0 { + return 0 + } + case 1: + prescalerBits := avr.TCCR1.Get() & 0x0f + prescaler = uint64(timer1Prescalers[prescalerBits]) + if prescaler == 0 { + return 0 + } + } + top := uint64(pwm.Top()) + return prescaler * top * 1000 / uint64(CPUFrequency()/1e6) +} + +// Channel returns a PWM channel for the given pin. +func (pwm PWM) Channel(pin Pin) (uint8, error) { + pin.Configure(PinConfig{Mode: PinOutput}) + pin.Low() + switch pwm.num { + case 0: + switch pin { + case PB0: // OC0A + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + return 0, nil + case PB1: // OC0B + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + return 1, nil + } + case 1: + switch pin { + case PB1: // OC1A + // Enable PWM on channel A + avr.TCCR1.SetBits(avr.TCCR1_PWM1A | avr.TCCR1_COM1A1) + return 0, nil + case PB4: // OC1B + // Enable PWM on channel B (controlled via GTCCR) + avr.GTCCR.SetBits(gtccrPWM1B | gtccrCOM1B1) + return 1, nil + } + } + return 0, ErrInvalidOutputPin +} + +// SetInverting sets whether to invert the output of this channel. +// Without inverting, a 25% duty cycle would mean the output is high for 25% of +// the time and low for the rest. Inverting flips the output as if a NOT gate +// was placed at the output, meaning that the output would be 25% low and 75% +// high with a duty cycle of 25%. +func (pwm PWM) SetInverting(channel uint8, inverting bool) { + switch pwm.num { + case 0: + switch channel { + case 0: // channel A, PB0 + if inverting { + avr.PORTB.SetBits(1 << 0) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0) + } else { + avr.PORTB.ClearBits(1 << 0) + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0) + } + case 1: // channel B, PB1 + if inverting { + avr.PORTB.SetBits(1 << 1) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0) + } else { + avr.PORTB.ClearBits(1 << 1) + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0) + } + } + case 1: + switch channel { + case 0: // channel A, PB1 + if inverting { + avr.PORTB.SetBits(1 << 1) + avr.TCCR1.SetBits(avr.TCCR1_COM1A0) + } else { + avr.PORTB.ClearBits(1 << 1) + avr.TCCR1.ClearBits(avr.TCCR1_COM1A0) + } + case 1: // channel B, PB4 + if inverting { + avr.PORTB.SetBits(1 << 4) + avr.GTCCR.SetBits(gtccrCOM1B0) + } else { + avr.PORTB.ClearBits(1 << 4) + avr.GTCCR.ClearBits(gtccrCOM1B0) + } + } + } +} + +// Set updates the channel value. This is used to control the channel duty +// cycle, in other words the fraction of time the channel output is high (or low +// when inverted). For example, to set it to a 25% duty cycle, use: +// +// pwm.Set(channel, pwm.Top() / 4) +// +// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, +// pwm.Top()) will set the output to high, assuming the output isn't inverted. +func (pwm PWM) Set(channel uint8, value uint32) { + switch pwm.num { + case 0: + switch channel { + case 0: // channel A, PB0 + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1) + } else { + avr.OCR0A.Set(uint8(value - 1)) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + } + case 1: // channel B, PB1 + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1) + } else { + avr.OCR0B.Set(uint8(value - 1)) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + } + } + case 1: + switch channel { + case 0: // channel A, PB1 + if value == 0 { + avr.TCCR1.ClearBits(avr.TCCR1_COM1A1) + } else { + avr.OCR1A.Set(uint8(value - 1)) + avr.TCCR1.SetBits(avr.TCCR1_COM1A1) + } + case 1: // channel B, PB4 + if value == 0 { + avr.GTCCR.ClearBits(gtccrCOM1B1) + } else { + avr.OCR1B.Set(uint8(value - 1)) + avr.GTCCR.SetBits(gtccrCOM1B1) + } + } + } +} + +// SPIConfig is used to store config info for SPI. +type SPIConfig struct { + Frequency uint32 + LSBFirst bool + Mode uint8 +} + +// SPI is the USI-based SPI implementation for ATTiny85. +// The ATTiny85 doesn't have dedicated SPI hardware, but uses the USI +// (Universal Serial Interface) in three-wire mode. +// +// Fixed pin mapping (directly controlled by USI hardware): +// - PB2: SCK (clock) +// - PB1: DO/MOSI (data out) +// - PB0: DI/MISO (data in) +// +// Note: CS pin must be managed by the user. +type SPI struct { + // Delay cycles for frequency control (0 = max speed) + delayCycles uint16 + + // USICR value configured for the selected SPI mode + usicrValue uint8 + + // LSB-first mode (requires software bit reversal) + lsbFirst bool +} + +// SPI0 is the USI-based SPI interface on the ATTiny85 +var SPI0 = SPI{} + +// Configure sets up the USI for SPI communication. +// Note: The user must configure and control the CS pin separately. +func (s *SPI) Configure(config SPIConfig) error { + // Configure USI pins (fixed by hardware) + // PB1 (DO/MOSI) -> OUTPUT + // PB2 (USCK/SCK) -> OUTPUT + // PB0 (DI/MISO) -> INPUT + PB1.Configure(PinConfig{Mode: PinOutput}) + PB2.Configure(PinConfig{Mode: PinOutput}) + PB0.Configure(PinConfig{Mode: PinInput}) + + // Reset USI registers + avr.USIDR.Set(0) + avr.USISR.Set(0) + + // Configure USI for SPI mode: + // - USIWM0: Three-wire mode (SPI) + // - USICS1: External clock source (software controlled via USITC) + // - USICLK: Clock strobe - enables counter increment on USITC toggle + // - USICS0: Controls clock phase (CPHA) + // + // SPI Modes: + // Mode 0 (CPOL=0, CPHA=0): Clock idle low, sample on rising edge + // Mode 1 (CPOL=0, CPHA=1): Clock idle low, sample on falling edge + // Mode 2 (CPOL=1, CPHA=0): Clock idle high, sample on falling edge + // Mode 3 (CPOL=1, CPHA=1): Clock idle high, sample on rising edge + // + // For USI, USICS0 controls the sampling edge when USICS1=1: + // USICS0=0: Positive edge (rising) + // USICS0=1: Negative edge (falling) + switch config.Mode { + case Mode0: // CPOL=0, CPHA=0: idle low, sample rising + PB2.Low() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICLK + case Mode1: // CPOL=0, CPHA=1: idle low, sample falling + PB2.Low() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICS0 | avr.USICR_USICLK + case Mode2: // CPOL=1, CPHA=0: idle high, sample falling + PB2.High() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICS0 | avr.USICR_USICLK + case Mode3: // CPOL=1, CPHA=1: idle high, sample rising + PB2.High() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICLK + default: // Default to Mode 0 + PB2.Low() + s.usicrValue = avr.USICR_USIWM0 | avr.USICR_USICS1 | avr.USICR_USICLK + } + avr.USICR.Set(s.usicrValue) + + // Calculate delay cycles for frequency control + // Each bit transfer requires 2 clock toggles (rising + falling edge) + // The loop overhead is approximately 10-15 cycles per toggle on AVR + // We calculate additional delay cycles needed to achieve the target frequency + if config.Frequency > 0 && config.Frequency < CPUFrequency()/2 { + // Cycles per half-period = CPUFrequency / (2 * Frequency) + // Subtract loop overhead (~15 cycles) to get delay cycles + cyclesPerHalfPeriod := CPUFrequency() / (2 * config.Frequency) + const loopOverhead = 15 + if cyclesPerHalfPeriod > loopOverhead { + s.delayCycles = uint16(cyclesPerHalfPeriod - loopOverhead) + } else { + s.delayCycles = 0 + } + } else { + // Max speed - no delay + s.delayCycles = 0 + } + + // Store LSBFirst setting for use in Transfer + s.lsbFirst = config.LSBFirst + + return nil +} + +// reverseByte reverses the bit order of a byte (MSB <-> LSB) +// Used for LSB-first SPI mode since USI hardware only supports MSB-first +func reverseByte(b byte) byte { + b = (b&0xF0)>>4 | (b&0x0F)<<4 + b = (b&0xCC)>>2 | (b&0x33)<<2 + b = (b&0xAA)>>1 | (b&0x55)<<1 + return b +} + +// Transfer performs a single byte SPI transfer (send and receive simultaneously) +// This implements the USI-based SPI transfer using the "clock strobing" technique +func (s *SPI) Transfer(b byte) (byte, error) { + // For LSB-first mode, reverse the bits before sending + // USI hardware only supports MSB-first, so we do it in software + if s.lsbFirst { + b = reverseByte(b) + } + + // Load the byte to transmit into the USI Data Register + avr.USIDR.Set(b) + + // Clear the counter overflow flag by writing 1 to it (AVR quirk) + // This also resets the 4-bit counter to 0 + avr.USISR.Set(avr.USISR_USIOIF) + + // Clock the data out/in + // We need 16 clock toggles (8 bits × 2 edges per bit) + // The USI counter counts each clock edge, so it overflows at 16 + // After 16 toggles, the clock returns to its idle state (set by CPOL in Configure) + // + // IMPORTANT: Only toggle USITC here! + // - USITC toggles the clock pin + // - The USICR mode bits (USIWM0, USICS1, USICS0, USICLK) were set in Configure() + // - SetBits preserves those bits and only sets USITC + if s.delayCycles == 0 { + // Fast path: no delay, run at maximum speed + for !avr.USISR.HasBits(avr.USISR_USIOIF) { + avr.USICR.SetBits(avr.USICR_USITC) + } + } else { + // Frequency-controlled path: add delay between clock toggles + for !avr.USISR.HasBits(avr.USISR_USIOIF) { + avr.USICR.SetBits(avr.USICR_USITC) + // Delay loop for frequency control + // Each iteration is approximately 3 cycles on AVR (dec, brne) + for i := s.delayCycles; i > 0; i-- { + avr.Asm("nop") + } + } + } + + // Get the received byte + result := avr.USIDR.Get() + + // For LSB-first mode, reverse the received bits + if s.lsbFirst { + result = reverseByte(result) + } + + return result, nil +} diff --git a/src/machine/machine_esp32_pwm.go b/src/machine/machine_esp32_pwm.go new file mode 100644 index 0000000000..9b43ea56fd --- /dev/null +++ b/src/machine/machine_esp32_pwm.go @@ -0,0 +1,601 @@ +//go:build esp32 +// +build esp32 + +package machine + +import ( + "device/esp" + "errors" + "runtime/volatile" + "unsafe" +) + +// PWM peripheral errors +var ( + errPWMPeriodTooShort = errors.New("pwm: period too short") +) + +// PWM is one PWM peripheral, which consists of a timer and the associated +// channels. There are 4 high-speed timers available (PWM0-PWM3), each can +// use any of the 8 high-speed channels. +// +// The ESP32 LEDC peripheral is used for PWM generation. It provides flexible +// frequency and duty cycle control with configurable resolution (1-20 bits). +type PWM struct { + num uint8 // Timer number (0-3 for high-speed) +} + +// pwmChannelInfo tracks the state of each LEDC channel +type pwmChannelInfo struct { + pin Pin // The pin assigned to this channel (NoPin if unused) + timer uint8 // The timer this channel is bound to + inUse bool // Whether this channel is currently in use +} + +// Number of PWM timers and channels +const ( + pwmTimerCount = 4 // Number of high-speed timers (0-3) + pwmChannelCount = 8 // Number of high-speed channels (0-7) +) + +// pwmChannels tracks which pin and timer is assigned to each channel (0-7) +var pwmChannels [pwmChannelCount]pwmChannelInfo + +// Hardware PWM peripherals available on ESP32. +// These use the high-speed LEDC timers for glitch-free PWM updates. +var ( + PWM0 = &PWM{num: 0} + PWM1 = &PWM{num: 1} + PWM2 = &PWM{num: 2} + PWM3 = &PWM{num: 3} +) + +// LEDC peripheral constants +const ( + // Clock enable bit in DPORT.PERIP_CLK_EN for LEDC + ledcClockEnable = 1 << 11 + + // GPIO matrix output signal numbers for LEDC high-speed channels + ledcHSSignalBase = 71 // LEDC_HS_SIG_OUT0, channels are consecutive (71-78) + + // GPIO matrix output inversion bit + gpioMatrixInvertBit = 1 << 9 + + // APB clock frequency in MHz (used by high-speed LEDC) + apbClockMHz = 80 + + // Clock divider fractional bits (register value = actual_divider * 256) + dividerFracBits = 8 + + // Maximum values + maxDivider = 0x3FFFF // 18-bit divider register value + minDivider = 1 << dividerFracBits // Minimum divider = 256 (represents 1.0) + maxResolution = 20 // Maximum duty resolution in bits + minResolution = 1 // Minimum duty resolution in bits + + // Default values + defaultPeriodNs = 1_000_000 // 1ms = 1kHz, good for LEDs + defaultResolution = 13 // 13-bit resolution for default period + + // Register offsets + timerRegisterStride = 0x8 // Bytes between timer registers + channelRegisterStride = 0x14 // Bytes between channel registers +) + +// LEDC register bit positions and masks for timer configuration +const ( + // HSTIMER_CONF register + timerDivNumPos = 5 // DIV_NUM position (bits 5-22) + timerDivNumMask = 0x3FFFF << timerDivNumPos + timerLimMask = 0x1F // duty_resolution (bits 0-4), stores bit width directly + timerPauseMask = 1 << 23 + timerRstMask = 1 << 24 + timerTickSelMask = 1 << 25 // TICK_SEL bit (0=REF_TICK, 1=APB_CLK) +) + +// LEDC register bit positions and masks for channel configuration +const ( + // HSCH_CONF0 register + chanTimerSelMask = 0x3 // TIMER_SEL (bits 0-1) + chanSigOutEnMask = 1 << 2 // SIG_OUT_EN bit + chanClkEnMask = 1 << 31 // CLK_EN bit + + // HSCH_CONF1 register + chanDutyCyclePos = 10 // DUTY_CYCLE position (bits 10-19) + chanDutyNumPos = 20 // DUTY_NUM position (bits 20-29) + chanDutyIncMask = 1 << 30 + chanDutyStartMask = 1 << 31 + + // HSCH_DUTY register - duty value uses bits 0-24 (25 bits total) + // The lower 4 bits are fractional, upper 20 bits are integer + chanDutyFracBits = 4 +) + +// pwmState holds the current configuration for each PWM timer +type pwmState struct { + resolution uint8 // Current duty resolution in bits + configured bool // Whether the timer has been configured +} + +var pwmStates [pwmTimerCount]pwmState + +// Configure enables and configures this PWM peripheral. +// The period is specified in nanoseconds. A period of 0 will select a default +// period suitable for LED dimming (~1kHz). +func (pwm *PWM) Configure(config PWMConfig) error { + // Enable LEDC peripheral clock + esp.DPORT.PERIP_CLK_EN.SetBits(ledcClockEnable) + // Clear reset bit + esp.DPORT.PERIP_RST_EN.ClearBits(ledcClockEnable) + + // Select APB clock (80MHz) for LEDC + esp.LEDC.CONF.Set(1) // APB_CLK_SEL = 1 + + // Calculate timer configuration + divider, resolution, err := pwm.calculateConfig(config.Period) + if err != nil { + return err + } + + // Store the resolution for duty cycle calculations + pwmStates[pwm.num].resolution = resolution + pwmStates[pwm.num].configured = true + + // Get timer configuration register + timerConf := pwm.timerConf() + + // Configure timer: + // - Use APB_CLK (80MHz) as clock source + // - Set divider + // - Set resolution (duty_resolution field stores the bit width directly) + var conf uint32 + conf |= timerTickSelMask // APB_CLK + conf |= (divider << timerDivNumPos) & timerDivNumMask // Divider + conf |= uint32(resolution) & timerLimMask // Resolution (bit width) + + // Reset the timer (set rst=1, then rst=0) + timerConf.Set(conf | timerRstMask) + timerConf.Set(conf) + + return nil +} + +// calculateConfig determines the optimal divider and resolution for a given period. +// Returns divider (with 8 fractional bits), resolution, and any error. +func (pwm *PWM) calculateConfig(period uint64) (uint32, uint8, error) { + if period == 0 { + period = defaultPeriodNs + } + + // Formula: period_ns = (2^resolution * divider) / 80MHz * 1e9 + // Where divider is the actual divider (not the register value) + // Register value = actual_divider * 256 (8 fractional bits) + // + // period_ns = (2^resolution * divider_reg / 256) / 80MHz * 1e9 + // divider_reg = period_ns * 80 * 256 / (2^resolution * 1000) + + var lastDividerReg uint64 + + // Try to find the highest resolution that gives a valid divider + for resolution := uint8(maxResolution); resolution >= minResolution; resolution-- { + resolutionValue := uint64(1) << resolution + + // Calculate divider register value (includes 8 fractional bits) + dividerReg := (period * apbClockMHz * (1 << dividerFracBits)) / (resolutionValue * 1000) + lastDividerReg = dividerReg + + if dividerReg < minDivider { + // Period too short for this resolution, try lower resolution + continue + } + + if dividerReg <= maxDivider { + return uint32(dividerReg), resolution, nil + } + } + + // Determine which error to return + if lastDividerReg < minDivider { + return 0, 0, errPWMPeriodTooShort + } + return 0, 0, ErrPWMPeriodTooLong +} + +// Channel returns a PWM channel for the given pin. If the pin is already +// configured for this PWM peripheral, the same channel is returned. +// The pin is configured for PWM output. +// +// Returns ErrInvalidOutputPin if the pin is already used by a different timer +// or if no channels are available. +func (pwm *PWM) Channel(pin Pin) (uint8, error) { + if !pwmStates[pwm.num].configured { + // Timer not configured, configure with default period + if err := pwm.Configure(PWMConfig{}); err != nil { + return 0, err + } + } + + // Single pass: find existing assignment, check for conflicts, find free channel + var freeChannel int8 = -1 + for ch := uint8(0); ch < pwmChannelCount; ch++ { + if !pwmChannels[ch].inUse { + if freeChannel < 0 { + freeChannel = int8(ch) + } + continue + } + + if pwmChannels[ch].pin == pin { + if pwmChannels[ch].timer == pwm.num { + // Already assigned to this timer + return ch, nil + } + // Pin is used by a different timer + return 0, ErrInvalidOutputPin + } + } + + // No existing assignment found, use free channel if available + if freeChannel < 0 { + return 0, ErrInvalidOutputPin + } + + ch := uint8(freeChannel) + + // Configure the new channel + pwmChannels[ch] = pwmChannelInfo{ + pin: pin, + timer: pwm.num, + inUse: true, + } + + // Configure the GPIO for PWM output through GPIO matrix + signal := uint32(ledcHSSignalBase + ch) + pin.configure(PinConfig{Mode: PinOutput}, signal) + + // Configure the channel + chanConf0 := pwm.channelConf0(ch) + + // Set channel configuration: + // - Enable clock + // - Select this timer + // - Enable signal output + var conf uint32 + conf |= chanClkEnMask // Enable clock + conf |= uint32(pwm.num) & chanTimerSelMask // Select timer + conf |= chanSigOutEnMask // Enable output + chanConf0.Set(conf) + + // Initialize duty to 0 + pwm.channelDuty(ch).Set(0) + + // Set HPOINT to 0 (start of cycle) + pwm.channelHpoint(ch).Set(0) + + // Configure CONF1 for non-fading operation and trigger duty update + // duty_scale=0, duty_cycle=1, duty_num=1, duty_inc=1, duty_start=1 + var conf1 uint32 + conf1 |= 1 << chanDutyCyclePos // duty_cycle = 1 + conf1 |= 1 << chanDutyNumPos // duty_num = 1 + conf1 |= chanDutyIncMask // duty_inc = 1 + conf1 |= chanDutyStartMask // duty_start = 1 + pwm.channelConf1(ch).Set(conf1) + + return ch, nil +} + +// ReleaseChannel releases a PWM channel, making it available for other uses. +// The pin is not reconfigured; call pin.Configure() to change its function. +func (pwm *PWM) ReleaseChannel(channel uint8) error { + if !pwm.isValidChannel(channel) { + return ErrInvalidOutputPin + } + + if !pwmChannels[channel].inUse || pwmChannels[channel].timer != pwm.num { + // Channel not in use or belongs to different timer + return ErrInvalidOutputPin + } + + // Disable signal output + pwm.channelConf0(channel).ClearBits(chanSigOutEnMask) + + // Clear channel tracking + pwmChannels[channel] = pwmChannelInfo{} + + return nil +} + +// IsConnected returns true if the given channel is in use by this PWM peripheral. +func (pwm *PWM) IsConnected(channel uint8) bool { + if !pwm.isValidChannel(channel) { + return false + } + return pwmChannels[channel].inUse && pwmChannels[channel].timer == pwm.num +} + +// isValidChannel returns true if the channel number is valid. +func (pwm *PWM) isValidChannel(channel uint8) bool { + return channel < pwmChannelCount +} + +// Set updates the channel value. This is used to control the channel duty +// cycle. 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) { + if !pwm.isValidChannel(channel) { + return + } + + resolution := pwmStates[pwm.num].resolution + if resolution == 0 { + resolution = defaultResolution + } + maxValue := uint32((1 << resolution) - 1) + + // Clamp value to valid range + // Note: Setting duty to exactly 2^resolution causes hardware overflow + if value > maxValue { + value = maxValue + } + + // The duty register uses 4 fractional bits, so shift left by 4 + dutyValue := value << chanDutyFracBits + + // Set the duty value + pwm.channelDuty(channel).Set(dutyValue) + + // Configure CONF1 and trigger duty update + // For non-fading operation: duty_scale=0, duty_cycle=1, duty_num=1, duty_inc=1 + // Then set duty_start=1 to apply the new duty value + var conf1 uint32 + conf1 |= 1 << chanDutyCyclePos // duty_cycle = 1 + conf1 |= 1 << chanDutyNumPos // duty_num = 1 + conf1 |= chanDutyIncMask // duty_inc = 1 + conf1 |= chanDutyStartMask // duty_start = 1 + pwm.channelConf1(channel).Set(conf1) + + // Ensure signal output is enabled + pwm.channelConf0(channel).SetBits(chanSigOutEnMask) +} + +// Get returns the current duty cycle value for the given channel. +func (pwm *PWM) Get(channel uint8) uint32 { + if !pwm.isValidChannel(channel) { + return 0 + } + + // Read from the duty read register and remove fractional bits + return pwm.channelDutyR(channel).Get() >> chanDutyFracBits +} + +// SetPeriod updates the period of this PWM peripheral in nanoseconds. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// SetPeriod will try to maintain the current duty cycle ratio when changing +// the period for channels bound to this timer. +func (pwm *PWM) SetPeriod(period uint64) error { + // Calculate new configuration + divider, resolution, err := pwm.calculateConfig(period) + if err != nil { + return err + } + + oldResolution := pwmStates[pwm.num].resolution + pwmStates[pwm.num].resolution = resolution + + // Update timer configuration + timerConf := pwm.timerConf() + + // Read current config, update divider and resolution + conf := timerConf.Get() + conf &^= timerDivNumMask | timerLimMask + conf |= (divider << timerDivNumPos) & timerDivNumMask + conf |= uint32(resolution) & timerLimMask + timerConf.Set(conf) + + // If resolution changed, scale duty values for channels bound to THIS timer + if resolution != oldResolution { + for ch := uint8(0); ch < pwmChannelCount; ch++ { + if pwmChannels[ch].inUse && pwmChannels[ch].timer == pwm.num { + // Read current duty (includes fractional bits) + currentDuty := pwm.channelDuty(ch).Get() >> chanDutyFracBits + + // Scale to new resolution + if oldResolution > resolution { + currentDuty >>= (oldResolution - resolution) + } else { + currentDuty <<= (resolution - oldResolution) + } + + // Apply new duty + pwm.Set(ch, currentDuty) + } + } + } + + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. +// The value returned is (2^resolution - 1), which is the maximum value +// that can be passed to Set(). +func (pwm *PWM) Top() uint32 { + resolution := pwmStates[pwm.num].resolution + if resolution == 0 { + resolution = defaultResolution + } + return (1 << resolution) - 1 +} + +// Counter returns the current counter value of the timer. +// This may be useful for debugging. +func (pwm *PWM) Counter() uint32 { + return pwm.timerValue().Get() +} + +// Period returns the current period in nanoseconds. +func (pwm *PWM) Period() uint64 { + conf := pwm.timerConf().Get() + dividerReg := (conf & timerDivNumMask) >> timerDivNumPos + resolution := conf & timerLimMask + + if resolution == 0 || dividerReg == 0 { + return 0 + } + + // period_ns = (2^resolution * divider_reg / 256) / 80MHz * 1e9 + // period_ns = (2^resolution * divider_reg * 1000) / (apbClockMHz * (1 << dividerFracBits)) + resolutionValue := uint64(1) << resolution + return resolutionValue * uint64(dividerReg) * 1000 / (apbClockMHz << dividerFracBits) +} + +// Frequency returns the current PWM frequency in Hz. +func (pwm *PWM) Frequency() uint32 { + period := pwm.Period() + if period == 0 { + return 0 + } + return uint32(1_000_000_000 / period) +} + +// SetFrequency sets the PWM frequency in Hz. +// This is a convenience method equivalent to SetPeriod(1e9 / frequency). +func (pwm *PWM) SetFrequency(frequency uint32) error { + if frequency == 0 { + return ErrPWMPeriodTooLong + } + return pwm.SetPeriod(1_000_000_000 / uint64(frequency)) +} + +// Resolution returns the current duty cycle resolution in bits. +func (pwm *PWM) Resolution() uint8 { + resolution := pwmStates[pwm.num].resolution + if resolution == 0 { + return defaultResolution + } + return resolution +} + +// ChannelCount returns the number of channels available for this PWM peripheral. +// Note: Channels are shared across all PWM timers on ESP32. +func (pwm *PWM) ChannelCount() uint8 { + return pwmChannelCount +} + +// 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) { + if !pwm.isValidChannel(channel) { + return + } + + // Get the pin for this channel to configure GPIO matrix inversion + if !pwmChannels[channel].inUse || pwmChannels[channel].timer != pwm.num { + return + } + + pin := pwmChannels[channel].pin + + // Reconfigure the GPIO with inversion setting through GPIO matrix + // The GPIO matrix FUNC_OUT_SEL_CFG register has an inversion bit (bit 9) + signal := uint32(ledcHSSignalBase + channel) + + // Get the GPIO function output select register + outFunc := pin.outFunc() + + if inverting { + outFunc.Set(signal | gpioMatrixInvertBit) + } else { + outFunc.Set(signal) + } +} + +// Enable enables or disables this PWM timer. When disabled (paused), the timer +// stops counting and all channels using this timer will hold their current state. +func (pwm *PWM) Enable(enable bool) { + timerConf := pwm.timerConf() + if enable { + timerConf.ClearBits(timerPauseMask) + } else { + timerConf.SetBits(timerPauseMask) + } +} + +// IsEnabled returns true if this PWM timer is running (not paused). +func (pwm *PWM) IsEnabled() bool { + return (pwm.timerConf().Get() & timerPauseMask) == 0 +} + +// ResetCounter resets the timer counter to 0. This can be used to +// synchronize multiple PWM timers. +func (pwm *PWM) ResetCounter() { + timerConf := pwm.timerConf() + conf := timerConf.Get() + timerConf.Set(conf | timerRstMask) + timerConf.Set(conf) +} + +// GetPin returns the pin assigned to the given channel, or NoPin if the +// channel is not in use by this PWM peripheral. +func (pwm *PWM) GetPin(channel uint8) Pin { + if !pwm.isValidChannel(channel) { + return NoPin + } + if !pwmChannels[channel].inUse || pwmChannels[channel].timer != pwm.num { + return NoPin + } + return pwmChannels[channel].pin +} + +// Register access helpers + +// timerConf returns the configuration register for this timer. +func (pwm *PWM) timerConf() *volatile.Register32 { + base := uintptr(unsafe.Pointer(&esp.LEDC.HSTIMER0_CONF)) + return (*volatile.Register32)(unsafe.Pointer(base + uintptr(pwm.num)*timerRegisterStride)) +} + +// timerValue returns the value register for this timer. +func (pwm *PWM) timerValue() *volatile.Register32 { + base := uintptr(unsafe.Pointer(&esp.LEDC.HSTIMER0_VALUE)) + return (*volatile.Register32)(unsafe.Pointer(base + uintptr(pwm.num)*timerRegisterStride)) +} + +// channelConf0 returns the CONF0 register for the given channel. +func (pwm *PWM) channelConf0(ch uint8) *volatile.Register32 { + base := uintptr(unsafe.Pointer(&esp.LEDC.HSCH0_CONF0)) + return (*volatile.Register32)(unsafe.Pointer(base + uintptr(ch)*channelRegisterStride)) +} + +// channelHpoint returns the HPOINT register for the given channel. +func (pwm *PWM) channelHpoint(ch uint8) *volatile.Register32 { + base := uintptr(unsafe.Pointer(&esp.LEDC.HSCH0_HPOINT)) + return (*volatile.Register32)(unsafe.Pointer(base + uintptr(ch)*channelRegisterStride)) +} + +// channelDuty returns the DUTY register for the given channel. +func (pwm *PWM) channelDuty(ch uint8) *volatile.Register32 { + base := uintptr(unsafe.Pointer(&esp.LEDC.HSCH0_DUTY)) + return (*volatile.Register32)(unsafe.Pointer(base + uintptr(ch)*channelRegisterStride)) +} + +// channelConf1 returns the CONF1 register for the given channel. +func (pwm *PWM) channelConf1(ch uint8) *volatile.Register32 { + base := uintptr(unsafe.Pointer(&esp.LEDC.HSCH0_CONF1)) + return (*volatile.Register32)(unsafe.Pointer(base + uintptr(ch)*channelRegisterStride)) +} + +// channelDutyR returns the DUTY_R (read) register for the given channel. +func (pwm *PWM) channelDutyR(ch uint8) *volatile.Register32 { + base := uintptr(unsafe.Pointer(&esp.LEDC.HSCH0_DUTY_R)) + return (*volatile.Register32)(unsafe.Pointer(base + uintptr(ch)*channelRegisterStride)) +} diff --git a/src/machine/machine_rp2_adc.go b/src/machine/machine_rp2_adc.go index e0d6a459a9..12ff152dc9 100644 --- a/src/machine/machine_rp2_adc.go +++ b/src/machine/machine_rp2_adc.go @@ -19,10 +19,8 @@ var adcAref uint32 // InitADC resets the ADC peripheral. func InitADC() { - rp.RESETS.RESET.SetBits(rp.RESETS_RESET_ADC) - rp.RESETS.RESET.ClearBits(rp.RESETS_RESET_ADC) - for !rp.RESETS.RESET_DONE.HasBits(rp.RESETS_RESET_ADC) { - } + resetBlock(rp.RESETS_RESET_ADC) + unresetBlockWait(rp.RESETS_RESET_ADC) // enable ADC rp.ADC.CS.Set(rp.ADC_CS_EN) adcAref = 3300 diff --git a/src/machine/machine_rp2_i2c.go b/src/machine/machine_rp2_i2c.go index 54a5e5357b..50e2e8a277 100644 --- a/src/machine/machine_rp2_i2c.go +++ b/src/machine/machine_rp2_i2c.go @@ -259,10 +259,7 @@ func (i2c *I2C) init(config I2CConfig) error { //go:inline func (i2c *I2C) reset() { resetVal := i2c.deinit() - rp.RESETS.RESET.ClearBits(resetVal) - // Wait until reset is done. - for !rp.RESETS.RESET_DONE.HasBits(resetVal) { - } + unresetBlockWait(resetVal) } // deinit sets reset bit for I2C. Must call reset to reenable I2C after deinit. @@ -276,7 +273,7 @@ func (i2c *I2C) deinit() (resetVal uint32) { resetVal = rp.RESETS_RESET_I2C1 } // Perform I2C reset. - rp.RESETS.RESET.SetBits(resetVal) + resetBlock(resetVal) return resetVal } diff --git a/src/machine/machine_rp2_spi.go b/src/machine/machine_rp2_spi.go index 75e4f86b7b..f3fb256f61 100644 --- a/src/machine/machine_rp2_spi.go +++ b/src/machine/machine_rp2_spi.go @@ -212,10 +212,7 @@ func (spi *SPI) setFormat(mode uint8) { //go:inline func (spi *SPI) reset() { resetVal := spi.deinit() - rp.RESETS.RESET.ClearBits(resetVal) - // Wait until reset is done. - for !rp.RESETS.RESET_DONE.HasBits(resetVal) { - } + unresetBlockWait(resetVal) } //go:inline @@ -227,7 +224,7 @@ func (spi *SPI) deinit() (resetVal uint32) { resetVal = rp.RESETS_RESET_SPI1 } // Perform SPI reset. - rp.RESETS.RESET.SetBits(resetVal) + resetBlock(resetVal) return resetVal } diff --git a/src/machine/machine_rp2_uart.go b/src/machine/machine_rp2_uart.go index 872418a766..37e2ca9c2a 100644 --- a/src/machine/machine_rp2_uart.go +++ b/src/machine/machine_rp2_uart.go @@ -73,6 +73,27 @@ func (uart *UART) Configure(config UARTConfig) error { return nil } +// Close the UART and disable its interrupt/power use. +func (uart *UART) Close() error { + uart.Interrupt.Disable() + + // Disable UART. + uart.Bus.UARTCR.ClearBits(rp.UART0_UARTCR_UARTEN) + + var resetVal uint32 + switch { + case uart.Bus == rp.UART0: + resetVal = rp.RESETS_RESET_UART0 + case uart.Bus == rp.UART1: + resetVal = rp.RESETS_RESET_UART1 + } + + // reset UART + resetBlock(resetVal) + + return nil +} + // SetBaudRate sets the baudrate to be used for the UART. func (uart *UART) SetBaudRate(br uint32) { div := 8 * CPUFrequency() / br @@ -148,10 +169,8 @@ func initUART(uart *UART) { } // reset UART - rp.RESETS.RESET.SetBits(resetVal) - rp.RESETS.RESET.ClearBits(resetVal) - for !rp.RESETS.RESET_DONE.HasBits(resetVal) { - } + resetBlock(resetVal) + unresetBlockWait(resetVal) } // handleInterrupt should be called from the appropriate interrupt handler for diff --git a/src/machine/spi.go b/src/machine/spi.go index 9a1033ca7d..fa507b961d 100644 --- a/src/machine/spi.go +++ b/src/machine/spi.go @@ -1,4 +1,4 @@ -//go:build !baremetal || atmega || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2) +//go:build !baremetal || atmega || attiny85 || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2) package machine diff --git a/src/machine/spi_tx.go b/src/machine/spi_tx.go index 97385bb596..aec3f52fe1 100644 --- a/src/machine/spi_tx.go +++ b/src/machine/spi_tx.go @@ -1,4 +1,4 @@ -//go:build atmega || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2) +//go:build atmega || attiny85 || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2) // This file implements the SPI Tx function for targets that don't have a custom // (faster) implementation for it. diff --git a/testdata/corpus.yaml b/testdata/corpus.yaml index 0ed29adbe4..36ac337cf6 100644 --- a/testdata/corpus.yaml +++ b/testdata/corpus.yaml @@ -29,8 +29,7 @@ - repo: github.com/dgryski/go-camellia - repo: github.com/dgryski/go-change - repo: github.com/dgryski/go-chaskey - tags: appengine noasm - skipwasi: true # siphash has build tag issues + tags: appengine noasm # for dchest/siphash - repo: github.com/dgryski/go-clefia - repo: github.com/dgryski/go-clockpro - repo: github.com/dgryski/go-cobs @@ -56,7 +55,6 @@ - repo: github.com/dgryski/go-linlog - repo: github.com/dgryski/go-maglev tags: appengine # for dchest/siphash - skipwasi: true - repo: github.com/dgryski/go-marvin32 - repo: github.com/dgryski/go-md5crypt - repo: github.com/dgryski/go-metro @@ -66,7 +64,6 @@ tags: noasm - repo: github.com/dgryski/go-mpchash tags: appengine # for dchest/siphash - skipwasi: true - repo: github.com/dgryski/go-neeva - repo: github.com/dgryski/go-nibz - repo: github.com/dgryski/go-nibblesort @@ -289,3 +286,8 @@ - repo: github.com/philhofer/fwd - repo: github.com/blevesearch/sear - repo: github.com/steveyen/gtreap +- repo: github.com/orsinium-labs/tinymath +- repo: github.com/orsinium-labs/jsony +- repo: github.com/tidwall/gjson +- repo: github.com/dchest/siphash + tags: appengine