3. Interface Control Document

3.2. Revision Log

Table 3.1 Revision Log
Date Author Changes
2017/04/04 WHF Added FIRMWARE_GET_CRC32 command description.
2017/03/22 WHF Added GET_WELL_FAULTS command description.
2017/3/3 MCR Added this revision log.
2018-06-21 WHF Document pixel encoding in bulk USB stream.

3.3. Introduction

This document describes the communication protocols used between the DMS controller (hereafter simply referred to as the DMS), and the host computer. It partially replicates information found in the header file, dmsCodes.h, found in the ‘Shared’ area of the source code tree. Every effort has been made to make these two documents agree, but in the case of disagreement dmsCodes.h, and thus the source code, prevail.

3.4. USB Interface

Communication between the DMS and the host PC occurs over a USB 2.0, Full speed (12 Mbps) connection. Creare recommends, and our example code utilizes, Microsoft’s WinUSB driver system, but the interface is generic USB and so any low level USB access library, such as lib-usb, could be used.

The vendor ID is currently 0xABCD. This is a bogus vendor ID, because at this time Creare is not registered with the USB Implementers Forum. However, it is adequate for non-public device deployment. The product ID is 0x7819.

Two endpoints are defined. Endpoint 0 is the standard control endpoint required for all USB devices. The DMS makes use of this endpoint to receive commands and return results.

Endpoint 1 is a standard bulk endpoint. This endpoint is used to stream raw data samples from the DMS to the host, for example in alignment mode. The DMS has no support for bulk streaming to the device.

Note that in all cases, all data elements are transmitted and received in LSB (little endian) format.

3.5. Endpoint 0 Commands

The DMS supports the commands shown in Table 3.2. Note that these are ‘Vendor’ Device Requests; refer to Chapter 9 of the USB 2.0 specification. A ‘Read’ is defined here as the DMS sending control data back to the host (Device-to-host); a ‘Write’ is defined as the host sending additional data along with the command (Host-to-device). If the length is 0 the direction is immaterial. The function HI(x) takes the high order 16-bits of a 32-bit word x. Likewise, LO(x) takes the low order 16-bits.

Table 3.2 Supported Commands Summary
Command Description R/W bRequest wValue wIndex wLength
STATUS Controller condition R 0x01 0 0 12
ID Build date and unique ID R 0x02 0 0 48
CONFIG_GET Read configuration data R 0x03 0 0 24
CONFIG_SET Write configuration data W 0x04 0 0 24
CALIBRATE Run a calibration process R 0x05 1, 2, or 3 0 0
GET_CALIBRATION Get calibration results R 0x06 0 0 1688
STORE_CALIBRATION Write current calibration to non-volatile memory R 0x07 0 0 0
MONITOR_DISPENSE Begin monitoring R 0x08 0 0 0
GET_DISPENSE_DATA Return results from monitoring R 0x09 HI(offset) LO(offset) (up to 4096)
CLEAR_HISTORY Remove the accumulated previous results used as reference R 0x0B 0 0 0
SET_REFERENCE_DISPENSE Set the last result as a baseline for comparison R 0x0C 0 0 0
GET_WELL_FAULTS Get data corresponding to well faults R 0x0D 0 offset (up to 4096)
STREAM Start / stop streaming of raw data from sensor R 0x10 0 or 1 0 0
FIRMWARE_BLOCK Send a block of firmware data W 0x20 HI(offset) LO(offset) (up to 512)
FIRMWARE_PROGRAM Cause the DMS to rewrite its FLASH based on the blocks sent R 0x21 HI(size) LO(size) 0
FIRMWARE_GET_CRC32 Cause the DMS to compute the 32-bit CRC of the firmware it has received R 0x22 HI(size) LO(size) 4

The commands are described in more detail below.

3.6. Status

In response to this command, the DMS returns its current state of operation. This command should be used to poll the DMS (~10 Hz) to determine the result of other commands.

Table 3.3 Manual Control Data
Data Item Field Lengths (bytes)
State (u32) 4
Flags (u32) 4
Last Reported Error (u32) 4

The following states are currently defined.

Table 3.4 Controller States
Code State
0 OFF
1 INITIALIZATION
2 READY
3 CALIBRATION
4 MONITOR
5 STREAM

The following flag bits are defined.

