From 8ff05abe3ed29bdb46d15b41b2deab318a4b1a3d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 13 Jan 2020 16:21:16 +0100 Subject: [PATCH 1/3] nrf: add support for pin change interrupts --- Makefile | 2 + src/examples/pininterrupt/pca10040.go | 10 ++++ src/examples/pininterrupt/pininterrupt.go | 52 +++++++++++++++++ src/machine/machine.go | 9 +-- src/machine/machine_nrf.go | 71 +++++++++++++++++++++++ 5 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 src/examples/pininterrupt/pca10040.go create mode 100644 src/examples/pininterrupt/pininterrupt.go 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/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_nrf.go b/src/machine/machine_nrf.go index 663bf02405..382dd05c20 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -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 @@ -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_PSEL_Pos) + pinCallbacks[i](pin) + } + } + }).Enable() + + // Everything was configured correctly. + return nil +} + // UART on the NRF. type UART struct { Buffer *RingBuffer From 7d944899943c985f374b7ff387e376212c57e64b Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 15 Jan 2020 12:58:40 +0100 Subject: [PATCH 2/3] sam: add support for pin change interrupts --- .../pininterrupt/circuitplay-express.go | 10 ++ src/machine/machine_atsamd21.go | 128 ++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 src/examples/pininterrupt/circuitplay-express.go 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/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< Date: Tue, 12 May 2020 21:07:08 +0900 Subject: [PATCH 3/3] sam: add support for pin change interrupts (samd5x) --- src/machine/machine_atsamd51.go | 160 ++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 08459748cd..5b2afdbc75 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -44,6 +44,26 @@ const ( PinInputPulldown PinMode = 18 ) +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) +) + // Hardware pins const ( PA00 Pin = 0 @@ -268,6 +288,146 @@ 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 PB26: + extint = 12 + case PB27: + extint = 13 + case PB28: + extint = 14 + case PB29: + extint = 15 + 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.CTRLA.Get() & 0x02) == 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.PCHCTRL[4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN) + + // should not be necessary (CLKCTRL is not synchronized) + for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK0 << sam.GCLK_SYNCBUSY_GENCTRL_Pos) { + } + } + + // CONFIG register is enable-protected, so disable EIC. + sam.EIC.CTRLA.Set(0) + + // 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.CONFIG[0] + if extint >= 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<