Demand Peripherals             Robotics and Automation Made Easy

  

Application Programmer's Interface

API refers to the protocol by which programmers control and monitor peripherals. The protocol uses ASCII strings sent over TCP connections. Using ASCII strings makes the protocol easy to learn, and using TCP lets the protocol work with any programming language.

 

QUICK INDEX:
   Application Architecture
   dpserver
   Resources
   API
   Examples
   Sample Program

 

APPLICATION ARCHITECTURE:

The diagram below shows a typical architecture of the application programs running on a Linux host. The program dpserver provides the interface between the low level register read/write protocol to the FPGA and your high level applications that manage the peripherals.

The architecture supports more than one controlling application and applications can use either the select() family of routines for demultiplexing or have a separate thread for each TCP connection. Typically when your applications start they will open connection to port 8880 and issue a dpcat command (see below). This TCP connection is now dedicated to receiving a stream of sensor data from one peripheral. Add the file descriptor to the select list if you are using select() or just wait for data if you are using threads. You will probably find that you have one TCP connection for each monitored sensor and an additional connection that you use to send configuration changes to the peripherals.

A driver is that part of a peripheral that runs in dpserver and implements the API for that peripheral. The dpserver program receives packets from the FPGA and routes those packets to the appropriate driver. Dpserver also accepts TCP connections from applications and routes ASCII commands and data to the appropriate driver. The driver translates API commands into the appropriate low level register read and write commands to send over the FTDI USB-to-serial interface. There is always a one-to-one correspondence between FPGA peripherals and drivers. Since they are loaded dynamically drivers are implemented as shared libraries.

 

dpserver:

As described above, dpserver is a host resident program that has to be installed prior to using the system. Install dpserver with the following commands:

   wget http://demandperipherals.com:/downloads/dpserver-latest.tgz
   tar -xf dpserver-latest.tgz
   cat dpserver
   make
   sudo make install
The default installation directories are /usr/local/bin and /usr/local/lib. You can examine /usr/local/lib to see the .so files that are the individual peripheral drivers.

Before starting dpserver be sure to download the FPGA image to the FPGA card. Put the serial port into raw mode so that CR is not translated into CRLF during the download.

   sudo stty -F /dev/ttyUSB0 raw
   sudo cat DPCore.fpga > /dev/ttyUSB0
Of course you may need to modify the above since your FPGA card may not enumerate as ttyUSB0.

There are a few command line options for dpserver that are worth mentioning. By default dpserver listens on TCP port 8880. If you have more than one FPGA card or if port 8880 is already in use you can use the "-p" option to set the listen port. The "-f" option keeps the program in the foreground and prevents it from becoming a daemon, and the "-e" option routes error messages to stderr instead of syslog. A typical invocation of dpserver might look like this:
   dpserver -ef /dev/ttyUSB0
You can add yourself to the "dialout" group while you are experimenting with dpserver and the FPGA card.

Peripheral number zero, the enumerator, has a list of the peripherals in the FPGA image. This list dictates which .so driver files are loaded into dpserver. You can use the "-s" option to override the enumerator list and load a new driver instead of the one specified in the enumerator. For example, say you have a gpio4 in slot 2 and you want to overload it with a driver that you created called bumper.so. You can replace the expected gpio4.so driver with yours using the command:

   dpserver -ef -s2:bumper /dev/ttyUSB0

 

RESOURCES:

Peripherals are identified either by their name or, if there is more than one instance of a given peripheral in the system, by their slot number. Each peripheral has a set of one or more resources associated with it. A resource may be compound in that it may take more than one value. For example, a "config" resource may group common configuration values into one line.

A resource is an application visible name given a configuration parameter or a sensor value. For example, the buttons and LEDs on the FPGA cards are are in the "bb4io" peripheral and have resourse names of "buttons" and "leds" respectively. Resources can be read-write, write-only, or read-only depanding on the nature of the resource. Most configuration resources are read-write, and sensor reading are usually read-only.

Some sensor resources can be configured to automatically send a reading only when the input changes or when a timer expires. This can greatly simplify your application since you do not need to continuously poll sensors to detect changes. The Peripherals section of this web site gives a detailed description of the meaning and use of the resources for each peripheral

 

API:

The API consists of space separated ASCII words sent over a TCP connection. Each command (or sensor reading) is terminated by a newline. There are four commands in the API:
   - dpset : write a configuration parameter
   - dpget : read a configuration parameter
   - dpcat : start streaming sensor data
   - dplist : list available peripherals or give help on specified peripheral

The commands have the following syntax

   dpset <slot#|peri_name> <resource_name> <value>
   dpget <slot#|peri_name> <resource_name>
   dpcat <slot#|peri_name> <resource_name>
   dplist [peri_name]

EXAMPLES:

The FPGA card has buttons and LEDs that are visible from the API.

   dpset bb4io leds ff   # turn all LEDs on
   dpcat bb4io buttons   # wait for a button press

The dual DC motor controller peripherals, dc2, has controls for the PWM frequency, the mode (forward, reverse, brake, or coast), the PWM duty cycle, and a watchdog timer that stops the motors if there are no speed updates within a certain time.

   dpset dc2 PWMfrequency 20000    # set PWM freq to 20KHz
   dpset dc2 mode0 f     # motor 0 in forward mode
   dpset dc2 mode1 f     # motor 1 in forward mode
   dpset dc2 watchdog 15 # watchdog set to 1.5 seconds
   dpset dc2 speed0 40   # motor 0 at 40% PWM
   dpset dc2 speed1 70   # motor 0 at 70% PWM

