Skip to content

Commit 4cbd34d

Browse files
committed
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 <ron@hybridgroup.com>
1 parent 6426bf7 commit 4cbd34d

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//go:build xiao_esp32s3
2+
3+
package main
4+
5+
import "machine"
6+
7+
const (
8+
button = machine.D1
9+
buttonMode = machine.PinInput
10+
buttonPinChange = machine.PinFalling
11+
)

src/machine/machine_esp32s3.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ package machine
55
import (
66
"device/esp"
77
"errors"
8+
"runtime/interrupt"
89
"runtime/volatile"
10+
"sync"
911
"unsafe"
1012
)
1113

@@ -303,6 +305,91 @@ func (p Pin) pinReg() *volatile.Register32 {
303305
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.PIN0), uintptr(p)*4))
304306
}
305307

308+
const maxPin = 49
309+
const cpuInterruptFromPin = 8
310+
311+
type PinChange uint8
312+
313+
// Pin change interrupt constants for SetInterrupt.
314+
const (
315+
PinRising PinChange = iota + 1
316+
PinFalling
317+
PinToggle
318+
)
319+
320+
// SetInterrupt sets an interrupt to be executed when a particular pin changes
321+
// state. The pin should already be configured as an input, including a pull up
322+
// or down if no external pull is provided.
323+
//
324+
// You can pass a nil func to unset the pin change interrupt. If you do so,
325+
// the change parameter is ignored and can be set to any value (such as 0).
326+
// If the pin is already configured with a callback, you must first unset
327+
// this pins interrupt before you can set a new callback.
328+
func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) {
329+
if p >= maxPin {
330+
return ErrInvalidInputPin
331+
}
332+
333+
if callback == nil {
334+
// Disable this pin interrupt
335+
p.pinReg().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk)
336+
337+
if pinCallbacks[p] != nil {
338+
pinCallbacks[p] = nil
339+
}
340+
return nil
341+
}
342+
343+
if pinCallbacks[p] != nil {
344+
// The pin was already configured.
345+
// To properly re-configure a pin, unset it first and set a new
346+
// configuration.
347+
return ErrNoPinChangeChannel
348+
}
349+
pinCallbacks[p] = callback
350+
351+
onceSetupPinInterrupt.Do(func() {
352+
err = setupPinInterrupt()
353+
})
354+
if err != nil {
355+
return err
356+
}
357+
358+
p.pinReg().Set(
359+
(p.pinReg().Get() & ^uint32(esp.GPIO_PIN_INT_TYPE_Msk|esp.GPIO_PIN_INT_ENA_Msk)) |
360+
uint32(change)<<esp.GPIO_PIN_INT_TYPE_Pos | uint32(1)<<esp.GPIO_PIN_INT_ENA_Pos)
361+
362+
return nil
363+
}
364+
365+
var (
366+
pinCallbacks [maxPin]func(Pin)
367+
onceSetupPinInterrupt sync.Once
368+
)
369+
370+
func setupPinInterrupt() error {
371+
esp.INTERRUPT_CORE0.SetGPIO_INTERRUPT_PRO_MAP(cpuInterruptFromPin)
372+
return interrupt.New(cpuInterruptFromPin, func(interrupt.Interrupt) {
373+
// Check status for GPIO0-31
374+
status := esp.GPIO.STATUS.Get()
375+
for i, mask := 0, uint32(1); i < 32; i, mask = i+1, mask<<1 {
376+
if (status&mask) != 0 && pinCallbacks[i] != nil {
377+
pinCallbacks[i](Pin(i))
378+
}
379+
}
380+
// Check status for GPIO32-48
381+
status1 := esp.GPIO.STATUS1.Get()
382+
for i, mask := 32, uint32(1); i < maxPin; i, mask = i+1, mask<<1 {
383+
if (status1&mask) != 0 && pinCallbacks[i] != nil {
384+
pinCallbacks[i](Pin(i))
385+
}
386+
}
387+
// Clear interrupt bits
388+
esp.GPIO.STATUS_W1TC.SetBits(status)
389+
esp.GPIO.STATUS1_W1TC.SetBits(status1)
390+
}).Enable()
391+
}
392+
306393
var DefaultUART = UART0
307394

308395
var (

0 commit comments

Comments
 (0)