Skip to content
rhn edited this page Aug 31, 2011 · 11 revisions

This is a quick tutorial on adding modules.

All modules live in src/modules/, along with some non-module files. The majority of a module code lives in modules/modulename.c, but it needs some hooks in signals.c and modules/base.c.

A good example of a simple module is modules/debug.c, which only manages the display backlight. We're going to extend it to display the pulse count.

Registering a module

Before you register a module, you must create a file, e.g. my_module.c, containing a module_record_t structure. The structure contains a reference to a redraw function, select function and a graphical icon. The icon is stored as a fixed-size array of bytes - each byte is a 8px-high column.

void mymodule_redraw(const uint8_t force) {
}
module_actions_t *mymodule_select(const uint8_t state) {
    return NULL;
}
#define mymodule_signature {0b00000000, 0b00000000, 0b00000001, 0b00000001, 0b00000010, 0b00000010, 0b00000100, 0b00000100}
#define mymodule_record {&mymodule_redraw, &mymodule_select, mymodule_signature}

After that, you can actually register the module. Open the file modules/base.c and include your file. As a result, somewhere in modules/base.c the code should look like this:

...
#ifdef BACKLIGHT
    #include "debug.c"
#endif

#ifdef MYMODULE
    #include "my_module.c"
#endif

...

The last step is to add a reference to the module code in the module table further down inside base.c:

// table of module records
const module_record_t modules[] = {
    #ifdef DISTANCE
        distance_record,
    #endif
    ...
    #ifdef MYMODULE
        mymodule_record,
    #endif
    };

So far, the only 2 functions the module has is select and redraw. To get more functions, some useful hooks are exposed in signals.c.

Using events

Our module is already registered, so we can start doing something with it. Since we want to get information about a pulse occuring, we need to look at events.

signals.c is a single place where you can hook into specific signals. We're going to use on_wheel_pulse.

Creating the handler

For now, let's create an empty function in my_module.c:

void my_module_on_wheel_pulse(void) {
};

This function must be registered in signals.c now:

inline void on_wheel_pulse(void) {
    ...
    #ifdef MYMODULE
        my_module_on_wheel_pulse();
    #endif
    ...
}

Now it will be called at each pulse. The next step is to make it useful. You might be tempted to do something like:

uint8_t my_module_pulses = 0;
void my_module_on_wheel_pulse(void) {
    my_module_pulses++;
    print_number(my_module_pulses);
};    

THE ABOVE WILL NOT WORK! If you remember the Architecture section from hacking guide, collecting and displaying data must be separate - fot printing the numbers there is my_module_on_redraw. This signal handler should only collect data:

uint8_t my_module_pulses = 0;
void my_module_on_wheel_pulse(void) {
    my_module_pulses++;
};

Displaying data

Displaying data takes place entirely in my_module_on_redraw. To print something on the screen, you have to use the other part of the API: drawing functions. Look at drawing.h to find print_number:

void print_number(uint32_t bin, upoint_t position, const upoint_t glyph_size, const uint8_t width, const number_display_t digits);

Don't worry, it's not that hard to use :). First, include display/drawing.h, then set up variables and print the actual number.

The resulting module:

#include "../display/drawing.h"

uint8_t my_module_pulses = 0;
void my_module_on_wheel_pulse(void) {
    my_module_pulses++;
};

void mymodule_redraw(const uint8_t force) {
    upoint_t position = {0, 5};
    upoint_t glyph_size = {8, 8};
    number_display_t num_disp = {3, 0};
    print_number(my_module_pulses, position, glyph_size, 1, num_disp);
}

#define mymodule_signature {0b00000000, 0b00000000, 0b00000001, 0b00000001, 0b00000010, 0b00000010, 0b00000100, 0b00000100}
#define mymodule_record {&mymodule_redraw, &mymodule_select, mymodule_signature}

This version will display the number every time any event occurs - timer, crank, wheel, stop etc. and so it will be somewhat inefficient. Every builtin module sets up a "refresh" flag and uses it with conjunction with the force parameter to make sure the value is redrawn only when necessary.

Very important notes about function handlers

Interrupts

All signals and button handling procedures are run as interrupts - it means that they cannot take a lot of time to process (ideally O(1)) - otherwise the precision of most measurements is going to get much worse. The general idea is to get as good precision as possible by gathering data as it comes. All interrupt handlers are executed atomically - this makes them easier to use, but delays the coming of later interrupts. This behavior may change in the future, to ensure saving of the timestamp as soon as the interrupt actually comes. Do not use any functions from drawing.h or pcd8544.h in interrupt handlers! They are not thread-safe.

on_redraw

This function is "safe" in terms of time it can take, but it can also be interrupted. Since this function is intended exclusively to display the data that interrupts gather, the data it uses can change while it's running. This is mostly harmless as long as no data is modified, next time it runs, it is unlikely to be interrupted again. The only drawback is a weird value displayed for some time. For a fix of this problem, see speed_on_redraw in builtins/speed.h.

Clone this wiki locally