Mike R / Mbed 2 deprecated Pinscape_Controller_V2

Dependencies:   mbed FastIO FastPWM USBDevice

Fork of Pinscape_Controller by Mike R

Committer:
mjr
Date:
Tue Mar 01 23:21:45 2016 +0000
Revision:
51:57eb311faafa
Parent:
48:058ace2aed1d
Child:
52:8298b2a73eb2
Saving old CCD processing modes

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 17:ab3cec0c8bf4 10
mjr 35:e959ffba78fd 11 #include "plunger.h"
mjr 17:ab3cec0c8bf4 12
mjr 17:ab3cec0c8bf4 13
mjr 25:e22b88bd783a 14 // PlungerSensor interface implementation for the CCD
mjr 35:e959ffba78fd 15 class PlungerSensorCCD: public PlungerSensor
mjr 17:ab3cec0c8bf4 16 {
mjr 17:ab3cec0c8bf4 17 public:
mjr 47:df7a88cd249c 18 PlungerSensorCCD(int nativePix, PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 43:7a6364d82a41 19 : ccd(nativePix, si, clock, ao1, ao2)
mjr 17:ab3cec0c8bf4 20 {
mjr 47:df7a88cd249c 21 // we don't know the direction yet
mjr 47:df7a88cd249c 22 dir = 0;
mjr 47:df7a88cd249c 23
mjr 48:058ace2aed1d 24 // set the midpoint history arbitrarily to the absolute halfway point
mjr 48:058ace2aed1d 25 memset(midpt, 127, sizeof(midpt));
mjr 48:058ace2aed1d 26 midptIdx = 0;
mjr 48:058ace2aed1d 27
mjr 51:57eb311faafa 28 // no history readings yet
mjr 51:57eb311faafa 29 histIdx = 0;
mjr 17:ab3cec0c8bf4 30 }
mjr 17:ab3cec0c8bf4 31
mjr 17:ab3cec0c8bf4 32 // initialize
mjr 35:e959ffba78fd 33 virtual void init()
mjr 17:ab3cec0c8bf4 34 {
mjr 17:ab3cec0c8bf4 35 // flush any random power-on values from the CCD's integration
mjr 17:ab3cec0c8bf4 36 // capacitors, and start the first integration cycle
mjr 17:ab3cec0c8bf4 37 ccd.clear();
mjr 17:ab3cec0c8bf4 38 }
mjr 17:ab3cec0c8bf4 39
mjr 48:058ace2aed1d 40 // Read the plunger position
mjr 48:058ace2aed1d 41 virtual bool read(PlungerReading &r)
mjr 17:ab3cec0c8bf4 42 {
mjr 48:058ace2aed1d 43 // start reading the next pixel array - this also waits for any
mjr 48:058ace2aed1d 44 // previous read to finish, ensuring that we have stable pixel
mjr 48:058ace2aed1d 45 // data in the capture buffer
mjr 47:df7a88cd249c 46 ccd.startCapture();
mjr 44:b5ac89b9cd5d 47
mjr 48:058ace2aed1d 48 // get the image array from the last capture
mjr 47:df7a88cd249c 49 uint8_t *pix;
mjr 47:df7a88cd249c 50 int n;
mjr 48:058ace2aed1d 51 uint32_t tpix;
mjr 48:058ace2aed1d 52 ccd.getPix(pix, n, tpix);
mjr 17:ab3cec0c8bf4 53
mjr 48:058ace2aed1d 54 // process the pixels and look for the edge position
mjr 48:058ace2aed1d 55 int pixpos;
mjr 48:058ace2aed1d 56 if (process(pix, n, pixpos, 0))
mjr 51:57eb311faafa 57 {
mjr 48:058ace2aed1d 58 // Normalize to the 16-bit range. Our reading from the
mjr 48:058ace2aed1d 59 // sensor is a pixel position, 0..n-1. To rescale to the
mjr 48:058ace2aed1d 60 // normalized range, figure pixpos*65535/(n-1).
mjr 48:058ace2aed1d 61 r.pos = uint16_t(((pixpos << 16) - pixpos) / (n-1));
mjr 48:058ace2aed1d 62 r.t = tpix;
mjr 44:b5ac89b9cd5d 63
mjr 47:df7a88cd249c 64 // success
mjr 47:df7a88cd249c 65 return true;
mjr 47:df7a88cd249c 66 }
mjr 47:df7a88cd249c 67 else
mjr 47:df7a88cd249c 68 {
mjr 47:df7a88cd249c 69 // no position found
mjr 47:df7a88cd249c 70 return false;
mjr 47:df7a88cd249c 71 }
mjr 47:df7a88cd249c 72 }
mjr 17:ab3cec0c8bf4 73
mjr 47:df7a88cd249c 74 // Process an image. Applies noise reduction and looks for edges.
mjr 47:df7a88cd249c 75 // If we detect the plunger position, we set 'pos' to the pixel location
mjr 48:058ace2aed1d 76 // of the edge and return true; otherwise we return false. The 'pos'
mjr 48:058ace2aed1d 77 // value returned, if any, is adjusted for sensor orientation so that
mjr 48:058ace2aed1d 78 // it reflects the logical plunger position.
mjr 48:058ace2aed1d 79 //
mjr 48:058ace2aed1d 80 // 'visMode' is the visualization mode. If non-zero, we replace the
mjr 48:058ace2aed1d 81 // pixels in the 'pix' array with a new version for visual presentation
mjr 48:058ace2aed1d 82 // to the user, as an aid to setup and debugging. The visualization
mjr 48:058ace2aed1d 83 // modes are:
mjr 48:058ace2aed1d 84 //
mjr 48:058ace2aed1d 85 // 0 = No visualization
mjr 48:058ace2aed1d 86 // 1 = High contrast: we set each pixel to white or black according
mjr 48:058ace2aed1d 87 // to whether it's brighter or dimmer than the midpoint brightness
mjr 48:058ace2aed1d 88 // we use to seek the shadow edge. This mode makes the edge
mjr 48:058ace2aed1d 89 // positions visually apparent.
mjr 48:058ace2aed1d 90 // 2 = Edge mode: we set all pixels to white except for detected edges,
mjr 48:058ace2aed1d 91 // which we set to black.
mjr 48:058ace2aed1d 92 //
mjr 48:058ace2aed1d 93 // The 'pix' array is overwritten with the processed pixels. If visMode
mjr 48:058ace2aed1d 94 // is 0, this reflects only the basic preprocessing we do in an edge
mjr 48:058ace2aed1d 95 // scan, such as noise reduction. For other visualization modes, the
mjr 48:058ace2aed1d 96 // pixels are replaced by the visualization results.
mjr 48:058ace2aed1d 97 bool process(uint8_t *pix, int &n, int &pos, int visMode)
mjr 47:df7a88cd249c 98 {
mjr 48:058ace2aed1d 99 // Get the levels at each end
mjr 48:058ace2aed1d 100 int a = (int(pix[0]) + pix[1] + pix[2] + pix[3] + pix[4])/5;
mjr 48:058ace2aed1d 101 int b = (int(pix[n-1]) + pix[n-2] + pix[n-3] + pix[n-4] + pix[n-5])/5;
mjr 47:df7a88cd249c 102
mjr 48:058ace2aed1d 103 // Figure the sensor orientation based on the relative
mjr 48:058ace2aed1d 104 // brightness levels at the opposite ends of the image
mjr 48:058ace2aed1d 105 int pi;
mjr 48:058ace2aed1d 106 if (a > b+10)
mjr 48:058ace2aed1d 107 {
mjr 48:058ace2aed1d 108 // left end is brighter - standard orientation
mjr 48:058ace2aed1d 109 dir = 1;
mjr 48:058ace2aed1d 110 pi = 5;
mjr 48:058ace2aed1d 111 }
mjr 48:058ace2aed1d 112 else if (b > a+10)
mjr 48:058ace2aed1d 113 {
mjr 48:058ace2aed1d 114 // right end is brighter - reverse orientation
mjr 48:058ace2aed1d 115 dir = -1;
mjr 48:058ace2aed1d 116 pi = n - 6;
mjr 48:058ace2aed1d 117 }
mjr 48:058ace2aed1d 118 else if (dir != 0)
mjr 17:ab3cec0c8bf4 119 {
mjr 48:058ace2aed1d 120 // We don't have enough contrast to detect the orientation
mjr 48:058ace2aed1d 121 // from this image, so either the image is too overexposed
mjr 48:058ace2aed1d 122 // or underexposed to be useful, or the entire sensor is in
mjr 48:058ace2aed1d 123 // light or darkness. We'll assume the latter: the plunger
mjr 48:058ace2aed1d 124 // is blocking the whole window or isn't in the frame at
mjr 48:058ace2aed1d 125 // all. We'll also assume that the exposure level is
mjr 48:058ace2aed1d 126 // similar to that in recent frames where we *did* detect
mjr 48:058ace2aed1d 127 // the direction. This means that if the new exposure level
mjr 48:058ace2aed1d 128 // (which is about the same over the whole array) is less
mjr 48:058ace2aed1d 129 // than the recent midpoint, we must be entirely blocked
mjr 48:058ace2aed1d 130 // by the plunger, so it's all the way forward; if the
mjr 48:058ace2aed1d 131 // brightness is above the recent midpoint, we must be
mjr 48:058ace2aed1d 132 // entirely exposed, so the plunger is all the way back.
mjr 48:058ace2aed1d 133
mjr 48:058ace2aed1d 134 // figure the average of the recent midpoint brightnesses
mjr 48:058ace2aed1d 135 int sum = 0;
mjr 48:058ace2aed1d 136 for (int i = 0 ; i < countof(midpt) ; sum += midpt[i++]) ;
mjr 48:058ace2aed1d 137 sum /= 10;
mjr 48:058ace2aed1d 138
mjr 48:058ace2aed1d 139 // Figure the average of our two ends. We have very
mjr 48:058ace2aed1d 140 // little contrast overall, so we already know that the
mjr 48:058ace2aed1d 141 // two ends are about the same, but we can't expect the
mjr 48:058ace2aed1d 142 // lighting to be perfectly uniform. Averaging the ends
mjr 48:058ace2aed1d 143 // will smooth out variations due to light source placement,
mjr 48:058ace2aed1d 144 // sensor noise, etc.
mjr 48:058ace2aed1d 145 a = (a+b)/2;
mjr 48:058ace2aed1d 146
mjr 48:058ace2aed1d 147 // Check if we seem to be fully exposed or fully covered
mjr 48:058ace2aed1d 148 pos = a < sum ? 0 : n;
mjr 48:058ace2aed1d 149 return true;
mjr 48:058ace2aed1d 150 }
mjr 48:058ace2aed1d 151 else
mjr 48:058ace2aed1d 152 {
mjr 48:058ace2aed1d 153 // We can't detect the orientation from this image, and
mjr 48:058ace2aed1d 154 // we don't know it from previous images, so we have nothing
mjr 48:058ace2aed1d 155 // to go on. Give up and return failure.
mjr 48:058ace2aed1d 156 return false;
mjr 48:058ace2aed1d 157 }
mjr 48:058ace2aed1d 158
mjr 48:058ace2aed1d 159 // figure the midpoint brigthness
mjr 48:058ace2aed1d 160 int mid = (a+b)/2;
mjr 48:058ace2aed1d 161
mjr 48:058ace2aed1d 162 // Scan from the bright side looking for an edge
mjr 48:058ace2aed1d 163 for (int i = 5 ; i < n-5 ; ++i, pi += dir)
mjr 48:058ace2aed1d 164 {
mjr 48:058ace2aed1d 165 // check to see if we found a dark pixel
mjr 48:058ace2aed1d 166 if (pix[pi] < mid)
mjr 48:058ace2aed1d 167 {
mjr 48:058ace2aed1d 168 // make sure we have a sustained edge
mjr 48:058ace2aed1d 169 int ok = 0;
mjr 48:058ace2aed1d 170 int pi2 = pi + dir;
mjr 48:058ace2aed1d 171 for (int j = 0 ; j < 5 ; ++j, pi2 += dir)
mjr 48:058ace2aed1d 172 {
mjr 48:058ace2aed1d 173 // count this pixel if it's darker than the midpoint
mjr 48:058ace2aed1d 174 if (pix[pi2] < mid)
mjr 48:058ace2aed1d 175 ++ok;
mjr 48:058ace2aed1d 176 }
mjr 48:058ace2aed1d 177
mjr 48:058ace2aed1d 178 // if we're clearly in the dark section, we have our edge
mjr 48:058ace2aed1d 179 if (ok > 3)
mjr 48:058ace2aed1d 180 {
mjr 48:058ace2aed1d 181 // Success. Since we found an edge in this scan, save the
mjr 48:058ace2aed1d 182 // midpoint brightness level in our history list, to help
mjr 48:058ace2aed1d 183 // with any future frames with insufficient contrast.
mjr 48:058ace2aed1d 184 midpt[midptIdx++] = mid;
mjr 48:058ace2aed1d 185 midptIdx %= countof(midpt);
mjr 48:058ace2aed1d 186
mjr 48:058ace2aed1d 187 // return the detected position
mjr 48:058ace2aed1d 188 pos = i;
mjr 48:058ace2aed1d 189 return true;
mjr 48:058ace2aed1d 190 }
mjr 48:058ace2aed1d 191 }
mjr 17:ab3cec0c8bf4 192 }
mjr 17:ab3cec0c8bf4 193
mjr 48:058ace2aed1d 194 // no edge found
mjr 48:058ace2aed1d 195 return false;
mjr 48:058ace2aed1d 196 }
mjr 48:058ace2aed1d 197
mjr 48:058ace2aed1d 198
mjr 48:058ace2aed1d 199 #if 0
mjr 48:058ace2aed1d 200 bool process3(uint8_t *pix, int &n, int &pos, int visMode)
mjr 48:058ace2aed1d 201 {
mjr 48:058ace2aed1d 202 // First, reduce the pixel array resolution to 1/4 of the
mjr 48:058ace2aed1d 203 // native sensor resolution. The native 400 dpi is higher
mjr 48:058ace2aed1d 204 // than we need for good results, so we can afford to cut
mjr 48:058ace2aed1d 205 // this down a bit. Reducing the resolution gives us
mjr 48:058ace2aed1d 206 // a little simplistic noise reduction (by averaging adjacent
mjr 48:058ace2aed1d 207 // pixels), and it speeds up the rest of the edge finder by
mjr 48:058ace2aed1d 208 // making the data set smaller.
mjr 48:058ace2aed1d 209 //
mjr 48:058ace2aed1d 210 // While we're scanning, collect the brightness range of the
mjr 48:058ace2aed1d 211 // reduced pixel set.
mjr 48:058ace2aed1d 212 register int src, dst;
mjr 48:058ace2aed1d 213 int lo = pix[0], hi = pix[0];
mjr 48:058ace2aed1d 214 for (src = 0, dst = 0 ; src < n ; )
mjr 47:df7a88cd249c 215 {
mjr 48:058ace2aed1d 216 // compute the average of this pixel group
mjr 48:058ace2aed1d 217 int p = (int(pix[src++]) + pix[src++] + pix[src++] + pix[src++]) / 4;
mjr 47:df7a88cd249c 218
mjr 48:058ace2aed1d 219 // note if it's the new high or low point
mjr 48:058ace2aed1d 220 if (p > hi)
mjr 48:058ace2aed1d 221 hi = p;
mjr 48:058ace2aed1d 222 else if (p < lo)
mjr 48:058ace2aed1d 223 lo = p;
mjr 44:b5ac89b9cd5d 224
mjr 48:058ace2aed1d 225 // Store the result back into the original array. Note
mjr 48:058ace2aed1d 226 // that there's no risk of overwriting anything we still
mjr 48:058ace2aed1d 227 // need, since the pixel set is shrinking, so the write
mjr 48:058ace2aed1d 228 // pointer is always behind the read pointer.
mjr 48:058ace2aed1d 229 pix[dst++] = p;
mjr 48:058ace2aed1d 230 }
mjr 48:058ace2aed1d 231
mjr 48:058ace2aed1d 232 // set the new array size
mjr 48:058ace2aed1d 233 n = dst;
mjr 48:058ace2aed1d 234
mjr 48:058ace2aed1d 235 // figure the midpoint brightness
mjr 48:058ace2aed1d 236 int mid = (hi + lo)/2;
mjr 48:058ace2aed1d 237
mjr 48:058ace2aed1d 238 // Look at the first few pixels on the left and right sides
mjr 48:058ace2aed1d 239 // to try to detect the sensor orientation.
mjr 48:058ace2aed1d 240 int left = pix[0] + pix[1] + pix[2] + pix[3];
mjr 48:058ace2aed1d 241 int right = pix[n-1] + pix[n-2] + pix[n-3] + pix[n-4];
mjr 48:058ace2aed1d 242 if (left > right + 40)
mjr 48:058ace2aed1d 243 {
mjr 48:058ace2aed1d 244 // left side is brighter - standard orientation
mjr 48:058ace2aed1d 245 dir = 1;
mjr 48:058ace2aed1d 246 }
mjr 48:058ace2aed1d 247 else if (right > left + 40)
mjr 48:058ace2aed1d 248 {
mjr 48:058ace2aed1d 249 // right side is brighter - reversed orientation
mjr 48:058ace2aed1d 250 dir = -1;
mjr 48:058ace2aed1d 251 }
mjr 17:ab3cec0c8bf4 252
mjr 48:058ace2aed1d 253 // scan for edges according to the direction
mjr 48:058ace2aed1d 254 bool found = false;
mjr 48:058ace2aed1d 255 if (dir == 0)
mjr 48:058ace2aed1d 256 {
mjr 48:058ace2aed1d 257 }
mjr 48:058ace2aed1d 258 else
mjr 48:058ace2aed1d 259 {
mjr 48:058ace2aed1d 260 // scan from the bright end to the dark end
mjr 48:058ace2aed1d 261 int stop;
mjr 48:058ace2aed1d 262 if (dir == 1)
mjr 47:df7a88cd249c 263 {
mjr 48:058ace2aed1d 264 src = 0;
mjr 48:058ace2aed1d 265 stop = n;
mjr 47:df7a88cd249c 266 }
mjr 48:058ace2aed1d 267 else
mjr 48:058ace2aed1d 268 {
mjr 48:058ace2aed1d 269 src = n - 1;
mjr 48:058ace2aed1d 270 stop = -1;
mjr 48:058ace2aed1d 271 }
mjr 48:058ace2aed1d 272
mjr 48:058ace2aed1d 273 // scan through the pixels
mjr 48:058ace2aed1d 274 for ( ; src != stop ; src += dir)
mjr 17:ab3cec0c8bf4 275 {
mjr 48:058ace2aed1d 276 // if this pixel is darker than the midpoint, we might
mjr 48:058ace2aed1d 277 // have an edge
mjr 48:058ace2aed1d 278 if (pix[src] < mid)
mjr 17:ab3cec0c8bf4 279 {
mjr 48:058ace2aed1d 280 // make sure it's not just noise by checking the next
mjr 48:058ace2aed1d 281 // few to make sure they're also darker
mjr 48:058ace2aed1d 282 if (dir > 0)
mjr 48:058ace2aed1d 283 dst = src + 10 > n ? n : src + 10;
mjr 48:058ace2aed1d 284 else
mjr 48:058ace2aed1d 285 dst = src - 10 < 0 ? -1 : src - 10;
mjr 48:058ace2aed1d 286 int i, nok;
mjr 48:058ace2aed1d 287 for (nok = 0, i = src ; i != dst ; i += dir)
mjr 47:df7a88cd249c 288 {
mjr 48:058ace2aed1d 289 if (pix[i] < mid)
mjr 48:058ace2aed1d 290 ++nok;
mjr 48:058ace2aed1d 291 }
mjr 48:058ace2aed1d 292 if (nok > 6)
mjr 48:058ace2aed1d 293 {
mjr 48:058ace2aed1d 294 // we have a winner
mjr 48:058ace2aed1d 295 pos = src;
mjr 48:058ace2aed1d 296 found = true;
mjr 48:058ace2aed1d 297 break;
mjr 47:df7a88cd249c 298 }
mjr 48:058ace2aed1d 299 }
mjr 48:058ace2aed1d 300 }
mjr 48:058ace2aed1d 301 }
mjr 48:058ace2aed1d 302
mjr 48:058ace2aed1d 303 // return the result
mjr 48:058ace2aed1d 304 return found;
mjr 48:058ace2aed1d 305 }
mjr 48:058ace2aed1d 306 #endif
mjr 48:058ace2aed1d 307
mjr 48:058ace2aed1d 308 #if 0
mjr 48:058ace2aed1d 309 bool process2(uint8_t *pix, int n, int &pos, int visMode)
mjr 48:058ace2aed1d 310 {
mjr 48:058ace2aed1d 311 // find the high and low brightness levels, and sum
mjr 48:058ace2aed1d 312 // all pixels (for the running averages)
mjr 48:058ace2aed1d 313 register int i;
mjr 48:058ace2aed1d 314 long sum = 0;
mjr 48:058ace2aed1d 315 int lo = 255, hi = 0;
mjr 48:058ace2aed1d 316 for (i = 0 ; i < n ; ++i)
mjr 48:058ace2aed1d 317 {
mjr 48:058ace2aed1d 318 int p = pix[i];
mjr 48:058ace2aed1d 319 sum += p;
mjr 48:058ace2aed1d 320 if (p > hi) hi = p;
mjr 48:058ace2aed1d 321 if (p < lo) lo = p;
mjr 48:058ace2aed1d 322 }
mjr 48:058ace2aed1d 323
mjr 48:058ace2aed1d 324 // Figure the midpoint brightness
mjr 48:058ace2aed1d 325 int mid = (lo + hi)/2;
mjr 48:058ace2aed1d 326
mjr 48:058ace2aed1d 327 // Scan for edges. An edge is where adjacent pixels are
mjr 48:058ace2aed1d 328 // on opposite sides of the brightness midpoint. For each
mjr 48:058ace2aed1d 329 // edge, we'll compute the "steepness" as the difference
mjr 48:058ace2aed1d 330 // between the average brightness on each side. We'll
mjr 48:058ace2aed1d 331 // keep only the steepest edge.
mjr 48:058ace2aed1d 332 register int bestSteepness = -1;
mjr 48:058ace2aed1d 333 register int bestPos = -1;
mjr 48:058ace2aed1d 334 register int sumLeft = 0;
mjr 48:058ace2aed1d 335 register int prv = pix[0], nxt = pix[1];
mjr 48:058ace2aed1d 336 for (i = 1 ; i < n ; prv = nxt, nxt = pix[++i])
mjr 48:058ace2aed1d 337 {
mjr 48:058ace2aed1d 338 // figure the new sums left and right of the i:i+1 boundary
mjr 48:058ace2aed1d 339 sumLeft += prv;
mjr 48:058ace2aed1d 340
mjr 48:058ace2aed1d 341 // if this is an edge, check if it's the best edge
mjr 48:058ace2aed1d 342 if (((mid - prv) & 0x80) ^ ((mid - nxt) & 0x80))
mjr 48:058ace2aed1d 343 {
mjr 48:058ace2aed1d 344 // compute the steepness
mjr 48:058ace2aed1d 345 int steepness = sumLeft/i - (sum - sumLeft)/(n-i);
mjr 48:058ace2aed1d 346 if (steepness > bestSteepness)
mjr 48:058ace2aed1d 347 {
mjr 48:058ace2aed1d 348 bestPos = i;
mjr 48:058ace2aed1d 349 bestSteepness = steepness;
mjr 17:ab3cec0c8bf4 350 }
mjr 17:ab3cec0c8bf4 351 }
mjr 17:ab3cec0c8bf4 352 }
mjr 17:ab3cec0c8bf4 353
mjr 48:058ace2aed1d 354 // if we found a position, return it
mjr 48:058ace2aed1d 355 if (bestPos >= 0)
mjr 48:058ace2aed1d 356 {
mjr 48:058ace2aed1d 357 pos = bestPos;
mjr 48:058ace2aed1d 358 return true;
mjr 48:058ace2aed1d 359 }
mjr 48:058ace2aed1d 360 else
mjr 48:058ace2aed1d 361 {
mjr 48:058ace2aed1d 362 return false;
mjr 48:058ace2aed1d 363 }
mjr 48:058ace2aed1d 364 }
mjr 48:058ace2aed1d 365 #endif
mjr 48:058ace2aed1d 366
mjr 48:058ace2aed1d 367 #if 0
mjr 48:058ace2aed1d 368 bool process1(uint8_t *pix, int n, int &pos, int visMode)
mjr 48:058ace2aed1d 369 {
mjr 48:058ace2aed1d 370 // presume failure
mjr 48:058ace2aed1d 371 bool ret = false;
mjr 48:058ace2aed1d 372
mjr 48:058ace2aed1d 373 // apply noise reduction
mjr 48:058ace2aed1d 374 noiseReduction(pix, n);
mjr 48:058ace2aed1d 375
mjr 48:058ace2aed1d 376 // make a histogram of brightness values
mjr 48:058ace2aed1d 377 uint8_t hist[256];
mjr 48:058ace2aed1d 378 memset(hist, 0, sizeof(hist));
mjr 48:058ace2aed1d 379 for (int i = 0 ; i < n ; ++i)
mjr 48:058ace2aed1d 380 {
mjr 48:058ace2aed1d 381 // get this pixel brightness, and count it in the histogram,
mjr 48:058ace2aed1d 382 // stopping if we hit the maximum count of 255
mjr 48:058ace2aed1d 383 int b = pix[i];
mjr 48:058ace2aed1d 384 if (hist[b] < 255)
mjr 48:058ace2aed1d 385 ++hist[b];
mjr 48:058ace2aed1d 386 }
mjr 48:058ace2aed1d 387
mjr 48:058ace2aed1d 388 // Find the high and low bounds. To avoid counting outliers that
mjr 48:058ace2aed1d 389 // might be noise, we'll scan in from each end of the brightness
mjr 48:058ace2aed1d 390 // range until we find a few pixels at or outside that level.
mjr 48:058ace2aed1d 391 int cnt, lo, hi;
mjr 48:058ace2aed1d 392 const int mincnt = 10;
mjr 48:058ace2aed1d 393 for (cnt = 0, lo = 0 ; lo < 255 ; ++lo)
mjr 48:058ace2aed1d 394 {
mjr 48:058ace2aed1d 395 cnt += hist[lo];
mjr 48:058ace2aed1d 396 if (cnt >= mincnt)
mjr 48:058ace2aed1d 397 break;
mjr 48:058ace2aed1d 398 }
mjr 48:058ace2aed1d 399 for (cnt = 0, hi = 255 ; hi >= 0 ; --hi)
mjr 48:058ace2aed1d 400 {
mjr 48:058ace2aed1d 401 cnt += hist[hi];
mjr 48:058ace2aed1d 402 if (cnt >= mincnt)
mjr 48:058ace2aed1d 403 break;
mjr 48:058ace2aed1d 404 }
mjr 48:058ace2aed1d 405
mjr 48:058ace2aed1d 406 // figure the inferred midpoint brightness level
mjr 48:058ace2aed1d 407 uint8_t m = uint8_t((int(lo) + int(hi))/2);
mjr 48:058ace2aed1d 408
mjr 48:058ace2aed1d 409 // Try finding an edge with the inferred brightness range
mjr 48:058ace2aed1d 410 if (findEdge(pix, n, m, pos, false))
mjr 48:058ace2aed1d 411 {
mjr 48:058ace2aed1d 412 // Found it! This image has sufficient contrast to find
mjr 48:058ace2aed1d 413 // an edge, so save the midpoint brightness for next time in
mjr 48:058ace2aed1d 414 // case the next image isn't as clear.
mjr 48:058ace2aed1d 415 midpt[midptIdx] = m;
mjr 48:058ace2aed1d 416 midptIdx = (midptIdx + 1) % countof(midpt);
mjr 47:df7a88cd249c 417
mjr 48:058ace2aed1d 418 // Infer the sensor orientation. If pixels at the bottom
mjr 48:058ace2aed1d 419 // of the array are brighter than pixels at the top, it's in the
mjr 48:058ace2aed1d 420 // standard orientation, otherwise it's the reverse orientation.
mjr 48:058ace2aed1d 421 int a = int(pix[0]) + int(pix[1]) + int(pix[2]);
mjr 48:058ace2aed1d 422 int b = int(pix[n-1]) + int(pix[n-2]) + int(pix[n-3]);
mjr 48:058ace2aed1d 423 dir = (a > b ? 1 : -1);
mjr 48:058ace2aed1d 424
mjr 48:058ace2aed1d 425 // if we're in the reversed orientation, mirror the position
mjr 48:058ace2aed1d 426 if (dir < 0)
mjr 48:058ace2aed1d 427 pos = n-1 - pos;
mjr 48:058ace2aed1d 428
mjr 48:058ace2aed1d 429 // success
mjr 48:058ace2aed1d 430 ret = true;
mjr 48:058ace2aed1d 431 }
mjr 48:058ace2aed1d 432 else
mjr 48:058ace2aed1d 433 {
mjr 48:058ace2aed1d 434 // We didn't find a clear edge using the inferred exposure
mjr 48:058ace2aed1d 435 // level. This might be because the image is entirely in or out
mjr 48:058ace2aed1d 436 // of shadow, with the plunger's shadow's edge out of the frame.
mjr 48:058ace2aed1d 437 // Figure the average of the recent history of successful frames
mjr 48:058ace2aed1d 438 // so that we can check to see if we have a low-contrast image
mjr 48:058ace2aed1d 439 // that's entirely above or below the recent midpoints.
mjr 48:058ace2aed1d 440 int avg = 0;
mjr 48:058ace2aed1d 441 for (int i = 0 ; i < countof(midpt) ; avg += midpt[i++]) ;
mjr 48:058ace2aed1d 442 avg /= countof(midpt);
mjr 48:058ace2aed1d 443
mjr 48:058ace2aed1d 444 // count how many we have above and below the midpoint
mjr 48:058ace2aed1d 445 int nBelow = 0, nAbove = 0;
mjr 48:058ace2aed1d 446 for (int i = 0 ; i < avg ; nBelow += hist[i++]) ;
mjr 48:058ace2aed1d 447 for (int i = avg + 1 ; i < 255 ; nAbove += hist[i++]) ;
mjr 48:058ace2aed1d 448
mjr 48:058ace2aed1d 449 // check if we're mostly above or below (we don't require *all*,
mjr 48:058ace2aed1d 450 // to allow for some pixel noise remaining)
mjr 48:058ace2aed1d 451 if (nBelow < 50)
mjr 48:058ace2aed1d 452 {
mjr 48:058ace2aed1d 453 // everything's bright -> we're in full light -> fully retracted
mjr 48:058ace2aed1d 454 pos = n - 1;
mjr 48:058ace2aed1d 455 ret = true;
mjr 48:058ace2aed1d 456 }
mjr 48:058ace2aed1d 457 else if (nAbove < 50)
mjr 48:058ace2aed1d 458 {
mjr 48:058ace2aed1d 459 // everything's dark -> we're in full shadow -> fully forward
mjr 48:058ace2aed1d 460 pos = 0;
mjr 48:058ace2aed1d 461 ret = true;
mjr 48:058ace2aed1d 462 }
mjr 48:058ace2aed1d 463
mjr 48:058ace2aed1d 464 // for visualization purposes, use the previous average as the midpoint
mjr 48:058ace2aed1d 465 m = avg;
mjr 48:058ace2aed1d 466 }
mjr 48:058ace2aed1d 467
mjr 48:058ace2aed1d 468 // If desired, apply the visualization mode to the pixels
mjr 48:058ace2aed1d 469 switch (visMode)
mjr 48:058ace2aed1d 470 {
mjr 48:058ace2aed1d 471 case 2:
mjr 48:058ace2aed1d 472 // High contrast mode. Peg each pixel to the white or black according
mjr 48:058ace2aed1d 473 // to which side of the midpoint it's on.
mjr 48:058ace2aed1d 474 for (int i = 0 ; i < n ; ++i)
mjr 48:058ace2aed1d 475 pix[i] = (pix[i] < m ? 0 : 255);
mjr 48:058ace2aed1d 476 break;
mjr 48:058ace2aed1d 477
mjr 48:058ace2aed1d 478 case 3:
mjr 48:058ace2aed1d 479 // Edge mode. Re-run the edge analysis in visualization mode.
mjr 48:058ace2aed1d 480 {
mjr 48:058ace2aed1d 481 int dummy;
mjr 48:058ace2aed1d 482 findEdge(pix, n, m, dummy, true);
mjr 48:058ace2aed1d 483 }
mjr 48:058ace2aed1d 484 break;
mjr 48:058ace2aed1d 485 }
mjr 48:058ace2aed1d 486
mjr 48:058ace2aed1d 487 // return the result
mjr 48:058ace2aed1d 488 return ret;
mjr 17:ab3cec0c8bf4 489 }
mjr 17:ab3cec0c8bf4 490
mjr 48:058ace2aed1d 491 // Apply noise reduction to the pixel array. We use a simple rank
mjr 48:058ace2aed1d 492 // selection median filter, which is fast and seems to produce pretty
mjr 48:058ace2aed1d 493 // good results with data from this sensor type. The filter looks at
mjr 48:058ace2aed1d 494 // a small window around each pixel; if a given pixel is the outlier
mjr 48:058ace2aed1d 495 // within its window (i.e., it has the maximum or minimum brightness
mjr 48:058ace2aed1d 496 // of all the pixels in the window), we replace it with the median
mjr 48:058ace2aed1d 497 // brightness of the pixels in the window. This works particularly
mjr 48:058ace2aed1d 498 // well with the structure of the image we expect to capture, since
mjr 48:058ace2aed1d 499 // the image should have stretches of roughly uniform brightness -
mjr 48:058ace2aed1d 500 // part fully exposed and part in the plunger's shadow. Spiky
mjr 48:058ace2aed1d 501 // variations in isolated pixels are almost guaranteed to be noise.
mjr 48:058ace2aed1d 502 void noiseReduction(uint8_t *pix, int n)
mjr 44:b5ac89b9cd5d 503 {
mjr 48:058ace2aed1d 504 // set up a rolling window of pixels
mjr 48:058ace2aed1d 505 uint8_t w[7] = { pix[0], pix[1], pix[2], pix[3], pix[4], pix[5], pix[6] };
mjr 47:df7a88cd249c 506 int a = 0;
mjr 47:df7a88cd249c 507
mjr 48:058ace2aed1d 508 // run through the pixels
mjr 48:058ace2aed1d 509 for (int i = 0 ; i < n ; ++i)
mjr 47:df7a88cd249c 510 {
mjr 48:058ace2aed1d 511 // set up a sorting array for the current window
mjr 48:058ace2aed1d 512 uint8_t tmp[7] = { w[0], w[1], w[2], w[3], w[4], w[5], w[6] };
mjr 44:b5ac89b9cd5d 513
mjr 48:058ace2aed1d 514 // sort it (using a Bose-Nelson sorting network for N=7)
mjr 48:058ace2aed1d 515 #define SWAP(x, y) { \
mjr 48:058ace2aed1d 516 const int a = tmp[x], b = tmp[y]; \
mjr 48:058ace2aed1d 517 if (a > b) tmp[x] = b, tmp[y] = a; \
mjr 48:058ace2aed1d 518 }
mjr 48:058ace2aed1d 519 SWAP(1, 2);
mjr 48:058ace2aed1d 520 SWAP(0, 2);
mjr 48:058ace2aed1d 521 SWAP(0, 1);
mjr 48:058ace2aed1d 522 SWAP(3, 4);
mjr 48:058ace2aed1d 523 SWAP(5, 6);
mjr 48:058ace2aed1d 524 SWAP(3, 5);
mjr 48:058ace2aed1d 525 SWAP(4, 6);
mjr 48:058ace2aed1d 526 SWAP(4, 5);
mjr 48:058ace2aed1d 527 SWAP(0, 4);
mjr 48:058ace2aed1d 528 SWAP(0, 3);
mjr 48:058ace2aed1d 529 SWAP(1, 5);
mjr 48:058ace2aed1d 530 SWAP(2, 6);
mjr 48:058ace2aed1d 531 SWAP(2, 5);
mjr 48:058ace2aed1d 532 SWAP(1, 3);
mjr 48:058ace2aed1d 533 SWAP(2, 4);
mjr 48:058ace2aed1d 534 SWAP(2, 3);
mjr 48:058ace2aed1d 535
mjr 48:058ace2aed1d 536 // if the current pixel is at one of the extremes, replace it
mjr 48:058ace2aed1d 537 // with the median, otherwise leave it unchanged
mjr 48:058ace2aed1d 538 if (pix[i] == tmp[0] || pix[i] == tmp[6])
mjr 48:058ace2aed1d 539 pix[i] = tmp[3];
mjr 48:058ace2aed1d 540
mjr 48:058ace2aed1d 541 // update our rolling window, if we're not at the start or
mjr 48:058ace2aed1d 542 // end of the overall pixel array
mjr 48:058ace2aed1d 543 if (i >= 3 && i < n-4)
mjr 47:df7a88cd249c 544 {
mjr 48:058ace2aed1d 545 w[a] = pix[i+4];
mjr 48:058ace2aed1d 546 a = (a + 1) % 7;
mjr 47:df7a88cd249c 547 }
mjr 47:df7a88cd249c 548 }
mjr 44:b5ac89b9cd5d 549 }
mjr 44:b5ac89b9cd5d 550
mjr 48:058ace2aed1d 551 // Find an edge in the image. 'm' is the midpoint brightness level
mjr 48:058ace2aed1d 552 // in the array. On success, fills in 'pos' with the pixel position
mjr 48:058ace2aed1d 553 // of the edge and returns true. Returns false if no clear, unique
mjr 48:058ace2aed1d 554 // edge can be detected.
mjr 48:058ace2aed1d 555 //
mjr 48:058ace2aed1d 556 // If 'vis' is true, we'll update the pixel array with a visualization
mjr 48:058ace2aed1d 557 // of the edges, for display in the config tool.
mjr 48:058ace2aed1d 558 bool findEdge(uint8_t *pix, int n, uint8_t m, int &pos, bool vis)
mjr 48:058ace2aed1d 559 {
mjr 48:058ace2aed1d 560 // Scan for edges. An edge is a transition where two adajacent
mjr 48:058ace2aed1d 561 // pixels are on opposite sides of the brightness midpoint.
mjr 48:058ace2aed1d 562 int nEdges = 0;
mjr 48:058ace2aed1d 563 int edgePos = 0;
mjr 48:058ace2aed1d 564 uint8_t prv = pix[0], nxt = pix[1];
mjr 48:058ace2aed1d 565 for (int i = 1 ; i < n-1 ; prv = nxt, nxt = pix[++i])
mjr 48:058ace2aed1d 566 {
mjr 48:058ace2aed1d 567 // presume we'll show a non-edge (white) pixel in the visualization
mjr 48:058ace2aed1d 568 uint8_t vispix = 255;
mjr 48:058ace2aed1d 569
mjr 48:058ace2aed1d 570 // if the two are on opposite sides of the midpoint, we have
mjr 48:058ace2aed1d 571 // an edge
mjr 48:058ace2aed1d 572 if ((prv < m && nxt > m) || (prv > m && nxt < m))
mjr 48:058ace2aed1d 573 {
mjr 48:058ace2aed1d 574 // count the edge and note its position
mjr 48:058ace2aed1d 575 ++nEdges;
mjr 48:058ace2aed1d 576 edgePos = i;
mjr 48:058ace2aed1d 577
mjr 48:058ace2aed1d 578 // color edges black in the visualization
mjr 48:058ace2aed1d 579 vispix = 0;
mjr 48:058ace2aed1d 580 }
mjr 48:058ace2aed1d 581
mjr 48:058ace2aed1d 582 // if in visualization mode, substitute the visualization pixel
mjr 48:058ace2aed1d 583 if (vis)
mjr 48:058ace2aed1d 584 pix[i] = vispix;
mjr 48:058ace2aed1d 585 }
mjr 48:058ace2aed1d 586
mjr 48:058ace2aed1d 587 // check for a unique edge
mjr 48:058ace2aed1d 588 if (nEdges == 1)
mjr 48:058ace2aed1d 589 {
mjr 51:57eb311faafa 590 // Successfully found an edge - presume we'll return the raw
mjr 51:57eb311faafa 591 // value we just found
mjr 48:058ace2aed1d 592 pos = edgePos;
mjr 51:57eb311faafa 593
mjr 51:57eb311faafa 594 // Filtering to the signal to reduce jitter. We sometimes see
mjr 51:57eb311faafa 595 // the detected position jitter around by a pixel or two when
mjr 51:57eb311faafa 596 // the plunger is stationary; the filtering is meant to reduce
mjr 51:57eb311faafa 597 // or (ideally) eliminate it. The jitter happens because the
mjr 51:57eb311faafa 598 // exactly pixel position of the edge can be a little ambiguous.
mjr 51:57eb311faafa 599 // The shadow is usually a little fuzzy and spans more than one
mjr 51:57eb311faafa 600 // pixel on the sensor, so our algorithm picks out the edge in
mjr 51:57eb311faafa 601 // each frame according to relative brightness from pixel to
mjr 51:57eb311faafa 602 // pixel. The exact relative brightnesses can vary a bit,
mjr 51:57eb311faafa 603 // though, due to variations in exposure time, light source
mjr 51:57eb311faafa 604 // uniformity, other stray light sources in the cabinet, pixel
mjr 51:57eb311faafa 605 // noise in the sensor, ADC error, etc.
mjr 51:57eb311faafa 606 //
mjr 51:57eb311faafa 607 // To filter the jitter, we'll look through the recent history
mjr 51:57eb311faafa 608 // to see if the recent samples are within a couple of pixels
mjr 51:57eb311faafa 609 // of each other. If so, we'll take an average and substitute
mjr 51:57eb311faafa 610 // that for our current reading.
mjr 51:57eb311faafa 611 bool allClose = true;
mjr 51:57eb311faafa 612 long sum = 0;
mjr 51:57eb311faafa 613 for (int i = 0 ; i < countof(hist) ; ++i)
mjr 51:57eb311faafa 614 {
mjr 51:57eb311faafa 615 // if this one isn't close enough, they're not all close
mjr 51:57eb311faafa 616 if (abs(hist[i] - edgePos) > 2)
mjr 51:57eb311faafa 617 {
mjr 51:57eb311faafa 618 allClose = false;
mjr 51:57eb311faafa 619 break;
mjr 51:57eb311faafa 620 }
mjr 51:57eb311faafa 621
mjr 51:57eb311faafa 622 // count it in the sum
mjr 51:57eb311faafa 623 sum += hist[i];
mjr 51:57eb311faafa 624 }
mjr 51:57eb311faafa 625 if (allClose)
mjr 51:57eb311faafa 626 [
mjr 51:57eb311faafa 627 // they're all close by - replace this reading with the
mjr 51:57eb311faafa 628 // average of nearby pixels
mjr 51:57eb311faafa 629 pos = int(sum / countof(hist));
mjr 51:57eb311faafa 630 }
mjr 51:57eb311faafa 631
mjr 51:57eb311faafa 632 // indicate success
mjr 48:058ace2aed1d 633 return true;
mjr 48:058ace2aed1d 634 }
mjr 48:058ace2aed1d 635 else
mjr 48:058ace2aed1d 636 {
mjr 48:058ace2aed1d 637 // failure
mjr 48:058ace2aed1d 638 return false;
mjr 48:058ace2aed1d 639 }
mjr 48:058ace2aed1d 640 }
mjr 48:058ace2aed1d 641 #endif
mjr 48:058ace2aed1d 642
mjr 45:c42166b2878c 643 // Send an exposure report to the joystick interface.
mjr 48:058ace2aed1d 644 // See plunger.h for details on the flags and visualization modes.
mjr 48:058ace2aed1d 645 virtual void sendExposureReport(USBJoystick &js, uint8_t flags, uint8_t visMode)
mjr 17:ab3cec0c8bf4 646 {
mjr 48:058ace2aed1d 647 // start a capture
mjr 47:df7a88cd249c 648 ccd.startCapture();
mjr 47:df7a88cd249c 649
mjr 48:058ace2aed1d 650 // get the stable pixel array
mjr 47:df7a88cd249c 651 uint8_t *pix;
mjr 47:df7a88cd249c 652 int n;
mjr 48:058ace2aed1d 653 uint32_t t;
mjr 48:058ace2aed1d 654 ccd.getPix(pix, n, t);
mjr 47:df7a88cd249c 655
mjr 48:058ace2aed1d 656 // Apply processing if desired. For visualization mode 0, apply no
mjr 48:058ace2aed1d 657 // processing at all. For all others it through the pixel processor.
mjr 48:058ace2aed1d 658 int pos = 0xffff;
mjr 48:058ace2aed1d 659 uint32_t processTime = 0;
mjr 48:058ace2aed1d 660 if (visMode != 0)
mjr 48:058ace2aed1d 661 {
mjr 48:058ace2aed1d 662 // count the processing time
mjr 48:058ace2aed1d 663 Timer pt;
mjr 48:058ace2aed1d 664 pt.start();
mjr 48:058ace2aed1d 665
mjr 48:058ace2aed1d 666 // do the processing
mjr 48:058ace2aed1d 667 process(pix, n, pos, visMode);
mjr 48:058ace2aed1d 668
mjr 48:058ace2aed1d 669 // note the processing time
mjr 48:058ace2aed1d 670 processTime = pt.read_us();
mjr 48:058ace2aed1d 671 }
mjr 47:df7a88cd249c 672
mjr 47:df7a88cd249c 673 // if a low-res scan is desired, reduce to a subset of pixels
mjr 48:058ace2aed1d 674 if (flags & 0x01)
mjr 47:df7a88cd249c 675 {
mjr 48:058ace2aed1d 676 // figure how many sensor pixels we combine into each low-res pixel
mjr 48:058ace2aed1d 677 const int group = 8;
mjr 48:058ace2aed1d 678 int lowResPix = n / group;
mjr 48:058ace2aed1d 679
mjr 48:058ace2aed1d 680 // combine the pixels
mjr 47:df7a88cd249c 681 int src, dst;
mjr 48:058ace2aed1d 682 for (src = dst = 0 ; dst < lowResPix ; ++dst)
mjr 48:058ace2aed1d 683 {
mjr 48:058ace2aed1d 684 // Combine these pixels - the best way to do this differs
mjr 48:058ace2aed1d 685 // by visualization mode...
mjr 48:058ace2aed1d 686 int a = 0;
mjr 48:058ace2aed1d 687 switch (visMode)
mjr 48:058ace2aed1d 688 {
mjr 48:058ace2aed1d 689 case 0:
mjr 48:058ace2aed1d 690 case 1:
mjr 48:058ace2aed1d 691 // Raw or noise-reduced pixels. This mode shows basically
mjr 48:058ace2aed1d 692 // a regular picture, so reduce the resolution by averaging
mjr 48:058ace2aed1d 693 // the grouped pixels.
mjr 48:058ace2aed1d 694 for (int j = 0 ; j < group ; ++j)
mjr 48:058ace2aed1d 695 a += pix[src++];
mjr 48:058ace2aed1d 696
mjr 48:058ace2aed1d 697 // we have the sum, so get the average
mjr 48:058ace2aed1d 698 a /= group;
mjr 48:058ace2aed1d 699 break;
mjr 48:058ace2aed1d 700
mjr 48:058ace2aed1d 701 case 2:
mjr 48:058ace2aed1d 702 // High contrast mode. To retain the high contrast, take a
mjr 48:058ace2aed1d 703 // majority vote of the pixels. Start by counting the white
mjr 48:058ace2aed1d 704 // pixels.
mjr 48:058ace2aed1d 705 for (int j = 0 ; j < group ; ++j)
mjr 48:058ace2aed1d 706 a += (pix[src++] > 127);
mjr 48:058ace2aed1d 707
mjr 48:058ace2aed1d 708 // If half or more are white, make the combined pixel white;
mjr 48:058ace2aed1d 709 // otherwise make it black.
mjr 48:058ace2aed1d 710 a = (a >= n/2 ? 255 : 0);
mjr 48:058ace2aed1d 711 break;
mjr 48:058ace2aed1d 712
mjr 48:058ace2aed1d 713 case 3:
mjr 48:058ace2aed1d 714 // Edge mode. Edges are shown as black. To retain every
mjr 48:058ace2aed1d 715 // detected edge in the result image, show the combined pixel
mjr 48:058ace2aed1d 716 // as an edge if ANY pixel within the group is an edge.
mjr 48:058ace2aed1d 717 a = 255;
mjr 48:058ace2aed1d 718 for (int j = 0 ; j < group ; ++j)
mjr 48:058ace2aed1d 719 {
mjr 48:058ace2aed1d 720 if (pix[src++] < 127)
mjr 48:058ace2aed1d 721 a = 0;
mjr 48:058ace2aed1d 722 }
mjr 48:058ace2aed1d 723 break;
mjr 48:058ace2aed1d 724 }
mjr 48:058ace2aed1d 725
mjr 48:058ace2aed1d 726 // store the down-res'd pixel in the array
mjr 48:058ace2aed1d 727 pix[dst] = uint8_t(a);
mjr 48:058ace2aed1d 728 }
mjr 48:058ace2aed1d 729
mjr 48:058ace2aed1d 730 // update the pixel count to the number we stored
mjr 47:df7a88cd249c 731 n = dst;
mjr 48:058ace2aed1d 732
mjr 48:058ace2aed1d 733 // if we have a valid position, rescale it to the reduced pixel count
mjr 48:058ace2aed1d 734 if (pos != 0xffff)
mjr 48:058ace2aed1d 735 pos = pos / group;
mjr 47:df7a88cd249c 736 }
mjr 43:7a6364d82a41 737
mjr 17:ab3cec0c8bf4 738 // send reports for all pixels
mjr 17:ab3cec0c8bf4 739 int idx = 0;
mjr 47:df7a88cd249c 740 while (idx < n)
mjr 47:df7a88cd249c 741 js.updateExposure(idx, n, pix);
mjr 17:ab3cec0c8bf4 742
mjr 48:058ace2aed1d 743 // send a special final report with additional data
mjr 48:058ace2aed1d 744 js.updateExposureExt(pos, dir, ccd.getAvgScanTime(), processTime);
mjr 48:058ace2aed1d 745
mjr 48:058ace2aed1d 746 // It takes us a while to send all of the pixels, since we have
mjr 48:058ace2aed1d 747 // to break them up into many USB reports. This delay means that
mjr 48:058ace2aed1d 748 // the sensor has been sitting there integrating for much longer
mjr 48:058ace2aed1d 749 // than usual, so the next frame read will be overexposed. To
mjr 48:058ace2aed1d 750 // mitigate this, make sure we don't have a capture running,
mjr 48:058ace2aed1d 751 // then clear the sensor and start a new capture.
mjr 48:058ace2aed1d 752 ccd.wait();
mjr 48:058ace2aed1d 753 ccd.clear();
mjr 47:df7a88cd249c 754 ccd.startCapture();
mjr 17:ab3cec0c8bf4 755 }
mjr 17:ab3cec0c8bf4 756
mjr 35:e959ffba78fd 757 protected:
mjr 44:b5ac89b9cd5d 758 // Sensor orientation. +1 means that the "tip" end - which is always
mjr 44:b5ac89b9cd5d 759 // the brighter end in our images - is at the 0th pixel in the array.
mjr 44:b5ac89b9cd5d 760 // -1 means that the tip is at the nth pixel in the array. 0 means
mjr 48:058ace2aed1d 761 // that we haven't figured it out yet. We automatically infer this
mjr 48:058ace2aed1d 762 // from the relative light levels at each end of the array when we
mjr 48:058ace2aed1d 763 // successfully find a shadow edge. The reason we save the information
mjr 48:058ace2aed1d 764 // is that we might occasionally get frames that are fully in shadow
mjr 48:058ace2aed1d 765 // or fully in light, and we can't infer the direction from such
mjr 48:058ace2aed1d 766 // frames. Saving the information from past frames gives us a fallback
mjr 48:058ace2aed1d 767 // when we can't infer it from the current frame. Note that we update
mjr 48:058ace2aed1d 768 // this each time we can infer the direction, so the device will adapt
mjr 48:058ace2aed1d 769 // on the fly even if the user repositions the sensor while the software
mjr 48:058ace2aed1d 770 // is running.
mjr 44:b5ac89b9cd5d 771 int dir;
mjr 51:57eb311faafa 772
mjr 51:57eb311faafa 773 // History of recent position readings. We keep a short history of
mjr 51:57eb311faafa 774 // readings so that we can apply some filtering to the data.
mjr 51:57eb311faafa 775 uint16_t hist[10];
mjr 51:57eb311faafa 776 int histIdx;
mjr 48:058ace2aed1d 777
mjr 48:058ace2aed1d 778 // History of midpoint brightness levels for the last few successful
mjr 48:058ace2aed1d 779 // scans. This is a circular buffer that we write on each scan where
mjr 48:058ace2aed1d 780 // we successfully detect a shadow edge. (It's circular, so we
mjr 48:058ace2aed1d 781 // effectively discard the oldest element whenever we write a new one.)
mjr 48:058ace2aed1d 782 //
mjr 48:058ace2aed1d 783 // The history is useful in cases where we have too little contrast
mjr 48:058ace2aed1d 784 // to detect an edge. In these cases, we assume that the entire sensor
mjr 48:058ace2aed1d 785 // is either in shadow or light, which can happen if the plunger is at
mjr 48:058ace2aed1d 786 // one extreme or the other such that the edge of its shadow is out of
mjr 48:058ace2aed1d 787 // the frame. (Ideally, the sensor should be positioned so that the
mjr 48:058ace2aed1d 788 // shadow edge is always in the frame, but it's not always possible
mjr 48:058ace2aed1d 789 // to do this given the constrained space within a cabinet.) The
mjr 48:058ace2aed1d 790 // history helps us decide which case we have - all shadow or all
mjr 48:058ace2aed1d 791 // light - by letting us compare our average pixel level in this
mjr 48:058ace2aed1d 792 // frame to the range in recent frames. This assumes that the
mjr 48:058ace2aed1d 793 // exposure varies minimally from frame to frame, which is usually
mjr 48:058ace2aed1d 794 // true because the physical installation (the light source and
mjr 48:058ace2aed1d 795 // sensor positions) are usually static.
mjr 48:058ace2aed1d 796 //
mjr 48:058ace2aed1d 797 // We always try first to infer the bright and dark levels from the
mjr 48:058ace2aed1d 798 // image, since this lets us adapt automatically to different exposure
mjr 48:058ace2aed1d 799 // levels. The exposure level can vary by integration time and the
mjr 48:058ace2aed1d 800 // intensity and positioning of the light source, and we want
mjr 48:058ace2aed1d 801 // to be as flexible as we can about both.
mjr 48:058ace2aed1d 802 uint8_t midpt[10];
mjr 48:058ace2aed1d 803 uint8_t midptIdx;
mjr 47:df7a88cd249c 804
mjr 44:b5ac89b9cd5d 805 public:
mjr 17:ab3cec0c8bf4 806 // the low-level interface to the CCD hardware
mjr 35:e959ffba78fd 807 TSL1410R ccd;
mjr 17:ab3cec0c8bf4 808 };
mjr 35:e959ffba78fd 809
mjr 35:e959ffba78fd 810
mjr 35:e959ffba78fd 811 // TSL1410R sensor
mjr 35:e959ffba78fd 812 class PlungerSensorTSL1410R: public PlungerSensorCCD
mjr 35:e959ffba78fd 813 {
mjr 35:e959ffba78fd 814 public:
mjr 35:e959ffba78fd 815 PlungerSensorTSL1410R(PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 47:df7a88cd249c 816 : PlungerSensorCCD(1280, si, clock, ao1, ao2)
mjr 35:e959ffba78fd 817 {
mjr 35:e959ffba78fd 818 }
mjr 35:e959ffba78fd 819 };
mjr 35:e959ffba78fd 820
mjr 35:e959ffba78fd 821 // TSL1412R
mjr 35:e959ffba78fd 822 class PlungerSensorTSL1412R: public PlungerSensorCCD
mjr 35:e959ffba78fd 823 {
mjr 35:e959ffba78fd 824 public:
mjr 35:e959ffba78fd 825 PlungerSensorTSL1412R(PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 47:df7a88cd249c 826 : PlungerSensorCCD(1536, si, clock, ao1, ao2)
mjr 35:e959ffba78fd 827 {
mjr 35:e959ffba78fd 828 }
mjr 35:e959ffba78fd 829 };