From 4cbd34d32f04fa5127dc8816221eec9727f8fe23 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 17 Apr 2026 10:30:58 +0200 Subject: [PATCH] machine/esp32s3: implement Pin.SetInterrupt for GPIO pin change interrupts Add support for rising, falling, and toggle edge interrupts on all ESP32-S3 GPIO pins (0-48). The GPIO peripheral interrupt is routed to CPU interrupt 8 via the interrupt matrix. The ISR reads both STATUS and STATUS1 registers to cover the full 49-pin range. Signed-off-by: deadprogram --- src/examples/pininterrupt/xiao-esp32s3.go | 11 +++ src/machine/machine_esp32s3.go | 87 +++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/examples/pininterrupt/xiao-esp32s3.go diff --git a/src/examples/pininterrupt/xiao-esp32s3.go b/src/examples/pininterrupt/xiao-esp32s3.go new file mode 100644 index 000000000..4173170c3 --- /dev/null +++ b/src/examples/pininterrupt/xiao-esp32s3.go @@ -0,0 +1,11 @@ +//go:build xiao_esp32s3 + +package main + +import "machine" + +const ( + button = machine.D1 + buttonMode = machine.PinInput + buttonPinChange = machine.PinFalling +) diff --git a/src/machine/machine_esp32s3.go b/src/machine/machine_esp32s3.go index 7a5be3d4a..78f5fd445 100644 --- a/src/machine/machine_esp32s3.go +++ b/src/machine/machine_esp32s3.go @@ -5,7 +5,9 @@ package machine import ( "device/esp" "errors" + "runtime/interrupt" "runtime/volatile" + "sync" "unsafe" ) @@ -303,6 +305,91 @@ func (p Pin) pinReg() *volatile.Register32 { return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.PIN0), uintptr(p)*4)) } +const maxPin = 49 +const cpuInterruptFromPin = 8 + +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + PinRising PinChange = iota + 1 + PinFalling + PinToggle +) + +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. The pin should already be configured as an input, including a pull up +// or down if no external pull is provided. +// +// You can pass a nil func to unset the pin change interrupt. If you do so, +// the change parameter is ignored and can be set to any value (such as 0). +// If the pin is already configured with a callback, you must first unset +// this pins interrupt before you can set a new callback. +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) { + if p >= maxPin { + return ErrInvalidInputPin + } + + if callback == nil { + // Disable this pin interrupt + p.pinReg().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk) + + if pinCallbacks[p] != nil { + pinCallbacks[p] = nil + } + return nil + } + + if pinCallbacks[p] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + pinCallbacks[p] = callback + + onceSetupPinInterrupt.Do(func() { + err = setupPinInterrupt() + }) + if err != nil { + return err + } + + p.pinReg().Set( + (p.pinReg().Get() & ^uint32(esp.GPIO_PIN_INT_TYPE_Msk|esp.GPIO_PIN_INT_ENA_Msk)) | + uint32(change)<