Skip to content
Merged
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
71 changes: 71 additions & 0 deletions src/machine/machine_nrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ const (
PinOutput PinMode = (nrf.GPIO_PIN_CNF_DIR_Output << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Disconnect << nrf.GPIO_PIN_CNF_INPUT_Pos)
)

type PinChange uint8

// Pin change interrupt constants for SetInterrupt.
const (
PinRising PinChange = nrf.GPIOTE_CONFIG_POLARITY_LoToHi
PinFalling PinChange = nrf.GPIOTE_CONFIG_POLARITY_HiToLo
PinToggle PinChange = nrf.GPIOTE_CONFIG_POLARITY_Toggle
)

// Callbacks to be called for pins configured with SetInterrupt.
var pinCallbacks [len(nrf.GPIOTE.CONFIG)]func(Pin)

// Configure this pin with the given configuration.
func (p Pin) Configure(config PinConfig) {
cfg := config.Mode | nrf.GPIO_PIN_CNF_DRIVE_S0S1 | nrf.GPIO_PIN_CNF_SENSE_Disabled
Expand Down Expand Up @@ -59,6 +71,65 @@ func (p Pin) Get() bool {
return (port.IN.Get()>>pin)&1 != 0
}

// 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 {
// Some variables to easily check whether a channel was already configured
// as an event channel for the given pin.
// This is not just an optimization, this is requred: the datasheet says
// that configuring more than one channel for a given pin results in
// unpredictable behavior.
expectedConfigMask := uint32(nrf.GPIOTE_CONFIG_MODE_Msk | nrf.GPIOTE_CONFIG_PSEL_Msk)
expectedConfig := nrf.GPIOTE_CONFIG_MODE_Event<<nrf.GPIOTE_CONFIG_MODE_Pos | uint32(p)<<nrf.GPIOTE_CONFIG_PSEL_Pos

foundChannel := false
for i := range nrf.GPIOTE.CONFIG {
config := nrf.GPIOTE.CONFIG[i].Get()
if config == 0 || config&expectedConfigMask == expectedConfig {
// Found an empty GPIOTE channel or one that was already configured
// for this pin.
if callback == nil {
// Disable this channel.
nrf.GPIOTE.INTENCLR.Set(uint32(1 << uint(i)))
pinCallbacks[i] = nil
return nil
}
// Enable this channel with the given callback.
nrf.GPIOTE.INTENCLR.Set(uint32(1 << uint(i)))
nrf.GPIOTE.CONFIG[i].Set(nrf.GPIOTE_CONFIG_MODE_Event<<nrf.GPIOTE_CONFIG_MODE_Pos |
uint32(p)<<nrf.GPIOTE_CONFIG_PSEL_Pos |
uint32(change)<<nrf.GPIOTE_CONFIG_POLARITY_Pos)
pinCallbacks[i] = callback
nrf.GPIOTE.INTENSET.Set(uint32(1 << uint(i)))
foundChannel = true
break
}
}

if !foundChannel {
return ErrNoPinChangeChannel
}

// Set and enable the GPIOTE interrupt. It's not a problem if this happens
// more than once.
interrupt.New(nrf.IRQ_GPIOTE, func(interrupt.Interrupt) {
for i := range nrf.GPIOTE.EVENTS_IN {
if nrf.GPIOTE.EVENTS_IN[i].Get() != 0 {
nrf.GPIOTE.EVENTS_IN[i].Set(0)
pin := Pin((nrf.GPIOTE.CONFIG[i].Get() & nrf.GPIOTE_CONFIG_PSEL_Msk) >> nrf.GPIOTE_CONFIG_PSEL_Pos)
pinCallbacks[i](pin)
}
}
}).Enable()

// Everything was configured correctly.
return nil
}

// UART on the NRF.
type UART struct {
Buffer *RingBuffer
Expand Down