Wake Up SamD21 Through an External Interrupt - BitKnitting/wakey_circuitpython GitHub Wiki

So far we have:

The Goal

We'll add code to main.c that puts the SamD21 in standby mode. To wake up the SamD21, a HIGH level is applied to GPIO pin PA19 (that is the pin I configured in start_wakey project). When the interrupt is triggered, the awake() function is called. Here is main.c:

#include <atmel_start.h>

void awake() {
	for (int i = 0;i < 4;i++) {
		delay_ms(200);
		gpio_toggle_pin_level(LED0);
	}
}
void zzz(void)
{
	__DSB(); // Complete any pending buffer writes.
	SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
	__WFI();
}
int main(void)
{
	/* Initializes MCU, drivers and middleware */
	atmel_start_init();
	ext_irq_register(WAKEY,awake);
	gpio_set_pin_level(LED0,true);
	/* Replace with your application code */
	while (1) {
		zzz();
	}
}

Configuring the EIC - Before

The section of the code will change is the _ext_irq_init() function that is in hpl_eic.c. This is where the EIC is set up to handle waking up on an external interrupt.

This is the code before I made any changes:

int32_t _ext_irq_init(void (*cb)(const uint32_t pin))
{
	hri_eic_wait_for_sync(EIC);
	if (hri_eic_get_CTRL_reg(EIC, EIC_CTRL_ENABLE)) {
		hri_eic_write_CTRL_reg(EIC, 0);
		hri_eic_wait_for_sync(EIC);
	}
	hri_eic_write_CTRL_reg(EIC, EIC_CTRL_SWRST);
	hri_eic_wait_for_sync(EIC);

	hri_eic_write_NMICTRL_reg(
	    EIC, (CONF_EIC_NMIFILTEN << EIC_NMICTRL_NMIFILTEN_Pos) | EIC_NMICTRL_NMISENSE(CONF_EIC_NMISENSE));
	hri_eic_write_EVCTRL_reg(EIC,
	                         (CONF_EIC_EXTINTEO0 << 0) | (CONF_EIC_EXTINTEO1 << 1) | (CONF_EIC_EXTINTEO2 << 2)
	                             | (CONF_EIC_EXTINTEO3 << 3) | (CONF_EIC_EXTINTEO4 << 4) | (CONF_EIC_EXTINTEO5 << 5)
	                             | (CONF_EIC_EXTINTEO6 << 6) | (CONF_EIC_EXTINTEO7 << 7) | (CONF_EIC_EXTINTEO8 << 8)
	                             | (CONF_EIC_EXTINTEO9 << 9) | (CONF_EIC_EXTINTEO10 << 10) | (CONF_EIC_EXTINTEO11 << 11)
	                             | (CONF_EIC_EXTINTEO12 << 12) | (CONF_EIC_EXTINTEO13 << 13)
	                             | (CONF_EIC_EXTINTEO14 << 14) | (CONF_EIC_EXTINTEO15 << 15) | 0);

	hri_eic_write_WAKEUP_reg(EIC,
	                         (CONF_EIC_WAKEUPEN0 << 0) | (CONF_EIC_WAKEUPEN1 << 1) | (CONF_EIC_WAKEUPEN2 << 2)
	                             | (CONF_EIC_WAKEUPEN3 << 3) | (CONF_EIC_WAKEUPEN4 << 4) | (CONF_EIC_WAKEUPEN5 << 5)
	                             | (CONF_EIC_WAKEUPEN6 << 6) | (CONF_EIC_WAKEUPEN7 << 7) | (CONF_EIC_WAKEUPEN8 << 8)
	                             | (CONF_EIC_WAKEUPEN9 << 9) | (CONF_EIC_WAKEUPEN10 << 10) | (CONF_EIC_WAKEUPEN11 << 11)
	                             | (CONF_EIC_WAKEUPEN12 << 12) | (CONF_EIC_WAKEUPEN13 << 13)
	                             | (CONF_EIC_WAKEUPEN14 << 14) | (CONF_EIC_WAKEUPEN15 << 15) | 0);
	hri_eic_write_CONFIG_reg(EIC,
	                         0,
	                         (CONF_EIC_FILTEN0 << EIC_CONFIG_FILTEN0_Pos) | EIC_CONFIG_SENSE0(CONF_EIC_SENSE0)
	                             | (CONF_EIC_FILTEN1 << EIC_CONFIG_FILTEN1_Pos) | EIC_CONFIG_SENSE1(CONF_EIC_SENSE1)
	                             | (CONF_EIC_FILTEN2 << EIC_CONFIG_FILTEN2_Pos) | EIC_CONFIG_SENSE2(CONF_EIC_SENSE2)
	                             | (CONF_EIC_FILTEN3 << EIC_CONFIG_FILTEN3_Pos) | EIC_CONFIG_SENSE3(CONF_EIC_SENSE3)
	                             | (CONF_EIC_FILTEN4 << EIC_CONFIG_FILTEN4_Pos) | EIC_CONFIG_SENSE4(CONF_EIC_SENSE4)
	                             | (CONF_EIC_FILTEN5 << EIC_CONFIG_FILTEN5_Pos) | EIC_CONFIG_SENSE5(CONF_EIC_SENSE5)
	                             | (CONF_EIC_FILTEN6 << EIC_CONFIG_FILTEN6_Pos) | EIC_CONFIG_SENSE6(CONF_EIC_SENSE6)
	                             | (CONF_EIC_FILTEN7 << EIC_CONFIG_FILTEN7_Pos) | EIC_CONFIG_SENSE7(CONF_EIC_SENSE7)
	                             | 0);

	hri_eic_write_CONFIG_reg(EIC,
	                         1,
	                         (CONF_EIC_FILTEN8 << EIC_CONFIG_FILTEN0_Pos) | EIC_CONFIG_SENSE0(CONF_EIC_SENSE8)
	                             | (CONF_EIC_FILTEN9 << EIC_CONFIG_FILTEN1_Pos) | EIC_CONFIG_SENSE1(CONF_EIC_SENSE9)
	                             | (CONF_EIC_FILTEN10 << EIC_CONFIG_FILTEN2_Pos) | EIC_CONFIG_SENSE2(CONF_EIC_SENSE10)
	                             | (CONF_EIC_FILTEN11 << EIC_CONFIG_FILTEN3_Pos) | EIC_CONFIG_SENSE3(CONF_EIC_SENSE11)
	                             | (CONF_EIC_FILTEN12 << EIC_CONFIG_FILTEN4_Pos) | EIC_CONFIG_SENSE4(CONF_EIC_SENSE12)
	                             | (CONF_EIC_FILTEN13 << EIC_CONFIG_FILTEN5_Pos) | EIC_CONFIG_SENSE5(CONF_EIC_SENSE13)
	                             | (CONF_EIC_FILTEN14 << EIC_CONFIG_FILTEN6_Pos) | EIC_CONFIG_SENSE6(CONF_EIC_SENSE14)
	                             | (CONF_EIC_FILTEN15 << EIC_CONFIG_FILTEN7_Pos) | EIC_CONFIG_SENSE7(CONF_EIC_SENSE15)
	                             | 0);

	hri_eic_set_CTRL_ENABLE_bit(EIC);

	NVIC_DisableIRQ(EIC_IRQn);
	NVIC_ClearPendingIRQ(EIC_IRQn);
	NVIC_EnableIRQ(EIC_IRQn);

	callback = cb;

	return ERR_NONE;
}

