Skip to content

Commit 5c8d4e5

Browse files
sago35deadprogram
authored andcommitted
sam: add support for pin change interrupts (samd5x)
1 parent fed433c commit 5c8d4e5

File tree

1 file changed

+160
-0
lines changed

1 file changed

+160
-0
lines changed

src/machine/machine_atsamd51.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,26 @@ const (
4444
PinInputPulldown PinMode = 18
4545
)
4646

47+
type PinChange uint8
48+
49+
// Pin change interrupt constants for SetInterrupt.
50+
const (
51+
PinRising PinChange = sam.EIC_CONFIG_SENSE0_RISE
52+
PinFalling PinChange = sam.EIC_CONFIG_SENSE0_FALL
53+
PinToggle PinChange = sam.EIC_CONFIG_SENSE0_BOTH
54+
)
55+
56+
// Callbacks to be called for pins configured with SetInterrupt. Unfortunately,
57+
// we also need to keep track of which interrupt channel is used by which pin,
58+
// as the only alternative would be iterating through all pins.
59+
//
60+
// We're using the magic constant 16 here because the SAM D21 has 16 interrupt
61+
// channels configurable for pins.
62+
var (
63+
interruptPins [16]Pin // warning: the value is invalid when pinCallbacks[i] is not set!
64+
pinCallbacks [16]func(Pin)
65+
)
66+
4767
// Hardware pins
4868
const (
4969
PA00 Pin = 0
@@ -268,6 +288,146 @@ func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok b
268288
return
269289
}
270290

