Vous êtes sur la page 1sur 18

Schwager Technologies

Arduino Lab Report


Test Number: 1
Document Version: 1.3

PinChangeInt Speed Tests


...monitoring the PinChangeInt Library's response across revisions

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.

Materials and Methods


Test Platform
Arduino Uno with ATmega328p running at 16 MHz MacBook Pro with Arduino 0022 IDE PinChangeInt libraries. The following are descriptions of the different versions:

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.

Choosing the Version Under Test


In all cases, the correct library is chosen by putting all libraries under the {ARDUINOHOME}/libraries directory on a MacBook Pro (Linux will also work). The libraries' directories are named as shown in the headings: PinChangeInt-1.1, PinChangeInt-1.2, etc. The Arduino IDE will give warnings and fail to recognize these directories. This is fine, as a symlink is created from PinChangeInt to the version under test; e.g:
ln -s PinChangeInt-1.1 PinChangeInt

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

Tests Basic PinChangeInt Tests


For each test, a set of pin(s) will be enabled for pin change interrupts. The pins will be configured as outputs, so as to enable software interrupts. The interrupted pin will then be selected. It is set to HIGH to start with. Pins are configured in the following code section:
pintest=4; pinIntLow=2; pinIntHigh=5; // pin to trigger interrupt. pins 0 and 1 are used // by Serial, so steer clear of them! // Interrupts are attached to these pins

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.

External Interrupt Comparison Tests


Another round of tests will be conducted, with pin 2 (aka External Interrupt Pin 0) configured first as an External Interrupt pin. Then it will be configured as a Pin Change Interrupt pin, using version 1.3 of the PinChangeInt library. Relative speeds of the two techniques can be compared. The function called by the interrupt will simply set a variable, as in the previous tests. Finally, a round of tests will be conducted whereby the task of the interrupt function will be to set a variable, as before, but also to turn on and off the LED on pin 13, and to read the value of pin13. In the first test the pin will be changed using direct port manipulation. In the second test the pin will be 6

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.

PinChangeInt Tests, Source Code


The test code is shown here, and downloadable at http://code.google.com/p/arduinopinchangeint/downloads/detail?name=PinChangeIntSpeedTest.pde. This code can utilize a number of different versions of the library, as described above in Materials and Methods.
//#define NO_PORTB_PINCHANGES // IS USELESS HERE! Modify it in PinChangeIntConfig.h //#define NO_PORTC_PINCHANGES // IS USELESS HERE! Modify it in PinChangeIntConfig.h #define VERSION 110 // 110 if using PinChangeInt-1.1, 120 for version 1.2, 200 for ooPinChangeIntversion 1.00, 201 for ooPinChangeInt version 2.01, etc. #define SERIALSTUFF // undef to take out all serial statements. Default: #define for measuring time. #undef MEMTEST // undef to take out memory tests. Default: #undef for measuring time. //----------------------#if VERSION >= 200 #include <ooPinChangeInt.h> #else #include <PinChangeInt.h> #endif #include <cb.h> #undef FLASH // to flash LED on pin 13 during test #ifdef MEMTEST #include <MemoryFree.h> #endif #define TEST 5 #if TEST == #define PTEST #define PLOW #define PHIGH #elif TEST == #define PTEST #define PLOW #define PHIGH #elif TEST == #define PTEST #define PLOW #define PHIGH #elif TEST == #define PTEST #define PLOW #define PHIGH #elif TEST == #define PTEST #define PLOW #define PHIGH 1 2 2 2 2 2 2 2 3 5 2 2 4 2 2 5 5 3 2 5 // pin to trigger interrupt. pins 0 and 1 are used // by Serial, so steer clear of them! // Interrupts are attached to these pins // see the #if TEST == 2 || TEST == 3 code, below // need to attachInterrupt to 5 in the code // see the #if TEST == 2 || TEST == 3 code, below // need to attachInterrupt to 5 in the code

#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); }

// pintest to 0 ****************************** 16.8 us // pintest to 1 ****************************** ...to get here

External Interrupt Comparison Tests, Source Code


#define PCIVERSION 130 // 110 if using PinChangeInt-1.1, 120 for version 1.2 //----------------------// NOTE: BECAUSE OF COLLISIONS in these libraries, you CANNOT have both libraries: PinChangeInt // and ooPinChangeInt in the libraries directory at the same time. That said, under UNIX-y operating // systems, it's easy to move the library directory to a name such as "PinChangeInt-1.3", which the // Arduino will not recognize, and then create a symbolic link when you want to use a library. Such as: // cd ~/Documents/Arduino/libaries // mv PinChangeInt PinChangeInt-1.30 // mv ooPinChangeInt ooPinChangeInt-1.00 // ln -s PinChangeInt-1.30 PinChangeInt #if defined(PCIVERSION) && PCIVERSION >= 200 #include <ooPinChangeInt.h> #else #include <PinChangeInt.h> #endif #include <cb.h> // TEST TYPES #define EXTERNALINTERRUPT #undef COMPAREWRITES #undef DIRECTPORT #define FLASH // to flash LED on pin 13 during test #define PTEST 2 // pin to trigger interrupt. 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; speedy speedster[8]={speedy(0), speedy(1), speedy(2), speedy(3), speedy(4), speedy(5), speedy(6), speedy(7) }; #define PINLED 13 volatile uint8_t *led_port; volatile uint8_t *pinT_OP; volatile uint8_t *pinT_IP;

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); }

