Responding to an External Interrupt on a SamD21 - BitKnitting/wakey_circuitpython GitHub Wiki

As I explored embedded firmware through the lens of learning how external interrupts worked on the SamD21 (with and without the CPU in standby mode), I gathered some key learnings that I wanted to be able to recall.

  • The usefulness of Atmel Start to jump-start a project.
  • The usefulness of Ozone to step through a .ELF file and view the contents of the EIC registers.
  • The code steps to build an embedded firmware app that responds to an external interrupt, with or without the CPU being in standby.

This page focuses on the code steps.

Code Steps

I use a mixture of ASF 4 APIs and CMSIS APIs. Whatever was easiest/made the most sense.

  • Attach a GCLK to the EIC peripheral.
_gclk_enable_channel(EIC_GCLK_ID, CONF_GCLK_EIC_SRC);

TBD: Pick a lower power GCLK.

  • Set up the GPIO pin (in this example we use PA19 which is tied to the EXT_INT3 channel).
	// Set pin direction to input
	gpio_set_pin_direction(PA19, GPIO_DIRECTION_IN);

	gpio_set_pin_pull_mode(PA19,
	                       // <y> Pull configuration
	                       // <id> pad_pull_config
	                       // <GPIO_PULL_OFF"> Off
	                       // <GPIO_PULL_UP"> Pull-up
	                       // <GPIO_PULL_DOWN"> Pull-down
	                       GPIO_PULL_DOWN);

	gpio_set_pin_function(PA19, PINMUX_PA19A_EIC_EXTINT3);

Note: The code created through Atmel Start put the pull mode to OFF. We change this to DOWN. This way, the voltage isn’t fluctuating between HIGH and LOW. If it did, our interrupt would be triggered when we didn’t want it to be.

  • Configure what voltage change we want to the interrupt to be triggered. (e.g.: PA19). We decide to trigger the interrupt when the voltage rises from LOW to HIGH.

While we're at it, we'll add in a bit of filtering to remove some amount of noise from responding to an interrupt.

	/*
	 * Set the interrupt to trigger when the the voltage rises from LOW to HIGH.
	 */
	uint8_t config_index = PIN_PA19A_EIC_EXTINT_NUM / 8;
	uint8_t position = (PIN_PA19A_EIC_EXTINT_NUM % 8) * 4;
	 // First clear the CONFIG register (in this case CONFIG0).
	EIC->CONFIG[config_index].reg &=~ (EIC_CONFIG_SENSE0_Msk << position);
	// Then set to rising with filtering.
	uint32_t sense_setting = EIC_CONFIG_SENSE0_RISE_Val | EIC_CONFIG_FILTEN0;
	EIC->CONFIG[config_index].reg |= sense_setting << position;
  • Set up the Interrupt to be triggered from standby mode.
        /* Configure the EIC so that the SamD21 will wake up from standby
         * when the external interrupt assigned to PA19 is triggered.
         */
	uint32_t extint_mask = 1 << PIN_PA19A_EIC_EXTINT_NUM;
	EIC->WAKEUP.reg |= extint_mask;  

Note: the external interrupt channel for PA19 is 3.

  • Enable the Interrupt.
hri_eic_set_INTEN_reg(EIC, extint_mask);
  • Turn the EIC peripheral on.
hri_eic_set_CTRL_ENABLE_bit(EIC);
  • Config the NVIC to let traffic from the EIC peripheral through to the CPU.
	/* Let the NVIC know the EIC will be communicating with it
	 */
	NVIC_DisableIRQ(EIC_IRQn);
	NVIC_ClearPendingIRQ(EIC_IRQn);
	NVIC_EnableIRQ(EIC_IRQn);
  • Set up the callback to be called by the CPU when the interrupt is triggered.
    When our code is told that a rising voltage was applied to our pin (e.g.: PA19), the CPU goes to the EIC Interrupt handler. The samd21g18a.h file points out the name of this function is void EIC_Handler(void).
    The Atmel Start code for this that is automatically generated is a lot of goop. To simplify, we go to the EIC_Handler() function in hpl_eic.c and simplify...
  • Clear the interrupt flag The SamD21 datasheet notes (21.6.6) The interrupt request remains active until the interrupt flag is cleared. Clear the interrupt flag in the interrupt flag status register.
volatile uint32_t flags = hri_eic_read_INTFLAG_reg(EIC) & hri_eic_read_INTEN_reg(EIC);
hri_eic_clear_INTFLAG_reg(EIC, flags);