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
ccdSensor.h@35:e959ffba78fd, 2015-12-19 (annotated)
- Committer:
- mjr
- Date:
- Sat Dec 19 06:37:19 2015 +0000
- Revision:
- 35:e959ffba78fd
- Parent:
- 25:e22b88bd783a
- Child:
- 40:cc0d9814522b
Keyboard/Media Control interface working, but the extra interface confuses the DOF connector.
Who changed what in which revision?
User | Revision | Line number | New 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 | 17:ab3cec0c8bf4 | 55 | int si = 1, di = 1; |
mjr | 17:ab3cec0c8bf4 | 56 | if (p1 < p2) |
mjr | 17:ab3cec0c8bf4 | 57 | si = nlpix, 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 |