Mike R / Mbed 2 deprecated Pinscape_Controller_V2

Dependencies:   mbed FastIO FastPWM USBDevice

Fork of Pinscape_Controller by Mike R

Committer:
mjr
Date:
Thu Feb 18 07:32:20 2016 +0000
Revision:
47:df7a88cd249c
Parent:
45:c42166b2878c
Child:
48:058ace2aed1d
3-channel linked DMA scheme for CCD image capture working

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 47:df7a88cd249c 31 PlungerSensorCCD(int nativePix, PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 43:7a6364d82a41 32 : ccd(nativePix, si, clock, ao1, ao2)
mjr 17:ab3cec0c8bf4 33 {
mjr 47:df7a88cd249c 34 // we don't know the direction yet
mjr 47:df7a88cd249c 35 dir = 0;
mjr 47:df7a88cd249c 36
mjr 47:df7a88cd249c 37 // we don't have contrast information from any prior images yet,
mjr 47:df7a88cd249c 38 // so just peg the low and high brightness levels at the extremes
mjr 47:df7a88cd249c 39 lastLo = 0x00;
mjr 47:df7a88cd249c 40 lastHi = 0xff;
mjr 17:ab3cec0c8bf4 41 }
mjr 17:ab3cec0c8bf4 42
mjr 17:ab3cec0c8bf4 43 // initialize
mjr 35:e959ffba78fd 44 virtual void init()
mjr 17:ab3cec0c8bf4 45 {
mjr 17:ab3cec0c8bf4 46 // flush any random power-on values from the CCD's integration
mjr 17:ab3cec0c8bf4 47 // capacitors, and start the first integration cycle
mjr 17:ab3cec0c8bf4 48 ccd.clear();
mjr 17:ab3cec0c8bf4 49 }
mjr 17:ab3cec0c8bf4 50
mjr 47:df7a88cd249c 51 // Perform a high-res scan of the sensor.
mjr 47:df7a88cd249c 52 virtual bool read(uint16_t &pos)
mjr 17:ab3cec0c8bf4 53 {
mjr 47:df7a88cd249c 54 // start reading the next pixel array
mjr 47:df7a88cd249c 55 ccd.startCapture();
mjr 44:b5ac89b9cd5d 56
mjr 47:df7a88cd249c 57 // get the last image array
mjr 47:df7a88cd249c 58 uint8_t *pix;
mjr 47:df7a88cd249c 59 int n;
mjr 47:df7a88cd249c 60 ccd.getPix(pix, n);
mjr 47:df7a88cd249c 61
mjr 47:df7a88cd249c 62 // process it
mjr 47:df7a88cd249c 63 int pixpos;
mjr 47:df7a88cd249c 64 process(pix, n, pixpos);
mjr 17:ab3cec0c8bf4 65
mjr 47:df7a88cd249c 66 // if we found the position, return it
mjr 47:df7a88cd249c 67 if (pixpos >= 0)
mjr 47:df7a88cd249c 68 {
mjr 47:df7a88cd249c 69 // adjust for the reversed orientation if necessary
mjr 47:df7a88cd249c 70 if (dir < 0)
mjr 47:df7a88cd249c 71 pixpos = n - pixpos;
mjr 47:df7a88cd249c 72
mjr 47:df7a88cd249c 73 // normalize to the 16-bit range
mjr 47:df7a88cd249c 74 pos = uint16_t(((pixpos << 16) - pixpos) / n);
mjr 44:b5ac89b9cd5d 75
mjr 47:df7a88cd249c 76 // success
mjr 47:df7a88cd249c 77 return true;
mjr 47:df7a88cd249c 78 }
mjr 47:df7a88cd249c 79 else
mjr 47:df7a88cd249c 80 {
mjr 47:df7a88cd249c 81 // no position found
mjr 47:df7a88cd249c 82 return false;
mjr 47:df7a88cd249c 83 }
mjr 47:df7a88cd249c 84 }
mjr 17:ab3cec0c8bf4 85
mjr 47:df7a88cd249c 86 // Process an image. Applies noise reduction and looks for edges.
mjr 47:df7a88cd249c 87 // If we detect the plunger position, we set 'pos' to the pixel location
mjr 47:df7a88cd249c 88 // of the edge; otherwise we set pos to -1. If 'copy is true, we copy
mjr 47:df7a88cd249c 89 // the noise-reduced pixel array back to the caller's pixel array,
mjr 47:df7a88cd249c 90 // otherwise we leave it unchanged.
mjr 47:df7a88cd249c 91 void process(uint8_t *pix, int n, int &pos, bool copy = false)
mjr 47:df7a88cd249c 92 {
mjr 47:df7a88cd249c 93 // allocate a working buffer
mjr 47:df7a88cd249c 94 uint8_t *tmp = (uint8_t *)malloc(n+2);
mjr 47:df7a88cd249c 95 printf("processing - tmp=%lx\r\n", tmp);
mjr 47:df7a88cd249c 96
mjr 47:df7a88cd249c 97 // find the low and high pixel values
mjr 47:df7a88cd249c 98 int lo = 256, hi = 0;
mjr 47:df7a88cd249c 99 for (int i = 0 ; i < n ; ++i)
mjr 17:ab3cec0c8bf4 100 {
mjr 47:df7a88cd249c 101 if (pix[i] < lo) lo = pix[i];
mjr 47:df7a88cd249c 102 if (pix[i] > hi) hi = pix[i];
mjr 17:ab3cec0c8bf4 103 }
mjr 17:ab3cec0c8bf4 104
mjr 47:df7a88cd249c 105 for (int pass = 1 ; pass <= 2 ; ++pass)
mjr 47:df7a88cd249c 106 {
mjr 47:df7a88cd249c 107 // peg the pixels to their ranges with these parameters
mjr 47:df7a88cd249c 108 pegPixels(pix, tmp, n, lo, hi);
mjr 47:df7a88cd249c 109
mjr 47:df7a88cd249c 110 // count the edges
mjr 47:df7a88cd249c 111 int nEdges;
mjr 47:df7a88cd249c 112 findEdges(tmp, n, pos, nEdges);
mjr 47:df7a88cd249c 113
mjr 47:df7a88cd249c 114 // if we found one edge, stop
mjr 47:df7a88cd249c 115 if (nEdges == 1)
mjr 47:df7a88cd249c 116 {
mjr 47:df7a88cd249c 117 // If this is the first pass, we appear to have a good
mjr 47:df7a88cd249c 118 // contrast level. Note this for future use, in case the
mjr 47:df7a88cd249c 119 // next image has insufficient contrast.
mjr 47:df7a88cd249c 120 lastLo = lo;
mjr 47:df7a88cd249c 121 lastHi = hi;
mjr 47:df7a88cd249c 122
mjr 47:df7a88cd249c 123 // This also tells us the orientation. If the bright
mjr 47:df7a88cd249c 124 // end is at the 0th pixel, we're installed in the standard
mjr 47:df7a88cd249c 125 // orientation (dir = 1), otherwise we're installed in the
mjr 47:df7a88cd249c 126 // reverse orientation (dir = -1).
mjr 47:df7a88cd249c 127 dir = (pix[0] == 255 ? 1 : -1);
mjr 47:df7a88cd249c 128
mjr 47:df7a88cd249c 129 // use this result
mjr 47:df7a88cd249c 130 break;
mjr 47:df7a88cd249c 131 }
mjr 44:b5ac89b9cd5d 132
mjr 47:df7a88cd249c 133 // we have other than one edge, so presume failure
mjr 47:df7a88cd249c 134 pos = -1;
mjr 17:ab3cec0c8bf4 135
mjr 47:df7a88cd249c 136 // On the first pass, if we didn't find any edges, or we
mjr 47:df7a88cd249c 137 // found more than one, make another pass with the brightness
mjr 47:df7a88cd249c 138 // range from the *previous* image. This helps us deal with
mjr 47:df7a88cd249c 139 // images where the plunger is positioned so that entire sensor
mjr 47:df7a88cd249c 140 // is entirely convered or uncovered, in which case we won't
mjr 47:df7a88cd249c 141 // the image won't contain any natural contrast that would
mjr 47:df7a88cd249c 142 // allow us to infer the exposure level automatically. In
mjr 47:df7a88cd249c 143 // these cases, we assume that the new image has a similar
mjr 47:df7a88cd249c 144 // exposure level to the prior image.
mjr 47:df7a88cd249c 145 if (pass == 1)
mjr 47:df7a88cd249c 146 {
mjr 47:df7a88cd249c 147 // first pass - try again with the previous exposure levels
mjr 47:df7a88cd249c 148 lo = lastLo;
mjr 47:df7a88cd249c 149 hi = lastHi;
mjr 47:df7a88cd249c 150 }
mjr 47:df7a88cd249c 151 else if (nEdges == 0)
mjr 17:ab3cec0c8bf4 152 {
mjr 47:df7a88cd249c 153 // There are no edges, and we're on the second pass, so
mjr 47:df7a88cd249c 154 // we're already using an exposure range that worked on a
mjr 47:df7a88cd249c 155 // previous exposure. If the sensor is reading in the
mjr 47:df7a88cd249c 156 // low end of the range, it must be entirely covered, which
mjr 47:df7a88cd249c 157 // means that the plunger is all the way forward. If it's
mjr 47:df7a88cd249c 158 // at the high end of the range, the sensor must be entirely
mjr 47:df7a88cd249c 159 // exposed, so the plunger is pulled all the way back.
mjr 47:df7a88cd249c 160 // Report the end based on the known orientation.
mjr 47:df7a88cd249c 161 if (dir != 0)
mjr 17:ab3cec0c8bf4 162 {
mjr 47:df7a88cd249c 163 if (tmp[0] == 0)
mjr 47:df7a88cd249c 164 {
mjr 47:df7a88cd249c 165 // all dark - fully covered, plunger is all the way forward
mjr 47:df7a88cd249c 166 pos = dir > 0 ? 0 : n - 1;
mjr 47:df7a88cd249c 167 }
mjr 47:df7a88cd249c 168 else if (tmp[0] == 255)
mjr 47:df7a88cd249c 169 {
mjr 47:df7a88cd249c 170 // all bright - fully exposed, plunger is all the way back
mjr 47:df7a88cd249c 171 pos = dir > 0 ? n - 1 : 0;
mjr 47:df7a88cd249c 172 }
mjr 17:ab3cec0c8bf4 173 }
mjr 17:ab3cec0c8bf4 174 }
mjr 17:ab3cec0c8bf4 175 }
mjr 17:ab3cec0c8bf4 176
mjr 47:df7a88cd249c 177 // if desired, copy the processed pixels back to the caller's array
mjr 47:df7a88cd249c 178 if (copy)
mjr 47:df7a88cd249c 179 memcpy(pix, tmp, n);
mjr 47:df7a88cd249c 180
mjr 47:df7a88cd249c 181 // done with the temp array
mjr 47:df7a88cd249c 182 delete [] tmp;
mjr 17:ab3cec0c8bf4 183 }
mjr 17:ab3cec0c8bf4 184
mjr 47:df7a88cd249c 185 // Peg each pixel to its third of the range
mjr 47:df7a88cd249c 186 void pegPixels(const uint8_t *pix, uint8_t *tmp, int n, int lo, int hi)
mjr 44:b5ac89b9cd5d 187 {
mjr 47:df7a88cd249c 188 // Figure the thresholds for the top third and bottom third
mjr 47:df7a88cd249c 189 // of the brightness range.
mjr 47:df7a88cd249c 190 int third = (hi - lo)/3;
mjr 47:df7a88cd249c 191 int midHi = hi - third;
mjr 47:df7a88cd249c 192 int midLo = lo + third;
mjr 47:df7a88cd249c 193
mjr 47:df7a88cd249c 194 // Peg each pixel to its third of the range
mjr 47:df7a88cd249c 195 for (int i = 0 ; i < n ; ++i)
mjr 47:df7a88cd249c 196 tmp[i] = (pix[i] < midLo ? 0 : pix[i] > midHi ? 255 : 127);
mjr 44:b5ac89b9cd5d 197
mjr 47:df7a88cd249c 198 // Set up a circular buffer for a rolling 5-sample window. To
mjr 47:df7a88cd249c 199 // simplify the loop, fill in two fake pixels before the first
mjr 47:df7a88cd249c 200 // one simply by repeating the first pixel in those slots.
mjr 47:df7a88cd249c 201 uint8_t t[5] = { tmp[0], tmp[0], tmp[0], tmp[1], tmp[2] };
mjr 47:df7a88cd249c 202 int a = 0;
mjr 47:df7a88cd249c 203
mjr 47:df7a88cd249c 204 // Likewise, fill in two fake pixels at the end by copying the
mjr 47:df7a88cd249c 205 // actual last pixel.
mjr 47:df7a88cd249c 206 tmp[n] = tmp[n+1] = tmp[n-1];
mjr 47:df7a88cd249c 207 int s = t[0] + t[1] + t[2] + t[3] + t[4];
mjr 47:df7a88cd249c 208
mjr 47:df7a88cd249c 209 // Run through the array and peg each pixel to the consensus
mjr 47:df7a88cd249c 210 // of its two neighbors to either side. This smooths out noise
mjr 47:df7a88cd249c 211 // by eliminating lone flipped pixels.
mjr 47:df7a88cd249c 212 for (int i = 1 ; i < n ; ++i)
mjr 47:df7a88cd249c 213 {
mjr 47:df7a88cd249c 214 // apply the consensus vote to this pixel
mjr 47:df7a88cd249c 215 tmp[i] = (s < 85*5 ? 0 : s > 382*5 ? 255 : 127);
mjr 44:b5ac89b9cd5d 216
mjr 47:df7a88cd249c 217 // update the rolling window with the next sample
mjr 47:df7a88cd249c 218 s -= t[0];
mjr 47:df7a88cd249c 219 s += (t[a] = pix[i+3]);
mjr 47:df7a88cd249c 220 a = (a + 1) % 5;
mjr 47:df7a88cd249c 221 }
mjr 47:df7a88cd249c 222 }
mjr 47:df7a88cd249c 223
mjr 47:df7a88cd249c 224 // Find the edge(s)
mjr 47:df7a88cd249c 225 void findEdges(const uint8_t *pix, int n, int &edgePos, int &nEdges)
mjr 47:df7a88cd249c 226 {
mjr 47:df7a88cd249c 227 // we don't have any edges yet
mjr 47:df7a88cd249c 228 nEdges = 0;
mjr 47:df7a88cd249c 229 edgePos = -1;
mjr 47:df7a88cd249c 230
mjr 47:df7a88cd249c 231 // loop over the pixels looking for edges
mjr 47:df7a88cd249c 232 int prv = pix[0], cur = pix[1], nxt = pix[2];
mjr 47:df7a88cd249c 233 for (int i = 1 ; i < n - 1 ; prv = cur, cur = nxt, nxt = pix[++i + 1])
mjr 47:df7a88cd249c 234 {
mjr 47:df7a88cd249c 235 if (cur != prv)
mjr 47:df7a88cd249c 236 {
mjr 47:df7a88cd249c 237 ++nEdges;
mjr 47:df7a88cd249c 238 edgePos = i;
mjr 47:df7a88cd249c 239 }
mjr 47:df7a88cd249c 240 }
mjr 44:b5ac89b9cd5d 241 }
mjr 44:b5ac89b9cd5d 242
mjr 45:c42166b2878c 243 // Send an exposure report to the joystick interface.
mjr 45:c42166b2878c 244 //
mjr 45:c42166b2878c 245 // Mode bits:
mjr 45:c42166b2878c 246 // 0x01 -> send processed pixels (default is raw pixels)
mjr 45:c42166b2878c 247 // 0x02 -> low res scan (default is high res scan)
mjr 45:c42166b2878c 248 virtual void sendExposureReport(USBJoystick &js, uint8_t mode)
mjr 17:ab3cec0c8bf4 249 {
mjr 45:c42166b2878c 250 // do a scan
mjr 47:df7a88cd249c 251 ccd.startCapture();
mjr 47:df7a88cd249c 252
mjr 47:df7a88cd249c 253 // get the last pixel array
mjr 47:df7a88cd249c 254 uint8_t *pix;
mjr 47:df7a88cd249c 255 int n;
mjr 47:df7a88cd249c 256 ccd.getPix(pix, n);
mjr 47:df7a88cd249c 257
mjr 47:df7a88cd249c 258 // apply processing if desired
mjr 47:df7a88cd249c 259 int pos = -1;
mjr 47:df7a88cd249c 260 if (mode & 0x01)
mjr 47:df7a88cd249c 261 process(pix, n, pos, true);
mjr 47:df7a88cd249c 262
mjr 47:df7a88cd249c 263 // if a low-res scan is desired, reduce to a subset of pixels
mjr 47:df7a88cd249c 264 if (mode & 0x02)
mjr 47:df7a88cd249c 265 {
mjr 47:df7a88cd249c 266 int lowResPix = 128;
mjr 47:df7a88cd249c 267 int skip = n / lowResPix;
mjr 47:df7a88cd249c 268 int src, dst;
mjr 47:df7a88cd249c 269 for (src = skip, dst = 1 ; dst < lowResPix ; ++dst, src += skip)
mjr 47:df7a88cd249c 270 pix[dst] = pix[src];
mjr 47:df7a88cd249c 271 n = dst;
mjr 47:df7a88cd249c 272 }
mjr 43:7a6364d82a41 273
mjr 17:ab3cec0c8bf4 274 // send reports for all pixels
mjr 17:ab3cec0c8bf4 275 int idx = 0;
mjr 47:df7a88cd249c 276 while (idx < n)
mjr 47:df7a88cd249c 277 js.updateExposure(idx, n, pix);
mjr 17:ab3cec0c8bf4 278
mjr 17:ab3cec0c8bf4 279 // The pixel dump requires many USB reports, since each report
mjr 17:ab3cec0c8bf4 280 // can only send a few pixel values. An integration cycle has
mjr 17:ab3cec0c8bf4 281 // been running all this time, since each read starts a new
mjr 17:ab3cec0c8bf4 282 // cycle. Our timing is longer than usual on this round, so
mjr 17:ab3cec0c8bf4 283 // the integration won't be comparable to a normal cycle. Throw
mjr 17:ab3cec0c8bf4 284 // this one away by doing a read now, and throwing it away - that
mjr 17:ab3cec0c8bf4 285 // will get the timing of the *next* cycle roughly back to normal.
mjr 47:df7a88cd249c 286 ccd.startCapture();
mjr 17:ab3cec0c8bf4 287 }
mjr 17:ab3cec0c8bf4 288
mjr 35:e959ffba78fd 289 protected:
mjr 44:b5ac89b9cd5d 290 // Sensor orientation. +1 means that the "tip" end - which is always
mjr 44:b5ac89b9cd5d 291 // the brighter end in our images - is at the 0th pixel in the array.
mjr 44:b5ac89b9cd5d 292 // -1 means that the tip is at the nth pixel in the array. 0 means
mjr 44:b5ac89b9cd5d 293 // that we haven't figured it out yet.
mjr 44:b5ac89b9cd5d 294 int dir;
mjr 43:7a6364d82a41 295
mjr 47:df7a88cd249c 296 // High and low brightness levels from last successful image. We keep
mjr 47:df7a88cd249c 297 // track of these so that we can apply them to any images we take with
mjr 47:df7a88cd249c 298 // insufficient contrast to detect an edge. We assume in these cases
mjr 47:df7a88cd249c 299 // that the plunger is positioned so that the sensor is entirely in
mjr 47:df7a88cd249c 300 // shadow or entirely in light. We assume that the exposure level is
mjr 47:df7a88cd249c 301 // roughly the same as the previous frame where we did find an edge,
mjr 47:df7a88cd249c 302 // so we use the last frame's levels to determine whether the uniform
mjr 47:df7a88cd249c 303 // brightness we're seeing is shadow or light.
mjr 47:df7a88cd249c 304 uint8_t lastLo, lastHi;
mjr 47:df7a88cd249c 305
mjr 44:b5ac89b9cd5d 306 public:
mjr 17:ab3cec0c8bf4 307 // the low-level interface to the CCD hardware
mjr 35:e959ffba78fd 308 TSL1410R ccd;
mjr 17:ab3cec0c8bf4 309 };
mjr 35:e959ffba78fd 310
mjr 35:e959ffba78fd 311
mjr 35:e959ffba78fd 312 // TSL1410R sensor
mjr 35:e959ffba78fd 313 class PlungerSensorTSL1410R: public PlungerSensorCCD
mjr 35:e959ffba78fd 314 {
mjr 35:e959ffba78fd 315 public:
mjr 35:e959ffba78fd 316 PlungerSensorTSL1410R(PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 47:df7a88cd249c 317 : PlungerSensorCCD(1280, si, clock, ao1, ao2)
mjr 35:e959ffba78fd 318 {
mjr 35:e959ffba78fd 319 }
mjr 35:e959ffba78fd 320 };
mjr 35:e959ffba78fd 321
mjr 35:e959ffba78fd 322 // TSL1412R
mjr 35:e959ffba78fd 323 class PlungerSensorTSL1412R: public PlungerSensorCCD
mjr 35:e959ffba78fd 324 {
mjr 35:e959ffba78fd 325 public:
mjr 35:e959ffba78fd 326 PlungerSensorTSL1412R(PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 47:df7a88cd249c 327 : PlungerSensorCCD(1536, si, clock, ao1, ao2)
mjr 35:e959ffba78fd 328 {
mjr 35:e959ffba78fd 329 }
mjr 35:e959ffba78fd 330 };