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:
Fri Feb 27 04:14:04 2015 +0000
Revision:
17:ab3cec0c8bf4
Child:
18:5e890ebd0023
FastIO and FastAnalogIn; better firing event sensing; potentiometer plunger sensor option; new key debouncing; ZB Launch Ball feature

Who changed what in which revision?

UserRevisionLine numberNew contents of line
mjr 17:ab3cec0c8bf4 1 // CCD plunger sensor
mjr 17:ab3cec0c8bf4 2 //
mjr 17:ab3cec0c8bf4 3 // This file implements our generic plunger sensor interface for the
mjr 17:ab3cec0c8bf4 4 // TAOS TSL1410R CCD array sensor.
mjr 17:ab3cec0c8bf4 5
mjr 17:ab3cec0c8bf4 6
mjr 17:ab3cec0c8bf4 7
mjr 17:ab3cec0c8bf4 8 // Number of pixels we read from the CCD on each frame. This can be
mjr 17:ab3cec0c8bf4 9 // less than the actual sensor size if desired; if so, we'll read every
mjr 17:ab3cec0c8bf4 10 // nth pixel. E.g., with a 1280-pixel physical sensor, if npix is 320,
mjr 17:ab3cec0c8bf4 11 // we'll read every 4th pixel. Reading a pixel is fairly time-consuming,
mjr 17:ab3cec0c8bf4 12 // because it requires waiting for the pixel's electric charge to
mjr 17:ab3cec0c8bf4 13 // stabilize on the CCD output, for the charge to transfer to the KL25Z
mjr 17:ab3cec0c8bf4 14 // input, and then for the KL25Z analog voltage sampler to get a stable
mjr 17:ab3cec0c8bf4 15 // reading. This all takes about 15us per pixel, which adds up to
mjr 17:ab3cec0c8bf4 16 // a relatively long time in such a large array. However, we can skip
mjr 17:ab3cec0c8bf4 17 // a pixel without waiting for all of that charge stabilization time,
mjr 17:ab3cec0c8bf4 18 // so we can get higher frame rates by only sampling a subset of the
mjr 17:ab3cec0c8bf4 19 // pixels. The array is so dense (400dpi) that we can still get
mjr 17:ab3cec0c8bf4 20 // excellent resolution by reading a fraction of the total pixels.
mjr 17:ab3cec0c8bf4 21 //
mjr 17:ab3cec0c8bf4 22 // Empirically, 160 pixels seems to be the point of diminishing returns
mjr 17:ab3cec0c8bf4 23 // for resolution - going higher will only improve the apparent smoothness
mjr 17:ab3cec0c8bf4 24 // slightly, if at all. 160 pixels gives us 50dpi on the sensor, which
mjr 17:ab3cec0c8bf4 25 // is roughly the same as the physical pixel pitch of a typical cabinet
mjr 17:ab3cec0c8bf4 26 // playfield monitor. (1080p HDTV displayed 1920x1080 pixels, and a 40"
mjr 17:ab3cec0c8bf4 27 // TV is about 35" wide, so the dot pitch is about 55dpi across the width
mjr 17:ab3cec0c8bf4 28 // of the TV. If on-screen plunger is displayed at roughly the true
mjr 17:ab3cec0c8bf4 29 // physical size, it's about 3" on the screen, or about 165 pixels. So at
mjr 17:ab3cec0c8bf4 30 // 160 pixels on the sensor, one pixel on the sensor translates to almost
mjr 17:ab3cec0c8bf4 31 // exactly one on-screen pixel on the TV, which makes the animated motion
mjr 17:ab3cec0c8bf4 32 // on-screen about as smooth as it can be. Looked at another way, 50dpi
mjr 17:ab3cec0c8bf4 33 // means that we're measuring the physical shooter rod position in about
mjr 17:ab3cec0c8bf4 34 // half-millimeter increments, which is probably better than I can
mjr 17:ab3cec0c8bf4 35 // discern by feel or sight.
mjr 17:ab3cec0c8bf4 36 const int npix = 160;
mjr 17:ab3cec0c8bf4 37
mjr 17:ab3cec0c8bf4 38
mjr 17:ab3cec0c8bf4 39 class PlungerSensor
mjr 17:ab3cec0c8bf4 40 {
mjr 17:ab3cec0c8bf4 41 public:
mjr 17:ab3cec0c8bf4 42 PlungerSensor() : ccd(CCD_SO_PIN)
mjr 17:ab3cec0c8bf4 43 {
mjr 17:ab3cec0c8bf4 44 }
mjr 17:ab3cec0c8bf4 45
mjr 17:ab3cec0c8bf4 46 // initialize
mjr 17:ab3cec0c8bf4 47 void init()
mjr 17:ab3cec0c8bf4 48 {
mjr 17:ab3cec0c8bf4 49 // flush any random power-on values from the CCD's integration
mjr 17:ab3cec0c8bf4 50 // capacitors, and start the first integration cycle
mjr 17:ab3cec0c8bf4 51 ccd.clear();
mjr 17:ab3cec0c8bf4 52 }
mjr 17:ab3cec0c8bf4 53
mjr 17:ab3cec0c8bf4 54 // Perform a low-res scan of the sensor.
mjr 17:ab3cec0c8bf4 55 int lowResScan()
mjr 17:ab3cec0c8bf4 56 {
mjr 17:ab3cec0c8bf4 57
mjr 17:ab3cec0c8bf4 58 // read the pixels at low resolution
mjr 17:ab3cec0c8bf4 59 const int nlpix = 32;
mjr 17:ab3cec0c8bf4 60 uint16_t pix[nlpix];
mjr 17:ab3cec0c8bf4 61 ccd.read(pix, nlpix);
mjr 17:ab3cec0c8bf4 62
mjr 17:ab3cec0c8bf4 63 // determine which end is brighter
mjr 17:ab3cec0c8bf4 64 uint16_t p1 = pix[0];
mjr 17:ab3cec0c8bf4 65 uint16_t p2 = pix[nlpix-1];
mjr 17:ab3cec0c8bf4 66 int si = 1, di = 1;
mjr 17:ab3cec0c8bf4 67 if (p1 < p2)
mjr 17:ab3cec0c8bf4 68 si = nlpix, di = -1;
mjr 17:ab3cec0c8bf4 69
mjr 17:ab3cec0c8bf4 70 // figure the shadow edge threshold - just use the midpoint
mjr 17:ab3cec0c8bf4 71 // of the levels at the bright and dark ends
mjr 17:ab3cec0c8bf4 72 uint16_t shadow = uint16_t((long(p1) + long(p2))/2);
mjr 17:ab3cec0c8bf4 73
mjr 17:ab3cec0c8bf4 74 // find the current tip position
mjr 17:ab3cec0c8bf4 75 for (int n = 0 ; n < nlpix ; ++n, si += di)
mjr 17:ab3cec0c8bf4 76 {
mjr 17:ab3cec0c8bf4 77 // check to see if we found the shadow
mjr 17:ab3cec0c8bf4 78 if (pix[si] <= shadow)
mjr 17:ab3cec0c8bf4 79 {
mjr 17:ab3cec0c8bf4 80 // got it - normalize it to normal 'npix' resolution and
mjr 17:ab3cec0c8bf4 81 // return the result
mjr 17:ab3cec0c8bf4 82 return n*npix/nlpix;
mjr 17:ab3cec0c8bf4 83 }
mjr 17:ab3cec0c8bf4 84 }
mjr 17:ab3cec0c8bf4 85
mjr 17:ab3cec0c8bf4 86 // didn't find a shadow - assume the whole array is in shadow (so
mjr 17:ab3cec0c8bf4 87 // the edge is at the zero pixel point)
mjr 17:ab3cec0c8bf4 88 return 0;
mjr 17:ab3cec0c8bf4 89 }
mjr 17:ab3cec0c8bf4 90
mjr 17:ab3cec0c8bf4 91 // Perform a high-res scan of the sensor.
mjr 17:ab3cec0c8bf4 92 bool highResScan(int &pos)
mjr 17:ab3cec0c8bf4 93 {
mjr 17:ab3cec0c8bf4 94 // read the array
mjr 17:ab3cec0c8bf4 95 ccd.read(pix, npix, ccdReadCB, 0, 3);
mjr 17:ab3cec0c8bf4 96
mjr 17:ab3cec0c8bf4 97 // get the average brightness at each end of the sensor
mjr 17:ab3cec0c8bf4 98 long avg1 = (long(pix[0]) + long(pix[1]) + long(pix[2]) + long(pix[3]) + long(pix[4]))/5;
mjr 17:ab3cec0c8bf4 99 long avg2 = (long(pix[npix-1]) + long(pix[npix-2]) + long(pix[npix-3]) + long(pix[npix-4]) + long(pix[npix-5]))/5;
mjr 17:ab3cec0c8bf4 100
mjr 17:ab3cec0c8bf4 101 // Work from the bright end to the dark end. VP interprets the
mjr 17:ab3cec0c8bf4 102 // Z axis value as the amount the plunger is pulled: zero is the
mjr 17:ab3cec0c8bf4 103 // rest position, and the axis maximum is fully pulled. So we
mjr 17:ab3cec0c8bf4 104 // essentially want to report how much of the sensor is lit,
mjr 17:ab3cec0c8bf4 105 // since this increases as the plunger is pulled back.
mjr 17:ab3cec0c8bf4 106 int si = 1, di = 1;
mjr 17:ab3cec0c8bf4 107 long avgHi = avg1;
mjr 17:ab3cec0c8bf4 108 if (avg1 < avg2)
mjr 17:ab3cec0c8bf4 109 si = npix - 2, di = -1, avgHi = avg2;
mjr 17:ab3cec0c8bf4 110
mjr 17:ab3cec0c8bf4 111 // Figure the shadow threshold. In practice, the portion of the
mjr 17:ab3cec0c8bf4 112 // sensor that's not in shadow has all pixels consistently near
mjr 17:ab3cec0c8bf4 113 // saturation; the first drop in brightness is pretty reliably the
mjr 17:ab3cec0c8bf4 114 // start of the shadow. So set the threshold level to be closer
mjr 17:ab3cec0c8bf4 115 // to the bright end's brightness level, so that we detect the leading
mjr 17:ab3cec0c8bf4 116 // edge if the shadow isn't perfectly sharp. Use the point 1/3 of
mjr 17:ab3cec0c8bf4 117 // the way down from the high top the low side, so:
mjr 17:ab3cec0c8bf4 118 //
mjr 17:ab3cec0c8bf4 119 // threshold = lo + (hi - lo)*2/3
mjr 17:ab3cec0c8bf4 120 // = lo + hi*2/3 - lo*2/3
mjr 17:ab3cec0c8bf4 121 // = lo - lo*2/3 + hi*2/3
mjr 17:ab3cec0c8bf4 122 // = lo*1/3 + hi*2/3
mjr 17:ab3cec0c8bf4 123 // = (lo + hi*2)/3
mjr 17:ab3cec0c8bf4 124 //
mjr 17:ab3cec0c8bf4 125 // Then multiply the whole thing by 3 to factor out the averaging
mjr 17:ab3cec0c8bf4 126 // of each three adjacent pixels that we do in the loop (to save a
mjr 17:ab3cec0c8bf4 127 // little time on a mulitply on each loop):
mjr 17:ab3cec0c8bf4 128 //
mjr 17:ab3cec0c8bf4 129 // threshold' = lo + 2*hi
mjr 17:ab3cec0c8bf4 130 //
mjr 17:ab3cec0c8bf4 131 // Now, 'lo' is always one of avg1 or avg2, and 'hi' is the other
mjr 17:ab3cec0c8bf4 132 // one, so we can rewrite this as hi + avg1 + avg2. We also already
mjr 17:ab3cec0c8bf4 133 // pulled out 'hi' as avgHi, so we finally come to the final
mjr 17:ab3cec0c8bf4 134 // simplified expression:
mjr 17:ab3cec0c8bf4 135 long midpt = avg1 + avg2 + avgHi;
mjr 17:ab3cec0c8bf4 136
mjr 17:ab3cec0c8bf4 137 // If we have enough contrast, proceed with the scan.
mjr 17:ab3cec0c8bf4 138 //
mjr 17:ab3cec0c8bf4 139 // If the bright end and dark end don't differ by enough, skip this
mjr 17:ab3cec0c8bf4 140 // reading entirely. Either we have an overexposed or underexposed frame,
mjr 17:ab3cec0c8bf4 141 // or the sensor is misaligned and is either fully in or out of shadow
mjr 17:ab3cec0c8bf4 142 // (it's supposed to be mounted such that the edge of the shadow always
mjr 17:ab3cec0c8bf4 143 // falls within the sensor, for any possible plunger position).
mjr 17:ab3cec0c8bf4 144 if (labs(avg1 - avg2) > 0x1000)
mjr 17:ab3cec0c8bf4 145 {
mjr 17:ab3cec0c8bf4 146 uint16_t *pixp = pix + si;
mjr 17:ab3cec0c8bf4 147 for (int n = 1 ; n < npix - 1 ; ++n, pixp += di)
mjr 17:ab3cec0c8bf4 148 {
mjr 17:ab3cec0c8bf4 149 // if we've crossed the midpoint, report this position
mjr 17:ab3cec0c8bf4 150 if (long(pixp[-1]) + long(pixp[0]) + long(pixp[1]) < midpt)
mjr 17:ab3cec0c8bf4 151 {
mjr 17:ab3cec0c8bf4 152 // note the new position
mjr 17:ab3cec0c8bf4 153 pos = n;
mjr 17:ab3cec0c8bf4 154 return true;
mjr 17:ab3cec0c8bf4 155 }
mjr 17:ab3cec0c8bf4 156 }
mjr 17:ab3cec0c8bf4 157 }
mjr 17:ab3cec0c8bf4 158
mjr 17:ab3cec0c8bf4 159 // we didn't find a shadow - return no reading
mjr 17:ab3cec0c8bf4 160 return false;
mjr 17:ab3cec0c8bf4 161 }
mjr 17:ab3cec0c8bf4 162
mjr 17:ab3cec0c8bf4 163 // send an exposure report to the joystick interface
mjr 17:ab3cec0c8bf4 164 void sendExposureReport(USBJoystick &js)
mjr 17:ab3cec0c8bf4 165 {
mjr 17:ab3cec0c8bf4 166 // send reports for all pixels
mjr 17:ab3cec0c8bf4 167 int idx = 0;
mjr 17:ab3cec0c8bf4 168 while (idx < npix)
mjr 17:ab3cec0c8bf4 169 js.updateExposure(idx, npix, pix);
mjr 17:ab3cec0c8bf4 170
mjr 17:ab3cec0c8bf4 171 // The pixel dump requires many USB reports, since each report
mjr 17:ab3cec0c8bf4 172 // can only send a few pixel values. An integration cycle has
mjr 17:ab3cec0c8bf4 173 // been running all this time, since each read starts a new
mjr 17:ab3cec0c8bf4 174 // cycle. Our timing is longer than usual on this round, so
mjr 17:ab3cec0c8bf4 175 // the integration won't be comparable to a normal cycle. Throw
mjr 17:ab3cec0c8bf4 176 // this one away by doing a read now, and throwing it away - that
mjr 17:ab3cec0c8bf4 177 // will get the timing of the *next* cycle roughly back to normal.
mjr 17:ab3cec0c8bf4 178 ccd.read(pix, npix);
mjr 17:ab3cec0c8bf4 179 }
mjr 17:ab3cec0c8bf4 180
mjr 17:ab3cec0c8bf4 181 private:
mjr 17:ab3cec0c8bf4 182 // pixel buffer
mjr 17:ab3cec0c8bf4 183 uint16_t pix[npix];
mjr 17:ab3cec0c8bf4 184
mjr 17:ab3cec0c8bf4 185 // the low-level interface to the CCD hardware
mjr 17:ab3cec0c8bf4 186 TSL1410R<CCD_SI_PIN, CCD_CLOCK_PIN> ccd;
mjr 17:ab3cec0c8bf4 187 };