Please note: Friday June 18 is a holiday celebrating Juneteenth, please allow extra time for your order to arrive and plan accordingly.
0

Perplexing interrupt problem
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Perplexing interrupt problem

by baxtertidwell on Fri Mar 05, 2021 5:09 pm

I've spent two days on this, and either I'm blind to something, or something just ain't right.
Doorbell. Front door switch and back door switch. Two interrupt service routines, one attached to each pin. Rudimentary debouncing in the ISR seems to work.
But for some reason, both interrupt routines are firing off when either of the switches is pressed. I've tried RISING, FALLING, LOW, CHANGE, and it all works the same. I tried INPUT_PULLUP and INPUT_PULLDOWN and changed the polarity on the switched. No change. I've tried different pins, digital, analog. Nothing seems to keep both ISRs from firing.
Here's the code and the output. I hope someone can see something I can't:
Code: Select all | TOGGLE FULL SIZE
#include <Arduino.h>

#define FRONT_DOOR_SWITCH A2
#define BACK_DOOR_SWITCH A3

volatile int frontDoorStatus = 0;
volatile int backDoorStatus = 0;

void frontDoorIsr()
{
   static unsigned long last_interrupt_time = 0;
   unsigned long interrupt_time = millis();
   // If interrupts come faster than 200ms, assume it's a bounce and ignore
   if (interrupt_time - last_interrupt_time > 200) {
      if (digitalRead(FRONT_DOOR_SWITCH) == HIGH) {
         frontDoorStatus = 1;
      }
      else if (digitalRead(BACK_DOOR_SWITCH) == LOW) {
         frontDoorStatus = 2;
      }
      else {
         frontDoorStatus = -1;
      }
   }
   last_interrupt_time = interrupt_time;
}

void backDoorIsr()
{
   static unsigned long last_interrupt_time = 0;
   unsigned long interrupt_time = millis();
   // If interrupts come faster than 200ms, assume it's a bounce and ignore
   if (interrupt_time - last_interrupt_time > 200) {
      if (digitalRead(BACK_DOOR_SWITCH) == HIGH) {
         backDoorStatus = 1;
      }
      else if (digitalRead(BACK_DOOR_SWITCH) == LOW) {
         backDoorStatus = 2;
      }
      else {
         backDoorStatus = -1;
      }
   }
   last_interrupt_time = interrupt_time;
}

void setup() {
   pinMode(FRONT_DOOR_SWITCH, INPUT_PULLDOWN);
   pinMode(BACK_DOOR_SWITCH, INPUT_PULLDOWN);
      
   attachInterrupt(digitalPinToInterrupt(FRONT_DOOR_SWITCH), frontDoorIsr, CHANGE);
   attachInterrupt(digitalPinToInterrupt(BACK_DOOR_SWITCH), backDoorIsr, CHANGE);
}

void loop()
{
   switch (frontDoorStatus) {
      case 0:
         break;
      case -1:
         Serial.println("front door WTF?");
         frontDoorStatus = 0;
         break;
      case 1:
         Serial.println("front door high");
         frontDoorStatus = 0;
         break;
      case 2:
         Serial.println("front door low");
         frontDoorStatus = 0;
         break;
   }

   switch (backDoorStatus) {
      case 0:
         break;
      case -1:
         Serial.println("back door WTF?");
         backDoorStatus = 0;
         break;
      case 1:
         Serial.println("back door high");
         backDoorStatus = 0;
         break;
      case 2:
         Serial.println("back door low");
         backDoorStatus = 0;
         break;
   }
}
Pressing front door button:
back door not high
front door high

Releasing front door button
front door low

Pressing back door button
front door WTF?
back door high

Releasing back door buttonfront door WTF?
back door low

baxtertidwell
 
Posts: 49
Joined: Mon Jan 07, 2013 6:16 pm

Re: Perplexing interrupt problem

by mckenney on Fri Mar 12, 2021 10:21 pm

