Mirror with some correction
Dependencies: mbed FastIO FastPWM USBDevice
VCNL4010/VCNL4010.h@116:7a67265d7c19, 2021-10-01 (annotated)
- Committer:
- arnoz
- Date:
- Fri Oct 01 08:19:46 2021 +0000
- Revision:
- 116:7a67265d7c19
- Parent:
- 114:c2410d2cfaf1
- Correct information regarding your last merge
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
mjr | 111:42dc75fbe623 | 1 | // Vishay VCNL4010 Proximity Sensor interface |
mjr | 111:42dc75fbe623 | 2 | // |
mjr | 111:42dc75fbe623 | 3 | // OVERVIEW |
mjr | 111:42dc75fbe623 | 4 | // |
mjr | 111:42dc75fbe623 | 5 | // The Vishay VCNL4010 is an IR proximity sensor chip with an I2C interface |
mjr | 111:42dc75fbe623 | 6 | // and an effective operating range of about 2mm to 100mm. The VirtuaPin |
mjr | 113:7330439f2ffc | 7 | // plunger kit v3 is based on this sensor, so it's in fairly widespread |
mjr | 113:7330439f2ffc | 8 | // use among pin cab builders. |
mjr | 111:42dc75fbe623 | 9 | // |
mjr | 113:7330439f2ffc | 10 | // Like all proximity sensors, this chip is designed for sensing *proxmity*, |
mjr | 113:7330439f2ffc | 11 | // not distance. Proximity sensing is answering the yes/no question "is |
mjr | 113:7330439f2ffc | 12 | // there an object within my detection range?". However, many types of |
mjr | 113:7330439f2ffc | 13 | // proximity sensors, including this one, don't answer the proximity |
mjr | 113:7330439f2ffc | 14 | // question directly, but rather report an analog quantity that correlates |
mjr | 113:7330439f2ffc | 15 | // with distance to a detected object. For a proximity reading, we'd |
mjr | 113:7330439f2ffc | 16 | // compare that analog quantitiy to a threshold level to determine whether |
mjr | 113:7330439f2ffc | 17 | // or not an object is in range. But we can "abuse" the analog reading by |
mjr | 113:7330439f2ffc | 18 | // interpreting it as a continuous value instead of merely being on one side |
mjr | 113:7330439f2ffc | 19 | // or the other of a cutoff point. Since the analog value varies (in some |
mjr | 113:7330439f2ffc | 20 | // way) with the distance to the detected object, we can re-interpret the |
mjr | 113:7330439f2ffc | 21 | // reported analog quantity as a continuous distance value, as long as we |
mjr | 113:7330439f2ffc | 22 | // know the mathematical relationship between the distance to the target |
mjr | 113:7330439f2ffc | 23 | // and the sensor's reported reading. |
mjr | 111:42dc75fbe623 | 24 | // |
mjr | 113:7330439f2ffc | 25 | // In the case of IR proximity sensors like this one, the analog quantity |
mjr | 113:7330439f2ffc | 26 | // that the sensor reports is the intensity of light reflected from the |
mjr | 113:7330439f2ffc | 27 | // target object. This type of sensor projects an IR light source in the |
mjr | 113:7330439f2ffc | 28 | // direction of the target, and measures the intensity of light reflected |
mjr | 114:c2410d2cfaf1 | 29 | // from the target. At the basic physics level, the apparent brightness |
mjr | 114:c2410d2cfaf1 | 30 | // of a point light source varies with the inverse of the square of the |
mjr | 114:c2410d2cfaf1 | 31 | // distance between source and observer. Our setup isn't quite as simple |
mjr | 114:c2410d2cfaf1 | 32 | // as that idealized model: we have a reflector instead of a point source, |
mjr | 114:c2410d2cfaf1 | 33 | // so there are other factors that could vary by distance, especially the |
mjr | 114:c2410d2cfaf1 | 34 | // cross-section of the target (the portion of the target within the spread |
mjr | 114:c2410d2cfaf1 | 35 | // angle of the source light). These other factors might not even have |
mjr | 114:c2410d2cfaf1 | 36 | // simple polynomial relationships to distance. Even so, the general idea |
mjr | 114:c2410d2cfaf1 | 37 | // that the reflected brightness varies inversely with the distance should |
mjr | 114:c2410d2cfaf1 | 38 | // hold, at least within a limited distance range. Assuming we can hold |
mjr | 114:c2410d2cfaf1 | 39 | // all of the other quantities constant (brightness of the light source, |
mjr | 114:c2410d2cfaf1 | 40 | // reflectivity of the target, etc), then, the reflected brightness should |
mjr | 114:c2410d2cfaf1 | 41 | // serve as a proxy for the distance. It's obviously not possible to |
mjr | 113:7330439f2ffc | 42 | // compute an absolute distance (in millimeters from the sensor, say) from |
mjr | 114:c2410d2cfaf1 | 43 | // the brightness reading alone, since that depends upon knowing the actual |
mjr | 114:c2410d2cfaf1 | 44 | // values of all of the other quantities that assuming are held constant. |
mjr | 114:c2410d2cfaf1 | 45 | // But we don't have to know those variables individually; we can roll them |
mjr | 114:c2410d2cfaf1 | 46 | // into a proportionality constant that we can compute via calibration, by |
mjr | 114:c2410d2cfaf1 | 47 | // taking brightness readings at known distances and then solving for the |
mjr | 114:c2410d2cfaf1 | 48 | // constant. |
mjr | 114:c2410d2cfaf1 | 49 | // |
mjr | 114:c2410d2cfaf1 | 50 | // The VCNL4010 data sheet doesn't provide any specifications of how the |
mjr | 114:c2410d2cfaf1 | 51 | // brightness reading relates to distance - it can't, for all of the reasons |
mjr | 114:c2410d2cfaf1 | 52 | // mentioned above. But it at least provides a rough plot of readings taken |
mjr | 114:c2410d2cfaf1 | 53 | // for a particular test configuration. That plot suggests that the power |
mjr | 114:c2410d2cfaf1 | 54 | // law observed in the test configuration is roughly |
mjr | 114:c2410d2cfaf1 | 55 | // |
mjr | 114:c2410d2cfaf1 | 56 | // Brightness ~ 1/Distance^3.2 |
mjr | 114:c2410d2cfaf1 | 57 | // |
mjr | 114:c2410d2cfaf1 | 58 | // over most of the range from 10mm to 100mm. In my own testing, the best |
mjr | 114:c2410d2cfaf1 | 59 | // fit was more like 1/r^2. I suspect that the power law depends quite a |
mjr | 114:c2410d2cfaf1 | 60 | // lot on the size and shape of the reflector. Vishay's test setup uses a |
mjr | 114:c2410d2cfaf1 | 61 | // 3cm x 3cm square reflector, whereas my plunger test rig has about a 2.5cm |
mjr | 114:c2410d2cfaf1 | 62 | // circular reflector, which is about as big as you can make the reflector |
mjr | 114:c2410d2cfaf1 | 63 | // for a pin cab plunger without conflicting with the flipper switches. I |
mjr | 114:c2410d2cfaf1 | 64 | // don't know if the difference in observed power law is due to the |
mjr | 114:c2410d2cfaf1 | 65 | // reflector geometry or other factors. We might need to revisit the |
mjr | 114:c2410d2cfaf1 | 66 | // formula I used for the distance conversion as we gain experience from |
mjr | 114:c2410d2cfaf1 | 67 | // different users setting up the sensor. A possible future enhancement |
mjr | 114:c2410d2cfaf1 | 68 | // would be to do a more detailed calibration as follows: |
mjr | 114:c2410d2cfaf1 | 69 | // |
mjr | 114:c2410d2cfaf1 | 70 | // - Ask the user to pull back the plunger slowly at a very steady rate, |
mjr | 114:c2410d2cfaf1 | 71 | // maybe 3-5 seconds per pull |
mjr | 114:c2410d2cfaf1 | 72 | // |
mjr | 114:c2410d2cfaf1 | 73 | // - Collect frequent readings throughout this period, say every 50ms |
mjr | 114:c2410d2cfaf1 | 74 | // (so around 60-100 readings per pull) |
mjr | 114:c2410d2cfaf1 | 75 | // |
mjr | 114:c2410d2cfaf1 | 76 | // - Do a best-fit calculation on the data to solve for the exponent X |
mjr | 114:c2410d2cfaf1 | 77 | // and proportionality constant C in (Brightness = C/Distance^X), |
mjr | 114:c2410d2cfaf1 | 78 | // assuming that the distances are uniformly distributed over the |
mjr | 114:c2410d2cfaf1 | 79 | // pull range (because the user was pulling at constant speed). |
mjr | 114:c2410d2cfaf1 | 80 | // |
mjr | 114:c2410d2cfaf1 | 81 | // - Save the exponent as config.plunger.cal.raw1 (perhaps as a 4.4 bit |
mjr | 114:c2410d2cfaf1 | 82 | // fixed-point value, such that X = raw1/16.0f) |
mjr | 114:c2410d2cfaf1 | 83 | // |
mjr | 114:c2410d2cfaf1 | 84 | // Alternatively, we could let the user provide the power law exponent |
mjr | 114:c2410d2cfaf1 | 85 | // manually, as a configuration parameter, and add a Config Tool command |
mjr | 114:c2410d2cfaf1 | 86 | // to collect the same calibration data described above and do the best-fit |
mjr | 114:c2410d2cfaf1 | 87 | // analysis. It might be preferable to do it that way - the user could |
mjr | 114:c2410d2cfaf1 | 88 | // experiment with different values manually to find one that provides the |
mjr | 114:c2410d2cfaf1 | 89 | // best subjective feel, and they could use the analysis tool to suggest |
mjr | 114:c2410d2cfaf1 | 90 | // the best value based on data collection. The reason I like the manual |
mjr | 114:c2410d2cfaf1 | 91 | // approach is that the actual distance/brightness relationship isn't as |
mjr | 114:c2410d2cfaf1 | 92 | // uniform as a simple power law, so even the best-fit power law will be |
mjr | 114:c2410d2cfaf1 | 93 | // imperfect. What looks best subjectively might not match the mathematical |
mjr | 114:c2410d2cfaf1 | 94 | // best fit, because divergence from the fit might be more noticeable to |
mjr | 114:c2410d2cfaf1 | 95 | // the eye in some regions than in others. A manual fit would allow the |
mjr | 114:c2410d2cfaf1 | 96 | // user to tweak it until it looked best in the whatever region they find |
mjr | 114:c2410d2cfaf1 | 97 | // most noticeable. |
mjr | 111:42dc75fbe623 | 98 | // |
mjr | 111:42dc75fbe623 | 99 | // |
mjr | 111:42dc75fbe623 | 100 | // SENSOR INTIALIZATION |
mjr | 111:42dc75fbe623 | 101 | // |
mjr | 111:42dc75fbe623 | 102 | // Initializing the VCNL4010 from the software side is just a matter of |
mjr | 111:42dc75fbe623 | 103 | // programming the registers that control sample rate and sample collection |
mjr | 111:42dc75fbe623 | 104 | // policy. From experience with other plunger sensors, we know that good |
mjr | 113:7330439f2ffc | 105 | // plunger motion tracking without aliasing requires samples at very short |
mjr | 113:7330439f2ffc | 106 | // intervals - ideally 2.5ms or less The VCNL4010's fastest sampling rate |
mjr | 113:7330439f2ffc | 107 | // for proximity is 250 samples/second, or 4ms intervals, so it's not quite |
mjr | 113:7330439f2ffc | 108 | // as fast as we'd like. But it's still usable. In addition, we'll use the |
mjr | 113:7330439f2ffc | 109 | // "on demand" mode to collect readings (rather than its interrupt mode), |
mjr | 113:7330439f2ffc | 110 | // since the upper software layers poll the sensor by design. |
mjr | 111:42dc75fbe623 | 111 | // |
mjr | 111:42dc75fbe623 | 112 | // |
mjr | 111:42dc75fbe623 | 113 | // I2C INFORMATION |
mjr | 111:42dc75fbe623 | 114 | // |
mjr | 113:7330439f2ffc | 115 | // This chip has an I2C interface with an immutable I2C address of 0010 011x. |
mjr | 113:7330439f2ffc | 116 | // In 8-bit address terms, this is 0x26 write, 0x27 read; or, if you prefer |
mjr | 113:7330439f2ffc | 117 | // the 7-bit notation, it's address 0x13. |
mjr | 111:42dc75fbe623 | 118 | // |
mjr | 111:42dc75fbe623 | 119 | |
mjr | 111:42dc75fbe623 | 120 | #ifndef _VCNL4010_H_ |
mjr | 111:42dc75fbe623 | 121 | #define _VCNL4010_H_ |
mjr | 111:42dc75fbe623 | 122 | |
mjr | 111:42dc75fbe623 | 123 | #include "mbed.h" |
mjr | 111:42dc75fbe623 | 124 | #include "BitBangI2C.h" |
mjr | 113:7330439f2ffc | 125 | #include "config.h" |
mjr | 111:42dc75fbe623 | 126 | |
mjr | 111:42dc75fbe623 | 127 | class VCNL4010 |
mjr | 111:42dc75fbe623 | 128 | { |
mjr | 111:42dc75fbe623 | 129 | public: |
mjr | 111:42dc75fbe623 | 130 | // Set up the interface with the given I2C pins. |
mjr | 111:42dc75fbe623 | 131 | // |
mjr | 111:42dc75fbe623 | 132 | // If 'internalPullups' is true, we'll set the I2C SDA/SCL pins to |
mjr | 111:42dc75fbe623 | 133 | // enable the internal pullup resistors. Set this to false if you're |
mjr | 111:42dc75fbe623 | 134 | // using your own external pullup resistors on the lines. External |
mjr | 111:42dc75fbe623 | 135 | // pullups are better if you're attaching more than one device to the |
mjr | 111:42dc75fbe623 | 136 | // same physical I2C bus; the internal pullups are fine if there's only |
mjr | 111:42dc75fbe623 | 137 | // one I2C device (in this case the VCNL4010) connected to these pins. |
mjr | 113:7330439f2ffc | 138 | VCNL4010(PinName sda, PinName scl, bool internalPullups, int iredCurrent); |
mjr | 111:42dc75fbe623 | 139 | |
mjr | 111:42dc75fbe623 | 140 | // initialize the chip |
mjr | 111:42dc75fbe623 | 141 | void init(); |
mjr | 111:42dc75fbe623 | 142 | |
mjr | 111:42dc75fbe623 | 143 | // Start a distance reading, returning immediately without waiting |
mjr | 111:42dc75fbe623 | 144 | // for the reading to finish. The caller can poll for the finished |
mjr | 113:7330439f2ffc | 145 | // reading via proxReady(). |
mjr | 111:42dc75fbe623 | 146 | void startProxReading(); |
mjr | 111:42dc75fbe623 | 147 | |
mjr | 111:42dc75fbe623 | 148 | // Is a proximity reading ready? |
mjr | 111:42dc75fbe623 | 149 | bool proxReady(); |
mjr | 113:7330439f2ffc | 150 | |
mjr | 113:7330439f2ffc | 151 | // Read the proximity value. Note that this returns the "brightness" |
mjr | 113:7330439f2ffc | 152 | // value from the sensor, not a distance reading. This must be converted |
mjr | 113:7330439f2ffc | 153 | // into a distance reading via countToDistance(). |
mjr | 113:7330439f2ffc | 154 | int getProx(int &proxCount, uint32_t &tMid, uint32_t &dt, uint32_t timeout_us); |
mjr | 111:42dc75fbe623 | 155 | |
mjr | 113:7330439f2ffc | 156 | // convert from raw sensor count values to distance units |
mjr | 113:7330439f2ffc | 157 | int countToDistance(int count); |
mjr | 113:7330439f2ffc | 158 | |
mjr | 111:42dc75fbe623 | 159 | // This chip has a fixed I2C address of 0x26 write, 0x27 read |
mjr | 111:42dc75fbe623 | 160 | static const uint8_t I2C_ADDR = 0x26; |
mjr | 113:7330439f2ffc | 161 | |
mjr | 113:7330439f2ffc | 162 | // Restore the saved calibration data from the configuration |
mjr | 113:7330439f2ffc | 163 | virtual void restoreCalibration(Config &config); |
mjr | 113:7330439f2ffc | 164 | |
mjr | 113:7330439f2ffc | 165 | // Begin calibration |
mjr | 113:7330439f2ffc | 166 | virtual void beginCalibration(); |
mjr | 111:42dc75fbe623 | 167 | |
mjr | 113:7330439f2ffc | 168 | // End calibration |
mjr | 113:7330439f2ffc | 169 | virtual void endCalibration(Config &config); |
mjr | 113:7330439f2ffc | 170 | |
mjr | 111:42dc75fbe623 | 171 | protected: |
mjr | 111:42dc75fbe623 | 172 | // I2C read/write |
mjr | 111:42dc75fbe623 | 173 | uint8_t readReg(uint8_t regAddr); |
mjr | 111:42dc75fbe623 | 174 | void writeReg(uint8_t regAddr, uint8_t data); |
mjr | 111:42dc75fbe623 | 175 | |
mjr | 111:42dc75fbe623 | 176 | // I2C interface to device |
mjr | 111:42dc75fbe623 | 177 | BitBangI2C i2c; |
mjr | 111:42dc75fbe623 | 178 | |
mjr | 113:7330439f2ffc | 179 | // IR LED current setting (from configuration) |
mjr | 113:7330439f2ffc | 180 | int iredCurrent; |
mjr | 113:7330439f2ffc | 181 | |
mjr | 111:42dc75fbe623 | 182 | // sample timer |
mjr | 111:42dc75fbe623 | 183 | Timer sampleTimer; |
mjr | 111:42dc75fbe623 | 184 | |
mjr | 111:42dc75fbe623 | 185 | // time (from Timer t) of start of last range sample |
mjr | 111:42dc75fbe623 | 186 | uint32_t tSampleStart; |
mjr | 113:7330439f2ffc | 187 | |
mjr | 113:7330439f2ffc | 188 | // last raw proximity reading |
mjr | 113:7330439f2ffc | 189 | uint16_t lastProxCount; |
mjr | 113:7330439f2ffc | 190 | |
mjr | 113:7330439f2ffc | 191 | // flag: calibration is in progress |
mjr | 113:7330439f2ffc | 192 | bool calibrating; |
mjr | 113:7330439f2ffc | 193 | |
mjr | 113:7330439f2ffc | 194 | // minimum and maximum observed proximity counts during calibration |
mjr | 113:7330439f2ffc | 195 | uint16_t minProxCount; |
mjr | 113:7330439f2ffc | 196 | uint16_t maxProxCount; |
mjr | 113:7330439f2ffc | 197 | |
mjr | 113:7330439f2ffc | 198 | // proximity count observed at "park" position during calibration |
mjr | 113:7330439f2ffc | 199 | uint16_t parkProxCount; |
mjr | 113:7330439f2ffc | 200 | |
mjr | 113:7330439f2ffc | 201 | // Calculate the scaling factor for count -> distance conversions. |
mjr | 113:7330439f2ffc | 202 | // This uses the data collected during calibration to figure the |
mjr | 113:7330439f2ffc | 203 | // conversion factors. |
mjr | 113:7330439f2ffc | 204 | void calcScalingFactor(); |
mjr | 113:7330439f2ffc | 205 | |
mjr | 113:7330439f2ffc | 206 | // DC Offset for converting from count to distance. Per the Vishay |
mjr | 113:7330439f2ffc | 207 | // application notes, the sensor brightness signal contains a fixed |
mjr | 113:7330439f2ffc | 208 | // component that comes from a combination of physical factors such |
mjr | 113:7330439f2ffc | 209 | // as internal reflections, ambient light, ADC artifacts, and sensor |
mjr | 113:7330439f2ffc | 210 | // noise. This must be subtracted from the reported proximity count |
mjr | 113:7330439f2ffc | 211 | // to get a measure of the actual reflected brightness level. The |
mjr | 113:7330439f2ffc | 212 | // DC offset is a function of the overall setup, so it has to be |
mjr | 113:7330439f2ffc | 213 | // determined through calibration. |
mjr | 113:7330439f2ffc | 214 | int dcOffset; |
mjr | 113:7330439f2ffc | 215 | |
mjr | 113:7330439f2ffc | 216 | // Scaling factor and offset for converting from count to distance. |
mjr | 113:7330439f2ffc | 217 | // We calculate these based on the counts collected at known points |
mjr | 113:7330439f2ffc | 218 | // during calibration. |
mjr | 113:7330439f2ffc | 219 | float scalingFactor; |
mjr | 113:7330439f2ffc | 220 | float scalingOffset; |
mjr | 111:42dc75fbe623 | 221 | }; |
mjr | 111:42dc75fbe623 | 222 | |
mjr | 111:42dc75fbe623 | 223 | #endif // _VCNL4010_H_ |