An I/O controller for virtual pinball machines: accelerometer nudge sensing, analog plunger input, button input encoding, LedWiz compatible output controls, and more.

Dependencies:   mbed FastIO FastPWM USBDevice

Fork of Pinscape_Controller by Mike R

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers VCNL4010.h Source File

VCNL4010.h

00001 // Vishay VCNL4010 Proximity Sensor interface
00002 //
00003 // OVERVIEW
00004 //
00005 // The Vishay VCNL4010 is an IR proximity sensor chip with an I2C interface
00006 // and an effective operating range of about 2mm to 100mm.  The VirtuaPin
00007 // plunger kit v3 is based on this sensor, so it's in fairly widespread
00008 // use among pin cab builders.
00009 //
00010 // Like all proximity sensors, this chip is designed for sensing *proxmity*,
00011 // not distance.  Proximity sensing is answering the yes/no question "is
00012 // there an object within my detection range?".  However, many types of
00013 // proximity sensors, including this one, don't answer the proximity
00014 // question directly, but rather report an analog quantity that correlates
00015 // with distance to a detected object.  For a proximity reading, we'd
00016 // compare that analog quantitiy to a threshold level to determine whether
00017 // or not an object is in range.  But we can "abuse" the analog reading by
00018 // interpreting it as a continuous value instead of merely being on one side
00019 // or the other of a cutoff point.  Since the analog value varies (in some
00020 // way) with the distance to the detected object, we can re-interpret the
00021 // reported analog quantity as a continuous distance value, as long as we
00022 // know the mathematical relationship between the distance to the target
00023 // and the sensor's reported reading.
00024 //
00025 // In the case of IR proximity sensors like this one, the analog quantity
00026 // that the sensor reports is the intensity of light reflected from the
00027 // target object.  This type of sensor projects an IR light source in the
00028 // direction of the target, and measures the intensity of light reflected
00029 // from the target.  At the basic physics level, the apparent brightness
00030 // of a point light source varies with the inverse of the square of the
00031 // distance between source and observer.  Our setup isn't quite as simple
00032 // as that idealized model: we have a reflector instead of a point source,
00033 // so there are other factors that could vary by distance, especially the
00034 // cross-section of the target (the portion of the target within the spread
00035 // angle of the source light).  These other factors might not even have
00036 // simple polynomial relationships to distance.  Even so, the general idea
00037 // that the reflected brightness varies inversely with the distance should
00038 // hold, at least within a limited distance range.  Assuming we can hold
00039 // all of the other quantities constant (brightness of the light source,
00040 // reflectivity of the target, etc), then, the reflected brightness should
00041 // serve as a proxy for the distance.  It's obviously not possible to
00042 // compute an absolute distance (in millimeters from the sensor, say) from
00043 // the brightness reading alone, since that depends upon knowing the actual
00044 // values of all of the other quantities that assuming are held constant.
00045 // But we don't have to know those variables individually; we can roll them
00046 // into a proportionality constant that we can compute via calibration, by
00047 // taking brightness readings at known distances and then solving for the
00048 // constant.
00049 //
00050 // The VCNL4010 data sheet doesn't provide any specifications of how the
00051 // brightness reading relates to distance - it can't, for all of the reasons
00052 // mentioned above.  But it at least provides a rough plot of readings taken
00053 // for a particular test configuration.  That plot suggests that the power
00054 // law observed in the test configuration is roughly
00055 //
00056 //   Brightness ~ 1/Distance^3.2
00057 // 
00058 // over most of the range from 10mm to 100mm.  In my own testing, the best
00059 // fit was more like 1/r^2.  I suspect that the power law depends quite a
00060 // lot on the size and shape of the reflector.  Vishay's test setup uses a
00061 // 3cm x 3cm square reflector, whereas my plunger test rig has about a 2.5cm
00062 // circular reflector, which is about as big as you can make the reflector
00063 // for a pin cab plunger without conflicting with the flipper switches.  I
00064 // don't know if the difference in observed power law is due to the
00065 // reflector geometry or other factors.  We might need to revisit the
00066 // formula I used for the distance conversion as we gain experience from
00067 // different users setting up the sensor.  A possible future enhancement
00068 // would be to do a more detailed calibration as follows:
00069 //
00070 //   - Ask the user to pull back the plunger slowly at a very steady rate,
00071 //     maybe 3-5 seconds per pull
00072 //
00073 //   - Collect frequent readings throughout this period, say every 50ms
00074 //     (so around 60-100 readings per pull)
00075 //
00076 //   - Do a best-fit calculation on the data to solve for the exponent X
00077 //     and proportionality constant C in (Brightness = C/Distance^X),
00078 //     assuming that the distances are uniformly distributed over the
00079 //     pull range (because the user was pulling at constant speed).
00080 //
00081 //   - Save the exponent as config.plunger.cal.raw1 (perhaps as a 4.4 bit
00082 //     fixed-point value, such that X = raw1/16.0f)
00083 //
00084 // Alternatively, we could let the user provide the power law exponent
00085 // manually, as a configuration parameter, and add a Config Tool command
00086 // to collect the same calibration data described above and do the best-fit
00087 // analysis.  It might be preferable to do it that way - the user could
00088 // experiment with different values manually to find one that provides the
00089 // best subjective feel, and they could use the analysis tool to suggest
00090 // the best value based on data collection.  The reason I like the manual
00091 // approach is that the actual distance/brightness relationship isn't as
00092 // uniform as a simple power law, so even the best-fit power law will be
00093 // imperfect.  What looks best subjectively might not match the mathematical
00094 // best fit, because divergence from the fit might be more noticeable to
00095 // the eye in some regions than in others.  A manual fit would allow the
00096 // user to tweak it until it looked best in the whatever region they find
00097 // most noticeable.
00098 //
00099 //
00100 // SENSOR INTIALIZATION
00101 //
00102 // Initializing the VCNL4010 from the software side is just a matter of
00103 // programming the registers that control sample rate and sample collection
00104 // policy.  From experience with other plunger sensors, we know that good
00105 // plunger motion tracking without aliasing requires samples at very short
00106 // intervals - ideally 2.5ms or less  The VCNL4010's fastest sampling rate
00107 // for proximity is 250 samples/second, or 4ms intervals, so it's not quite
00108 // as fast as we'd like.  But it's still usable.  In addition, we'll use the
00109 // "on demand" mode to collect readings (rather than its interrupt mode),
00110 // since the upper software layers poll the sensor by design.
00111 //
00112 //
00113 // I2C INFORMATION
00114 //
00115 // This chip has an I2C interface with an immutable I2C address of 0010 011x.
00116 // In 8-bit address terms, this is 0x26 write, 0x27 read; or, if you prefer
00117 // the 7-bit notation, it's address 0x13.
00118 //
00119 
00120 #ifndef _VCNL4010_H_
00121 #define _VCNL4010_H_
00122 
00123 #include "mbed.h"
00124 #include "BitBangI2C.h"
00125 #include "config.h"
00126 
00127 class VCNL4010
00128 {
00129 public:
00130     // Set up the interface with the given I2C pins.
00131     //
00132     // If 'internalPullups' is true, we'll set the I2C SDA/SCL pins to 
00133     // enable the internal pullup resistors.  Set this to false if you're
00134     // using your own external pullup resistors on the lines.  External
00135     // pullups are better if you're attaching more than one device to the
00136     // same physical I2C bus; the internal pullups are fine if there's only
00137     // one I2C device (in this case the VCNL4010) connected to these pins.
00138     VCNL4010(PinName sda, PinName scl, bool internalPullups, int iredCurrent);
00139     
00140     // initialize the chip
00141     void init();
00142 
00143     // Start a distance reading, returning immediately without waiting
00144     // for the reading to finish.  The caller can poll for the finished
00145     // reading via proxReady().
00146     void startProxReading();
00147     
00148     // Is a proximity reading ready?
00149     bool proxReady();
00150     
00151     // Read the proximity value.  Note that this returns the "brightness"
00152     // value from the sensor, not a distance reading.  This must be converted
00153     // into a distance reading via countToDistance().
00154     int getProx(int &proxCount, uint32_t &tMid, uint32_t &dt, uint32_t timeout_us);
00155 
00156     // convert from raw sensor count values to distance units
00157     int countToDistance(int count);
00158         
00159     // This chip has a fixed I2C address of 0x26 write, 0x27 read
00160     static const uint8_t I2C_ADDR = 0x26;
00161 
00162     // Restore the saved calibration data from the configuration
00163     virtual void restoreCalibration(Config &config);
00164 
00165     // Begin calibration    
00166     virtual void beginCalibration();
00167     
00168     // End calibration
00169     virtual void endCalibration(Config &config);
00170   
00171 protected:
00172     // I2C read/write
00173     uint8_t readReg(uint8_t regAddr);
00174     void writeReg(uint8_t regAddr, uint8_t data);
00175 
00176     // I2C interface to device
00177     BitBangI2C i2c;
00178     
00179     // IR LED current setting (from configuration)
00180     int iredCurrent;
00181     
00182     // sample timer
00183     Timer sampleTimer;
00184 
00185     // time (from Timer t) of start of last range sample
00186     uint32_t tSampleStart;
00187     
00188     // last raw proximity reading
00189     uint16_t lastProxCount;
00190     
00191     // flag: calibration is in progress
00192     bool calibrating;
00193         
00194     // minimum and maximum observed proximity counts during calibration
00195     uint16_t minProxCount;
00196     uint16_t maxProxCount;
00197     
00198     // proximity count observed at "park" position during calibration
00199     uint16_t parkProxCount;
00200 
00201     // Calculate the scaling factor for count -> distance conversions.
00202     // This uses the data collected during calibration to figure the
00203     // conversion factors.
00204     void calcScalingFactor();
00205     
00206     // DC Offset for converting from count to distance.  Per the Vishay
00207     // application notes, the sensor brightness signal contains a fixed
00208     // component that comes from a combination of physical factors such
00209     // as internal reflections, ambient light, ADC artifacts, and sensor
00210     // noise.  This must be subtracted from the reported proximity count
00211     // to get a measure of the actual reflected brightness level.  The
00212     // DC offset is a function of the overall setup, so it has to be
00213     // determined through calibration.
00214     int dcOffset;
00215 
00216     // Scaling factor and offset for converting from count to distance.
00217     // We calculate these based on the counts collected at known points
00218     // during calibration.
00219     float scalingFactor;
00220     float scalingOffset;
00221 };
00222 
00223 #endif // _VCNL4010_H_