Configuring the EIC - After

Here's the code I ended up using.

int32_t _ext_irq_init(void (*cb)(const uint32_t pin))
{
	hri_eic_wait_for_sync(EIC);
	if (hri_eic_get_CTRL_reg(EIC, EIC_CTRL_ENABLE)) {
		hri_eic_write_CTRL_reg(EIC, 0);
		hri_eic_wait_for_sync(EIC);
	}
	hri_eic_write_CTRL_reg(EIC, EIC_CTRL_SWRST);
	hri_eic_wait_for_sync(EIC);

        /* 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;
	/*
	 * Set the interrupt to trigger when the level goes to HIGH.
	 */
	uint8_t config_index = PIN_PA19A_EIC_EXTINT_NUM / 8;
	uint8_t position = (PIN_PA19A_EIC_EXTINT_NUM % 8) * 4;
	EIC->CONFIG[config_index].reg &=~ (EIC_CONFIG_SENSE0_Msk << position);
	EIC->CONFIG[config_index].reg |= EIC_CONFIG_SENSE0_HIGH_Val << position;


	hri_eic_set_CTRL_ENABLE_bit(EIC);
	/* Let the NVIC know the EIC will be communicating with it
	 */
	NVIC_DisableIRQ(EIC_IRQn);
	NVIC_ClearPendingIRQ(EIC_IRQn);
	NVIC_EnableIRQ(EIC_IRQn);

	callback = cb;

	return ERR_NONE;
}  

What's Going on

NVIC

	NVIC_DisableIRQ(EIC_IRQn);
	NVIC_ClearPendingIRQ(EIC_IRQn);
	NVIC_EnableIRQ(EIC_IRQn);

As noted in the SamD21 data sheet: An interrupt request will set the corresponding interrupt pending bit in the NVIC interrupt pending registers (SETPEND/CLRPEND bits in ISPR/ICPR). For the NVIC to activate the interrupt, it must be enabled in the NVIC interrupt enable register (SETENA/CLRENA bits in ISER/ICER).

Registers

We configure:

  • WAKEUP
  • CONFIGy
  • INTENSET, INTENCLR
  • CTRL

INTENSET, INTENCLR

21.6.6 of the SamD21 datasheet notes: "Each interrupt, except NMI, can be individually enabled by setting the corresponding bit in the Interrupt Enable Set register (INTENSET=1), and disabled by setting the corresponding bit in the Interrupt Enable Clear register (INTENCLR=1)." In our main.c, we add a line to register our callback interrupt:

  ext_irq_register(BUTTON,button_pressed);  

This function is located in hal_eic.c. It calls _ext_irq_enable() that is located in hpl_eic.c. In this function, the INTEN register bit is set. The CMSIS code for this would be

EIC->INTENSET.reg = 1 << PIN_PA15A_EIC_EXTINT15;

CTRL

The CTRL starts up the EIC. When setting these bits, the datasheet (21.6.9 Synchronization) notes:

The following bits are synchronized when written:
• Software Reset bit in the Control register (CTRL.SWRST)
• Enable bit in the Control register (CTRL.ENABLE)

Don't need GCLK

When the interrupt:

  • is level triggered,
  • isn't using a filter,
    the GCLK doesn't need to be configured. I deleted this line from driver_init.c:
void EXTERNAL_IRQ_0_init(void)
{
	_gclk_enable_channel(EIC_GCLK_ID, CONF_GCLK_EIC_SRC);

When I run this on the Itsy Bitsy, applying 3.3V to D12 (mapped to PA19) causes the red LED to blink 4 times.

YIPPEE! ....On to building a Circuit Python module....