Skip to content

Commit

Permalink
machine/rp2040: add interrupt API
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Oct 30, 2021
1 parent 5fa1e71 commit 6ace5d7
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 0 deletions.
9 changes: 9 additions & 0 deletions src/machine/machine_rp2040.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build rp2040
// +build rp2040

package machine
Expand Down Expand Up @@ -114,3 +115,11 @@ func init() {
UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt)
UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt)
}

// CurrentCore returns the core number the call was made from.
func CurrentCore() int {
return int(rp.SIO.CPUID.Get())
}

// NumCores returns number of cores available on the device.
func NumCores() int { return 2 }
107 changes: 107 additions & 0 deletions src/machine/machine_rp2040_gpio.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//go:build rp2040
// +build rp2040

package machine

import (
"device/rp"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
)
Expand Down Expand Up @@ -208,3 +210,108 @@ func (p Pin) Set(value bool) {
func (p Pin) Get() bool {
return p.get()
}

// PinChange represents one or more trigger events that can happen on a given GPIO pin
// on the RP2040. ORed PinChanges are valid input to most IRQ functions.
type PinChange uint8

// Pin change interrupt constants for SetInterrupt.
const (
// PinLevelLow triggers whenever pin is at a low (around 0V) logic level.
PinLevelLow PinChange = 1 << iota
// PinLevelLow triggers whenever pin is at a high (around 3V) logic level.
PinLevelHigh
// Edge falling
PinFalling
// Edge rising
PinRising
)

// Callbacks to be called for pins configured with SetInterrupt.
var (
pinCallbacks [2]func(Pin)
setInt [2]bool
)

// 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 {
if p > 31 || p < 0 {
return ErrInvalidInputPin
}
core := CurrentCore()
if callback == nil {
// disable current interrupt
p.setInterrupt(change, false)
pinCallbacks[core] = nil
return nil
}

if pinCallbacks[core] != nil {
// Callback already configured. Should disable callback by passing a nil callback first.
return ErrNoPinChangeChannel
}
p.setInterrupt(change, true)
pinCallbacks[core] = callback

if setInt[core] {
// interrupt has already been set. Exit.
println("core set")
return nil
}
interrupt.New(rp.IRQ_IO_IRQ_BANK0, gpioHandleInterrupt).Enable()
irqSet(rp.IRQ_IO_IRQ_BANK0, true)
return nil
}

// gpioHandleInterrupt finds the corresponding pin for the interrupt.
// C SDK equivalent of gpio_irq_handler
func gpioHandleInterrupt(intr interrupt.Interrupt) {
// panic("END") // if program is not ended here rp2040 will call interrupt again when finished, a vicious spin cycle.
core := CurrentCore()
callback := pinCallbacks[core]
if callback != nil {
// TODO fix gpio acquisition (see below)
// For now all callbacks get pin 255 (nonexistent).
callback(0xff)
}
var gpio Pin
for gpio = 0; gpio < _NUMBANK0_GPIOS; gpio++ {
// Acknowledge all GPIO interrupts for now
// since we are yet unable to acquire interrupt status
gpio.acknowledgeInterrupt(0xff) // TODO fix status get. For now we acknowledge all pending interrupts.
// Commented code below from C SDK not working.
// statreg := base.intS[gpio>>3]
// change := getIntChange(gpio, statreg.Get())
// if change != 0 {
// gpio.acknowledgeInterrupt(change)
// if callback != nil {
// callback(gpio)
// return
// } else {
// panic("unset callback in handler")
// }
// }
}
}

// events returns the bit representation of the pin change for the rp2040.
func (change PinChange) events() uint32 {
return uint32(change)
}

// intBit is the bit storage form of a PinChange for a given Pin
// in the IO_BANK0 interrupt registers (page 269 RP2040 Datasheet).
func (p Pin) ioIntBit(change PinChange) uint32 {
return change.events() << (4 * (p % 8))
}

// Acquire interrupt data from a INT status register.
func getIntChange(p Pin, status uint32) PinChange {
return PinChange(status>>(4*(p%8))) & 0xf
}
73 changes: 73 additions & 0 deletions src/machine/machine_rp2040_sync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//go:build rp2040
// +build rp2040

package machine

import (
"device/rp"
)

// machine_rp2040_sync.go contains interrupt and
// lock primitives similat to thos found in Pico SDK's
// irq.c

const (
// Number of spin locks available
_NUMSPINLOCKS = 32
// Number of interrupt handlers available
_NUMIRQ = 32
_PICO_SPINLOCK_ID_IRQ = 9
_NUMBANK0_GPIOS = 30
)

// Clears interrupt flag on a pin
func (p Pin) acknowledgeInterrupt(change PinChange) {
ioBank0.intR[p>>3].Set(p.ioIntBit(change))
}

// Basic interrupt setting via ioBANK0 for GPIO interrupts.
func (p Pin) setInterrupt(change PinChange, enabled bool) {
// Separate mask/force/status per-core, so check which core called, and
// set the relevant IRQ controls.
switch CurrentCore() {
case 0:
p.ctrlSetInterrupt(change, enabled, &ioBank0.proc0IRQctrl)
case 1:
p.ctrlSetInterrupt(change, enabled, &ioBank0.proc1IRQctrl)
}
}

// ctrlSetInterrupt acknowledges any pending interrupt and enables or disables
// the interrupt for a given IRQ control bank (IOBANK, DormantIRQ, QSPI).
//
// pico-sdk calls this the _gpio_set_irq_enabled, not to be confused with
// gpio_set_irq_enabled (no leading underscore).
func (p Pin) ctrlSetInterrupt(change PinChange, enabled bool, base *irqCtrl) {
p.acknowledgeInterrupt(change)
enReg := &base.intE[p>>3]
if enabled {
enReg.SetBits(p.ioIntBit(change))
} else {
enReg.ClearBits(p.ioIntBit(change))
}
}

// Enable or disable a specific interrupt on the executing core.
// num is the interrupt number which must be in [0,31].
func irqSet(num uint32, enabled bool) {
if num >= _NUMIRQ {
return
}
irqSetMask(1<<num, enabled)
}

func irqSetMask(mask uint32, enabled bool) {
if enabled {
// Clear pending before enable
// (if IRQ is actually asserted, it will immediately re-pend)
rp.PPB.NVIC_ICPR.Set(mask)
rp.PPB.NVIC_ISER.Set(mask)
} else {
rp.PPB.NVIC_ICER.Set(mask)
}
}

0 comments on commit 6ace5d7

Please sign in to comment.