Table 3.5 Controller Status Bit Field
Bits Status
0-3 RESERVED
4 Configuration Fail (Hardware Error)
5-19 RESERVED
20 Configuration Using Defaults
21-31 RESERVED

The following error codes are currently defined.

Table 3.6 Error Codes
Code Mnemonic Suggested Display Setting
0 DMS_ERR_NONE No error.
1 DMS_ERR_SENSOR_NOT_DARK Sensor is not dark.
2 DMS_ERR_INSUFFICIENT_BG_ILLUM Insufficient background illumination.
3 DMS_ERR_INSUFFICIENT_PEAKS 8 peaks not found in calibration image.
4 DMS_ERR_CAL_NOT_CENTERED Calibration not centered on sensor.
5 DMS_ERR_ILLEGAL_STATE The DMS cannot accept that command at this time.
6 DMS_ERR_UNSUPPORTED_OPERATION The DMS does not support that operation.
7 DMS_ERR_MEMORY The DMS has insufficient memory to store the desired number of signals.
8 DMS_ERR_NO_VALID_REFERENCE User reference mode was indicated, but no user reference has been set.
9 DMS_ERR_STREAM_DIAMETER_UNSUPPORTED No thresholds are defined for this stream diameter.
10 DMS_ERR_NO_RECENT_HISTORY No dispense exists in the history to use for the user reference.
11 DMS_ERR_CALIBRATION_INVALID The data in calibration contain errors.
12-255 RESERVED  

3.7. ID

This command causes the DMS to return the build date and time of its firmware, the unique ID associated with the processor, and the engineering version number.

Table 3.7 Identification Data
Data Item Format Field Length (bytes)
Build Date / Time String(Mmm dd yyyy hh:mm:ss) 24
Unique ID u32 x 4 16
Engineering Version Number String, e.g. ‘1.0’ 8

3.8. Config Get

In response to the CONFIG_GET command, the DMS will return the current value of all configuration parameters. See Table 3.8.

Table 3.8 Configuration Data
Data Item Field Length (bytes)
Stream Diameter mils, (u32) 4
Number of Dispenses (u32) 4
Dispense Time, msec (u32) 4
Dispense Period, msec (u32) 4
N Ref History (u32) 4
Ref Mode (u32) 4

Please refer to the Configuration Parameters in the Firmware Specification for the exact usage of these parameters.

3.9. Config Set

The host may overwrite the configuration parameters with this command. The data provided should exactly match Table 3.8.

3.10. Calibrate

This command requests the DMS to perform a calibration operation. The DMS must be in the READY state. There are currently three operations defined, which the host may select via the wValue control parameter; see :numref: Calibration Operations.

Table 3.9 Calibration Operations
Operation wValue
RESERVED 0
Get Dark Level 1
Set Background 2
Calibrate 3
RESERVED 4-65535

See Calibration in the Firmware Specification for the full details of these operations.

After the operation is commanded, Status should be polled to observe the return of the DMS to the READY state. If the error code is zero, then the calibration operation was successful; otherwise there was a problem that requires operator attention.

3.11. Get Calibration

The DMS returns the current calibration in response to this command. This is primarily for debugging purposes. The calibration has the format shown in Table 3.10.

Table 3.10 Calibration Data
Data Item Field Length (bytes)
Dark Level (u16) 2
Background (384 * u16) 768
Pixel Range (2 * u16) 4
Bin Edges (9 * u16) 18
Cal Image (384 * u16) 768
Cal Center (8 * f32) 32
Cal Sigma (8 * f32) 32
Cal Amp Scale (8 * f32) 32
Cal Lateral Scale (8 * f32) 32
Cal Sigma Scale (8 * f32) 32

Please refer to the Calibration section of the Firmware Specification for the meaning of these calibration elements.

3.12. Store Calibration

After the calibration is computed, it is held in RAM only. After this command is executed the calibration is written to non-volatile memory.

3.13. Monitor Dispense

This command instructs the DMS to begin monitoring the sensor for a dispense operation. Assuming it was in the READY state when the command was received, it will proceed to the MONITOR state, and wait for triggers. When the DMS has returned to the READY state, the results are ready and can be requested.

3.14. Get Dispense Data

After monitoring is complete, this command causes the DMS to emit the last results of the operation. 4096 bytes may be captured with each read, which means that some fields (‘signals’, in particular) will require several reads to capture fully. To use this command most effectively, instantiate an instance of the DMS_DISPENSE_DATA structure. Then, to receive a particular field, use code similar to the following:

