An input/output controller for virtual pinball machines, with plunger position tracking, accelerometer-based nudge sensing, button input encoding, and feedback device control.

Dependencies:   USBDevice mbed FastAnalogIn FastIO FastPWM SimpleDMA

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers ccdSensor.h Source File

ccdSensor.h

00001 // CCD plunger sensor
00002 //
00003 // This file implements our generic plunger sensor interface for the 
00004 // TAOS TSL1410R CCD array sensor.
00005 
00006 
00007 
00008 // Number of pixels we read from the CCD on each frame.  Use the
00009 // sample size from config.h.
00010 const int npix = CCD_NPIXELS_SAMPLED;
00011 
00012 // PlungerSensor interface implementation for the CCD
00013 class PlungerSensor
00014 {
00015 public:
00016     PlungerSensor() : ccd(CCD_SO_PIN)
00017     {
00018     }
00019     
00020     // initialize
00021     void init()
00022     {
00023         // flush any random power-on values from the CCD's integration
00024         // capacitors, and start the first integration cycle
00025         ccd.clear();
00026     }
00027     
00028     // Perform a low-res scan of the sensor.  
00029     int lowResScan()
00030     {
00031         // read the pixels at low resolution
00032         const int nlpix = 32;
00033         uint16_t pix[nlpix];
00034         ccd.read(pix, nlpix);
00035     
00036         // determine which end is brighter
00037         uint16_t p1 = pix[0];
00038         uint16_t p2 = pix[nlpix-1];
00039         int si = 0, di = 1;
00040         if (p1 < p2)
00041             si = nlpix - 1, di = -1;
00042         
00043         // figure the shadow edge threshold - just use the midpoint 
00044         // of the levels at the bright and dark ends
00045         uint16_t shadow = uint16_t((long(p1) + long(p2))/2);
00046         
00047         // find the current tip position
00048         for (int n = 0 ; n < nlpix ; ++n, si += di)
00049         {
00050             // check to see if we found the shadow
00051             if (pix[si] <= shadow)
00052             {
00053                 // got it - normalize it to normal 'npix' resolution and
00054                 // return the result
00055                 return n*npix/nlpix;
00056             }
00057         }
00058         
00059         // didn't find a shadow - assume the whole array is in shadow (so
00060         // the edge is at the zero pixel point)
00061         return 0;
00062     }
00063 
00064     // Perform a high-res scan of the sensor.
00065     bool highResScan(int &pos)
00066     {
00067         // read the array
00068         ccd.read(pix, npix);
00069 
00070         // get the brightness at each end of the sensor
00071         long b1 = pix[0];
00072         long b2 = pix[npix-1];
00073         
00074         // Work from the bright end to the dark end.  VP interprets the
00075         // Z axis value as the amount the plunger is pulled: zero is the
00076         // rest position, and the axis maximum is fully pulled.  So we 
00077         // essentially want to report how much of the sensor is lit,
00078         // since this increases as the plunger is pulled back.
00079         int si = 0, di = 1;
00080         long hi = b1;
00081         if (b1 < b2)
00082             si = npix - 1, di = -1, hi = b2;
00083 
00084         // Figure the shadow threshold.  In practice, the portion of the
00085         // sensor that's not in shadow has all pixels consistently near
00086         // saturation; the first drop in brightness is pretty reliably the
00087         // start of the shadow.  So set the threshold level to be closer
00088         // to the bright end's brightness level, so that we detect the leading
00089         // edge if the shadow isn't perfectly sharp.  Use the point 1/3 of
00090         // the way down from the high top the low side, so:
00091         //
00092         //   threshold = lo + (hi - lo)*2/3
00093         //             = lo + hi*2/3 - lo*2/3
00094         //             = lo - lo*2/3 + hi*2/3
00095         //             = lo*1/3 + hi*2/3
00096         //             = (lo + hi*2)/3
00097         //
00098         // Now, 'lo' is always one of b1 or b2, and 'hi' is the other
00099         // one, so we can rewrite this as:
00100         long midpt = (b1 + b2 + hi)/3;
00101         
00102         // If we have enough contrast, proceed with the scan.
00103         //
00104         // If the bright end and dark end don't differ by enough, skip this
00105         // reading entirely.  Either we have an overexposed or underexposed frame,
00106         // or the sensor is misaligned and is either fully in or out of shadow
00107         // (it's supposed to be mounted such that the edge of the shadow always
00108         // falls within the sensor, for any possible plunger position).
00109         if (labs(b1 - b2) > 0x1000)
00110         {
00111             uint16_t *pixp = pix + si;           
00112             for (int n = 0 ; n < npix ; ++n, pixp += di)
00113             {
00114                 // if we've crossed the midpoint, report this position
00115                 if (long(*pixp) < midpt)
00116                 {
00117                     // note the new position
00118                     pos = n;
00119                     return true;
00120                 }
00121             }
00122         }
00123         
00124         // we didn't find a shadow - return no reading
00125         return false;
00126     }
00127     
00128     // send an exposure report to the joystick interface
00129     void sendExposureReport(USBJoystick &js)
00130     {
00131         // send reports for all pixels
00132         int idx = 0;
00133         while (idx < npix)
00134         {
00135             js.updateExposure(idx, npix, pix);
00136             wait_ms(1);
00137         }
00138             
00139         // The pixel dump requires many USB reports, since each report
00140         // can only send a few pixel values.  An integration cycle has
00141         // been running all this time, since each read starts a new
00142         // cycle.  Our timing is longer than usual on this round, so
00143         // the integration won't be comparable to a normal cycle.  Throw
00144         // this one away by doing a read now, and throwing it away - that 
00145         // will get the timing of the *next* cycle roughly back to normal.
00146         ccd.read(pix, npix);
00147     }
00148     
00149 private:
00150     // pixel buffer
00151     uint16_t pix[npix];
00152     
00153     // the low-level interface to the CCD hardware
00154     TSL1410R<CCD_SI_PIN, CCD_CLOCK_PIN> ccd;
00155 };