For some time, I have been working in small portions on Dispatch as a serial library for the curve tracer. Initially, I thought that the serial library would be limited in scope to that particular project, but I began to see that the library could, potentially, be very useful on many of my past microcontroller projects.
As the C and Python modules have matured into Dispatch
(C) and serialdispatch
(Python), so,
too, has my vision of what this library could potentially achieve in the future with relatively
little effort. I will present this vision in this post.
The Dispatch libraries, as they are, allow the user to define any topic to which data may be published and consumed. This in itself is quite useful. Data may be sent and received easily and simply for any purpose.
The next level will be to create standard topics to which to publish. When data is written to any of these topics, then the module would automatically bring up the appropriate display type and, then, display the data. We wish to turn standard C strings into a sort of scripting command for the PC to interpret and display with no further user intervention!
Why do this? Couldn't anyone write any program in Python/Ruby/Java/
In essence, the visualization will be controlled by the C code that is publishing to it with the end user not having to write a single line of Python or other in order to see his/her data.
We will create a standard topic format which should - ideally - be extendable and easy to use.
Currently, subscriptions are expecting to find the exact string that is to be sent as the topic, nothing
more and nothing less. For instance, data sent to the topic plot
will have different subscribers
from data sent to the topic plot
(there is an extra space in there). As a result, we will need to
add a new type of subscriber to the currently existing serialdispatch which will subscribe if the first
few characters match a defined pattern. In this case, plot
. I suspect that we will call this new
subscriber method something like subscribe_to_prefix
or subscribe_to_command
. This will not affect
the functionality of the normal subscribe
and will be 100% backwards compatible.
The standard format will be space-delimited (recall that we have already utilized commas and colons) in the dispatch format). We will use the somewhat-familiar command-line syntax of higher-level OSs. Fortunately, we won't have to interpret them in C but in the more dynamic Python code.
DIS_publish("<command> <option1> <option2> ... <optionX>", &dataPtr1, &dataPtr2, ..., &dataPtrX)
At the moment, I can only think of a couple of commands that would be most useful. This list is sure to expand:
Command | Description |
---|---|
plot | Plots the data according to its options. |
log | Log the data according to its options. |
csv | Save the data to a CSV file. |
Option | Short Equivalent | Description |
---|---|---|
--name 'name in single quotes' | -n 'name in single quotes' | Applies the data to a named plot. If this is not present, the named plot will be 'default'. Writing data to the 'default' plot will have the same effect. |
--dots | -d | The data will be plotted using dots. |
--lines | -l | The data will be plotted using lines. |
--polar | -p | The plot will be a polar plot. If the plot is currently a different type of plot with existing data on it, then that plot will be wiped and the new dataset will be applied. |
--keep-data | -k | Append the data contained herein to the existing data set. |
--xLabel 'x-axis label in single quotes' | -x 'x-axis label in single quotes' | The label for the x-axis. |
--yLabel 'y-axis label in single quotes' | -y 'y-axis label in single quotes' | The label for the y-axis. |
We will begin with the most basic example and expand on that. If you haven't read the Dispatch introductory material on Dispatch, you may experience some confusion as this material will not be explained in detail here.
These examples are provided as a vision of capability. As happened with Dispatch, this vision of the implementation will likely morph in the future, leaving this blog post behind, but the goal will remain the same.
uint8_t xData = 1;
uint16_t yData = 2;
DIS_publish("plot,u8,u16", &xData, &yData);
// ^ ^ data addresses
// ^ ^ format specifiers
// ^ command
The simplest plot type. When no additional parameters are supplied, then the first data point is plotted on x and the second data point is plotted on y. If data continues to be published in this manner, the points will continually be added to the plot. If you don't understand the 'u8' and 'u16' notation, go back to the vision posts on Dispatch for a review.
If the plot type is not specified using the --dots
, --lines
, or --polar
notation, then the
plot type is assumed to be --dots
.
The above works great when only one plot is required... but what if there are multiple parameters that need to be plotted?
uint8_t xData = 1;
uint16_t y1Data = 2;
uint16_t y2Data = 3;
DIS_publish("plot,u8,u16,u16", &xData, &y1Data, &y2Data);
// ^ ^ ^ data addresses
// ^ ^ ^ format specifiers
It is always assumed that all scatter data has the first element along the x axis and all addional elements on the y axis.
The primary reason for creating Dispatch in the first place and not simply using the Telemetry library was so that we could send multi-dimensional data of arbitrary length. In other words, we want to send arrays of data, not just single data points:
uint8_t xData[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
uint16_t y1Data[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
uint16_t y2Data[10] = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29};
DIS_publish("plot:10,u8,u16,u16", xData, y1Data, y2Data);
// ^ ^ ^ data addresses
// ^ number of elements in each array
Note that this is basically the same command with the :10
attached and the &
removed from the
data addresses since the addresses are now arrays (if you don't understand this, search 'C arrays
pointers', enjoy the reading).
In some cases, it is useful to name the data appropriately. In this case, we will use single-quotes to surround the data label.
uint8_t xData[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
uint16_t y1Data[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
uint16_t y2Data[10] = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29};
DIS_publish("plot --x 'time' --y 'adc':10,u8,u16,u16", xData, y1Data, y2Data);
// ^ ^ axis labels
// ^ ^ axis label option
The --x
and --y
stand for '--xLabel' and '--yLabel`, but the shortened notation allows for
somewhat shorter transmissions. Both are valid.
We can use the --name
option to name the plot. If more than one named plot has data sent to it,
then each named plot will have its own plot window allocated to it.
uint8_t time[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
uint16_t adc1[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
uint8_t samples[5] = {0, 1, 2, 3, 4};
uint16_t adc2[5] = {5, 6, 7, 8, 9};
DIS_publish("plot --name 'adc vs. time'", time, adc1);
DIS_publish("plot --name 'adc vs. samples'", samples, adc2);
// ^ plot name in single quotes
// ^ plot name option
The default behavior is that every time the new data is received for a particular plot, the old
data is deleted/overwritten. Using the --keep-data
option will ensure that old data is
retained for display. It may be useful to have a 'clear' button on the GUI to clear the screen
when necessary.
At times, the user may wish to simply log the data.
uint8_t xData[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
uint16_t y1Data[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
uint16_t y2Data[10] = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29};
DIS_publish("log:10,u8,u16,u16", xData, y1Data, y2Data);
// ^ log command
The default behavior for log
is to simply display the data to a console as a series of comma-
separated values.
Sometimes it is most efficient to simply log data directly to a CSV file for later processing. This is particularly true for higher-volume data. For this, we can supply a relative path for the data:
uint8_t xData[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
uint16_t y1Data[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
uint16_t y2Data[10] = {20, 21, 22, 23, 24, 25, 26, 27, 28, 29};
DIS_publish("csv:10,u8,u16,u16", xData, y1Data, y2Data);
// ^ csv command
As you can see, we are basically attempting to turn our micro-controller into a plot-scripter for the PC. I'm sure that getting all of this accomplished will take some real time, but some of the effort is already in-progress within the curve-tracer project. With a nice debugging tool like this, bringing up ADCs and troubleshooting waveforms should become much easier for the embedded engineer. I will start work on this project as the curve-tracer becomes a more mature platform, particularly as it will become a development platform for this effort.
Until next time!