uint32_t offsetBytes = offsetof(dmsDispenseData.info);
ctrl_read(
                DMS_CMD_GET_DISPENSE_DATA,         // bReq
                HI(offsetBytes),                   // wValue
                LO(offsetBytes),                   // wIndex
                &dmsDispenseData.info,             // data
                sizeof(dmsDispenseData.info)       // nBytes
);

To receive larger blocks, simply loop, incrementing offsetBytes each time by the number of characters received.

3.15. Clear History

When calculation features used for fault detection, several features are normalized by the median of results from previous plates. This is useful when the baseline has changed, for example due to the replacement of the dispense cartridge.

3.16. Set Reference Dispense

If the ‘Ref Mode’ configuration parameter is non-zero, a user reference will be used. This command makes the most recent dispense the user reference.

3.17. Get Well Faults

This command is used to return the data corresponding to well faults. For each dispense in the monitor event (available through n_dispenses in the configuration data structure) there will be eight u32s, each corresponding to one channel. Each two bits correspond to a fault indication from the Fault Categories table. The two LSB correspond to Fault Description 1, the next two to Fault Description 2, up to Fault Description 15. The upper two MSB should be zero after a successful monitoring operation.

Each two bits correspond to a fault severity level: 0, no fault; 1, notice; 2, warning; and 3, error.

3.18. Stream

This command controls raw data streaming. The wValue parameter controls the streaming: 0 halts streaming, 1 begins streaming, and all other values are reserved. After receiving this command with a wValue of 1, the DMS will begin streaming the raw data from the sensor to the host at the full, 1kHz rate, on Bulk Transfer endpoint 1. The DMS will stream packets of the form described in Table 3.11 until commanded to stop. Note the DMS must be in the READY state to use this command.

Table 3.11 Stream Packet Format
Data Item Field Length (bytes)
Header (u32) 4
Packed Samples (u32) 768 (512 pixels, 12 bits per pixel, packed into 192 32-bit words)

This picture illustrates the mapping of bits within the 8-bit bytes of the stream data to the corresponding bits within the equivalent stream of 12-bit pixels.

_images/Mapping_of_Stream_Data_Bytes_to_Pixels.png

However, to implement in this way would be naïve. Modern processors are much more efficient with 32-bit word sizes, and so we implement on that block size. The resulting mapping is straightforward.

_images/Mapping_of_Stream_Words_to_Pixels.png

The header consists of two parts. The upper 16 bits holds the value 0x781C, which may be used to scan for the start of a packet if synchronization is lost. The lower 16 bits contain trigger status. Bit 0 is the pump trigger, while bit 1 is the plate trigger. Bits 2 through 15 are reserved.

3.19. Firmware Block

In order to update the firmware on the DMS, it needs to be transferred to the DMS’s memory using this command. Up to 512 bytes can be sent with each transfer. The offset of each block in bytes are the wValue and wIndex parameters. Only send binary program files (e.g. DMS.bin).

3.20. Firmware Program

Once the program has been successfully transmitted to the DMS in its entirety, this command may be executed to copy the program from RAM into the device’s FLASH. The device will then be rebooted.

3.21. Firmware Get CRC-32

Once the program has been successfully transmitted to the DMS in its entirety, in response to this command the DMS will compute and transmit the CRC-32 of the bytes which have sent which comprise the Firmware. This can be compared against the local CRC of the firmware as a verification step before programming it to the DMS’s FLASH.

3.22. dmsCodes.h

The current version of dmsCodes.h is included here for reference. Note the horizontal scroll bar at the bottom, if you need to see the end of the lines. Sorry, this is a pain!

/*
  dmsCodes.h
  
  Declarations of codes common to the DMS and host devices.
  
  2016-08-23  WHF  Created.
*/

#ifndef __DMSCODES_H__
#define __DMSCODES_H__

#include <stdint.h>

//********************************  Constants  *******************************//
// To be replaced with Creare's vendor ID:
#define DMS_VID                                                      0xABCD

// Product ID for the Droplet Measurement System.
#define DMS_PID                                                      0x7819

// The number of pixels in the sensor.  The real number is twice this; 
//  every two pixels are averaged.
#define DMS_N_PIXELS                                                    512

