Académique Documents
Professionnel Documents
Culture Documents
Abstract
We test the PinChangeInt library for its speed. The basic C-styleness of the library means that in its earliest incarnations it is fastest, however, it lacks a small bit of functionality that, although it costs a couple of microseconds in time, may prove useful to the programmer: notably, later versions of the library set a public variable, which can be queried, that indicates which Arduino pin did the interrupting. Additionally, utilizing C++ techniques can give us some further benefits that may be worth the notinsignificant impact to speed. Finally, we compare the PinChangeInt speed to the speed of the External Interrupts on the ATmega328p chip. We find that the external interrupts are indeed significantly faster. However, there are only two external interrupt pins on the ATmega328p so the programmer may find the Pin Change interrupts necessary for their project. Ultimately, engineering is about tradeoffs. Speed, ease-of-use, utility: pick any two. It is up to the designer of any system to weigh the costs and benefits and decide what works best for them.
Table of Contents
Table of Contents
Abstract...................................................................................................................................................... 1 Table of Contents....................................................................................................................................... 2 Versions...................................................................................................................................................... 3 Copyright Notice........................................................................................................................................3 Introduction................................................................................................................................................ 3 Materials and Methods...............................................................................................................................4 Test Platform......................................................................................................................................... 4 PinChangeInt-1.1.............................................................................................................................. 4 PinChangeInt-1.2.............................................................................................................................. 4 PinChangeInt-1.3.............................................................................................................................. 4 ooPinChangeInt-1.0.......................................................................................................................... 5 Choosing the Version Under Test.......................................................................................................... 5 Tests.......................................................................................................................................................5 Basic PinChangeInt Tests................................................................................................................. 5 Test 1.................................................................................................................................................6 Test 2.................................................................................................................................................6 Test 3.................................................................................................................................................6 Test 4, 5, 6, 7.....................................................................................................................................6 External Interrupt Comparison Tests................................................................................................ 6 PinChangeInt Tests, Source Code.............................................................................................................. 7 External Interrupt Comparison Tests, Source Code................................................................................. 10 Results...................................................................................................................................................... 12 PinChangeInt-1.1.................................................................................................................................12 PinChangeInt-1.1, modified in sketch.................................................................................................12 PinChangeInt-1.1, modified in PinChangeIntConfig.h....................................................................... 13 PinChangeInt-1.2.................................................................................................................................13 PinChangeInt-1.3.................................................................................................................................14 ooPinChangeInt-1.00...........................................................................................................................14 ooPinChangeInt-1.01...........................................................................................................................14 External Interrupt Comparison....................................................................................................... 15 Conclusion............................................................................................................................................... 15 The Speed Champ............................................................................................................................... 16 The Champ's Got Issues...................................................................................................................... 16 C's Got Issues...................................................................................................................................... 16 C++ has issues..................................................................................................................................... 18
Versions
Date 2011-Dec-01 2011-Dec-22 2012-Jan-05 2012-Jan-05 PinChangeInt Document Changes Version Version 1.1, 1.2, 1.3 ooPCInt1.0 1.3 1.3 1 1.1 1.2 1.3 Updated Abstract, added External Interrupt tests Initial version Author Mike Schwager Mike Schwager Mike Schwager Mike Schwager
Copyright Notice
Copyright 2012 by Michael Schwager. This document, PinChangeInt Speed Tests by Michael Schwager is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
Introduction
This paper concerns the speed of the PinChangeInt software library for the Arduino platform. Over the lifetime of the library code, changes may affect its speed and we wish to track those effects here. The ATmega328p has two different kinds of interrupts: external, and pin change. There are only two external interrupt pins, INT0 and INT1, and they are mapped to Arduino pins 4 and 5. These interrupts can be set to trigger on RISING or FALLING signal edges, or on low level. On the other hand the pin change interrupts can be enabled on any or all of the Arduino's signal pins. They are triggered equally on RISING or FALLING signal edges, so it is up to the interrupt code to determine what happened (did the signal rise, or fall?) and handle it properly. Furthermore, the pin change interrupts are grouped into 3 ports on the MCU, so there are only 3 interrupt vectors (subroutines) for the entire body of 19 pins. This makes the job of resolving the action on a single interrupt even more complicated. The interrupt routine should be fast, but complication is the enemy of speed. The PinChangeInt library is designed to handle the Arduino's pin change interrupts. The Arduino's pins are shown below, in parentheses. The corresponding pin numbers on the ATmega 328p are shown alongside the diagram of the 28-pin dip package:
(D (D (D PWM+ (D (D 0) 1) 2) 3) 4) PC6 PD0 PD1 PD2 PD3 PD4 VCC GND PB6 PB7 PD5 PD6 PD7 PB0 +-\/-+ 1| |28 2| |27 3| |26 4| |25 5| |24 6| |23 7| |22 8| |21 9| |20 10| |19 11| |18 12| |17 13| |16 14| |15 PC5 (AI 5) PC4 (AI 4) PC3 (AI 3) PC2 (AI 2) PC1 (AI 1) PC0 (AI 0) GND AREF AVCC PB5 (D 13) PB4 (D 12) PB3 (D 11) PWM PB2 (D 10) PWM PB1 (D 9) PWM
PWM+ (D 5) PWM+ (D 6) (D 7) (D 8)
+----+
The ATmega 328p's pins' state can be sampled by simply reading a pin state register, which is a single 8-bit number. The ATmega 328p's pins are grouped in sets of Ports, shown as PDx, PBx, PCx above, where x represents an integer from 0 to 8. So you can see that Port B, pin 0 is pin 14 on the chip, or D8 on the Arduino board. Earlier versions of the PinChangeInt library require that the programmer have some knowledge of the implications of his/her choice of pins and pin combinations. For example, there are settings in the PinChangeInt code that allow one to disable ports entirely from use by the PinChangeInt library. This saves memory. Later versions of the PinChangeInt library endeavor to make the programmer's choice of pins less impactful.
PinChangeInt-1.1
The original version of PinChangeInt, updated to fix a bug in the detachInterrupt() routine. There is an optimization that the programmer can perform: If you know what port(s) are in use, you can disable utilization of memory for the unused ports by including compiler directives in the PinChangeIntConfig.h file, like this:
#define #define NO_PORTB_PINCHANGES NO_PORTC_PINCHANGES
You may be tempted, as I was, to put them in your sketch, ahead of the #include <PinChangeInt.h> line, and you may believe that will work. It won't. You must put those #defines in the PinChangeIntConfig.h file. I will include two rounds of tests: One in which the #defines are in the sketch, the other in which they are in the .h file. You will see that only by using them in the .h file will you save the memory space.
PinChangeInt-1.2
Fixes a bug whereby the initial value of each port was set to 0. Instead, the initial stored value of the port is taken from the state of the port at the time the interrupt was created. This initial state is updated every time a new pin interrupt is attached. This may still be buggy on a circuit where the signals are coming in at the time the interrupts are attached.
PinChangeInt-1.3
Significant internal changes: 4
Tested and modified to work with Arduino 1.0. Modified to use the new() operator and symbolic links instead of creating a pre-populated PCintPins[]. Renamed some variables to simplify or make their meaning more obvious (IMHO anyway). Modified the PCintPort::PCint() code (ie, the interrupt code) to loop over a linked-list. For those who love arrays, I have left some code in there that should work to loop over an array instead. But it is commented out in the release version. For Arduino versions prior to 1.0: The new() operator requires the cppfix.h library, which is included with this package. For Arduino 1.0 and above: new.h comes with the distribution, and that is #include'd.
ooPinChangeInt-1.0
Significant underlying changes. Uses linked lists instead of arrays of pins. Pin data storage is not allocated unless it's used, for a potential savings in RAM utilization. Also eliminates the idea of the arduinoPin variable, in favor of C++-style callback interface methodology.
The Arduino IDE will recognize the PinChangeInt library path without issue. To form the next battery of tests, simply change the symlink: Windows users would likely need to copy folders into and out of the libraries folder, as symlinks are not available on Windows.
rm PinChangeInt; ln -s PinChangeInt-1.2 PinChangeInt
The test is performed as follows: Set up two index variables, i and k. i is of type int and k is of type uint8_t. These types are used so they can be incremented quickly (vs. a 32-bit long type). Therefore incrementing as loop counters is as fast as possible. 5
Between i and k, the loop will iterate 100,000 times. The loop sends an interrupt to the pintest pin, by setting it to LOW by AND'ing the appropriate PORT register with a 0 at the proper bit. The interrupt routine is thus run, and upon return the main routine sets its value HIGH by OR'ing the appropriate PORT register with a 1 at the proper bit. This runs the interrupt routine again. And so on, to the limits of i * k. The elapsed time it takes to interrupt 100,000 times is measured using the millis() counter. During the iterations the SERIALSTUFF will be defined. The MEMTEST will be undefined. Then SERIALSTUFF and MEMTEST will both be on to measure the free memory of the code. Finally, both will be undefined so the compiled size of the code may be shown without the additional cruft. The code and free memory sizes are given for comparison purposes across versions only, as the additional code included for the tests makes an accurate number very difficult to determine.
Test 1
Arduino Pin 2 only enabled for interrupts, and pin 2 interrupted (in the code, pintest=2, pinLow=2, pinHigh=2). The code's compiled size, minus the FreeMemory code and any Serial statements, will be reported. Then the code will be recompiled with the FreeMemory routine, and its size reported prior to any interrupts.
Test 2
Arduino Pins 2 and 5 enabled for interrupts, pin 2 interrupted. The code's compiled size, minus the FreeMemory code and any Serial statements, will be reported. Then the code will be recompiled with the FreeMemory routine, and its size reported prior to any interrupts.
Test 3
Arduino Pins 2 and 5 enabled for interrupts, pin 5 interrupted. The code's compiled size, minus the FreeMemory code and any Serial statements, will be reported.. Then the code will be recompiled with the FreeMemory routine, and its size reported prior to any interrupts.
Test 4, 5, 6, 7
Arduino Pins 2-5 enabled for interrupts, pin 2, 3, 4, 5 interrupted, respectively. The code's compiled size will be reported. Then the code will be recompiled with the FreeMemory routine, and its size reported prior to any interrupts.
changed using the digitalWrite() Arduino function under version 022 of the Arduino software. In test 3, as the digitalWrite() function is thought to have been optimized under Arduino-1.0, we will test the code compiled by Arduino-1.0 as well (review of the source shows a minor change but no significant technical modifications). We will repeat these 3 tests by doing: 1) A direct port read of the pin, 2) A read of the pin using digitalRead() under Arduino-022, and 3) a read of the pin using digitalRead() and Arduino-1.0.
#elif TEST == #define PTEST #define PLOW #define PHIGH #elif TEST == #define PTEST #define PLOW #define PHIGH #endif
6 4 2 5 7 5 2 5
uint8_t qf0; void quicfunc() { qf0=TCNT0; } class speedy : public CallBackInterface { public: uint8_t id; static uint8_t var0; speedy () { id=0; }; speedy (uint8_t _i): id(_i) {}; void cbmethod() { speedy::var0=TCNT0; //Serial.print("Speedy method "); // debugging //Serial.println(id, DEC); }; }; uint8_t speedy::var0=0; volatile uint8_t *led_port; volatile uint8_t *pinT_OP; volatile uint8_t *pinT_IP; uint8_t led_mask, not_led_mask; uint8_t pinT_M, not_pinT_M; volatile uint8_t pintest, pinIntLow, pinIntHigh; uint8_t totalpins; speedy speedster[8]={speedy(0), speedy(1), speedy(2), speedy(3), speedy(4), speedy(5), speedy(6), speedy(7) }; #ifdef MEMTEST int freemem; #endif int i=0; #define PINLED 13 void setup() { #ifdef SERIALSTUFF Serial.begin(115200); Serial.println("---------------------------------------"); #endif SERIALSTUFF pinMode(PINLED, OUTPUT); digitalWrite(PINLED, LOW); // set up ports for trigger pinMode(0, OUTPUT); digitalWrite(0, HIGH); pinMode(1, OUTPUT); digitalWrite(1, HIGH); pinMode(2, OUTPUT); digitalWrite(2, HIGH); pinMode(3, OUTPUT); digitalWrite(3, HIGH); pinMode(4, OUTPUT); digitalWrite(4, HIGH); pinMode(5, OUTPUT); digitalWrite(5, HIGH);
pinMode(6, OUTPUT); digitalWrite(6, HIGH); pinMode(7, OUTPUT); digitalWrite(7, HIGH); #ifdef FLASH led_port=portOutputRegister(digitalPinToPort(PINLED)); led_mask=digitalPinToBitMask(PINLED); not_led_mask=led_mask^0xFF; #endif // ***************************************************************************** // set up ports for output ************ PIN TO TEST IS GIVEN HERE ************** // ***************************************************************************** pintest=PTEST; pinIntLow=PLOW; pinIntHigh=PHIGH; // Interrupts are attached to these pins // ***************************************************************************** // ***************************************************************************** pinT_OP=portOutputRegister(digitalPinToPort(pintest)); // output port pinT_IP=portInputRegister(digitalPinToPort(pintest)); // input port pinT_M=digitalPinToBitMask(pintest); // mask not_pinT_M=pinT_M^0xFF; // not-mask *pinT_OP|=pinT_M; for (i=pinIntLow; i <= pinIntHigh; i++) { #if VERSION >= 200 PCintPort::attachInterrupt(i, &speedster[i], CHANGE); // C++ technique; v1.3 or better #else PCintPort::attachInterrupt(i, &quicfunc, CHANGE); // C technique; v1.2 or earlier #endif } #if TEST == 2 || TEST == 3 i=5; totalpins=2; #if VERSION >= 131 PCintPort::attachInterrupt(i, &speedster[i], CHANGE); // C++ technique; v1.3 or better #else PCintPort::attachInterrupt(i, &quicfunc, CHANGE); // C technique; v1.2 or earlier #endif #else totalpins=pinIntHigh - pinIntLow + 1; #endif i=0; } // end setup() uint8_t k=0; unsigned long milliStart, milliEnd, elapsed; void loop() { k=0; *pinT_OP|=pinT_M; // pintest to 1 #ifdef SERIALSTUFF Serial.print("TEST: "); Serial.print(TEST, DEC); Serial.print(" "); #ifndef MEMTEST Serial.print("test pin mask: "); Serial.print(pinT_M, HEX); Serial.print(" interrupted pin: "); Serial.print(speedster[pintest].id, DEC); Serial.print(" of a total of "); Serial.print(totalpins, DEC); Serial.println(" pins."); #endif #ifdef MEMTEST freemem=freeMemory(); Serial.print("Free memory: "); Serial.println(freemem, DEC); #endif #endif delay(2000); Serial.println("Start..."); #ifdef FLASH *led_port|=led_mask; #endif milliStart=millis(); while (k < 10) {
i=0; while (i < 10000) { *pinT_OP&=not_pinT_M; *pinT_OP|=pinT_M; i++; } k++; } milliEnd=millis(); #ifdef FLASH *led_port&=not_led_mask; #endif elapsed=milliEnd-milliStart; #ifdef SERIALSTUFF #ifndef MEMTEST Serial.print("Elapsed: "); Serial.println(elapsed, DEC); #endif #endif delay(500); }
10
uint8_t led_mask, not_led_mask; uint8_t pinT_M, not_pinT_M; volatile uint8_t pintest, pinIntLow, pinIntHigh; uint8_t qf0; void quicfunc() { qf0=TCNT0; #ifdef COMPAREWRITES if (qf0 > 128) #ifdef DIRECTPORT *led_port|=led_mask; #else digitalWrite(PINLED, HIGH); #endif else #ifdef DIRECTPORT *led_port&=not_led_mask; #else digitalWrite(PINLED, LOW); #endif #endif } int i=0; void setup() { Serial.begin(115200); Serial.println("---------------------------------------"); pinMode(PINLED, OUTPUT); digitalWrite(PINLED, LOW); // set up ports for trigger pinMode(PTEST, OUTPUT); digitalWrite(PTEST, HIGH); led_port=portOutputRegister(digitalPinToPort(PINLED)); led_mask=digitalPinToBitMask(PINLED); not_led_mask=led_mask^0xFF; // ***************************************************************************** // set up ports for output ************ PIN TO TEST IS GIVEN HERE ************** // ***************************************************************************** pintest=PTEST; pinT_OP=portOutputRegister(digitalPinToPort(pintest)); // output port pinT_IP=portInputRegister(digitalPinToPort(pintest)); // input port pinT_M=digitalPinToBitMask(pintest); // mask not_pinT_M=pinT_M^0xFF; // not-mask *pinT_OP|=pinT_M; #ifdef EXTERNALINTERRUPT attachInterrupt(0,quicfunc,CHANGE); #else #if PCIVERSION >= 200 PCintPort::attachInterrupt(pintest, &speedster[i], CHANGE); // C++ technique; ooPinChangeInt-1.0 or better #else PCintPort::attachInterrupt(pintest, &quicfunc, CHANGE); // C technique; v1.2 or earlier #endif #endif i=0; } // end setup() uint8_t k=0; unsigned long milliStart, milliEnd, elapsed; void loop() { k=0; *pinT_OP|=pinT_M; // pintest to 1 Serial.print("test pin mask: "); Serial.print(pinT_M, HEX); Serial.print(" interrupted pin: "); Serial.println(pintest, DEC); delay(2000); Serial.println("Start..."); #ifdef FLASH *led_port|=led_mask; #endif milliStart=millis(); while (k < 10) {
11
} milliEnd=millis(); #ifdef FLASH *led_port&=not_led_mask; #endif elapsed=milliEnd-milliStart; Serial.print("Elapsed: "); Serial.println(elapsed, DEC); delay(500); }
Results
PinChangeInt-1.1
Test Pin triggered 1 2 3 4 5 6 7 2 2 5 2 3 4 5 Pins, Loop time, Average time Code Size, bytes interrupt ms. per loop (us) enabled 100,000 iterations 2 2,5 2,5 2-5 2-5 2-5 2-5 2892 3157 3157 3684 3684 3685 3685 28.92 31.57 31.57 36.84 36.84 36.84 36.84 3646 3662 3664 3648 3650 3650 3648 Free Memory, bytes
NO_PORTB_PINCHANGES NO_PORTC_PINCHANGES
Pins, Loop time, Average time Code Size, bytes interrupt ms. per loop (us) enabled 100,000 iterations 2 2,5 2,5 2-5 2-5 2-5 2-5 2892 3157 3157 3684 3684 3685 3685 28.92 31.57 31.57 36.84 36.84 36.85 36.85 3646 3662 3664 3648 3650 3650 3648 Free Memory, bytes
12
NO_PORTB_PINCHANGES NO_PORTC_PINCHANGES
Pins, Loop time, Average time Code Size, bytes interrupt ms. per loop (us) enabled 100,000 iterations 2 2,5 2,5 2-5 2-5 2-5 2-5 2490 2741 2741 3245 3245 3245 3245 24.90 27.41 27.41 32.45 32.45 32.45 32.45 3470 3486 3488 3472 3474 3474 3472 Free Memory, bytes
PinChangeInt-1.2
Fixes a bug in the initial port value (all pins were assumed to be 0). Makes available the arduinoPin variable, to tell which pin triggered the interrupt.
Test Pin triggered 1 2 3 4 5 6 7 2 2 5 2 3 4 5 Pins, Loop time, Average time Code Size, bytes interrupt ms. per loop (us) enabled 100,000 iterations 2 2,5 2,5 2-5 2-5 2-5 2-5 2943 3207 3207 3735 3735 3735 3735 29.43 32.07 32.07 37.35 37.35 37.35 37.35 3700 3716 3718 3702 3704 3704 3702 Free Memory, bytes
13
PinChangeInt-1.3
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.
Test Pin triggered 1 2 3 4 5 6 7 2 2 5 2 3 4 5 Pins, Loop time, Average time Code Size, bytes interrupt ms. per loop (us) enabled 100,000 iterations 2 2,5 2,5 2-5 2-5 2-5 2-5 2967 3207 3207 3684 3684 3685 3685 29.67 32.07 32.07 36.84 36.84 36.85 36.85 4180 4196 4198 4182 4184 4184 4182 Free Memory, bytes
ooPinChangeInt-1.00
Modified to use a C++ callback function. The arduinoPin variable is no longer necessary, as this creates a new methodology for using the library.
Test Pin triggered 1 2 3 4 5 6 7 2 2 5 2 3 4 5 Pins, Loop time, Average time Code Size, bytes interrupt ms. per loop (us) enabled 100,000 iterations 2 2,5 2,5 2-5 2-5 2-5 2-5 3584 3835 3835 4338 4338 4338 4338 35.84 38.35 38.35 43.38 43.38 43.38 43.38 4286 4302 4304 4288 4290 4290 4288 Free Memory, bytes
ooPinChangeInt-1.01
In the while loop, added some code to exit it quicker in circumstances with more enabled pins:
changedPins ^= p->mask; if (!changedPins) break;
Test Pin triggered 1 2 3 4 5 6 7 2 2 5 2 3 4 5 Pins, Loop time, Average time Code Size, bytes interrupt ms. per loop (us) enabled 100,000 iterations 2 2,5 2,5 2-5 2-5 2-5 2-5 3471 3471 3886 3471 4514 4514 4514 34.71 34.71 38.86 34.71 45.14 45.14 45.14 4286 4302 4304 4288 4290 4280 4288 Free Memory, bytes
14
1 2
Next, we turned on the LED Pin using direct port manipulation, then turned it on using digitalWrite() under Arduino 022 and Arduino 1.0. This test is enabled by #define'ing the COMPAREWRITES compiler directive in the source code. If DIRECTPORT is #define'ed the LED pin is turned on and off using direct port manipulation. If #undef'ed, digitalWrite() is used to manipulate the LED pin.
Test Pin triggered 1 2 3 2 2 2 Interrupt Type External External External Arduino version 022 022 1.0 LED pin turned on using... Direct Port Manipulation digitalWrite() digitalWrite() Loop time, ms. 100,000 iterations 1603 2232 2245 Average time per loop (us) 16.03 22.32 22.45
Now read the LED Pin using direct port manipulation, then read it using digitalWrite() under Arduino 022 and Arduino 1.0. This test is enabled by #define'ing the COMPAREREADS compiler directive in the source code. If DIRECTPORT is #define'ed the LED pin is read using direct port manipulation. If #undef'ed, digitalREAD() is used to manipulate the LED pin.
Test Pin triggered 1 2 3 2 2 2 Interrupt Type External External External Arduino version 022 022 1.0 LED pin turned on using... Direct Port Manipulation digitalRead() digitalRead() Loop time, ms. 100,000 iterations 1559 2188 2189 Average time per loop (us) 15.59 21.88 21.89
Conclusion
For sheer speed, nothing beats assembly language. But we're not doing that- the raison d'tre of the Arduino platform is ease-of-use, which means the C or C++ programming languages are used. The programmer does not have to keep track of registers and have knowledge of the tiniest details of the mcu (microcontroller unit). Indeed, the ATmega processor line was designed with a comparatively large body of registers precisely for use by the C compiler.
15
Yet when programming for interrupts the idea is to get out of the interrupt routine as quickly as possible. So we are concerned about speed. We are also concerned about memory utilization, especially RAM, as we only have 2k available.
16
In the language of the computer, we have: 1. Attached a device to a pin, which will 2. Send interrupts to that pin, and 3. Respond in some regular fashion to those interrupts. In the language of the problem domain, we: 1. Attached a switch, which when pressed will 2. Respond in some regular fashion to that event. So in the language of the computer we will need to draw associations between pin numbers and the various settings on that pin, such as the type of interrupt to be interpreted (on FALLING, RISING, or CHANGE signals), the state of the internal resistor, and the minimum delay acceptable between pin state changes (to account for switch bounce). In the language of the computer we will create a number of data structures, likely kept in proper order using arrays. This allows us to store data in the proper place- by pin number, which would be a cell in an array. Our task, then, distills down to maintaining and interpreting data in arrays that correspond to the state of the computer and switch. In the language of the problem domain we are concerned with the same issues, but we handle them differently. Our task distills down to maintaining and interpreting data from each switch. We do this by encapsulating the data in objects. Indeed I have done so, using a C++ class called Tigger which represents a pin on the Arduino (see http://code.google.com/p/tigger/). Now look at how we add a new pin. This attaches an object to pin 7, triggers on FALLING signal, turns the Arduino resistor high, and accepts a minimum 150 milliseconds between switch closures:
blueswitch=Tigger(7,FALLING,HIGH,150);
Now in our code, we can refer to the blueswitch, which perhaps by its name indicates that it turns a blue LED on and off. We are no longer concerned with keeping track of data structures in arrays. blueswitch, in its very creating, indicates The switch that operates the blue LED on my Arduino. The object-orientatedness of C++ allows you to program more firmly in the problem domain, which is that we want to use a switch to turn an LED on via the processor. We are connected to pin 7, we need to turn on a resistor, and we trigger on a FALLING signal- so it's true, we cannot escape the computer's domain entirely (the 150 ms delay being part of the switch and therefore part of the problem domain, IMHO). But the whole point of of object-orientated programming is to encapsulate and therefore insulate the developer from such concerns. Compare my code for the AdaEncoder, version 0.3, to my code for Tigger version 0.1. AdaEncoder contains the following code which shows us that my focus is the computer's domain. This is because I have one interrupt routine for the library, and I need to determine which structure to address:
static encoder* AdaEncoders[20]; // maximum of 20 possible pins on ATmega328, 0-19 encoder *tmpencoder=AdaEncoders[PCintPort::arduinoPin]; // find proper encoder
On the contrary, each Tigger pin appears to get a different interrupt routine (from the init() method):
PCintPort::attachInterrupt(_arduinoPin, this, _trigger);
(Under the covers, the same code will be used for all interrupt routines but different data structures will be associated depending on which object is attached to the interrupt. The C++ compiler handles this.) 17
...so that the interrupt routine is completely unconcerned about which pin caused the interrupt. It knows, because the routine is attached to the pin when the object is created. The attaching to the pin was shown above, i.e.:
blueswitch=Tigger(7,FALLING,HIGH,150);
...no need for the developer to worry about which pin I'm attached to, because the tigermillis, startTime, and count variables are all encapsulated in the blueswitch object. When I call the object's method, I call it like this:
blueswitch.getCount();
Which means, Get me the count for the blue switch [which happens to be attached to pin 7, btw, but I'm greatly unconcerned about it because what I want to do is turn the blue LED on and- guess what?I'm working in the problem domain]. How elegant is that? It is for this reason that the PinChangeInt library was made object-oriented and its name changed to ooPinChangeInt.
18