ARCHIVE: (using XPlained Pro board ATOM) Wake up SamD21 Through an Interrupt - BitKnitting/wakey_circuitpython GitHub Wiki

Goal

Understand how to wake up a SamD21 from standby mode using an external interrupt:

  • Project: Press a button to wake up the SamD21 from standby mode. We’ll use the SamD21 XPlained Pro.
  • Software environment:
    • C
    • Atom
    • GCC toolchain
    • Atmel Start
    • OpenOCD

By completing this exploration, we gain more context and insight into:

  • What the SamD21 datasheet is trying to say about setting up this scenario through configuring registers. Clear as mud.
  • How Atmel Start is used to jump-start programming an Atmel Chip - like the SamD21 - in C.
  • Atmel’s recommended steps to wake up a SamD21 from standby mode through an interrupt.

Get Going with Atmel Start

I found the easiest way to start SamD21 C source was to use Atmel Start . Atmel Start is a Web User Interface that lets us point-n-click together a C project + files to compile/link into a .elf file (e.g.: Makefile for the gcc toolchain). The C project inserts the ASF4 APIs. From Atmel: ”…the Atmel Software Framework, provides a rich set of proven drivers and code modules developed by experts to reduce customer design-time. It simplifies the usage of microcontrollers by providing an abstraction to the hardware through drivers and high-value middlewares. ASF is a free and open-source code library designed to be used for evaluation, prototyping, design, and production phases. The ASF4 driver architecture is built of three layers; HAL (Hardware Abstraction Layer), HPL (Hardware Proxy Layer), and HRI (Hardware Register Interface).

Get Initial Code

I thought this video I made is the "best" way to walk us through using Atmel Start to get to our initial code.

AtmelStart wakey
Getting a project up and running with Atmel Start was a big “OH…THAT’S How it’s done!”

Configure External Interrupt

We need to configure:

  • The pin that will generate the interrupt.
  • The NVIC.
  • The External Interrupt Controller (EIC).
  • Our callback to run when the interrupt is fired.

Configure the GPIO Pin

In the video, I set up both the LED0 and BUTTON pins. Atmel Start puts these definitions in the atmel_start_pins.h file:

#define BUTTON GPIO(GPIO_PORTA, 15)
#define LED0 GPIO(GPIO_PORTB, 30)

The code for configuring the Button is in driver_init.c:

	// Set pin direction to input
	gpio_set_pin_direction(BUTTON, GPIO_DIRECTION_IN);

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

	gpio_set_pin_function(BUTTON, PINMUX_PA15A_EIC_EXTINT15);  

Configure the NVIC

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).
We need to set up the NVIC to accept interrupt pending requests from the EIC peripheral. We see hpl_eic.c uses the CMSIS APIs to do this:

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

Configure the EIC

Registers

Here we configure the registers:

  • WAKEUP
  • CONFIGy
  • INTENSET, INTENCLR
  • INTFLAG
  • CTRL

WAKEUP

I changed the goo that was auto-generated for wakup (_ext_irq_init() hpl_eic.c) to simplify/better understand:

       /*
	* Write a 1 in the EIC’s WAKEUP register in the bit for WAKEUPEN15.
	*  i.e.: Tell the EIC to wake up the SamD21 when the interrupt triggers and the 
	*  SamD21 is in sleep mode.
	*/
	uint32_t extint_mask = 1 << PIN_PA15A_EIC_EXTINT15;
	EIC->WAKEUP.reg |= extint_mask;

CONFIGy

The CONFIGy register tells the EIC what will trigger our callback. In our case, it will be a change to a LOW logic level. We could also use filtering although I chose not to. Because we are not using filtering and not edge triggering, the EIC does not need any clocks. As with the WAKEUP register, I modified _ext_irq_init() hpl_eic.c CONFIGy setting goo to:

	/* Tell the EIC to trigger the interrupt on GPIO PA15/EXTINT[15] when the
	* voltage level goes from HIGH to LOW.  This is done by figuring out how to
	* talk with the CONFIGn register(s).
	*/
	uint8_t config_index = PIN_PA15A_EIC_EXTINT15 / 8;
	uint8_t position = (PIN_PA15A_EIC_EXTINT15 % 8) * 4;
	EIC->CONFIG[config_index].reg &=~ (EIC_CONFIG_SENSE0_Msk << position);
	EIC->CONFIG[config_index].reg |= EIC_CONFIG_SENSE0_LOW_Val << position;

INTENSET

Before we put the SamD21 into standby mode, we must enable the interrupt (in our case the BUTTON). 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;

INTFLAG

The SamD21 datasheet (21.6.3) notes the INTFLAG for the interrupt is set when the interrupt condition is met...."In level-sensitive mode, when interrupt has been cleared, INTFLAG.EXTINT[x] will be set immediately if the EXTINTx pin still matches the interrupt condition." The EIC handler provided in hpl_eic.c handles reading and clearing the INTFLAG.

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)

I left the Atmel Start code alone that did this step in _ext_irq_init():

	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);
    

Other Changes

GCLK

driver_init.c (EXTERNAL_IRQ_0_INIT) has a call to the function:

_gclk_enable_channel(EIC_GCLK_ID, CONF_GCLK_EIC_SRC);

I comment this out because of our scenario:

  • Trigger on a level.
  • Not using a Filter. Does not require clock configuration.

Configuring NMI and EVCTRL

We are not using NMI or events. I commented out (hpl_eic.c):

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);

Configuring The Filter

We are not using a filter. I commented out (hpl_eic.c):

	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);

Go To Sleep

I added the function zzz() in main.c and call it within the while(1) loop:

void zzz(void)
{
	__DSB(); // Complete any pending buffer writes.
	SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
	__WFI();
}

Compiling and Linking

I use the gcc toolchain. I wrote a python script - mm.py:

  • Took off the .py extension (i.e.: script starts with #!).
  • Copied to /usr/local/bin (i.e.: in PATH).
  • Copied over a .gdbinit file that helps with gdb debugging. I then run it within the gcc folder where the .elf file is built. I think (OK, I hope) what is going on is self-explanatory.

Debugging

I open two terminal windows. One runs OpenOCD the other runs GDB.

OpenOCD Terminal Window

I run the OpenOCD from the path where atmel_samd21_xplained_pro.cfg is located.

openocd -f atmel_samd21_xplained_pro.cfg

GDB Terminal Window

I run GDB from the gcc path.

arm-none-eabi-gdb AtmelStart.elf  

At This Point

Whew. I hope this is helpful. Please find many things to smile about.