Skip to content

Commit 7bc7a42

Browse files
committed
machine/rp2040: add interrupt API
1 parent 5fa1e71 commit 7bc7a42

File tree

3 files changed

+189
-0
lines changed

3 files changed

+189
-0
lines changed

src/machine/machine_rp2040.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//go:build rp2040
12
// +build rp2040
23

34
package machine
@@ -114,3 +115,11 @@ func init() {
114115
UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt)
115116
UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt)
116117
}
118+
119+
// CurrentCore returns the core number the call was made from.
120+
func CurrentCore() int {
121+
return int(rp.SIO.CPUID.Get())
122+
}
123+
124+
// NumCores returns number of cores available on the device.
125+
func NumCores() int { return 2 }

src/machine/machine_rp2040_gpio.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
//go:build rp2040
12
// +build rp2040
23

34
package machine
45

56
import (
67
"device/rp"
8+
"runtime/interrupt"
79
"runtime/volatile"
810
"unsafe"
911
)
@@ -208,3 +210,108 @@ func (p Pin) Set(value bool) {
208210
func (p Pin) Get() bool {
209211
return p.get()
210212
}
213+
214+
// PinChange represents one or more trigger events that can happen on a given GPIO pin
215+
// on the RP2040. ORed PinChanges are valid input to most IRQ functions.
216+
type PinChange uint8
217+
218+
// Pin change interrupt constants for SetInterrupt.
219+
const (
220+
// PinLevelLow triggers whenever pin is at a low (around 0V) logic level.
221+
PinLevelLow PinChange = 1 << iota
222+
// PinLevelLow triggers whenever pin is at a high (around 3V) logic level.
223+
PinLevelHigh
224+
// Edge falling
225+
PinFalling
226+
// Edge rising
227+
PinRising
228+
)
229+
230+
// Callbacks to be called for pins configured with SetInterrupt.
231+
var (
232+
pinCallbacks [2]func(Pin)
233+
setInt [2]bool
234+
)
235+
236+
// SetInterrupt sets an interrupt to be executed when a particular pin changes
237+
// state. The pin should already be configured as an input, including a pull up
238+
// or down if no external pull is provided.
239+
//
240+
// This call will replace a previously set callback on this pin. You can pass a
241+
// nil func to unset the pin change interrupt. If you do so, the change
242+
// parameter is ignored and can be set to any value (such as 0).
243+
func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
244+
if p > 31 || p < 0 {
245+
return ErrInvalidInputPin
246+
}
247+
core := CurrentCore()
248+
if callback == nil {
249+
// disable current interrupt
250+
p.setInterrupt(change, false)
251+
pinCallbacks[core] = nil
252+
return nil
253+
}
254+
255+
if pinCallbacks[core] != nil {
256+
// Callback already configured. Should disable callback by passing a nil callback first.
257+
return ErrNoPinChangeChannel
258+
}
259+
p.setInterrupt(change, true)
260+
pinCallbacks[core] = callback
261+
262+
if setInt[core] {
263+
// interrupt has already been set. Exit.
264+
println("core set")
265+
return nil
266+
}
267+
interrupt.New(rp.IRQ_IO_IRQ_BANK0, gpioHandleInterrupt).Enable()
268+
irqSet(rp.IRQ_IO_IRQ_BANK0, true)
269+
return nil
270+
}
271+
272+
// gpioHandleInterrupt finds the corresponding pin for the interrupt.
273+
// C SDK equivalent of gpio_irq_handler
274+
func gpioHandleInterrupt(intr interrupt.Interrupt) {
275+
// panic("END") // if program is not ended here rp2040 will call interrupt again when finished, a vicious spin cycle.
276+
core := CurrentCore()
277+
callback := pinCallbacks[core]
278+
if callback != nil {
279+
// TODO fix gpio acquisition (see below)
280+
// For now all callbacks get pin 255 (nonexistent).
281+
callback(0xff)
282+
}
283+
var gpio Pin
284+
for gpio = 0; gpio < _NUMBANK0_GPIOS; gpio++ {
285+
// Acknowledge all GPIO interrupts for now
286+
// since we are yet unable to acquire interrupt status
287+
gpio.acknowledgeInterrupt(0xff) // TODO fix status get. For now we acknowledge all pending interrupts.
288+
// Commented code below from C SDK not working.
289+
// statreg := base.intS[gpio>>3]
290+
// change := getIntChange(gpio, statreg.Get())
291+
// if change != 0 {
292+
// gpio.acknowledgeInterrupt(change)
293+
// if callback != nil {
294+
// callback(gpio)
295+
// return
296+
// } else {
297+
// panic("unset callback in handler")
298+
// }
299+
// }
300+
}
301+
}
302+
303+
// events returns the bit representation of the pin change for the rp2040.
304+
func (change PinChange) events() uint32 {
305+
return uint32(change)
306+
}
307+
308+
// intBit is the bit storage form of a PinChange for a given Pin
309+
// in the IO_BANK0 interrupt registers (page 269 RP2040 Datasheet).
310+
func (p Pin) ioIntBit(change PinChange) uint32 {
311+
return change.events() << (4 * (p % 8))
312+
}
313+
314+
// Acquire interrupt data from a INT status register.
315+
func getIntChange(p Pin, status uint32) PinChange {
316+
return PinChange(status>>(4*(p%8))) & 0xf
317+
}

