aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorsoypat <[email protected]>2021-10-30 16:56:39 -0300
committerRon Evans <[email protected]>2021-11-12 10:38:02 +0100
commitb534dd67e06911c1806e157d4702f54bd9607eab (patch)
treea9d896bce231b3aff9175bd65d1007295b77a8b3
parent6c02b4956cab4ebcb36d39fe9ff0cb646e120639 (diff)
downloadtinygo-b534dd67e06911c1806e157d4702f54bd9607eab.tar.gz
tinygo-b534dd67e06911c1806e157d4702f54bd9607eab.zip
machine/rp2040: add interrupt API
-rw-r--r--src/machine/machine_rp2040.go9
-rw-r--r--src/machine/machine_rp2040_gpio.go107
-rw-r--r--src/machine/machine_rp2040_sync.go73
3 files changed, 189 insertions, 0 deletions
diff --git a/src/machine/machine_rp2040.go b/src/machine/machine_rp2040.go
index 297c92e3b..1d86f2082 100644
--- a/src/machine/machine_rp2040.go
+++ b/src/machine/machine_rp2040.go
@@ -1,3 +1,4 @@
+//go:build rp2040
// +build rp2040
package machine
@@ -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 }
diff --git a/src/machine/machine_rp2040_gpio.go b/src/machine/machine_rp2040_gpio.go
index 3aea11e82..e20a84c69 100644
--- a/src/machine/machine_rp2040_gpio.go
+++ b/src/machine/machine_rp2040_gpio.go
@@ -1,9 +1,11 @@
+//go:build rp2040
// +build rp2040
package machine
import (
"device/rp"
+ "runtime/interrupt"
"runtime/volatile"
"unsafe"
)
@@ -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
+}
diff --git a/src/machine/machine_rp2040_sync.go b/src/machine/machine_rp2040_sync.go
new file mode 100644
index 000000000..18ac261cb
--- /dev/null
+++ b/src/machine/machine_rp2040_sync.go
@@ -0,0 +1,73 @@
+//go:build rp2040
+// +build rp2040
+
+package machine
+
+import (
+ "device/rp"
+)
+
+// machine_rp2040_sync.go contains interrupt and
+// lock primitives similar to those 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)
+ }
+}