diff options
author | soypat <[email protected]> | 2021-10-30 16:56:39 -0300 |
---|---|---|
committer | Ron Evans <[email protected]> | 2021-11-12 10:38:02 +0100 |
commit | b534dd67e06911c1806e157d4702f54bd9607eab (patch) | |
tree | a9d896bce231b3aff9175bd65d1007295b77a8b3 | |
parent | 6c02b4956cab4ebcb36d39fe9ff0cb646e120639 (diff) | |
download | tinygo-b534dd67e06911c1806e157d4702f54bd9607eab.tar.gz tinygo-b534dd67e06911c1806e157d4702f54bd9607eab.zip |
machine/rp2040: add interrupt API
-rw-r--r-- | src/machine/machine_rp2040.go | 9 | ||||
-rw-r--r-- | src/machine/machine_rp2040_gpio.go | 107 | ||||
-rw-r--r-- | src/machine/machine_rp2040_sync.go | 73 |
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) + } +} |