// The number of bins to use for determining characteristics.
#define DMS_N_BINS                                                        8

// The maximum number of dispense events on a plate.
#define DMS_MAX_DISPENSE                                                192

// The rate at which frames are sampled from the sensor.
#define DMS_FRAME_RATE                                                 1000

// The maximum number of signals to process.  This is limited by the 
//   physical size of the memory.
#define DMS_MAX_SIGNALS                                              348180

// The maximum number of history dispenses that can be stored.
#define DMS_MAX_HISTORY                                                  10

// Note header changed for Rev. C.
#define DMS_STREAM_HEADER_MAGIC                                  0x781C0000

#define DMS_STREAM_HEADER_MAGIC_MASK                             0xFFFF0000


/////  Command Codes  /////
#define DMS_CMD_STATUS                                                 0x01
#define DMS_CMD_ID                                                     0x02
#define DMS_CMD_CONFIG_GET                                             0x03
#define DMS_CMD_CONFIG_SET                                             0x04
#define DMS_CMD_CALIBRATE                                              0x05
#define DMS_CMD_GET_CALIBRATION                                        0x06
#define DMS_CMD_STORE_CALIBRATION                                      0x07
#define DMS_CMD_MONITOR_DISPENSE                                       0x08
#define DMS_CMD_GET_DISPENSE_DATA                                      0x09
#define DMS_CMD_RESET                                                  0x0A
#define DMS_CMD_CLEAR_HISTORY                                          0x0B
#define DMS_CMD_SET_REFERENCE_DISPENSE                                 0x0C
#define DMS_CMD_GET_WELL_FAULTS                                        0x0D
#define DMS_CMD_GET_BACKGROUND                                         0x0E

#define DMS_CMD_STREAM                                                 0x10

#define DMS_CMD_FIRMWARE_BLOCK                                         0x20
#define DMS_CMD_FIRMWARE_PROGRAM                                       0x21
#define DMS_CMD_FIRMWARE_GET_CRC32                                     0x22

#define DMS_CMD_GET_FAULT_THRESHOLDS                                   0x30
#define DMS_CMD_SET_FAULT_THRESHOLDS                                   0x31
#define DMS_CMD_DELETE_FAULT_THRESHOLDS                                0x32


/////  Calibration Command Sub-Codes  /////
typedef enum tag_cal_mode {
  DMS_CAL_OFF,
  DMS_CAL_GET_DARK_LEVEL,
  DMS_CAL_SET_BACKGROUND,
  DMS_CAL_CALIBRATE
} dms_cal_mode_t; 

/////  Flags  /////
//  The first 16 are hardware errors; the last 16 are warnings.
#define DMS_FLAGS_EEPROM_FAIL                                   0x00000010U
#define DMS_FLAGS_NO_VALID_REFERENCE                            0x00010000U
#define DMS_FLAGS_CONFIG_DEFAULTS                               0x00100000U
#define DMS_FLAGS_FAULT_THRESH_DEFAULTS                         0x00200000U


/////  Error Codes  /////
typedef enum tag_dms_err {
  DMS_ERR_NONE,
  DMS_ERR_SENSOR_NOT_DARK,
  DMS_ERR_INSUFFICIENT_BG_ILLUM,
  DMS_ERR_INSUFFICIENT_PEAKS,
  DMS_ERR_CAL_NOT_CENTERED,
  
  DMS_ERR_ILLEGAL_STATE,
  DMS_ERR_UNSUPPORTED_OPERATION,
  DMS_ERR_MEMORY,
  DMS_ERR_NO_VALID_REFERENCE,
  DMS_ERR_STREAM_DIAMETER_UNSUPPORTED,
  DMS_ERR_NO_RECENT_HISTORY,
  DMS_ERR_CALIBRATION_INVALID,
  
  DMS_ERR_THRESHOLD_TABLE_FULL,
  
  DMS_N_ERRORS
} dms_err_t;


//**********************************  Types  *********************************//
#include <pshpack4.h>

typedef uint32_t dms_timestamp_t;      // type used for recording event times

typedef struct tag_dms_status {
  uint32_t
    state,                 // Current controller state
    flags,                 // Bit flags set
    lastError;             // Result of the last command
} DMS_STATUS;

