Skip to content
Closed
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 Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
Expand Down
10 changes: 10 additions & 0 deletions src/examples/pininterrupt/circuitplay-express.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// +build circuitplay_express

package main

import "machine"

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

package main

import "machine"

const (
buttonMode = machine.PinInputPullup
buttonPinChange = machine.PinRising
)
52 changes: 52 additions & 0 deletions src/examples/pininterrupt/pininterrupt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

// This example demonstrates how to use pin change interrupts.
//
// This is only an example and should not be copied directly in any serious
// circuit, because it lacks an important feature: debouncing.
// See: https://en.wikipedia.org/wiki/Switch#Contact_bounce

import (
"machine"
"runtime/volatile"
"time"
)

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

func main() {
var lightLed volatile.Register8
lightLed.Set(0)

// Configure the LED, defaulting to on (usually setting the pin to low will
// turn the LED on).
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
led.Low()

// Make sure the pin is configured as a pullup to avoid floating inputs.
// Pullup works for most buttons, as most buttons short to ground when
// pressed.
button.Configure(machine.PinConfig{Mode: buttonMode})

// Set an interrupt on this pin.
err := button.SetInterrupt(buttonPinChange, func(machine.Pin) {
if lightLed.Get() != 0 {
lightLed.Set(0)
led.Low()
} else {
lightLed.Set(1)
led.High()
}
})
if err != nil {
println("could not configure pin interrupt:", err.Error())
}

// Make sure the program won't exit.
for {
time.Sleep(time.Hour)
}
}
9 changes: 5 additions & 4 deletions src/machine/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package machine
import "errors"

var (
ErrInvalidInputPin = errors.New("machine: invalid input pin")
ErrInvalidOutputPin = errors.New("machine: invalid output pin")
ErrInvalidClockPin = errors.New("machine: invalid clock pin")
ErrInvalidDataPin = errors.New("machine: invalid data pin")
ErrInvalidInputPin = errors.New("machine: invalid input pin")
ErrInvalidOutputPin = errors.New("machine: invalid output pin")
ErrInvalidClockPin = errors.New("machine: invalid clock pin")
ErrInvalidDataPin = errors.New("machine: invalid data pin")
ErrNoPinChangeChannel = errors.New("machine: no channel available for pin interrupt")
)

type PinConfig struct {
Expand Down
128 changes: 128 additions & 0 deletions src/machine/machine_atsamd21.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ const (
PinInputPulldown PinMode = 12
)

type PinChange uint8

// Pin change interrupt constants for SetInterrupt.
const (
PinRising PinChange = sam.EIC_CONFIG_SENSE0_RISE
PinFalling PinChange = sam.EIC_CONFIG_SENSE0_FALL
PinToggle PinChange = sam.EIC_CONFIG_SENSE0_BOTH
)

// Callbacks to be called for pins configured with SetInterrupt. Unfortunately,
// we also need to keep track of which interrupt channel is used by which pin,
// as the only alternative would be iterating through all pins.
//
// We're using the magic constant 16 here because the SAM D21 has 16 interrupt
// channels configurable for pins.
var (
interruptPins [16]Pin // warning: the value is invalid when pinCallbacks[i] is not set!
pinCallbacks [16]func(Pin)
)

const (
pinPadMapSERCOM0Pad0 byte = (0x10 << 1) | 0x00
pinPadMapSERCOM1Pad0 byte = (0x20 << 1) | 0x00
Expand Down Expand Up @@ -144,6 +164,114 @@ func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok b
return
}

// SetInterrupt sets an interrupt to be executed when a particular pin changes
// state.
//
// 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 {
// Most pins follow a common pattern where the EXTINT value is the pin
// number modulo 16. However, there are a few exceptions, as you can see
// below.
extint := uint8(0)
switch p {
case PA08:
// Connected to NMI. This is not currently supported.
return ErrInvalidInputPin
case PA24:
extint = 12
case PA25:
extint = 13
case PA27:
extint = 15
case PA28:
extint = 8
case PA30:
extint = 10
case PA31:
extint = 11
default:
// All other pins follow a normal pattern.
extint = uint8(p) % 16
}

if callback == nil {
// Disable this pin interrupt (if it was enabled).
sam.EIC.INTENCLR.Set(1 << extint)
if pinCallbacks[extint] != nil {
pinCallbacks[extint] = nil
}
return nil
}

if pinCallbacks[extint] != nil {
// The pin was already configured.
// To properly re-configure a pin, unset it first and set a new
// configuration.
return ErrNoPinChangeChannel
}
pinCallbacks[extint] = callback
interruptPins[extint] = p

if sam.EIC.CTRL.Get() == 0 {
// EIC peripheral has not yet been initialized. Initialize it now.

// The EIC needs two clocks: CLK_EIC_APB and GCLK_EIC. CLK_EIC_APB is
// enabled by default, so doesn't have to be re-enabled. The other is
// required for detecting edges and must be enabled manually.
sam.GCLK.CLKCTRL.Set(sam.GCLK_CLKCTRL_ID_EIC<<sam.GCLK_CLKCTRL_ID_Pos |
sam.GCLK_CLKCTRL_GEN_GCLK0<<sam.GCLK_CLKCTRL_GEN_Pos |
sam.GCLK_CLKCTRL_CLKEN)

// should not be necessary (CLKCTRL is not synchronized)
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
}

sam.EIC.CTRL.Set(sam.EIC_CTRL_ENABLE)
for sam.EIC.STATUS.HasBits(sam.EIC_STATUS_SYNCBUSY) {
}
}

// Configure this pin. Set the 4 bits of the EIC.CONFIGx register to the
// sense value (filter bit set to 0, sense bits set to the change value).
addr := &sam.EIC.CONFIG0
if extint >= 8 {
addr = &sam.EIC.CONFIG1
}
pos := (extint % 8) * 4 // bit position in register
addr.Set((addr.Get() &^ (0xf << pos)) | uint32(change)<<pos)

// Enable external interrupt for this pin.
sam.EIC.INTENSET.Set(1 << extint)

// Set the PMUXEN flag, while keeping the INEN and PULLEN flags (if they
// were set before). This avoids clearing the pin pull mode while
// configuring the pin interrupt.
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | (p.getPinCfg() & (sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)))
if p&1 > 0 {
// odd pin, so save the even pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
p.setPMux(val | (sam.PORT_PMUX0_PMUXO_A << sam.PORT_PMUX0_PMUXO_Pos))
} else {
// even pin, so save the odd pins
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
p.setPMux(val | (sam.PORT_PMUX0_PMUXE_A << sam.PORT_PMUX0_PMUXE_Pos))
}

interrupt.New(sam.IRQ_EIC, func(interrupt.Interrupt) {
flags := sam.EIC.INTFLAG.Get()
sam.EIC.INTFLAG.Set(flags) // clear interrupt
for i := uint(0); i < 16; i++ { // there are 16 channels
if flags&(1<<i) != 0 {
pinCallbacks[i](interruptPins[i])
}
}
}).Enable()

return nil
}

// InitADC initializes the ADC.
func InitADC() {
// ADC Bias Calibration
Expand Down
Loading