Pinscape Controller version 1 fork. This is a fork to allow for ongoing bug fixes to the original controller version, from before the major changes for the expansion board project.

Dependencies:   FastIO FastPWM SimpleDMA mbed

Fork of Pinscape_Controller by Mike R

Committer:
mjr
Date:
Wed Feb 03 22:57:25 2016 +0000
Revision:
40:cc0d9814522b
Parent:
35:e959ffba78fd
Child:
43:7a6364d82a41
Gamma correction option for outputs; work in progress on new config program

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 35:e959ffba78fd 31 PlungerSensorCCD(int nPix, PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 35:e959ffba78fd 32 : ccd(nPix, si, clock, ao1, ao2)
mjr 17:ab3cec0c8bf4 33 {
mjr 17:ab3cec0c8bf4 34 }
mjr 17:ab3cec0c8bf4 35
mjr 17:ab3cec0c8bf4 36 // initialize
mjr 35:e959ffba78fd 37 virtual void init()
mjr 17:ab3cec0c8bf4 38 {
mjr 17:ab3cec0c8bf4 39 // flush any random power-on values from the CCD's integration
mjr 17:ab3cec0c8bf4 40 // capacitors, and start the first integration cycle
mjr 17:ab3cec0c8bf4 41 ccd.clear();
mjr 17:ab3cec0c8bf4 42 }
mjr 17:ab3cec0c8bf4 43
mjr 17:ab3cec0c8bf4 44 // Perform a low-res scan of the sensor.
mjr 35:e959ffba78fd 45 virtual bool lowResScan(int &pos)
mjr 17:ab3cec0c8bf4 46 {
mjr 17:ab3cec0c8bf4 47 // read the pixels at low resolution
mjr 17:ab3cec0c8bf4 48 const int nlpix = 32;
mjr 17:ab3cec0c8bf4 49 uint16_t pix[nlpix];
mjr 17:ab3cec0c8bf4 50 ccd.read(pix, nlpix);
mjr 17:ab3cec0c8bf4 51
mjr 17:ab3cec0c8bf4 52 // determine which end is brighter
mjr 17:ab3cec0c8bf4 53 uint16_t p1 = pix[0];
mjr 17:ab3cec0c8bf4 54 uint16_t p2 = pix[nlpix-1];
mjr 40:cc0d9814522b 55 int si = 0, di = 1;
mjr 17:ab3cec0c8bf4 56 if (p1 < p2)
mjr 40:cc0d9814522b 57 si = nlpix - 1, di = -1;
mjr 17:ab3cec0c8bf4 58
mjr 17:ab3cec0c8bf4 59 // figure the shadow edge threshold - just use the midpoint
mjr 17:ab3cec0c8bf4 60 // of the levels at the bright and dark ends
mjr 17:ab3cec0c8bf4 61 uint16_t shadow = uint16_t((long(p1) + long(p2))/2);
mjr 17:ab3cec0c8bf4 62
mjr 17:ab3cec0c8bf4 63 // find the current tip position
mjr 17:ab3cec0c8bf4 64 for (int n = 0 ; n < nlpix ; ++n, si += di)
mjr 17:ab3cec0c8bf4 65 {
mjr 17:ab3cec0c8bf4 66 // check to see if we found the shadow
mjr 17:ab3cec0c8bf4 67 if (pix[si] <= shadow)
mjr 17:ab3cec0c8bf4 68 {
mjr 17:ab3cec0c8bf4 69 // got it - normalize it to normal 'npix' resolution and
mjr 17:ab3cec0c8bf4 70 // return the result
mjr 35:e959ffba78fd 71 pos = n*npix/nlpix;
mjr 35:e959ffba78fd 72 return true;
mjr 17:ab3cec0c8bf4 73 }
mjr 17:ab3cec0c8bf4 74 }
mjr 17:ab3cec0c8bf4 75
mjr 35:e959ffba78fd 76 // didn't find a shadow - return failure
mjr 35:e959ffba78fd 77 return false;
mjr 17:ab3cec0c8bf4 78 }
mjr 17:ab3cec0c8bf4 79
mjr 17:ab3cec0c8bf4 80 // Perform a high-res scan of the sensor.
mjr 35:e959ffba78fd 81 virtual bool highResScan(int &pos)
mjr 17:ab3cec0c8bf4 82 {
mjr 17:ab3cec0c8bf4 83 // read the array
mjr 18:5e890ebd0023 84 ccd.read(pix, npix);
mjr 17:ab3cec0c8bf4 85
mjr 18:5e890ebd0023 86 // get the brightness at each end of the sensor
mjr 18:5e890ebd0023 87 long b1 = pix[0];
mjr 18:5e890ebd0023 88 long b2 = pix[npix-1];
mjr 17:ab3cec0c8bf4 89
mjr 17:ab3cec0c8bf4 90 // Work from the bright end to the dark end. VP interprets the
mjr 17:ab3cec0c8bf4 91 // Z axis value as the amount the plunger is pulled: zero is the
mjr 17:ab3cec0c8bf4 92 // rest position, and the axis maximum is fully pulled. So we
mjr 17:ab3cec0c8bf4 93 // essentially want to report how much of the sensor is lit,
mjr 17:ab3cec0c8bf4 94 // since this increases as the plunger is pulled back.
mjr 18:5e890ebd0023 95 int si = 0, di = 1;
mjr 18:5e890ebd0023 96 long hi = b1;
mjr 18:5e890ebd0023 97 if (b1 < b2)
mjr 18:5e890ebd0023 98 si = npix - 1, di = -1, hi = b2;
mjr 17:ab3cec0c8bf4 99
mjr 17:ab3cec0c8bf4 100 // Figure the shadow threshold. In practice, the portion of the
mjr 17:ab3cec0c8bf4 101 // sensor that's not in shadow has all pixels consistently near
mjr 17:ab3cec0c8bf4 102 // saturation; the first drop in brightness is pretty reliably the
mjr 17:ab3cec0c8bf4 103 // start of the shadow. So set the threshold level to be closer
mjr 17:ab3cec0c8bf4 104 // to the bright end's brightness level, so that we detect the leading
mjr 17:ab3cec0c8bf4 105 // edge if the shadow isn't perfectly sharp. Use the point 1/3 of
mjr 17:ab3cec0c8bf4 106 // the way down from the high top the low side, so:
mjr 17:ab3cec0c8bf4 107 //
mjr 17:ab3cec0c8bf4 108 // threshold = lo + (hi - lo)*2/3
mjr 17:ab3cec0c8bf4 109 // = lo + hi*2/3 - lo*2/3
mjr 17:ab3cec0c8bf4 110 // = lo - lo*2/3 + hi*2/3
mjr 17:ab3cec0c8bf4 111 // = lo*1/3 + hi*2/3
mjr 17:ab3cec0c8bf4 112 // = (lo + hi*2)/3
mjr 17:ab3cec0c8bf4 113 //
mjr 18:5e890ebd0023 114 // Now, 'lo' is always one of b1 or b2, and 'hi' is the other
mjr 18:5e890ebd0023 115 // one, so we can rewrite this as:
mjr 18:5e890ebd0023 116 long midpt = (b1 + b2 + hi)/3;
mjr 17:ab3cec0c8bf4 117
mjr 17:ab3cec0c8bf4 118 // If we have enough contrast, proceed with the scan.
mjr 17:ab3cec0c8bf4 119 //
mjr 17:ab3cec0c8bf4 120 // If the bright end and dark end don't differ by enough, skip this
mjr 17:ab3cec0c8bf4 121 // reading entirely. Either we have an overexposed or underexposed frame,
mjr 17:ab3cec0c8bf4 122 // or the sensor is misaligned and is either fully in or out of shadow
mjr 17:ab3cec0c8bf4 123 // (it's supposed to be mounted such that the edge of the shadow always
mjr 17:ab3cec0c8bf4 124 // falls within the sensor, for any possible plunger position).
mjr 18:5e890ebd0023 125 if (labs(b1 - b2) > 0x1000)
mjr 17:ab3cec0c8bf4 126 {
mjr 17:ab3cec0c8bf4 127 uint16_t *pixp = pix + si;
mjr 18:5e890ebd0023 128 for (int n = 0 ; n < npix ; ++n, pixp += di)
mjr 17:ab3cec0c8bf4 129 {
mjr 17:ab3cec0c8bf4 130 // if we've crossed the midpoint, report this position
mjr 18:5e890ebd0023 131 if (long(*pixp) < midpt)
mjr 17:ab3cec0c8bf4 132 {
mjr 17:ab3cec0c8bf4 133 // note the new position
mjr 17:ab3cec0c8bf4 134 pos = n;
mjr 17:ab3cec0c8bf4 135 return true;
mjr 17:ab3cec0c8bf4 136 }
mjr 17:ab3cec0c8bf4 137 }
mjr 17:ab3cec0c8bf4 138 }
mjr 17:ab3cec0c8bf4 139
mjr 17:ab3cec0c8bf4 140 // we didn't find a shadow - return no reading
mjr 17:ab3cec0c8bf4 141 return false;
mjr 17:ab3cec0c8bf4 142 }
mjr 17:ab3cec0c8bf4 143
mjr 17:ab3cec0c8bf4 144 // send an exposure report to the joystick interface
mjr 35:e959ffba78fd 145 virtual void sendExposureReport(USBJoystick &js)
mjr 17:ab3cec0c8bf4 146 {
mjr 17:ab3cec0c8bf4 147 // send reports for all pixels
mjr 17:ab3cec0c8bf4 148 int idx = 0;
mjr 17:ab3cec0c8bf4 149 while (idx < npix)
mjr 18:5e890ebd0023 150 {
mjr 17:ab3cec0c8bf4 151 js.updateExposure(idx, npix, pix);
mjr 18:5e890ebd0023 152 wait_ms(1);
mjr 18:5e890ebd0023 153 }
mjr 17:ab3cec0c8bf4 154
mjr 17:ab3cec0c8bf4 155 // The pixel dump requires many USB reports, since each report
mjr 17:ab3cec0c8bf4 156 // can only send a few pixel values. An integration cycle has
mjr 17:ab3cec0c8bf4 157 // been running all this time, since each read starts a new
mjr 17:ab3cec0c8bf4 158 // cycle. Our timing is longer than usual on this round, so
mjr 17:ab3cec0c8bf4 159 // the integration won't be comparable to a normal cycle. Throw
mjr 17:ab3cec0c8bf4 160 // this one away by doing a read now, and throwing it away - that
mjr 17:ab3cec0c8bf4 161 // will get the timing of the *next* cycle roughly back to normal.
mjr 17:ab3cec0c8bf4 162 ccd.read(pix, npix);
mjr 17:ab3cec0c8bf4 163 }
mjr 17:ab3cec0c8bf4 164
mjr 35:e959ffba78fd 165 protected:
mjr 17:ab3cec0c8bf4 166 // pixel buffer
mjr 35:e959ffba78fd 167 uint16_t *pix;
mjr 17:ab3cec0c8bf4 168
mjr 17:ab3cec0c8bf4 169 // the low-level interface to the CCD hardware
mjr 35:e959ffba78fd 170 TSL1410R ccd;
mjr 17:ab3cec0c8bf4 171 };
mjr 35:e959ffba78fd 172
mjr 35:e959ffba78fd 173
mjr 35:e959ffba78fd 174 // TSL1410R sensor
mjr 35:e959ffba78fd 175 class PlungerSensorTSL1410R: public PlungerSensorCCD
mjr 35:e959ffba78fd 176 {
mjr 35:e959ffba78fd 177 public:
mjr 35:e959ffba78fd 178 PlungerSensorTSL1410R(PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 35:e959ffba78fd 179 : PlungerSensorCCD(1280, si, clock, ao1, ao2)
mjr 35:e959ffba78fd 180 {
mjr 35:e959ffba78fd 181 // This sensor is 1x1280 pixels at 400dpi. Sample every 8th
mjr 35:e959ffba78fd 182 // pixel -> 160 pixels at 50dpi == 0.5mm spatial resolution.
mjr 35:e959ffba78fd 183 npix = 160;
mjr 35:e959ffba78fd 184 pix = pixbuf;
mjr 35:e959ffba78fd 185 }
mjr 35:e959ffba78fd 186
mjr 35:e959ffba78fd 187 uint16_t pixbuf[160];
mjr 35:e959ffba78fd 188 };
mjr 35:e959ffba78fd 189
mjr 35:e959ffba78fd 190 // TSL1412R
mjr 35:e959ffba78fd 191 class PlungerSensorTSL1412R: public PlungerSensorCCD
mjr 35:e959ffba78fd 192 {
mjr 35:e959ffba78fd 193 public:
mjr 35:e959ffba78fd 194 PlungerSensorTSL1412R(PinName si, PinName clock, PinName ao1, PinName ao2)
mjr 35:e959ffba78fd 195 : PlungerSensorCCD(1536, si, clock, ao1, ao2)
mjr 35:e959ffba78fd 196 {
mjr 35:e959ffba78fd 197 // This sensor is 1x1536 pixels at 400dpi. Sample every 8th
mjr 35:e959ffba78fd 198 // pixel -> 192 pixels at 50dpi == 0.5mm spatial resolution.
mjr 35:e959ffba78fd 199 npix = 192;
mjr 35:e959ffba78fd 200 pix = pixbuf;
mjr 35:e959ffba78fd 201 }
mjr 35:e959ffba78fd 202
mjr 35:e959ffba78fd 203 uint16_t pixbuf[192];
mjr 35:e959ffba78fd 204 };
mjr 35:e959ffba78fd 205