src/machine/machine_rp2040_sync.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//go:build rp2040
2+
// +build rp2040
3+
4+
package machine
5+
6+
import (
7+
"device/rp"
8+
)
9+
10+
// machine_rp2040_sync.go contains interrupt and
11+
// lock primitives similar to those found in Pico SDK's
12+
// irq.c
13+
14+
const (
15+
// Number of spin locks available
16+
_NUMSPINLOCKS = 32
17+
// Number of interrupt handlers available
18+
_NUMIRQ = 32
19+
_PICO_SPINLOCK_ID_IRQ = 9
20+
_NUMBANK0_GPIOS = 30
21+
)
22+
23+
// Clears interrupt flag on a pin
24+
func (p Pin) acknowledgeInterrupt(change PinChange) {
25+
ioBank0.intR[p>>3].Set(p.ioIntBit(change))
26+
}
27+
28+
// Basic interrupt setting via ioBANK0 for GPIO interrupts.
29+
func (p Pin) setInterrupt(change PinChange, enabled bool) {
30+
// Separate mask/force/status per-core, so check which core called, and
31+
// set the relevant IRQ controls.
32+
switch CurrentCore() {
33+
case 0:
34+
p.ctrlSetInterrupt(change, enabled, &ioBank0.proc0IRQctrl)
35+
case 1:
36+
p.ctrlSetInterrupt(change, enabled, &ioBank0.proc1IRQctrl)
37+
}
38+
}
39+
40+
// ctrlSetInterrupt acknowledges any pending interrupt and enables or disables
41+
// the interrupt for a given IRQ control bank (IOBANK, DormantIRQ, QSPI).
42+
//
43+
// pico-sdk calls this the _gpio_set_irq_enabled, not to be confused with
44+
// gpio_set_irq_enabled (no leading underscore).
45+
func (p Pin) ctrlSetInterrupt(change PinChange, enabled bool, base *irqCtrl) {
46+
p.acknowledgeInterrupt(change)
47+
enReg := &base.intE[p>>3]
48+
if enabled {
49+
enReg.SetBits(p.ioIntBit(change))
50+
} else {
51+
enReg.ClearBits(p.ioIntBit(change))
52+
}
53+
}
54+
55+
// Enable or disable a specific interrupt on the executing core.
56+
// num is the interrupt number which must be in [0,31].
57+
func irqSet(num uint32, enabled bool) {
58+
if num >= _NUMIRQ {
59+
return
60+
}
61+
irqSetMask(1<<num, enabled)
62+
}
63+
64+
func irqSetMask(mask uint32, enabled bool) {
65+
if enabled {
66+
// Clear pending before enable
67+
// (if IRQ is actually asserted, it will immediately re-pend)
68+
rp.PPB.NVIC_ICPR.Set(mask)
69+
rp.PPB.NVIC_ISER.Set(mask)
70+
} else {
71+
rp.PPB.NVIC_ICER.Set(mask)
72+
}
73+
}

0 commit comments

Comments
 (0)