New Pin Change Interrupt Library - Anyone interested ?

yesterday complety refactored/rewrote the PinChangeInt library. There is still a compatibilty layer so there shouldnt be any need to change existing code. The new lib is more object oriented, gives you the all the needed information in the callbacks (without the need to query static variables in the lib) and allows you have several different listeners/callbacks for/on the same pin for different states

void onPinChangePortB(uint8_t pin, uint8_t state) {
    printEventInfo("PortB", pin, state);
}

struct MyPinChangeListener : public PinChangeInterrupt::Listener {
    virtual void pinStateChanged(uint8_t pin, uint8_t state) {
        printEventInfo("PinChangeListener", pin, state);
    }
};

void testGlobalUsage() {
    using namespace PinChangeInterrupt;
    //global usage....all calls are equivalent
    attach(onPinChangePortB, PIN, CHANGE);
    attachInterrupt(PIN, onPinChangePortB, CHANGE);
    registerListener(new MyPinChangeListener(), PIN, CHANGE);
}
void testStaticUsage() {
    using namespace PinChangeInterrupt;
    //port static....all calls are equivalent
    Port::registerListener(new MyPinChangeListener(), PIN, CHANGE);
    Port::registerListener(onPinChangePortB, PIN, CHANGE);
}
void testInstanceUsage() {
    using namespace PinChangeInterrupt;
    //port instance....all calls are equivalent
    Port::instanceByPin(PIN)->registerListener(new MyPinChangeListener(), PIN, RISING);
    Port::instanceByID(PORTB)->registerListener(new MyPinChangeListener(), PIN, FALLING);

i'm not realy satisfied with the testInstanceUsage yet (providing two times the PIN is somethat error prone and annoying).

let me know, if you are interested and if i should post the code...

and if i should post the code

Yes, you should.

Do you have any performance figures?
How many micros it take to react to an interrupt and call the right routine?

There was a long discussion 2 yrs ago - http://forum.arduino.cc/index.php?topic=87195.0 -
maybe you can use same performance measurements.

thx for the info...will read the thread and see, if i can measure the same way...

don't know how much overhead is added to it... i tryed to keep it to a minimum..but there is always some. At the moment it depens on how many pins are observed on a port and how many observers are registered on a single pin. On an interrupt, i'm iterating over 1st) the pin list until the pin is found 2nd) the listeners for that pin

1) could be minimized with a hashmap or an array...but thats adds some memory 2) not optimaziale

I'm not sure whats more important...ram usage or runtime overhead. I currently decided to prefere to memory usage over the performance.

I'm not sure whats more important...ram usage or runtime overhead.

Performance! If you weren't after performance, why bother with an interrupt? Could just poll the pin in question.

CrossRoads:

I'm not sure whats more important...ram usage or runtime overhead.

Performance! If you weren't after performance, why bother with an interrupt? Could just poll the pin in question.

Can't disagree with that Although interrupts are also a way to get a bit more event drivenness into the Arduino

CrossRoads:

I'm not sure whats more important...ram usage or runtime overhead.

Performance! If you weren't after performance, why bother with an interrupt? Could just poll the pin in question.

guess that depends on the situation... if you just don't want to miss a pin change (i.e. measuring some RPMs or button presses) you are more concerned about ram usage then squezing out the last clock cycle. On the other hand, i built an DRO for my lathe and mill with 3 quadrature encoders... where i REALY wanted every single clock cycle...

