Mirror with some correction

Dependencies:   mbed FastIO FastPWM USBDevice

VCNL4010/VCNL4010.h

Committer:
arnoz
Date:
2021-10-01
Revision:
116:7a67265d7c19
Parent:
114:c2410d2cfaf1

File content as of revision 116:7a67265d7c19:

// Vishay VCNL4010 Proximity Sensor interface
//
// OVERVIEW
//
// The Vishay VCNL4010 is an IR proximity sensor chip with an I2C interface
// and an effective operating range of about 2mm to 100mm.  The VirtuaPin
// plunger kit v3 is based on this sensor, so it's in fairly widespread
// use among pin cab builders.
//
// Like all proximity sensors, this chip is designed for sensing *proxmity*,
// not distance.  Proximity sensing is answering the yes/no question "is
// there an object within my detection range?".  However, many types of
// proximity sensors, including this one, don't answer the proximity
// question directly, but rather report an analog quantity that correlates
// with distance to a detected object.  For a proximity reading, we'd
// compare that analog quantitiy to a threshold level to determine whether
// or not an object is in range.  But we can "abuse" the analog reading by
// interpreting it as a continuous value instead of merely being on one side
// or the other of a cutoff point.  Since the analog value varies (in some
// way) with the distance to the detected object, we can re-interpret the
// reported analog quantity as a continuous distance value, as long as we
// know the mathematical relationship between the distance to the target
// and the sensor's reported reading.
//
// In the case of IR proximity sensors like this one, the analog quantity
// that the sensor reports is the intensity of light reflected from the
// target object.  This type of sensor projects an IR light source in the
// direction of the target, and measures the intensity of light reflected
// from the target.  At the basic physics level, the apparent brightness
// of a point light source varies with the inverse of the square of the
// distance between source and observer.  Our setup isn't quite as simple
// as that idealized model: we have a reflector instead of a point source,
// so there are other factors that could vary by distance, especially the
// cross-section of the target (the portion of the target within the spread
// angle of the source light).  These other factors might not even have
// simple polynomial relationships to distance.  Even so, the general idea
// that the reflected brightness varies inversely with the distance should
// hold, at least within a limited distance range.  Assuming we can hold
// all of the other quantities constant (brightness of the light source,
// reflectivity of the target, etc), then, the reflected brightness should
// serve as a proxy for the distance.  It's obviously not possible to
// compute an absolute distance (in millimeters from the sensor, say) from
// the brightness reading alone, since that depends upon knowing the actual
// values of all of the other quantities that assuming are held constant.
// But we don't have to know those variables individually; we can roll them
// into a proportionality constant that we can compute via calibration, by
// taking brightness readings at known distances and then solving for the
// constant.
//
// The VCNL4010 data sheet doesn't provide any specifications of how the
// brightness reading relates to distance - it can't, for all of the reasons
// mentioned above.  But it at least provides a rough plot of readings taken
// for a particular test configuration.  That plot suggests that the power
// law observed in the test configuration is roughly
//
//   Brightness ~ 1/Distance^3.2
// 
// over most of the range from 10mm to 100mm.  In my own testing, the best
// fit was more like 1/r^2.  I suspect that the power law depends quite a
// lot on the size and shape of the reflector.  Vishay's test setup uses a
// 3cm x 3cm square reflector, whereas my plunger test rig has about a 2.5cm
// circular reflector, which is about as big as you can make the reflector
// for a pin cab plunger without conflicting with the flipper switches.  I
// don't know if the difference in observed power law is due to the
// reflector geometry or other factors.  We might need to revisit the
// formula I used for the distance conversion as we gain experience from
// different users setting up the sensor.  A possible future enhancement
// would be to do a more detailed calibration as follows:
//
//   - Ask the user to pull back the plunger slowly at a very steady rate,
//     maybe 3-5 seconds per pull
//
//   - Collect frequent readings throughout this period, say every 50ms
//     (so around 60-100 readings per pull)
//
//   - Do a best-fit calculation on the data to solve for the exponent X
//     and proportionality constant C in (Brightness = C/Distance^X),
//     assuming that the distances are uniformly distributed over the
//     pull range (because the user was pulling at constant speed).
//
//   - Save the exponent as config.plunger.cal.raw1 (perhaps as a 4.4 bit
//     fixed-point value, such that X = raw1/16.0f)
//
// Alternatively, we could let the user provide the power law exponent
// manually, as a configuration parameter, and add a Config Tool command
// to collect the same calibration data described above and do the best-fit
// analysis.  It might be preferable to do it that way - the user could
// experiment with different values manually to find one that provides the
// best subjective feel, and they could use the analysis tool to suggest
// the best value based on data collection.  The reason I like the manual
// approach is that the actual distance/brightness relationship isn't as
// uniform as a simple power law, so even the best-fit power law will be
// imperfect.  What looks best subjectively might not match the mathematical
// best fit, because divergence from the fit might be more noticeable to
// the eye in some regions than in others.  A manual fit would allow the
// user to tweak it until it looked best in the whatever region they find
// most noticeable.
//
//
// SENSOR INTIALIZATION
//
// Initializing the VCNL4010 from the software side is just a matter of
// programming the registers that control sample rate and sample collection
// policy.  From experience with other plunger sensors, we know that good
// plunger motion tracking without aliasing requires samples at very short
// intervals - ideally 2.5ms or less  The VCNL4010's fastest sampling rate
// for proximity is 250 samples/second, or 4ms intervals, so it's not quite
// as fast as we'd like.  But it's still usable.  In addition, we'll use the
// "on demand" mode to collect readings (rather than its interrupt mode),
// since the upper software layers poll the sensor by design.
//
//
// I2C INFORMATION
//
// This chip has an I2C interface with an immutable I2C address of 0010 011x.
// In 8-bit address terms, this is 0x26 write, 0x27 read; or, if you prefer
// the 7-bit notation, it's address 0x13.
//

