Thank you for your purchase of the Software Defined Peripherals (SDP) Starter Kit. This kit has everything you need to learn about software defined peripherals and to build a simple system using a set of peripherals that you choose.
You should have some familiarity with the Linux command line and with programming in Python or C/C++. Prior experience with microcontrollers, robots, and simple wiring is very helpful. Although this kit is based on a field programmable gate array (FPGA) you do not need to know anything about FPGAs or circuit design. You will need a Linux computer that has a spare USB port. The computer can be a desktop, laptop, or a single board computer such as a Raspberry Pi. This kit contains a motor controller (called an H-bridge) but does not contain any motors. The H-bridge card is designed to connect directly to motors from Pololu but you can use any low voltage DC motors that you have available. The Pololu motors are: https://www.pololu.com/category/115/25d-metal-gearmotors
This document is broken into three major sections. The first section covers the theory of operation of software defined peripherals (SDP) and the Baseboard as an example. The second and largest section covers how to use the Baseboard. This section describes the API, how to order a set of peripherals for the Baseboard, and how to write a simple application using the Baseboard. We re-implement the simple application using ROS2. A third, more advanced section describes how to write your own software defined peripherals. This section covers both the driver and API as well as Verilog and circuit requirements. In the earlier sections we are careful to define the terms we use and explain ideas carefully. The later sections are more concise and will assume your Linux and programming experience.
In the world of robotics and automation a peripheral is a device used for communication or for sensing and control. Communication peripherals include Inter-Integrated Circuit (I2C), Serial Peripheral Interface (SPI), asynchronous serial interfaces, and Ethernet interfaces. Counter and timer peripherals can implement pulse width modulation (PWM) for servo control and for DC motor control. Peripherals are generally hard-wired into the computer or microcontroller and can not be changed after the chip is built. Software defined peripherals are typically built on a Field Programmable Gate Array (FPGA). As the name implies, an FPGA has a huge array of gates and flip-flops. How the gates and flip-flops are interconnected is controlled by RAM that is set (or programmed) by the user (in the field). Software defined peripherals are built using gates, flip-flops, registers, and RAM. When you think of software, you probably think of lines of code executed in sequence on a Von Neumann CPU. FPGAs are not CPUs and do not execute sequential lines of code. Peripherals built using an FPGA use dedicated gates and registers designed to fill the intent of the peripheral. Many peripheral designers use schematics for their designs but a more common approach is to use a hardware description language such as Verilog or VHDL. In the same sense that HTML describes how to render a page, Verilog describes the interconnect of gates, registers, and external pins that make up a peripheral. The Verilog sources are compiled into a binary file that is loaded into the FPGA to implement the desired logic. Just as a PC has hardware that is independent of the OS loaded, so software defined peripherals must be divided into the hardware and the binary FPGA image. Your Baseboard4 is the hardware. DPcore is the FPGA image you load onto the Baseboard. We will refer to the FPGA binary in DPcore as the “SDP image” to make clear that it implements a set of software defined peripherals.
Figure 1 shows the internal architecture of DPcore with some typical peripherals. The areas in gray are built into every DPcore. The peripherals in white can change from one DPcore to the next. Later you will see how to build DPcore with your own selection of peripherals. Each peripheral is assigned a slot that has four dedicated FPGA pins. (Slot 10 has 3 pins.) The address bus in DPcore has 12 bits. The high 4 bits select the peripheral making the low 8 bits are available to the peripheral. Peripheral #0 is the enumerator, a read-only memory that has a list of the peripherals in the SDP image. At system start the host reads the enumerator to find what peripherals are available. The host can read and write peripherals on the bus by sending a command packet to DPcore. The command packet is decoded by the Command Parser and converted to read and write bus cycles. Commands can read or write to sequential addresses or can read or write to one address if a FIFO. Command packet are encapsulated using SLIP even though the packets are not IP packets. Microcontroller peripherals are tightly coupled to the CPU and are accessed by the CPU over an internal address and data bus. Tightly coupling peripherals to the CPU has advantages and disadvantages. On the plus side you can get get much higher data rates with a closely coupled system. This might be important for image or video processing peripherals. Tightly coupled peripherals usually have an interrupt request line. This can be a problem for heavily loaded CPUs in which interrupts (and so data) can be missed or cause irregular sampling rates. The peripherals in DPcore are not closely coupled to a CPU and can not request service using an interrupt. Instead, most peripherals can be configured to automatically send data to the host when needed. Typically this might be for regular analog to digital conversion or when a monitored pin changes value. Polling is almost never needed with DPcore. One advantage of this is that your application can be entirely event driven if you wish.
Figure 2 shows the architecture for a complete system. Peripherals share a common link between the Baseboard and the host. Dpdaemon is a program that runs on the host and provides the multiplexing and demultiplexing of data to and from the Baseboard. For every peripheral there is a corresponding user-space shared library, which we call a driver. Do not confuse our shared-object libraries for real Linux kernel drivers. We are using the term driver to mean the software that provides the API for the peripheral. When dpdaemon starts it reads the list of peripherals from the enumerator and loads the appropriate set of drivers. In this way the system responds automatically to whatever set of peripherals you load into DPcore.
In this section you will see how to connect your Baseboard4 to the other cards, how to select the peripherals you want in your system, how to generate an SDP image with those peripherals, how to load and run dpdaemon, and finally, how to use the API to test your system.
The Baseboard has four 16-pin connectors, each of which can support two different peripherals. Y-cables split the 16-pin connector into two 8-pin ones. Photo 1 shows the slot assignment for the connectors. Compare what you see in Photo 1 to what you saw in Figure 1. Slot numbers specifies a set of four pins on the Baseboard, and a peripheral number specifies the high four bits of the peripheral address. Slot numbers are always the same as peripheral numbers and we sometimes use the terms interchangeably.
Some peripherals need eight FPGA pins and so use two slots. The octal servo controller and the octal IR proximity sensor are examples of an eight pin peripheral. However, most daughter cards have the 8-pin interface shown in Figure 3.
Photo 2 shows a host computer, a Baseboard, and a two daughter cards. Can you see that the quad touch sensor is in Slot 3 and the octal ADC is in Slot 2?
The easiest way to get an FPGA image is to use our web interface to ask us to build an image for you. Select Support and “Build your FPGA Image” to go to the Peripheral/Slot Assignment page. Figure 5 shows the top part of the slot assignment page. The peripherals are listed down the page and slots are listed across the page. Click the corresponding radio button to assign a particular peripheral to a slot. Figure 5 shows that a dual DC motor controller peripheral has been assigned to Slot #2.
Figure 5 graphically illustrates one of the great advantages of software defined peripherals - any peripheral in any slot and they all work seamlessly. You no longer need to worry about pin conflicts, interrupt latency, or a VonNeumann bottleneck. The peripherals all have a Linux API, removing one more thing from your to-do list. After selecting peripherals for each slot press the Continue button at the bottom of the page. This takes you to a page where you can review and confirm your selection. This page also has a photo similar to Photo 1 above showing how the slot assignment map to the pins on the Baseboard.
Click Continue on the review page to go to a page asking where to send the finished DPCore image. You will need to give name, email address, and accept the license. Figure 6 shows the page asking where to send the FPGA binary.
The name of the SDP image file is DPcore.bin and it should appear in your inbox the day after your request. The list of peripherals is prepended to the file and doing a ‘more’ on the file will show you its contents. The SDP image for the Starter Kit can be downloaded directly from
If you wish to test building your own image for the Starter Kit please be sure to put the Dual DC Motor Controller Peripheral in Slot #2, the Dual Quadrature Decoder in Slot #3, and the Octal Pololu QTR Interface in Slot #4.
The Baseboard connects to your computer using a USB-serial link. Data over the link consists of packets of binary data that read and write the registers in the software defined peripherals in DPCore. Your high level application could communicate with the Baseboard directly but almost everyone finds it easier to deal with an API that translates high-level API commands into read and write packets to the Baseboard. Dpdaemon provides this easy to use API for your applications. You can download and install dpdaemon with the following commands:
git clone https://github.com/DemandPeripherals/dpdaemon.git cd dpdaemon make sudo make install
The Baseboard has an FTDI USB-to-serial interface. Connect the Baseboard to your computer and determine the name of the serial port. The USB serial port is owned by the group dialout. Add yourself to the group dialout and start dpdaemon with the commands:
sudo addgroup $LOGNAME dialout dpdaemon -l /usr/local/lib/DPCore.bin -s /dev/ttyUSB0
You can perform a quick test that everything is working by changing the LEDs on the Baseboard with the commands:
export PATH=$PATH:/usr/local/bin/ dpset bb4io leds 55
Dpdaemon is a true daemon in that, by default, it will disassociate from the controlling terminal, respawn itself, and use syslog() to report errors. You can change this behavior with the -f command line option. This keeps the daemon in the foreground. The -e command option tells the daemon to not use syslog() and to report errors to the controlling terminal. The API to dpdaemon uses ASCII commands over a TCP link. The TCP listen port has a default of 8870. You can override this default with the -p command line option. Dpdaemon monitors only one serial port. You can use an alternate TCP port if you ever want to run two instances of dpdaemon connected to two different serial ports. The -a option tells the daemon to listen on any IP interface not the default of 127.0.0.1. While a security risk, listening on any network interface can be handy when the high level application runs on a separate computer. The -l option tells the daemon which binary image to load onto the FPGA at startup. There is no default image. The -s option specifies which serial port to use. The default is /dev/ttyUSB0. Many Linux systems support real-time extensions to the job scheduler. You can invoke these extensions for dpdaemon using the -r command line option. The drivers to load when dpdaemon starts are specified in the enumerator peripheral. You can override what driver is loaded using the -o option. Running dpdaemon with the -h option give the following help text:
dpdaemon [options] options: -e, --stderr Route messages to stderr instead of log even if running in background (i.e. no stderr redirection). -v, --verbosity Set the verbosity level of messages: 0 (errors), 1 (+debug), 2 (+ warnings), or 3 (+ info), default = 0. -d, --debug Enable debug mode. -f, --foreground Stay in foreground. -a, --listen_any Use any/all IP addresses for UI TCP connections -p, --listen_port Listen for incoming UI connections on this TCP port -r, --realtime Try to run with real-time extensions. -V, --version Print version number and exit. -o, --overload Load .so driver file for slot, as slotID:file.so -h, --help Print usage message. -s, --serialport Use serial port specified not default port. -l, --load Load DPCore.bin specified.
It is easy to start dpdaemon in your rc.local or other start up file with the following command:
/usr/local/bin/dpdaemon -l /usr/local/lib/DPcore.bin
Of course the above will need to match where you install dpdaemon and your SDP image file.
The next section describes the dpdaemon API. We believe that the simple API offered by dpdaemon is suitable for all types of simple automation such as vending machines, automatic cat doors, and two-wheeled robots. However, the dpdaemon API is just one of many possible APIs. You could, for example, replace dpdaemon with a g-code interpreter if you wanted to to build a 3-axis machine tool, or you could replace the loadable driver modules of dpdaemon with their equivalent ROS2 nodes.
A resource is an application visible name given a configuration parameter or a sensor value. For example, the buttons and LEDs on the FPGA card are are in the “bb4io” peripheral and have resource names of “buttons” and “leds” respectively. Resources can be read-write, write-only, or read-only depending 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 the Demand Peripherals web site gives a detailed description of the meaning and use of the resources for each peripheral. You can also get help on a peripheral's resources using the dplist command described below.
The API has five commands. You can set a resource, such as a configuration parameter. You can get a resource, such as a status value. You can cat a resource, such as a stream of sensor data. Two more commands let you list the peripherals in the system and load a driver module not included in the enumerator. The commands are all ASCII text terminated by a newline over a TCP connection. Resource values are also given as ASCII text. Dpdaemon will send back an error message if the command was not recognized and a single backslash character after the command has been processed. 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] dploadso <full path to .so driver file>
Dplist lists the driver modules loaded by dpdaemon. Giving dplist the name of a peripheral returns a page of help text and examples for that peripheral. Note that when you have more than one instance of a peripheral in the system you must use the slot number to address instances other than the first one. One of the things we like about the dpdaemon API is that the commands all look like Linux command line commands. In fact, while the underlying protocol is still TCP based, all of these command have bash command line equivalents. This means you can easy view, test, or configure your system from the command prompt.
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 power0 40 # motor 0 at 40% PWM dpset dc2 power1 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
The following is a simple but complete Python program that shows how to use the dpdaemon API. The source code is available http://demandperipherals.com/downloads/counter.py
#!/usr/bin/env python import socket import sys # This program opens two sockets to the dpdaemon, one # to listen for button press events and one to update # the LED display. This code uses a blocking read but # a select() implementation would work too. # # Pressing button 1 increments the count, button 2 # clears the count and button 3 decrements the count. # Buttons are represented as hex values 1, 2, and 4. # Global state information count = 0 try: sock_cmd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_cmd.connect(('localhost', 8870)) sock_button = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_button.connect(('localhost', 8870)) sock_button.send('dpcat bb4io buttons\n') # loop forever getting button presses and updating the count while True: display_count = count if display_count < 0: display_count = count + 256 sock_cmd.send('dpset bb4io leds ' "%x" '\n' % display_count) key = sock_button.recv(6) keyint = int(key[:3]) if keyint == 1: count = (count + 1) % 256 elif keyint == 2: count = 0; elif keyint == 4: count = (count - 1) % 256 except KeyboardInterrupt: # exit on Ctrl^C sock_cmd.close() sock_button.close() sys.exit() except socket.error: print "Couldn't connect to dpdaemon" sys.exit()