diff --git a/Makefile b/Makefile index 02d6a099..073b46ff 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 00000000..82825274 --- /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 00000000..0cb29bc8 --- /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 a7863d8f..e4e1dcb7 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 663bf024..382dd05c 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