How to clear ISR for individual pin?

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

Prior to enabling an interrupt for a particular port pin, I would like to clear any pending interrupts for that pin (and only that particular pin). There is a register PIO_ISR (Interrupt Status Register) which indicates whether a state change has been detected since the last time this register was read. The register is automatically cleared once it is read.

 

I have verified that reading the PIO_ISR register prior to enabling the interrupt prevents the interrupt from misfiring due to a level change that occurred prior to enabling the interrupt for that pin. The problem is that this will also clear pending interrupts for ALL of the other pins on the port - which is not what I want. This could cause me to miss a level change on one of the other pins which are being used for other purposes. Imagine trying to track down that bug later on.

 

Once I read PIO_ISR, if I find that there are interrupts pending on other pins, there is no way to retrigger those interrupts. PIO_ISR is read-only and the interrupts just got cleared.

 

Just about every other register in the PIO has bit-level access. I don't understand why the Interrupt Status Register isn't the same, as it would really help here. Seems crazy. Am I missing something?

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

You don't say which SAM you are using.

I am just guessing that it is the "almost obsolete" SAM3X

 

And the datasheet seems to say that PIO_ISR is read-only.

A sensible design would give write-access.

Then you could clear individual bits by writing a 1.

 

Hey-ho.   I suppose that you will have to set a flag or something.

The SAM3X is about 12 years old now.   Someone will have shown a workaround.

 

David.

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

Hi David, 

 

I am using the SAME70. Its PIO_ISR register is also read-only.

 

A sensible design would give write-access.

Then you could clear individual bits by writing a 1.

Exactly.

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

Have you tried writing 1 to clear a bit?

Have you tried writing to PIO_IDR and PIO_IER ?

 

SAME70 is a current design.   I do not have a datasheet.    Atmel are renowned for copy-paste errors.

If I am feeling keen,  I might play with SAM4S-XPRO

 

If you don't get an answer here,  try StackExchange.

 

David.

Last Edited: Mon. Jan 14, 2019 - 02:04 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is an ARM architecture issue, not a Atmel SAM E70 specific issue.  For ARM Cortex-M7 processors, pins are grouped into ports.  On the SAM E70 there are five ports - PORTA through PORTE.  Each one of these five ports have a separate entry in the interrupt vector table.  So five possible interrupt sources.

 

Within each port there are up to 32 pins, not all of which are populated on the chip itself.  Each of the 32 pins can be masked to enable or disable interrupts on their shared port.  So PORTC.PIN17 can generate an interrupt on PORTC's interrupt vector.  This means all pins common to the port must be serviced at the same time.  Reading the corresponding port status register clears all pending interrupts for ALL pins on that port as you have discovered.

 

Why the grouping of pins into ports?  Because the alternative means 32 * 5 = 160 individual vectors, status registers, mask registers, and so on.  This quickly consumes register space and physical pins on the chip.  

 

What to do?  First enable and disable individual pin interrupts as expected - through their common port interrupt enable/disable registers (PIO_IER/PIO_IDR write only).  Next enable/disable individual port interrupts using their peripheral ID's and the corresponding peripheral interrupt enable/disable registers (i.e. NVIC_EnableIRQ(PIOA_IRQn) ).  At this point any interrupt on any enabled pin in that port will vector to that port's ISR.  The ISR then reads the PORT status register and services ALL pins with pending interrupts.

 

One workaround is to trigger the PORT interrupt before enabling that PIN sub-interrupt source.  Use the NVIC_SetPendingIRQ() function to trigger the PORT interrupt.  Then the PORT ISR reads the PORT status register, servicing all pending interrupts (or none at all if the SR shows no pins were triggered).  But honestly this workaround is not needed as there should be no pending pin interrupts until you enable the pin interrupt.

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

Thanks for the detailed reply ScottMN.

 

I get the whole shared interrupt vector thing and understand that relates to the ARM processor rather than the Atmel's implementation of the PIO peripheral. I don't have any issue with a shared interrupt vector, so long as there is a way to reliably determine which pin on the port triggered the interrupt in software. Atmels PIO peripheral does provide a mechanism for determining which pin caused the interrupt: the ISR register (I think this is what you were referring to by "port status register"?). But I feel that the way this register has been implemented (a read clears all bits) opens up the potential for problems. 

 

The work-around you suggested works in most cases, but there are cases where it could produce unexpected behavior. Here's the function for enabling an interrupt:

 

void enable_pin_interrupt(uint8_t pin_num)
{
    //TODO: configure pin & interrupt
    NVIC_SetPendingIRQ(ID_PIOA); // reset ISR register
    PIOA->IER = 1 << pin_num;
}

But what happens if you call this from within the PORT A interrupt service routine (ie. an interrupt on one PORT A pin enables an interrupt on another PORT A pin)?  NVIC_SetPendingIRQ(ID_PIOA) would not execute the ISR immediately. The ISR would execute *after* the bit is set in PIOA->IER. Then you get a misfire of the interrupt.

 

I admit that the circumstances that produce this bug are unusual (enabling an interrupt on a port pin from within an interrupt service routine). But if you were unfortunate enough to do this in a complex piece of software, you would pull your hair out trying to find the source of the bug. If PIO_ISR register had bit-wise access (like most other registers in the PIO) then there is no possibility for these kind of obscure bugs to occur in the first place.

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

I had a look at how the SAM3X handled pin-changes on an Arduino Due.

 

Any change on an "enabled pin" will fire the interrupt corresponding to that port.

You read PIO_ISR into a variable.   

Then step through all the bits in that variable.   Service the valid bits.

 

It is much the same process as you would use on an AVR.    The only difference being that you can't clear individual flag bits.

 

In practice,  your foreground code is never going to see PIO_ISR reacting to existing enabled bits.   The ISR() clears them promptly.    It also clears any non-valid bits at the same time.

If you read PIO_ISR in the foreground just before you enable a fresh bit it should clear all the non-valid bits.

 

This gives you a clean start with your freshly enabled bit.    I suppose that your foreground read might occur during the response cycles that have been triggered by a pin-change on existing pins.    Which would mean the ISR() has lost the bits to service.

 

I could create some test code to deliberately clash the ISR()

Do you have a practical application where this is critical?

 

David.

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

Thanks for looking into whats happening on the Arduino Due. The approach I ended up taking is pretty much same. It should work fine, providing one rule is followed: don't enable a pin interrupt from within an interrupt.

 

Adhering to this rule would be fine if I was developing the full system myself. However, I'm hoping to build a platform which other people will build upon and use for their own applications. I can't anticipate all the possible ways it will be used in advance.

 

For example, in low-power battery-operated systems it is common to put the microcontroller into sleep mode most of the time and only wake up to service interrupts. Peripherals and circuits are disabled when the system is idle to conserve battery. Then when the system wakes up in response to some event (eg. a button press or a accelerometer detects movement), it enables and initializes the other pins & peripherals that it needs. If the initialisations are called directly in the ISR, and it involves configuring pin interrupts, then suddenly you have a bug. The solution is pretty obvious if you know what the problem is. Rather than doing the initialisation from within the ISR, you use some type of scheduler to defer the initialisation tasks so they aren't executed directly by the ISR. But if you're not aware of the potential issues, it could be an easy trap to fall into.

 

I once spent several days trying to figure out why a bluetooth stack that I was using was hard faulting. It turned out that one of my bluetooth event handlers was making a call back to the bluetooth stack. This wasn't allowed, because the event handler was called by the bluetooth stack from within an ISR handled by the stack. Debugging faults like that is horrible.