Code: Select all | TOGGLE FULL SIZE
      else if (digitalRead(BACK_DOOR_SWITCH) == LOW) {
         frontDoorStatus = 2;

Typo alert: I expect you meant FRONT_DOOR_SWITCH here.

mckenney
 
Posts: 19
Joined: Sun Sep 30, 2018 11:09 pm

Re: Perplexing interrupt problem

by baxtertidwell on Sat Mar 13, 2021 12:38 pm

Thanks for pointing that out. I fixed that and it still doesn't work as I would expect it to.

I put this out on StackExchange as well, and pretty much everyone is perplexed. It should work.

baxtertidwell
 
Posts: 49
Joined: Mon Jan 07, 2013 6:16 pm

Re: Perplexing interrupt problem

by stratosfear on Sat Mar 13, 2021 10:19 pm

You didn’t specify which board or microcontroller you’re using, so the following is only a guess based on my experience with a Grand Central: The M4 has only sixteen external interrupts multiplexed across multiple pins, so even though you’re using different physical pins, they may both trigger the same interrupt.

Find the pad names/pin numbers for the A2 and A3 pins on Adafruit’s schematic. Locate those in the datasheet to find whether both pins are multiplexed to the same EIC/EXTINT[].

Jon

stratosfear
 
Posts: 12
Joined: Tue Dec 31, 2013 8:21 am

Re: Perplexing interrupt problem

by mckenney on Sun Mar 14, 2021 8:19 pm

I expected that the indicated change would at least fix the neither-high-nor-low part of your symptom.
How does it behave now?

mckenney
 
Posts: 19
Joined: Sun Sep 30, 2018 11:09 pm

Re: Perplexing interrupt problem

by baxtertidwell on Sun Mar 14, 2021 10:31 pm

Still the same, but I took stratosfear's recommendation and pored through the datasheet. Over 2,100 pages! I think I've found the section where they talk about multiplexed interrupts. Each of the pairs of pins I use is on the same serial controller.

So I changed the pins to be on different controller schemes. Unfortunately, that didn't change anything. The interrupts were still erratic, but erratic in a different way.

This current project does not really have a time-critical interrupt requirement; I just wanted to learn more about them. For now, I'll just use TimedAction, which does a pretty good job of emulating multi-threading on these little controllers.

Just to document it if anyone wants to start here and has more patience than I have right now.

Schematic of the Feather M4 Express here: https://cdn-learn.adafruit.com/assets/a ... 1531010817

SAMD51 datasheet here: https://www.mouser.com/datasheet/2/268/ ... 130176.pdf

Thanks, everyone, for your help!

Multiplexing is on page 32:

6.1 Multiplexed Signals
By default each pin is controlled by the PORT as a general purpose I/O, and alternatively it can be
assigned a different peripheral functions. To enable a peripheral function on a pin, the Peripheral
Multiplexer Enable bit in the Pin Configuration register corresponding to that pin (PINCFGn.PMUXEN, n =
0-31) in the PORT must be written to '1'. The selection of peripheral functions, A to N, is done by writing
to the Peripheral Multiplexing Odd and Even bits in the Peripheral Multiplexing register
(PMUXn.PMUXE/O) of the PORT.

baxtertidwell
 
Posts: 49
Joined: Mon Jan 07, 2013 6:16 pm

Re: Perplexing interrupt problem

by adafruit_support_mike on Wed Mar 17, 2021 12:22 am

Interrupts on an Mx microcontroller are different from the ones you'll see on 8-bit microcontrollers like the ATmega328.

Instead of being associated with specific pins, the SAMD architecture associates interrupts with peripherals. Any interrupt fired by the ADC would be interrupt 9, any interrupt fired by the external interrupt handler would be interrupt 7, and so on (those almost certainly aren't the correct numbers).

The nearest equivalent on the ATmega architecture would be pin-change interrupts: any pin that changes fires the same interrupt, so the first thing you have to do is check the pins and see which one changed.

I notice you mentioned putting debunce code in the ISRs.. that can cause problems. In general, ISRs should be as short and fast as possible. Instead of doing the whole debounce business in the ISR, just set a flag for the correct pin and let your main code to the debouncing. The debounce period begins when the main code sees the flag set TRUE, then it can poll the pin at intervals or check back at a later time based on the debounce strategy you use.

adafruit_support_mike
 
Posts: 62799
Joined: Thu Feb 11, 2010 2:51 pm

Re: Perplexing interrupt problem

by baxtertidwell on Wed Mar 17, 2021 11:14 am

Thanks, Mike. Great answer.

You say
any pin that changes fires the same interrupt,
To clarify, there's only one interrupt, and therefore you need only one ISR, correct? That would make sense looking at what I'm seeing.

To confirm, then, just a single ISR, and in that routine I check which pin has changed and set a global volatile that I can check in the loop (and debounce if necessary). Is that what you're saying?

baxtertidwell
 
Posts: 49
Joined: Mon Jan 07, 2013 6:16 pm

Re: Perplexing interrupt problem

by adafruit_support_mike on Wed Mar 17, 2021 9:00 pm

baxtertidwell wrote:To clarify, there's only one interrupt, and therefore you need only one ISR, correct?

Generally speaking, yes.

The actual plumbing is a bit more complicated because of the relationship between the CPU and the peripherals. According to the SAMD51 datasheet, there are 138 interrupt lines, each of which connects the Nested Vector Interrupt Controller (NVIC) to an instance of some peripheral. There are six SERCOM instances for instance, and each one has its own interrupt line.

The interrupts can be mapped to a list of priority levels, with higher-priority interrupts being able to suspend lower-priority ones. That mapping is done by peripheral category, so all SERCOM interrupts have the same priority. The Cortex Microcontroller Software Interface Standard (CMSIS) defines function hooks a compiler can use to interact with the CPU, and it has functions that let you set the peripheral associated with each priority level.

With all that as context, and focusing on a single peripheral instance and its interrupt line to the CPU, the instance uses that same line every time it wants the CPU to handle an interrupt.

Unlike 8-bit microcontrollers, 32-bit microcontrollers have memory all over the place. The details aren't documented, but it would make sense for each peripheral to have its own chunk of memory to handle its internal state. The main Flash array is a peripheral all its own. The CPU and all the peripherals are connected through a set of high-speed data buses, and all the chunks of memory are mapped to a single contiguous address space. SERCOM 0's memory starts at address 0x40003000, for instance. SERCOM 1's memory is next, starting at address 0x40003400.

Within its memory, each peripheral has a set of interrupt flags that it uses to identify the condition it wants the CPU to handle.

To make things even more fun, each peripheral instance has its own clock, and can run at a different speed from the CPU.

When a peripheral instance wants to fire an interrupt, it sets the appropriate flag bits in its own memory, then toggles its interrupt line to the NVIC. That tells the CPU it needs to suspend the code it's running at the moment, sync its timing with the instance's clock, and read the instance's flag bytes to figure out what's happening.

So from the CPU's point of view, yes: all it sees is its interrupt line, and it executes the same ISR every time that line fires. From there, it's the ISR's job to read the peripheral instance's flags, figure out what happened, and take the appropriate action.

There's only one last complication: some peripherals have conditions that require higher priority handling than others, so the thing about 'one line per peripheral instance' was a lie: some peripheral instances have more than one line to the NVIC, and those lines fire different ISRs. The 'one ISR per interrupt line' idea still holds though.

baxtertidwell wrote:To confirm, then, just a single ISR, and in that routine I check which pin has changed and set a global volatile that I can check in the loop (and debounce if necessary). Is that what you're saying?

Yes. Use the ISR to catch the fact that something happened, but keep it as lightweight as possible. Let the main code do any work that doesn't have to be handled within a few clock cycles of the interrupt firing.

Not only does that approach make your ISRs easier to write, it also helps you manage flow control better. Your main code is better at deciding how to handle an interrupt condition in context of everything it's doing at the moment.

adafruit_support_mike
 
Posts: 62799
Joined: Thu Feb 11, 2010 2:51 pm

Re: Perplexing interrupt problem

by baxtertidwell on Thu Mar 18, 2021 2:29 pm

Thanks, Mike. That worked. Here's my demonstration code just in case anyone else has the same problem:

Code: Select all | TOGGLE FULL SIZE
#include <Arduino.h>

#define FRONT_DOOR_SWITCH A2
#define BACK_DOOR_SWITCH A3

volatile bool frontDoor = LOW;
volatile bool backDoor = LOW;

void isr()
{
   static unsigned long last_interrupt_time = 0;
   unsigned long interrupt_time = millis();
   // If interrupts come faster than 200ms, assume it's a bounce and ignore
   if (interrupt_time - last_interrupt_time > 50)
   {
      int fds = digitalRead(FRONT_DOOR_SWITCH);
      int bds = digitalRead(BACK_DOOR_SWITCH);
      if (fds == HIGH) frontDoor = HIGH;
      if (fds == LOW) frontDoor = LOW;
      if (bds == HIGH) backDoor = HIGH;
      if (bds == LOW) backDoor = LOW;
      last_interrupt_time = interrupt_time;
   }
}

void setup()
{
   Serial.begin(115200);
   while (!Serial);
   Serial.println("in setup...");
   pinMode(FRONT_DOOR_SWITCH, INPUT_PULLDOWN);
   pinMode(BACK_DOOR_SWITCH, INPUT_PULLDOWN);
      
   attachInterrupt(digitalPinToInterrupt(FRONT_DOOR_SWITCH), isr, CHANGE);
   attachInterrupt(digitalPinToInterrupt(BACK_DOOR_SWITCH), isr, CHANGE);
}

bool lastFrontDoor = LOW;
bool lastBackDoor = LOW;

void loop()
{
   if ((frontDoor == HIGH) && (lastFrontDoor == LOW))
   {
      Serial.print("fd+");
      lastFrontDoor = HIGH;
   }
   if ((frontDoor == LOW) && (lastFrontDoor == HIGH))
   {
      Serial.println("fd-");
      lastFrontDoor = LOW;
   }
   if ((backDoor == HIGH) && (lastBackDoor == LOW))
   {
      Serial.print("bd+");
      lastBackDoor = HIGH;
   }
   if ((backDoor == LOW) && (lastBackDoor == HIGH))
   {
      Serial.println("bd-");
      lastBackDoor = LOW;
   }
}

baxtertidwell
 
Posts: 49
Joined: Mon Jan 07, 2013 6:16 pm

Re: Perplexing interrupt problem

by adafruit_support_mike on Thu Mar 18, 2021 4:02 pm

Looks good. Thanks for posting the follow-up!

adafruit_support_mike
 
Posts: 62799
Joined: Thu Feb 11, 2010 2:51 pm

Re: Perplexing interrupt problem

by baxtertidwell on Thu Mar 18, 2021 8:29 pm

Now I think I know why CircuitPython doesn't deal with pin interrupts. Old-school interrupts were pretty easy to understand. But then, of course, everything gets more complex.

It would be nice if we could create an abstraction layer that would provide old-school one-pin-one-isr interrupt handling for the newer classes of processors. It seems like MicroPython is heading in that direction, where CircuitPython errs on the side of simplicity and ease of learning. Both are good, but I'm glad we still have the relatively high power of C++ for the tricky bits.

Maybe that's something I'll work on when I retire.

baxtertidwell
 
Posts: 49
Joined: Mon Jan 07, 2013 6:16 pm

Re: Perplexing interrupt problem

by adafruit_support_mike on Sat Mar 20, 2021 8:13 pm

That's the beginning of OS development: creating a hardware abstraction layer to make an interface that works the same way across multiple platforms.

adafruit_support_mike
 
Posts: 62799
Joined: Thu Feb 11, 2010 2:51 pm

Please be positive and constructive with your questions and comments.