Mirror with some correction

Dependencies:   mbed FastIO FastPWM USBDevice

Committer:
arnoz
Date:
Fri Oct 01 08:19:46 2021 +0000
Revision:
116:7a67265d7c19
Parent:
106:e9e3b46132c1
- Correct information regarding your last merge

Who changed what in which revision?

UserRevisionLine numberNew contents of line
mjr 100:1ff35c07217c 1 // Plunger sensor implementation for rotary absolute encoders
mjr 100:1ff35c07217c 2 //
mjr 100:1ff35c07217c 3 // This implements the plunger interfaces for rotary absolute encoders. A
mjr 106:e9e3b46132c1 4 // rotary encoder measures the angle of a rotating shaft. An absolute encoder
mjr 106:e9e3b46132c1 5 // is one where the microcontroller can ask the sensor for its current angular
mjr 106:e9e3b46132c1 6 // position at any time. (As opposed to incremental encoders, which don't have
mjr 106:e9e3b46132c1 7 // any notion of their current position, but can only signal the host on each
mjr 106:e9e3b46132c1 8 // change in position.)
mjr 106:e9e3b46132c1 9 //
mjr 106:e9e3b46132c1 10 //
mjr 106:e9e3b46132c1 11 // For plunger sensing, we can convert the plunger's linear motion into angular
mjr 106:e9e3b46132c1 12 // motion using a mechanical link between the plunger rod and a rotating shaft
mjr 106:e9e3b46132c1 13 // positioned at a fixed point, somewhere nearby, but away from the plunger's
mjr 106:e9e3b46132c1 14 // axis of motion:
mjr 100:1ff35c07217c 15 //
mjr 100:1ff35c07217c 16 // =X=======================|=== <- plunger, X = connector attachment point
mjr 100:1ff35c07217c 17 // \
mjr 100:1ff35c07217c 18 // \ <- connector between plunger and shaft
mjr 100:1ff35c07217c 19 // \
mjr 100:1ff35c07217c 20 // * <- rotating shaft, at a fixed position
mjr 100:1ff35c07217c 21 //
mjr 100:1ff35c07217c 22 // As the plunger moves, the angle of the connector relative to the fixed
mjr 106:e9e3b46132c1 23 // shaft position changes in a predictable way, so we can infer the plunger's
mjr 106:e9e3b46132c1 24 // linear position at any given time by measuring the current rotational
mjr 106:e9e3b46132c1 25 // angle of the shaft.
mjr 100:1ff35c07217c 26 //
mjr 106:e9e3b46132c1 27 // The mechanical diagram above is, obviously, simplified for ASCII art's sake.
mjr 100:1ff35c07217c 28 // What's not shown is that the distance between the rotating shaft and the
mjr 100:1ff35c07217c 29 // "X" connection point on the plunger varies as the plunger moves, so the
mjr 100:1ff35c07217c 30 // mechanical linkage requires some way to accommodate that changing length.
mjr 106:e9e3b46132c1 31 // If the connector is a rigid rod, it has to be able to slide at one or
mjr 106:e9e3b46132c1 32 // the other connection points. Alternatively, rather than using a rigid
mjr 106:e9e3b46132c1 33 // linkage, we can use a spring or elastic band. We leave these details up
mjr 106:e9e3b46132c1 34 // to the mechanical design, since the software isn't affected by that, as
mjr 106:e9e3b46132c1 35 // long as the basic relationship between linear and angular motion as shown
mjr 106:e9e3b46132c1 36 // in the diagram is achieved.
mjr 100:1ff35c07217c 37 //
mjr 100:1ff35c07217c 38 //
mjr 100:1ff35c07217c 39 // Translating the angle to a linear position
mjr 100:1ff35c07217c 40 //
mjr 100:1ff35c07217c 41 // There are two complications to translating the angular reading back to
mjr 100:1ff35c07217c 42 // a linear plunger position.
mjr 100:1ff35c07217c 43 //
mjr 106:e9e3b46132c1 44 // 1. We have to consider the sensor's zero point to be arbitrary, because
mjr 106:e9e3b46132c1 45 // these sorts of sensors don't typically give the user a way to align the
mjr 106:e9e3b46132c1 46 // zero point at a desired physical position. The zero point will just be
mjr 106:e9e3b46132c1 47 // wherever it ends up after installation. The zero point could easily end
mjr 106:e9e3b46132c1 48 // up being somewhere in the middle of the plunger's travel range, which
mjr 106:e9e3b46132c1 49 // means that readings might "wrap" - e.g., we might see a series of readings
mjr 106:e9e3b46132c1 50 // when the plunger is moving in one direction like this: 4050, 4070, 4090,
mjr 106:e9e3b46132c1 51 // 14, 34 (note how we "wrapped" past some maximum angle reading for the
mjr 106:e9e3b46132c1 52 // sensor and went back to zero, then continued from there).
mjr 100:1ff35c07217c 53 //
mjr 100:1ff35c07217c 54 // To deal with this, we have to make a couple of assumptions:
mjr 100:1ff35c07217c 55 //
mjr 100:1ff35c07217c 56 // - The park position is at about 1/6 of the overall travel range
mjr 100:1ff35c07217c 57 // - The total angular travel range is less than one full revolution
mjr 100:1ff35c07217c 58 //
mjr 100:1ff35c07217c 59 // With those assumptions in hand, we can bias the raw readings to the
mjr 100:1ff35c07217c 60 // park position, and then take them modulo the raw scale. That will
mjr 100:1ff35c07217c 61 // ensure that readings wrap properly, regardless of where the raw zero
mjr 100:1ff35c07217c 62 // point lies.
mjr 100:1ff35c07217c 63 //
mjr 103:dec22cd65b2a 64 // 2. Going back to the original diagram, you can see that there's some
mjr 103:dec22cd65b2a 65 // trigonometry required to interpret the sensor's angular reading as a
mjr 103:dec22cd65b2a 66 // linear position on the plunger axis, which is of course what we need
mjr 103:dec22cd65b2a 67 // to report to the PC software.
mjr 103:dec22cd65b2a 68 //
mjr 103:dec22cd65b2a 69 // Let's use the vertical line between the plunger and the rotation point
mjr 103:dec22cd65b2a 70 // as the zero-degree reference point. To figure the plunger position,
mjr 100:1ff35c07217c 71 // we need to figure the difference between the raw angle reading and the
mjr 100:1ff35c07217c 72 // zero-degree point; call this theta. Let L be the position of the plunger
mjr 100:1ff35c07217c 73 // relative to the vertical reference point, let D be the length of the
mjr 100:1ff35c07217c 74 // vertical reference point line, and let H by the distance from the rotation
mjr 100:1ff35c07217c 75 // point to the plunger connection point. This is a right triangle with
mjr 100:1ff35c07217c 76 // hypotenuse H and sides L and D. D is a constant, because the rotation
mjr 100:1ff35c07217c 77 // point never moves, and the plunger never moves vertically. Thus we can
mjr 100:1ff35c07217c 78 // calculate D = H*cos(theta) and L = H*sin(theta). D is a constant, so
mjr 100:1ff35c07217c 79 // we can figure H = D/cos(theta) hence L = D*sin(theta)/cos(theta) or
mjr 100:1ff35c07217c 80 // D*tan(theta). If we wanted to know the true position in real-world
mjr 100:1ff35c07217c 81 // units, we'd have to know D, but only need arbitrary linear units, so
mjr 100:1ff35c07217c 82 // we can choose whatever value for D we find convenient: in particular,
mjr 100:1ff35c07217c 83 // a value that gives us the desired range and resolution for the final
mjr 100:1ff35c07217c 84 // result.
mjr 100:1ff35c07217c 85 //
mjr 103:dec22cd65b2a 86 // Note that the tangent diverges at +/-90 degrees, but that's okay,
mjr 103:dec22cd65b2a 87 // because the mechanical setup we've described is inherently constrained
mjr 103:dec22cd65b2a 88 // to stay well within those limits. This would even be true for an
mjr 103:dec22cd65b2a 89 // arbitrarily long range of motion along the travel axis, but we don't
mjr 103:dec22cd65b2a 90 // even have to worry about that since we have such a well-defined range
mjr 103:dec22cd65b2a 91 // of travel (of only about 3") to track.
mjr 100:1ff35c07217c 92 //
mjr 100:1ff35c07217c 93 // There's still one big piece missing here: we somehow have to know where
mjr 100:1ff35c07217c 94 // that vertical zero point lies. That's something we can only learn by
mjr 100:1ff35c07217c 95 // calibration. Unfortunately, we don't have a good way to detect this
mjr 100:1ff35c07217c 96 // directly. We *could* ask the user to look inside the cabinet and press
mjr 103:dec22cd65b2a 97 // a button when the needle is straight up, but that seems too cumbersome
mjr 103:dec22cd65b2a 98 // for the user, not to mention terribly imprecise. So we'll approach this
mjr 103:dec22cd65b2a 99 // from the other direction: we'll assume a particular placement of the
mjr 103:dec22cd65b2a 100 // rotation point relative to the travel range, and we'll provide
mjr 103:dec22cd65b2a 101 // installation instructions to achieve that assumed alignment.
mjr 100:1ff35c07217c 102 //
mjr 100:1ff35c07217c 103 // The full range we actually have after calibration consists of the park
mjr 100:1ff35c07217c 104 // position and the maximum retracted position. We could in principle also
mjr 100:1ff35c07217c 105 // calibrate the maximum forward position, but that can't be read as reliably
mjr 100:1ff35c07217c 106 // as the other two, because the barrel spring makes it difficult for the
mjr 100:1ff35c07217c 107 // user to be sure they've pushed it all the way forward. Since we can
mjr 100:1ff35c07217c 108 // extract the information we need from the park and max retract positions,
mjr 100:1ff35c07217c 109 // it's better to rely on those alone and not ask for information that the
mjr 100:1ff35c07217c 110 // user can't as easily provide. Given these positions, AND the assumption
mjr 100:1ff35c07217c 111 // that the rotation point is at the midpoint of the plunger travel range,
mjr 103:dec22cd65b2a 112 // we can do some grungy trig work to come up with a formula for the angle
mjr 103:dec22cd65b2a 113 // between the park position and the vertical:
mjr 100:1ff35c07217c 114 //
mjr 100:1ff35c07217c 115 // let C1 = 1 1/32" (distance from midpoint to park),
mjr 100:1ff35c07217c 116 // C2 = 1 17/32" (distance from midpoint to max retract),
mjr 100:1ff35c07217c 117 // C = C2/C1 = 1.48484849,
mjr 100:1ff35c07217c 118 // alpha = angle from park to vertical,
mjr 100:1ff35c07217c 119 // beta = angle from max retract to vertical
mjr 100:1ff35c07217c 120 // theta = alpha + beta = angle from park to max retract, known from calibration,
mjr 100:1ff35c07217c 121 // T = tan(theta);
mjr 100:1ff35c07217c 122 //
mjr 100:1ff35c07217c 123 // then
mjr 100:1ff35c07217c 124 // alpha = atan(sqrt(4*T*T*C + C^2 + 2*C + 1) - C - 1)/(2*T*C))
mjr 100:1ff35c07217c 125 //
mjr 103:dec22cd65b2a 126 // Did I mention this was grungy? At any rate, everything going into that
mjr 103:dec22cd65b2a 127 // last equation is either constant or known from the calibration, so we
mjr 103:dec22cd65b2a 128 // can pre-compute alpha and store it after each calibration operation.
mjr 103:dec22cd65b2a 129 // And once we've computed alpha, we can easily translate an angle reading
mjr 103:dec22cd65b2a 130 // from the sensor to an angle relative to the vertical, which we can plug
mjr 103:dec22cd65b2a 131 // into D*tan(angle) to convert to a linear position on the plunger axis.
mjr 103:dec22cd65b2a 132 //
mjr 103:dec22cd65b2a 133 // The final step is to scale that linear position into joystick reporting
mjr 103:dec22cd65b2a 134 // units. Those units are arbitrary, so we don't have to relate this to any
mjr 103:dec22cd65b2a 135 // real-world lengths. We can simply figure a scaling factor that maps the
mjr 103:dec22cd65b2a 136 // physical range to map to roughly the full range of the joystick units.
mjr 100:1ff35c07217c 137 //
mjr 100:1ff35c07217c 138 // If you're wondering how we derived that ugly formula, read on. Start
mjr 100:1ff35c07217c 139 // with the basic relationships D*tan(alpha) = C1 and D*tan(beta) = C2.
mjr 100:1ff35c07217c 140 // This lets us write tan(beta) in terms of tan(alpha) as
mjr 100:1ff35c07217c 141 // C2/C1*tan(alpha) = C*tan(alpha). We can combine this with an identity
mjr 100:1ff35c07217c 142 // for the tan of a sum of angles:
mjr 100:1ff35c07217c 143 //
mjr 100:1ff35c07217c 144 // tan(alpha + beta) = (tan(alpha) + tan(beta))/(1 - tan(alpha)*tan(beta))
mjr 100:1ff35c07217c 145 //
mjr 100:1ff35c07217c 146 // to obtain:
mjr 100:1ff35c07217c 147 //
mjr 100:1ff35c07217c 148 // tan(theta) = tan(alpha + beta) = (1 + C*tan(alpha))/(1 - C*tan^2(alpha))
mjr 100:1ff35c07217c 149 //
mjr 100:1ff35c07217c 150 // Everything here except alpha is known, so we now have a quadratic equation
mjr 100:1ff35c07217c 151 // for tan(alpha). We can solve that by cranking through the normal algorithm
mjr 100:1ff35c07217c 152 // for solving a quadratic equation, arriving at the solution above.
mjr 100:1ff35c07217c 153 //
mjr 100:1ff35c07217c 154 //
mjr 100:1ff35c07217c 155 // Choosing an install position
mjr 100:1ff35c07217c 156 //
mjr 100:1ff35c07217c 157 // There are two competing factors in choosing the optimal "D". On the one
mjr 100:1ff35c07217c 158 // hand, you'd like D to be as large as possible, to maximum linearity of the
mjr 100:1ff35c07217c 159 // tan function used to translate angle to linear position. Higher linearity
mjr 100:1ff35c07217c 160 // gives us greater immunity to variations in the precise centering of the
mjr 103:dec22cd65b2a 161 // rotation axis in the plunger travel range. tan() is pretty linear (that
mjr 103:dec22cd65b2a 162 // is, tan(theta) is approximately proportional to theta) for small theta,
mjr 103:dec22cd65b2a 163 // within about +/- 30 degrees. On the other hand, you'd like D to be as
mjr 103:dec22cd65b2a 164 // small as possible so that we get the largest overall angle range. Our
mjr 103:dec22cd65b2a 165 // sensor has a fixed angular resolution, so the more of the overall circle
mjr 103:dec22cd65b2a 166 // we use, the more sensor increments we have over the range, and thus the
mjr 103:dec22cd65b2a 167 // better effective linear resolution.
mjr 100:1ff35c07217c 168 //
mjr 100:1ff35c07217c 169 // Let's do some calculations for various "D" values (vertical distance
mjr 103:dec22cd65b2a 170 // between rotation point and plunger rod). We'll base our calculations
mjr 103:dec22cd65b2a 171 // on the AEAT-6012 sensor's 12-bit angular resolution.
mjr 100:1ff35c07217c 172 //
mjr 100:1ff35c07217c 173 // D theta(max) eff dpi theta(park)
mjr 100:1ff35c07217c 174 // -----------------------------------------------
mjr 100:1ff35c07217c 175 // 1 17/32" 45 deg 341 34 deg
mjr 100:1ff35c07217c 176 // 2" 37 deg 280 27 deg
mjr 100:1ff35c07217c 177 // 2 21/32" 30 deg 228 21 deg
mjr 100:1ff35c07217c 178 // 3 1/4" 25 deg 190 17 deg
mjr 100:1ff35c07217c 179 // 4 3/16" 20 deg 152 14 deg
mjr 100:1ff35c07217c 180 //
mjr 100:1ff35c07217c 181 // I'd consider 50 dpi to be the minimum for acceptable performance, 100 dpi
mjr 100:1ff35c07217c 182 // to be excellent, and anything above 300 dpi to be diminishing returns. So
mjr 100:1ff35c07217c 183 // for a 12-bit sensor, 2" looks like the sweet spot. It doesn't take us far
mjr 100:1ff35c07217c 184 // outside of the +/-30 deg zone of tan() linearity, and it achieves almost
mjr 100:1ff35c07217c 185 // 300 dpi of effective linear resolution. I'd stop there are not try to
mjr 100:1ff35c07217c 186 // push the angular resolution higher with a shorter D; with the 45 deg
mjr 100:1ff35c07217c 187 // theta(max) at D = 1-17/32", we'd get a lovely DPI level of 341, but at
mjr 100:1ff35c07217c 188 // the cost of getting pretty non-linear around the ends of the plunger
mjr 100:1ff35c07217c 189 // travel. Our math corrects for the non-linearity, but the more of that
mjr 100:1ff35c07217c 190 // correction we need, the more sensitive the whole contraption becomes to
mjr 100:1ff35c07217c 191 // getting the sensor positioning exactly right. The closer we can stay to
mjr 100:1ff35c07217c 192 // the linear approximation, the more tolerant we are of inexact sensor
mjr 100:1ff35c07217c 193 // positioning.
mjr 100:1ff35c07217c 194 //
mjr 100:1ff35c07217c 195 //
mjr 100:1ff35c07217c 196 // Supported sensors
mjr 100:1ff35c07217c 197 //
mjr 100:1ff35c07217c 198 // * AEAT-6012-A06. This is a magnetic absolute encoder with 12-bit
mjr 100:1ff35c07217c 199 // resolution. It linearly encodes one full (360 degree) rotation in
mjr 100:1ff35c07217c 200 // 4096 increments, so each increment represents 360/4096 = .088 degrees.
mjr 100:1ff35c07217c 201 //
mjr 100:1ff35c07217c 202 // The base class doesn't actually care much about the sensor type; all it
mjr 100:1ff35c07217c 203 // needs from the sensor is an angle reading represented on an arbitrary
mjr 100:1ff35c07217c 204 // linear scale. ("Linear" in the angle, so that one increment represents
mjr 100:1ff35c07217c 205 // a fixed number of degrees of arc. The full scale can represent one full
mjr 100:1ff35c07217c 206 // turn but doesn't have to, as long as the scale is linear over the range
mjr 100:1ff35c07217c 207 // covered.) To add new sensor types, you just need to add the code to
mjr 100:1ff35c07217c 208 // interface to the physical sensor and return its reading on an arbitrary
mjr 100:1ff35c07217c 209 // linear scale.
mjr 100:1ff35c07217c 210
mjr 100:1ff35c07217c 211 #ifndef _ROTARYSENSOR_H_
mjr 100:1ff35c07217c 212 #define _ROTARYSENSOR_H_
mjr 100:1ff35c07217c 213
mjr 100:1ff35c07217c 214 #include "FastInterruptIn.h"
mjr 100:1ff35c07217c 215 #include "AEAT6012.h"
mjr 100:1ff35c07217c 216
mjr 100:1ff35c07217c 217 // The conversion from raw sensor reading to linear position involves a
mjr 100:1ff35c07217c 218 // bunch of translations to different scales and unit systems. To help
mjr 100:1ff35c07217c 219 // keep things straight, let's give each scale a name:
mjr 100:1ff35c07217c 220 //
mjr 100:1ff35c07217c 221 // * "Raw" refers to the readings directly from the sensor. These are
mjr 103:dec22cd65b2a 222 // unsigned ints in the range 0..maxRawAngle, and represent angles in a
mjr 102:41d49e78c253 223 // unit system where one increment equals 360/maxRawAngle degrees. The
mjr 100:1ff35c07217c 224 // zero point is arbitrary, determined by the physical orientation
mjr 100:1ff35c07217c 225 // of the sensor.
mjr 100:1ff35c07217c 226 //
mjr 100:1ff35c07217c 227 // * "Biased" refers to angular units with a zero point equal to the
mjr 103:dec22cd65b2a 228 // park position. This uses the same unit size as the "raw" system, but
mjr 100:1ff35c07217c 229 // the zero point is adjusted so that 0 always means the park position.
mjr 100:1ff35c07217c 230 // Negative values are forward of the park position. This scale is
mjr 100:1ff35c07217c 231 // also adjusted for wrapping, by ensuring that the value lies in the
mjr 100:1ff35c07217c 232 // range -(maximum forward excursion) to +(scale max - max fwd excursion).
mjr 100:1ff35c07217c 233 // Any values below or above the range are bumped up or down (respectively)
mjr 100:1ff35c07217c 234 // to wrap them back into the range.
mjr 100:1ff35c07217c 235 //
mjr 100:1ff35c07217c 236 // * "Linear" refers to the final linear results, in joystick units, on
mjr 100:1ff35c07217c 237 // the abstract integer scale from 0..65535 used by the generic plunger
mjr 100:1ff35c07217c 238 // base class.
mjr 100:1ff35c07217c 239 //
mjr 100:1ff35c07217c 240 class PlungerSensorRotary: public PlungerSensor
mjr 100:1ff35c07217c 241 {
mjr 100:1ff35c07217c 242 public:
mjr 102:41d49e78c253 243 PlungerSensorRotary(int maxRawAngle, float radiansPerSensorUnit) :
mjr 100:1ff35c07217c 244 PlungerSensor(65535),
mjr 102:41d49e78c253 245 maxRawAngle(maxRawAngle),
mjr 100:1ff35c07217c 246 radiansPerSensorUnit(radiansPerSensorUnit)
mjr 100:1ff35c07217c 247 {
mjr 100:1ff35c07217c 248 // start our sample timer with an arbitrary zero point of now
mjr 100:1ff35c07217c 249 timer.start();
mjr 100:1ff35c07217c 250
mjr 100:1ff35c07217c 251 // clear the timing statistics
mjr 100:1ff35c07217c 252 nReads = 0;
mjr 100:1ff35c07217c 253 totalReadTime = 0;
mjr 100:1ff35c07217c 254
mjr 100:1ff35c07217c 255 // Pre-calculate the maximum forward excursion distance, in raw
mjr 100:1ff35c07217c 256 // units. For our reference mechanical setup with "D" in a likely
mjr 100:1ff35c07217c 257 // range, theta(max) is always about 10 degrees higher than
mjr 100:1ff35c07217c 258 // theta(park). 10 degrees is about 1/36 of the overall circle,
mjr 100:1ff35c07217c 259 // which is the same as 1/36 of the sensor scale. To be
mjr 100:1ff35c07217c 260 // conservative, allow for about 3X that, so allow 1/12 of scale
mjr 100:1ff35c07217c 261 // as the maximum forward excursion. For wrapping purposes, we'll
mjr 100:1ff35c07217c 262 // consider any reading outside of the range from -(excursion)
mjr 102:41d49e78c253 263 // to +(maxRawAngle - excursion) to be wrapped.
mjr 102:41d49e78c253 264 maxForwardExcursionRaw = maxRawAngle/12;
mjr 100:1ff35c07217c 265
mjr 100:1ff35c07217c 266 // reset the calibration counters
mjr 100:1ff35c07217c 267 biasedMinObserved = biasedMaxObserved = 0;
mjr 100:1ff35c07217c 268 }
mjr 100:1ff35c07217c 269
mjr 100:1ff35c07217c 270 // Restore the saved calibration at startup
mjr 100:1ff35c07217c 271 virtual void restoreCalibration(Config &cfg)
mjr 100:1ff35c07217c 272 {
mjr 100:1ff35c07217c 273 // only proceed if there's calibration data to retrieve
mjr 100:1ff35c07217c 274 if (cfg.plunger.cal.calibrated)
mjr 100:1ff35c07217c 275 {
mjr 100:1ff35c07217c 276 // we store the raw park angle in raw0
mjr 100:1ff35c07217c 277 rawParkAngle = cfg.plunger.cal.raw0;
mjr 100:1ff35c07217c 278
mjr 100:1ff35c07217c 279 // we store biased max angle in raw1
mjr 100:1ff35c07217c 280 biasedMax = cfg.plunger.cal.raw1;
mjr 100:1ff35c07217c 281 }
mjr 100:1ff35c07217c 282 else
mjr 100:1ff35c07217c 283 {
mjr 100:1ff35c07217c 284 // Use the current sensor reading as the initial guess at the
mjr 100:1ff35c07217c 285 // park position. The system is usually powered up with the
mjr 100:1ff35c07217c 286 // plunger at the neutral position, so this is a good guess in
mjr 100:1ff35c07217c 287 // most cases. If the plunger has been calibrated, we'll restore
mjr 100:1ff35c07217c 288 // the better guess when we restore the configuration later on in
mjr 100:1ff35c07217c 289 // the initialization process.
mjr 100:1ff35c07217c 290 rawParkAngle = 0;
mjr 100:1ff35c07217c 291 readSensor(rawParkAngle);
mjr 100:1ff35c07217c 292
mjr 100:1ff35c07217c 293 // Set an initial wild guess at a range equal to +/-35 degrees.
mjr 100:1ff35c07217c 294 // Note that this is in the "biased" coordinate system - raw
mjr 100:1ff35c07217c 295 // units, but relative to the park angle. The park angle is
mjr 102:41d49e78c253 296 // about -25 degrees in this setup.
mjr 102:41d49e78c253 297 biasedMax = (35 + 25) * maxRawAngle/360;
mjr 100:1ff35c07217c 298 }
mjr 100:1ff35c07217c 299
mjr 100:1ff35c07217c 300 // recalculate the vertical angle
mjr 100:1ff35c07217c 301 updateAlpha();
mjr 100:1ff35c07217c 302 }
mjr 100:1ff35c07217c 303
mjr 100:1ff35c07217c 304 // Begin calibration
mjr 100:1ff35c07217c 305 virtual void beginCalibration(Config &)
mjr 100:1ff35c07217c 306 {
mjr 100:1ff35c07217c 307 // Calibration starts out with the plunger at the park position, so
mjr 100:1ff35c07217c 308 // we can take the current sensor reading to be the park position.
mjr 100:1ff35c07217c 309 rawParkAngle = 0;
mjr 100:1ff35c07217c 310 readSensor(rawParkAngle);
mjr 100:1ff35c07217c 311
mjr 100:1ff35c07217c 312 // Reset the observed calibration counters
mjr 100:1ff35c07217c 313 biasedMinObserved = biasedMaxObserved = 0;
mjr 100:1ff35c07217c 314 }
mjr 100:1ff35c07217c 315
mjr 100:1ff35c07217c 316 // End calibration
mjr 100:1ff35c07217c 317 virtual void endCalibration(Config &cfg)
mjr 100:1ff35c07217c 318 {
mjr 100:1ff35c07217c 319 // apply the observed maximum angle
mjr 100:1ff35c07217c 320 biasedMax = biasedMaxObserved;
mjr 100:1ff35c07217c 321
mjr 100:1ff35c07217c 322 // recalculate the vertical angle
mjr 100:1ff35c07217c 323 updateAlpha();
mjr 100:1ff35c07217c 324
mjr 100:1ff35c07217c 325 // save our raw configuration data
mjr 100:1ff35c07217c 326 cfg.plunger.cal.raw0 = static_cast<uint16_t>(rawParkAngle);
mjr 100:1ff35c07217c 327 cfg.plunger.cal.raw1 = static_cast<uint16_t>(biasedMax);
mjr 100:1ff35c07217c 328
mjr 100:1ff35c07217c 329 // Refigure the range for the generic code
mjr 100:1ff35c07217c 330 cfg.plunger.cal.min = biasedAngleToLinear(biasedMinObserved);
mjr 100:1ff35c07217c 331 cfg.plunger.cal.max = biasedAngleToLinear(biasedMaxObserved);
mjr 100:1ff35c07217c 332 cfg.plunger.cal.zero = biasedAngleToLinear(0);
mjr 100:1ff35c07217c 333 }
mjr 100:1ff35c07217c 334
mjr 100:1ff35c07217c 335 // figure the average scan time in microseconds
mjr 100:1ff35c07217c 336 virtual uint32_t getAvgScanTime()
mjr 100:1ff35c07217c 337 {
mjr 100:1ff35c07217c 338 return nReads == 0 ? 0 : static_cast<uint32_t>(totalReadTime / nReads);
mjr 100:1ff35c07217c 339 }
mjr 100:1ff35c07217c 340
mjr 100:1ff35c07217c 341 // read the sensor
mjr 100:1ff35c07217c 342 virtual bool readRaw(PlungerReading &r)
mjr 100:1ff35c07217c 343 {
mjr 100:1ff35c07217c 344 // note the starting time for the reading
mjr 100:1ff35c07217c 345 uint32_t t0 = timer.read_us();
mjr 100:1ff35c07217c 346
mjr 100:1ff35c07217c 347 // read the angular position
mjr 100:1ff35c07217c 348 int angle;
mjr 100:1ff35c07217c 349 if (!readSensor(angle))
mjr 100:1ff35c07217c 350 return false;
mjr 102:41d49e78c253 351
mjr 100:1ff35c07217c 352 // Refigure the angle relative to the raw park position. This
mjr 100:1ff35c07217c 353 // is the "biased" angle.
mjr 100:1ff35c07217c 354 angle -= rawParkAngle;
mjr 100:1ff35c07217c 355
mjr 100:1ff35c07217c 356 // Adjust for wrapping.
mjr 100:1ff35c07217c 357 //
mjr 100:1ff35c07217c 358 // An angular sensor reports the position on a circular scale, for
mjr 100:1ff35c07217c 359 // obvious reasons, so there's some point along the circle where the
mjr 100:1ff35c07217c 360 // angle is zero. One tick before that point reads as the maximum
mjr 100:1ff35c07217c 361 // angle on the scale, so we say that the scale "wraps" at that point.
mjr 100:1ff35c07217c 362 //
mjr 100:1ff35c07217c 363 // To correct for this, we can look to the layout of the mechanical
mjr 100:1ff35c07217c 364 // setup to constrain the values. Consider anything below the maximum
mjr 100:1ff35c07217c 365 // forward exclusion to be wrapped on the low side, and consider
mjr 100:1ff35c07217c 366 // anything outside of the complementary range on the high side to
mjr 100:1ff35c07217c 367 // be wrapped on the high side.
mjr 102:41d49e78c253 368 if (angle < -maxForwardExcursionRaw)
mjr 102:41d49e78c253 369 angle += maxRawAngle;
mjr 102:41d49e78c253 370 else if (angle >= maxRawAngle - maxForwardExcursionRaw)
mjr 102:41d49e78c253 371 angle -= maxRawAngle;
mjr 100:1ff35c07217c 372
mjr 100:1ff35c07217c 373 // Note if this is the highest/lowest observed reading on the biased
mjr 100:1ff35c07217c 374 // scale since the last calibration started.
mjr 100:1ff35c07217c 375 if (angle > biasedMaxObserved)
mjr 100:1ff35c07217c 376 biasedMaxObserved = angle;
mjr 100:1ff35c07217c 377 if (angle < biasedMinObserved)
mjr 100:1ff35c07217c 378 biasedMinObserved = angle;
mjr 100:1ff35c07217c 379
mjr 100:1ff35c07217c 380 // figure the linear result
mjr 100:1ff35c07217c 381 r.pos = biasedAngleToLinear(angle);
mjr 102:41d49e78c253 382
mjr 100:1ff35c07217c 383 // Set the timestamp on the reading to right now
mjr 100:1ff35c07217c 384 uint32_t now = timer.read_us();
mjr 100:1ff35c07217c 385 r.t = now;
mjr 100:1ff35c07217c 386
mjr 100:1ff35c07217c 387 // count the read statistics
mjr 100:1ff35c07217c 388 totalReadTime += now - t0;
mjr 100:1ff35c07217c 389 nReads += 1;
mjr 100:1ff35c07217c 390
mjr 100:1ff35c07217c 391 // success
mjr 100:1ff35c07217c 392 return true;
mjr 100:1ff35c07217c 393 }
mjr 100:1ff35c07217c 394
mjr 100:1ff35c07217c 395 private:
mjr 100:1ff35c07217c 396 // Read the underlying sensor - implemented by the hardware-specific
mjr 100:1ff35c07217c 397 // subclasses. Returns true on success, false if the sensor can't
mjr 100:1ff35c07217c 398 // be read. The angle is returned in raw sensor units.
mjr 100:1ff35c07217c 399 virtual bool readSensor(int &angle) = 0;
mjr 100:1ff35c07217c 400
mjr 100:1ff35c07217c 401 // Convert a biased angle value to a linear reading
mjr 100:1ff35c07217c 402 int biasedAngleToLinear(int angle)
mjr 100:1ff35c07217c 403 {
mjr 100:1ff35c07217c 404 // Translate to an angle relative to the vertical, in sensor units
mjr 102:41d49e78c253 405 float theta = static_cast<float>(angle)*radiansPerSensorUnit - alpha;
mjr 100:1ff35c07217c 406
mjr 102:41d49e78c253 407 // Calculate the linear position relative to the vertical. Zero
mjr 102:41d49e78c253 408 // is right at the intersection of the vertical line from the
mjr 102:41d49e78c253 409 // sensor rotation center to the plunger axis; positive numbers
mjr 102:41d49e78c253 410 // are behind the vertical (more retracted).
mjr 102:41d49e78c253 411 int linearPos = static_cast<int>(tanf(theta) * linearScaleFactor);
mjr 100:1ff35c07217c 412
mjr 102:41d49e78c253 413 // Finally, figure the offset. The vertical is the halfway point
mjr 102:41d49e78c253 414 // of the plunger motion, so we want to put it at half of the raw
mjr 102:41d49e78c253 415 // scale of 0..65535.
mjr 102:41d49e78c253 416 return linearPos + 32767;
mjr 100:1ff35c07217c 417 }
mjr 100:1ff35c07217c 418
mjr 100:1ff35c07217c 419 // Update the estimation of the vertical angle, based on the angle
mjr 100:1ff35c07217c 420 // between the park position and maximum retraction point.
mjr 100:1ff35c07217c 421 void updateAlpha()
mjr 100:1ff35c07217c 422 {
mjr 100:1ff35c07217c 423 // See the comments at the top of the file for details on this
mjr 100:1ff35c07217c 424 // formula. This figures the angle between the park position
mjr 100:1ff35c07217c 425 // and the vertical by applying the known constraints of the
mjr 100:1ff35c07217c 426 // mechanical setup: the known length of a standard plunger,
mjr 100:1ff35c07217c 427 // and the requirement that the rotation axis be placed at
mjr 100:1ff35c07217c 428 // roughly the midpoint of the plunger travel.
mjr 100:1ff35c07217c 429 const float C = 1.4848489f; // 1-17/32" / 1-1/32"
mjr 102:41d49e78c253 430 float maxInRadians = static_cast<float>(biasedMax) * radiansPerSensorUnit;
mjr 102:41d49e78c253 431 float T = tanf(maxInRadians);
mjr 102:41d49e78c253 432 alpha = atanf((sqrtf(4*T*T*C + C*C + 2*C + 1) - C - 1)/(2*T*C));
mjr 102:41d49e78c253 433
mjr 102:41d49e78c253 434 // While we're at it, figure the linear conversion factor. Alpha
mjr 102:41d49e78c253 435 // represents the angle from the park position to the midpoint,
mjr 102:41d49e78c253 436 // which in the real world represents about 31/32", or just less
mjr 102:41d49e78c253 437 // then 1/3 of the overall travel. We want to normalize this to
mjr 102:41d49e78c253 438 // the corresponding fraction of our 0..65535 abstract linear unit
mjr 102:41d49e78c253 439 // system. To avoid overflow, normalize to a slightly smaller
mjr 102:41d49e78c253 440 // scale.
mjr 100:1ff35c07217c 441 const float safeMax = 60000.0f;
mjr 102:41d49e78c253 442 const float alphaInLinearUnits = safeMax * .316327f; // 31/22" / 3-1/16"
mjr 102:41d49e78c253 443 linearScaleFactor = static_cast<int>(alphaInLinearUnits / tanf(alpha));
mjr 100:1ff35c07217c 444 }
mjr 100:1ff35c07217c 445
mjr 100:1ff35c07217c 446 // Maximum raw angular reading from the sensor. The sensor's readings
mjr 102:41d49e78c253 447 // will always be on a scale from 0..maxRawAngle.
mjr 102:41d49e78c253 448 int maxRawAngle;
mjr 100:1ff35c07217c 449
mjr 100:1ff35c07217c 450 // Radians per sensor unit. This is a constant for the sensor.
mjr 100:1ff35c07217c 451 float radiansPerSensorUnit;
mjr 100:1ff35c07217c 452
mjr 100:1ff35c07217c 453 // Pre-calculated value of the maximum forward excursion, in raw units.
mjr 102:41d49e78c253 454 int maxForwardExcursionRaw;
mjr 100:1ff35c07217c 455
mjr 100:1ff35c07217c 456 // Raw reading at the park position. We use this to handle "wrapping",
mjr 100:1ff35c07217c 457 // if the sensor's raw zero reading position is within the plunger travel
mjr 100:1ff35c07217c 458 // range. All readings are taken to be within
mjr 100:1ff35c07217c 459 int rawParkAngle;
mjr 100:1ff35c07217c 460
mjr 100:1ff35c07217c 461 // Biased maximum angle. This is the angle at the maximum retracted
mjr 100:1ff35c07217c 462 // position, in biased units (sensor units, relative to the park angle).
mjr 100:1ff35c07217c 463 int biasedMax;
mjr 100:1ff35c07217c 464
mjr 100:1ff35c07217c 465 // Mininum and maximum angle observed since last calibration start, on
mjr 100:1ff35c07217c 466 // the biased scale
mjr 100:1ff35c07217c 467 int biasedMinObserved;
mjr 100:1ff35c07217c 468 int biasedMaxObserved;
mjr 100:1ff35c07217c 469
mjr 100:1ff35c07217c 470 // The "alpha" angle - the angle between the park position and the
mjr 100:1ff35c07217c 471 // vertical line between the rotation axis and the plunger. This is
mjr 102:41d49e78c253 472 // represented in radians.
mjr 102:41d49e78c253 473 float alpha;
mjr 100:1ff35c07217c 474
mjr 100:1ff35c07217c 475 // The linear scaling factor, applied in our trig calculation from
mjr 100:1ff35c07217c 476 // angle to linear position. This corresponds to the distance from
mjr 100:1ff35c07217c 477 // the rotation center to the plunger rod, but since the linear result
mjr 100:1ff35c07217c 478 // is in abstract joystick units, this distance is likewise in abstract
mjr 100:1ff35c07217c 479 // units. The value isn't chosen to correspond to any real-world
mjr 100:1ff35c07217c 480 // distance units, but rather to yield a joystick result that takes
mjr 100:1ff35c07217c 481 // advantage of most of the available axis range, to minimize rounding
mjr 100:1ff35c07217c 482 // errors when converting between scales.
mjr 100:1ff35c07217c 483 float linearScaleFactor;
mjr 100:1ff35c07217c 484
mjr 100:1ff35c07217c 485 // timer for input timestamps and read timing measurements
mjr 100:1ff35c07217c 486 Timer timer;
mjr 100:1ff35c07217c 487
mjr 100:1ff35c07217c 488 // read timing statistics
mjr 100:1ff35c07217c 489 uint64_t totalReadTime;
mjr 100:1ff35c07217c 490 uint64_t nReads;
mjr 100:1ff35c07217c 491
mjr 100:1ff35c07217c 492 // Keep track of when calibration is in progress. The calibration
mjr 100:1ff35c07217c 493 // procedure is usually handled by the generic main loop code, but
mjr 100:1ff35c07217c 494 // in this case, we have to keep track of some of the raw sensor
mjr 100:1ff35c07217c 495 // data during calibration for our own internal purposes.
mjr 100:1ff35c07217c 496 bool calibrating;
mjr 100:1ff35c07217c 497 };
mjr 100:1ff35c07217c 498
mjr 100:1ff35c07217c 499 // Specialization for the AEAT-601X sensors
mjr 100:1ff35c07217c 500 template<int nDataBits> class PlungerSensorAEAT601X : public PlungerSensorRotary
mjr 100:1ff35c07217c 501 {
mjr 100:1ff35c07217c 502 public:
mjr 100:1ff35c07217c 503 PlungerSensorAEAT601X(PinName csPin, PinName clkPin, PinName doPin) :
mjr 100:1ff35c07217c 504 PlungerSensorRotary((1 << nDataBits) - 1, 6.283185f/((1 << nDataBits) - 1)),
mjr 100:1ff35c07217c 505 aeat(csPin, clkPin, doPin)
mjr 100:1ff35c07217c 506 {
mjr 100:1ff35c07217c 507 // Make sure the sensor has had time to finish initializing.
mjr 100:1ff35c07217c 508 // Power-up time (tCF) from the data sheet is 20ms for the 12-bit
mjr 100:1ff35c07217c 509 // version, 50ms for the 10-bit version.
mjr 100:1ff35c07217c 510 wait_ms(nDataBits == 12 ? 20 :
mjr 100:1ff35c07217c 511 nDataBits == 10 ? 50 :
mjr 100:1ff35c07217c 512 50);
mjr 100:1ff35c07217c 513 }
mjr 100:1ff35c07217c 514
mjr 100:1ff35c07217c 515 // read the angle
mjr 100:1ff35c07217c 516 virtual bool readSensor(int &angle)
mjr 100:1ff35c07217c 517 {
mjr 100:1ff35c07217c 518 angle = aeat.readAngle();
mjr 100:1ff35c07217c 519 return true;
mjr 100:1ff35c07217c 520 }
mjr 100:1ff35c07217c 521
mjr 100:1ff35c07217c 522 protected:
mjr 100:1ff35c07217c 523 // physical sensor interface
mjr 100:1ff35c07217c 524 AEAT601X<nDataBits> aeat;
mjr 100:1ff35c07217c 525 };
mjr 100:1ff35c07217c 526
mjr 100:1ff35c07217c 527 #endif