Wake Up SamD21 Through an External Interrupt - BitKnitting/wakey_circuitpython GitHub Wiki
So far we have:
- Created a project with Atmel Start
- Loaded the project (and some additional goodies like EmbSysRegView) into Eclipse.
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 fromdriver_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....