291+
// SetInterrupt sets an interrupt to be executed when a particular pin changes
292+
// state.
293+
//
294+
// This call will replace a previously set callback on this pin. You can pass a
295+
// nil func to unset the pin change interrupt. If you do so, the change
296+
// parameter is ignored and can be set to any value (such as 0).
297+
func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
298+
// Most pins follow a common pattern where the EXTINT value is the pin
299+
// number modulo 16. However, there are a few exceptions, as you can see
300+
// below.
301+
extint := uint8(0)
302+
303+
switch p {
304+
case PA08:
305+
// Connected to NMI. This is not currently supported.
306+
return ErrInvalidInputPin
307+
case PB26:
308+
extint = 12
309+
case PB27:
310+
extint = 13
311+
case PB28:
312+
extint = 14
313+
case PB29:
314+
extint = 15
315+
default:
316+
// All other pins follow a normal pattern.
317+
extint = uint8(p) % 16
318+
}
319+
320+
if callback == nil {
321+
// Disable this pin interrupt (if it was enabled).
322+
sam.EIC.INTENCLR.Set(1 << extint)
323+
if pinCallbacks[extint] != nil {
324+
pinCallbacks[extint] = nil
325+
}
326+
return nil
327+
}
328+
329+
if pinCallbacks[extint] != nil {
330+
// The pin was already configured.
331+
// To properly re-configure a pin, unset it first and set a new
332+
// configuration.
333+
return ErrNoPinChangeChannel
334+
}
335+
pinCallbacks[extint] = callback
336+
interruptPins[extint] = p
337+
338+
if (sam.EIC.CTRLA.Get() & 0x02) == 0 {
339+
// EIC peripheral has not yet been initialized. Initialize it now.
340+
341+
// The EIC needs two clocks: CLK_EIC_APB and GCLK_EIC. CLK_EIC_APB is
342+
// enabled by default, so doesn't have to be re-enabled. The other is
343+
// required for detecting edges and must be enabled manually.
344+
sam.GCLK.PCHCTRL[4].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | sam.GCLK_PCHCTRL_CHEN)
345+
346+
// should not be necessary (CLKCTRL is not synchronized)
347+
for sam.GCLK.SYNCBUSY.HasBits(sam.GCLK_SYNCBUSY_GENCTRL_GCLK0 << sam.GCLK_SYNCBUSY_GENCTRL_Pos) {
348+
}
349+
}
350+
351+
// CONFIG register is enable-protected, so disable EIC.
352+
sam.EIC.CTRLA.Set(0)
353+
354+
// Configure this pin. Set the 4 bits of the EIC.CONFIGx register to the
355+
// sense value (filter bit set to 0, sense bits set to the change value).
356+
addr := &sam.EIC.CONFIG[0]
357+
if extint >= 8 {
358+
addr = &sam.EIC.CONFIG[1]
359+
}
360+
pos := (extint % 8) * 4 // bit position in register
361+
addr.Set((addr.Get() &^ (0xf << pos)) | uint32(change)<<pos)
362+
363+
// Enable external interrupt for this pin.
364+
sam.EIC.INTENSET.Set(1 << extint)
365+
366+
sam.EIC.CTRLA.Set(sam.EIC_CTRLA_ENABLE)
367+
for sam.EIC.SYNCBUSY.HasBits(sam.EIC_SYNCBUSY_ENABLE) {
368+
}
369+
370+
// Set the PMUXEN flag, while keeping the INEN and PULLEN flags (if they
371+
// were set before). This avoids clearing the pin pull mode while
372+
// configuring the pin interrupt.
373+
p.setPinCfg(sam.PORT_GROUP_PINCFG_PMUXEN | (p.getPinCfg() & (sam.PORT_GROUP_PINCFG_INEN | sam.PORT_GROUP_PINCFG_PULLEN)))
374+
if p&1 > 0 {
375+
// odd pin, so save the even pins
376+
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk
377+
p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXO_Pos))
378+
} else {
379+
// even pin, so save the odd pins
380+
val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk
381+
p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXE_Pos))
382+
}
383+
384+
handleEICInterrupt := func(interrupt.Interrupt) {
385+
flags := sam.EIC.INTFLAG.Get()
386+
sam.EIC.INTFLAG.Set(flags) // clear interrupt
387+
for i := uint(0); i < 16; i++ { // there are 16 channels
388+
if flags&(1<<i) != 0 {
389+
pinCallbacks[i](interruptPins[i])
390+
}
391+
}
392+
}
393+
switch extint {
394+
case 0:
395+
interrupt.New(sam.IRQ_EIC_EXTINT_0, handleEICInterrupt).Enable()
396+
case 1:
397+
interrupt.New(sam.IRQ_EIC_EXTINT_1, handleEICInterrupt).Enable()
398+
case 2:
399+
interrupt.New(sam.IRQ_EIC_EXTINT_2, handleEICInterrupt).Enable()
400+
case 3:
401+
interrupt.New(sam.IRQ_EIC_EXTINT_3, handleEICInterrupt).Enable()
402+
case 4:
403+
interrupt.New(sam.IRQ_EIC_EXTINT_4, handleEICInterrupt).Enable()
404+
case 5:
405+
interrupt.New(sam.IRQ_EIC_EXTINT_5, handleEICInterrupt).Enable()
406+
case 6:
407+
interrupt.New(sam.IRQ_EIC_EXTINT_6, handleEICInterrupt).Enable()
408+
case 7:
409+
interrupt.New(sam.IRQ_EIC_EXTINT_7, handleEICInterrupt).Enable()
410+
case 8:
411+
interrupt.New(sam.IRQ_EIC_EXTINT_8, handleEICInterrupt).Enable()
412+
case 9:
413+
interrupt.New(sam.IRQ_EIC_EXTINT_9, handleEICInterrupt).Enable()
414+
case 10:
415+
interrupt.New(sam.IRQ_EIC_EXTINT_10, handleEICInterrupt).Enable()
416+
case 11:
417+
interrupt.New(sam.IRQ_EIC_EXTINT_11, handleEICInterrupt).Enable()
418+
case 12:
419+
interrupt.New(sam.IRQ_EIC_EXTINT_12, handleEICInterrupt).Enable()
420+
case 13:
421+
interrupt.New(sam.IRQ_EIC_EXTINT_13, handleEICInterrupt).Enable()
422+
case 14:
423+
interrupt.New(sam.IRQ_EIC_EXTINT_14, handleEICInterrupt).Enable()
424+
case 15:
425+
interrupt.New(sam.IRQ_EIC_EXTINT_15, handleEICInterrupt).Enable()
426+
}
427+
428+
return nil
429+
}
430+
271431
// Return the register and mask to enable a given GPIO pin. This can be used to
272432
// implement bit-banged drivers.
273433
func (p Pin) PortMaskSet() (*uint32, uint32) {

0 commit comments

Comments
 (0)