[quote author=robtillaart Although interrupts are also a way to get a bit more event drivenness into the Arduino[/quote] thats actualy the main reason for me

but i got the point...i'll optimize some performance stuff...

quick question... Can i asume that just a single pin has changed on PinChangeInterrupt or is it possible, that several pins may have changed, until interrupt is delivered ? What happens if i.e. the ISR needs 2ms. During this time-slot, another pin raises from low to high for a micro second and goes back again to low (befor the isr has finished handling the last pin change) - will this second pin-change be lost ?

uhmm..hope you understand what i mean...

If your isr takes 2mS you're doing something wrong. Otherwise yes, 2 pcints can happen together.

CrossRoads:
If your isr takes 2mS you’re doing something wrong.

that was just for the example :wink:

CrossRoads:
Otherwise yes, 2 pcints can happen together.

thx…

refactoring completed… need to test now - you are happily welcome to help testing :wink:

here we go… an first beta release.

See the example/usage how to use it…
There are 2 PinManagementStrategies implemented yet…a LinkedPin and a LazyArrayPin.
The LazyArrayPin calculates the index of the interrupt pin rather then looping trough all pins to notify the listeners. The assumption is, this will perform better in situations, where you have a lot of pins but only any (or a few) are interrupted at a time. Though i haven’t tested to perfromance for that situation. You can select the PinManagerStrategy with a typedef at the end of the PinChangeInterruptPort.h file

Some facts:

Old-PinChangeLib:

Code-Size: 2596bytes
SRAM: 58bytes static + 7bytes per pin
Preformance: 9-10us per Interrupt

Code size - 2070 bytes

SRAM : 49bytes static, + 3bytes per port + 5bytes per pin + 4bytes per Listener
Performance: LinkendPin: 11-12us, LazyArrayPin: 21-22us per interrupt

Advantage:

  • “unlimited” numbers of listeners per pin
  • lazy code - pins/ports are created on demand (no need for NOT_USE_PORT_X macros - well almost no need)
  • template implementation: you don’t “pay” for features you don’t need/use (no code will generated for that)
  • customizable: there are several points where you can add your own implementation strategies
  • less hassle in a “real” project (compared with the header-impl of the old lib)

Things to improve:
Performance is about 20% lower compared with to the old library. Not sure, if it’s optimizable to reach that point (without reducing easy of use)

Todo:
I’m thinking about some more “PinHanlderStrategies” - i.e. a SingleListenerPin…

let me know what you think…

PinChangeInterrupt.zip (14.3 KB)

looking at the downloads above - not sure if any one is interested at all :wink:

Anyway, i added my Pin-Implementation to the library…

Its fully templated and based on the concept of template injection. The sizeof is exaclty 1byte (the pin number), and no virtual methods are used. The advantage over more traditional implementation is, that the framework doesn’t decide how a pin interface looks and how the pins behave. Almost everything can be “extended” by writing a small portion of code and “inject” the code to the framework.
It supports Analog-, Digital-, Input- , Output- and DIO-Pins. There are cached and uncached version for input. Below is a list of the currently supported pin types…

NonCachedAnalogInputPin;
CachedAnalogInputPin;
typedef NonCachedAnalogInputPin AnalogInputPin;

NonCachedDigitalInputPin;
ChachedDigitalInputPin;
typedef ChachedDigitalInputPin DigitalInputPin;

DigitalOutputPin;
AnalogOutputPin;
typedef AnalogOutputPin PwmPin;

NonCachedDigitalPin;
CachedDigitalPin;
typedef CachedDigitalPin DigitalPin;

cached means, that it will allways return the same (cached) value, unless you call refresh() or a pin interrupt is received
Well actualy there are no AnalogInputPin, DigitalInputPin etc at all…these are just typedefs for certain template-combinations.

To attach a pin to an (pin) interrupt, simply call

DigitalOutputPin pin(Pin::PCI1);
pin..attachInterrupt();

Also, most pins support a Listener/Observer interface where you can register a Listener (even without using pin change interrupts). You don’t want to have to memory overhead for the oberservers ? Go ahead and inject the NullObserver. Registering one listener is not enough ? Inject the Multilistener instead of the SingleListner…

As i mentioned, the advantage is, that the user can decide on a project base how the pin should behave. Is performance the bigger concern or the memory-foot print ? Take the decision and inject the appropriate code (for example should the pin mask be pre-calculated or calculated on the fly).

you want to use digitelWriteFast instead of digitalWrite ? Just use a typedef and all your coding is done!

typedef DigitalWriterBase<digitalWriteFast> DigitalWriter;

you want to have an addition interterface for a relay, button or led ? Write the interface and inject it…

struct LedInterface : public CachedDigitalWriter {
	typedef  CachedDigitalWriter super;

	LedInterface(Digital pin) : super::pin_base(pin), super(pin){ }

	bool isOn() { super::isHigh(); }
	void on() { super::high(); }

	bool isOff() { !isOn(); }
	void off() { super::low(); }
};
typedef OutputPin<LedInterface> Led;

I also took the conecpt of the strong-typed pin-numbers from kowalski and his great cosa framework.

The disadvantage is, that the code inside the library is probably harder to understand - but just for implementation/maintenace of the lib. From a users-point of view, it 's as easy as a traditional implementation

Pin.v0.1.zip (17.2 KB)