EIC not working in Arduino MKR1000 using SAMD21

Hello,
I'm unable to get the EIC to work on my MKR1000 using SAMD21 (have been at it since the past two days). I've used the following two posts as reference to enable EIC on D0 which is PA22.
1.) Enable Alternate Pins for Interrupts - #7 by gavinremme
2.) SAMD-Projects/app.c at master · ArmstrongSubero/SAMD-Projects · GitHub

According to the variant.cpp file, D0 has EXTINT6. I've shared the code below.

const int redPin = 3;
const int bluePin = 1;
const int greenPin = 2;
bool check = false;

void setup()
{
  // Set D1 (PA23), D2 (PA10), D3 (PA11) as output. Same as pinMode
  PORT->Group[PORTA].DIRSET.reg = PORT_PA11;
  PORT->Group[PORTA].DIRSET.reg = PORT_PA23;
  PORT->Group[PORTA].DIRSET.reg = PORT_PA10;

  // Enable EIC
  enable_EIC();

  // Disable external interrupt controller
  NVIC_DisableIRQ(EIC_IRQn);

  // Clear any pending device specific IRQn
  NVIC_ClearPendingIRQ(EIC_IRQn);

  // Set interrupt priority
  NVIC_SetPriority(EIC_IRQn, 0);

  // Enabling Interrupt
  NVIC_EnableIRQ(EIC_IRQn);

  analogWrite(redPin, 100);
  analogWrite(bluePin, 0);
  analogWrite(greenPin, 10);
}

void loop()
{
}

void enable_EIC()
{

  PORT->Group[PORTA].PINCFG[22].bit.INEN = 1;
  PORT->Group[PORTA].PINCFG[22].bit.PULLEN = 1;
  PORT->Group[PORTA].PINCFG[22].bit.PMUXEN = 1;
  PORT->Group[PORTA].PMUX[22 >> 1].reg |= PORT_PMUX_PMUXE_A;

  // Enable GCLK for IEC (External Interrupt Controller)
  GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EIC));
  while (GCLK->STATUS.bit.SYNCBUSY);

  // Enable EIC
  EIC->CTRL.bit.ENABLE = 1;
  while (EIC->STATUS.bit.SYNCBUSY == 1) { }

  EIC->WAKEUP.bit.WAKEUPEN6 = 1;
  EIC->INTENSET.bit.EXTINT6 = 1;

  // Rising edge detection
  // Filter 7 enable
  // config 0 for EXTINT 0-7 and config 1 for EXTINT 8-15
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE7_RISE | EIC_CONFIG_FILTEN7;
}

void EIC_Handler(void)
{
  Serial.println("Hello");
  analogWrite(redPin, 0);
  analogWrite(bluePin, 155);
  analogWrite(greenPin, 0);
  EIC->INTFLAG.bit.EXTINT6 = 1;
  //  if ((EIC->INTFLAG.reg & PORT_PB11) != 0)
  //  {
  //
  //
  //    // Clear interrupt flag
  //    EIC->INTFLAG.reg = PORT_PB11;
  //  }
}

Thanks a lot for the help.

How is everything wired?

@natsu_dragneel9 Why not just use the Arduino attachInterrupt() function?: https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/.

 attachInterrupt(digitalPinToInterrupt(0), intCallbackFunc, RISING);

Also, on the SAMD21 (unlike the AVR microcontrollers), it isn't necessary to enable the outputs for analogWrite(), as the function does this for you.

@er_name_not_found
RGB led is working as expected. No problems there.

Pushbutton:
Vcc is connected to one side (let's call it side A) of the pushbutton. A 220 ohm resistor from the other side (let's call this side B) of pushbutton to gnd. A wire from side B to D0.

Also, this connection seems correct because it worked with attachInterrupt().

@MartinL
I wanted some practice with register level programming and C/C++. So, I'm trying out various things like EIC, I2C, SERCOMs, etc. Just wanted to understand things better! I did try with attachInterrupt() to test for any errors in the physical connection.

Ohk. I'll keep that in mind.

Fair enough :smiley:.

Have you tried also enabling the interrupt on channel 6?:

EIC->INTENSET.reg = EIC_INTENSET_EXTINT6;

Also, I noticed that the register bitfields in your code have been set for interrupt channel 7, shouldn't these be on channel 6?

@MartinL
EIC->INTENSET.reg = EIC_INTENSET_EXTINT6;

Doesn't the following line perform the same operation?

EIC->INTENSET.bit.EXTINT6 = 1;

After changing the interrupt channel from 7 to 6, the code works now. I've two other questions:

  1. If I want to enable EIC on pin D5 which is PB11 according to the variant.cpp file, what should be the interrupt channel? I couldn't find the EIC_CONFIG_SENSE11_RISE in the eic.h.
  2. How to know when to perform the bitwise operation on register assignment? For example, after PMUX[22 >> 1].reg there's |= inPORT->Group[PORTA].PMUX[22 >> 1].reg |= PORT_PMUX_PMUXE_A; . This means that that particular register is being ORed and the result is reassigned to the same register. However, in GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EIC)); there's no ORing and assignment. Similarly, there's no ORing and assignment in line 106 in SAMD-Projects/app.c at master · ArmstrongSubero/SAMD-Projects · GitHub as well as in EIC->INTENSET.reg = EIC_INTENSET_EXTINT6;

