DUDS
Distributed Update of Data from Something
Tools

Bit-Per-Pixel Image Compiler

The Bit-Per-Pixel Image Compiler (bppic) takes image data written in a text file and produces either C++ source code with data for use with the BppImage class, or an image archive file for use with BppImageArchive. The compiler is itself a C++ program.

Source file format

The source file format is intended to make it easy to represent simple images, but it does have a few peculiarities. There are three different parsing areas in the file: in-between areas, image headers, and image data.

In-between areas

These are the areas of the file that do not have any image data. They consist only of whitespace and comments. All comments begin with a slash ('/'). If the slash is followed by an asterisk ('*'), the comment will continue until the two characters are found in reverse. These comments are added to C++ output files. Otherwise, the comment will end at the end of the line and will not be added to the output file. Comments are not nested.

Comments may appear in the other two area types of the file as well.

Image headers

These announce the start of an image. An image header starts in an in-between area with a non-whitespace character that doesn't start a comment. The character is the start of the name of the image. If the compiler's output will be a C++ source file, the name must be a valid C++ identifier because it will be used in the generated source code.

For archive file output, the name may have any non-whitespace character, but cannot start with a slash. Backslashes are used as the start of an escape sequence, but only a limited set of sequences are currently supported:

  • A decimal number, not starting with zero, represents the character with the given code. For example, "\32" will put a space in the name. The number stops at the first non-decimal digit, whitespace, or before the number becomes too large for any valid character.
  • Tab using "\t".
  • Backslash using "\\".
    Doxygen 1.9.6 fails to parse and output the rest of this document correctly. Version 1.8.13 handled it correctly. Symptoms: the line of dashes above caused output of a literal user readable version of the horizontal rule tag ("<hr>"), and on one test run, generated a new sub-section tag for it, but no more HTML is generated until near the end. Enough with this bug and back to the documentation.

Names are stored using UTF-8, but only the decimal number escape sequence code has any code specific to UTF-8. This may change to make it easier to specify non-ASCII characters.

The name must be followed by any amount of any combination of whitespace and commas, then the width, followed by more whitespace and/or commas, and then the height. Following the height, all characters are ignored until an opening curly brace ('{'). That brace begins the image data.

Image data

Each pixel of the image is represented by a space for a clear (0) pixel, or by an alphabetic character or pound sign ('#') for a set (1) pixel. A slash ('/') starts a rest-of-line comment. Except for a comment, if any of these characters are encountered on a line, that line provides the next row of image data. A row of image data cannot extend to the next line in the text file. Any other characters encountered are ignored. This allows numbers to be added to assist with placing the pixels within the specified size of the image.

Characters for set pixels may not occur outside the image's width except inside a comment. Spaces, used for clear pixels, may occur outside the width; these will be ignored. Clear pixels do not need to pad out the width of the image. Unspecified pixels, both for width and height, will be clear. Characters for any pixels may not occur outside the height of the image.

The image is terminated with a closing curly brace ('}'). The text following it will be parsed as an in-between area.

Sample source

The same image twice, first with few comments, then heavily commented.

/* * (space between asterisks so Doxygen will let you see this)
* Tiny wireless LAN icon for use with HD44780 type text displays.
*/
WirelessLAN
5, 8
01234 {
0 // space in front of comment
1
2 XXX
3 X
4 XX X
5 X X
6 X X X
}
/*
* This comment will be found in C++ output files. The double-slash
* comments will not.
*/
WirelessLAN_commented // The name; must be unique in the source file.
5, 8 // Width then height.
Completely ignored text. Better style-wise to use comments.
Done so that a row of numbers can be added to label the columns
of the image data.
// No space before the start of the comment after '{', or it will
// be the first row of the image.
01234 {// the number on this line is ignored, '{' starts image data
0 // tab then space between '0' and '/'; only the space is important
1 // need the space after tab, or line is ignored, boo!
2 XXX // extra spaces OK
3 X // numbers at start are ignored
4 XX X // X marks darkened or brightened spots on display, depending
5 X X // on display specifics like LCD polarizer.
6 X a # // Can use other characters, but I like X best.
7 // don't actually need this line; unspecified data same as a space
} // end of image

Except for the width and height, the numbers in the code segment will be ignored. I find them useful for ensuring the image data fits in the specified dimensions. The row numbers at the start of lines are each followed with a tab. The image compiler ignores the tab character, but does not ignore spaces. Multiple tab characters may be used. If a line has neither a space nor an alphabetic character, it will be ignored. If the space before the comment is removed from rows 0 and 1 in the example, then the line marked row 2 will be used as the first row of the image. I'm not happy with this, but don't want to enforce a lack of empty lines and I'd like to keep the parsing code from becoming too complex.

Output

