Skip to content

Latest commit

 

History

History
256 lines (168 loc) · 11.5 KB

software.md

File metadata and controls

256 lines (168 loc) · 11.5 KB

Software package

Software package

The software package part of Cert3D will be PC-based application. It shall have the following features:

  • Have USB interface to the Cert3D hardware board and be able to download information.

  • (If possible) Interface with the printer directly to be able to send g codes and request status updates.

File organization

  • cert3d.py: this is the main file.

  • ScopeResultsWindow.py: This file is generated by wxFormBuilder and is the layout of the window.

  • ScopePanel.py: This is a custom widget to draw oscilloscope-type information on a panel.

  • Channel.py: This hold information about a singl channel, either digital or analog.

  • BilevelData.py: This holds data on a single channel of digital data.

  • PlotData.py: This holds xy data on a single channel.

ScopePanel.py

This widget inherits from wx.Panel and nicely displays a number of channels on a form. Data can either be bilevel or plot.

To add a channel, we use the Add(data) function where data is either (for now) BilevelData or PlotData.

Graphic layout

Channel names should be on the left-hand side of the widget. Each channel can have a custom height, with the minimum height being the height of the channel name.

The width of the channel name display shall be fixed to the minimum possible size and recalculated when a new channel is added.

ScopeChannel.py

This should hold aethestic information about the channel (color, height, scaling). It will also do the drawing.

Commands

The C3D board can be sent commands over USB. The following are implemented:

  • start
    • Begin streaming over the USB port.
  • stop
    • Stop streaming data.
  • info
    • Send the configuration packet over the USB port. Streaming must be disabled.

Info packet

The information packet contains information on how to interpret data coming over the USB. Note that some configuration is done over the USB.

  • char[9] start_string
    • The string InfoStart to denote the start of this packet.
  • uint32_t clock
    • System clock speed.
  • uint8_t signal_channel_count
    • Number of signal channels in the packet.
  • uint8_t adc_channel_count
    • Number of ADC channels in the packet.
  • For each signal channel:
    • uint32_t clock
      • Clock speed in Hz of this channel.
  • For each ADC signal:
    • float zero_value
      • Voltage value corresponding to a zero count value on the channel.
    • float high_value
      • Voltage value corresponding to a max count (4095) on this channel.
  • char[8] end_string
    • The string InfoStop to denote the start of this packet.

TriStateData

I added a third signal type: TriStateData. This is similar to the bilevel data, except it has a third state. The third state will be high-z and will display as grayed out over the entire range. The idea is to use this to simplify bilevel data with millions of points for zoomed out views.

Simplifying data

I expect to have massive data sets. If a STEP channel for a motor triggers at ~100kHz, that is 200,000 data points per second. One can see how this adds up. Drawing all data set points would be prohibitve.

To simplify bilevel data, we can approximate it using a

I also expect channels to be dormant for long periods of time. In a zoomed out view, it would be nice to be able to see period of inactivity and activity.

Data sets will be simplified by augmenting the data set with one or more simplified sets. These sets will have less points than the original and will be used when the data set is zoomed out.

How to simplify the data set?

Zoom value is a ticks/pixel value.

  1. Look at the original data set. If it has less than X (maybe 1000?) edges, do not simplify. Else, continue.

  2. Find the level of zoom at which we have no more than X (maybe 1000) edges per X (maybe asize(2000)?) pixels. This is our maximum zoom.

  3. Find the level of zoom to fit the entire data set within X (maybe asize(2000)?) pixels. This is our minimum zoom.

  4. Add zoom levels between min and max such that the zoom value jumps by no more than a factor of X (maybe 2.0)?

At this point, we have all zoom levels we need to create, with the most zoomed in level equal to the original data set.

Better idea:

  1. Find the maximum zoom level as in (2) above. Then, create a list of edge durations and sort it. Collapse each duration less than double the median edge duration. Calculate level of zoom for this new data set, and repeat until we have under 1000 edges in the set. This should only repeat a few times.

The problem with this is the "collapse a duration." Collapsing a single duration doesn't actuall achieve anything except reduce quality of the data set. We need to collapse two durations to reduce the number of edges. With that in mind, here is an improved algorithm:

  1. Find the maximum zoom level as in (2) above.

  2. Create a list of double edge durations (i.e. the duration between edge 0 and edge 2, edge 1 and edge, 3, edge 2 and edge 4, etc...). Take twice the median of this list. Collapse all durations which are less than this value.

  3. Note that collapsing a duration means setting the value from 0 or 1 to 2, and combining adjacent durations of value 2.

This works really well. I implemented it within a DataCluster object for now.

Object achitecture

We have a lot of different objects which are all related. Maybe we can get rid of some and make the heirarchy flatter? Maybe we can eliminate or consolidate some *Data types?

Where should all these objects live, in terms of files?

