Mike R / Mbed 2 deprecated Pinscape_Controller_V2

Dependencies:   mbed FastIO FastPWM USBDevice

Fork of Pinscape_Controller by Mike R

Committer:
mjr
Date:
Mon Feb 15 20:30:32 2016 +0000
Revision:
45:c42166b2878c
Parent:
44:b5ac89b9cd5d
Child:
47:df7a88cd249c
More work in progress on CCD speedups;

Who changed what in which revision?

UserRevisionLine numberNew contents of line
mjr 17:ab3cec0c8bf4 1 // CCD plunger sensor
mjr 17:ab3cec0c8bf4 2 //
mjr 35:e959ffba78fd 3 // This class implements our generic plunger sensor interface for the
mjr 35:e959ffba78fd 4 // TAOS TSL1410R and TSL1412R linear sensor arrays. Physically, these
mjr 35:e959ffba78fd 5 // sensors are installed with their image window running parallel to
mjr 35:e959ffba78fd 6 // the plunger rod, spanning the travel range of the plunger tip.
mjr 35:e959ffba78fd 7 // A light source is positioned on the opposite side of the rod, so
mjr 35:e959ffba78fd 8 // that the rod casts a shadow on the sensor. We sense the position
mjr 35:e959ffba78fd 9 // by looking for the edge of the shadow.
mjr 35:e959ffba78fd 10 //
mjr 35:e959ffba78fd 11 // These sensors can take an image quickly, but it takes a significant
mjr 35:e959ffba78fd 12 // amount of time to transfer the image data from the sensor to the
mjr 35:e959ffba78fd 13 // microcontroller, since each pixel's analog voltage level must be
mjr 35:e959ffba78fd 14 // sampled serially. It takes about 20us to sample a pixel accurately.
mjr 35:e959ffba78fd 15 // The TSL1410R has 1280 pixels, and the 1412R has 1536. Sampling
mjr 35:e959ffba78fd 16 // every pixel would thus take about 25ms or 30ms respectively.
mjr 35:e959ffba78fd 17 // This is too slow for a responsive feel in the UI, and much too
mjr 35:e959ffba78fd 18 // slow to track the plunger release motion in real time. To improve
mjr 35:e959ffba78fd 19 // on the read speed, we only sample a subset of pixels for each
mjr 35:e959ffba78fd 20 // reading - for higher speed at the expense of spatial resolution.
mjr 35:e959ffba78fd 21 // The sensor's native resolution is much higher than we need, so
mjr 35:e959ffba78fd 22 // this is a perfectly equitable trade.
mjr 17:ab3cec0c8bf4 23
mjr 35:e959ffba78fd 24 #include "plunger.h"
mjr 17:ab3cec0c8bf4 25
mjr 17:ab3cec0c8bf4 26
mjr 25:e22b88bd783a 27 // PlungerSensor interface implementation for the CCD
mjr 35:e959ffba78fd 28 class PlungerSensorCCD: public PlungerSensor
mjr 17:ab3cec0c8bf4 29 {
mjr 17:ab3cec0c8bf4 30 public:
mjr 44:b5ac89b9cd5d 31 PlungerSensorCCD(
mjr 44:b5ac89b9cd5d 32 int nativePix, int highResPix, int lowResPix,
mjr 44:b5ac89b9cd5d 33 PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 43:7a6364d82a41 34 : ccd(nativePix, si, clock, ao1, ao2)
mjr 17:ab3cec0c8bf4 35 {
mjr 44:b5ac89b9cd5d 36 this->highResPix = highResPix;
mjr 44:b5ac89b9cd5d 37 this->lowResPix = lowResPix;
mjr 44:b5ac89b9cd5d 38 this->pix = new uint16_t[highResPix];
mjr 17:ab3cec0c8bf4 39 }
mjr 17:ab3cec0c8bf4 40
mjr 17:ab3cec0c8bf4 41 // initialize
mjr 35:e959ffba78fd 42 virtual void init()
mjr 17:ab3cec0c8bf4 43 {
mjr 17:ab3cec0c8bf4 44 // flush any random power-on values from the CCD's integration
mjr 17:ab3cec0c8bf4 45 // capacitors, and start the first integration cycle
mjr 17:ab3cec0c8bf4 46 ccd.clear();
mjr 17:ab3cec0c8bf4 47 }
mjr 17:ab3cec0c8bf4 48
mjr 17:ab3cec0c8bf4 49 // Perform a low-res scan of the sensor.
mjr 44:b5ac89b9cd5d 50 virtual bool lowResScan(float &pos)
mjr 17:ab3cec0c8bf4 51 {
mjr 44:b5ac89b9cd5d 52 // If we haven't sensed the direction yet, do a high-res scan
mjr 44:b5ac89b9cd5d 53 // first, so that we can get accurate direction data. Return
mjr 44:b5ac89b9cd5d 54 // the result of the high-res scan if successful, since it
mjr 44:b5ac89b9cd5d 55 // provides even better data than the caller requested from us.
mjr 44:b5ac89b9cd5d 56 // This will take longer than the caller wanted, but it should
mjr 44:b5ac89b9cd5d 57 // only be necessary to do this once after the system stabilizes
mjr 44:b5ac89b9cd5d 58 // after startup, so the timing difference won't affect normal
mjr 44:b5ac89b9cd5d 59 // operation.
mjr 44:b5ac89b9cd5d 60 if (dir == 0)
mjr 44:b5ac89b9cd5d 61 return highResScan(pos);
mjr 44:b5ac89b9cd5d 62
mjr 17:ab3cec0c8bf4 63 // read the pixels at low resolution
mjr 44:b5ac89b9cd5d 64 ccd.read(pix, lowResPix);
mjr 17:ab3cec0c8bf4 65
mjr 44:b5ac89b9cd5d 66 // set the loop variables for the sensor orientation
mjr 44:b5ac89b9cd5d 67 int si = 0;
mjr 44:b5ac89b9cd5d 68 if (dir < 0)
mjr 44:b5ac89b9cd5d 69 si = lowResPix - 1;
mjr 44:b5ac89b9cd5d 70
mjr 44:b5ac89b9cd5d 71 // Figure the shadow edge threshold. Use the midpoint between
mjr 44:b5ac89b9cd5d 72 // the average levels of a few pixels at each end.
mjr 44:b5ac89b9cd5d 73 uint16_t shadow = uint16_t(
mjr 44:b5ac89b9cd5d 74 (long(pix[0]) + long(pix[1]) + long(pix[2])
mjr 44:b5ac89b9cd5d 75 + long(pix[lowResPix-1]) + long(pix[lowResPix-2]) + long(pix[lowResPix-3])
mjr 44:b5ac89b9cd5d 76 )/6);
mjr 17:ab3cec0c8bf4 77
mjr 17:ab3cec0c8bf4 78 // find the current tip position
mjr 44:b5ac89b9cd5d 79 for (int n = 0 ; n < lowResPix ; ++n, si += dir)
mjr 17:ab3cec0c8bf4 80 {
mjr 17:ab3cec0c8bf4 81 // check to see if we found the shadow
mjr 17:ab3cec0c8bf4 82 if (pix[si] <= shadow)
mjr 17:ab3cec0c8bf4 83 {
mjr 44:b5ac89b9cd5d 84 // got it - normalize to the 0.0..1.0 range and return success
mjr 44:b5ac89b9cd5d 85 pos = float(n)/lowResPix;
mjr 35:e959ffba78fd 86 return true;
mjr 17:ab3cec0c8bf4 87 }
mjr 17:ab3cec0c8bf4 88 }
mjr 17:ab3cec0c8bf4 89
mjr 35:e959ffba78fd 90 // didn't find a shadow - return failure
mjr 35:e959ffba78fd 91 return false;
mjr 17:ab3cec0c8bf4 92 }
mjr 17:ab3cec0c8bf4 93
mjr 17:ab3cec0c8bf4 94 // Perform a high-res scan of the sensor.
mjr 44:b5ac89b9cd5d 95 virtual bool highResScan(float &pos)
mjr 17:ab3cec0c8bf4 96 {
mjr 17:ab3cec0c8bf4 97 // read the array
mjr 44:b5ac89b9cd5d 98 ccd.read(pix, highResPix);
mjr 17:ab3cec0c8bf4 99
mjr 44:b5ac89b9cd5d 100 // Sense the orientation of the sensor if we haven't already. If
mjr 44:b5ac89b9cd5d 101 // that fails, we must not have enough contrast to find a shadow edge
mjr 44:b5ac89b9cd5d 102 // in the image, so there's no point in looking any further - just
mjr 44:b5ac89b9cd5d 103 // return failure.
mjr 44:b5ac89b9cd5d 104 if (dir == 0 && !senseOrientation(highResPix))
mjr 44:b5ac89b9cd5d 105 return false;
mjr 44:b5ac89b9cd5d 106
mjr 44:b5ac89b9cd5d 107 // Get the average brightness for a few pixels at each end.
mjr 44:b5ac89b9cd5d 108 long b1 = (long(pix[0]) + long(pix[1]) + long(pix[2]) + long(pix[3]) + long(pix[4])) / 5;
mjr 44:b5ac89b9cd5d 109 long b2 = (long(pix[highResPix-1]) + long(pix[highResPix-2]) + long(pix[highResPix-3])
mjr 44:b5ac89b9cd5d 110 + long(pix[highResPix-4]) + long(pix[highResPix-5])) / 5;
mjr 44:b5ac89b9cd5d 111
mjr 17:ab3cec0c8bf4 112 // Work from the bright end to the dark end. VP interprets the
mjr 17:ab3cec0c8bf4 113 // Z axis value as the amount the plunger is pulled: zero is the
mjr 17:ab3cec0c8bf4 114 // rest position, and the axis maximum is fully pulled. So we
mjr 17:ab3cec0c8bf4 115 // essentially want to report how much of the sensor is lit,
mjr 17:ab3cec0c8bf4 116 // since this increases as the plunger is pulled back.
mjr 44:b5ac89b9cd5d 117 int si = 0;
mjr 18:5e890ebd0023 118 long hi = b1;
mjr 44:b5ac89b9cd5d 119 if (dir < 0)
mjr 44:b5ac89b9cd5d 120 si = highResPix - 1, hi = b2;
mjr 17:ab3cec0c8bf4 121
mjr 17:ab3cec0c8bf4 122 // Figure the shadow threshold. In practice, the portion of the
mjr 17:ab3cec0c8bf4 123 // sensor that's not in shadow has all pixels consistently near
mjr 17:ab3cec0c8bf4 124 // saturation; the first drop in brightness is pretty reliably the
mjr 17:ab3cec0c8bf4 125 // start of the shadow. So set the threshold level to be closer
mjr 17:ab3cec0c8bf4 126 // to the bright end's brightness level, so that we detect the leading
mjr 17:ab3cec0c8bf4 127 // edge if the shadow isn't perfectly sharp. Use the point 1/3 of
mjr 17:ab3cec0c8bf4 128 // the way down from the high top the low side, so:
mjr 17:ab3cec0c8bf4 129 //
mjr 17:ab3cec0c8bf4 130 // threshold = lo + (hi - lo)*2/3
mjr 17:ab3cec0c8bf4 131 // = lo + hi*2/3 - lo*2/3
mjr 17:ab3cec0c8bf4 132 // = lo - lo*2/3 + hi*2/3
mjr 17:ab3cec0c8bf4 133 // = lo*1/3 + hi*2/3
mjr 17:ab3cec0c8bf4 134 // = (lo + hi*2)/3
mjr 17:ab3cec0c8bf4 135 //
mjr 18:5e890ebd0023 136 // Now, 'lo' is always one of b1 or b2, and 'hi' is the other
mjr 18:5e890ebd0023 137 // one, so we can rewrite this as:
mjr 18:5e890ebd0023 138 long midpt = (b1 + b2 + hi)/3;
mjr 17:ab3cec0c8bf4 139
mjr 17:ab3cec0c8bf4 140 // If we have enough contrast, proceed with the scan.
mjr 17:ab3cec0c8bf4 141 //
mjr 17:ab3cec0c8bf4 142 // If the bright end and dark end don't differ by enough, skip this
mjr 17:ab3cec0c8bf4 143 // reading entirely. Either we have an overexposed or underexposed frame,
mjr 17:ab3cec0c8bf4 144 // or the sensor is misaligned and is either fully in or out of shadow
mjr 17:ab3cec0c8bf4 145 // (it's supposed to be mounted such that the edge of the shadow always
mjr 17:ab3cec0c8bf4 146 // falls within the sensor, for any possible plunger position).
mjr 18:5e890ebd0023 147 if (labs(b1 - b2) > 0x1000)
mjr 17:ab3cec0c8bf4 148 {
mjr 17:ab3cec0c8bf4 149 uint16_t *pixp = pix + si;
mjr 44:b5ac89b9cd5d 150 for (int n = 0 ; n < highResPix ; ++n, pixp += dir)
mjr 17:ab3cec0c8bf4 151 {
mjr 17:ab3cec0c8bf4 152 // if we've crossed the midpoint, report this position
mjr 18:5e890ebd0023 153 if (long(*pixp) < midpt)
mjr 17:ab3cec0c8bf4 154 {
mjr 44:b5ac89b9cd5d 155 // normalize to the 0.0..1.0 range and return success
mjr 44:b5ac89b9cd5d 156 pos = float(n)/highResPix;
mjr 17:ab3cec0c8bf4 157 return true;
mjr 17:ab3cec0c8bf4 158 }
mjr 17:ab3cec0c8bf4 159 }
mjr 17:ab3cec0c8bf4 160 }
mjr 17:ab3cec0c8bf4 161
mjr 17:ab3cec0c8bf4 162 // we didn't find a shadow - return no reading
mjr 17:ab3cec0c8bf4 163 return false;
mjr 17:ab3cec0c8bf4 164 }
mjr 17:ab3cec0c8bf4 165
mjr 44:b5ac89b9cd5d 166 // Infer the sensor orientation from the image data. This determines
mjr 44:b5ac89b9cd5d 167 // which end of the array has the brighter pixels. In some cases it
mjr 44:b5ac89b9cd5d 168 // might not be possible to tell: if the light source is turned off,
mjr 44:b5ac89b9cd5d 169 // or if the plunger is all the way to one extreme so that the entire
mjr 44:b5ac89b9cd5d 170 // pixel array is in shadow or in full light. To sense the direction
mjr 44:b5ac89b9cd5d 171 // we need to have a sufficient difference in brightness between the
mjr 44:b5ac89b9cd5d 172 // two ends of the array to be confident that one end is in shadow
mjr 44:b5ac89b9cd5d 173 // and the other isn't. On success, sets member variable 'dir' and
mjr 44:b5ac89b9cd5d 174 // returns true; on failure (i.e., we don't have sufficient contrast
mjr 44:b5ac89b9cd5d 175 // to sense the orientation), returns false and leaves 'dir' unset.
mjr 44:b5ac89b9cd5d 176 bool senseOrientation(int n)
mjr 44:b5ac89b9cd5d 177 {
mjr 44:b5ac89b9cd5d 178 // get the total brightness for the first few pixels at
mjr 44:b5ac89b9cd5d 179 // each end of the array (a proxy for the average - just
mjr 44:b5ac89b9cd5d 180 // save time by skipping the divide-by-N)
mjr 44:b5ac89b9cd5d 181 long a = long(pix[0]) + long(pix[1]) + long(pix[2])
mjr 44:b5ac89b9cd5d 182 + long(pix[3]) + long(pix[4]);
mjr 44:b5ac89b9cd5d 183 long b = long(pix[n-1]) + long(pix[n-2]) + long(pix[n-3])
mjr 44:b5ac89b9cd5d 184 + long(pix[n-4]) + long(pix[n-5]);
mjr 44:b5ac89b9cd5d 185
mjr 44:b5ac89b9cd5d 186 // if the difference is too small, we can't tell
mjr 44:b5ac89b9cd5d 187 const long minPct = 10;
mjr 44:b5ac89b9cd5d 188 const long minDiff = 65535*5*minPct/100;
mjr 44:b5ac89b9cd5d 189 if (labs(a - b) < minDiff)
mjr 44:b5ac89b9cd5d 190 return false;
mjr 44:b5ac89b9cd5d 191
mjr 44:b5ac89b9cd5d 192 // we now know the orientation - set the 'dir' member variable
mjr 44:b5ac89b9cd5d 193 // for future use
mjr 44:b5ac89b9cd5d 194 if (a > b)
mjr 44:b5ac89b9cd5d 195 dir = 1;
mjr 44:b5ac89b9cd5d 196 else
mjr 44:b5ac89b9cd5d 197 dir = -1;
mjr 44:b5ac89b9cd5d 198
mjr 44:b5ac89b9cd5d 199 // success
mjr 44:b5ac89b9cd5d 200 return true;
mjr 44:b5ac89b9cd5d 201 }
mjr 44:b5ac89b9cd5d 202
mjr 45:c42166b2878c 203 // Send an exposure report to the joystick interface.
mjr 45:c42166b2878c 204 //
mjr 45:c42166b2878c 205 // Mode bits:
mjr 45:c42166b2878c 206 // 0x01 -> send processed pixels (default is raw pixels)
mjr 45:c42166b2878c 207 // 0x02 -> low res scan (default is high res scan)
mjr 45:c42166b2878c 208 virtual void sendExposureReport(USBJoystick &js, uint8_t mode)
mjr 17:ab3cec0c8bf4 209 {
mjr 45:c42166b2878c 210 // get the number of pixels according to the high res/low res mode
mjr 45:c42166b2878c 211 int npix = (mode & 0x02 ? lowResPix : highResPix);
mjr 45:c42166b2878c 212
mjr 45:c42166b2878c 213 // do a scan
mjr 45:c42166b2878c 214 ccd.read(pix, npix);
mjr 43:7a6364d82a41 215
mjr 17:ab3cec0c8bf4 216 // send reports for all pixels
mjr 17:ab3cec0c8bf4 217 int idx = 0;
mjr 45:c42166b2878c 218 while (idx < npix)
mjr 45:c42166b2878c 219 js.updateExposure(idx, npix, pix);
mjr 17:ab3cec0c8bf4 220
mjr 17:ab3cec0c8bf4 221 // The pixel dump requires many USB reports, since each report
mjr 17:ab3cec0c8bf4 222 // can only send a few pixel values. An integration cycle has
mjr 17:ab3cec0c8bf4 223 // been running all this time, since each read starts a new
mjr 17:ab3cec0c8bf4 224 // cycle. Our timing is longer than usual on this round, so
mjr 17:ab3cec0c8bf4 225 // the integration won't be comparable to a normal cycle. Throw
mjr 17:ab3cec0c8bf4 226 // this one away by doing a read now, and throwing it away - that
mjr 17:ab3cec0c8bf4 227 // will get the timing of the *next* cycle roughly back to normal.
mjr 44:b5ac89b9cd5d 228 ccd.read(pix, highResPix);
mjr 17:ab3cec0c8bf4 229 }
mjr 17:ab3cec0c8bf4 230
mjr 35:e959ffba78fd 231 protected:
mjr 45:c42166b2878c 232 // Pixel buffer. We allocate this to be big enough for the high-res scan,
mjr 45:c42166b2878c 233 // which is the most pixels we'll need to capture.
mjr 35:e959ffba78fd 234 uint16_t *pix;
mjr 44:b5ac89b9cd5d 235
mjr 44:b5ac89b9cd5d 236 // number of pixels in a high-res scan
mjr 44:b5ac89b9cd5d 237 int highResPix;
mjr 44:b5ac89b9cd5d 238
mjr 44:b5ac89b9cd5d 239 // number of pixels in a low-res scan
mjr 44:b5ac89b9cd5d 240 int lowResPix;
mjr 17:ab3cec0c8bf4 241
mjr 44:b5ac89b9cd5d 242 // Sensor orientation. +1 means that the "tip" end - which is always
mjr 44:b5ac89b9cd5d 243 // the brighter end in our images - is at the 0th pixel in the array.
mjr 44:b5ac89b9cd5d 244 // -1 means that the tip is at the nth pixel in the array. 0 means
mjr 44:b5ac89b9cd5d 245 // that we haven't figured it out yet.
mjr 44:b5ac89b9cd5d 246 int dir;
mjr 43:7a6364d82a41 247
mjr 44:b5ac89b9cd5d 248 public:
mjr 17:ab3cec0c8bf4 249 // the low-level interface to the CCD hardware
mjr 35:e959ffba78fd 250 TSL1410R ccd;
mjr 17:ab3cec0c8bf4 251 };
mjr 35:e959ffba78fd 252
mjr 35:e959ffba78fd 253
mjr 35:e959ffba78fd 254 // TSL1410R sensor
mjr 35:e959ffba78fd 255 class PlungerSensorTSL1410R: public PlungerSensorCCD
mjr 35:e959ffba78fd 256 {
mjr 35:e959ffba78fd 257 public:
mjr 35:e959ffba78fd 258 PlungerSensorTSL1410R(PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 45:c42166b2878c 259 : PlungerSensorCCD(1280, 256, 80, si, clock, ao1, ao2)
mjr 35:e959ffba78fd 260 {
mjr 35:e959ffba78fd 261 }
mjr 35:e959ffba78fd 262 };
mjr 35:e959ffba78fd 263
mjr 35:e959ffba78fd 264 // TSL1412R
mjr 35:e959ffba78fd 265 class PlungerSensorTSL1412R: public PlungerSensorCCD
mjr 35:e959ffba78fd 266 {
mjr 35:e959ffba78fd 267 public:
mjr 35:e959ffba78fd 268 PlungerSensorTSL1412R(PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 45:c42166b2878c 269 : PlungerSensorCCD(1536, 256, 128, si, clock, ao1, ao2)
mjr 35:e959ffba78fd 270 {
mjr 35:e959ffba78fd 271 }
mjr 35:e959ffba78fd 272 };