The C++ output is a const char array for each image with the same name as the image. It can be used as a header file, but must be included by only one source file. Changing the output to generate a source file and a header file could fix this. The output does not include any namespaces, which would be nice to support, but a namespace can be put around an include statement as a workaround.

The image archive file has the advantage of not requiring a new build to try out a change to an image. A BppImageArchive object can read in the file and provide shared pointers to the images. Lookups are done by the image name.

Digital Pin Configuration

Note
This really isn't a tool, but it should be documented separate from the implementing class and it isn't yet clear to me where else to put it.

The PinConfiguration class parses configuration data that has been put into a Boost property tree. The property tree library can parse text files in several forms. This documentation will show the files using the INFO form.

Once the parsing is complete, the data is available for inspection from data structures inside the PinConfiguration object. Before configured objects are created, a DigitalPort object must be constructed and attached to the PinConfiguration. After that, pre-made and configured DigitalPin, DigitalPinSet, ChipSelect, and ChipSelectManager objects are available through the PinConfiguration by name look-up.

Property tree code

The PinConfiguration class has a constructor and a parse() function that take a boost::property_tree::ptree object. This is the root of the tree that PinConfiguration will inspect. This allows the code to chose the boost::property_tree parser to use, and means that the parsed file may include items that PinConfiguration will not parse.

The following program demonstrates how to parse the property tree. It finds a sub-tree under the key "pins" and passes it to PinConfiguration. Other keys could exist at the root level for program-specific use; PinConfiguration will not see these items. The program will report errors in the configuration data.

#include <boost/property_tree/info_parser.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <iostream>
int main()
try {
// will hold the root of the parsed file
boost::property_tree::ptree tree;
// parse a file into the property tree
boost::property_tree::read_info("pins.conf", tree);
// find the subtree at the key "pins"
boost::property_tree::ptree &pinconf = tree.get_child("pins");
// use the subtree as the pin configuration
// place some useful code here
return 0;
} catch (...) {
std::cerr << "Program failed in main():\n" <<
boost::current_exception_diagnostic_information() << std::endl;
return 1;
}

Property tree file data

The above program will parse files like this one, which configures pins for use with a HD44780 display (long lines may wrap less than nicely):

pins {
ports {
default {
idoffset 0 ; this is the default; may be omitted
5 {
lcdData4
}
6 {
lcdData5
}
16 {
lcdData6
}
20 {
lcdData7
}
26 {
lcdText ; often labeled RS
}
1 {
lcdSelectPin
}
}
}
selects {
lcdSelMgr Pin {
pin lcdSelectPin
name lcdSelect
}
}
sets {
lcd {
pins {
lcdData4
lcdData5
lcdData6
lcdData7
lcdText
}
select lcdSelect
}
}
}

The semicolon character is used to mark the rest of the line as a comment.

Ports

The ports subtree describes the requirements for DigitalPort objects. PinConfiguration does not instantiate DigitalPort objects to avoid limiting what implementations may be used, so this subtree may describe less than what is available, but only what is described may be used in the rest of the configuration tree.

The port description starts with a name, which is "default" in this example. Any string may be used; whitespace can be included if the string is quoted. The name of each port must be unique among all the ports in the configuration, but may be the same as a name used for something other than a port.

An optional type string may follow the name of the port; this is not shown in the example. The value will be available in the typeval field of the port's configuration. This is intended to provide a hint as to what DigitalPort implementation should be used. Most programs likely will not need this, and may ignore the value. If the value is not specified in the configuration, the typeval field will contain an empty string.

Following the port name and optional type value is an opening curly brace that starts the subtree detailing the individual pins that the port is expected to have or not have. It may be in one of three forms. The example shows the form that is best to use in the general case.

5 { ; port specific pin ID
lcdData4 ; arbitrary pin name
}

The above form (same as the more complete example) starts with a port specific ID number for the pin. Which pin is selected is entirely dependent upon the DigitalPort implementation and the underlying hardware. The global pin ID, used by the software using the DUDS library, is not specified explicitly here. It's default value is the sum of the pin's port ID and the port's ID offset. With an ID offset of zero, the port ID and global ID of the pin are the same.

Inside the curly braces is an optional arbitrary name assigned to the pin for the purpose of making configuring the pins easier. A pin's names must be unique across all pins in the configuration. A PinConfiguration can be queried for DigitalPins and their configuration by name. Elsewhere in the configuration tree, a pin may be referenced by either its global ID, or by its name. Using a name makes it easier to rearrange the pin configuration, which can make it easier to support multiple hardware configurations or changing configurations during development.

The pin name may be omitted by not giving the subtree with the name. The example above would lack pin names, but otherwise be identical, if the port was specified like this:

default {
5
6
16
20
26
1
}