Here is our current file/object heirarchy:

  • File AnalysisWindowBase.py
    • Object AnalysisWindowBase
    • (Auto-generated from wxFormBuilder)
  • File cert3d.py
    • Object AnalysisWindow which inherits from AnalysisWindowBase
      • Inherits from AnalysisWindowBase
  • File ScopePanel.py
    • Object ScopePanel which inherits from wx.Panel
  • File ScopeChannel.py
    • Object Signal
    • Object ScopeChannel
  • File dpi.py
    • Contains methods for working with high (and low) DPI displays.
  • File BilevelData.py
    • Object DataCluster
    • Object BilevelData
    • Object TriStateData
  • File PlotData.py
    • Object PlotData

Here is our window object heirarchy

  • Object AnalysisWindow
    • Many standard objects like buttons, which are not listed here
    • Object ScopePanel
      • Many visual properties like zoom state, channel length, etc.
      • Time offset of start of display (in seconds)
      • Zoom state (in pixels per second)
      • Many ScopeChannel objects
        • channel height
        • (if necessary as for PlotData), value at bottom and top
        • Each can have many Signal objects
          • name of signal
          • color of signal
          • thickness of signal
          • reference to active data
          • function to select active data given a zoom factor
          • DataCluster object with min zoom state and data objects

I think I should eliminate the DataCluster object and turn it into a property of a Signal.

Here's what a Signal could look like:

  • Object Signal
    • property name for name of signal, displayed on left of the channel
    • property color of type wx.Colour
    • property thickness for thickness in pixels of the signal drawn
    • property active_data which points to the active data based on zoom level of type (BilevelData, TriStateData, or PlotData)
    • property data_cluster which is a list of (min_zoom, data)
      • property min_zoom is the minimum zoom at which this data can be active
      • property data is a data of type (BilevelData, TriStateData, or PlotData)
    • function draw_signal which draws the signal on a rectangle
      • parameter wx.DC
      • parameter wx.Rectangle
      • parameter pixels_per_second
      • parameter start_time which is the time at the very left of the rectangle
      • parameter value_range which is the value at the bottom and top of the rectangle. Only used for data of type PlotData.

The Signal object should have the following:

  • Parameter start_time for the start of the data time
  • Function get_length() which returns the length of the data

Each *Data type should have the following parameters/functions:

  • Parameter points which holds the data points in a format used only by itself.
  • Parameter start_time for the start of the data time
  • Function get_edge_near_time(time) which returns the time of the edge closest to the given time.

I did this, it seems way better. The cert3d.py file is a little busy, but workable.

Slave thread instructions

The slave thread is necessary because the buffers for the USB data on both the STM32 side and the computer side are small. In order to maximize throughput, the port needs to be read out very quickly. The slave this does this without any interruption from other things such as UI redrawing. Even with the PIL, this works well since most of the waiting is IO based.

The master thread needs to communicate with the slave. It does so through some global variables.

The master thread should be able to know when the slave thread has crashed. It can do so with this Thread.is_alive function.

Here are some instructions we need to implement:

  • Open USB port (or don't)
  • Start or stop logging to file, or read but ignore data
    • File is only open when logging is active
  • Exit thread

Postprocessing data

Once we have the STEP and DIR channels, we need to estimate the position, velocity and acceleration of the channel. This is a nontrivial exercise.

  • To calculate POS, assume each active edge on STEP is immediate.

  • After POS is calculated, post-process it by looking for regions of large idle time and add points to make the POS channel constant during the majority of these times. Use an idle time of 1ms.

    • For example, if we have data points (0.0 s, 0), (1.0 s, 1), add a point at (0.999s, 0).
  • Calculate VEL as the sum of many triangular pulses, the area of each is the same and the duration is varied based on nearby points. This may be tuned.

    • We need a way to calculate (x, y) points from a bunch of triangular pulses.

    • Put pulses in a list sorted by the endtime.

    • I did this. It works okay.

  • Calculate ACC as the exact derivative of VEL. Since VEL is piecewise linear, ACC is piecewise constant.

    • This produces some artifacts, which are real but undesired. One way to remove these would be to apply a slew rate control to the signal.

Saving g-code tests

In the Test Runner window, I need a method for saving many different tests. I'll have a combo box populated with the name of each test along with a Save and Delete buttons. They will be saved in the LOCALAPPDATA folder in the tests.py file in the following format:

test["basic_accel"] = ["G1 X0", "G1 X1"]
...

I can then use exec to read these back in.

I implemented this. It's nice.

Simplifying PlotData data

We need a way to create a simplified data set of out PlotData. Maybe we can have another Data type similar to PlotData, except it would have a range of values. I have two ideas.

  • For each interval, draw a rectangle with a stipple interior over the range of values it can have. This has one downside in that if the data is towards one side of the range, that information is lost in translation.

  • For each interval, find the mean value. Draw a line connecting these mean values. Also find the range of values and maybe draw a thick stippled line with this information.

After looking, there is a DrawPolygon function. I'm sure this could be useful.

How can I calculate somewhat tight-fitting min and max value curves?