Jump to main content | Jump to Primary Navigation | Jump to Sub Navigation


 

Drivers 1: A new device driver

Back to main drivers page Next tutorial

This tutorial illustrates how to use the DevBot infrastructure to create drivers for your custom hardware. Before reading this tutorial you should read Architecture document and the tutorials on developing DevBot controllers.

There are several parts to a driver. This tutorial covers the most basic elements of a driver by examining the simple virtual device driver simple_driver.

Contents

  1. Driver boilerplate
  2. The property template table
  3. Device initialization
  4. Poll Function
  5. Compiling a driver
  6. Bringing it all together

Driver boilerplate

Every driver should define and export a BotDriver struct specifying important metadata such as its name, description and version string. The BotDriver struct also identifies the property template table, initialization function and poll function that are described in the following sections.

With the exception of name and version version all of the BotDriver fields may be left blank. But since most drivers are intended to do something useful they will usually define at least a poll function and property template table and often a device initialization function.

For example, the boilerplate for the simple_driver driver (used in Tutorial 1: First steps and Tutorial 3: Creating devices programmatically) looks like this:

static const BotDriver boilerplate = {
    .name               = "simple_driver",
    .description        = "Demonstrates the basic features of the driver API",
    .version            = "1.0",
    .initialize_device  = init_device,
    .poll_device        = poll_device,
    .driver_properties  = driver_properties
};

BOT_DRIVER_BOILERPLATE(boilerplate);

BOT_DRIVER_BOILERPLATE is a convenience macro used to export this structure.

The property template table

DevBot provides a convenient way for drivers to specify the properties they support in the form of a property table. This is a simple array of BotPropertyProto structures. Each of BotPropertyProto structure defines a single property by specifying its name, description, and data type. The name should be no more than 64 characters long. The data type will commonly be one of the built-in types describe in Type system. The use of external types in drivers is covered by a later tutorial.

The template structure also sets the "flags" field which is used to define access permissions. The most commonly used flags are shown in the table below.

BOT_NO_DEFAULT Property requires initialization -- there is no default
BOT_NO_READ Property is write-only
BOT_NO_WRITE Property is read-only

This field is a bit-field, so these flags can combined using bit-wise operators. For example, the following code would indicate a mandatory, read-only property.

Finally the property table must end with the END_PROPERTY_PROTO. An example of a finished property table is:

static const BotDeviceProto driver_properties[] = {
    {
        .name = "Countdown",
        .description = "Value that will count down to zero.",
        .type = BOT_TYPE_UINT_NAME,
        .flags = BOT_NO_DEFAULT,
    },
    {
        .name = "Message",
        .description = "Message to print. Limited to 256 characters.",
        .type = BOT_TYPE_STRING_NAME,
        .length = 256,
    },
    END_PROPERTY_PROTO
};

Note that it is possible for drivers to create device properties dynamically. This is covered in a later tutorial.

Device initialization

The device initialization function (conventionally initialize_device()) is executed each time the driver creates a new device instance. In the simple_driver this is used to install a callback that will print messages set on the Message property:

static bool init_device(BotDevice *device) {
    BotProperty *message_p;

    message_p = bot_device_lookup_property(device, "Message");
    bot_property_add_callback(message_p, device,
                              message_updated, CALLBACK_RELAXED);
    return TRUE;
}

A real driver might probe a bus, or initialize internal device-specific data-structures instead.

Poll Function

Where a driver specifies a poll function, the system will create a mandatory (BOT_NO_DEFAULT, uint) property called "PollPeriod" on its device instances. The system will then execute the poll function once every PollPeriod microseconds.

If the user specifies a poll period that is too short for the work the poll function has to do the system will print a diagnostic error and terminate. An example poll_device() which accesses the "Countdown" property and decrements its value is given below.

static bool poll_device(BotDevice *device)
{
    BotProperty *countdown_p;
    uint32_t countdown;

    /* Update countdown property */
    countdown_p = bot_device_lookup_property(device, "Countdown");
    countdown = bot_property_get_uint(countdown_p);

    if (countdown > 0)
        bot_property_set_uint(countdown_p, countdown - 1);

    return TRUE;
}

Note that instead of looking up a property on every poll in this way, one would normally store a reference during device initialization. This is covered in a later tutorial.

Compiling a driver

  1. Drivers are typically called drivername.so (not libdrivername.so)
  2. Compiled drivers must be included in LD_LIBRARY_PATH in order to be found by the controller program

The following example Makefile shows the required compiler flags for compilation.

# Makefile for compiling a DevBot driver

CCROOT=/opt/devbot/armxscale-uclibc
CCTARGET=$CCROOT/bin/arm-linux-uclibc-gcc
CFLAGS="-Wall -shared -O2 -I$CCROOT/include -L$CCROOT/lib"

drivername.so:
    $CCTARGET $CFLAGS $PKCFG -lbot -o drivername.so drivername.c

Bringing it all together

Bring all of the above together yields simple_driver.c

/******************************************************************************
 *
 * Simple driver used for basic controller and driver tutorials.
 *
 ******************************************************************************/

#include <devbot/driver/property.h>
#include <devbot/driver/driver.h>
#include <devbot/driver/device.h>

#include <devbot/dispatcher.h>
#include <devbot/type.h>
#include <devbot/bot.h>

#include <stdlib.h>
#include <stdio.h>

static const BotDeviceProto driver_properties[] = {
    {
        .name = "Countdown",
        .description = "Value that will count down to zero.",
        .type = BOT_TYPE_UINT_NAME,
        .flags = BOT_NO_DEFAULT,
    },
    {
        .name = "Message",
        .description = "Message to print. Limited to 256 characters.",
        .type = BOT_TYPE_STRING_NAME,
        .length = 256,
    },
    END_PROPERTY_PROTO
};

/* Callback attached to Message property */
static bool message_updated(BotProperty *message_p, void *user_data)
{
    char buffer[256];
    bot_property_get_string(message_p, buffer);
    bot_mesg("%s says, \"%s\"", bot_device_get_name(user_data), buffer);
    return TRUE;
}

/* Device initialization function */
static bool init_device(BotDevice *device) {
    BotProperty *message_p;

    message_p = bot_device_lookup_property(device, "Message");
    bot_property_add_callback(message_p, device,
                              message_updated, CALLBACK_RELAXED);
    return TRUE;
}

/* Poll function -- decrement the Countdown property */
static bool poll_device(BotDevice *device)
{
    BotProperty *countdown_p;
    uint32_t countdown;

    /* Update countdown property */
    countdown_p = bot_device_lookup_property(device, "Countdown");
    countdown = bot_property_get_uint(countdown_p);

    if (countdown > 0)
        bot_property_set_uint(countdown_p, countdown - 1);

    return TRUE;
}

static const BotDriver boilerplate = {
    .name               = "simple_driver",
    .description        = "Demonstrates the basic features of the driver API",
    .version            = "1.0",
    .initialize_device  = init_device,
    .poll_device        = poll_device,
    .driver_properties  = driver_properties
};

BOT_DRIVER_BOILERPLATE(boilerplate);

Back to main drivers page Next tutorial