Go Down

Topic: PinChangeInt library- To attach interrupts to multiple Arduino (Uno/Mega) pins (Read 27877 times) previous topic - next topic

GreyGnome

After some time, I have updated the library. There is a significant fix submitted by jrhelbert which makes the two Port J pins work properly with the Mega. See http://code.google.com/p/arduino-pinchangeint/.

Downloads available at https://bintray.com/greygnome/generic/PinChangeInt/view

Bleeding edge source available at https://github.com/GreyGnome/PinChangeInt

GreyGnome

Note: I don't have a Mega, so I didn't test ports J and K. If you use it, please let me know if it works. Thanks.

Petertje

Maybe somebody can help me?
I have this code, it is used to read a rotary encoder with an Arduino Uno. I want to make this work on an Arduino Mega.


Code: [Select]
  PORTC |= _BV(PORTC0) | _BV(PORTC1) | _BV(PORTC2) | _BV(PORTC3);   // enable pullup for pins
  PCMSK1 = _BV(PCINT8) | _BV(PCINT9) | _BV(PCINT10) | _BV(PCINT11); // enable button pin change interrupt A0 A1 A2 A3
  PCICR = _BV(PCIE1);
     

I can not find any help about converting this to a working Mega sketch. Can you help me?

GreyGnome

First you have to study the Mega pinout and figure out which pins and ports are available to you. Have you done this?

It looks like you have two rotary encoders (4 pins total). Is that correct?