The 6 digit LCD display peripheral, lcd6, lets you control individual segments or output fully formed digits.

   dpset lcd6 display 1234.56 # display a number 
   dpget lcd6 segments        # ask which segments are on

The octal 12-bit analog-to-digital peripheral, adc812, lets you specify the sample rate and whether or not to combine two inputs into one differential input ADC channel.
   dpset adc812 config 25, 0x00  # 25 ms updates, all singled ended
   dpcat adc812 samples   # start sample stream

 

A SAMPLE PROGRAM:

The following is a simple but complete program that shows how to use the dpserver API. The source code is available here: counter.c.


/* Counter.c  :  This program demonstrates the use of the
 * Demand Peripherals API for the Baseboard4 FPGA cards.
 *
 * The idea is that we create a counter and watch the
 * buttons on the FPGA card.  If button 1 is press the
 * count goes down.  If button 2 is pressed the count
 * is zeroed, and if button 3 is pressed the count is
 * incremented.  The count is an 8 bit signed number
 * that is displayed on the LEDs of the FPGA card.
 *
 * Build with: gcc -o counter counter.c
 * Be sure dpserver is running and listening on port 8880
 */


/* Overview:
 *  Open a TCP connection to the FPGA card.
 *  Send commands to flash the LEDs
 *  Open a second connection to the FPGA card
 *  dpcat the button presses on the second connection
 *  loop forever
 *   -- wait for button press (ignore release events)
 *   -- decrement, clear, or increment counter
 *   
 * As a tutorial, this program is not flexible, uses
 * magic numbers,  ignores key bounce, and does a very
 * poor job of error checking.  It tries to be brief and 
 * readable instead.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>    /* for memset */
#include <arpa/inet.h> /* for inet_addr() */

static int  cmdfd;          // FD for commands to the FPGA card
static void sndcmd(char *cmd); // send a command to the board, get prompt


main()
{
    int8_t counter;         // the 8-bit count to display
    int  tmp_int;           // a temporary integer
    int  evtfd;             // FD for button events from the FPGA card
    struct sockaddr_in skt; // network address for dpserver
    int  adrlen;
    char strled[99];        // command to set the leds
    char strevt[99];        // where to receive the button press string
    int  buttons;           // latest button event as an integer

    // Open connection to DPserver daemon
    adrlen = sizeof(struct sockaddr_in);
    (void) memset((void *) &skt, 0, (size_t) adrlen);
    skt.sin_family = AF_INET;
    skt.sin_port = htons(8880);
    if ((inet_aton("127.0.0.1", &(skt.sin_addr)) == 0) ||
        ((cmdfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) ||
        (connect(cmdfd, (struct sockaddr *) &skt, adrlen) < 0)) {
        printf("Error: unable to connect to dpserver.\n");
        exit(-1);
    }

    /* Blink the LEDs */
    sndcmd("dpset bb4io leds ff\n");
    sleep(1);
    sndcmd("dpset bb4io leds 00\n");
    sleep(1);
    sndcmd("dpset bb4io leds ff\n");
    sleep(1);
    sndcmd("dpset bb4io leds 00\n");

    /* Open another connection to receive button events */
    (void) memset((void *) &skt, 0, (size_t) adrlen);
    skt.sin_family = AF_INET;
    skt.sin_port = htons(8880);
    if ((inet_aton("127.0.0.1", &(skt.sin_addr)) == 0) ||
        ((evtfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) ||
        (connect(evtfd, (struct sockaddr *) &skt, adrlen) < 0)) {
        printf("Error: unable to connect to dpserver.\n");
        exit(-1);
    }

    counter = 0;   // leds are already showing zero

    /* Start the stream of button events */
    write(evtfd, "dpcat bb4io buttons\n", 20);
    /* the above command never returns so we do not use sndcmd() */

    while(1) {
        /* wait for a button press */
        read(evtfd, strevt, 3);    // two digits and a newline
        sscanf(strevt, "%d", &buttons);

        /* Examine pressed button and change counter */
        /* (We don't keep the old button value so do not do
           edge detection.  ie. One button at a time please) */
        if (buttons & 1) {
            counter--;
        }
        if (buttons & 2) {
            counter = 0;
        }
        if (buttons & 4) {
            counter++;
        }

        /* display new value of count */
        sprintf(strled, "dpset bb4io leds %02x\n", (counter & 0xff));
        sndcmd(strled);
    }
}

/* sndcmd():  Send a command to dpserver and wait for a response.  The
 *     response will be a prompt character, which we ignore and return,
 *     or an error message which we send to stderr. */
static void sndcmd(char *cmd)
{
    size_t count;          // number of chars in command to send
    char   c;              // prompt or error message character
    int    retval;         // return value of read()

    count = strlen(cmd);        // should sanity check count
    write(cmdfd, cmd, count);   // should look at write() return value

    /* loop getting characters.  Return on a prompt character '\' and
     * send any other character to stderr. */
    while (1) {
        retval = read(cmdfd, &c, 1);
        if (0 >= retval)
            exit(1);       // did TCP conn go down?
        else if ('\\' == c)
            return;        // got a prompt char.  Done with command
        else
            write(2, &c, 1);    // send to stderr
    }
}