typedef struct tag_dms_id {
  char build_date_time[24];  // Mmm dd yyyy hh:mm:ss  (20 char, plus 4 nulls)
  uint32_t unique_id[4];     // from the processor
  char engineering_version_number[8];  // null terminated string, e.g. "1.0.0"
} DMS_ID;

typedef struct tag_dms_config {
  uint32_t
    stream_diameter_mils,
    n_dispenses,
    dispense_time_msec,
    dispense_period_msec,
    n_ref_history,
    user_ref_mode,            // non-zero if in user mode
    trigger_delay_msec,
    background_mode;          // non-zero if calibration background is used
} DMS_CONFIG;

#include <poppack.h>

#include <pshpack2.h>

typedef struct tag_dms_calibration {
  uint16_t
    dark_level,
    cal_background[DMS_N_PIXELS],
    cal_pix_range[2],
    cal_bin_edges[DMS_N_BINS + 1];  // the left edge of bin, plus last right
    
  int16_t 
    cal_image[DMS_N_PIXELS];
    
  float
    cal_center[DMS_N_BINS],
    cal_sigma[DMS_N_BINS],
    cal_amp_scale[DMS_N_BINS],
    cal_lateral_scale[DMS_N_BINS],
    cal_sigma_scale[DMS_N_BINS];
} DMS_CALIBRATION;
#include <poppack.h>


#include <pshpack4.h>
typedef struct tag_dms_signals {
  float
    signal_amp[DMS_N_BINS],
    signal_disp[DMS_N_BINS],
    signal_width[DMS_N_BINS];
} DMS_SIGNALS;

typedef struct tag_dms_triggers {
  dms_timestamp_t
    begin_dispense,
    end_dispense;   
} DMS_TRIGGERS;

typedef struct tag_dms_info {
  uint32_t
    background_warnings;
} DMS_INFO;

// Macros to generate bit-masks for warnings: 
#define DMS_INFO_BACKGROUND_WARNING_RATIO(c)                     (1 << (c))
#define DMS_INFO_BACKGROUND_WARNING_LOW(c)                    (1 << (c)+16)

// These features are defined for each well (n_bins * n_dispenses elements)
typedef struct tag_dms_features { 
  float
    disp_mean,
    disp_sdev,
    width_mean,
    width_sdev,
    width_mean_n,
    amp_mean_btw,
    amp_mean_dur_n,
    amp_mean_dur,
    amp_corr; 
} DMS_FEATURES;

typedef struct tag_dms_bin_features {
  DMS_FEATURES bins[DMS_N_BINS];
} DMS_BIN_FEATURES;

typedef struct tag_dms_all_features {
  DMS_BIN_FEATURES
    plate_features,
    channel_features[DMS_MAX_DISPENSE];
} DMS_ALL_FEATURES;

typedef struct tag_dms_faults {
  uint32_t well_faults[DMS_MAX_DISPENSE][DMS_N_BINS];
} DMS_FAULTS;

typedef struct tag_dms_dispense_data {
  uint32_t signalCount;
  DMS_SIGNALS signals[DMS_MAX_SIGNALS];
  DMS_TRIGGERS triggers[DMS_MAX_DISPENSE];
  DMS_INFO info;
  DMS_ALL_FEATURES features;
  DMS_FAULTS faults;
  DMS_BIN_FEATURES reference;
} DMS_DISPENSE_DATA;

typedef struct tag_dms_stream_out {
  uint32_t 
    header,     // magic value, and trigger state
    // Buffer for samples, packed 12 bits at a time into 32-bit words.
    packedSamples[DMS_N_PIXELS * 12 / 32];
} DMS_STREAM_OUT;

typedef struct tag_dms_fault_det_param {
  float thresholds[3]; // corresponding to notice, warning, and error.
} DMS_FAULT_DET_PARAM;

typedef struct tag_dms_fault_det_thresholds {
  DMS_FAULT_DET_PARAM
    amp_corr_u,   
    amp_mean_dur_n_u,
    amp_mean_dur_n_l,
    amp_mean_dur_min,
    amp_mean_btw_u,
    disp_mean_lu,
    disp_sdev_u, 
    width_mean_n_lu,
    width_mean_u,
    width_mean_l,
    width_sdev_u;
} DMS_FAULT_DET_THRESHOLDS;


#include <poppack.h>

#endif /* __DMSCODES_H__ */