#ifndef _VCNL4010_H_
#define _VCNL4010_H_

#include "mbed.h"
#include "BitBangI2C.h"
#include "config.h"

class VCNL4010
{
public:
    // Set up the interface with the given I2C pins.
    //
    // If 'internalPullups' is true, we'll set the I2C SDA/SCL pins to 
    // enable the internal pullup resistors.  Set this to false if you're
    // using your own external pullup resistors on the lines.  External
    // pullups are better if you're attaching more than one device to the
    // same physical I2C bus; the internal pullups are fine if there's only
    // one I2C device (in this case the VCNL4010) connected to these pins.
    VCNL4010(PinName sda, PinName scl, bool internalPullups, int iredCurrent);
    
    // initialize the chip
    void init();

    // Start a distance reading, returning immediately without waiting
    // for the reading to finish.  The caller can poll for the finished
    // reading via proxReady().
    void startProxReading();
    
    // Is a proximity reading ready?
    bool proxReady();
    
    // Read the proximity value.  Note that this returns the "brightness"
    // value from the sensor, not a distance reading.  This must be converted
    // into a distance reading via countToDistance().
    int getProx(int &proxCount, uint32_t &tMid, uint32_t &dt, uint32_t timeout_us);

    // convert from raw sensor count values to distance units
    int countToDistance(int count);
        
    // This chip has a fixed I2C address of 0x26 write, 0x27 read
    static const uint8_t I2C_ADDR = 0x26;

    // Restore the saved calibration data from the configuration
    virtual void restoreCalibration(Config &config);

    // Begin calibration    
    virtual void beginCalibration();
    
    // End calibration
    virtual void endCalibration(Config &config);
  
protected:
    // I2C read/write
    uint8_t readReg(uint8_t regAddr);
    void writeReg(uint8_t regAddr, uint8_t data);

    // I2C interface to device
    BitBangI2C i2c;
    
    // IR LED current setting (from configuration)
    int iredCurrent;
    
    // sample timer
    Timer sampleTimer;

    // time (from Timer t) of start of last range sample
    uint32_t tSampleStart;
    
    // last raw proximity reading
    uint16_t lastProxCount;
    
    // flag: calibration is in progress
    bool calibrating;
        
    // minimum and maximum observed proximity counts during calibration
    uint16_t minProxCount;
    uint16_t maxProxCount;
    
    // proximity count observed at "park" position during calibration
    uint16_t parkProxCount;

    // Calculate the scaling factor for count -> distance conversions.
    // This uses the data collected during calibration to figure the
    // conversion factors.
    void calcScalingFactor();
    
    // DC Offset for converting from count to distance.  Per the Vishay
    // application notes, the sensor brightness signal contains a fixed
    // component that comes from a combination of physical factors such
    // as internal reflections, ambient light, ADC artifacts, and sensor
    // noise.  This must be subtracted from the reported proximity count
    // to get a measure of the actual reflected brightness level.  The
    // DC offset is a function of the overall setup, so it has to be
    // determined through calibration.
    int dcOffset;

    // Scaling factor and offset for converting from count to distance.
    // We calculate these based on the counts collected at known points
    // during calibration.
    float scalingFactor;
    float scalingOffset;
};

#endif // _VCNL4010_H_