Programming Interrupts

adwsystems:
How do you enable multiple pins of the same PCMSKx mask?

Just "OR" it with more bits. PCMSK0 |= 0x30; // enable PCINT4 and PCINT5

How do you disable one pin in the PCMSKx mask after you have enabled it (without losing all the others)?

use "AND".

PCMSK0 &= ~0x20; // Disable PCINT5

How do you tell which pin triggered the interrupt? (a big blank left out of all examples I have found)

I haven't yet needed to do this, but I have wondered the same thing.

I suspect that you just have to keep a copy of the state of the port (or at least of all the relevant bits) and compare the previous value (captured during the previous ISR call) to the present one. I'd make capturing the port contents the first thing done in the ISR.

XOR (^) is the best way to tell what bits have changed.

stuart0:
I haven't yet needed to do this, but I have wondered the same thing.

I suspect that you just have to keep a copy of the state of the port (or at least of all the relevant bits) and compare the previous value (captured during the previous ISR call) to the present one. I'd make capturing the port contents the first thing done in the ISR.

Uh oh. I have a generic all input debounce routine that includes the previous pin state (high/low) and performs a digitalRead. So I have been doing a read of each involved pin in the PCMSK mask for the port/interrupt and comparing to the previous state. You make me wonder if (a) I'm doing it the right way/best way and (b) if there is another way (capture port contents).

Can you explain further on what you mean by

stuart0:
I'd make capturing the port contents the first thing done in the ISR.

?

adwsystems:
Can you explain further on what you mean by "I'd make capturing the port contents the first thing done in the ISR."

Read the present state of the port into a byte variable. For example:

byte portbCurrentState = PINB;
byte bitsChanged = portbCurrentState ^ portbPreviousState;
portbPreviousState = portbCurrentState;    // a static or global var

Robin2:
If the inputs are the result of a human pushing buttons you can be quite certain that will be so slow that there is no need for interrupts.

That is why I have asked you (this is the third time) to tell us what the project is.

...R

I have been posting on board like this for better than 15 years. I've learned to phrase my questions to focus on the issue at hand and to avoid tangents such as this.

The project is a 6 way event timer/counter. It will count and time the number of events detected by the logic sensor between when someone presses the RUN button and when they press the STOP button. Hence the 6 pairs of inputs, RUN/STOP and the sensor.

Not even that much information is needed in order to provide links and information on how to program the pin change interrupts in the Arduino IDE. I have the logic and the hardware set, it is a matter of compiling all the necessary information to program it. Now you have a project with a title and purpose, what information can you provide on how to program the pin change interrupts?

stuart0:
Read the present state of the port into a byte variable. For example:

byte portbCurrentState = PORTB;

I had not seen that before. Do you think that is faster than the digitalRead?

I'll have to rewrite my debounce routine. :smiley:

Dumb question time...which port is which? OK I found it. What happened to PORTA?

adwsystems:
I had not seen that before. Do you think that is faster than the digitalRead?

I'll have to rewrite my debounce routine. :smiley:

Yes I think so. Definitely for reading an entire port at once. Like a lot of this stuff there are more high level methods like digitalRead, but C also lets you get right down to the hardware level as well. After all, these are 8 bit registers in the MCU I/O space, they just happen to also have bit wise addressing as an added bonus.

What happened to PORTA?

I've been wondering exactly the same thing since the first time I saw this MCU. :grin:

stuart0:
Read the present state of the port into a byte variable. For example:

byte portbCurrentState = PORTB;

byte bitsChanged = portbCurrentState ^ portbPreviousState;
portbPreviousState = portbCurrentState;    // a static or global var

