mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-15 16:33:40 +00:00
machine/esp32s3: use edge-triggered CPU interrupt for GPIO pin interrupts
When SPI is configured via the GPIO Matrix, SPI signal transitions set GPIO.STATUS bits on the routed pins. With a level-triggered CPU interrupt (line 8), the ISR re-enters continuously as long as any STATUS bit is asserted — causing user GPIO callbacks to fire spuriously. Switch cpuInterruptFromPin to CPU interrupt 10, which is edge-triggered (level 1) on the Xtensa LX7. This ensures the ISR fires once per GPIO event rather than looping while SPI is active. Also move STATUS_W1TC clears to before callback dispatch so that new GPIO events arriving during handler execution generate a fresh edge, and add writeINTCLEAR(active) in handleInterrupt to properly acknowledge edge-triggered CPU interrupt pending bits via the INTCLEAR register. Fixes GPIO interrupts firing constantly when SPI and pin interrupts are used together. Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
@@ -6,6 +6,6 @@ import "machine"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
button = machine.D1
|
button = machine.D1
|
||||||
buttonMode = machine.PinInput
|
buttonMode = machine.PinInputPullup
|
||||||
buttonPinChange = machine.PinFalling
|
buttonPinChange = machine.PinFalling
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -306,7 +306,12 @@ func (p Pin) pinReg() *volatile.Register32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const maxPin = 49
|
const maxPin = 49
|
||||||
const cpuInterruptFromPin = 8
|
|
||||||
|
// cpuInterruptFromPin selects an edge-triggered CPU interrupt line for GPIO.
|
||||||
|
// CPU interrupt 10 is edge-triggered level-1 on the Xtensa LX7, which prevents
|
||||||
|
// the ISR from re-entering continuously when other peripherals (e.g. SPI via
|
||||||
|
// the GPIO Matrix) keep GPIO.STATUS bits asserted.
|
||||||
|
const cpuInterruptFromPin = 10
|
||||||
|
|
||||||
type PinChange uint8
|
type PinChange uint8
|
||||||
|
|
||||||
@@ -370,23 +375,28 @@ var (
|
|||||||
func setupPinInterrupt() error {
|
func setupPinInterrupt() error {
|
||||||
esp.INTERRUPT_CORE0.SetGPIO_INTERRUPT_PRO_MAP(cpuInterruptFromPin)
|
esp.INTERRUPT_CORE0.SetGPIO_INTERRUPT_PRO_MAP(cpuInterruptFromPin)
|
||||||
return interrupt.New(cpuInterruptFromPin, func(interrupt.Interrupt) {
|
return interrupt.New(cpuInterruptFromPin, func(interrupt.Interrupt) {
|
||||||
// Check status for GPIO0-31
|
// Read and immediately clear interrupt status bits.
|
||||||
|
// Clearing before processing is critical for edge-triggered CPU
|
||||||
|
// interrupts: any new GPIO events that arrive during callback
|
||||||
|
// execution will set fresh STATUS bits, generating a new edge
|
||||||
|
// on the CPU interrupt line so they are not lost.
|
||||||
status := esp.GPIO.STATUS.Get()
|
status := esp.GPIO.STATUS.Get()
|
||||||
|
status1 := esp.GPIO.STATUS1.Get()
|
||||||
|
esp.GPIO.STATUS_W1TC.Set(status)
|
||||||
|
esp.GPIO.STATUS1_W1TC.Set(status1)
|
||||||
|
|
||||||
|
// Check status for GPIO0-31
|
||||||
for i, mask := 0, uint32(1); i < 32; i, mask = i+1, mask<<1 {
|
for i, mask := 0, uint32(1); i < 32; i, mask = i+1, mask<<1 {
|
||||||
if (status&mask) != 0 && pinCallbacks[i] != nil {
|
if (status&mask) != 0 && pinCallbacks[i] != nil {
|
||||||
pinCallbacks[i](Pin(i))
|
pinCallbacks[i](Pin(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check status for GPIO32-48
|
// Check status for GPIO32-48
|
||||||
status1 := esp.GPIO.STATUS1.Get()
|
|
||||||
for i, mask := 32, uint32(1); i < maxPin; i, mask = i+1, mask<<1 {
|
for i, mask := 32, uint32(1); i < maxPin; i, mask = i+1, mask<<1 {
|
||||||
if (status1&mask) != 0 && pinCallbacks[i] != nil {
|
if (status1&mask) != 0 && pinCallbacks[i] != nil {
|
||||||
pinCallbacks[i](Pin(i))
|
pinCallbacks[i](Pin(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clear interrupt bits
|
|
||||||
esp.GPIO.STATUS_W1TC.SetBits(status)
|
|
||||||
esp.GPIO.STATUS1_W1TC.SetBits(status1)
|
|
||||||
}).Enable()
|
}).Enable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,12 @@ func handleInterrupt() {
|
|||||||
enabled := readINTENABLE()
|
enabled := readINTENABLE()
|
||||||
active := pending & enabled
|
active := pending & enabled
|
||||||
|
|
||||||
|
// Clear edge-triggered pending bits before dispatching handlers so that
|
||||||
|
// new edges arriving during handler execution are not lost. Writing to
|
||||||
|
// INTCLEAR is a no-op for level-triggered lines, so this is safe for all
|
||||||
|
// interrupt types.
|
||||||
|
writeINTCLEAR(active)
|
||||||
|
|
||||||
for i := firstCPUInt; i <= lastCPUInt; i++ {
|
for i := firstCPUInt; i <= lastCPUInt; i++ {
|
||||||
if active&(1<<uint(i)) != 0 {
|
if active&(1<<uint(i)) != 0 {
|
||||||
// callHandlers requires a compile-time constant, so we
|
// callHandlers requires a compile-time constant, so we
|
||||||
@@ -212,6 +218,16 @@ func readINTERRUPT() uint32 {
|
|||||||
return uint32(device.AsmFull("rsr {}, INTERRUPT", nil))
|
return uint32(device.AsmFull("rsr {}, INTERRUPT", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeINTCLEAR writes the INTCLEAR special register (SR 227).
|
||||||
|
// Setting bit N clears CPU interrupt N if it is edge-triggered or
|
||||||
|
// software-triggered. Bits corresponding to level-triggered interrupts
|
||||||
|
// are ignored by hardware.
|
||||||
|
func writeINTCLEAR(val uint32) {
|
||||||
|
device.AsmFull("wsr {val}, INTCLEAR", map[string]interface{}{
|
||||||
|
"val": val,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// -- Interrupt matrix helpers -----------------------------------------------
|
// -- Interrupt matrix helpers -----------------------------------------------
|
||||||
// The ESP32-S3 interrupt matrix has one mapping register per peripheral
|
// The ESP32-S3 interrupt matrix has one mapping register per peripheral
|
||||||
// source. These are memory-mapped in the INTERRUPT_CORE0 peripheral.
|
// source. These are memory-mapped in the INTERRUPT_CORE0 peripheral.
|
||||||
|
|||||||
Reference in New Issue
Block a user