Skip to content

Commit c72f9eb

Browse files
aykevldeadprogram
authored andcommitted
sam: add support for pin change interrupts
1 parent 19c7965 commit c72f9eb

File tree

2 files changed

+138
-0
lines changed

2 files changed

+138
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// +build circuitplay_express
2+
3+
package main
4+
5+
import "machine"
6+
7+
const (
8+
buttonMode = machine.PinInputPulldown
9+
buttonPinChange = machine.PinFalling
10+
)

src/machine/machine_atsamd21.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@ const (
3333
PinInputPulldown PinMode = 12
3434
)
3535

36+
type PinChange uint8
37+
38+
// Pin change interrupt constants for SetInterrupt.
39+
const (
40+
PinRising PinChange = sam.EIC_CONFIG_SENSE0_RISE
41+
PinFalling PinChange = sam.EIC_CONFIG_SENSE0_FALL
42+
PinToggle PinChange = sam.EIC_CONFIG_SENSE0_BOTH
43+
)
44+
45+
// Callbacks to be called for pins configured with SetInterrupt. Unfortunately,
46+
// we also need to keep track of which interrupt channel is used by which pin,
47+
// as the only alternative would be iterating through all pins.
48+
//
49+
// We're using the magic constant 16 here because the SAM D21 has 16 interrupt
50+
// channels configurable for pins.
51+
var (
52+
interruptPins [16]Pin // warning: the value is invalid when pinCallbacks[i] is not set!
53+
pinCallbacks [16]func(Pin)
54+
)
55+
3656
const (
3757
pinPadMapSERCOM0Pad0 byte = (0x10 << 1) | 0x00
3858
pinPadMapSERCOM1Pad0 byte = (0x20 << 1) | 0x00
@@ -144,6 +164,114 @@ func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok b
144164
return
145165
}
146166

167+
// SetInterrupt sets an interrupt to be executed when a particular pin changes
168+
// state.
169+
//
170+
// This call will replace a previously set callback on this pin. You can pass a
171+
// nil func to unset the pin change interrupt. If you do so, the change
172+
// parameter is ignored and can be set to any value (such as 0).
173+
func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
174+
// Most pins follow a common pattern where the EXTINT value is the pin
175+
// number modulo 16. However, there are a few exceptions, as you can see
176+
// below.
177+
extint := uint8(0)
178+
switch p {
179+
case PA08:
180+
// Connected to NMI. This is not currently supported.
181+
return ErrInvalidInputPin
182+
case PA24:
183+
extint = 12
184+
case PA25:
185+
extint = 13
186+
case PA27:
187+
extint = 15
188+
case PA28:
189+
extint = 8
190+
case PA30:
191+
extint = 10
192+
case PA31:
193+
extint = 11
194+
default:
195+
// All other pins follow a normal pattern.
196+
extint = uint8(p) % 16
197+
}
198+
199+
if callback == nil {
200+
// Disable this pin interrupt (if it was enabled).
201+
sam.EIC.INTENCLR.Set(1 << extint)
202+
if pinCallbacks[extint] != nil {
203+
pinCallbacks[extint] = nil
204+
}
205+
return nil
206+
}
207+
208+
if pinCallbacks[extint] != nil {
209+
// The pin was already configured.
210+
// To properly re-configure a pin, unset it first and set a new
211+
// configuration.
212+
return ErrNoPinChangeChannel
213+
}
214+
pinCallbacks[extint] = callback
215+
interruptPins[extint] = p
216+
217+
if sam.EIC.CTRL.Get() == 0 {
218+
// EIC peripheral has not yet been initialized. Initialize it now.
219+
220+
// The EIC needs two clocks: CLK_EIC_APB and GCLK_EIC. CLK_EIC_APB is
221+
// enabled by default, so doesn't have to be re-enabled. The other is
222+
// required for detecting edges and must be enabled manually.
223+
sam.GCLK.CLKCTRL.Set(sam.GCLK_CLKCTRL_ID_EIC<<sam.GCLK_CLKCTRL_ID_Pos |
224+
sam.GCLK_CLKCTRL_GEN_GCLK0<<sam.GCLK_CLKCTRL_GEN_Pos |
225+
sam.GCLK_CLKCTRL_CLKEN)
226+
227+
// should not be necessary (CLKCTRL is not synchronized)
228+
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
229+
}
230+
231+
sam.EIC.CTRL.Set(sam.EIC_CTRL_ENABLE)
232+
for sam.EIC.STATUS.HasBits(sam.EIC_STATUS_SYNCBUSY) {
233+
}
234+
}
235+
236+
// Configure this pin. Set the 4 bits of the EIC.CONFIGx register to the
237+
// sense value (filter bit set to 0, sense bits set to the change value).
238+
addr := &sam.EIC.CONFIG0
239+
if extint >= 8 {
240+
addr = &sam.EIC.CONFIG1
241+
}
242+
pos := (extint % 8) * 4 // bit position in register
243+
addr.Set((addr.Get() &^ (0xf << pos)) | uint32(change)<<pos)
244+
245+
// Enable external interrupt for this pin.
246+
sam.EIC.INTENSET.Set(1 << extint)
247+
248+
// Set the PMUXEN flag, while keeping the INEN and PULLEN flags (if they
249+
// were set before). This avoids clearing the pin pull mode while
250+
// configuring the pin interrupt.
251+
p.setPinCfg(sam.PORT_PINCFG0_PMUXEN | (p.getPinCfg() & (sam.PORT_PINCFG0_INEN | sam.PORT_PINCFG0_PULLEN)))
252+
if p&1 > 0 {
253+
// odd pin, so save the even pins
254+
val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk
255+
p.setPMux(val | (sam.PORT_PMUX0_PMUXO_A << sam.PORT_PMUX0_PMUXO_Pos))
256+
} else {
257+
// even pin, so save the odd pins
258+
val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk
259+
p.setPMux(val | (sam.PORT_PMUX0_PMUXE_A << sam.PORT_PMUX0_PMUXE_Pos))
260+
}
261+
262+
interrupt.New(sam.IRQ_EIC, func(interrupt.Interrupt) {
263+
flags := sam.EIC.INTFLAG.Get()
264+
sam.EIC.INTFLAG.Set(flags) // clear interrupt
265+
for i := uint(0); i < 16; i++ { // there are 16 channels
266+
if flags&(1<<i) != 0 {
267+
pinCallbacks[i](interruptPins[i])
268+
}
269+
}
270+
}).Enable()
271+
272+
return nil
273+
}
274+
147275
// InitADC initializes the ADC.
148276
func InitADC() {
149277
// ADC Bias Calibration

0 commit comments

Comments
 (0)