I'm going to have to meet in the middle. I'll save the port status but I need to evaluate each bit one at a time (don't need to save the previous port state).

This is what I have but it isn't working.

  portstatus  = PORTB;

  lcd.setCursor(0, 1);            // move to the begining of the second line
  lcd.print(portstatus);


  tpin_number = pin_number; // pin number 0-13
  if (tpin_number >= 8)
   { tpin_number -= 8; }

  pin_status = bitRead(port_status, tpin_number);

//rest is same as before so I know it works

portstatus always outputs 56 (b111000) to the LCD, even if b4/Pin11 is low (verify with DVM).

What did I miss?

I missed reading a port in PINB not PORTB.

adwsystems:
I missed reading a port in PINB not PORTB.

Ah yes sorry that was a mistake, I typed that a bit too quickly. Of course it should have been PINB not PORTB.

PORTB is the I/O address for output to port B, and PINB is the I/O address for input from the same port B. They do have some other functions (like toggle and internal state readback) but mostly it's PORT for out and PIN for in - hey it almost rhymes.

Hi,
I do not know if what you want to do is to when if one of the six inputs change you will read it and save the time. I think you said that you have about 6 inputs to check.If the above it is true I have the following suggestion. What you need to do it is "anded" all the inputs and connect it to an interrupt pin. Any changing in the inputs will trigger the interrupt. Write an interrupt routine that read all the inputs and find which trigger the interrupt and save the time for that input. In the interrupt routine after you save the time you can active the interrupt for the next trigger. In the above explained you will need only one interrupt for the six inputs.

So... We've already established that "External Interrupts" as supported by the Arduino "attachInterrupt()" function only supports two pins, and that they're different than "pin change interrupts", which work on all (or nearly all) of the pins, but are NOT supported by an official Arduino library, and have rather less functionality than "external interrupts." You'll have to either use some 3rd-party library (which tend to be less often used, less often discussed, and less well documented), or write your own code in essentially "bare avr-gcc."

Try Googling: "pin change interrupt site:avrfreaks.net" - there are pretty frequent questions on the topic.

I even found the 0x40 is a constant called PCINT6

Well, NO! PCINT6 is "6"; a bit number; It's usually used in the form (1<<PCINT6), which is 0x40...

Frankly, the Atmel datasheet sucks. Here's a brief overview that might help...

  • Pin change interrupts give you one vectored interrupt per 8bit IOport.
  • The ATmega328 has three ports: PORTB, PORTC, and PORTD. Most of the ports are not actually attached to 8 pins, because there aren't enough pins on the package to allow for that. So you can use up to three "pin change interrupt" ISRs.
  • Rather than coming up with sensible names for these interrupts (like, say, "PCINTB"), Atmel named them PCINT0 (portB), PCINT1(portc), and PCINT2(portD). These happen to be the same names as the bit numbers for bit 0,1,2 of the PCMSK0 (isn't that great!) The actual ISR names are PCINT0_vect, etc. There is an enable bit for each port worth of PC interrupts in the PCICR AVR register.
  • In addition to per-port ISRs, you can enable/disable WHETHER a change on a particular bit of a port will cause an interrupt by setting bits in the Pin Change Mask Registers (PCMSKn) (N is 0,1,2 again, and not B,C,D. At least it's consistent.) (there are three of them, one for each port.) You get the same interrupt vector for every bit in a particular port, but you don't have to get interrupts at all for bits you don't care about.
  • Atmel likes to give names to every bit in a register, even if the bit doesn't really have any distinguishing identity. So PORTB0 == PORTD0 == DDB0 == 0 == PCINT0. The Pin change Mask Register (PCMSKn) bits, in keeping with the policy of having bad names, are NOT called something like "PCINTB0", but instead are called PCINT0..7 (PortB), PCINT8..14 (PortC), and PCINT16..23 (PortD). Frankly, I think code would be clearer if you used the PORTB0/etc names in the PCMSK
  • You get a Pin Change interrupt for ANY enabled bit in that port, but there is no hardware to tell you exactly which bit actually changed, or in which direction.) You'll have to keep track of "old state", read the new state, and figure it out yourself. (unless you only enable one bit per port.)
  • Arduino DOES provide macros for translating Arduino pin numbers to the various PCMSK variables. "digitalPinToPCICRbit(pin)" gives you the bit in PCICR, "digitalPinToPCMSK" gives you the specific mask register, "digitalPinToPCMSKbit" gives you the bit within the register. No one seems to have noticed that digitalPinToPCMSKbit() really ought to be the same as digitalPinToBitMask(), and they're implemented differently. Which makes me nervous. Given the need to keep shadow values to detect which pin changed, it's not clear to me that dealing with Arduino pin numbers instead of "bare ports" is a good idea.

I think that's about it. To use a pin change interrupt on PIN, do the following:

  • Figure out the port and bit number.
  • Enable Pin Change Interrupts for the port in PCICR by setting the correct (per-port) bit.
  • Set the appropriate bit in the correct PCMSKn register.
  • In the ISR, determine whether the pin you are interested in has changed state by comparing current pin values with saved pin values.

It seems to me that this could all be wrapped up in a library that provides about the same functionality as attachInterrupt(), even including the "direction" capabilities (but probably not the "level" function...)

adwsystems:
Not even that much information is needed in order to provide links and information on how to program the pin change interrupts in the Arduino IDE.

Of course not. But how many times did we have to ask questions before we nailed down the fact that it was pinChange interrupts that you were interested in.

Which makes me wonder what other relevant information is being withheld. Anyway as you seem to know it all I will happily leave you to it.

...R

Robin2:
Of course not. But how many times did we have to ask questions before we nailed down the fact that it was pinChange interrupts that you were interested in.

Which makes me wonder what other relevant information is being withheld. Anyway as you seem to know it all I will happily leave you to it.

...R

I guess that is a two way street. What information do you think is relevant to provide a good comprehensive source on programming the pin change interrupts for the Arduino/Arduino IDE? It only took the original post to ask about pin change interrupts. Though there were "two" pin change interrupts (INT0/1 and the ports) it was quickly vetted.

westfw:
Well, NO! PCINT6 is "6"; a bit number; It's usually used in the form (1<<PCINT6), which is 0x40...

Yep my bad. I missed the bit instruction in the line PCMSK0 |= bit (PCINT3); Yet another way to slice the interrupt bread.

I'm here because I got totally confused about those names.... like was questioning why "PCINT0" was seen all over the place in unrelated things - like bit names in a register, and an interrupt request name. Thanks to westfw for that golden write-up of post #30. I reckon every single tutorial for pin change interrupt should include westfw's post, which is #30 of this thread. And have it at the BEGINNING of every tutorial. Also, the people at ATMEL shouldn't ever pull this sort of two-timing naming stunt ever again.

I'll upload my working code that I modified from existing online code..... that works on the UNO .... detects pin changes on pins (labelled on UNO board) 8, 9 and 10; and pins A0, A1 and A2.

Monitor the activity in Serial Monitor at 115200 bit per second.

Just put a jumper wire into a GND socket of the UNO, then touch the other end of the ground wire to any of these pins labelled on the UNO board..... 8, 9, 10, A0, A1, and A2.

Thanks westfw.

EDIT: code below updated to avoid race condition (thanks to important advice from forum member "Coding Badly"). Avoid reading PINB and PINC registers more than one time within a interrupt service routine, since the register values can change at any time. So, immediately read the register (eg. PINB) at the beginning of the service routine, and don't read it again within that routine.

#include <avr/io.h>
#include <stdint.h>            // has to be added to use uint8_t

#include <avr/interrupt.h>    // Needed to use interrupts    


volatile uint8_t portbhistory = 0xFF;     // default is high because the pull-up
volatile uint8_t portchistory = 0xFF;     // default is high because the pull-up

void setup()
{
    DDRB &= ~((1 << DDB0) | (1 << DDB1) | (1 << DDB2)); // Clear the PB0, PB1, PB2 pin // Clearing (setting to zero) these pins is the code for setting the pins to be INPUT pins.
    // PB0,PB1,PB2 (PCINT0, PCINT1, PCINT2 pin) are now inputs

    PORTB |= ((1 << PORTB0) | (1 << PORTB1) | (1 << PORTB2)); // turn On the Pull-up  //This is an unintuitive operation, which is a method for ACTIVATING internal PULLUP mode for particular UNO pins associated with 'PORTB'. In this case, PB0, PB1 and PB2 are pins 8, 9 and 10 on the UNO.
    // PB0, PB1 and PB2 are now inputs with pull-up enabled //NOTE: this pullup configuration method only works when a pin has been configured as an INPUT pin (and configuration of portB pins is done with data direction register DDRB)
    
    PCICR |= (1 << PCIE0);     // set PCIE0 to enable PCMSK0 scan  //When the bit called PCIE0 in the pin change interrupt register (PCICR) is "set" (to 1), it enables monitoring of the PCMSK0 register bits (that include pins PCINT0, PCINT1 and PCINT2 - which are pins 8, 9 and 10 on the UNO).
    
    //PCMSK0 |= (1 << PCINT0);   // set PCINT0 to trigger an interrupt on state change 
    //PCMSK0 |= (1 << PCINT1);   // set PCINT1 to trigger an interrupt on state change 
    //PCMSK0 |= (1 << PCINT2);   // set PCINT2 to trigger an interrupt on state change 

    PCMSK0 |= (1 << PCINT0) | (1 << PCINT1) | (1 << PCINT2);    //alternative way of setting these bits all at once.


    DDRC &= ~((1 << DDC0) | (1 << DDC1) | (1 << DDC2)); // Clear the PC0, PC1, PC2 pin // Clearing (setting to zero) these pins is the code for setting the pins to be INPUT pins.
    // PC0,PC1,PC2 (PCINT8, PCINT9, PCINT10 pin) are now inputs

    PORTC |= ((1 << PORTC0) | (1 << PORTC1) | (1 << PORTC2)); // turn On the Pull-up  //This is an unintuitive operation, which is a method for ACTIVATING internal PULLUP mode for particular UNO pins associated with 'PORTC'. In this case, PC0, PC1 and PC2 are pins A0, A1 and A2 on the UNO (which are unlabeled digital pins 14, 15, 16).
    // PC0, PC1 and PC2 are now inputs with pull-up enabled
    
    PCICR |= (1 << PCIE1);     // set PCIE1 to enable PCMSK1 scan  //When the bit called PCIE1 in the pin change interrupt register (PCICR) is "set" (to 1), it enables monitoring of the PCMSK1 register bits (that include pins PCINT8, PCINT9 and PCINT10 - which are pins A0, A1 and A2 on the UNO - ie. unlabeled digital pins 14, 15, 16).
    
    //PCMSK1 |= (1 << PCINT8);   // set PCINT8 to trigger an interrupt on state change 
    //PCMSK1 |= (1 << PCINT9);   // set PCINT9 to trigger an interrupt on state change 
    //PCMSK1 |= (1 << PCINT10);   // set PCINT10 to trigger an interrupt on state change 

    PCMSK1 |= (1 << PCINT8) | (1 << PCINT9) | (1 << PCINT10);    //alternative way of setting these bits all at once.

    


    //This example handles a subset of pins associate with PORTB and PORTC
    //If pins associated with other ports are needed, such as PORTD, then would need to focus on DDRD, PORTD, and to set (to 1) the 'bit' called PCIE2 in the PCICR register, which would enable monitoring of the PCMSK2 register bits (that include pins PCINT16, PCINT17, PCINT18 etc - which are pins 0, 1, 2 on the UNO. 
  Serial.begin(115200);
  Serial.println("READY");
}

void loop()
{


    while(1)
    {
        /*main program loop here */
    }
}



ISR (PCINT0_vect)  //PCINT0_vect is the interrupt service routine NAME (ISR name) for PORTB. Unintuitive, but that's the way it is. Also, the name PCINT0 is confusing, because this name is used not only as an interrupt request name, but also happens to be used as a bit name. Totally unrelated things. 
{
    uint8_t changedbits;
    uint8_t PINB_store;

    PINB_store = PINB; //temporary storage of PINB register value to avoid RACE condition. This means - PINB register values can change at any time. So we simply read PINB once only in this service routine, and store it.
    changedbits = PINB_store ^ portbhistory; //NOTE: PINB is a register that contains the values of the digital bits of PORTB pins - only relevant when PORTB pins are INPUT pins.
    portbhistory = PINB_store;

    
    if(changedbits & (1 << PINB0))
    {
        /* PCINT0 changed */
        Serial.println("  PINB0"); //Pin 8 on UNO
    }
    
    if(changedbits & (1 << PINB1))
    {
        /* PCINT1 changed */
        Serial.println("  PINB1"); //Pin 9 on UNO
    }

    if(changedbits & (1 << PINB2))
    {
        /* PCINT2 changed */
        Serial.println("  PINB2"); //Pin 10 on UNO
    }
}


ISR (PCINT1_vect)
{
    uint8_t changedbits;
    uint8_t PINC_store;

    PINC_store = PINC;
    changedbits = PINC_store ^ portchistory;
    portchistory = PINC_store;


    if(changedbits & (1 << PINC0))
    {
        /* PCINT8 changed */
        Serial.println("  PINC0"); //Pin A0 on UNO
    }
    
    if(changedbits & (1 << PINC1))
    {
        /* PCINT9 changed */
        Serial.println("  PINC1"); //Pin A1 on UNO
    }

    if(changedbits & (1 << PINC2))
    {
        /* PCINT10 changed */
        Serial.println("  PINC2"); //Pin A2 on UNO
    }
}
    changedbits = PINB ^ portbhistory; //NOTE: PINB is a register that contains the values of the digital bits of PORTB pins - only relevant when PORTB pins are INPUT pins.
    portbhistory = PINB;

Race condition. The other handlers have the same bug.

Thanks CB. Does this mean that --- once the interrupt occurs, there's no guarantee that the particular pin(s) we're monitoring will be at exactly the same state as at the time when the pin-change interrupt was triggered?

Eg.... an interrupt occurs due to one of the various pins triggering it. Then....by the time we read the values of PINB, at least one of the pin values may have changed (due to the finite amount of time it takes to execute the reading instruction) ..... which spoils the party.

Does this also mean that we shouldn't use group pin-change interrupts, and just stick to dedicated 1 pin hardware interrupt?

Southpark:
once the interrupt occurs, there's no guarantee that the particular pin(s) we're monitoring will be at exactly the same state as at the time when the pin-change interrupt was triggered?

While that may be true we actually don't care. (Mostly because we cannot do anything about it. And, if our code is fast enough and the interrupt rate is slow enough that particular race condition will never cause a problem.)

The race condition occurs because you read the PIN register more than once. The value could change between readings. In the best case, a pin will get tagged as changing twice when it actually only changed once. In the worst case, a pin change will get missed. You need to read PIN exactly once, preferably towards the top of the ISR, save the value in a local variable, then use the saved value throughout the rest of the ISR. (Which is why I quoted the two PIN reads. :wink: )

Thanks very much CB! I will remember what you taught, and also pass it on. Greatly appreciated.

I'll go back into the code to fix it up. And then update the code in the thread. Thanks again!

And then update the code in the thread.

Please post it in a new message rather than updating a previous post