i=0; while (i < 10000) { *pinT_OP&=not_pinT_M; *pinT_OP|=pinT_M; i++; } k++;

// pintest to 0 ****************************** 16.8 us // pintest to 1 ****************************** ...to get here

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

1612 1621 1621 1621 1621 1621 1621

PinChangeInt-1.1, modified in sketch


...Notice that it makes absolutely no difference if you put these lines in your sketch. That is not the place for them.
#define #define
Test Pin triggered 1 2 3 4 5 6 7 2 2 5 2 3 4 5

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

1621 1621 1621 1621 1621 1621 1621

12

PinChangeInt-1.1, modified in PinChangeIntConfig.h


Notice that adding these lines to the .h file reduces code size but does not modify RAM utilization at all.
#define #define
Test Pin triggered 1 2 3 4 5 6 7 2 2 5 2 3 4 5

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

1621 1621 1621 1621 1621 1621 1621

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

1612 1612 1612 1612 1612 1612 1612

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

1685 1676 1676 1658 1658 1658 1658

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

1680 1664 1664 1632 1632 1632 1632

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

1680 1664 1664 1632 1632 1632 1632

14

External Interrupt Comparison


Tested External Interrupts against Pin Change Interrupt, version 1.3:
Test Pin triggered 2 2 Interrupt Type Loop time, ms. 100,000 iterations 1420 2967 Average time per loop (us) 14.20 29.67

1 2

External Pin Change

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.

The Speed Champ


The winner in speed is to use the External Interrupts, which we could have guessed. Here we have shown what the speed difference is: the fastest Pin Change interrupt is 150% slower than an external interrupt. However, there are only 2 external interrupt pins. For the remaining pins, we must look at Pin Change interrupts. For those, the winner in speed is PinChangeInt-1.1, optimized by shutting out ports we are not concerned with. With a single port enabled, the interrupt took on average 24.90 microseconds to service.

The Champ's Got Issues


However, if multiple pins are in use, the champ's utility is limited. There are no provisions in the 1.1 code so that the end programmer's code can tell which pin was triggered- if the programmer wants to use a common function to work with the interrupt. For example, in the author's AdaEncoder library (which uses this library), there is a single interrupt routine. Each encoder has a C-language structure associated with it that tracks its state. The interrupt routine must determine which pin was triggered, and then call up the proper encoder structure. The routine takes the same actions no matter how many encoders have been attached to the Arduino: Each encoder may be in a different state at any given time, but they all act the same. Therefore, finding the interrupting pin is required. So PinChangeInt-1.1 may not be adequate for the programmer. Version 1.2 adds some additional functionality at little cost- about 5 microseconds per interrupt. Whether this is acceptable or not only the programmer can decide.

C's Got Issues


During the design of the AdaEncoder library (a library to handle incremental rotary encoders), it became clear to the author that the C-ness of the library made it impossible to take advantage of some of the key benefits of the C++ language: Data encapsulation, object reuse, blah de blah... all the good object-oriented benefits. In the words of Sum Taru (http://EzineArticles.com/1055579), This higher level of abstraction [provided by C++] allows programmers to develop software in the language of the problem domain rather than in the language of the computer. What does that mean? ...the language of the problem domain... Imagine that you are creating a program to respond to one or more pushbutton switches, using interrupts. What is necessary are methods to: 1. Determine when a switch has been pressed (or released, as desired), 2. Determine which switch has been pressed, 3. Handle bounce, 4. React accordingly.

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);

The complete interrupt routine (sans comments) is shown here:


/* * Interrupt has been triggered. */ void Tigger::cbmethod() { uint8_t oldSREG = SREG; cli(); tigermillis = timer0_millis; SREG = oldSREG; if (tigermillis-startTime <= _delay) return; startTime=tigermillis; count++; }

...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.

C++ has issues


The downside of all that object-oriented elegance is greater complexity under the hood, which translates to slower interrupt speed. It is up to the programmer to determine if the speed is adequate for their needs, or not. This is the purpose of this document, to show what the impact truly is in real numbers.

18

Vous aimerez peut-être aussi