Because other parts of the example use pin names, those parts would also need modification to make a valid configuration. Please don't. The pain of the additional typing is less than the pain of dealing with all the global pin IDs.

There may be cases when a global pin ID needs to be specified. It can optionally be given immediately following the port's pin ID. Port implementations do not have to support explicit global pin IDs. If an implementation does not, specifying a global ID other than the default should be an error condition when configuring the port object. Since it is port specific, the error condition can only be checked when attempting to use the port configuration.

A specific pin ID may also be required to not have a pin. This is different from not mentioning a global ID in the configuration because the lack of a mention makes no requirement. If "none" is given immediately following the port specific ID, then attempting to use a DigitalPort that supplies a pin at that ID will cause a DigitalPortHasPinError exception to be thrown.

6 none ; port specific pin ID that must lack a usable pin

Selects

The selects subtree is used to define ChipSelectManager objects, along with ChipSelect objects in a parent-child style relationship. These objects abstract the common task of selecting a device using a digital output so that several different selection schemes can be used and easily changed.

selects {
lcdSelMgr Pin {
pin lcdSelectPin
name lcdSelect
select low ; optional, low is the default
}
}

The above snippet describes a chip select manager and one chip select. It starts with a name for the manager. The name must be unique across all select managers in the configuration. It is followed by the type of the manager. Different types require different data in their sub-trees. The types are:

Pin manager
This manager uses a single pin to select and deselect exactly one device. The example configuration sets the pin to use with the key "pin", and names the resulting ChipSelect object with the "name" key. The "select" key configures the logic state used to select the device. It can be given a value of 0 or "low", 1 or "high". The default is low.
PinSet manager
This manager uses multiple pins that each select and deselect exactly one device. Like all managers, one device at most can be selected at any given time. The manager is suitable for selecting devices that share resources, like pins connected to a shared data bus for communication.
pins {
ports {
default {
0 {
lcd0SelectPin
}
1 {
lcd1SelectPin
}
2 {
tempSelectPin
}
}
}
selects {
lcdSelectManager PinSet {
lcd0Sel lcd0SelectPin ; short form; makes ChipSelect lcd0Sel
lcd1Sel { ; makes ChipSelect lcd1Sel
pin lcd1SelectPin ; uses this pin for the select line
}
tempSel {
pin tempSelectPin
select high ; select state; default is low
}
}
}
}
Multiplexer manager
This manager selects a chip by outputting a number in binary using several pins. The number zero is used for no selection. It takes a list of pins to use in order from the least to most significant bit in the "pins" key. All pins must come from the same port. The "selects" key has list of chip selects with the key as the unique name, and the value with the number to output to select the corresponding device
pins {
ports {
default {
17 {
bit0
}
27 {
bit1
}
22 {
bit2
}
}
}
selects {
muxSel Multiplexer {
pins { ; configures output pins to use
bit0 ; LSb
bit1 ; sequence, not name, determines bit position
bit2 ; MSb
}
selects { ; configuration for ChipSelect objects
; name-value pairs; value is number to output; must be > 0
SelectA 2
SelectB 3
SelectC 7
}
}
}
}
Binary manager
This manager selects one of two devices by toggling the output state of a single pin. This means that a device is selected at all times. Not all devices will work properly with this manager. The pin to use is configured with the "pin" key. The "high" and "low" keys name a chip select for the high and low logic states, respectively. The "init" key selects the initial logic state, and the output state to use when the software isn't attempting to select either device. The values 0 or "low", and 1 or "high" are accepted.
pins {
ports {
default {
25 {
binSelPin
}
}
}
selects {
binSel Binary {
pin binSelPin
low SelectLow
high SelectHigh
init low
}
}
}

Pin sets

DigitalPinSet objects are used to atomically claim multiple pins for use. While DigitalPinSets do not have a chip select, the configuration optionally allows a pin set to be associated with a select. This assists working with hardware that uses multiple control lines or a parallel data bus along with a chip select. Use the getPinSetAndSelect() function to look up a pin set and its associated chip select.

sets {
lcd {
pins {
lcdData4 ; LSb, set position 0
lcdData5
lcdData6
lcdData7
lcdText ; MSb, set position 4
}
select lcdSelect ; optional; may be omitted
}
}

Each pin set is given an arbitrary name that must be unique among all sets in the configuration. In the example, the name is "lcd". Inside the set's sub-tree is the key "pins". It contains the list of pins that the set will operate upon. All pins must come from the same port. The optional key "select" names a chip select object that will be associated with the set.

If a set lacks an associated chip select, then it may optionally be written in an abbreviated form. This form lacks the "pins" key, and has the list of pins as a direct child of the set:

sets {
lcd {
lcdData4 ; LSb, set position 0
lcdData5
lcdData6
lcdData7
lcdText ; MSb, set position 4
}
}