FunctionPointer

Callbacks : How libraries can callback/hook to user's application

FunctionPointer

When writing library code to be used in a generic sense by applications they often need to inform the application on certain events. The Mbed libraries already contain many examples of just such callbacks and include:-

These are just three examples of callbacks used in the Mbed library. So, how does the Mbed library implement these callbacks? Well, it uses a special class type called the FunctionPointer (defined here). By including mbed.h in your project this class type is also available to you the user (or library writer). Lets look at an example of it in use.

First, we will create a simple library called FOO.h. (Note, this example is pretty pointless, it does what InterruptIn already does for the most part, it's just a demonstration)

FOO.h

#ifndef FOO_H
#define FOO_H

#include "mbed.h"

class FOO {
    protected:
        InterruptIn     _irqpin;
        FunctionPointer _callback_rise;
        FunctionPointer _callback_fall;

    public:
        enum Edge { RisingEdge = 0, FallingEdge };
        
        FOO(PinName p) : _irqpin(p) {
            _irqpin.rise(this, &FOO::call_rise);
            _irqpin.fall(this, &FOO::call_fall);
        }
        
        void attach(Edge edge, void (*function)(void)) { 
            if (edge == RisingEdge) _callback_rise.attach( function ); 
            else {                  _callback_fall.attach( function ); }
        }

        template<typename T>
        void attach(Edge edge, T *object, void (T::*member)(void)) { 
            if (edge == RisingEdge) _callback_rise.attach( object, member ); 
            else                    _callback_fall.attach( object, member ); 
        }

        void call_rise(void) { _callback_rise.call(); }
        void call_fall(void) { _callback_fall.call(); }
};
#endif

As stated, this library does what InterruptIn already does with one minor difference. InterruptIn provides two different attachment functions, .rise() and .fall(). Class FOO however only has one method, attach(). An extra argument is added to tell the library whether it's for rising edges or falling edges.

Lets look at how we might use this new library type FOO in a real world program/application:-

main.cpp

#include "mbed.h"
#include "FOO.h"

DigitalOut myled(LED1);

DigialOut led3(LED3);
DigialOut led4(LED4);

// Create an instance of FOO called foo
FOO foo(p21);

void myCallbackLED3(void) { led3 = !led3; }
void myCallbackLED4(void) { led4 = !led4; }
  
int main() {
    
    foo.attach(FOO::RisingEdge,  &myCallbackLED3);
    foo.attach(FOO::FallingEdge, &myCallbackLED4);
    
    while(1) {
        myled = 1;
        wait(0.2);
        myled = 0;
        wait(0.2);
    }
}

It should be obvious that when the value of pin p21 goes from 0 to 1 (going high) LED3 will toggle and when it goes from 1 to 0 (going low) LED4 toggles. Now, we could have just done that directly using InterruptIn but this is shown as a demonstration of how to implement your own callbacks using the Mbed FunctionPointer type.

FunctionPointer : The callback function prototype

You will have noticed in the above demostration that both callbacks were defined void myCallbackLED3(void) and void myCallbackLED4(void). In both cases the callback can neither take any arguments or return a value. It's a void/void function. Generally this makes perfect sense because when an Mbed library calls back into your application it's actually just an event notification. Taking InterruptIn as an example, if you created an InterruptIn then you will have already bound that to a specific pin. So your application already knows which pin "changed" when the callback is made. There's no need to tell the callback which pin changed because the callback is already bound to a specific pin when it was created, it's implicit in your design.

However, there are times when you may want to pass some sort of information to a callback. Lets create such an example by way of demonstration and then provide solutions to the problem. First, a "mini specification":-

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 look at two possible solutions. The first uses straight Mbed InterruptIn types and uses stub-callbacks and it assumes you are developing an application or program and not a library.

#include "mbed.h"

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

InterruptIn P21(p21);
InterruptIn P22(p22);
InterruptIn P23(p23);
InterruptIn P24(p24);

// Common callback.
void callback(PinName p, int direction) {
    // Now, our common function knows which pin changes and how it changed.    
    switch(p) {
        case p21: led1 = 0; led2 = 0; break;
        case p22: led1 = 1; led2 = 0; break;
        case p23: led1 = 0; led2 = 1; break;
        case p24: led1 = 1; led2 = 1; break; 
    }
    
    led4 = (direction & 1);
}

// "callback stubs"
void callback_p21_rise(void) { callback(p21, 1); }
void callback_p21_fall(void) { callback(p21, 0); }
void callback_p22_rise(void) { callback(p22, 1); }
void callback_p22_fall(void) { callback(p22, 0); }
void callback_p23_rise(void) { callback(p23, 1); }
void callback_p23_fall(void) { callback(p23, 0); }
void callback_p24_rise(void) { callback(p24, 1); }
void callback_p24_fall(void) { callback(p24, 0); }

int main() {
    P21.rise(&callback_p21_rise);
    P21.fall(&callback_p21_fall);
    P22.rise(&callback_p22_rise);
    P22.fall(&callback_p22_fall);
    P23.rise(&callback_p23_rise);
    P23.fall(&callback_p23_fall);
    P24.rise(&callback_p24_rise);
    P24.fall(&callback_p24_fall);
    
    while(1);
}

This gets the job done. However, lets imagine we wanted to write a library that does this. In this case the library does not know which pins the Interrupts in are going to attach to. Also, we would like it if our library could call the common callback with arguments rather than having to have a lot of stub-callbacks. The callback should just pass an integer BCD representation of the pin change (which pin changed) and the direction of change. The problem here is that Mbed's FuntionPointer class type cannot help as it cannot pass arguments nor return a value.

To solve this problem we can use a recently published (released by the author) FPointer class type. This class type and the solution to the above problem are both explained in the FPointer cookbook page.