Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: mbed FastIO FastPWM USBDevice
Fork of Pinscape_Controller by
Revision 52:8298b2a73eb2, committed 2016-03-05
- Comitter:
- mjr
- Date:
- Sat Mar 05 00:16:52 2016 +0000
- Parent:
- 51:57eb311faafa
- Child:
- 53:9b2611964afc
- Commit message:
- New calibration procedure - attempt #1, with separate calibration release sensingi
Changed in this revision
--- a/USBJoystick/USBJoystick.cpp Tue Mar 01 23:21:45 2016 +0000
+++ b/USBJoystick/USBJoystick.cpp Sat Mar 05 00:16:52 2016 +0000
@@ -94,7 +94,58 @@
return writeTO(EP4IN, report.data, report.length, MAX_PACKET_SIZE_EPINT, 100);
}
-bool USBJoystick::updateExposure(int &idx, int npix, const uint8_t *pix)
+bool USBJoystick::sendPlungerStatus(
+ int npix, int edgePos, int dir, uint32_t avgScanTime, uint32_t processingTime)
+{
+ HID_REPORT report;
+
+ // Set the special status bits to indicate it's an extended
+ // exposure report.
+ put(0, 0x87FF);
+
+ // start at the second byte
+ int ofs = 2;
+
+ // write the report subtype (0) to byte 2
+ report.data[ofs++] = 0;
+
+ // write the number of pixels to bytes 3-4
+ put(ofs, uint16_t(npix));
+ ofs += 2;
+
+ // write the shadow edge position to bytes 5-6
+ put(ofs, uint16_t(edgePos));
+ ofs += 2;
+
+ // write the flags to byte 7
+ extern bool plungerCalMode;
+ uint8_t flags = 0;
+ if (dir == 1)
+ flags |= 0x01;
+ else if (dir == -1)
+ flags |= 0x02;
+ if (plungerCalMode)
+ flags |= 0x04;
+ report.data[ofs++] = flags;
+
+ // write the average scan time in 10us intervals to bytes 8-10
+ uint32_t t = uint32_t(avgScanTime / 10);
+ report.data[ofs++] = t & 0xff;
+ report.data[ofs++] = (t >> 8) & 0xff;
+ report.data[ofs++] = (t >> 16) & 0xff;
+
+ // write the processing time to bytes 11-13
+ t = uint32_t(processingTime / 10);
+ report.data[ofs++] = t & 0xff;
+ report.data[ofs++] = (t >> 8) & 0xff;
+ report.data[ofs++] = (t >> 16) & 0xff;
+
+ // send the report
+ report.length = reportLen;
+ return sendTO(&report, 100);
+}
+
+bool USBJoystick::sendPlungerPix(int &idx, int npix, const uint8_t *pix)
{
HID_REPORT report;
@@ -107,13 +158,6 @@
// start at the second byte
int ofs = 2;
- // in the first report, add the total pixel count as the next two bytes
- if (idx == 0)
- {
- put(ofs, npix);
- ofs += 2;
- }
-
// now fill out the remaining bytes with exposure values
report.length = reportLen;
for ( ; ofs < report.length ; ++ofs)
@@ -123,50 +167,6 @@
return sendTO(&report, 100);
}
-bool USBJoystick::updateExposureExt(
- int edgePos, int dir, uint32_t avgScanTime, uint32_t processingTime)
-{
- HID_REPORT report;
-
- // Set the special status bits to indicate it's an extended
- // exposure report.
- put(0, 0x87FF);
-
- // start at the second byte
- int ofs = 2;
-
- // write the report subtype (0) to byte 2
- report.data[ofs++] = 0;
-
- // write the shadow edge position to bytes 3-4
- put(ofs, uint16_t(edgePos));
- ofs += 2;
-
- // write the flags to byte 5:
- // 0x01 -> standard orientation detected (dir == 1)
- // 0x02 -> reverse orientation detected (dir == -1)
- uint8_t flags = 0;
- if (dir == 1) flags |= 0x01;
- if (dir == -1) flags |= 0x02;
- report.data[ofs++] = flags;
-
- // write the average scan time in 10us intervals to bytes 6-8
- uint32_t t = uint32_t(avgScanTime / 10);
- report.data[ofs++] = t & 0xff;
- report.data[ofs++] = (t >> 8) & 0xff;
- report.data[ofs++] = (t >> 16) & 0xff;
-
- // write the processing time to bytes 9-11
- t = uint32_t(processingTime / 10);
- report.data[ofs++] = t & 0xff;
- report.data[ofs++] = (t >> 8) & 0xff;
- report.data[ofs++] = (t >> 16) & 0xff;
-
- // send the report
- report.length = reportLen;
- return sendTO(&report, 100);
-}
-
bool USBJoystick::reportID()
{
@@ -189,7 +189,30 @@
return sendTO(&report, 100);
}
-bool USBJoystick::reportConfig(int numOutputs, int unitNo, int plungerZero, int plungerMax, bool configured)
+bool USBJoystick::reportConfigVar(const uint8_t *data)
+{
+ HID_REPORT report;
+
+ // initially fill the report with zeros
+ memset(report.data, 0, sizeof(report.data));
+
+ // Set the special status bits to indicate that it's a config
+ // variable report
+ uint16_t s = 0x9800;
+ put(0, s);
+
+ // Copy the variable data (7 bytes, starting with the variable ID)
+ memcpy(report.data + 2, data, 7);
+
+ // send the report
+ report.length = reportLen;
+ return sendTO(&report, 100);
+}
+
+bool USBJoystick::reportConfig(
+ int numOutputs, int unitNo,
+ int plungerZero, int plungerMax, int plungerRlsTime,
+ bool configured)
{
HID_REPORT report;
@@ -209,10 +232,11 @@
// write the plunger zero and max values
put(6, plungerZero);
put(8, plungerMax);
+ report.data[10] = uint8_t(plungerRlsTime);
// write the status bits:
// 0x01 -> configuration loaded
- report.data[10] = (configured ? 0x01 : 0x00);
+ report.data[11] = (configured ? 0x01 : 0x00);
// send the report
report.length = reportLen;
--- a/USBJoystick/USBJoystick.h Tue Mar 01 23:21:45 2016 +0000
+++ b/USBJoystick/USBJoystick.h Sat Mar 05 00:16:52 2016 +0000
@@ -213,6 +213,17 @@
bool updateStatus(uint32_t stat);
/**
+ * Write the plunger status report.
+ *
+ * @param npix number of pixels in the sensor (0 for non-imaging sensors)
+ * @param edgePos the pixel position of the detected edge in this image, or -1 if none detected
+ * @param dir sensor orientation (1 = standard, -1 = reversed, 0 = unknown)
+ * @param avgScanTime average sensor scan time in microseconds
+ * @param processingTime time in microseconds to process the current frame
+ */
+ bool sendPlungerStatus(int npix, int edgePos, int dir, uint32_t avgScanTime, uint32_t processingTime);
+
+ /**
* Write an exposure report. We'll fill out a report with as many pixels as
* will fit in the packet, send the report, and update the index to the next
* pixel to send. The caller should call this repeatedly to send reports for
@@ -222,18 +233,7 @@
* @param npix number of pixels in the overall array
* @param pix pixel array
*/
- bool updateExposure(int &idx, int npix, const uint8_t *pix);
-
- /**
- * Write the special extended exposure report with additional data about the
- * scan.
- *
- * @param edgePos the pixel position of the detected edge in this image, or -1 if none detected
- * @param dir detected sensor orientation: 1 for standard, -1 for reversed, 0 for unknown
- * @param avgScanTime average sensor scan time in microseconds
- * @param processingTime time in microseconds to process the current frame
- */
- bool updateExposureExt(int edgePos, int dir, uint32_t avgScanTime, uint32_t processingTime);
+ bool sendPlungerPix(int &idx, int npix, const uint8_t *pix);
/**
* Write a configuration report.
@@ -242,9 +242,19 @@
* @param unitNo the device unit number
* @param plungerZero plunger zero calibration point
* @param plungerMax plunger max calibration point
+ * @param plungerRlsTime measured plunger release time, in milliseconds
* @param configured true if a configuration has been saved to flash from the host
*/
- bool reportConfig(int numOutputs, int unitNo, int plungerZero, int plungerMax, bool configured);
+ bool reportConfig(int numOutputs, int unitNo,
+ int plungerZero, int plungerMax, int plunterRlsTime,
+ bool configured);
+
+ /**
+ * Write a configuration variable query report.
+ *
+ * @param data the 7-byte data variable buffer, starting with the variable ID byte
+ */
+ bool reportConfigVar(const uint8_t *data);
/**
* Write a device ID report.
--- a/USBProtocol.h Tue Mar 01 23:21:45 2016 +0000 +++ b/USBProtocol.h Sat Mar 05 00:16:52 2016 +0000 @@ -55,18 +55,66 @@ // as an opaque vendor-defined value, so the joystick interface on the // Windows side simply ignores it.) // -// 2A. Plunger sensor pixel dump -// Software on the PC can request a full read of the pixels from the plunger -// image sensor (if an imaging sensor type is being used) by sending custom -// protocol message 65 3 (see below). Normally, the pixels from the image -// sensor are read and processed on the controller device without being sent -// to the PC; the PC only receives the plunger position reading obtained from -// analyzing the image data. For debugging and setup purposes, software on -// the host can use this special report to obtain the full image pixel array. -// The image sensors we use have too many pixels to fit into one report, so -// we have to send a series of reports to transmit the full image. We send -// as many reports as necessary to transmit the full image. Each report -// looks like this: +// 2A. Plunger sensor status report +// Software on the PC can request a detailed status report from the plunger +// sensor. The status information is meant as an aid to installing and +// adjusting the sensor device for proper performance. For imaging sensor +// types, the status report includes a complete current image snapshot +// (an array of all of the pixels the sensor is currently imaging). For +// all sensor types, it includes the current plunger position registered +// on the sensor, and some timing information. +// +// To request the sensor status, the host sends custom protocol message 65 3 +// (see below). The device replies with a message in this format: +// +// bytes 0:1 = 0x87FF +// byte 2 = 0 -> first (currently only) status report packet +// (additional packets could be added in the future if +// more fields need to be added) +// bytes 3:4 = number of pixels to be sent in following messages, as +// an unsigned 16-bit little-endian integer. This is 0 if +// the sensor isn't an imaging type. +// bytes 5:6 = current plunger position registered on the sensor. +// For imaging sensors, this is the pixel position, so it's +// scaled from 0 to number of pixels - 1. For non-imaging +// sensors, this uses the generic joystick scale 0..4095. +// The special value 0xFFFF means that the position couldn't +// be determined, +// byte 7 = bit flags: +// 0x01 = normal orientation detected +// 0x02 = reversed orientation detected +// 0x04 = calibration mode is active (no pixel packets +// are sent for this reading) +// bytes 8:9:10 = average time for each sensor read, in 10us units. +// This is the average time it takes to complete the I/O +// operation to read the sensor, to obtain the raw sensor +// data for instantaneous plunger position reading. For +// an imaging sensor, this is the time it takes for the +// sensor to capture the image and transfer it to the +// microcontroller. For an analog sensor (e.g., an LVDT +// or potentiometer), it's the time to complete an ADC +// sample. +// bytes 11:12:13 = time it took to process the current frame, in 10us +// units. This is the software processing time that was +// needed to analyze the raw data read from the sensor. +// This is typically only non-zero for imaging sensors, +// where it reflects the time required to scan the pixel +// array to find the indicated plunger position. The time +// is usually zero or negligible for analog sensor types, +// since the only "analysis" is a multiplication to rescale +// the ADC sample. +// +// If the sensor is an imaging sensor type, this will be followed by a +// series of pixel messages. The imaging sensor types have too many pixels +// to send in a single USB transaction, so the device breaks up the array +// into as many packets as needed and sends them in sequence. For non- +// imaging sensors, the "number of pixels" field in the lead packet is +// zero, so obviously no pixel packets will follow. If the "calibration +// active" bit in the flags byte is set, no pixel packets are sent even +// if the sensor is an imaging type, since the transmission time for the +// pixels would intefere with the calibration process. If pixels are sent, +// they're sent in order starting at the first pixel. The format of each +// pixel packet is: // // bytes 0:1 = 11-bit index, with high 5 bits set to 10000. For // example, 0x8004 (encoded little endian as 0x04 0x80) @@ -77,31 +125,13 @@ // bytes 3 = brightness of pixel at index+1 // etc for the rest of the packet // -// The pixel dump also sends a special final report, after all of the -// pixel messages, with the "index" field set to 0x7FF (11 bits of 1's). -// This report packs special fields instead of pixels. There are two -// subtypes, sent in sequence: -// -// Subtype 0: -// bytes 0:1 = 0x87FF (pixel report flags + index 0x7FF) -// byte 2 = 0x00 -> special report subtype 0 -// bytes 3:4 = pixel position of detected shadow edge in this image, -// or 0xFFFF if no edge was found in this image. For -// raw pixel reports, no edge will be detected because -// we don't look for one. -// byte 5 = flags: -// 0x01 = normal orientation detected -// 0x02 = reversed orientation detected -// bytes 6:7:8 = average time for a sensor scan, in 10us units -// byte 9:10:11 = time for processing this image, in 10us units -// -// Subtype 1: -// bytes 0:1 = 0x87FF -// byte 2 = 0x01 -> special report subtype 1 -// bytes 3:4 = calibration zero point, in pixels (16-bit little-endian) -// bytes 5:6 = calibration maximum point, in pixels -// bytes 7:8 = calibration minimum point, in pixels -// byte 9 = calibrated release time, in milliseconds +// Note that we currently only support one-dimensional imaging sensors +// (i.e., pixel arrays that are 1 pixel wide). The report format doesn't +// have any provision for a two-dimensional layout. The KL25Z probably +// isn't powerful enough to do real-time image analysis on a 2D image +// anyway, so it's unlikely that we'd be able to make 2D sensors work at +// all, but if we ever add such a thing we'll have to upgrade the report +// format here accordingly. // // // 2B. Configuration query. @@ -114,7 +144,8 @@ // bytes 2:3 = total number of outputs, little endian // bytes 6:7 = plunger calibration zero point, little endian // bytes 8:9 = plunger calibration maximum point, little endian -// byte 10 = bit flags: +// byte 10 = plunger calibration release time, in milliseconds +// byte 11 = bit flags: // 0x01 -> configuration loaded; 0 in this bit means that // the firmware has been loaded but no configuration // has been sent from the host @@ -124,14 +155,26 @@ // This is requested by sending custom protocol message 65 7 (see below). // In response, the device sends one report to the host using this format: // -// bytes 0:1 = 0x9000. This has bit pattern 10010 in the high 5 -// bits, which distinguishes this special report from other -// report types. +// bytes 0:1 = 0x9000. This has bit pattern 10010 in the high 5 bits +// to distinguish this from other report types. // bytes 2-11 = Unique CPU ID. This is the ID stored in the CPU at the // factory, guaranteed to be unique across Kinetis devices. // This can be used by the host to distinguish devices when // two or more controllers are attached. // +// 2D. Configuration variable query. +// This is requested by sending custom protocol message 65 9 (see below). +// In response, the device sends one report to the host using this format: +// +// bytes 0:1 = 0x9800. This has bit pattern 10011 in the high 5 bits +// to distinguish this from other report types. +// byte 2 = Variable ID. This is the same variable ID sent in the +// query message, to relate the reply to the request. +// bytes 3-8 = Current value of the variable, in the format for the +// individual variable type. The variable formats are +// described in the CONFIGURATION VARIABLES section below. +// +// // WHY WE USE THIS HACKY APPROACH TO DIFFERENT REPORT TYPES // // The HID report system was specifically designed to provide a clean, @@ -294,6 +337,12 @@ // engage night mode, 0 to disengage night mode. (This mode isn't stored // persistently; night mode is disengaged after a reset or power cycle.) // +// 9 -> Query configuration variable. The second byte is the config variable +// number (see the CONFIGURATION VARIABLES section below). For the array +// variables (button assignments, output ports), the third byte is the +// array index. The device replies with a configuration variable report +// (see above) with the current setting for the requested variable. +// // 66 -> Set configuration variable. The second byte of the message is the config // variable number, and the remaining bytes give the new value for the variable. // The value format is specific to each variable; see the list below for details. @@ -527,7 +576,27 @@ // timeout period. Bytes 3 give the new reboot timeout in seconds. Setting this // to 0 disables the reboot timeout. // - +// 15 -> Plunger calibration. In most cases, the calibration is set internally by the +// device by running the calibration procedure. However, it's sometimes useful +// for the host to be able to get and set the calibration, such as to back up +// the device settings on the PC, or to save and restore the current settings +// when installing a software update. +// +// bytes 3:4 = rest position (unsigned 16-bit little-endian) +// bytes 5:6 = maximum retraction point (unsigned 16-bit little-endian) +// byte 7 = measured plunger release travel time in milliseconds +// +// 16 -> Expansion board configuration. This doesn't affect the controller behavior +// directly; the individual options related to the expansion boards (such as +// the TLC5940 and 74HC595 setup) still need to be set separately. This is +// stored so that the PC config UI can store and recover the information to +// present in the UI. For the "classic" KL25Z-only configuration, simply set +// all of the fields to zero. +// +// byte 3 = number of main interface boards +// byte 4 = number of MOSFET power boards +// byte 5 = number of chime boards +// // --- PIN NUMBER MAPPINGS ---
--- a/ccdSensor.h Tue Mar 01 23:21:45 2016 +0000
+++ b/ccdSensor.h Sat Mar 05 00:16:52 2016 +0000
@@ -55,6 +55,9 @@
int pixpos;
if (process(pix, n, pixpos, 0))
{
+ // run the position through the anti-jitter filter
+ filter(pixpos);
+
// Normalize to the 16-bit range. Our reading from the
// sensor is a pixel position, 0..n-1. To rescale to the
// normalized range, figure pixpos*65535/(n-1).
@@ -76,24 +79,6 @@
// of the edge and return true; otherwise we return false. The 'pos'
// value returned, if any, is adjusted for sensor orientation so that
// it reflects the logical plunger position.
- //
- // 'visMode' is the visualization mode. If non-zero, we replace the
- // pixels in the 'pix' array with a new version for visual presentation
- // to the user, as an aid to setup and debugging. The visualization
- // modes are:
- //
- // 0 = No visualization
- // 1 = High contrast: we set each pixel to white or black according
- // to whether it's brighter or dimmer than the midpoint brightness
- // we use to seek the shadow edge. This mode makes the edge
- // positions visually apparent.
- // 2 = Edge mode: we set all pixels to white except for detected edges,
- // which we set to black.
- //
- // The 'pix' array is overwritten with the processed pixels. If visMode
- // is 0, this reflects only the basic preprocessing we do in an edge
- // scan, such as noise reduction. For other visualization modes, the
- // pixels are replaced by the visualization results.
bool process(uint8_t *pix, int &n, int &pos, int visMode)
{
// Get the levels at each end
@@ -194,455 +179,75 @@
// no edge found
return false;
}
-
-
-#if 0
- bool process3(uint8_t *pix, int &n, int &pos, int visMode)
- {
- // First, reduce the pixel array resolution to 1/4 of the
- // native sensor resolution. The native 400 dpi is higher
- // than we need for good results, so we can afford to cut
- // this down a bit. Reducing the resolution gives us
- // a little simplistic noise reduction (by averaging adjacent
- // pixels), and it speeds up the rest of the edge finder by
- // making the data set smaller.
- //
- // While we're scanning, collect the brightness range of the
- // reduced pixel set.
- register int src, dst;
- int lo = pix[0], hi = pix[0];
- for (src = 0, dst = 0 ; src < n ; )
+
+ // Filter a result through the jitter reducer. We tend to have some
+ // very slight jitter - by a pixel or two - even when the plunger is
+ // stationary. This happens due to analog noise. In the theoretical
+ // ideal, analog noise wouldn't be a factor for this sensor design,
+ // in that we'd have enough contrast between the bright and dark
+ // regions that there'd be no ambiguity as to where the shadow edge
+ // falls. But in the real system, the shadow edge isn't perfectly
+ // sharp on the scale of our pixels, so the edge isn't an ideal
+ // digital 0-1 discontinuity but rather a ramp of gray levels over
+ // a few pixels. Our edge detector picks the pixel where we cross
+ // the midpoint brightness threshold. The exact midpoint can vary
+ // a little from frame to frame due to exposure length variations,
+ // light source variations, other stray light sources in the cabinet,
+ // ADC error, sensor pixel noise, and electrical noise. As the
+ // midpoint varies, the pixel that qualifies as the edge position
+ // can move by a pixel or two from one from to the next, even
+ // though the physical shadow isn't moving. This all adds up to
+ // some slight jitter in the final position reading.
+ //
+ // To reduce the jitter, we keep a short history of recent readings.
+ // When we see a new reading that's close to the whole string of
+ // recent readings, we peg the new reading to the consensus of the
+ // recent history. This smooths out these small variations without
+ // affecting response time or resolution.
+ void filter(int &pos)
+ {
+ // check to see if it's close to all of the history elements
+ const int dpos = 1;
+ bool isClose = true;
+ long sum = 0;
+ for (int i = 0 ; i < countof(hist) ; ++i)
{
- // compute the average of this pixel group
- int p = (int(pix[src++]) + pix[src++] + pix[src++] + pix[src++]) / 4;
-
- // note if it's the new high or low point
- if (p > hi)
- hi = p;
- else if (p < lo)
- lo = p;
-
- // Store the result back into the original array. Note
- // that there's no risk of overwriting anything we still
- // need, since the pixel set is shrinking, so the write
- // pointer is always behind the read pointer.
- pix[dst++] = p;
- }
-
- // set the new array size
- n = dst;
-
- // figure the midpoint brightness
- int mid = (hi + lo)/2;
-
- // Look at the first few pixels on the left and right sides
- // to try to detect the sensor orientation.
- int left = pix[0] + pix[1] + pix[2] + pix[3];
- int right = pix[n-1] + pix[n-2] + pix[n-3] + pix[n-4];
- if (left > right + 40)
- {
- // left side is brighter - standard orientation
- dir = 1;
- }
- else if (right > left + 40)
- {
- // right side is brighter - reversed orientation
- dir = -1;
- }
-
- // scan for edges according to the direction
- bool found = false;
- if (dir == 0)
- {
- }
- else
- {
- // scan from the bright end to the dark end
- int stop;
- if (dir == 1)
- {
- src = 0;
- stop = n;
- }
- else
+ int ipos = hist[i];
+ sum += ipos;
+ if (pos > ipos + dpos || pos < ipos - dpos)
{
- src = n - 1;
- stop = -1;
- }
-
- // scan through the pixels
- for ( ; src != stop ; src += dir)
- {
- // if this pixel is darker than the midpoint, we might
- // have an edge
- if (pix[src] < mid)
- {
- // make sure it's not just noise by checking the next
- // few to make sure they're also darker
- if (dir > 0)
- dst = src + 10 > n ? n : src + 10;
- else
- dst = src - 10 < 0 ? -1 : src - 10;
- int i, nok;
- for (nok = 0, i = src ; i != dst ; i += dir)
- {
- if (pix[i] < mid)
- ++nok;
- }
- if (nok > 6)
- {
- // we have a winner
- pos = src;
- found = true;
- break;
- }
- }
- }
- }
-
- // return the result
- return found;
- }
-#endif
-
-#if 0
- bool process2(uint8_t *pix, int n, int &pos, int visMode)
- {
- // find the high and low brightness levels, and sum
- // all pixels (for the running averages)
- register int i;
- long sum = 0;
- int lo = 255, hi = 0;
- for (i = 0 ; i < n ; ++i)
- {
- int p = pix[i];
- sum += p;
- if (p > hi) hi = p;
- if (p < lo) lo = p;
- }
-
- // Figure the midpoint brightness
- int mid = (lo + hi)/2;
-
- // Scan for edges. An edge is where adjacent pixels are
- // on opposite sides of the brightness midpoint. For each
- // edge, we'll compute the "steepness" as the difference
- // between the average brightness on each side. We'll
- // keep only the steepest edge.
- register int bestSteepness = -1;
- register int bestPos = -1;
- register int sumLeft = 0;
- register int prv = pix[0], nxt = pix[1];
- for (i = 1 ; i < n ; prv = nxt, nxt = pix[++i])
- {
- // figure the new sums left and right of the i:i+1 boundary
- sumLeft += prv;
-
- // if this is an edge, check if it's the best edge
- if (((mid - prv) & 0x80) ^ ((mid - nxt) & 0x80))
- {
- // compute the steepness
- int steepness = sumLeft/i - (sum - sumLeft)/(n-i);
- if (steepness > bestSteepness)
- {
- bestPos = i;
- bestSteepness = steepness;
- }
+ isClose = false;
+ break;
}
}
- // if we found a position, return it
- if (bestPos >= 0)
+ // check if we're close to all recent readings
+ if (isClose)
{
- pos = bestPos;
- return true;
+ // We're close, so just stick to the average of recent
+ // readings. Note that we don't add the new reading to
+ // the history in this case. If the edge is about halfway
+ // between two pixels, the history will be about 50/50 on
+ // an ongoing basis, so if just kept adding samples we'd
+ // still jitter (just at a slightly reduced rate). By
+ // stalling the history when it looks like we're stationary,
+ // we'll just pick one of the pixels and stay there as long
+ // as the plunger stays where it is.
+ pos = sum/countof(hist);
}
else
{
- return false;
- }
- }
-#endif
-
-#if 0
- bool process1(uint8_t *pix, int n, int &pos, int visMode)
- {
- // presume failure
- bool ret = false;
-
- // apply noise reduction
- noiseReduction(pix, n);
-
- // make a histogram of brightness values
- uint8_t hist[256];
- memset(hist, 0, sizeof(hist));
- for (int i = 0 ; i < n ; ++i)
- {
- // get this pixel brightness, and count it in the histogram,
- // stopping if we hit the maximum count of 255
- int b = pix[i];
- if (hist[b] < 255)
- ++hist[b];
- }
-
- // Find the high and low bounds. To avoid counting outliers that
- // might be noise, we'll scan in from each end of the brightness
- // range until we find a few pixels at or outside that level.
- int cnt, lo, hi;
- const int mincnt = 10;
- for (cnt = 0, lo = 0 ; lo < 255 ; ++lo)
- {
- cnt += hist[lo];
- if (cnt >= mincnt)
- break;
- }
- for (cnt = 0, hi = 255 ; hi >= 0 ; --hi)
- {
- cnt += hist[hi];
- if (cnt >= mincnt)
- break;
- }
-
- // figure the inferred midpoint brightness level
- uint8_t m = uint8_t((int(lo) + int(hi))/2);
-
- // Try finding an edge with the inferred brightness range
- if (findEdge(pix, n, m, pos, false))
- {
- // Found it! This image has sufficient contrast to find
- // an edge, so save the midpoint brightness for next time in
- // case the next image isn't as clear.
- midpt[midptIdx] = m;
- midptIdx = (midptIdx + 1) % countof(midpt);
-
- // Infer the sensor orientation. If pixels at the bottom
- // of the array are brighter than pixels at the top, it's in the
- // standard orientation, otherwise it's the reverse orientation.
- int a = int(pix[0]) + int(pix[1]) + int(pix[2]);
- int b = int(pix[n-1]) + int(pix[n-2]) + int(pix[n-3]);
- dir = (a > b ? 1 : -1);
-
- // if we're in the reversed orientation, mirror the position
- if (dir < 0)
- pos = n-1 - pos;
-
- // success
- ret = true;
- }
- else
- {
- // We didn't find a clear edge using the inferred exposure
- // level. This might be because the image is entirely in or out
- // of shadow, with the plunger's shadow's edge out of the frame.
- // Figure the average of the recent history of successful frames
- // so that we can check to see if we have a low-contrast image
- // that's entirely above or below the recent midpoints.
- int avg = 0;
- for (int i = 0 ; i < countof(midpt) ; avg += midpt[i++]) ;
- avg /= countof(midpt);
-
- // count how many we have above and below the midpoint
- int nBelow = 0, nAbove = 0;
- for (int i = 0 ; i < avg ; nBelow += hist[i++]) ;
- for (int i = avg + 1 ; i < 255 ; nAbove += hist[i++]) ;
-
- // check if we're mostly above or below (we don't require *all*,
- // to allow for some pixel noise remaining)
- if (nBelow < 50)
- {
- // everything's bright -> we're in full light -> fully retracted
- pos = n - 1;
- ret = true;
- }
- else if (nAbove < 50)
- {
- // everything's dark -> we're in full shadow -> fully forward
- pos = 0;
- ret = true;
- }
-
- // for visualization purposes, use the previous average as the midpoint
- m = avg;
- }
-
- // If desired, apply the visualization mode to the pixels
- switch (visMode)
- {
- case 2:
- // High contrast mode. Peg each pixel to the white or black according
- // to which side of the midpoint it's on.
- for (int i = 0 ; i < n ; ++i)
- pix[i] = (pix[i] < m ? 0 : 255);
- break;
-
- case 3:
- // Edge mode. Re-run the edge analysis in visualization mode.
- {
- int dummy;
- findEdge(pix, n, m, dummy, true);
- }
- break;
- }
-
- // return the result
- return ret;
- }
-
- // Apply noise reduction to the pixel array. We use a simple rank
- // selection median filter, which is fast and seems to produce pretty
- // good results with data from this sensor type. The filter looks at
- // a small window around each pixel; if a given pixel is the outlier
- // within its window (i.e., it has the maximum or minimum brightness
- // of all the pixels in the window), we replace it with the median
- // brightness of the pixels in the window. This works particularly
- // well with the structure of the image we expect to capture, since
- // the image should have stretches of roughly uniform brightness -
- // part fully exposed and part in the plunger's shadow. Spiky
- // variations in isolated pixels are almost guaranteed to be noise.
- void noiseReduction(uint8_t *pix, int n)
- {
- // set up a rolling window of pixels
- uint8_t w[7] = { pix[0], pix[1], pix[2], pix[3], pix[4], pix[5], pix[6] };
- int a = 0;
-
- // run through the pixels
- for (int i = 0 ; i < n ; ++i)
- {
- // set up a sorting array for the current window
- uint8_t tmp[7] = { w[0], w[1], w[2], w[3], w[4], w[5], w[6] };
-
- // sort it (using a Bose-Nelson sorting network for N=7)
-#define SWAP(x, y) { \
- const int a = tmp[x], b = tmp[y]; \
- if (a > b) tmp[x] = b, tmp[y] = a; \
- }
- SWAP(1, 2);
- SWAP(0, 2);
- SWAP(0, 1);
- SWAP(3, 4);
- SWAP(5, 6);
- SWAP(3, 5);
- SWAP(4, 6);
- SWAP(4, 5);
- SWAP(0, 4);
- SWAP(0, 3);
- SWAP(1, 5);
- SWAP(2, 6);
- SWAP(2, 5);
- SWAP(1, 3);
- SWAP(2, 4);
- SWAP(2, 3);
-
- // if the current pixel is at one of the extremes, replace it
- // with the median, otherwise leave it unchanged
- if (pix[i] == tmp[0] || pix[i] == tmp[6])
- pix[i] = tmp[3];
-
- // update our rolling window, if we're not at the start or
- // end of the overall pixel array
- if (i >= 3 && i < n-4)
- {
- w[a] = pix[i+4];
- a = (a + 1) % 7;
- }
+ // This isn't near enough to the recent stationary position,
+ // so keep the new reading exactly as it is, and add it to the
+ // history.
+ hist[histIdx++] = pos;
+ histIdx %= countof(hist);
}
}
- // Find an edge in the image. 'm' is the midpoint brightness level
- // in the array. On success, fills in 'pos' with the pixel position
- // of the edge and returns true. Returns false if no clear, unique
- // edge can be detected.
- //
- // If 'vis' is true, we'll update the pixel array with a visualization
- // of the edges, for display in the config tool.
- bool findEdge(uint8_t *pix, int n, uint8_t m, int &pos, bool vis)
- {
- // Scan for edges. An edge is a transition where two adajacent
- // pixels are on opposite sides of the brightness midpoint.
- int nEdges = 0;
- int edgePos = 0;
- uint8_t prv = pix[0], nxt = pix[1];
- for (int i = 1 ; i < n-1 ; prv = nxt, nxt = pix[++i])
- {
- // presume we'll show a non-edge (white) pixel in the visualization
- uint8_t vispix = 255;
-
- // if the two are on opposite sides of the midpoint, we have
- // an edge
- if ((prv < m && nxt > m) || (prv > m && nxt < m))
- {
- // count the edge and note its position
- ++nEdges;
- edgePos = i;
-
- // color edges black in the visualization
- vispix = 0;
- }
-
- // if in visualization mode, substitute the visualization pixel
- if (vis)
- pix[i] = vispix;
- }
-
- // check for a unique edge
- if (nEdges == 1)
- {
- // Successfully found an edge - presume we'll return the raw
- // value we just found
- pos = edgePos;
-
- // Filtering to the signal to reduce jitter. We sometimes see
- // the detected position jitter around by a pixel or two when
- // the plunger is stationary; the filtering is meant to reduce
- // or (ideally) eliminate it. The jitter happens because the
- // exactly pixel position of the edge can be a little ambiguous.
- // The shadow is usually a little fuzzy and spans more than one
- // pixel on the sensor, so our algorithm picks out the edge in
- // each frame according to relative brightness from pixel to
- // pixel. The exact relative brightnesses can vary a bit,
- // though, due to variations in exposure time, light source
- // uniformity, other stray light sources in the cabinet, pixel
- // noise in the sensor, ADC error, etc.
- //
- // To filter the jitter, we'll look through the recent history
- // to see if the recent samples are within a couple of pixels
- // of each other. If so, we'll take an average and substitute
- // that for our current reading.
- bool allClose = true;
- long sum = 0;
- for (int i = 0 ; i < countof(hist) ; ++i)
- {
- // if this one isn't close enough, they're not all close
- if (abs(hist[i] - edgePos) > 2)
- {
- allClose = false;
- break;
- }
-
- // count it in the sum
- sum += hist[i];
- }
- if (allClose)
- [
- // they're all close by - replace this reading with the
- // average of nearby pixels
- pos = int(sum / countof(hist));
- }
-
- // indicate success
- return true;
- }
- else
- {
- // failure
- return false;
- }
- }
-#endif
-
- // Send an exposure report to the joystick interface.
+ // Send a status report to the joystick interface.
// See plunger.h for details on the flags and visualization modes.
- virtual void sendExposureReport(USBJoystick &js, uint8_t flags, uint8_t visMode)
+ virtual void sendStatusReport(USBJoystick &js, uint8_t flags, uint8_t visMode)
{
// start a capture
ccd.startCapture();
@@ -652,23 +257,20 @@
int n;
uint32_t t;
ccd.getPix(pix, n, t);
+
+ // start a timer to measure the processing time
+ Timer pt;
+ pt.start();
+
+ // process the pixels and read the position
+ int pos;
+ if (process(pix, n, pos, visMode))
+ filter(pos);
+ else
+ pos = 0xFFFF;
- // Apply processing if desired. For visualization mode 0, apply no
- // processing at all. For all others it through the pixel processor.
- int pos = 0xffff;
- uint32_t processTime = 0;
- if (visMode != 0)
- {
- // count the processing time
- Timer pt;
- pt.start();
-
- // do the processing
- process(pix, n, pos, visMode);
-
- // note the processing time
- processTime = pt.read_us();
- }
+ // note the processing time
+ uint32_t processTime = pt.read_us();
// if a low-res scan is desired, reduce to a subset of pixels
if (flags & 0x01)
@@ -681,68 +283,39 @@
int src, dst;
for (src = dst = 0 ; dst < lowResPix ; ++dst)
{
- // Combine these pixels - the best way to do this differs
- // by visualization mode...
+ // average this block of pixels
int a = 0;
- switch (visMode)
- {
- case 0:
- case 1:
- // Raw or noise-reduced pixels. This mode shows basically
- // a regular picture, so reduce the resolution by averaging
- // the grouped pixels.
- for (int j = 0 ; j < group ; ++j)
- a += pix[src++];
+ for (int j = 0 ; j < group ; ++j)
+ a += pix[src++];
- // we have the sum, so get the average
- a /= group;
- break;
-
- case 2:
- // High contrast mode. To retain the high contrast, take a
- // majority vote of the pixels. Start by counting the white
- // pixels.
- for (int j = 0 ; j < group ; ++j)
- a += (pix[src++] > 127);
-
- // If half or more are white, make the combined pixel white;
- // otherwise make it black.
- a = (a >= n/2 ? 255 : 0);
- break;
-
- case 3:
- // Edge mode. Edges are shown as black. To retain every
- // detected edge in the result image, show the combined pixel
- // as an edge if ANY pixel within the group is an edge.
- a = 255;
- for (int j = 0 ; j < group ; ++j)
- {
- if (pix[src++] < 127)
- a = 0;
- }
- break;
- }
-
+ // we have the sum, so get the average
+ a /= group;
+
// store the down-res'd pixel in the array
pix[dst] = uint8_t(a);
}
- // update the pixel count to the number we stored
- n = dst;
-
- // if we have a valid position, rescale it to the reduced pixel count
- if (pos != 0xffff)
- pos = pos / group;
+ // rescale the position for the reduced resolution
+ if (pos != 0xFFFF)
+ pos = pos * (lowResPix-1) / (n-1);
+
+ // update the pixel count to the reduced array size
+ n = lowResPix;
}
- // send reports for all pixels
- int idx = 0;
- while (idx < n)
- js.updateExposure(idx, n, pix);
+ // send the sensor status report report
+ js.sendPlungerStatus(n, pos, dir, ccd.getAvgScanTime(), processTime);
+
+ // If we're not in calibration mode, send the pixels
+ extern bool plungerCalMode;
+ if (!plungerCalMode)
+ {
+ // send the pixels in report-sized chunks until we get them all
+ int idx = 0;
+ while (idx < n)
+ js.sendPlungerPix(idx, n, pix);
+ }
- // send a special final report with additional data
- js.updateExposureExt(pos, dir, ccd.getAvgScanTime(), processTime);
-
// It takes us a while to send all of the pixels, since we have
// to break them up into many USB reports. This delay means that
// the sensor has been sitting there integrating for much longer
@@ -754,6 +327,9 @@
ccd.startCapture();
}
+ // get the average sensor scan time
+ virtual uint32_t getAvgScanTime() { return ccd.getAvgScanTime(); }
+
protected:
// Sensor orientation. +1 means that the "tip" end - which is always
// the brighter end in our images - is at the 0th pixel in the array.
@@ -772,7 +348,7 @@
// History of recent position readings. We keep a short history of
// readings so that we can apply some filtering to the data.
- uint16_t hist[10];
+ uint16_t hist[8];
int histIdx;
// History of midpoint brightness levels for the last few successful
--- a/cfgVarMsgMap.h Tue Mar 01 23:21:45 2016 +0000
+++ b/cfgVarMsgMap.h Sat Mar 05 00:16:52 2016 +0000
@@ -156,6 +156,20 @@
// Disconnect reboot timeout
v_byte(disconnectRebootTimeout, 2);
break;
+
+ case 15:
+ // plunger calibration
+ v_ui16(plunger.cal.zero, 2);
+ v_ui16(plunger.cal.max, 4);
+ v_byte(plunger.cal.tRelease, 6);
+ break;
+
+ case 16:
+ // expansion board configuration
+ v_byte(expan.nMain, 2);
+ v_byte(expan.nPower, 3);
+ v_byte(expan.nChime, 4);
+ break;
}
}
--- a/config.h Tue Mar 01 23:21:45 2016 +0000
+++ b/config.h Sat Mar 05 00:16:52 2016 +0000
@@ -20,7 +20,7 @@
// $$$ TESTING CONFIGURATIONS
#define TEST_CONFIG_EXPAN 0
#define TEST_CONFIG_CAB 1
-#define TEST_KEEP_PRINTF 1
+#define TEST_KEEP_PRINTF 0
#ifndef CONFIG_H
@@ -145,6 +145,11 @@
// assume standard orientation, with USB ports toward front of cabinet
orientation = OrientationFront;
+ // assume a basic setup with no expansion boards
+ expan.nMain = 0;
+ expan.nPower = 0;
+ expan.nChime = 0;
+
// assume no plunger is attached
plunger.enabled = false;
plunger.sensorType = PlungerType_None;
@@ -394,6 +399,16 @@
char orientation;
+ // --- EXPANSION BOARDS ---
+ struct
+ {
+ int nMain; // number of main interface boards (usually 1 max)
+ int nPower; // number of MOSFET power boards
+ int nChime; // number of chime boards
+
+ } expan;
+
+
// --- PLUNGER CONFIGURATION ---
struct
{
@@ -471,6 +486,9 @@
uint16_t min;
uint16_t zero;
uint16_t max;
+
+ // Measured release time, in milliseconds.
+ uint8_t tRelease;
// Reset the plunger calibration
void setDefaults()
@@ -479,6 +497,7 @@
min = 0; // assume we can go all the way forward...
max = 0xffff; // ...and all the way back
zero = max/6; // the rest position is usually around 1/2" back = 1/6 of total travel
+ tRelease = 65; // standard 65ms release time
}
// Begin calibration. This sets each limit to the worst
@@ -491,6 +510,7 @@
min = 0; // we don't calibrate the maximum forward position, so keep this at zero
zero = 0xffff; // set the zero position all the way back
max = 0; // set the retracted position all the way forward
+ tRelease = 65; // revert to a default release time
}
} cal;
--- a/main.cpp Tue Mar 01 23:21:45 2016 +0000
+++ b/main.cpp Sat Mar 05 00:16:52 2016 +0000
@@ -2308,6 +2308,9 @@
}
}
+// Global plunger calibration mode flag
+bool plungerCalMode;
+
// Plunger reader
//
// This class encapsulates our plunger data processing. At the simplest
@@ -2368,9 +2371,6 @@
// no history yet
histIdx = 0;
-
- // not in calibration mode
- cal = false;
}
// Collect a reading from the plunger sensor. The main loop calls
@@ -2384,21 +2384,9 @@
if (plungerSensor->read(r))
{
// if in calibration mode, apply it to the calibration
- if (cal)
+ if (plungerCalMode)
{
- // if it's outside of the current calibration bounds,
- // expand the bounds
- if (r.pos < cfg.plunger.cal.min)
- cfg.plunger.cal.min = r.pos;
- if (r.pos < cfg.plunger.cal.zero)
- cfg.plunger.cal.zero = r.pos;
- if (r.pos > cfg.plunger.cal.max)
- cfg.plunger.cal.max = r.pos;
-
- // As long as we're in calibration mode, return the raw
- // sensor position as the joystick value, adjusted to the
- // JOYMAX scale.
- z = int16_t((long(r.pos) * JOYMAX)/65535);
+ readForCal(r);
return;
}
@@ -2419,7 +2407,7 @@
// bounds-check the calibration data
checkCalBounds(r.pos);
- // calibrate and rescale the value
+ // Apply the calibration and rescale to the joystick range.
r.pos = int(
(long(r.pos - cfg.plunger.cal.zero) * JOYMAX)
/ (cfg.plunger.cal.max - cfg.plunger.cal.zero));
@@ -2638,20 +2626,195 @@
uint32_t getTimestamp() const { return nthHist(0).t; }
// Set calibration mode on or off
- void calMode(bool f)
+ void setCalMode(bool f)
{
- // if entering calibration mode, reset the saved calibration data
- if (f && !cal)
+ // check to see if we're entering calibration mode
+ if (f && !plungerCalMode)
+ {
+ // reset the calibration in the configuration
cfg.plunger.cal.begin();
-
+
+ // start in state 0 (waiting to settle)
+ calState = 0;
+ calZeroPosSum = 0;
+ calZeroPosN = 0;
+ calRlsTimeSum = 0;
+ calRlsTimeN = 0;
+
+ // set the initial zero point to the current position
+ PlungerReading r;
+ if (plungerSensor->read(r))
+ {
+ // got a reading - use it as the initial zero point
+ cfg.plunger.cal.zero = r.pos;
+
+ // use it as the starting point for the settling watch
+ f1 = r;
+ }
+ else
+ {
+ // no reading available - use the default 1/6 position
+ cfg.plunger.cal.zero = 0xffff/6;
+
+ // we don't have a starting point for the setting watch
+ f1.pos = -65535;
+ f1.t = 0;
+ }
+ }
+
// remember the new mode
- cal = f;
+ plungerCalMode = f;
}
// is a firing event in progress?
bool isFiring() { return firing > 3; }
private:
+ // Read the sensor in calibration mode
+ void readForCal(PlungerReading r)
+ {
+ // if it's outside of the current calibration bounds,
+ // expand the bounds
+ if (r.pos < cfg.plunger.cal.min)
+ cfg.plunger.cal.min = r.pos;
+ if (r.pos > cfg.plunger.cal.max)
+ cfg.plunger.cal.max = r.pos;
+
+ // While we're in calibration mode, report the raw sensor
+ // position as the joystick value, adjusted to the JOYMAX scale.
+ z = int16_t((long(r.pos) * JOYMAX)/65535);
+
+ // for the release monitoring, take readings at least 2ms apart
+ if (uint32_t(r.t - f2.t) < 2000UL)
+ return;
+
+ // Check our state
+ switch (calState)
+ {
+ case 0:
+ // We're waiting for the position to settle. Check to see if
+ // we've been at the recent settling position long enough.
+ // Consider 1/50" (about 0.5mm) close enough to count as stable,
+ // to allow for some slight sensor noise from reading to reading.
+ if (abs(r.pos - f1.pos) > 65535/3/50)
+ {
+ // too far away - set the new starting point
+ f1 = r;
+ }
+ else if (uint32_t(r.t - f1.t) > 100000)
+ {
+ // We've been stationary long enough to count as settled.
+ // Wwitch to "at rest" state.
+ calState = 1;
+
+ // collect the new zero point for our average
+ calZeroPosSum += r.pos;
+ calZeroPosN += 1;
+
+ // use the new average as the zero point
+ cfg.plunger.cal.zero = uint16_t(calZeroPosSum / calZeroPosN);
+
+ // remember the current position in f1 to detect when we start
+ // moving again
+ f1 = r;
+ }
+ break;
+
+ case 1:
+ // At rest. We remain in this state until we see the plunger
+ // retract more than about 1/2".
+ if (r.pos - f1.pos > 65535/6)
+ {
+ // switch to state 2 - retracting
+ calState = 2;
+
+ // use f1 as the max so far
+ f1 = r;
+ }
+ break;
+
+ case 2:
+ // Away from rest position. Note the maximum point so far in f1,
+ // and monitor for release motions.
+ if (r.pos >= f1.pos)
+ {
+ // moving back - note the new max point on this run
+ f1 = r;
+ }
+ else
+ {
+ // moving forward - switch to possible release mode
+ calState = 3;
+ }
+ break;
+
+ case 3:
+ // Possible release. We have to move forward on each new
+ // reading, relative to two readings ago, to stay in release
+ // mode.
+ if (r.pos >= f3r.pos)
+ {
+ // not moving forward - switch back to retract mode
+ calState = 2;
+ f1 = r;
+ }
+ else if (r.pos <= cfg.plunger.cal.zero)
+ {
+ // Crossed the zero point. Figure the release time. If
+ // it's within a reasonable range, add it to the average.
+ // We'll ignore outliers on the assumption that they
+ // don't reflect actual release motions.
+ int dt = uint32_t(r.t - f1.t)/1000;
+ if (dt < 250 && dt > 25)
+ {
+ // count it in the average
+ calRlsTimeSum += dt;
+ calRlsTimeN += 1;
+
+ // store the new average in the configuration
+ cfg.plunger.cal.tRelease = uint8_t(calRlsTimeSum / calRlsTimeN);
+ cfg.plunger.cal.tRelease = dt; // $$$
+ }
+
+ // release done - switch to "waiting to settle" mode
+ calState = 0;
+ f1 = r;
+ }
+ break;
+ }
+
+ // f2 is always the immediately previous reading in cal mode,
+ // and f3r is the one before that
+ f3r = f2;
+ f2 = r;
+ }
+
+ // Calibration state. During calibration mode, we watch for release
+ // events, to measure the time it takes to complete the release
+ // motion; and we watch for the plunger to come to reset after a
+ // release, to gather statistics on the rest position.
+ // 0 = waiting to settle
+ // 1 = at rest
+ // 2 = retracting
+ // 3 = possibly releasing
+ uint8_t calState;
+
+ // Calibration zero point statistics.
+ // During calibration mode, we collect data on the rest position (the
+ // zero point) by watching for the plunger to come to rest after each
+ // release. We average these rest positions to get the calibrated
+ // zero point. We use the average because the real physical plunger
+ // itself doesn't come to rest at exactly the same spot every time,
+ // largely due to friction in the mechanism. To calculate the average,
+ // we keep a sum of the readings and a count of samples.
+ long calZeroPosSum;
+ int calZeroPosN;
+
+ // Calibration release time statistics.
+ // During calibration, we collect an average for the release time.
+ long calRlsTimeSum;
+ int calRlsTimeN;
+
// set a firing mode
inline void firingMode(int m)
{
@@ -2817,9 +2980,6 @@
// freely.
PlungerReading f3r;
- // flag: we're in calibration mode
- bool cal;
-
// next Z value to report to the joystick interface (in joystick
// distance units)
int z;
@@ -3125,9 +3285,9 @@
// Pixel dump mode - the host requested a dump of image sensor pixels
// (helpful for installing and setting up the sensor and light source)
-bool reportPix = false;
-uint8_t reportPixFlags; // pixel report flag bits (see ccdSensor.h)
-uint8_t reportPixVisMode; // pixel report visualization mode (not currently used)
+bool reportPlungerStat = false;
+uint8_t reportStatFlags; // pixel report flag bits (see ccdSensor.h)
+uint8_t reportStatVisMode; // pixel report visualization mode (not currently used)
// ---------------------------------------------------------------------------
@@ -3298,17 +3458,17 @@
// enter calibration mode
calBtnState = 3;
- plungerReader.calMode(true);
+ plungerReader.setCalMode(true);
calBtnTimer.reset();
break;
case 3:
- // 3 = pixel dump
+ // 3 = plunger sensor status report
// data[2] = flag bits
// data[3] = visualization mode
- reportPix = true;
- reportPixFlags = data[2];
- reportPixVisMode = data[3];
+ reportPlungerStat = true;
+ reportStatFlags = data[2];
+ reportStatVisMode = data[3];
// show purple until we finish sending the report
diagLED(1, 0, 1);
@@ -3320,7 +3480,7 @@
js.reportConfig(
numOutputs,
cfg.psUnitNo - 1, // report 0-15 range for unit number (we store 1-16 internally)
- cfg.plunger.cal.zero, cfg.plunger.cal.max,
+ cfg.plunger.cal.zero, cfg.plunger.cal.max, cfg.plunger.cal.tRelease,
nvm.valid());
break;
@@ -3351,6 +3511,24 @@
// data[2] = 1 to engage, 0 to disengage
setNightMode(data[2]);
break;
+
+ case 9:
+ // 9 = Config variable query.
+ // data[2] = config var ID
+ // data[3] = array index (for array vars: button assignments, output ports)
+ {
+ // set up the reply buffer with the variable ID data
+ uint8_t reply[8];
+ reply[1] = data[2];
+ reply[2] = data[3];
+
+ // query the value
+ configVarGet(reply);
+
+ // send the reply
+ js.reportConfigVar(reply + 1);
+ }
+ break;
}
}
else if (data[0] == 66)
@@ -3626,7 +3804,7 @@
calBtnTimer.reset();
// begin the plunger calibration limits
- plungerReader.calMode(true);
+ plungerReader.setCalMode(true);
}
break;
@@ -3650,7 +3828,7 @@
{
// exit calibration mode
calBtnState = 0;
- plungerReader.calMode(false);
+ plungerReader.setCalMode(false);
// save the updated configuration
cfg.plunger.cal.calibrated = 1;
@@ -3777,14 +3955,14 @@
jsReportTimer.reset();
}
- // If we're in pixel dump mode, report all pixel exposure values
- if (reportPix)
+ // If we're in sensor status mode, report all pixel exposure values
+ if (reportPlungerStat)
{
// send the report
- plungerSensor->sendExposureReport(js, reportPixFlags, reportPixVisMode);
+ plungerSensor->sendStatusReport(js, reportStatFlags, reportStatVisMode);
// we have satisfied this request
- reportPix = false;
+ reportPlungerStat = false;
}
// If joystick reports are turned off, send a generic status report
--- a/nullSensor.h Tue Mar 01 23:21:45 2016 +0000
+++ b/nullSensor.h Sat Mar 05 00:16:52 2016 +0000
@@ -15,6 +15,7 @@
virtual void init() { }
virtual bool read(PlungerReading &r) { return false; }
+ virtual uint32_t getAvgScanTime() { return 0; }
};
#endif /* NULLSENSOR_H */
--- a/plunger.h Tue Mar 01 23:21:45 2016 +0000
+++ b/plunger.h Sat Mar 05 00:16:52 2016 +0000
@@ -72,29 +72,69 @@
// that it wasn't possible to take a valid reading.
virtual bool read(PlungerReading &r) = 0;
- // Send an exposure report to the host, via the joystick interface. This
- // is for image sensors, and can be omitted by other sensor types. For
- // image sensors, this takes one exposure and sends all pixels to the host
- // through special joystick reports. This is used by tools on the host PC
- // to let the user view the low-level sensor pixel data, which can be
- // helpful during installation to adjust the sensor positioning and light
- // source.
+ // Send a sensor status report to the host, via the joystick interface.
+ // This provides some common information for all sensor types, and also
+ // includes a full image snapshot of the current sensor pixels for
+ // imaging sensor types.
+ //
+ // The default implementation here sends the common information
+ // packet, with the pixel size set to 0.
+ //
+ // 'flags' is a combination of bit flags:
+ // 0x01 -> low-res scan (default is high res scan)
//
- // Flag bits:
- // 0x01 -> low res scan (default is high res scan)
+ // Low-res scan mode means that the sensor should send a scaled-down
+ // image, at a reduced size determined by the sensor subtype. The
+ // default if this flag isn't set is to send the full image, at the
+ // sensor's native pixel size. The low-res version is a reduced size
+ // image in the normal sense of scaling down a photo image, keeping the
+ // image intact but at reduced resolution. Note that low-res mode
+ // doesn't affect the ongoing sensor operation at all. It only applies
+ // to this single pixel report. The purpose is simply to reduce the USB
+ // transmission time for the image, to allow for a faster frame rate for
+ // displaying the sensor image in real time on the PC. For a high-res
+ // sensor like the TSL1410R, sending the full pixel array by USB takes
+ // so long that the frame rate is way below regular video rates.
//
- // Visualization modes:
- // 0 -> raw pixels
- // 1 -> processed pixels (noise reduction, etc)
- // 2 -> exaggerated contrast mode
- // 3 -> edge visualization
- //
- // If processed mode is selected, the sensor should apply any pixel
- // processing it normally does when taking a plunger position reading,
- // such as exposure correction, noise reduction, etc. In raw mode, we
- // simply send the pixels as read from the sensor. Both modes are useful
- // in setting up the physical sensor.
- virtual void sendExposureReport(class USBJoystick &js, uint8_t flags, uint8_t visMode) { }
+ // 'visMode' is the visualization mode. This is currently unused. (In
+ // a preliminary design, the CCD sensor was going to pre-process the pixels
+ // through some filters, such as noise reduction and contrast enhnacement,
+ // before detecting the edge. 'visMode' was meant to select how much of
+ // this processing to apply to the pixels transmitted to the host, to allow
+ // the user to see each stage of the processing from raw sensor pixels to
+ // fully processed pixels. But the filtering process proved to be too slow,
+ // so in the end we removed it and now just do the edge detection directly
+ // on the raw pixels. This makes the visualization mode unnecessary.
+ // However, we're keeping the parameter in case it becomes useful in the
+ // future. Note that this could be used for special displays that don't
+ // reflect actual pre-processing that would be done in normal edge
+ // detection, but instead visualize the internals of the algorithm, as
+ // a debugging or optimization tool.)
+ virtual void sendStatusReport(class USBJoystick &js, uint8_t flags, uint8_t visMode)
+ {
+ // read the current position
+ int pos = 0xFFFF;
+ PlungerReading r;
+ if (read(r))
+ {
+ // success - scale it to 0..4095 (the generic scale used
+ // for non-imaging sensors)
+ pos = int(r.pos*4095L / 65535L);
+ }
+
+ // Send the common status information, indicating 0 pixels, standard
+ // sensor orientation, and zero processing time. Non-imaging sensors
+ // usually don't have any way to detect the orientation, so they have
+ // to rely on being installed in a pre-determined direction. Non-
+ // imaging sensors usually have negligible analysis time (the only
+ // "analysis" is usually nothing more than a multiply to rescale an
+ // ADC sample), so there's no point in collecting actual timing data;
+ // just report zero.
+ js.sendPlungerStatus(0, pos, 1, getAvgScanTime(), 0);
+ }
+
+ // Get the average sensor scan time in microseconds
+ virtual uint32_t getAvgScanTime() = 0;
protected:
};
--- a/potSensor.h Tue Mar 01 23:21:45 2016 +0000
+++ b/potSensor.h Sat Mar 05 00:16:52 2016 +0000
@@ -55,17 +55,25 @@
+ uint32_t(pot.read_u16())
) / 5U);
- // Get the ending time of the sample, and figure the indicated
+ // Get the elapsed time of the sample, and figure the indicated
// sample time as the midpoint between the start and end times.
// (Note that the timer might overflow the uint32_t between t0
// and now, in which case it will appear that now < t0. The
// calculation will always work out right anyway, because it's
// effectively performed mod 2^32-1.)
- r.t = t0 + (timer.read_us() - t0)/2;
+ uint32_t dt = timer.read_us() - t0;
+ r.t = t0 + dt/2;
+
+ // add the current sample to our timing statistics
+ totScanTime += dt;
+ nScans += 1;
// success
return true;
}
+
+ // figure the average scan time in microseconds
+ virtual uint32_t getAvgScanTime() { return uint32_t(totScanTime/nScans); }
private:
// analog input for the pot wiper
@@ -73,4 +81,8 @@
// timer for input timestamps
Timer timer;
+
+ // total sensor scan time in microseconds, and number of scans completed
+ long long totScanTime;
+ int nScans;
};
