Trying to get external interrupts on a SAMD51 to work

Go To Last Post
12 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I pieced together the following amalgamation of snippets of code from forum posts to try and make a falling edge interrupt work on my Adafruit Metro m4 (SAMD51J19A). I basically want to have a falling edge interrupt on pin PA21 cause a short pulse on pin PA20. It doesn't work.

 

Please excuse if my code doesn't make any sense at parts, I have little to no clue what I'm doing so I'd appreciate corrections as well as explanations of how this stuff works on a basic level.

 

#include "sam.h"

int main(void)
{	
	PORT->Group[0].DIRSET.reg = PORT_PA20; //set pin 9 as output pa20=9
	PORT->Group[0].PINCFG[21].bit.INEN = 1;  // Enable PA21 (pin 8) input with a pull-up resistor
	PORT->Group[0].PINCFG[21].bit.PULLEN = 1;
	PORT->Group[0].DIRCLR.reg = PORT_PA21;
	PORT->Group[0].OUTSET.reg = PORT_PA21; //set the pullup resistor
	
	MCLK->APBAMASK.reg |= MCLK_APBAMASK_EIC; //copied from a forum post, initialize the clocks
	GCLK->PCHCTRL[EIC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK2 | GCLK_PCHCTRL_CHEN;
	while (0 == (GCLK->PCHCTRL[EIC_GCLK_ID].reg & GCLK_PCHCTRL_CHEN)) {}
		
	PORT->Group[0].PINCFG[21].bit.PMUXEN = 1; //Enable the multiplexer (i think)
	PORT->Group[0].PMUX[10].bit.PMUXE = PORT_PMUX_PMUXE(0); //No clue how this works or if this line is needed
	EIC->CONFIG[0].bit.SENSE0 = EIC_CONFIG_SENSE0_FALL; //Not sure what sense to use. Sense0? Sense1? Who knows
	EIC->INTENSET.bit.EXTINT = EIC_INTENSET_EXTINT(21); //I'm pretty sure I'm not supposed to put in 21 but idk what to put
	EIC->INTFLAG.reg = EIC_INTFLAG_EXTINT(1<<21); //Is this anywhere close to right?
	NVIC_EnableIRQ(EIC_5_IRQn); //Pretty sure I got this line right
	EIC->CTRLA.bit.ENABLE=1; //This one is maybe right too?

    while (1) 
    {
	}
}

void EIC_5_Handler(void)
{
	PORT->Group[0].OUTSET.reg = PORT_PA20; //turn on pin 9
	PORT->Group[0].OUTCLR.reg = PORT_PA20; //turn oFF pin 9	
	EIC->INTFLAG.reg = EIC_INTFLAG_EXTINT(1<<21); //Pretty sure this line is wrong
}

 

This topic has a solution.
Last Edited: Sat. Oct 31, 2020 - 05:18 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Having just started a new project using the SAMD51 my first suggestion would be to have you try out MPLAB and the HARMONY V3 configurator.  It is actually a nice tool for helping you answer some of your questions (almost like an interactive data sheet).  The best part is the code that is created is fairly easy to follow.  You could then take that code and use it to figure out what values you want for your code.  It may take a little extra time to setup but I do think it is really useful for helping to get a better understanding of what is going on.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

	PORT->Group[0].PMUX[10].bit.PMUXE = PORT_PMUX_PMUXE(0); //No clue how this works or if this line is needed

 

Try the following instead:

PORT->Group[0].PMUX[10].bit.PMUXO = PORT_PMUX_PMUXO(0);

Since PA21 is odd, you need PMUXO rather than PMUXE.

 

Quote:

EIC->CONFIG[0].bit.SENSE0 = EIC_CONFIG_SENSE0_FALL; //Not sure what sense to use. Sense0? Sense1? Who knows

 

You need to consult the pin multiplexing section of the data sheet. From there, you will find PA21 corresponds to EIC/EXTINT[5]. So you need to deal with SENSE5 in EIC->CONFIG[0].

 

Quote:

EIC->INTENSET.bit.EXTINT = EIC_INTENSET_EXTINT(21); //I'm pretty sure I'm not supposed to put in 21 but idk what to put
EIC->INTFLAG.reg = EIC_INTFLAG_EXTINT(1<<21); //Is this anywhere close to right?

 

Same here. You're dealing with EXTINT5, not bit 21. So substitute '21' with '5'.

 

Hope this helps.

 

Steve

Maverick Embedded Technologies Ltd. Home of Maven and wAVR.

Maven: WiFi ARM Cortex-M Debugger/Programmer

wAVR: WiFi AVR ISP/PDI/uPDI Programmer

https://www.maverick-embedded.co...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
#include "sam.h"

int main(void)
{
    PORT->Group[0].DIRSET.reg = PORT_PA20; //set pin 9 as output pa20=9
    PORT->Group[0].PINCFG[21].bit.INEN = 1;  // Enable PA21 (pin 8) input with a pull-up resistor
    PORT->Group[0].PINCFG[21].bit.PULLEN = 1;
    PORT->Group[0].DIRCLR.reg = PORT_PA21;
    PORT->Group[0].OUTSET.reg = PORT_PA21; //set the pullup resistor
    
    MCLK->APBAMASK.reg |= MCLK_APBAMASK_EIC; //copied from a forum post, initialize the clocks
    GCLK->PCHCTRL[EIC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK2 | GCLK_PCHCTRL_CHEN;
    while (0 == (GCLK->PCHCTRL[EIC_GCLK_ID].reg & GCLK_PCHCTRL_CHEN)) {}
    
    PORT->Group[0].PINCFG[21].bit.PMUXEN = 1;
    PORT->Group[0].PMUX[10].bit.PMUXO = PORT_PMUX_PMUXO(0); //I can't put anything other than 0 in PORT_PMUX_PMUXO() otherwise I get the warning "large integer implicitly truncated to unsigned type"
    EIC->CONFIG[0].bit.SENSE5 = EIC_CONFIG_SENSE5_FALL; //I get a warning "large integer implicitly truncated to unsigned type" on this line also when I put in SENSE5 instead of SENSE0
    EIC->INTENSET.bit.EXTINT = EIC_INTENSET_EXTINT(5);
    EIC->INTFLAG.reg = EIC_INTFLAG_EXTINT(1<<5); //Still not sure if I'm doing the bitshifting correctly on this line
    NVIC_EnableIRQ(EIC_5_IRQn); 
    EIC->CTRLA.bit.ENABLE=1;

    while (1)
    {
    }
}

void EIC_5_Handler(void)
{
    PORT->Group[0].OUTSET.reg = PORT_PA20; //turn on pin 9
    PORT->Group[0].OUTCLR.reg = PORT_PA20; //turn oFF pin 9
    EIC->INTFLAG.reg = EIC_INTFLAG_EXTINT(1<<5); //Is this how I clear a bit?
}

This is my updated code after I changed to the suggestions posted above. Still doesn't work.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, the problem is you're mixing bitfield structure members with non-bitfield constants. Personally, I detest the use of bitfields to describe hardware registers, but horses for courses...

 

The above is related to your comments regarding "large integer implicitly truncated to unsigned type". These are because you're trying to assign a value which requires more bits than available in the bitfield.

 

Rather than EIC_CONFIG_SENSE5_FALL., you should be using EIC_CONFIG_SENSE5_FALL_Val. The former is for non-bitfield use only.

 

Likewise, and I should've spotted this earlier, you should simply assign '0' to PORT->Group[0].PMUX[10].bit.PMUX0.

 

No guarantees this will fix things, but you're heading in the right direction now.

 

Steve

 

Edit: For clarification, my comment regarding my dislike of register bitfields was not directed at you! I blame the CMSIS specifications.

Maverick Embedded Technologies Ltd. Home of Maven and wAVR.

Maven: WiFi ARM Cortex-M Debugger/Programmer

wAVR: WiFi AVR ISP/PDI/uPDI Programmer

https://www.maverick-embedded.co...

Last Edited: Tue. Oct 27, 2020 - 02:06 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

scdoubleu wrote:
Personally, I detest the use of bitfields to describe hardware registers

While others are ranting at those who don't use bit fields: https://www.avrfreaks.net/commen...

 

cheeky laugh wink

 

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Let's just say I've had some bad experiences with bitfields when porting device drivers between compilers / CPU architectures / endianness. wink

 

Steve

 

Maverick Embedded Technologies Ltd. Home of Maven and wAVR.

Maven: WiFi ARM Cortex-M Debugger/Programmer

wAVR: WiFi AVR ISP/PDI/uPDI Programmer

https://www.maverick-embedded.co...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here is en example of external interrupts using the E54 explained board.  The external interrupt is connected to the user button (PB31) on the Xplained board and flashes the LED twice when the button is pressed

 

#include "sam.h"

#define LED_PORT         2
#define LED_PIN          18         /* PC18 */
#define LED_BITMASK		(1 << LED_PIN)

#define BUTTON_PORT      1
#define BUTTON_PIN       31         /* PB31 */
#define BUTTON_BITMASK   (1 << BUTTON_PIN)
#define BUTTON_EIC_MASK  (1 << 15)

#define TICKS_PER_MS    (800 * 8)

/* millisecond delay */
void DelayMs(int n)
{
    int i;
    for (i = 0; i < (TICKS_PER_MS * n); i++)
    {
        __asm("nop");
    }
}

int main (void)
{
    SystemInit();                                                       /* Initialize the SAM system */
    __disable_irq();

    GCLK->PCHCTRL[4].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(0);     /* Connect GenClk0 to IEC */
    while ((GCLK->PCHCTRL[4].reg & GCLK_PCHCTRL_CHEN) == 0);            /* Wait for Sync */

    EIC->CTRLA.reg = EIC_CTRLA_SWRST;                                   /* reset the External Interrupt Controller */
    while(EIC->SYNCBUSY.reg & EIC_SYNCBUSY_SWRST);                      /* wait for reset to complete */
    EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE7_FALL;                       /* trigger on falling edge (8 + 7 = IEC15) */ 
    EIC->INTENSET.reg = BUTTON_EIC_MASK;
    EIC->INTFLAG.reg  = BUTTON_EIC_MASK;
    EIC->CTRLA.reg = EIC_CTRLA_ENABLE;                                  /* enable EIC */ 
    while(EIC->SYNCBUSY.reg & EIC_SYNCBUSY_ENABLE);                     /* wait for enable to complete */

	PORT->Group[BUTTON_PORT].PINCFG[BUTTON_PIN].reg = PORT_PINCFG_PMUXEN | PORT_PINCFG_PULLEN | PORT_PINCFG_INEN;  /* alternate function for PB31 */
    PORT->Group[BUTTON_PORT].PMUX[BUTTON_PIN/2].reg |= PORT_PMUX_PMUXO(0);                                         /* PB31 = EIC/EXTINT[15] */
    PORT->Group[BUTTON_PORT].OUTSET.reg = BUTTON_BITMASK;                                                           /* Make user button a pull-up */

    PORT->Group[LED_PORT].DIRSET.reg = LED_BITMASK;                            /* make Port C pin 27 output */
    PORT->Group[LED_PORT].OUTSET.reg = LED_BITMASK;                            /* turn off LED */ 
    
    NVIC_EnableIRQ(EIC_15_IRQn);
    __enable_irq();

    while(1)
    {
    } 
} 

/* in the interrupt handler, the yellow LED0 blinks twice */
void EIC_15_Handler( void)
{ 
    if (EIC->INTFLAG.reg & BUTTON_EIC_MASK)
    { 
        PORT->Group[2].OUTCLR.reg = LED_BITMASK;    /* turn on LED */
        DelayMs(250); 
        PORT->Group[2].OUTSET.reg = LED_BITMASK;    /* turn off LED */
        DelayMs(250); 
        PORT->Group[2].OUTCLR.reg = LED_BITMASK;    /* turn on LED */
        DelayMs(250); 
        PORT->Group[2].OUTSET.reg = LED_BITMASK;    /* turn off LED */
        DelayMs(250); 
        EIC->INTFLAG.reg = BUTTON_EIC_MASK;         /* clear interrupt flag */
    }
    else
    {                                               /* spurious interrupt?, clear all interrupt flags */ 
        EIC->INTFLAG.reg = EIC->INTFLAG.reg;        /* clear all EIC interrupt flags */
    }
} 

 

John Malaugh

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0


After combining some of the suggestions in this thread I've gotten closer. I've been looking at the bits in the debugger and I'm able to get to the point where the INTFLAG bit gets set when a falling edge happens but it doesn't seem to enter the interrupt vector.

 

#include "sam.h"

#define LED_PORT         0
#define LED_PIN          20         /* PA20 */
#define LED_BITMASK		(1 << LED_PIN)

#define BUTTON_PORT      0
#define BUTTON_PIN       21         /* PA21 */
#define BUTTON_BITMASK   (1 << BUTTON_PIN)
#define BUTTON_EIC_MASK  (1 << 5)



int main (void)
{
    SystemInit();                                                       /* Initialize the SAM system */
    __disable_irq();

    GCLK->PCHCTRL[4].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(0);     /* Connect GenClk0 to IEC */
    while ((GCLK->PCHCTRL[4].reg & GCLK_PCHCTRL_CHEN) == 0);            /* Wait for Sync */

    EIC->CTRLA.reg = EIC_CTRLA_SWRST;                                   /* reset the External Interrupt Controller */
    while(EIC->SYNCBUSY.reg & EIC_SYNCBUSY_SWRST);                      /* wait for reset to complete */
    EIC->CONFIG[0].bit.SENSE5 = EIC_CONFIG_SENSE5_FALL_Val;                       /* trigger on falling edge (8 + 7 = IEC15) */ 
    EIC->INTENSET.bit.EXTINT = EIC_INTENSET_EXTINT(5);
	EIC->INTFLAG.reg  = BUTTON_EIC_MASK;
	
    EIC->CTRLA.reg = EIC_CTRLA_ENABLE;                                  /* enable EIC */ 
    while(EIC->SYNCBUSY.reg & EIC_SYNCBUSY_ENABLE);                     /* wait for enable to complete */

	PORT->Group[BUTTON_PORT].PINCFG[BUTTON_PIN].reg = PORT_PINCFG_PMUXEN | PORT_PINCFG_PULLEN | PORT_PINCFG_INEN;  /* alternate function for PB31 */
    PORT->Group[BUTTON_PORT].PMUX[BUTTON_PIN/2].reg |= PORT_PMUX_PMUXO(0);                                         /* PB31 = EIC/EXTINT[15] */
    PORT->Group[BUTTON_PORT].OUTSET.reg = BUTTON_BITMASK;                                                           /* Make user button a pull-up */

    PORT->Group[LED_PORT].DIRSET.reg = LED_BITMASK;                            /* make Port C pin 27 output */
    PORT->Group[LED_PORT].OUTSET.reg = LED_BITMASK;                            /* turn off LED */ 
    
    NVIC_EnableIRQ(EIC_15_IRQn);
    __enable_irq();

    while(1)
    {
    } 
} 

void EIC_5_Handler( void)
{ 
    PORT->Group[0].OUTCLR.reg = LED_BITMASK;    /* turn on LED */
    PORT->Group[0].OUTSET.reg = LED_BITMASK;    /* turn off LED */
} 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The comments in that code sure are confusing but this also can't be right:

    NVIC_EnableIRQ(EIC_15_IRQn);

/Lars

 

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I agree with above poster.  Once you find that PA21 alternate function maps to IEC5, then all IEC setup must reference IEC5 rather than IEC15 as in my example.

 

So IEC_MASK, which was (1 << 15) in my example need to be changed to (1 << 5).  Since IEC5 is in the lower half the line EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE7_FALL; changes to EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE5_FALL; and lastly (which I believe is the reason your code does not work) is NVIC_EnableIRQ(EIC_15_IRQn) changes to NVIC_EnableIRQ(EIC_5_IRQn).

 

Like one of the other posters, I also do not use (and do not recommend) bitmaps in I/O mapping, its too easy to run into issues due to the read-modify-write operation on these types of register.

 

Also, your interrupt handler turns on the LED then immediately turns it off, so the LED will only illuminate for microseconds.

 

Her is my code modified for PA21/IEC5

 

#include "sam.h"

#define LED_PORT         0
#define LED_PIN          20         /* PA20 */
#define LED_BITMASK	(1 << LED_PIN)

#define BUTTON_PORT      0
#define BUTTON_PIN       21         /* PA21 */
#define BUTTON_BITMASK   (1 << BUTTON_PIN)
#define BUTTON_EIC_MASK  (1 << 5)

#define TICKS_PER_MS    (800 * 8)

/* millisecond delay */
void DelayMs(int n)
{
    int i;
    for (i = 0; i < (TICKS_PER_MS * n); i++)
    {
        __asm("nop");
    }
}

int main (void)
{
    SystemInit();                                                       /* Initialize the SAM system */
    __disable_irq();

    GCLK->PCHCTRL[4].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(0);     /* Connect GenClk0 to IEC */
    while ((GCLK->PCHCTRL[4].reg & GCLK_PCHCTRL_CHEN) == 0);            /* Wait for Sync */

    EIC->CTRLA.reg = EIC_CTRLA_SWRST;                                   /* reset the External Interrupt Controller */
    while(EIC->SYNCBUSY.reg & EIC_SYNCBUSY_SWRST);                      /* wait for reset to complete */
    EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE5_FALL;                       /* trigger on falling edge of IEC5 */ 
    EIC->INTENSET.reg = BUTTON_EIC_MASK;
    EIC->INTFLAG.reg  = BUTTON_EIC_MASK;
    EIC->CTRLA.reg = EIC_CTRLA_ENABLE;                                  /* enable EIC */ 
    while(EIC->SYNCBUSY.reg & EIC_SYNCBUSY_ENABLE);                     /* wait for enable to complete */

    PORT->Group[BUTTON_PORT].PINCFG[BUTTON_PIN].reg = PORT_PINCFG_PMUXEN | PORT_PINCFG_PULLEN | PORT_PINCFG_INEN;  /* alternate function for PA21 */
    PORT->Group[BUTTON_PORT].PMUX[BUTTON_PIN/2].reg |= PORT_PMUX_PMUXO(0);                                         /* PA21 = EIC/EXTINT[5] */
    PORT->Group[BUTTON_PORT].OUTSET.reg = BUTTON_BITMASK;                                                          /* Make user button a pull-up */

    PORT->Group[LED_PORT].DIRSET.reg = LED_BITMASK;                      /* make LED pin output */
    PORT->Group[LED_PORT].OUTSET.reg = LED_BITMASK;                      /* turn off LED */ 
    
    NVIC_EnableIRQ(EIC_5_IRQn);
    __enable_irq();

    while(1)
    {
    } 
} 

/* interrupt handler, yellow LED blinks twice */
void EIC_5_Handler( void)
{ 
    if (EIC->INTFLAG.reg & BUTTON_EIC_MASK)
    { 
        PORT->Group[LED_PORT].OUTCLR.reg = LED_BITMASK;    /* turn on LED */
        DelayMs(250); 
        PORT->Group[LED_PORT].OUTSET.reg = LED_BITMASK;    /* turn off LED */
        DelayMs(250); 
        PORT->Group[LED_PORT].OUTCLR.reg = LED_BITMASK;    /* turn on LED */
        DelayMs(250); 
        PORT->Group[LED_PORT].OUTSET.reg = LED_BITMASK;    /* turn off LED */
        DelayMs(250); 
        EIC->INTFLAG.reg = BUTTON_EIC_MASK;                /* clear interrupt flag */
    }
    else
    {                                                      /* spurious interrupt?, clear all interrupt flags */ 
        EIC->INTFLAG.reg = EIC->INTFLAG.reg;               /* clear all EIC interrupt flags */
    }
} 

 

 

John Malaugh

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

THANK YOU! I finally got it working with the above code