FPointer

FPointer : Callbacks with arguments and return values

In a previous cookbook article (Mbed's FunctionPointer) the Mbed's library callback mechanism was described and also worked demonstrations were presented. If you have not yet read that article then please do so now. This cookpage article is a continuation from that.

The FPointer class type

One of the limiting factors of the FunctionPointer type was that the callback required a function prototype of void/void. It can neither take any arguments nor can it return a value. FPointer differs from FunctionPointer in one crucial way. It's callback function prototype is of the type:-

FPointer callback function prototype

uint32_t myCallback(uint32_t);

It can be seen that FPointer callbacks can take a single argument of type uint32_t and return the same uint32_t data type. This allows FPointer to send and return values in multiple ways. The full definition of the FPointer can be found here:-

Import libraryFPointer

FPointer - A callback system that allows for 32bit unsigned ints to be passed to and from the callback.

If your callback only needs to take or return a uint32_t then it's possible to use it "as is". However, if you need to pass multiple arguments to a callback and/or of differing types then it may at first not be obvious how to. However, since the Mbed's microcontroller (LPC17xx) has a natural data width of 32bits, a uint32_t can also be a pointer. That allows us to create data structures and pass pointers to those structures.

In the FunctionPointer article a simple demonstration showed the use of "stub-callbacks" used to call a common callback with additional arguments. Here, we will use FPointer to develop a library that is generic in implementation and shows how to make a callback to a user's application/program with the extra data needed. A brief recap of the simple specification follows:-

Quote:

We have four inputs and we want to display this as BCD on two LEDs, led1 and led2. We want this display to be interrupt driven so as not to have to "poll" the pins and update the display. Additionally, led4 should show how the change came about, on if it was a rising edge, off if it was a falling edge.

Let's begin by creating a library class type and we'll then follow that with a user's application that uses this library.

myLib.h

#ifndef MY_LIB_H
#define MY_LIB_H


#include "mbed.h"
#include "FPointer.h"

typedef struct {
    int value;
    int direction;
} MYDATA;

#define MYDATA_VALUE_P(x)     ((MYDATA *)x)->value
#define MYDATA_DIRECTION_P(x) ((MYDATA *)x)->direction

// InterruptIn stores it's PinName in a protected area
// but doesn't provide a method to get which pin after
// it's been created. So extend InterruptIn and add an
// extra method to allow us to get this information.
class myInterruptIn : public InterruptIn {
    public:
        myInterruptIn(PinName p) : InterruptIn(p) {};
        PinName pinName(void) { return _pin; }
};

class myLib {
    protected:
        myInterruptIn _pA;
        myInterruptIn _pB;
        myInterruptIn _pC;
        myInterruptIn _pD;
        FPointer      _callback;
        MYDATA        _myData;
        
        void common(PinName p, int direction) { 
            if      (p == _pA.pinName()) _myData.value = 0;
            else if (p == _pB.pinName()) _myData.value = 1;
            else if (p == _pC.pinName()) _myData.value = 2;
            else if (p == _pD.pinName()) _myData.value = 3;
                        
            _myData.direction = direction;
            _callback.call((uint32_t)&_myData);
        }
        
        void _pAcb_rise(void) { common(_pA.pinName(), 1); }
        void _pAcb_fall(void) { common(_pA.pinName(), 0); }
        void _pBcb_rise(void) { common(_pB.pinName(), 1); }
        void _pBcb_fall(void) { common(_pB.pinName(), 0); }
        void _pCcb_rise(void) { common(_pC.pinName(), 1); }
        void _pCcb_fall(void) { common(_pC.pinName(), 0); }
        void _pDcb_rise(void) { common(_pD.pinName(), 1); }
        void _pDcb_fall(void) { common(_pD.pinName(), 0); }
        
    public:
    
        myLib(PinName pA, PinName pB, PinName pC, PinName pD) :
            _pA(pA), _pB(pB), _pC(pC), _pD(pD) {
            
            _pA.rise(this, &myLib::_pAcb_rise); _pA.fall(this, &myLib::_pAcb_fall);
            _pB.rise(this, &myLib::_pBcb_rise); _pB.fall(this, &myLib::_pBcb_fall);
            _pC.rise(this, &myLib::_pCcb_rise); _pC.fall(this, &myLib::_pCcb_fall);
            _pD.rise(this, &myLib::_pDcb_rise); _pD.fall(this, &myLib::_pDcb_fall);
        }
        
        void attach(uint32_t (*function)(uint32_t) = 0) { _callback.attach(function); }
        
        template<class T> 
        void attach(T* item, uint32_t (T::*method)(uint32_t)) { _callback.attach(item, method); }    
};

#endif

Now that we have created our library here is how a user's application would look using it:-

main.cpp

#include "mbed.h"
#include "myLib.h"

DigitalOut led1(LED1);
DigitalOut led2(LED2);
DigitalOut led4(LED4);

myLib lib(p24, p23, p22, p21);

uint32_t myCallback(uint32_t arg) {
    switch( MYDATA_VALUE_P(arg) ) {
        case 0: led1 = 0; led2 = 0;
        case 1: led1 = 0; led2 = 1;
        case 2: led1 = 1; led2 = 0;
        case 3: led1 = 1; led2 = 1;
    }
    
    led4 = ( MYDATA_DIRECTION_P(arg) ) ? 1 : 0;
    return 0;
}

int main() {

    lib.attach(&myCallback);
    while(1);
}

The above shows us two important points/features. At first glance myLib.h appears to be a little more complex than the demonstration presented in the previous article. This is often true of libraries and it stems from the fact that libraries are usually generic in nature (for example the library doesn't know in advance what pins you are going to assign to the InterruptIns) and secondly adds a data structure to the mix. Not shown here also is the doxygen comments library writers put in to allow for documenting any API or usage of the library. But the first important concept is a library is usually "done once, used often". Take a look at the "application" main.cpp. As you can see, from the user's perspective the implementation is a whole lot simpler. So long as the user creating the application has ample documentation and/or examples of use, using the library becomes very simple.

The second important feature and most relevent to this article is the use of the FPointer callback. As used here when it calls back to the user's application it is passing a pointer to a data structure that, in this instance, holds two variables: value and direction. This makes FPointer's callback mechanism very powerful and flexible.