Mike R / Mbed 2 deprecated Pinscape_Controller_V2

Dependencies:   mbed FastIO FastPWM USBDevice

Fork of Pinscape_Controller by Mike R

Committer:
mjr
Date:
Fri Feb 26 18:42:03 2016 +0000
Revision:
48:058ace2aed1d
Parent:
47:df7a88cd249c
Child:
51:57eb311faafa
New plunger processing 1

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