Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/examples/pininterrupt/circuitplay-express.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//go:build circuitplay_express
// +build circuitplay_express

package main

import "machine"

const (
buttonPin = machine.BUTTON
buttonMode = machine.PinInputPulldown
buttonPinChange = machine.PinFalling
)
2 changes: 2 additions & 0 deletions src/examples/pininterrupt/pca10040.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//go:build pca10040
// +build pca10040

package main

import "machine"

const (
buttonPin = machine.BUTTON
buttonMode = machine.PinInputPullup
buttonPinChange = machine.PinRising
)
2 changes: 1 addition & 1 deletion src/examples/pininterrupt/pininterrupt.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

const (
button = machine.BUTTON
button = buttonPin
led = machine.LED
)

Expand Down
12 changes: 12 additions & 0 deletions src/examples/pininterrupt/rp2040.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build rp2040
// +build rp2040

package main

import "machine"

const (
buttonPin = machine.GPIO5 // GP5 on Pico and D10 on Nano-RP2040
buttonMode = machine.PinInputPullup
buttonPinChange = machine.PinRising
)
2 changes: 2 additions & 0 deletions src/examples/pininterrupt/stm32.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//go:build stm32
// +build stm32

package main

import "machine"

const (
buttonPin = machine.BUTTON
buttonMode = machine.PinInputPulldown
buttonPinChange = machine.PinRising | machine.PinFalling
)
2 changes: 2 additions & 0 deletions src/examples/pininterrupt/wioterminal.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//go:build wioterminal
// +build wioterminal

package main

import "machine"

const (
buttonPin = machine.BUTTON
buttonMode = machine.PinInput
buttonPinChange = machine.PinFalling
)
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)
}
}