// This is the ooPinChangeInt library for the Arduino. // See google code project for latest bugs and info http://code.google.com/p/arduino-oopinchangeint/ // We use 4-character tabstops, so IN VIM: :set ts=4 sw=4 // ...that's: ESCAPE key, colon key, then "s-e-t SPACE key t-s = 4 SPACE key s-w = 4" // //-------- define these in your sketch, if applicable ---------------------------------------------------------- //-------- This must go ahead of the #include ooPinChangeInt.h statement in your sketch ------------------------ // You can reduce the memory footprint of this handler by declaring that there will be no pin change interrupts // on any one or two of the three ports. If only a single port remains, the handler will be declared inline // reducing the size and latency of the handler. // #define NO_PORTB_PINCHANGES // to indicate that port b will not be used for pin change interrupts // #define NO_PORTC_PINCHANGES // to indicate that port c will not be used for pin change interrupts // #define NO_PORTD_PINCHANGES // to indicate that port d will not be used for pin change interrupts // You can reduce the code size by maybe 20 bytes, and you can speed up the interrupt routine // slightly by declaring that you don't care if the static variable PCintPort::pinState // is made available to your interrupt routine. // #define NO_PIN_STATE // to indicate that you don't need the pinState // // define DISABLE_PCINT_MULTI_SERVICE below to limit the handler to servicing a single interrupt per invocation. // #define DISABLE_PCINT_MULTI_SERVICE // #define GET_OOPCIVERSION // to enable the uint16_t getOOPCIintVersion () function. //-------- define the above in your sketch, if applicable ------------------------------------------------------ /* ooPinChangeInt.h ---- VERSIONS ---------------------------------------------------------------------------- Library begins with the PinChangeInt v 1.3 code. See http://code.google.com/p/arduino-pinchangeint/ Version 1.03beta Wed Nov 21 18:20:46 CST 2012 Version 1.00 Sat Dec 3 22:56:20 CST 2011 Modified to use the new() operator and symbolic links instead of creating a pre-populated array of pointers to the pins. This consumes more flash, but makes possible some additional C++ style functionality later. Version 1.01 Thu Dec 8 21:29:11 CST 2011 Modified to use a C++ callback function. The arduinoPin variable is no longer necessary, as this creates a new methodology for using the library. Version 1.02 Tue Mon Mar 5 18:37:28 CST 2012 All code moved into this .h file so as to make it possible to recognize #define's in the user's sketch. Added #ifdef LIBCALL_OOPINCHANGEINT. Programmers using this library in another library should define this macro, because this will allow you to #include it in your sketch AND #include it in the library. (As a matter of act, you must always #include this file in your sketch, even if it's only used to support another library. See the Tigger library and example, for an example.) Code uses the cbiface library, which is a much simplified and renamed version of cb.h ---- VERSIONS ---------------------------------------------------------------------------- This is the ooPinChangeInt library for the Arduino. See google code project for latest, bugs and info http://code.google.com/p/arduino-oopinchangeint/ This library provides an extension to the interrupt support for arduino by adding pin change interrupts, giving a way for users to have interrupts drive off of any pin (ATmega328-based Arduinos) and by the Port B, J, and K pins on the Arduino Mega and its ilk.. This library was originally written by Chris J. Klick, Robot builder and all around geek, who said of it, "HI, Yeah, I wrote the original PCint library. It was a bit of a hack and the new one has better features. I intended the code to be freely usable. Didn't really think about a license. Feel free to use it in your code: I hereby grant you permission." Thanks, Chris! A hack? I dare say not, if I have taken this any further it's merely by standing on the shoulders of giants. This library was the best "tutorial" I found on Arduino Pin Change Interrupts and because of that I decided to continue to maintain and (hopefully) improve it. We, the Arduino community of robot builders and geeks, owe you a great debt of gratitude for your hack- a hack in the finest sense. The library was then picked up by Lex Talionis, who created the Google Code website. We all owe a debt of thanks to Lex, too, for all his hard work! He is currently the other official maintainer of this code. Chris' original PCInt Arduino Playground example here: http://www.arduino.cc/playground/Main/PcInt Many thanks to all the contributors who have contributed bug fixes, code, and suggestions to this project: John Boiles and Baziki (who added fixes to PcInt), Maurice Beelen, nms277, Akesson Karlpetter, and Orly Andico for various fixes to this code, Rob Tillaart for some excellent code reviews and nice optimizations, Andre' Franken for a good bug report that kept me thinking, cserveny.tamas a special shout out for providing the MEGA code to PinChangeInt- Thanks! Regarding the MEGA and friends, Cserveny says: "J is mostly useless, because of the hardware UART. I was not able to get pin change notifications from the TX pin (14), so only 15 left. All other pins are not connected on the arduino boards." LICENSE This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ooPinChangeInt_h #define ooPinChangeInt_h #define OOPCIVERSION 1030 #include "stddef.h" // Thanks to Maurice Beelen, nms277, Akesson Karlpetter, and Orly Andico for these fixes. #if defined(ARDUINO) && ARDUINO >= 100 #include #include #else #include #ifndef LIBCALL_OOPINCHANGEINT #include "../cppfix/cppfix.h" #endif #include "WProgram.h" #endif #include "../cbiface/cbiface.h" #undef DEBUG /* * Theory: all IO pins on Atmega168 are covered by Pin Change Interrupts. * The PCINT corresponding to the pin must be enabled and masked, and * an ISR routine provided. Since PCINTs are per port, not per pin, the ISR * must use some logic to actually implement a per-pin interrupt service. */ /* Pin to interrupt map: * D0-D7 = PCINT 16-23 = PCIR2 = PD = PCIE2 = pcmsk2 * D8-D13 = PCINT 0-5 = PCIR0 = PB = PCIE0 = pcmsk0 * A0-A5 (D14-D19) = PCINT 8-13 = PCIR1 = PC = PCIE1 = pcmsk1 */ #undef INLINE_PCINT #define INLINE_PCINT // Thanks to cserveny...@gmail.com for MEGA support! #if defined __AVR_ATmega2560__ || defined __AVR_ATmega1280__ || defined __AVR_ATmega1281__ || defined __AVR_ATmega2561__ || defined __AVR_ATmega640__ #define __USE_PORT_JK // Mega does not have PORTC or D #define NO_PORTC_PINCHANGES #define NO_PORTD_PINCHANGES #if ((defined(NO_PORTB_PINCHANGES) && defined(NO_PORTJ_PINCHANGES)) || \ (defined(NO_PORTJ_PINCHANGES) && defined(NO_PORTK_PINCHANGES)) || \ (defined(NO_PORTK_PINCHANGES) && defined(NO_PORTB_PINCHANGES))) #define INLINE_PCINT inline #endif #else #if ((defined(NO_PORTB_PINCHANGES) && defined(NO_PORTC_PINCHANGES)) || \ (defined(NO_PORTC_PINCHANGES) && defined(NO_PORTD_PINCHANGES)) || \ (defined(NO_PORTD_PINCHANGES) && defined(NO_PORTB_PINCHANGES))) #define INLINE_PCINT inline #endif #endif class PCintPort { public: PCintPort(int index,volatile uint8_t& maskReg) : portInputReg(*portInputRegister(index + 2)), portPCMask(maskReg), firstPin(NULL), PCICRbit(1 << index), portRisingPins(0), portFallingPins(0) { } volatile uint8_t& portInputReg; // cbIface should be an object instantiated from a subclass of CallBackInterface static int8_t attachInterrupt(uint8_t pin, CallBackInterface* cbIface, int mode); static void detachInterrupt(uint8_t pin); INLINE_PCINT void PCint(); static uint8_t curr; protected: class PCintPin { public: PCintPin() : mode(0) {} CallBackInterface* pinCallBack; uint8_t mode; uint8_t mask; #ifndef NO_PIN_STATE uint8_t state; #endif PCintPin* next; }; void enable(PCintPin* pin, CallBackInterface* cbIface, uint8_t mode); int8_t addPin(uint8_t arduinoPin,CallBackInterface* cbIface, uint8_t mode); void delPin(uint8_t mask); volatile uint8_t& portPCMask; const uint8_t PCICRbit; volatile uint8_t portRisingPins; volatile uint8_t portFallingPins; volatile uint8_t lastPinView; PCintPin* firstPin; }; #endif // ********************************************************************************************************** #ifdef LIBCALL_OOPINCHANGEINT // LIBCALL_OOPINCHANGEINT ************************************************* extern PCintPort portB; extern PCintPort portC; extern PCintPort portD; #ifdef __USE_PORT_JK extern PCintPort portJ; extern PCintPort portK; #endif #else // LIBCALL_OOPINCHANGEINT uint8_t PCintPort::curr=0; #ifndef NO_PORTB_PINCHANGES PCintPort portB=PCintPort(0,PCMSK0); // port PB==2 (from Arduino.h, Arduino version 1.0) #endif #ifndef NO_PORTC_PINCHANGES PCintPort portC=PCintPort(1,PCMSK1); // port PC==3 (also in pins_arduino.c, Arduino version 022) #endif #ifndef NO_PORTD_PINCHANGES PCintPort portD=PCintPort(2,PCMSK2); // port PD==4 #endif #ifdef __USE_PORT_JK #ifndef NO_PORTJ_PINCHANGES // PCintPort portJ=PCintPort(10,1,PCMSK1); // port PJ==10 original version, error, copied from PinChangeInt 2.19 (beta) PCintPort portJ=PCintPort(10,PCMSK1); // port PJ==10 eliminated middle 'index' parameter #endif #ifndef NO_PORTK_PINCHANGES //PCintPort portK=PCintPort(11,2,PCMSK2); // port PK==11 original version, error, copied from PinChangeInt 2.19 (beta) PCintPort portK=PCintPort(11,PCMSK2); // port PK==11 eliminated middle 'index' parameter #endif #endif #endif // LIBCALL_OOPINCHANGEINT #ifndef LIBCALL_OOPINCHANGEINT static PCintPort *lookupPortNumToPort( int portNum ) { PCintPort *port = NULL; switch (portNum) { #ifndef NO_PORTB_PINCHANGES case 2: port=&portB; break; #endif #ifndef NO_PORTC_PINCHANGES case 3: port=&portC; break; #endif #ifndef NO_PORTD_PINCHANGES case 4: port=&portD; break; #endif #ifdef __USE_PORT_JK #ifndef NO_PORTJ_PINCHANGES case 10: port=&portJ; break; #endif #ifndef NO_PORTK_PINCHANGES case 11: port=&portK; break; #endif #endif } return port; } void PCintPort::enable(PCintPin* p, CallBackInterface* cbIface, uint8_t mode) { // Enable the pin for interrupts by adding to the PCMSKx register. // ...The final steps; at this point the interrupt is enabled on this pin. p->mode=mode; p->pinCallBack=cbIface; portPCMask |= p->mask; if ((p->mode == RISING) || (p->mode == CHANGE)) portRisingPins |= p->mask; if ((p->mode == FALLING) || (p->mode == CHANGE)) portFallingPins |= p->mask; PCICR |= PCICRbit; } int8_t PCintPort::addPin(uint8_t arduinoPin, CallBackInterface* cbIface, uint8_t mode) { PCintPin* tmp; uint8_t bitmask=digitalPinToBitMask(arduinoPin); // Add to linked list, starting with firstPin if (firstPin != NULL) { tmp=firstPin; do { if (tmp->mask == bitmask) { enable(tmp, cbIface, mode); return(0); } if (tmp->next == NULL) break; tmp=tmp->next; } while (true); } // Create pin p: fill in the data PCintPin* p=new PCintPin; if (p == NULL) return(-1); p->mode = mode; p->next=NULL; p->mask = bitmask; // the mask // ...Pin created if (firstPin == NULL) firstPin=p; else tmp->next=p; #ifdef DEBUG Serial.print("addPin. pin given: "); Serial.print(arduinoPin, DEC), Serial.print (" pin stored: "); int addr = (int) p; Serial.print(" instance addr: "); Serial.println(addr, HEX); #endif enable(p, cbIface, mode); return(1); } /* * attach an interrupt to a specific pin using pin change interrupts. */ int8_t PCintPort::attachInterrupt(uint8_t arduinoPin, CallBackInterface* cbIface, int mode) { PCintPort *port; uint8_t portNum = digitalPinToPort(arduinoPin); if ((portNum == NOT_A_PORT) || (cbIface == NULL)) return(-1); port=lookupPortNumToPort(portNum); // Added by GreyGnome... must set the initial value of lastPinView for it to be correct on the 1st interrupt. // ...but even then, how do you define "correct"? Ultimately, the user must specify (not provisioned for yet). port->lastPinView=port->portInputReg; #ifdef DEBUG Serial.print("attachInterrupt FUNC: "); Serial.println(arduinoPin, DEC); #endif // map pin to PCIR register return(port->addPin(arduinoPin,cbIface,mode)); } void PCintPort::detachInterrupt(uint8_t arduinoPin) { PCintPort *port; PCintPin* current; uint8_t mask; #ifdef DEBUG Serial.print("detachInterrupt: "); Serial.println(arduinoPin, DEC); #endif uint8_t portNum = digitalPinToPort(arduinoPin); if (portNum == NOT_A_PORT) return; port=lookupPortNumToPort(portNum); mask=digitalPinToBitMask(arduinoPin); current=port->firstPin; while (current) { if (current->mask == mask) { // found the target uint8_t oldSREG = SREG; cli(); // disable interrupts port->portPCMask &= ~mask; // disable the mask entry. if (port->portPCMask == 0) PCICR &= ~(port->PCICRbit); port->portRisingPins &= ~mask; port->portFallingPins &= ~mask; SREG = oldSREG; // Restore register; reenables interrupts return; } current=current->next; } } // common code for isr handler. "port" is the PCINT number. // there isn't really a good way to back-map ports and masks to pins. void PCintPort::PCint() { uint8_t thisChangedPin, changedPins; #ifndef DISABLE_PCINT_MULTI_SERVICE uint8_t pcifr; do { #endif // get the pin states for the indicated port. //uint8_t changedPins = PCintPort::curr ^ lastPinView; //lastPinView = PCintPort::curr; //changedPins &= portPCMask; // NEW changedPins=(PCintPort::curr ^ lastPinView) & ((portRisingPins & PCintPort::curr) | ( portFallingPins & ~PCintPort::curr)); lastPinView = PCintPort::curr; PCintPin* p = firstPin; while (p) { if (p->mask & changedPins) { // a changed bit // Trigger interrupt if mode is CHANGE, or if mode is RISING and // the bit is currently high, or if mode is FALLING and bit is low. #ifndef NO_PIN_STATE p->state=PCintPort::curr & p->mask ? HIGH : LOW; #endif (*(p->pinCallBack)).cbmethod(); //} } //changedPins ^= p->mask; // MIKE: Check on this optimization. //if (!changedPins) break; p=p->next; } #ifndef DISABLE_PCINT_MULTI_SERVICE pcifr = PCIFR & PCICRbit; if (pcifr == 0) break; PCIFR |= pcifr; // clear the interrupt if we will process it (no effect if bit is zero) PCintPort::curr=portInputReg; } while (true); #endif } #ifndef NO_PORTB_PINCHANGES ISR(PCINT0_vect) { PCintPort::curr = portB.portInputReg; portB.PCint(); } #endif #ifndef NO_PORTC_PINCHANGES ISR(PCINT1_vect) { PCintPort::curr = portC.portInputReg; portC.PCint(); } #endif #ifndef NO_PORTD_PINCHANGES ISR(PCINT2_vect) { PCintPort::curr = portD.portInputReg; portD.PCint(); } #endif #ifdef __USE_PORT_JK #ifndef NO_PORTJ_PINCHANGES ISR(PCINT1_vect) { #ifdef PINMODE PCintPort::s_PORT='J'; #endif PCintPort::curr = portJ.portInputReg; portJ.PCint(); } #endif #ifndef NO_PORTK_PINCHANGES ISR(PCINT2_vect){ #ifdef PINMODE PCintPort::s_PORT='K'; #endif PCintPort::curr = portK.portInputReg; portK.PCint(); } #endif #endif // __USE_PORT_JK #ifdef GET_OOPCIVERSION uint16_t getOOPCIntVersion () { return ((uint16_t) OOPCIVERSION); } #endif // GET_OOPCIVERSION #endif // LIBCALL_OOPINCHANGEINT