|
DUDS
|
Distributed Update of Data from Something
|
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.
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.
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.
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:
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.
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.
The same image twice, first with few comments, then heavily commented.
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.
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.
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.
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.
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):
The semicolon character is used to mark the rest of the line as a comment.
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.
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:
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.
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.
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:
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.
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: