diff --git a/Makefile b/Makefile index b027d9c0de..ed9d81e04c 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/src/examples/pininterrupt/circuitplay-express.go b/src/examples/pininterrupt/circuitplay-express.go new file mode 100644 index 0000000000..e37105c2c7 --- /dev/null +++ b/src/examples/pininterrupt/circuitplay-express.go @@ -0,0 +1,10 @@ +// +build circuitplay_express + +package main + +import "machine" + +const ( + buttonMode = machine.PinInputPulldown + buttonPinChange = machine.PinFalling +) diff --git a/src/examples/pininterrupt/pca10040.go b/src/examples/pininterrupt/pca10040.go new file mode 100644 index 0000000000..828252746e --- /dev/null +++ b/src/examples/pininterrupt/pca10040.go @@ -0,0 +1,10 @@ +// +build pca10040 + +package main + +import "machine" + +const ( + buttonMode = machine.PinInputPullup + buttonPinChange = machine.PinRising +) diff --git a/src/examples/pininterrupt/pininterrupt.go b/src/examples/pininterrupt/pininterrupt.go new file mode 100644 index 0000000000..0cb29bc854 --- /dev/null +++ b/src/examples/pininterrupt/pininterrupt.go @@ -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) + } +} diff --git a/src/machine/machine.go b/src/machine/machine.go index a7863d8ff6..e4e1dcb70b 100644 --- a/src/machine/machine.go +++ b/src/machine/machine.go @@ -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 { diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index cd83b7b4df..8acabf34f4 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -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 @@ -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<= 8 { + addr = &sam.EIC.CONFIG1 + } + pos := (extint % 8) * 4 // bit position in register + addr.Set((addr.Get() &^ (0xf << pos)) | uint32(change)< 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<= 8 { + addr = &sam.EIC.CONFIG[1] + } + pos := (extint % 8) * 4 // bit position in register + addr.Set((addr.Get() &^ (0xf << pos)) | uint32(change)< 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk + p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk + p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXE_Pos)) + } + + handleEICInterrupt := 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<>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_PSEL_Pos) + pinCallbacks[i](pin) + } + } + }).Enable() + + // Everything was configured correctly. + return nil +} + // UART on the NRF. type UART struct { Buffer *RingBuffer