I tried to use this lib, but sadly doesn't work as expected, so moved to http://www.geertlangereis.nl/Electronics/Pin_Change_Interrupts/PinChange_en.html . It works fine and I got all Analog pins on my Mega 2560 working as interrupt. but there I got other problem.... I lost Serial1, Serial2, Serial3.
None other than Serial0 Serial3 works with PCINT1 to PCINT23, with the ATMEL datasheet for 2560 it has stated that Serial3 (ATMEL's RXD3/TXD3) are on  PCINT9 and PCINT10 , so there are chances that Serial3 wont work with manipulated PCICR setting. Yet we have 2 other Serial Ports (Serial1 and Serial2) which haven't specified on any PCINT pins.
Please guide me about this.
This is where I Initialise Interrupts
Code: [Select]
[code]void InitialiseInterrupt(){
 cli(); // switch interrupts off while messing with their settings  

 PCICR =0x06;          // Enable PCIE2 and PCIE1
 PCMSK1 = 0b11111111; // Enabling A0 to A7 pins on Mega 2560 (PCINT23:16)
 PCMSK2 = 0b11111111; // Enabling A8 to A15 pins on Mega 2560(PCINT15:8)
 sei(); // turn interrupts back on
}


also as per the ATMEL's pdf you dont have any PCINT on ADC0...ADC7 pins. yet those are working


the full code is
Code: [Select]
#include <TimerOne.h>
#include <Encoder.h>
#include <SPI.h>

Encoder xenc(18,19);
int iPin[] = {54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69}; //{"A0","A1","A2","A3","A4","A5","A6","A7","A8","A9","A10","A11","A12","A13","A14","A15"};
int iLast[16];
void setup()
{
 SPI.begin();
 Serial.begin(9600);
 Serial.println("Boe");
 Serial2.begin(9600);
 InitialiseIO();
 InitialiseInterrupt();
Timer1.initialize(100000);
Timer1.attachInterrupt(Timer1_tick);
}
void Timer1_tick(){
 float z = xenc.read();
digitalWrite(13,!digitalRead(13));
SPI.transfer(z);
}
void serialEvent(){
Serial2.print("A");
}
void loop() {
 /* Nothing to do: the program jumps automatically to Interrupt Service Routine "blink"
  in case of a hardware interrupt  */
}  

void InitialiseIO(){
 pinMode(13,OUTPUT);
 digitalWrite(13,LOW);
 pinMode(A0, INPUT);   // Pin A0 is input to which a switch is connected
 digitalWrite(A0, HIGH);   // Configure internal pull-up resistor
 pinMode(A1, INPUT);   // Pin A1 is input to which a switch is connected
 digitalWrite(A1, HIGH);   // Configure internal pull-up resistor
 pinMode(A2, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A2, HIGH);   // Configure internal pull-up resistor
  pinMode(A3, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A3, HIGH);   // Configure internal pull-up resistor
  pinMode(A4, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A4, HIGH);   // Configure internal pull-up resistor
  pinMode(A5, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A5, HIGH);   // Configure internal pull-up resistor
  pinMode(A6, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A6, HIGH);   // Configure internal pull-up resistor
  pinMode(A7, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A7, HIGH);   // Configure internal pull-up resistor
 pinMode(A8, INPUT);   // Pin A0 is input to which a switch is connected
 digitalWrite(A8, HIGH);   // Configure internal pull-up resistor
 pinMode(A9, INPUT);   // Pin A1 is input to which a switch is connected
 digitalWrite(A9, HIGH);   // Configure internal pull-up resistor
 pinMode(A10, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A10, HIGH);   // Configure internal pull-up resistor
  pinMode(A11, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A11, HIGH);   // Configure internal pull-up resistor
  pinMode(A12, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A12, HIGH);   // Configure internal pull-up resistor
  pinMode(A13, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A13, HIGH);   // Configure internal pull-up resistor
  pinMode(A14, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A14, HIGH);   // Configure internal pull-up resistor
  pinMode(A15, INPUT);   // Pin A2 is input to which a switch is connected
 digitalWrite(A15, HIGH);   // Configure internal pull-up resistor
 
}

void InitialiseInterrupt(){
 cli(); // switch interrupts off while messing with their settings  

 PCICR =0x06;          // Enable PCINT1 interrupt
 PCMSK1 = 0b11111111;
 PCMSK2 = 0b11111111;
 sei(); // turn interrupts back on
}
/*
ISR(PCINT1_vect) {    // Interrupt service routine. Every single PCINT8..14 (=ADC0..5) change
           // will generate an interrupt: but this will always be the same interrupt routine
 if (digitalRead(A0)==0)  Serial.println("A0");
 if (digitalRead(A1)==0)  Serial.println("A1");
 if (digitalRead(A2)==0)  Serial.println("A2");
  if (digitalRead(A3)==0)  Serial.println("A3");
 if (digitalRead(A4)==0)  Serial.println("A4");
 if (digitalRead(A5)==0)  Serial.println("A5");
  if (digitalRead(A6)==0)  Serial.println("A6");
 if (digitalRead(A7)==0)  Serial.println("A7");

}
ISR(PCINT2_vect) {    // Interrupt service routine. Every single PCINT8..14 (=ADC0..5) change
           // will generate an interrupt: but this will always be the same interrupt routine
 if (digitalRead(A8)==0)  Serial.println("A8");
 if (digitalRead(A9)==0)  Serial.println("A9");
 if (digitalRead(A10)==0)  Serial.println("A10");
  if (digitalRead(A11)==0)  Serial.println("A11");
 if (digitalRead(A12)==0)  Serial.println("A12");
 if (digitalRead(A13)==0)  Serial.println("A13");
  if (digitalRead(A14)==0)  Serial.println("A14");
 if (digitalRead(A15)==0)  Serial.println("A15");

}*/
ISR(PCINT1_vect) {
 for (int q = 0; q <= 7; q++) {
   int u = digitalRead(iPin[q]);
   String OP = "IP";

   if (iLast[q] != u) {
     OP += q;
     OP += u;
     OP += "!";
     Serial.print(OP);
   }
   iLast[q] = u;
 }

}
ISR(PCINT2_vect) {
 for (int q = 8; q <= 15; q++) {
   int u = digitalRead(iPin[q]);
   String OP = "IP";

   if (iLast[q] != u) {
     OP += q;
     OP += u;
     OP += "!";
     Serial.print(OP);
   }
   iLast[q] = u;
 }

}

[/code]

GreyGnome

Don't do a Serial.print() inside an ISR. If it does work, consider yourself lucky and stop doing it as soon as possible because things are (were?) problematic when printing from an ISR (but supposedly fixed in IDE 1.5; see https://github.com/matthijskooijman/Arduino/commit/eb86af7b67407e198bac24ecd16a8c1669e3efaa ).

Regarding the rest of your question, you stated that you tried the PinChangeInt library and it didn't work for you. This thread is about the PinChangeInt library. The code you speak of was written by someone else so I don't think this is the appropriate place for this question, but perhaps someone will lend you the time and help you out.

GreyGnome

Announcing the release of version 2.40-rc2 for the Arduino PinChangeInt library.

See http://code.google.com/p/arduino-pinchangeint/.
Downloads available at https://bintray.com/greygnome/generic/PinChangeInt/view
Bleeding edge source available at https://github.com/GreyGnome/PinChangeInt

See the RELEASE_NOTES for detailed information. In short, some of the changes in recent history include:

The library is now licensed under the Apache 2.0 License. I wanted to free the code up for any use, and Chris J. Kiick and Lex Talionis graciously agreed.

Cleaned up some code, and added the following #define's for ease-of-use:

#define        detachPinChangeInterrupt(pin)
#define        attachPinChangeInterrupt(pin,userFunc,mode)
#define        getInterruptedPin()



Cleaned up the README for better display on GitHub.

Version 2.40-rc1 Fri Nov  7 07:26:36 CST 2014
I'm going to the "-rcX" numbering format like the Linux kernel. That is, 2.40-rc1 is the first "release
candidate" on the way to a stable 2.40 release.

Beginning with this release, only Arduino >= 1.00 is supported. The older Arduinos are left to the
dustbin of history...

I have BIG news: Jan Baeyens ("jantje") has graciously DONATED an Arduino Mega ADK to the PinChangeInt project!!! Wow, thanks Jan! This makes the 2560-based Arduino Mega a first class supported platform- I will be able to test it and verify that it works. I have done so in this release.

To that end, The PinChangeIntExample has been modified to work properly with the Arduino Mega. Thanks, Jan!

Many thanks to JRHelbert for fixing the PJ0 and PJ1 interrupt PCMSK1 issue on the Arduino Mega! This was a great coup, in my humble opinion.  Mega interrupt users are indebted to you, jrhelbert.

tomega3

Great Library,
Any chance to add support in it for the Atmel 1284p micro controller?

I have a board with a 1284p (moteino mega) that I can test it with.

Thanks

GreyGnome

Great Library,
Any chance to add support in it for the Atmel 1284p micro controller?

I have a board with a 1284p (moteino mega) that I can test it with.

Thanks
It would probably be pretty easy, since the library already has Sanguino support. Could you take the following code and add/modify the 1284 to the #define and see if it works just like that?

#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__)

just change it to

#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega1284__) || (defined __AVR_ATmega1284P__)

(that final "P" may need to be a "p". Could you check both?)

Thanks.

Hello devs,

I'm having a curious interrupt-dropping problem using PinChangeInt 2.40-rc2 on Arduino 1.0.5.

I'm using it like many people to capture R/C servo pulsewidths. In a previous version of my board code which was written last year with Arduino 1.0.1 (and whichever version of PinChangeInt was back then), I was capturing 3 (some times 4) channels of servo inputs without issue.

Right now, I'm facing a weird issue where it seems to be dropping ISR functions or otherwise failing to execute them. The code and loop structure that reads the R/C servo pulsewidths is basically identical - it's mostly copy and paste from the v1 board with variable names changed.

Here's the board and schematic. The pins called AIN0:2 are the inputs, and there is nothing else connected to those pins.





Here's the code segment that I isolated into its own sketch to try and determine if other timers, ISRs, etc. were causing issues. "Combine" is a pin I'm using temporarily as an output to scope the ISR timing.

Code: [Select]

//#define NO_PORTB_PINCHANGES
//#define NO_PORTD_PINCHANGES
//#define NO_PIN_STATE      
//#define NO_PIN_NUMBER
#include <PinChangeInt.h>

#define COMBINE 5

#define RCIN0 16 //Analog pin 2 is also called Digital pin 16
#define RCIN1 17 // ...
#define RCIN2 18 // ...

#define SLOW_LOOP_PRESCALER 10000

volatile unsigned long slow_loop_prescale_counter = 0;


//RC channel collection variables
volatile unsigned long temp_ch1_pw, temp_ch2_pw, temp_ch3_pw; //Used for counting the duration of the pulse width
volatile signed int vol_ch1_pw, vol_ch2_pw, vol_ch3_pw; //Used to communicate the pulse width to the main loop
signed int ch1_pw, ch2_pw, ch3_pw; //A buffered copy of the volatile variable to be used during the loop

//RISING EDGE. Captures a system clock reading when channel goes high
void rch1() {
  temp_ch1_pw = micros();
}

//FALLING EDGE. Takes another reading when channel goes low, finds delta time
void fch1() {
  vol_ch1_pw = (signed int)(micros() - temp_ch1_pw);
}

void rch2() {
  PORTD |= (1 << 5);
  temp_ch2_pw = micros();
  PORTD &= ~(1 << 5);
}

void fch2() {
  PORTD |= (1 << 5);
  vol_ch2_pw = (signed int)(micros() - temp_ch2_pw);
  PORTD &= ~(1 << 5);
}

void rch3() {
  temp_ch3_pw = micros();
}

void fch3() {
  vol_ch3_pw = (signed int)(micros() - temp_ch3_pw);
}

//Other R/C-only functions
//boolean check_rc_pulsewidth(signed int pulsewidth) {
//  
//  return (pulsewidth > RC_ABS_MIN && pulsewidth < RC_ABS_MAX);
//}

void setup() {
  
  Serial.begin(57600);
  
  //Inputs, analog
  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  pinMode(A2, INPUT);
  pinMode(A3, INPUT);
  pinMode(A4, INPUT);
  pinMode(A5, INPUT);
  
  pinMode(COMBINE, OUTPUT);

  PCintPort::attachInterrupt(RCIN0,rch1,RISING); //CH1 (steer): rising edge
  PCintPort::attachInterrupt(RCIN0,fch1,FALLING); //CH1  falling edge
  PCintPort::attachInterrupt(RCIN1,rch2,RISING); //CH2 (go): rising edge
  PCintPort::attachInterrupt(RCIN1,fch2,FALLING); //CH2: falling edge
  PCintPort::attachInterrupt(RCIN2,rch3,RISING); //CH3 (invert): rising edge
  PCintPort::attachInterrupt(RCIN2,fch3,FALLING); //CH3: falling edge
  
}

void loop() {
  
  if(micros() - slow_loop_prescale_counter > SLOW_LOOP_PRESCALER) {
    slow_loop_prescale_counter = micros();
    
//    noInterrupts();
    ch1_pw = vol_ch1_pw;
    ch2_pw = vol_ch2_pw;
//    interrupts();
//    
    Serial.print(ch1_pw);
    Serial.print("\t");
    Serial.print(ch2_pw);
    Serial.print("\n");
  }
}


Here's the result I'm getting. It's erratic for the most part, and contains positives and negatives indicating what I think is the unsigned long intermediate variable overflowing the signed int variable repeatedly. A good signal in this case would be approximately 1000 to 2000 (microseconds).




What is curious to me was that the scope trace some times showed two pulses - one at the rising edge ISR and one at the falling edge ISR. The temporal spacing changes as I modulate the input pulse signal, which is a good sign...



However, very frequently, the second pulse is nonexistent. I'm certain it's not an artifact of the oscilloscope, because on a "real" Tek scope I have, it's clear that there are several two-pulse trains and several one-pulse erroneous ones.




For sanity and a control test, I popped the code into a fresh Arduino Nano on a breadboard, by itself, and got the exact same erratic numbers and missing interrupts.



Any insight you have would be appreciated. I'm confident it's one of two things - either I got something very simple during the porting from version 1 board to version 2 board completely wrong (the more likely scenario, as I'm not an embedded programmer by hobby or trade...) or something change between early-mid 2013 PinChangeInt & Arduino and early 2015 PinChangeInt & Arduino that is causing issues.

Hope this is enough info. Thanks!



GreyGnome

The question in my mind at this point is not about what's wrong (broken) with the library and/or code, but rather how anything like the code example you've given would ever work. This:

Code: [Select]

  PCintPort::attachInterrupt(RCIN0,rch1,RISING); //CH1 (steer): rising edge
  PCintPort::attachInterrupt(RCIN0,fch1,FALLING); //CH1  falling edge


should technically never work. There can be only 1 function and 1 mode per pin; that's how the library is designed.

The library's design follows the model set by the attachInterrupt() function, which attaches a single interrupt request routine to a pin, and that routine is triggered by a single mode- a rising, OR falling, OR change, OR low signal. You can't assign more than one function to a pin, and you can't assign different modes.

How it was able to work last year, I don't know, but it shouldn't have. The way to handle this is to create a frontend:

Code: [Select]
  PCintPort::attachInterrupt(RCIN0,frontend1,CHANGE);

Then, inside frontend1, for example:

Code: [Select]
  currentPinValue=PINC & 0x04; // PC4 == A4 on Arduino Uno
  if (currentPinValue) { // the signal rose, do something
  else { // the signal fell, do something else


Changing the library to include that kind of functionality would mean modifying the main decision-making logic and thereby slow it down for every circumstance, when I believe the majority of cases do not need to attach multiple functions to a single pin. For that reason I don't think the library should be modified to include this functionality, and the best route is to design your sketch as I've illustrated.

What you just said is pretty much what I differentially debugged to discover over the past day or two. I went over the documentation for PinChangeInt and did not see where it explicitly said only 1 interrupt function could be assigned per pin. Can you show me where in the docs that is mentioned?

I only say this because... the 2-function method was how I was shown PCI in late 2011, and somehow used it all through 2012! In fact, this very same code structure flew a 4-channel quadcopter I built in early 2012...




That's 8 interrupts being declared. This thing flew great, so it must have been working! I can only assume that some changes to the library to make it faster also made it more strict...

The way I "found" the one-interrrupt-only characteristic was making the ISRs only increment or decrement a variable. Ideally if both sides were being triggered equally, the counter will remain at 0. I found that whichever function was declared last was the only one that executed.



Then I changed the structure to roughly what you just said, using CHANGE as the type, and it started working...



So now everything works fine. Go figure.



It definitely comes as a surprise to me that PCI only permits 1 interrupt function per pin, since I never made use of the external interrupts function nor peeked under the hood of Arduino much (only on the Analog side...)

If "one function per pin only" isn't in big red letters somewhere up top in the documentation, it probably should be. I also now have to broadcast to all the people I showed PCI to in the 2-function form that it is wrong and no longer working!

GreyGnome

In your code with the rail and fail ISRs, the one that worked, I cannot for the life of me explain how any of the rising functions would ever get called. However, by performing the two PCintPort::attachInterrupt()s, one on RISING and the other on FALLING, you would have populated a RISING variable and a FALLING variable, and the fail ISR would have in reality been called on both, that is, you made it get called on CHANGE. Weird.

Your suggestion about updating the docs is fine, I have modified https://code.google.com/p/arduino-pinchangeint/wiki/Usage, put a Note at the bottom of the "Quick and Dirty method" section and also its own section https://code.google.com/p/arduino-pinchangeint/wiki/Usage#Assigning_Multiple_Functions_or_Modes_to_a_Pin .

Glad this one is cleared up, at any rate! Sorry for your trouble, I guess it's an assumption to assume that everyone would expect a single ISR and a single mode per pin. No reason why a processor couldn't be designed with multiple ISRs per pin, depending on mode though the ATmega series is not. I've read the gory details so you don't have to, and I'm trying to build a library that works in the way that you need. I appreciate the feedback.

Wow, that really is something then! I think a big red letter warning is fine, since the structure is easy to implement. It just amazes me too that somehow this worked for 2+ years, AND that I got it from someone else! If only I can remember who it was...

GreyGnome

I am currently working on a library to replace the Pin Change Int library. It will be faster, and perhaps even include support for all interrupt types on both ATmega and ARM chips. See http://forum.arduino.cc/index.php?topic=309112.msg2144737#msg2144737

Go Up
 


Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

Arduino
via Egeo 16
Torino, 10131
Italy