Yes it does. My mistake I missed that line in your code.

If I want to enable EIC on pin D5 which is PB11 according to the variant.cpp file, what should be the interrupt channel? I couldn't find the EIC_CONFIG_SENSE11_RISE in the eic.h.

To go above interrupt channel 7, it's necessary to use the CONFIG[1] register. For example to use EIC interrupt channel 8:

EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE0_RISE;                            // Set event detecting a RISE-ing edge

channel 9:

EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE1_RISE;                            // Set event detecting a RISE-ing edge

...and so on...

How to know when to perform the bitwise operation on register assignment? For example, after PMUX[22 >> 1].reg there's |= inPORT->Group[PORTA].PMUX[22 >> 1].reg |= PORT_PMUX_PMUXE_A; . This means that that particular register is being ORed and the result is reassigned to the same register. However, in GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EIC)); there's no ORing and assignment. Similarly, there's no ORing and assignment in line 106 in SAMD-Projects/app.c at master · ArmstrongSubero/SAMD-Projects · GitHub as well as in EIC->INTENSET.reg = EIC_INTENSET_EXTINT6;

That's because the even and odd port pairs (PA00 & PA01, PA02 & PA03, etc...) share the same PMUX register with the PMUXE (even) taking the lowest significant nibble (4-bits) and the PMUXO (odd) the most significant of the 8-bit register. The use of the bitwise OR in the line:

PORT->Group[PORTA].PMUX[22 >> 1].reg |= PORT_PMUX_PMUXE_A;

... is to prevent the writing of the PMUXE bitfield from overwriting any previous PMUXO entry.

The reason for not bitwise ORing the GCLK's CLKCTRL, GENCTRL and GENDIV registers is because they use indirect access, meaning that the register is acting as a portal through which the GCLK to be configured is specified by an ID bitfield in the register itself. It's mentioned in section 15.6.4.1 Indirect Access in the SAMD21 datasheet.

The INTENSET (set) and its INTENCLR (clear) counterpart automatically perform a read-modify-write in hardware, making the bitwise ORing of the register in software unnecessary. A small performance enhancement when activating and deactivating interrupt enable bits.

That's because the even and odd port pairs (PA00 & PA01, PA02 & PA03, etc...) share the same PMUX register with the PMUXE (even) taking the lowest significant nibble (4-bits) and the PMUXO (odd) the most significant of the 8-bit register. The use of the bitwise OR in the line:

The INTENSET (set) and its INTENCLR (clear) counterpart automatically perform a read-modify-write in hardware, making the bitwise ORing of the register in software unnecessary. A small performance enhancement when activating and deactivating interrupt enable bits.

Are these details mentioned in the datasheet? I come back to my former question - how exactly to know whether we need to perform a binary operation or not? As far as I've read the datasheet, such topics are not explicitly explained. If not in datasheet, any other relevant source where I can get a better understanding of this else every time I encounter .reg, I'll have the same question.

@MartinL
I noticed another difference. In line 107 in SAMD-Projects/app.c at master · ArmstrongSubero/SAMD-Projects · GitHub the EXT0_PIN_NUMBER
is left-shifted inside PMUX[EXT0_PIN_NUMBER << 1]
while in Enable Alternate Pins for Interrupts - #2 by MartinL, you perform a right shift on the pin as shown by
PORT->Group[PORTA].PMUX[8 >> 1].reg |= PORT_PMUX_PMUXE_A;

On testing, the LED changes color in both the cases. Please explain what exactly is happening here.

I don't think the author of the project knows what they're doing with this line of code:

PORT->Group[EXT0_PORT].PMUX[EXT0_PIN_NUMBER<<1].bit.PMUXE = MUX_PA07A_EIC_EXTINT7; // Set up IRQ-pin (PA7) to special function A (External Interrupt 7)

He actually targets the wrong PMUX register, but gets away with it since by default all the PMUX registers are set to zero (0x00), which selects the EIC anyway.

Ther reason for shifting to the right by 1, is that it's equivalent to dividing by 2. Since the port pin pairs (PA00 & PA01, PA02 & PA03, etc...) share PMUX registers there up to 32 port pins, but only 16 PMUX registers, hence the necessity to divide the port number index by 2.

Unfortunately, the SAMD21 datasheet mearly details the registers, but doesn't tell you how to program them. That only comes from looking at example code that you can find on the internet, gradually building up experience with a particular microcontroller and an understanding of how it operates.

Normally assume that registers require explicit read-modify-write operation in software, unless the registers are separated into SET and CLR. Indect register access is also rare.

Registers for the SAMD21, (on my machine at least) can be found here:

C:\Users\Computer\AppData\Local\Arduino15\packages\arduino\tools\CMSIS-Atmel\1.2.0\CMSIS\Device\ATMEL\samd21\include\component

Also, check out the "instance" and "pio" directories up one level in the "include" folder.

@MartinL , @er_name_not_found
I see.
Thanks a lot for everything.